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