1ec727ea7Spatrick#!/usr/bin/env python 2ec727ea7Spatrick 3ec727ea7Spatrickimport argparse 4ec727ea7Spatrickimport sys 5ec727ea7Spatrickimport os 6ec727ea7Spatrick 7ec727ea7Spatrickfrom subprocess import call 8ec727ea7Spatrick 9ec727ea7SpatrickSCRIPTS_DIR = os.path.dirname(os.path.realpath(__file__)) 10ec727ea7SpatrickPROJECTS_DIR = os.path.join(SCRIPTS_DIR, "projects") 11ec727ea7SpatrickDEFAULT_LLVM_DIR = os.path.realpath(os.path.join(SCRIPTS_DIR, 12ec727ea7Spatrick os.path.pardir, 13ec727ea7Spatrick os.path.pardir, 14ec727ea7Spatrick os.path.pardir)) 15ec727ea7Spatrick 16ec727ea7Spatrick 17ec727ea7Spatrickdef add(parser, args): 18ec727ea7Spatrick import SATestAdd 19ec727ea7Spatrick from ProjectMap import ProjectInfo 20ec727ea7Spatrick 21ec727ea7Spatrick if args.source == "git" and (args.origin == "" or args.commit == ""): 22ec727ea7Spatrick parser.error( 23ec727ea7Spatrick "Please provide both --origin and --commit if source is 'git'") 24ec727ea7Spatrick 25ec727ea7Spatrick if args.source != "git" and (args.origin != "" or args.commit != ""): 26ec727ea7Spatrick parser.error("Options --origin and --commit don't make sense when " 27ec727ea7Spatrick "source is not 'git'") 28ec727ea7Spatrick 29ec727ea7Spatrick project = ProjectInfo(args.name[0], args.mode, args.source, args.origin, 30ec727ea7Spatrick args.commit) 31ec727ea7Spatrick 32ec727ea7Spatrick SATestAdd.add_new_project(project) 33ec727ea7Spatrick 34ec727ea7Spatrick 35ec727ea7Spatrickdef build(parser, args): 36ec727ea7Spatrick import SATestBuild 37ec727ea7Spatrick 38ec727ea7Spatrick SATestBuild.VERBOSE = args.verbose 39ec727ea7Spatrick 40a9ac8606Spatrick projects = get_projects(parser, args) 41ec727ea7Spatrick tester = SATestBuild.RegressionTester(args.jobs, 42ec727ea7Spatrick projects, 43ec727ea7Spatrick args.override_compiler, 44ec727ea7Spatrick args.extra_analyzer_config, 45*12c85518Srobert args.extra_checkers, 46ec727ea7Spatrick args.regenerate, 47ec727ea7Spatrick args.strictness) 48ec727ea7Spatrick tests_passed = tester.test_all() 49ec727ea7Spatrick 50ec727ea7Spatrick if not tests_passed: 51ec727ea7Spatrick sys.stderr.write("ERROR: Tests failed.\n") 52ec727ea7Spatrick sys.exit(42) 53ec727ea7Spatrick 54ec727ea7Spatrick 55ec727ea7Spatrickdef compare(parser, args): 56ec727ea7Spatrick import CmpRuns 57ec727ea7Spatrick 58ec727ea7Spatrick choices = [CmpRuns.HistogramType.RELATIVE.value, 59ec727ea7Spatrick CmpRuns.HistogramType.LOG_RELATIVE.value, 60ec727ea7Spatrick CmpRuns.HistogramType.ABSOLUTE.value] 61ec727ea7Spatrick 62ec727ea7Spatrick if args.histogram is not None and args.histogram not in choices: 63ec727ea7Spatrick parser.error("Incorrect histogram type, available choices are {}" 64ec727ea7Spatrick .format(choices)) 65ec727ea7Spatrick 66ec727ea7Spatrick dir_old = CmpRuns.ResultsDirectory(args.old[0], args.root_old) 67ec727ea7Spatrick dir_new = CmpRuns.ResultsDirectory(args.new[0], args.root_new) 68ec727ea7Spatrick 69ec727ea7Spatrick CmpRuns.dump_scan_build_results_diff(dir_old, dir_new, 70ec727ea7Spatrick show_stats=args.show_stats, 71ec727ea7Spatrick stats_only=args.stats_only, 72ec727ea7Spatrick histogram=args.histogram, 73ec727ea7Spatrick verbose_log=args.verbose_log) 74ec727ea7Spatrick 75ec727ea7Spatrick 76ec727ea7Spatrickdef update(parser, args): 77ec727ea7Spatrick import SATestUpdateDiffs 78ec727ea7Spatrick from ProjectMap import ProjectMap 79ec727ea7Spatrick 80ec727ea7Spatrick project_map = ProjectMap() 81ec727ea7Spatrick for project in project_map.projects: 82a9ac8606Spatrick SATestUpdateDiffs.update_reference_results(project, args.git) 83ec727ea7Spatrick 84ec727ea7Spatrick 85ec727ea7Spatrickdef benchmark(parser, args): 86ec727ea7Spatrick from SATestBenchmark import Benchmark 87ec727ea7Spatrick 88a9ac8606Spatrick projects = get_projects(parser, args) 89ec727ea7Spatrick benchmark = Benchmark(projects, args.iterations, args.output) 90ec727ea7Spatrick benchmark.run() 91ec727ea7Spatrick 92ec727ea7Spatrick 93ec727ea7Spatrickdef benchmark_compare(parser, args): 94ec727ea7Spatrick import SATestBenchmark 95ec727ea7Spatrick SATestBenchmark.compare(args.old, args.new, args.output) 96ec727ea7Spatrick 97ec727ea7Spatrick 98a9ac8606Spatrickdef get_projects(parser, args): 99a9ac8606Spatrick from ProjectMap import ProjectMap, Size 100ec727ea7Spatrick 101ec727ea7Spatrick project_map = ProjectMap() 102ec727ea7Spatrick projects = project_map.projects 103ec727ea7Spatrick 104a9ac8606Spatrick def filter_projects(projects, predicate, force=False): 105a9ac8606Spatrick return [project.with_fields(enabled=(force or project.enabled) and 106a9ac8606Spatrick predicate(project)) 107a9ac8606Spatrick for project in projects] 108a9ac8606Spatrick 109a9ac8606Spatrick if args.projects: 110a9ac8606Spatrick projects_arg = args.projects.split(",") 111ec727ea7Spatrick available_projects = [project.name 112ec727ea7Spatrick for project in projects] 113ec727ea7Spatrick 114ec727ea7Spatrick # validate that given projects are present in the project map file 115ec727ea7Spatrick for manual_project in projects_arg: 116ec727ea7Spatrick if manual_project not in available_projects: 117ec727ea7Spatrick parser.error("Project '{project}' is not found in " 118ec727ea7Spatrick "the project map file. Available projects are " 119ec727ea7Spatrick "{all}.".format(project=manual_project, 120ec727ea7Spatrick all=available_projects)) 121ec727ea7Spatrick 122a9ac8606Spatrick projects = filter_projects(projects, lambda project: 123a9ac8606Spatrick project.name in projects_arg, 124a9ac8606Spatrick force=True) 125a9ac8606Spatrick 126a9ac8606Spatrick try: 127a9ac8606Spatrick max_size = Size.from_str(args.max_size) 128a9ac8606Spatrick except ValueError as e: 129a9ac8606Spatrick parser.error("{}".format(e)) 130a9ac8606Spatrick 131a9ac8606Spatrick projects = filter_projects(projects, lambda project: 132a9ac8606Spatrick project.size <= max_size) 133ec727ea7Spatrick 134ec727ea7Spatrick return projects 135ec727ea7Spatrick 136ec727ea7Spatrick 137ec727ea7Spatrickdef docker(parser, args): 138ec727ea7Spatrick if len(args.rest) > 0: 139ec727ea7Spatrick if args.rest[0] != "--": 140ec727ea7Spatrick parser.error("REST arguments should start with '--'") 141ec727ea7Spatrick args.rest = args.rest[1:] 142ec727ea7Spatrick 143ec727ea7Spatrick if args.build_image: 144ec727ea7Spatrick docker_build_image() 145ec727ea7Spatrick elif args.shell: 146ec727ea7Spatrick docker_shell(args) 147ec727ea7Spatrick else: 148ec727ea7Spatrick sys.exit(docker_run(args, ' '.join(args.rest))) 149ec727ea7Spatrick 150ec727ea7Spatrick 151ec727ea7Spatrickdef docker_build_image(): 152ec727ea7Spatrick sys.exit(call("docker build --tag satest-image {}".format(SCRIPTS_DIR), 153ec727ea7Spatrick shell=True)) 154ec727ea7Spatrick 155ec727ea7Spatrick 156ec727ea7Spatrickdef docker_shell(args): 157ec727ea7Spatrick try: 158ec727ea7Spatrick # First we need to start the docker container in a waiting mode, 159ec727ea7Spatrick # so it doesn't do anything, but most importantly keeps working 160ec727ea7Spatrick # while the shell session is in progress. 161ec727ea7Spatrick docker_run(args, "--wait", "--detach") 162ec727ea7Spatrick # Since the docker container is running, we can actually connect to it 163ec727ea7Spatrick call("docker exec -it satest bash", shell=True) 164ec727ea7Spatrick 165ec727ea7Spatrick except KeyboardInterrupt: 166ec727ea7Spatrick pass 167ec727ea7Spatrick 168ec727ea7Spatrick finally: 169ec727ea7Spatrick docker_cleanup() 170ec727ea7Spatrick 171ec727ea7Spatrick 172ec727ea7Spatrickdef docker_run(args, command, docker_args=""): 173ec727ea7Spatrick try: 174ec727ea7Spatrick return call("docker run --rm --name satest " 175ec727ea7Spatrick "-v {llvm}:/llvm-project " 176ec727ea7Spatrick "-v {build}:/build " 177ec727ea7Spatrick "-v {clang}:/analyzer " 178ec727ea7Spatrick "-v {scripts}:/scripts " 179ec727ea7Spatrick "-v {projects}:/projects " 180ec727ea7Spatrick "{docker_args} " 181ec727ea7Spatrick "satest-image:latest {command}" 182ec727ea7Spatrick .format(llvm=args.llvm_project_dir, 183ec727ea7Spatrick build=args.build_dir, 184ec727ea7Spatrick clang=args.clang_dir, 185ec727ea7Spatrick scripts=SCRIPTS_DIR, 186ec727ea7Spatrick projects=PROJECTS_DIR, 187ec727ea7Spatrick docker_args=docker_args, 188ec727ea7Spatrick command=command), 189ec727ea7Spatrick shell=True) 190ec727ea7Spatrick 191ec727ea7Spatrick except KeyboardInterrupt: 192ec727ea7Spatrick docker_cleanup() 193ec727ea7Spatrick 194ec727ea7Spatrick 195ec727ea7Spatrickdef docker_cleanup(): 196ec727ea7Spatrick print("Please wait for docker to clean up") 197ec727ea7Spatrick call("docker stop satest", shell=True) 198ec727ea7Spatrick 199ec727ea7Spatrick 200ec727ea7Spatrickdef main(): 201ec727ea7Spatrick parser = argparse.ArgumentParser() 202ec727ea7Spatrick subparsers = parser.add_subparsers() 203ec727ea7Spatrick 204ec727ea7Spatrick # add subcommand 205ec727ea7Spatrick add_parser = subparsers.add_parser( 206ec727ea7Spatrick "add", 207ec727ea7Spatrick help="Add a new project for the analyzer testing.") 208ec727ea7Spatrick # TODO: Add an option not to build. 209ec727ea7Spatrick # TODO: Set the path to the Repository directory. 210ec727ea7Spatrick add_parser.add_argument("name", nargs=1, help="Name of the new project") 211ec727ea7Spatrick add_parser.add_argument("--mode", action="store", default=1, type=int, 212ec727ea7Spatrick choices=[0, 1, 2], 213ec727ea7Spatrick help="Build mode: 0 for single file project, " 214ec727ea7Spatrick "1 for scan_build, " 215ec727ea7Spatrick "2 for single file c++11 project") 216ec727ea7Spatrick add_parser.add_argument("--source", action="store", default="script", 217ec727ea7Spatrick choices=["script", "git", "zip"], 218ec727ea7Spatrick help="Source type of the new project: " 219ec727ea7Spatrick "'git' for getting from git " 220ec727ea7Spatrick "(please provide --origin and --commit), " 221ec727ea7Spatrick "'zip' for unpacking source from a zip file, " 222ec727ea7Spatrick "'script' for downloading source by running " 223ec727ea7Spatrick "a custom script") 224ec727ea7Spatrick add_parser.add_argument("--origin", action="store", default="", 225ec727ea7Spatrick help="Origin link for a git repository") 226ec727ea7Spatrick add_parser.add_argument("--commit", action="store", default="", 227ec727ea7Spatrick help="Git hash for a commit to checkout") 228ec727ea7Spatrick add_parser.set_defaults(func=add) 229ec727ea7Spatrick 230ec727ea7Spatrick # build subcommand 231ec727ea7Spatrick build_parser = subparsers.add_parser( 232ec727ea7Spatrick "build", 233ec727ea7Spatrick help="Build projects from the project map and compare results with " 234ec727ea7Spatrick "the reference.") 235ec727ea7Spatrick build_parser.add_argument("--strictness", dest="strictness", 236ec727ea7Spatrick type=int, default=0, 237ec727ea7Spatrick help="0 to fail on runtime errors, 1 to fail " 238ec727ea7Spatrick "when the number of found bugs are different " 239ec727ea7Spatrick "from the reference, 2 to fail on any " 240ec727ea7Spatrick "difference from the reference. Default is 0.") 241ec727ea7Spatrick build_parser.add_argument("-r", dest="regenerate", action="store_true", 242ec727ea7Spatrick default=False, 243ec727ea7Spatrick help="Regenerate reference output.") 244ec727ea7Spatrick build_parser.add_argument("--override-compiler", action="store_true", 245ec727ea7Spatrick default=False, help="Call scan-build with " 246ec727ea7Spatrick "--override-compiler option.") 247ec727ea7Spatrick build_parser.add_argument("-j", "--jobs", dest="jobs", 248ec727ea7Spatrick type=int, default=0, 249ec727ea7Spatrick help="Number of projects to test concurrently") 250ec727ea7Spatrick build_parser.add_argument("--extra-analyzer-config", 251ec727ea7Spatrick dest="extra_analyzer_config", type=str, 252ec727ea7Spatrick default="", 253ec727ea7Spatrick help="Arguments passed to to -analyzer-config") 254*12c85518Srobert build_parser.add_argument("--extra-checkers", 255*12c85518Srobert dest="extra_checkers", type=str, 256*12c85518Srobert default="", 257*12c85518Srobert help="Extra checkers to enable") 258ec727ea7Spatrick build_parser.add_argument("--projects", action="store", default="", 259ec727ea7Spatrick help="Comma-separated list of projects to test") 260a9ac8606Spatrick build_parser.add_argument("--max-size", action="store", default=None, 261a9ac8606Spatrick help="Maximum size for the projects to test") 262ec727ea7Spatrick build_parser.add_argument("-v", "--verbose", action="count", default=0) 263ec727ea7Spatrick build_parser.set_defaults(func=build) 264ec727ea7Spatrick 265ec727ea7Spatrick # compare subcommand 266ec727ea7Spatrick cmp_parser = subparsers.add_parser( 267ec727ea7Spatrick "compare", 268ec727ea7Spatrick help="Comparing two static analyzer runs in terms of " 269ec727ea7Spatrick "reported warnings and execution time statistics.") 270ec727ea7Spatrick cmp_parser.add_argument("--root-old", dest="root_old", 271ec727ea7Spatrick help="Prefix to ignore on source files for " 272ec727ea7Spatrick "OLD directory", 273ec727ea7Spatrick action="store", type=str, default="") 274ec727ea7Spatrick cmp_parser.add_argument("--root-new", dest="root_new", 275ec727ea7Spatrick help="Prefix to ignore on source files for " 276ec727ea7Spatrick "NEW directory", 277ec727ea7Spatrick action="store", type=str, default="") 278ec727ea7Spatrick cmp_parser.add_argument("--verbose-log", dest="verbose_log", 279ec727ea7Spatrick help="Write additional information to LOG " 280ec727ea7Spatrick "[default=None]", 281ec727ea7Spatrick action="store", type=str, default=None, 282ec727ea7Spatrick metavar="LOG") 283ec727ea7Spatrick cmp_parser.add_argument("--stats-only", action="store_true", 284ec727ea7Spatrick dest="stats_only", default=False, 285ec727ea7Spatrick help="Only show statistics on reports") 286ec727ea7Spatrick cmp_parser.add_argument("--show-stats", action="store_true", 287ec727ea7Spatrick dest="show_stats", default=False, 288ec727ea7Spatrick help="Show change in statistics") 289ec727ea7Spatrick cmp_parser.add_argument("--histogram", action="store", default=None, 290ec727ea7Spatrick help="Show histogram of paths differences. " 291ec727ea7Spatrick "Requires matplotlib") 292ec727ea7Spatrick cmp_parser.add_argument("old", nargs=1, help="Directory with old results") 293ec727ea7Spatrick cmp_parser.add_argument("new", nargs=1, help="Directory with new results") 294ec727ea7Spatrick cmp_parser.set_defaults(func=compare) 295ec727ea7Spatrick 296ec727ea7Spatrick # update subcommand 297ec727ea7Spatrick upd_parser = subparsers.add_parser( 298ec727ea7Spatrick "update", 299ec727ea7Spatrick help="Update static analyzer reference results based on the previous " 300ec727ea7Spatrick "run of SATest build. Assumes that SATest build was just run.") 301a9ac8606Spatrick upd_parser.add_argument("--git", action="store_true", 302a9ac8606Spatrick help="Stage updated results using git.") 303ec727ea7Spatrick upd_parser.set_defaults(func=update) 304ec727ea7Spatrick 305ec727ea7Spatrick # docker subcommand 306ec727ea7Spatrick dock_parser = subparsers.add_parser( 307ec727ea7Spatrick "docker", 308ec727ea7Spatrick help="Run regression system in the docker.") 309ec727ea7Spatrick 310ec727ea7Spatrick dock_parser.add_argument("--build-image", action="store_true", 311ec727ea7Spatrick help="Build docker image for running tests.") 312ec727ea7Spatrick dock_parser.add_argument("--shell", action="store_true", 313ec727ea7Spatrick help="Start a shell on docker.") 314ec727ea7Spatrick dock_parser.add_argument("--llvm-project-dir", action="store", 315ec727ea7Spatrick default=DEFAULT_LLVM_DIR, 316ec727ea7Spatrick help="Path to LLVM source code. Defaults " 317ec727ea7Spatrick "to the repo where this script is located. ") 318ec727ea7Spatrick dock_parser.add_argument("--build-dir", action="store", default="", 319ec727ea7Spatrick help="Path to a directory where docker should " 320ec727ea7Spatrick "build LLVM code.") 321ec727ea7Spatrick dock_parser.add_argument("--clang-dir", action="store", default="", 322ec727ea7Spatrick help="Path to find/install LLVM installation.") 323ec727ea7Spatrick dock_parser.add_argument("rest", nargs=argparse.REMAINDER, default=[], 324*12c85518Srobert help="Additional args that will be forwarded " 325ec727ea7Spatrick "to the docker's entrypoint.") 326ec727ea7Spatrick dock_parser.set_defaults(func=docker) 327ec727ea7Spatrick 328ec727ea7Spatrick # benchmark subcommand 329ec727ea7Spatrick bench_parser = subparsers.add_parser( 330ec727ea7Spatrick "benchmark", 331ec727ea7Spatrick help="Run benchmarks by building a set of projects multiple times.") 332ec727ea7Spatrick 333ec727ea7Spatrick bench_parser.add_argument("-i", "--iterations", action="store", 334ec727ea7Spatrick type=int, default=20, 335ec727ea7Spatrick help="Number of iterations for building each " 336ec727ea7Spatrick "project.") 337ec727ea7Spatrick bench_parser.add_argument("-o", "--output", action="store", 338ec727ea7Spatrick default="benchmark.csv", 339ec727ea7Spatrick help="Output csv file for the benchmark results") 340ec727ea7Spatrick bench_parser.add_argument("--projects", action="store", default="", 341ec727ea7Spatrick help="Comma-separated list of projects to test") 342a9ac8606Spatrick bench_parser.add_argument("--max-size", action="store", default=None, 343a9ac8606Spatrick help="Maximum size for the projects to test") 344ec727ea7Spatrick bench_parser.set_defaults(func=benchmark) 345ec727ea7Spatrick 346ec727ea7Spatrick bench_subparsers = bench_parser.add_subparsers() 347ec727ea7Spatrick bench_compare_parser = bench_subparsers.add_parser( 348ec727ea7Spatrick "compare", 349ec727ea7Spatrick help="Compare benchmark runs.") 350ec727ea7Spatrick bench_compare_parser.add_argument("--old", action="store", required=True, 351ec727ea7Spatrick help="Benchmark reference results to " 352ec727ea7Spatrick "compare agains.") 353ec727ea7Spatrick bench_compare_parser.add_argument("--new", action="store", required=True, 354ec727ea7Spatrick help="New benchmark results to check.") 355ec727ea7Spatrick bench_compare_parser.add_argument("-o", "--output", 356ec727ea7Spatrick action="store", required=True, 357ec727ea7Spatrick help="Output file for plots.") 358ec727ea7Spatrick bench_compare_parser.set_defaults(func=benchmark_compare) 359ec727ea7Spatrick 360ec727ea7Spatrick args = parser.parse_args() 361ec727ea7Spatrick args.func(parser, args) 362ec727ea7Spatrick 363ec727ea7Spatrick 364ec727ea7Spatrickif __name__ == "__main__": 365ec727ea7Spatrick main() 366