text: #!/usr/bin/env python
  
  import argparse
  import sys
  import os
  
  from subprocess import call
  
  SCRIPTS_DIR = os.path.dirname(os.path.realpath(__file__))
  PROJECTS_DIR = os.path.join(SCRIPTS_DIR, "projects")
  DEFAULT_LLVM_DIR = os.path.realpath(os.path.join(SCRIPTS_DIR,
                                                   os.path.pardir,
                                                   os.path.pardir,
                                                   os.path.pardir))
  
  
  def add(parser, args):
      import SATestAdd
      from ProjectMap import ProjectInfo
  
      if args.source == "git" and (args.origin == "" or args.commit == ""):
          parser.error(
              "Please provide both --origin and --commit if source is 'git'")
  
      if args.source != "git" and (args.origin != "" or args.commit != ""):
          parser.error("Options --origin and --commit don't make sense when "
                       "source is not 'git'")
  
      project = ProjectInfo(args.name[0], args.mode, args.source, args.origin,
                            args.commit)
  
      SATestAdd.add_new_project(project)
  
  
  def build(parser, args):
      import SATestBuild
  
      SATestBuild.VERBOSE = args.verbose
  
      projects = get_projects(parser, args)
      tester = SATestBuild.RegressionTester(args.jobs,
                                            projects,
                                            args.override_compiler,
                                            args.extra_analyzer_config,
                                            args.extra_checkers,
                                            args.regenerate,
                                            args.strictness)
      tests_passed = tester.test_all()
  
      if not tests_passed:
          sys.stderr.write("ERROR: Tests failed.\n")
          sys.exit(42)
  
  
  def compare(parser, args):
      import CmpRuns
  
      choices = [CmpRuns.HistogramType.RELATIVE.value,
                 CmpRuns.HistogramType.LOG_RELATIVE.value,
                 CmpRuns.HistogramType.ABSOLUTE.value]
  
      if args.histogram is not None and args.histogram not in choices:
          parser.error("Incorrect histogram type, available choices are {}"
                       .format(choices))
  
      dir_old = CmpRuns.ResultsDirectory(args.old[0], args.root_old)
      dir_new = CmpRuns.ResultsDirectory(args.new[0], args.root_new)
  
      CmpRuns.dump_scan_build_results_diff(dir_old, dir_new,
                                           show_stats=args.show_stats,
                                           stats_only=args.stats_only,
                                           histogram=args.histogram,
                                           verbose_log=args.verbose_log)
  
  
  def update(parser, args):
      import SATestUpdateDiffs
      from ProjectMap import ProjectMap
  
      project_map = ProjectMap()
      for project in project_map.projects:
          SATestUpdateDiffs.update_reference_results(project, args.git)
  
  
  def benchmark(parser, args):
      from SATestBenchmark import Benchmark
  
      projects = get_projects(parser, args)
      benchmark = Benchmark(projects, args.iterations, args.output)
      benchmark.run()
  
  
  def benchmark_compare(parser, args):
      import SATestBenchmark
      SATestBenchmark.compare(args.old, args.new, args.output)
  
  
  def get_projects(parser, args):
      from ProjectMap import ProjectMap, Size
  
      project_map = ProjectMap()
      projects = project_map.projects
  
      def filter_projects(projects, predicate, force=False):
          return [project.with_fields(enabled=(force or project.enabled) and
                                      predicate(project))
                  for project in projects]
  
      if args.projects:
          projects_arg = args.projects.split(",")
          available_projects = [project.name
                                for project in projects]
  
          # validate that given projects are present in the project map file
          for manual_project in projects_arg:
              if manual_project not in available_projects:
                  parser.error("Project '{project}' is not found in "
                               "the project map file. Available projects are "
                               "{all}.".format(project=manual_project,
                                               all=available_projects))
  
          projects = filter_projects(projects, lambda project:
                                     project.name in projects_arg,
                                     force=True)
  
      try:
          max_size = Size.from_str(args.max_size)
      except ValueError as e:
          parser.error("{}".format(e))
  
      projects = filter_projects(projects, lambda project:
                                 project.size <= max_size)
  
      return projects
  
  
  def docker(parser, args):
      if len(args.rest) > 0:
          if args.rest[0] != "--":
              parser.error("REST arguments should start with '--'")
          args.rest = args.rest[1:]
  
      if args.build_image:
          docker_build_image()
      elif args.shell:
          docker_shell(args)
      else:
          sys.exit(docker_run(args, ' '.join(args.rest)))
  
  
  def docker_build_image():
      sys.exit(call("docker build --tag satest-image {}".format(SCRIPTS_DIR),
                    shell=True))
  
  
  def docker_shell(args):
      try:
          # First we need to start the docker container in a waiting mode,
          # so it doesn't do anything, but most importantly keeps working
          # while the shell session is in progress.
          docker_run(args, "--wait", "--detach")
          # Since the docker container is running, we can actually connect to it
          call("docker exec -it satest bash", shell=True)
  
      except KeyboardInterrupt:
          pass
  
      finally:
          docker_cleanup()
  
  
  def docker_run(args, command, docker_args=""):
      try:
          return call("docker run --rm --name satest "
                      "-v {llvm}:/llvm-project "
                      "-v {build}:/build "
                      "-v {clang}:/analyzer "
                      "-v {scripts}:/scripts "
                      "-v {projects}:/projects "
                      "{docker_args} "
                      "satest-image:latest {command}"
                      .format(llvm=args.llvm_project_dir,
                              build=args.build_dir,
                              clang=args.clang_dir,
                              scripts=SCRIPTS_DIR,
                              projects=PROJECTS_DIR,
                              docker_args=docker_args,
                              command=command),
                      shell=True)
  
      except KeyboardInterrupt:
          docker_cleanup()
  
  
  def docker_cleanup():
      print("Please wait for docker to clean up")
      call("docker stop satest", shell=True)
  
  
  def main():
      parser = argparse.ArgumentParser()
      subparsers = parser.add_subparsers()
  
      # add subcommand
      add_parser = subparsers.add_parser(
          "add",
          help="Add a new project for the analyzer testing.")
      # TODO: Add an option not to build.
      # TODO: Set the path to the Repository directory.
      add_parser.add_argument("name", nargs=1, help="Name of the new project")
      add_parser.add_argument("--mode", action="store", default=1, type=int,
                              choices=[0, 1, 2],
                              help="Build mode: 0 for single file project, "
                              "1 for scan_build, "
                              "2 for single file c++11 project")
      add_parser.add_argument("--source", action="store", default="script",
                              choices=["script", "git", "zip"],
                              help="Source type of the new project: "
                              "'git' for getting from git "
                              "(please provide --origin and --commit), "
                              "'zip' for unpacking source from a zip file, "
                              "'script' for downloading source by running "
                              "a custom script")
      add_parser.add_argument("--origin", action="store", default="",
                              help="Origin link for a git repository")
      add_parser.add_argument("--commit", action="store", default="",
                              help="Git hash for a commit to checkout")
      add_parser.set_defaults(func=add)
  
      # build subcommand
      build_parser = subparsers.add_parser(
          "build",
          help="Build projects from the project map and compare results with "
          "the reference.")
      build_parser.add_argument("--strictness", dest="strictness",
                                type=int, default=0,
                                help="0 to fail on runtime errors, 1 to fail "
                                "when the number of found bugs are different "
                                "from the reference, 2 to fail on any "
                                "difference from the reference. Default is 0.")
      build_parser.add_argument("-r", dest="regenerate", action="store_true",
                                default=False,
                                help="Regenerate reference output.")
      build_parser.add_argument("--override-compiler", action="store_true",
                                default=False, help="Call scan-build with "
                                "--override-compiler option.")
      build_parser.add_argument("-j", "--jobs", dest="jobs",
                                type=int, default=0,
                                help="Number of projects to test concurrently")
      build_parser.add_argument("--extra-analyzer-config",
                                dest="extra_analyzer_config", type=str,
                                default="",
                                help="Arguments passed to to -analyzer-config")
      build_parser.add_argument("--extra-checkers",
                                dest="extra_checkers", type=str,
                                default="",
                                help="Extra checkers to enable")
      build_parser.add_argument("--projects", action="store", default="",
                                help="Comma-separated list of projects to test")
      build_parser.add_argument("--max-size", action="store", default=None,
                                help="Maximum size for the projects to test")
      build_parser.add_argument("-v", "--verbose", action="count", default=0)
      build_parser.set_defaults(func=build)
  
      # compare subcommand
      cmp_parser = subparsers.add_parser(
          "compare",
          help="Comparing two static analyzer runs in terms of "
          "reported warnings and execution time statistics.")
      cmp_parser.add_argument("--root-old", dest="root_old",
                              help="Prefix to ignore on source files for "
                              "OLD directory",
                              action="store", type=str, default="")
      cmp_parser.add_argument("--root-new", dest="root_new",
                              help="Prefix to ignore on source files for "
                              "NEW directory",
                              action="store", type=str, default="")
      cmp_parser.add_argument("--verbose-log", dest="verbose_log",
                              help="Write additional information to LOG "
                              "[default=None]",
                              action="store", type=str, default=None,
                              metavar="LOG")
      cmp_parser.add_argument("--stats-only", action="store_true",
                              dest="stats_only", default=False,
                              help="Only show statistics on reports")
      cmp_parser.add_argument("--show-stats", action="store_true",
                              dest="show_stats", default=False,
                              help="Show change in statistics")
      cmp_parser.add_argument("--histogram", action="store", default=None,
                              help="Show histogram of paths differences. "
                              "Requires matplotlib")
      cmp_parser.add_argument("old", nargs=1, help="Directory with old results")
      cmp_parser.add_argument("new", nargs=1, help="Directory with new results")
      cmp_parser.set_defaults(func=compare)
  
      # update subcommand
      upd_parser = subparsers.add_parser(
          "update",
          help="Update static analyzer reference results based on the previous "
          "run of SATest build. Assumes that SATest build was just run.")
      upd_parser.add_argument("--git", action="store_true",
                              help="Stage updated results using git.")
      upd_parser.set_defaults(func=update)
  
      # docker subcommand
      dock_parser = subparsers.add_parser(
          "docker",
          help="Run regression system in the docker.")
  
      dock_parser.add_argument("--build-image", action="store_true",
                               help="Build docker image for running tests.")
      dock_parser.add_argument("--shell", action="store_true",
                               help="Start a shell on docker.")
      dock_parser.add_argument("--llvm-project-dir", action="store",
                               default=DEFAULT_LLVM_DIR,
                               help="Path to LLVM source code. Defaults "
                               "to the repo where this script is located. ")
      dock_parser.add_argument("--build-dir", action="store", default="",
                               help="Path to a directory where docker should "
                               "build LLVM code.")
      dock_parser.add_argument("--clang-dir", action="store", default="",
                               help="Path to find/install LLVM installation.")
      dock_parser.add_argument("rest", nargs=argparse.REMAINDER, default=[],
                               help="Additionall args that will be forwarded "
                               "to the docker's entrypoint.")
      dock_parser.set_defaults(func=docker)
  
      # benchmark subcommand
      bench_parser = subparsers.add_parser(
          "benchmark",
          help="Run benchmarks by building a set of projects multiple times.")
  
      bench_parser.add_argument("-i", "--iterations", action="store",
                                type=int, default=20,
                                help="Number of iterations for building each "
                                "project.")
      bench_parser.add_argument("-o", "--output", action="store",
                                default="benchmark.csv",
                                help="Output csv file for the benchmark results")
      bench_parser.add_argument("--projects", action="store", default="",
                                help="Comma-separated list of projects to test")
      bench_parser.add_argument("--max-size", action="store", default=None,
                                help="Maximum size for the projects to test")
      bench_parser.set_defaults(func=benchmark)
  
      bench_subparsers = bench_parser.add_subparsers()
      bench_compare_parser = bench_subparsers.add_parser(
          "compare",
          help="Compare benchmark runs.")
      bench_compare_parser.add_argument("--old", action="store", required=True,
                                        help="Benchmark reference results to "
                                        "compare agains.")
      bench_compare_parser.add_argument("--new", action="store", required=True,
                                        help="New benchmark results to check.")
      bench_compare_parser.add_argument("-o", "--output",
                                        action="store", required=True,
                                        help="Output file for plots.")
      bench_compare_parser.set_defaults(func=benchmark_compare)
  
      args = parser.parse_args()
      args.func(parser, args)
  
  
  if __name__ == "__main__":
      main()
  