1# Copyright (C) 2022 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15load("@bazel_skylib//lib:new_sets.bzl", "sets")
16load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
17load(
18    "//build/bazel/rules/test_common:args.bzl",
19    "get_all_args_with_prefix",
20    "get_single_arg_with_prefix",
21)
22load("//build/bazel/rules/test_common:rules.bzl", "expect_failure_test")
23load(":cc_library_static.bzl", "cc_library_static")
24load(":clang_tidy.bzl", "generate_clang_tidy_actions")
25
26_PACKAGE_HEADER_FILTER = "^build/bazel/rules/cc/"
27_DEFAULT_GLOBAL_CHECKS = [
28    "android-*",
29    "bugprone-*",
30    "cert-*",
31    "clang-diagnostic-unused-command-line-argument",
32    "google-build-explicit-make-pair",
33    "google-build-namespaces",
34    "google-runtime-operator",
35    "google-upgrade-*",
36    "misc-*",
37    "performance-*",
38    "portability-*",
39    "-bugprone-assignment-in-if-condition",
40    "-bugprone-easily-swappable-parameters",
41    "-bugprone-narrowing-conversions",
42    "-misc-const-correctness",
43    "-misc-no-recursion",
44    "-misc-non-private-member-variables-in-classes",
45    "-misc-unused-parameters",
46    "-performance-no-int-to-ptr",
47    "-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling",
48]
49_DEFAULT_CHECKS = [
50    "-misc-no-recursion",
51    "-readability-function-cognitive-complexity",
52    "-bugprone-unchecked-optional-access",
53    "-bugprone-reserved-identifier*",
54    "-cert-dcl51-cpp",
55    "-cert-dcl37-c",
56    "-readability-qualified-auto",
57    "-bugprone-implicit-widening-of-multiplication-result",
58    "-bugprone-easily-swappable-parameters",
59    "-cert-err33-c",
60    "-bugprone-unchecked-optional-access",
61    "-misc-use-anonymous-namespace",
62    "-performance-avoid-endl",
63]
64_DEFAULT_CHECKS_AS_ERRORS = [
65    "-bugprone-assignment-in-if-condition",
66    "-bugprone-branch-clone",
67    "-bugprone-signed-char-misuse",
68    "-misc-const-correctness",
69    "-bugprone-unsafe-functions",
70    "-cert-msc24-c",
71    "-cert-msc33-c",
72    "-modernize-type-traits",
73    "-readability-avoid-unconditional-preprocessor-if",
74]
75_EXTRA_ARGS_BEFORE = [
76    "-D__clang_analyzer__",
77    "-Xclang",
78    "-analyzer-config",
79    "-Xclang",
80    "c++-temp-dtor-inlining=false",
81]
82
83def _clang_tidy_impl(ctx):
84    tidy_outs = generate_clang_tidy_actions(
85        ctx,
86        ctx.attr.copts,
87        ctx.attr.deps,
88        ctx.files.srcs,
89        ctx.files.hdrs,
90        ctx.attr.language,
91        ctx.attr.tidy_flags,
92        ctx.attr.tidy_checks,
93        ctx.attr.tidy_checks_as_errors,
94        ctx.attr.tidy_timeout_srcs,
95    )
96    return [
97        DefaultInfo(files = depset(tidy_outs)),
98    ]
99
100_clang_tidy = rule(
101    implementation = _clang_tidy_impl,
102    attrs = {
103        "srcs": attr.label_list(allow_files = True),
104        "deps": attr.label_list(),
105        "copts": attr.string_list(),
106        "hdrs": attr.label_list(allow_files = True),
107        "language": attr.string(values = ["c++", "c"], default = "c++"),
108        "tidy_checks": attr.string_list(),
109        "tidy_checks_as_errors": attr.string_list(),
110        "tidy_flags": attr.string_list(),
111        "tidy_timeout_srcs": attr.label_list(allow_files = True),
112        "_clang_tidy_sh": attr.label(
113            default = Label("@//prebuilts/clang/host/linux-x86:clang-tidy.sh"),
114            allow_single_file = True,
115            executable = True,
116            cfg = "exec",
117            doc = "The clang tidy shell wrapper",
118        ),
119        "_clang_tidy": attr.label(
120            default = Label("@//prebuilts/clang/host/linux-x86:clang-tidy"),
121            allow_single_file = True,
122            executable = True,
123            cfg = "exec",
124            doc = "The clang tidy executable",
125        ),
126        "_clang_tidy_real": attr.label(
127            default = Label("@//prebuilts/clang/host/linux-x86:clang-tidy.real"),
128            allow_single_file = True,
129            executable = True,
130            cfg = "exec",
131        ),
132        "_with_tidy": attr.label(
133            default = "//build/bazel/flags/cc/tidy:with_tidy",
134        ),
135        "_allow_local_tidy_true": attr.label(
136            default = "//build/bazel/flags/cc/tidy:allow_local_tidy_true",
137        ),
138        "_with_tidy_flags": attr.label(
139            default = "//build/bazel/flags/cc/tidy:with_tidy_flags",
140        ),
141        "_default_tidy_header_dirs": attr.label(
142            default = "//build/bazel/flags/cc/tidy:default_tidy_header_dirs",
143        ),
144        "_tidy_timeout": attr.label(
145            default = "//build/bazel/flags/cc/tidy:tidy_timeout",
146        ),
147        "_tidy_checks": attr.label(
148            default = "//build/bazel/product_config:tidy_checks",
149        ),
150    },
151    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
152    fragments = ["cpp"],
153)
154
155def _get_all_arg(env, actions, argname):
156    args = get_all_args_with_prefix(actions[0].argv, argname)
157    asserts.false(env, args == [], "could not arguments that start with `{}`".format(argname))
158    return args
159
160def _get_single_arg(actions, argname):
161    return get_single_arg_with_prefix(actions[0].argv, argname)
162
163def _checks_test_impl(ctx):
164    env = analysistest.begin(ctx)
165    actions = analysistest.target_actions(env)
166
167    checks = _get_single_arg(actions, "-checks=").split(",")
168    asserts.set_equals(env, sets.make(ctx.attr.expected_checks), sets.make(checks))
169    if len(ctx.attr.unexpected_checks) > 0:
170        for c in ctx.attr.unexpected_checks:
171            asserts.false(env, c in checks, "found unexpected check in -checks flag: %s" % c)
172
173    checks_as_errors = _get_single_arg(actions, "-warnings-as-errors=").split(",")
174    asserts.set_equals(env, sets.make(ctx.attr.expected_checks_as_errors), sets.make(checks_as_errors))
175
176    return analysistest.end(env)
177
178_checks_test = analysistest.make(
179    _checks_test_impl,
180    attrs = {
181        "expected_checks": attr.string_list(mandatory = True),
182        "expected_checks_as_errors": attr.string_list(mandatory = True),
183        "unexpected_checks": attr.string_list(),
184    },
185)
186
187def _copts_test_impl(ctx):
188    env = analysistest.begin(ctx)
189    actions = analysistest.target_actions(env)
190
191    args = actions[0].argv
192    clang_flags = []
193    for i, a in enumerate(args):
194        if a == "--" and len(args) > i + 1:
195            clang_flags = args[i + 1:]
196            break
197    asserts.true(
198        env,
199        len(clang_flags) > 0,
200        "no flags passed to clang; all arguments: %s" % args,
201    )
202
203    for expected_arg in ctx.attr.expected_copts:
204        asserts.true(
205            env,
206            expected_arg in clang_flags,
207            "expected `%s` not present in clang flags" % expected_arg,
208        )
209
210    return analysistest.end(env)
211
212_copts_test = analysistest.make(
213    _copts_test_impl,
214    attrs = {
215        "expected_copts": attr.string_list(mandatory = True),
216    },
217)
218
219def _tidy_flags_test_impl(ctx):
220    env = analysistest.begin(ctx)
221    actions = analysistest.target_actions(env)
222
223    args = actions[0].argv
224    tidy_flags = []
225    for i, a in enumerate(args):
226        if a == "--" and len(args) > i + 1:
227            tidy_flags = args[:i]
228    asserts.true(
229        env,
230        len(tidy_flags) > 0,
231        "no tidy flags passed to clang-tidy; all arguments: %s" % args,
232    )
233
234    for expected_arg in ctx.attr.expected_tidy_flags:
235        asserts.true(
236            env,
237            expected_arg in tidy_flags,
238            "expected `%s` not present in flags to clang-tidy" % expected_arg,
239        )
240
241    header_filter = _get_single_arg(actions, "-header-filter=")
242    asserts.true(
243        env,
244        header_filter == ctx.attr.expected_header_filter,
245        (
246            "expected header-filter to have value `%s`; got `%s`" %
247            (ctx.attr.expected_header_filter, header_filter)
248        ),
249    )
250
251    extra_arg_before = _get_all_arg(env, actions, "-extra-arg-before=")
252    for expected_arg in ctx.attr.expected_extra_arg_before:
253        asserts.true(
254            env,
255            expected_arg in extra_arg_before,
256            "did not find expected flag `%s` in args to clang-tidy" % expected_arg,
257        )
258
259    return analysistest.end(env)
260
261_tidy_flags_test = analysistest.make(
262    _tidy_flags_test_impl,
263    attrs = {
264        "expected_tidy_flags": attr.string_list(),
265        "expected_header_filter": attr.string(mandatory = True),
266        "expected_extra_arg_before": attr.string_list(),
267    },
268)
269
270def _test_clang_tidy():
271    name = "checks"
272    test_name = name + "_test"
273    checks_test_name = test_name + "_checks"
274    copts_test_name = test_name + "_copts"
275    tidy_flags_test_name = test_name + "_tidy_flags"
276
277    _clang_tidy(
278        name = name,
279        # clang-tidy operates differently on generated and non-generated files
280        # use test_srcs so that the tidy rule doesn't think these are genearted
281        # files
282        srcs = ["//build/bazel/rules/cc/testing:test_srcs"],
283        copts = ["-asdf1", "-asdf2"],
284        tidy_flags = ["-tidy-flag1", "-tidy-flag2"],
285        tags = ["manual"],
286    )
287
288    _checks_test(
289        name = checks_test_name,
290        target_under_test = name,
291        expected_checks = _DEFAULT_CHECKS + _DEFAULT_GLOBAL_CHECKS,
292        expected_checks_as_errors = _DEFAULT_CHECKS_AS_ERRORS,
293    )
294
295    _copts_test(
296        name = copts_test_name,
297        target_under_test = name,
298        expected_copts = ["-asdf1", "-asdf2"],
299    )
300
301    _tidy_flags_test(
302        name = tidy_flags_test_name,
303        target_under_test = name,
304        expected_tidy_flags = ["-tidy-flag1", "-tidy-flag2"],
305        expected_header_filter = _PACKAGE_HEADER_FILTER,
306        expected_extra_arg_before = _EXTRA_ARGS_BEFORE,
307    )
308
309    return [
310        checks_test_name,
311        copts_test_name,
312        tidy_flags_test_name,
313    ]
314
315def _test_custom_header_dir():
316    name = "custom_header_dir"
317    test_name = name + "_test"
318
319    _clang_tidy(
320        name = name,
321        srcs = ["a.cpp"],
322        tidy_flags = ["-header-filter=dir1/"],
323        tags = ["manual"],
324    )
325
326    _tidy_flags_test(
327        name = test_name,
328        target_under_test = name,
329        expected_header_filter = "dir1/",
330    )
331
332    return [
333        test_name,
334    ]
335
336def _test_disabled_checks_are_removed():
337    name = "disabled_checks_are_removed"
338    test_name = name + "_test"
339
340    _clang_tidy(
341        name = name,
342        # clang-tidy operates differently on generated and non-generated files.
343        # use test_srcs so that the tidy rule doesn't think these are genearted
344        # files
345        srcs = ["//build/bazel/rules/cc/testing:test_srcs"],
346        tidy_checks = ["misc-no-recursion", "readability-function-cognitive-complexity"],
347        tags = ["manual"],
348    )
349
350    _checks_test(
351        name = test_name,
352        target_under_test = name,
353        expected_checks = _DEFAULT_CHECKS + _DEFAULT_GLOBAL_CHECKS,
354        expected_checks_as_errors = _DEFAULT_CHECKS_AS_ERRORS,
355        unexpected_checks = ["misc-no-recursion", "readability-function-cognitive-complexity"],
356    )
357
358    return [
359        test_name,
360    ]
361
362def _create_bad_tidy_checks_test(name, tidy_checks, failure_message):
363    name = "bad_tidy_checks_fail_" + name
364    test_name = name + "_test"
365
366    _clang_tidy(
367        name = name,
368        srcs = ["a.cpp"],
369        tidy_checks = tidy_checks,
370        tags = ["manual"],
371    )
372
373    expect_failure_test(
374        name = test_name,
375        target_under_test = name,
376        failure_message = failure_message,
377    )
378
379    return [
380        test_name,
381    ]
382
383def _test_bad_tidy_checks_fail():
384    return (
385        _create_bad_tidy_checks_test(
386            name = "with_spaces",
387            tidy_checks = ["check with spaces"],
388            failure_message = "Check `check with spaces` invalid, cannot contain spaces",
389        ) +
390        _create_bad_tidy_checks_test(
391            name = "with_commas",
392            tidy_checks = ["check,with,commas"],
393            failure_message = "Check `check,with,commas` invalid, cannot contain commas. Split each entry into its own string instead",
394        )
395    )
396
397def _create_bad_tidy_flags_test(name, tidy_flags, failure_message):
398    name = "bad_tidy_flags_fail_" + name
399    test_name = name + "_test"
400
401    _clang_tidy(
402        name = name,
403        srcs = ["a.cpp"],
404        tidy_flags = tidy_flags,
405        tags = ["manual"],
406    )
407
408    expect_failure_test(
409        name = test_name,
410        target_under_test = name,
411        failure_message = failure_message,
412    )
413
414    return [
415        test_name,
416    ]
417
418def _test_bad_tidy_flags_fail():
419    return (
420        _create_bad_tidy_flags_test(
421            name = "without_leading_dash",
422            tidy_flags = ["flag1"],
423            failure_message = "Flag `flag1` must start with `-`",
424        ) +
425        _create_bad_tidy_flags_test(
426            name = "fix_flags",
427            tidy_flags = ["-fix"],
428            failure_message = "Flag `%s` is not allowed, since it could cause multiple writes to the same source file",
429        ) +
430        _create_bad_tidy_flags_test(
431            name = "checks_in_flags",
432            tidy_flags = ["-checks=asdf"],
433            failure_message = "Flag `-checks=asdf` is not allowed, use `tidy_checks` property instead",
434        ) +
435        _create_bad_tidy_flags_test(
436            name = "warnings_as_errors_in_flags",
437            tidy_flags = ["-warnings-as-errors=asdf"],
438            failure_message = "Flag `-warnings-as-errors=asdf` is not allowed, use `tidy_checks_as_errors` property instead",
439        ) +
440        _create_bad_tidy_flags_test(
441            name = "space_in_flags",
442            tidy_flags = ["-flag with spaces"],
443            failure_message = "Bad flag: `-flag with spaces` is not an allowed multi-word flag. Should it be split into multiple flags",
444        )
445    )
446
447def _test_disable_global_checks():
448    name = "disable_global_checks"
449    test_name = name + "_test"
450
451    _clang_tidy(
452        name = name,
453        srcs = ["a.cpp"],
454        tidy_checks = ["-*"],
455        tags = ["manual"],
456    )
457
458    _checks_test(
459        name = test_name,
460        target_under_test = name,
461        expected_checks = ["-*"] + _DEFAULT_CHECKS,
462        expected_checks_as_errors = _DEFAULT_CHECKS_AS_ERRORS,
463    )
464
465    return [
466        test_name,
467    ]
468
469def _cc_library_static_generates_clang_tidy_actions_for_srcs_test_impl(ctx):
470    env = analysistest.begin(ctx)
471    actions = analysistest.target_actions(env)
472
473    clang_tidy_actions = [a for a in actions if a.mnemonic == "ClangTidy"]
474    asserts.equals(
475        env,
476        ctx.attr.expected_num_actions,
477        len(clang_tidy_actions),
478        "expected to have %s clang-tidy actions, but got %s; actions: %s" % (
479            ctx.attr.expected_num_actions,
480            len(clang_tidy_actions),
481            clang_tidy_actions,
482        ),
483    )
484
485    for a in clang_tidy_actions:
486        for input in a.inputs.to_list():
487            input_is_expected_header = input.short_path in [f.short_path for f in ctx.files.expected_headers]
488            if input in ctx.files._clang_tidy_tools or input_is_expected_header:
489                continue
490            asserts.true(
491                env,
492                input in ctx.files.srcs,
493                "clang-tidy operated on a file not in srcs: %s; all inputs: %s" % (input, a.inputs.to_list()),
494            )
495            asserts.true(
496                env,
497                input not in ctx.files.disabled_srcs,
498                "clang-tidy operated on a file in disabled_srcs: %s; all inputs: %s" % (input, a.inputs.to_list()),
499            )
500
501    return analysistest.end(env)
502
503_cc_library_static_generates_clang_tidy_actions_for_srcs_test = analysistest.make(
504    impl = _cc_library_static_generates_clang_tidy_actions_for_srcs_test_impl,
505    attrs = {
506        "expected_num_actions": attr.int(mandatory = True),
507        "srcs": attr.label_list(allow_files = True),
508        "disabled_srcs": attr.label_list(allow_files = True),
509        "expected_headers": attr.label_list(allow_files = True),
510        "_clang_tidy_tools": attr.label_list(
511            default = [
512                "@//prebuilts/clang/host/linux-x86:clang-tidy",
513                "@//prebuilts/clang/host/linux-x86:clang-tidy.real",
514                "@//prebuilts/clang/host/linux-x86:clang-tidy.sh",
515            ],
516            allow_files = True,
517        ),
518    },
519    config_settings = {
520        "@//build/bazel/flags/cc/tidy:allow_local_tidy_true": True,
521    },
522)
523
524def _create_cc_library_static_generates_clang_tidy_actions_for_srcs(
525        name,
526        srcs,
527        expected_num_actions,
528        disabled_srcs = None,
529        expected_headers = []):
530    name = "cc_library_static_generates_clang_tidy_actions_for_srcs_" + name
531    test_name = name + "_test"
532
533    cc_library_static(
534        name = name,
535        srcs = srcs,
536        tidy_disabled_srcs = disabled_srcs,
537        tidy = "local",
538        tags = ["manual"],
539    )
540
541    _cc_library_static_generates_clang_tidy_actions_for_srcs_test(
542        name = test_name,
543        target_under_test = name,
544        expected_num_actions = expected_num_actions,
545        srcs = srcs,
546        disabled_srcs = disabled_srcs,
547        expected_headers = expected_headers + select({
548            "//build/bazel_common_rules/platforms/os:android": ["@//bionic/libc:generated_android_ids"],
549            "//conditions:default": [],
550        }),
551    )
552
553    return test_name
554
555def _test_cc_library_static_generates_clang_tidy_actions_for_srcs():
556    return [
557        _create_cc_library_static_generates_clang_tidy_actions_for_srcs(
558            name = "with_srcs",
559            srcs = ["a.cpp", "b.cpp"],
560            expected_num_actions = 2,
561        ),
562        _create_cc_library_static_generates_clang_tidy_actions_for_srcs(
563            name = "with_disabled_srcs",
564            srcs = ["a.cpp", "b.cpp"],
565            disabled_srcs = ["b.cpp", "c.cpp"],
566            expected_num_actions = 1,
567        ),
568    ]
569
570def _no_clang_analyzer_on_generated_files_test_impl(ctx):
571    env = analysistest.begin(ctx)
572    actions = analysistest.target_actions(env)
573
574    clang_tidy_actions = [a for a in actions if a.mnemonic == "ClangTidy"]
575    for a in clang_tidy_actions:
576        found_clang_analyzer = False
577        for arg in a.argv:
578            if "-clang-analyzer-*" in arg:
579                found_clang_analyzer = True
580        asserts.true(env, found_clang_analyzer)
581
582    return analysistest.end(env)
583
584_no_clang_analyzer_on_generated_files_test = analysistest.make(
585    impl = _no_clang_analyzer_on_generated_files_test_impl,
586    config_settings = {
587        "@//build/bazel/flags/cc/tidy:allow_local_tidy_true": True,
588    },
589)
590
591def _test_no_clang_analyzer_on_generated_files():
592    name = "no_clang_analyzer_on_generated_files"
593    gen_name = name + "_generated_files"
594    test_name = name + "_test"
595
596    native.genrule(
597        name = gen_name,
598        outs = ["aout.cpp", "bout.cpp"],
599        cmd = "touch $(OUTS)",
600        tags = ["manual"],
601    )
602
603    cc_library_static(
604        name = name,
605        srcs = [":" + gen_name],
606        tidy = "local",
607        tags = ["manual"],
608    )
609
610    _no_clang_analyzer_on_generated_files_test(
611        name = test_name,
612        target_under_test = name,
613    )
614
615    return [
616        test_name,
617    ]
618
619def _clang_tidy_actions_count_no_tidy_env_test_impl(ctx):
620    env = analysistest.begin(ctx)
621    actions = analysistest.target_actions(env)
622
623    clang_tidy_actions = [a for a in actions if a.mnemonic == "ClangTidy"]
624    asserts.equals(
625        env,
626        ctx.attr.expected_num_tidy_actions,
627        len(clang_tidy_actions),
628        "expected to find %d tidy actions, but found %d" % (
629            ctx.attr.expected_num_tidy_actions,
630            len(clang_tidy_actions),
631        ),
632    )
633
634    return analysistest.end(env)
635
636_clang_tidy_actions_count_no_tidy_env_test = analysistest.make(
637    impl = _clang_tidy_actions_count_no_tidy_env_test_impl,
638    attrs = {
639        "expected_num_tidy_actions": attr.int(),
640    },
641)
642
643_clang_tidy_actions_count_with_tidy_true_test = analysistest.make(
644    impl = _clang_tidy_actions_count_no_tidy_env_test_impl,
645    attrs = {
646        "expected_num_tidy_actions": attr.int(),
647    },
648    config_settings = {
649        "@//build/bazel/flags/cc/tidy:with_tidy": True,
650    },
651)
652
653_clang_tidy_actions_count_with_allow_local_tidy_true_test = analysistest.make(
654    impl = _clang_tidy_actions_count_no_tidy_env_test_impl,
655    attrs = {
656        "expected_num_tidy_actions": attr.int(),
657    },
658    config_settings = {
659        "@//build/bazel/flags/cc/tidy:allow_local_tidy_true": True,
660    },
661)
662
663def _test_clang_tidy_runs_if_tidy_true():
664    name = "clang_tidy_runs_if_tidy_true"
665    test_name = name + "_test"
666    with_tidy_test_name = test_name + "_with_tidy_true"
667    allow_local_tidy_true_test_name = test_name + "_allow_local_tidy_true"
668
669    cc_library_static(
670        name = name,
671        srcs = ["a.cpp"],
672        tidy = "local",
673        tags = ["manual"],
674    )
675    _clang_tidy_actions_count_no_tidy_env_test(
676        name = test_name,
677        target_under_test = name,
678        expected_num_tidy_actions = 0,
679    )
680    _clang_tidy_actions_count_with_tidy_true_test(
681        name = with_tidy_test_name,
682        target_under_test = name,
683        expected_num_tidy_actions = 1,
684    )
685    _clang_tidy_actions_count_with_allow_local_tidy_true_test(
686        name = allow_local_tidy_true_test_name,
687        target_under_test = name,
688        expected_num_tidy_actions = 1,
689    )
690    return [
691        test_name,
692        with_tidy_test_name,
693        allow_local_tidy_true_test_name,
694    ]
695
696def _test_clang_tidy_runs_if_attribute_unset():
697    name = "clang_tidy_runs_if_attribute_unset"
698    test_name = name + "_test"
699    with_tidy_test_name = test_name + "_with_tidy_true"
700    allow_local_tidy_true_test_name = test_name + "_allow_local_tidy_true"
701
702    cc_library_static(
703        name = name,
704        srcs = ["a.cpp"],
705        tags = ["manual"],
706    )
707    _clang_tidy_actions_count_no_tidy_env_test(
708        name = test_name,
709        target_under_test = name,
710        expected_num_tidy_actions = 0,
711    )
712    _clang_tidy_actions_count_with_tidy_true_test(
713        name = with_tidy_test_name,
714        target_under_test = name,
715        expected_num_tidy_actions = 1,
716    )
717    _clang_tidy_actions_count_with_allow_local_tidy_true_test(
718        name = allow_local_tidy_true_test_name,
719        target_under_test = name,
720        expected_num_tidy_actions = 0,
721    )
722    return [
723        test_name,
724        with_tidy_test_name,
725        allow_local_tidy_true_test_name,
726    ]
727
728def _test_no_clang_tidy_if_tidy_false():
729    name = "no_clang_tidy_if_tidy_false"
730    test_name = name + "_test"
731    with_tidy_test_name = test_name + "_with_tidy_true"
732    allow_local_tidy_true_test_name = test_name + "_allow_local_tidy_true"
733
734    cc_library_static(
735        name = name,
736        srcs = ["a.cpp"],
737        tidy = "never",
738        tags = ["manual"],
739    )
740    _clang_tidy_actions_count_no_tidy_env_test(
741        name = test_name,
742        target_under_test = name,
743        expected_num_tidy_actions = 0,
744    )
745    _clang_tidy_actions_count_with_tidy_true_test(
746        name = with_tidy_test_name,
747        target_under_test = name,
748        expected_num_tidy_actions = 0,
749    )
750    _clang_tidy_actions_count_with_allow_local_tidy_true_test(
751        name = allow_local_tidy_true_test_name,
752        target_under_test = name,
753        expected_num_tidy_actions = 0,
754    )
755    return [
756        test_name,
757        with_tidy_test_name,
758        allow_local_tidy_true_test_name,
759    ]
760
761def clang_tidy_test_suite(name):
762    native.test_suite(
763        name = name,
764        tests =
765            _test_clang_tidy() +
766            _test_custom_header_dir() +
767            _test_disabled_checks_are_removed() +
768            _test_bad_tidy_checks_fail() +
769            _test_bad_tidy_flags_fail() +
770            _test_disable_global_checks() +
771            _test_cc_library_static_generates_clang_tidy_actions_for_srcs() +
772            _test_no_clang_analyzer_on_generated_files() +
773            _test_no_clang_tidy_if_tidy_false() +
774            _test_clang_tidy_runs_if_tidy_true() +
775            _test_clang_tidy_runs_if_attribute_unset(),
776    )
777