A simple command-line interface for the runexecutor module of BenchExec.

def main(argv=None):
    """
    A simple command-line interface for the runexecutor module of BenchExec.
    """
    if argv is None:
        argv = sys.argv

    # parse options
    parser = argparse.ArgumentParser(
        fromfile_prefix_chars='@',
        description=
        """Execute a command with resource limits and measurements.
           Command-line parameters can additionally be read from a file if file name prefixed with '@' is given as argument.
           Part of BenchExec: https://github.com/sosy-lab/benchexec/""")

    resource_args = parser.add_argument_group("optional arguments for resource limits")
    resource_args.add_argument("--memlimit", type=util.parse_memory_value, metavar="BYTES",
        help="memory limit in bytes")
    resource_args.add_argument("--timelimit", type=util.parse_timespan_value, metavar="SECONDS",
        help="CPU time limit in seconds")
    resource_args.add_argument("--softtimelimit", type=util.parse_timespan_value, metavar="SECONDS",
        help='"soft" CPU time limit in seconds (command will be send the TERM signal at this time)')
    resource_args.add_argument("--walltimelimit", type=util.parse_timespan_value, metavar="SECONDS",
        help='wall time limit in seconds (default is CPU time limit plus a few seconds)')
    resource_args.add_argument("--cores", type=util.parse_int_list, metavar="N,M-K",
        help="list of CPU cores to use")
    resource_args.add_argument("--memoryNodes", type=util.parse_int_list, metavar="N,M-K",
        help="list of memory nodes to use")

    io_args = parser.add_argument_group("optional arguments for run I/O")
    io_args.add_argument("--input", metavar="FILE",
        help="name of file used as stdin for command "
            "(default: /dev/null; use - for stdin passthrough)")
    io_args.add_argument("--output", default="output.log", metavar="FILE",
        help="name of file where command output (stdout and stderr) is written")
    io_args.add_argument("--maxOutputSize", type=util.parse_memory_value, metavar="BYTES",
        help="shrink output file to approximately this size if necessary "
            "(by removing lines from the middle of the output)")
    io_args.add_argument("--filesCountLimit", type=int, metavar="COUNT",
        help="maximum number of files the tool may write to (checked periodically, counts only files written in container mode or to temporary directories, only supported with --no-tmpfs)")
    io_args.add_argument("--filesSizeLimit", type=util.parse_memory_value, metavar="BYTES",
        help="maximum size of files the tool may write (checked periodically, counts only files written in container mode or to temporary directories, only supported with --no-tmpfs)")
    io_args.add_argument("--skip-cleanup", action="store_false", dest="cleanup",
        help="do not delete files created by the tool in temp directory")

    container_args = parser.add_argument_group("optional arguments for run container")
    container_on_args = container_args.add_mutually_exclusive_group()
    container_on_args.add_argument("--container", action='store_true',
        help="force isolation of run in container (future default starting with BenchExec 2.0)")
    container_on_args.add_argument("--no-container", action='store_true',
        help="disable use of containers for isolation of runs (current default)")
    containerexecutor.add_basic_container_args(container_args)
    containerexecutor.add_container_output_args(container_args)

    environment_args = parser.add_argument_group("optional arguments for run environment")
    environment_args.add_argument("--require-cgroup-subsystem", action="append", default=[], metavar="SUBSYSTEM",
        help="additional cgroup system that should be enabled for runs "
            "(may be specified multiple times)")
    environment_args.add_argument("--set-cgroup-value", action="append", dest="cgroup_values", default=[],
        metavar="SUBSYSTEM.OPTION=VALUE",
        help="additional cgroup values that should be set for runs (e.g., 'cpu.shares=1000')")
    environment_args.add_argument("--dir", metavar="DIR",
        help="working directory for executing the command (default is current directory)")
    environment_args.add_argument("--user", metavar="USER",
        help="execute tool under given user account (needs password-less sudo setup, "
            "not supported in combination with --container)")

    baseexecutor.add_basic_executor_options(parser)

    options = parser.parse_args(argv[1:])
    baseexecutor.handle_basic_executor_options(options, parser)

    if options.container:
        if options.user is not None:
            sys.exit("Cannot use --user in combination with --container.")
        container_options = containerexecutor.handle_basic_container_args(options, parser)
        container_output_options = containerexecutor.handle_container_output_args(options, parser)
        if container_options['container_tmpfs'] and (options.filesCountLimit or options.filesSizeLimit):
            parser.error("Files-count limit and files-size limit are not supported if tmpfs is used in container. Use --no-tmpfs to make these limits work or disable them (typically they are unnecessary if a tmpfs is used).")
    else:
        container_options = {}
        container_output_options = {}
        if options.user is not None:
            logging.warning(
                "Executing benchmarks at another user with --user is deprecated and may be removed in the future. "
                "Consider using the container mode instead for isolating runs "
                "(cf. https://github.com/sosy-lab/benchexec/issues/215).")
        elif not options.no_container:
            logging.warning(
                "Neither --container or --no-container was specified, "
                "not using containers for isolation of runs. "
                "Either specify --no-container to silence this warning, "
                "or specify --container to use containers for better isolation of runs "
                "(this will be the default starting with BenchExec 2.0). "
                "Please read https://github.com/sosy-lab/benchexec/blob/master/doc/container.md "
                "for more information.")

    # For integrating into some benchmarking frameworks,
    # there is a DEPRECATED special mode
    # where the first and only command-line argument is a serialized dict
    # with additional options
    env = {}
    if len(options.args) == 1 and options.args[0].startswith("{"):
        data = eval(options.args[0])
        options.args = data["args"]
        env = data.get("env", {})
        options.debug = data.get("debug", options.debug)
        if "maxLogfileSize" in data:
            try:
                options.maxOutputSize = int(data["maxLogfileSize"]) * _BYTE_FACTOR * _BYTE_FACTOR # MB to bytes
            except ValueError:
                options.maxOutputSize = util.parse_memory_value(data["maxLogfileSize"])

    if options.input == '-':
        stdin = sys.stdin
    elif options.input is not None:
        if options.input == options.output:
            parser.error("Input and output files cannot be the same.")
        try:
            stdin = open(options.input, 'rt')
        except IOError as e:
            parser.error(e)
    else:
        stdin = None

    cgroup_subsystems = set(options.require_cgroup_subsystem)
    cgroup_values = {}
    for arg in options.cgroup_values:
        try:
            key, value = arg.split("=", 1)
            subsystem, option = key.split(".", 1)
            if not subsystem or not option:
                raise ValueError()
        except ValueError:
            parser.error(
                'Cgroup value "{}" has invalid format, needs to be "subsystem.option=value".'
                    .format(arg))
        cgroup_values[(subsystem, option)] = value
        cgroup_subsystems.add(subsystem)

    executor = RunExecutor(user=options.user, cleanup_temp_dir=options.cleanup,
                           additional_cgroup_subsystems=list(cgroup_subsystems),
                           use_namespaces=options.container, **container_options)

    # ensure that process gets killed on interrupt/kill signal
    def signal_handler_kill(signum, frame):
        executor.stop()
    signal.signal(signal.SIGTERM, signal_handler_kill)
    signal.signal(signal.SIGINT,  signal_handler_kill)

    formatted_args = " ".join(map(util.escape_string_shell, options.args))
    logging.info('Starting command %s', formatted_args)
    if options.container and options.output_directory and options.result_files:
        logging.info('Writing output to %s and result files to %s',
                     util.escape_string_shell(options.output),
                     util.escape_string_shell(options.output_directory))
    else:
        logging.info('Writing output to %s', util.escape_string_shell(options.output))

    # actual run execution
    try:
        result = executor.execute_run(
                            args=options.args,
                            output_filename=options.output,
                            stdin=stdin,
                            hardtimelimit=options.timelimit,
                            softtimelimit=options.softtimelimit,
                            walltimelimit=options.walltimelimit,
                            cores=options.cores,
                            memlimit=options.memlimit,
                            memory_nodes=options.memoryNodes,
                            cgroupValues=cgroup_values,
                            environments=env,
                            workingDir=options.dir,
                            maxLogfileSize=options.maxOutputSize,
                            files_count_limit=options.filesCountLimit,
                            files_size_limit=options.filesSizeLimit,
                            **container_output_options)
    finally:
        if stdin:
            stdin.close()

    executor.check_for_new_files_in_home()

    # exit_code is a special number:
    exit_code = util.ProcessExitCode.from_raw(result['exitcode'])

    def print_optional_result(key, unit=''):
        if key in result:
            # avoid unicode literals such that the string can be parsed by Python 3.2
            print(key + "=" + str(result[key]).replace("'u", '') + unit)

    # output results
    print_optional_result('terminationreason')
    print("exitcode=" + str(exit_code.raw))
    if exit_code.value is not None:
        print("returnvalue=" + str(exit_code.value))
    if exit_code.signal is not None:
        print("exitsignal=" + str(exit_code.signal))
    print("walltime=" + str(result['walltime']) + "s")
    print("cputime=" + str(result['cputime']) + "s")
    for key in sorted(result.keys()):
        if key.startswith('cputime-'):
            print("{}={:.9f}s".format(key, result[key]))
    print_optional_result('memory')
    print_optional_result('blkio-read', 'B')
    print_optional_result('blkio-write', 'B')
    energy = intel_cpu_energy.format_energy_results(result.get('cpuenergy'))
    for energy_key, energy_value in energy.items():
        print('{}={}J'.format(energy_key, energy_value))