1"""Copyright (C) 2022 The Android Open Source Project
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7     http://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14"""
15
16load("@bazel_skylib//lib:new_sets.bzl", "sets")
17load("@bazel_skylib//lib:paths.bzl", "paths")
18load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
19load("//build/bazel/rules/aidl:aidl_interface.bzl", "aidl_interface")
20load("//build/bazel/rules/aidl:aidl_library.bzl", "AidlGenInfo")
21load("//build/bazel/rules/test_common:rules.bzl", "target_under_test_exist_test")
22load("//build/bazel/rules/test_common:flags.bzl", "action_flags_present_only_for_mnemonic_test_with_config_settings")
23
24def _ndk_backend_test_impl(ctx):
25    env = analysistest.begin(ctx)
26    actions = analysistest.target_actions(env)
27    asserts.true(
28        env,
29        len(actions) == 1,
30        "expected to have one action per aidl_library target",
31    )
32    cc_aidl_code_gen_target = analysistest.target_under_test(env)
33
34    # output_path: <bazel-bin>/<package-dir>/<cc_aidl_library-labelname>_aidl_code_gen
35    # Since cc_aidl_library-label is unique among cpp and ndk backends,
36    # the output_path is guaranteed to be unique
37    output_path = paths.join(
38        ctx.genfiles_dir.path,
39        ctx.label.package,
40        cc_aidl_code_gen_target.label.name,
41    )
42    expected_outputs = [
43        # headers for ndk backend are nested in aidl directory to prevent
44        # collision in c++ namespaces with cpp backend
45        paths.join(output_path, "aidl/b/BpFoo.h"),
46        paths.join(output_path, "aidl/b/BnFoo.h"),
47        paths.join(output_path, "aidl/b/Foo.h"),
48        paths.join(output_path, "b/Foo.cpp"),
49    ]
50
51    # Check output files in DefaultInfo provider
52    asserts.set_equals(
53        env,
54        sets.make(expected_outputs),
55        sets.make([
56            output.path
57            for output in cc_aidl_code_gen_target[DefaultInfo].files.to_list()
58        ]),
59    )
60
61    # Check the output path is correctly added to includes in CcInfo.compilation_context
62    asserts.true(
63        env,
64        output_path in cc_aidl_code_gen_target[CcInfo].compilation_context.includes.to_list(),
65        "output path is added to CcInfo.compilation_context.includes",
66    )
67
68    return analysistest.end(env)
69
70ndk_backend_test = analysistest.make(
71    _ndk_backend_test_impl,
72)
73
74def _ndk_backend_test():
75    name = "foo"
76    aidl_library_target = name + "-ndk"
77    aidl_code_gen_target = aidl_library_target + "_aidl_code_gen"
78    test_name = aidl_code_gen_target + "_test"
79
80    aidl_interface(
81        name = "foo",
82        ndk_config = {
83            "enabled": True,
84        },
85        unstable = True,
86        srcs = ["a/b/Foo.aidl"],
87        strip_import_prefix = "a",
88        tags = ["manual"],
89    )
90
91    ndk_backend_test(
92        name = test_name,
93        target_under_test = aidl_code_gen_target,
94    )
95
96    return test_name
97
98def _ndk_config_test_impl(ctx):
99    env = analysistest.begin(ctx)
100    actions = analysistest.target_actions(env)
101    asserts.true(
102        env,
103        len(actions) == 1,
104        "expected to have one action per aidl_library target",
105    )
106    asserts.true(
107        env,
108        "--min_sdk_version=30",
109        "expected to have min_sdk_version flag",
110    )
111    return analysistest.end(env)
112
113ndk_config_test = analysistest.make(
114    _ndk_config_test_impl,
115)
116
117def _ndk_config_test():
118    name = "ndk-config"
119    aidl_library_target = name + "-ndk"
120    aidl_code_gen_target = aidl_library_target + "_aidl_code_gen"
121    test_name = aidl_code_gen_target + "_test"
122
123    aidl_interface(
124        name = name,
125        unstable = True,
126        ndk_config = {
127            "enabled": True,
128            "min_sdk_version": "30",
129        },
130        srcs = ["Foo.aidl"],
131        tags = ["manual"],
132    )
133
134    ndk_config_test(
135        name = test_name,
136        target_under_test = aidl_code_gen_target,
137    )
138
139    return test_name
140
141def _aidl_library_has_flags_test_impl(ctx):
142    env = analysistest.begin(ctx)
143    target_under_test = analysistest.target_under_test(env)
144
145    asserts.true(
146        env,
147        AidlGenInfo in target_under_test,
148        "",
149    )
150    asserts.equals(
151        env,
152        ctx.attr.expected_flags,
153        target_under_test[AidlGenInfo].flags,
154        "",
155    )
156
157    return analysistest.end(env)
158
159aidl_library_has_flags_test = analysistest.make(
160    _aidl_library_has_flags_test_impl,
161    attrs = {
162        "expected_flags": attr.string_list(),
163    },
164)
165
166def _test_aidl_interface_passes_flags_to_aidl_libraries():
167    name = "aidl_interface_passes_version_flags_to_aidl_libraries"
168    aidl_interface(
169        name = name,
170        srcs = ["Foo.aidl"],
171        tags = ["manual"],
172        versions_with_info = [
173            {
174                "version": "1",
175            },
176            {
177                "version": "2",
178            },
179        ],
180    )
181
182    target_v1_test_name = name + "_test-V1"
183    aidl_library_has_flags_test(
184        name = target_v1_test_name,
185        target_under_test = name + "-V1",
186        expected_flags = [
187            "--structured",
188            "--version=1",
189        ],
190    )
191    target_v2_test_name = name + "_test-V2"
192    aidl_library_has_flags_test(
193        name = target_v2_test_name,
194        target_under_test = name + "-V2",
195        expected_flags = [
196            "--structured",
197            "--version=2",
198        ],
199    )
200    target_v_next_test_name = name + "_test-V_next"
201    aidl_library_has_flags_test(
202        name = target_v_next_test_name,
203        target_under_test = name + "-V3",
204        expected_flags = [
205            "--structured",
206            "--version=3",
207        ],
208    )
209
210    return [
211        target_v1_test_name,
212        target_v2_test_name,
213        target_v_next_test_name,
214    ]
215
216def _next_version_for_unversioned_stable_interface_test():
217    name = "unversioned_stable_interface_next_version"
218    test_name = name + "_test"
219    next_version_aidl_library_target = name + "-V1"
220
221    aidl_interface(
222        name = name,
223        srcs = ["Foo.aidl"],
224        tags = ["manual"],
225    )
226
227    target_under_test_exist_test(
228        name = test_name,
229        target_under_test = next_version_aidl_library_target,
230    )
231
232    return test_name
233
234def _next_version_for_versioned_stable_interface_test():
235    name = "versioned_stable_interface_next_version"
236    test_name = name + "_test"
237    next_version_aidl_library_target = name + "-V3"
238
239    aidl_interface(
240        name = name,
241        versions_with_info = [
242            {
243                "version": "1",
244            },
245            {
246                "version": "2",
247            },
248        ],
249        srcs = ["Foo.aidl"],
250        tags = ["manual"],
251    )
252
253    target_under_test_exist_test(
254        name = test_name,
255        target_under_test = next_version_aidl_library_target,
256    )
257
258    return test_name
259
260def _tidy_flags_has_generated_directory_header_filter_test_impl(ctx):
261    env = analysistest.begin(ctx)
262
263    actions = analysistest.target_actions(env)
264    clang_tidy_actions = [a for a in actions if a.mnemonic == "ClangTidy"]
265
266    action_header_filter = None
267    for action in clang_tidy_actions:
268        for arg in action.argv:
269            if arg.startswith("-header-filter="):
270                action_header_filter = arg
271
272    if action_header_filter == None:
273        asserts.true(
274            env,
275            False,
276            "did not find header-filter in ClangTidy actions: `%s`" % (
277                clang_tidy_actions
278            ),
279        )
280
281    # The genfiles path for tests and cc_libraries is different (the latter contains a
282    # configuration prefix ST-<hash>). So instead (lacking regexes) we can just check
283    # that the beginning and end of the header-filter is correct.
284    expected_header_filter_prefix = "-header-filter=" + paths.join(ctx.genfiles_dir.path)
285    expected_header_filter_prefix = expected_header_filter_prefix.removesuffix("/bin")
286    expected_header_filter_suffix = ctx.label.package + ".*"
287    asserts.true(
288        env,
289        action_header_filter.startswith(expected_header_filter_prefix),
290        "expected header-filter to start with `%s`; but was `%s`" % (
291            expected_header_filter_prefix,
292            action_header_filter,
293        ),
294    )
295    asserts.true(
296        env,
297        action_header_filter.endswith(expected_header_filter_suffix),
298        "expected header-filter to end with `%s`; but was `%s`" % (
299            expected_header_filter_suffix,
300            action_header_filter,
301        ),
302    )
303
304    return analysistest.end(env)
305
306_tidy_flags_has_generated_directory_header_filter_test = analysistest.make(
307    _tidy_flags_has_generated_directory_header_filter_test_impl,
308    config_settings = {
309        "@//build/bazel/flags/cc/tidy:allow_local_tidy_true": True,
310    },
311)
312
313def _test_aidl_interface_generated_header_filter():
314    name = "aidl_interface_generated_header_filter"
315    test_name = name + "_test"
316    aidl_library_target = name + "-cpp"
317    shared_target_under_test = aidl_library_target + "__internal_root"
318    shared_test_name = test_name + "_shared"
319    static_target_under_test = aidl_library_target + "_bp2build_cc_library_static"
320    static_test_name = test_name + "_static"
321
322    aidl_interface(
323        name = name,
324        cpp_config = {
325            "enabled": True,
326        },
327        unstable = True,
328        srcs = ["a/b/Foo.aidl"],
329        tags = ["manual"],
330    )
331
332    _tidy_flags_has_generated_directory_header_filter_test(
333        name = shared_test_name,
334        target_under_test = shared_target_under_test,
335    )
336    _tidy_flags_has_generated_directory_header_filter_test(
337        name = static_test_name,
338        target_under_test = static_target_under_test,
339    )
340    return [
341        shared_test_name,
342        static_test_name,
343    ]
344
345_action_flags_present_with_tidy_test = action_flags_present_only_for_mnemonic_test_with_config_settings({
346    "@//build/bazel/flags/cc/tidy:allow_local_tidy_true": True,
347})
348
349def _test_aidl_interface_generated_cc_library_has_correct_tidy_checks_as_errors():
350    name = "aidl_interface_generated_cc_library_has_correct_tidy_checks_as_errors"
351    test_name = name + "_test"
352    aidl_library_target = name + "-cpp"
353    shared_target_under_test = aidl_library_target + "__internal_root"
354    shared_test_name = test_name + "_shared"
355    static_target_under_test = aidl_library_target + "_bp2build_cc_library_static"
356    static_test_name = test_name + "_static"
357
358    aidl_interface(
359        name = name,
360        cpp_config = {
361            "enabled": True,
362        },
363        unstable = True,
364        srcs = ["a/b/Foo.aidl"],
365        tags = ["manual"],
366    )
367
368    _action_flags_present_with_tidy_test(
369        name = shared_test_name,
370        target_under_test = shared_target_under_test,
371        mnemonics = ["ClangTidy"],
372        expected_flags = [
373            "-warnings-as-errors=*,-clang-analyzer-deadcode.DeadStores,-clang-analyzer-cplusplus.NewDeleteLeaks,-clang-analyzer-optin.performance.Padding,-bugprone-assignment-in-if-condition,-bugprone-branch-clone,-bugprone-signed-char-misuse,-misc-const-correctness,-bugprone-unsafe-functions,-cert-msc24-c,-cert-msc33-c,-modernize-type-traits,-readability-avoid-unconditional-preprocessor-if",
374        ],
375    )
376    _action_flags_present_with_tidy_test(
377        name = static_test_name,
378        target_under_test = static_target_under_test,
379        mnemonics = ["ClangTidy"],
380        expected_flags = [
381            "-warnings-as-errors=*,-clang-analyzer-deadcode.DeadStores,-clang-analyzer-cplusplus.NewDeleteLeaks,-clang-analyzer-optin.performance.Padding,-bugprone-assignment-in-if-condition,-bugprone-branch-clone,-bugprone-signed-char-misuse,-misc-const-correctness,-bugprone-unsafe-functions,-cert-msc24-c,-cert-msc33-c,-modernize-type-traits,-readability-avoid-unconditional-preprocessor-if",
382        ],
383    )
384    return [
385        shared_test_name,
386        static_test_name,
387    ]
388
389def _cc_library_has_flags_test_impl(ctx):
390    env = analysistest.begin(ctx)
391    target = analysistest.target_under_test(env)
392    actions = [a for a in target.actions if a.mnemonic == "CppCompile"]
393
394    asserts.true(
395        env,
396        len(actions) == 1,
397        "There should be one cc compile action: %s" % actions,
398    )
399
400    action = actions[0]
401    for flag in ctx.attr.expected_flags:
402        if flag not in action.argv:
403            fail("{} is not in list of flags for linking {}".format(flag, action.argv))
404
405    return analysistest.end(env)
406
407cc_library_has_flags_test = analysistest.make(
408    _cc_library_has_flags_test_impl,
409    attrs = {
410        "expected_flags": attr.string_list(),
411    },
412)
413
414def _test_aidl_interface_sets_flags_to_cc_libraries():
415    name = "aidl_interface_sets_flags_to_cc_libraries"
416    test_name = name + "_test"
417    aidl_library_target = name + "-ndk"
418    shared_target_under_test = aidl_library_target + "__internal_root_cpp"
419    shared_test_name = test_name + "_shared"
420    static_target_under_test = aidl_library_target + "_bp2build_cc_library_static_cpp"
421    static_test_name = test_name + "_static"
422
423    aidl_interface(
424        name = name,
425        ndk_config = {
426            "enabled": True,
427        },
428        srcs = ["Foo.aidl"],
429        unstable = True,
430        tags = ["manual"],
431    )
432
433    cc_library_has_flags_test(
434        name = shared_test_name,
435        target_under_test = shared_target_under_test,
436        expected_flags = [
437            "-DBINDER_STABILITY_SUPPORT",
438        ],
439    )
440
441    cc_library_has_flags_test(
442        name = static_test_name,
443        target_under_test = static_target_under_test,
444        expected_flags = [
445            "-DBINDER_STABILITY_SUPPORT",
446        ],
447    )
448
449    return [
450        shared_test_name,
451        static_test_name,
452    ]
453
454def aidl_interface_test_suite(name):
455    native.test_suite(
456        name = name,
457        tests = (
458            [
459                "//build/bazel/rules/aidl/testing:generated_targets_have_correct_srcs_test",
460                "//build/bazel/rules/aidl/testing:interface_macro_produces_all_targets_test",
461                _ndk_backend_test(),
462                _ndk_config_test(),
463                _next_version_for_unversioned_stable_interface_test(),
464                _next_version_for_versioned_stable_interface_test(),
465            ] +
466            _test_aidl_interface_generated_header_filter() +
467            _test_aidl_interface_passes_flags_to_aidl_libraries() +
468            _test_aidl_interface_sets_flags_to_cc_libraries() +
469            _test_aidl_interface_generated_cc_library_has_correct_tidy_checks_as_errors()
470        ),
471    )
472