xref: /aosp_15_r20/external/bazelbuild-rules_android/test/utils/asserts.bzl (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1# Copyright 2020 The Bazel Authors. All rights reserved.
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
15"""Bazel testing library asserts."""
16
17load(
18    "//rules:providers.bzl",
19    "ResourcesNodeInfo",
20    "StarlarkAndroidResourcesInfo",
21)
22
23_ATTRS = dict(
24    expected_default_info = attr.string_list_dict(),
25    expected_java_info = attr.string_list_dict(),
26    expected_proguard_spec_provider = attr.string_list_dict(),
27    expected_starlark_android_resources_info = attr.label(),
28    expected_output_group_info = attr.string_list_dict(),
29    expected_native_libs_info = attr.label(),
30    expected_generated_extension_registry_provider = attr.string_list_dict(),
31)
32
33def _expected_resources_node_info_impl(ctx):
34    return [
35        ResourcesNodeInfo(
36            label = ctx.attr.label.label,
37            assets = ctx.files.assets,
38            assets_dir = ctx.attr.assets_dir,
39            assets_symbols = ctx.attr.assets_symbols if ctx.attr.assets_symbols else None,
40            compiled_assets = ctx.attr.compiled_assets if ctx.attr.compiled_assets else None,
41            compiled_resources = ctx.attr.compiled_resources if ctx.attr.compiled_resources else None,
42            r_txt = ctx.attr.r_txt if ctx.attr.r_txt else None,
43            manifest = ctx.attr.manifest if ctx.attr.manifest else None,
44            exports_manifest = ctx.attr.exports_manifest,
45        ),
46    ]
47
48_expected_resources_node_info = rule(
49    implementation = _expected_resources_node_info_impl,
50    attrs = dict(
51        label = attr.label(),
52        assets = attr.label_list(allow_files = True),
53        assets_dir = attr.string(),
54        assets_symbols = attr.string(),
55        compiled_assets = attr.string(),
56        compiled_resources = attr.string(),
57        r_txt = attr.string(),
58        manifest = attr.string(),
59        exports_manifest = attr.bool(default = False),
60    ),
61)
62
63def ExpectedResourcesNodeInfo(
64        label,
65        assets = [],
66        assets_dir = "",
67        assets_symbols = None,
68        compiled_assets = None,
69        compiled_resources = None,
70        r_txt = None,
71        manifest = None,
72        exports_manifest = False,
73        name = "unused"):  # appease linter
74    name = label + str(assets) + assets_dir + str(assets_symbols) + str(compiled_resources) + str(exports_manifest)
75    name = ":" + "".join([c for c in name.elems() if c != ":"])
76
77    _expected_resources_node_info(
78        name = name[1:],
79        label = label,
80        assets = assets,
81        assets_dir = assets_dir,
82        assets_symbols = assets_symbols,
83        compiled_assets = compiled_assets,
84        compiled_resources = compiled_resources,
85        r_txt = r_txt,
86        manifest = manifest,
87        exports_manifest = exports_manifest,
88    )
89    return name
90
91def _expected_starlark_android_resources_info_impl(ctx):
92    return [
93        StarlarkAndroidResourcesInfo(
94            direct_resources_nodes = [node[ResourcesNodeInfo] for node in ctx.attr.direct_resources_nodes],
95            transitive_resources_nodes = [node[ResourcesNodeInfo] for node in ctx.attr.transitive_resources_nodes],
96            transitive_assets = ctx.attr.transitive_assets,
97            transitive_assets_symbols = ctx.attr.transitive_assets_symbols,
98            transitive_compiled_resources = ctx.attr.transitive_compiled_resources,
99            packages_to_r_txts = ctx.attr.packages_to_r_txts,
100        ),
101    ]
102
103_expected_starlark_android_resources_info = rule(
104    implementation = _expected_starlark_android_resources_info_impl,
105    attrs = dict(
106        direct_resources_nodes = attr.label_list(
107            providers = [ResourcesNodeInfo],
108        ),
109        transitive_resources_nodes = attr.label_list(
110            providers = [ResourcesNodeInfo],
111        ),
112        transitive_assets = attr.string_list(),
113        transitive_assets_symbols = attr.string_list(),
114        transitive_compiled_resources = attr.string_list(),
115        packages_to_r_txts = attr.string_list_dict(),
116    ),
117)
118
119def ExpectedStarlarkAndroidResourcesInfo(
120        direct_resources_nodes = None,
121        transitive_resources_nodes = [],
122        transitive_assets = [],
123        transitive_assets_symbols = [],
124        transitive_compiled_resources = [],
125        packages_to_r_txts = {},
126        name = "unused"):  # appease linter
127    name = (str(direct_resources_nodes) + str(transitive_resources_nodes) + str(transitive_assets) +
128            str(transitive_assets_symbols) + str(transitive_compiled_resources))
129    name = ":" + "".join([c for c in name.elems() if c not in [":", "\\"]])
130    _expected_starlark_android_resources_info(
131        name = name[1:],
132        direct_resources_nodes = direct_resources_nodes,
133        transitive_resources_nodes = transitive_resources_nodes,
134        transitive_assets = transitive_assets,
135        transitive_assets_symbols = transitive_assets_symbols,
136        transitive_compiled_resources = transitive_compiled_resources,
137        packages_to_r_txts = packages_to_r_txts,
138    )
139    return name
140
141def _build_expected_resources_node_info(string):
142    parts = string.split(":")
143    if len(parts) != 5:
144        fail("Error: malformed resources_node_info string: %s" % string)
145    return dict(
146        label = parts[0],
147        assets = parts[1].split(",") if parts[1] else [],
148        assets_dir = parts[2],
149        assets_symbols = parts[3],
150        compiled_resources = parts[4],
151    )
152
153def _expected_android_binary_native_libs_info_impl(ctx):
154    return _ExpectedAndroidBinaryNativeInfo(
155        transitive_native_libs = ctx.attr.transitive_native_libs,
156        native_libs_name = ctx.attr.native_libs_name,
157        native_libs = ctx.attr.native_libs,
158    )
159
160_expected_android_binary_native_libs_info = rule(
161    implementation = _expected_android_binary_native_libs_info_impl,
162    attrs = {
163        "transitive_native_libs": attr.string_list(),
164        "native_libs_name": attr.string(),
165        "native_libs": attr.string_list_dict(),
166    },
167)
168
169def ExpectedAndroidBinaryNativeLibsInfo(**kwargs):
170    name = "".join([str(kwargs[param]) for param in kwargs])
171    name = ":" + "".join([c for c in name.elems() if c not in [" ", "[", "]", ":", "\\", "{", "\""]])
172    _expected_android_binary_native_libs_info(name = name[1:], **kwargs)
173    return name
174
175_ExpectedAndroidBinaryNativeInfo = provider(
176    "Test provider to compare native deps info",
177    fields = ["native_libs", "native_libs_name", "transitive_native_libs"],
178)
179
180def _assert_native_libs_info(expected, actual):
181    expected = expected[_ExpectedAndroidBinaryNativeInfo]
182    if expected.native_libs_name:
183        _assert_file(
184            expected.native_libs_name,
185            actual.native_libs_name,
186            "AndroidBinaryNativeInfo.native_libs_name",
187        )
188    for config in expected.native_libs:
189        if config not in actual.native_libs:
190            fail("Error for AndroidBinaryNativeInfo.native_libs: expected key %s was not found" % config)
191        _assert_files(
192            expected.native_libs[config],
193            actual.native_libs[config].to_list(),
194            "AndroidBinaryNativeInfo.native_libs." + config,
195        )
196    _assert_files(
197        expected.transitive_native_libs,
198        actual.transitive_native_libs.to_list(),
199        "AndroidBinaryNativeInfo.transitive_native_libs",
200    )
201
202def _assert_files(expected_file_names, actual_files, error_msg_field_name):
203    """Asserts that expected file names and actual list of files is equal.
204
205    Args:
206      expected_file_names: The expected names of file basenames (no path),
207      actual_files: The actual list of files produced.
208      error_msg_field_name: The field the actual list of files is from.
209    """
210    actual_file_names = [f.basename for f in actual_files]
211    if sorted(actual_file_names) == sorted(expected_file_names):
212        return
213    fail("""Error for %s, expected and actual file names are not the same:
214expected file names: %s
215actual files: %s
216""" % (error_msg_field_name, expected_file_names, actual_files))
217
218def _assert_file_objects(expected_files, actual_files, error_msg_field_name):
219    if sorted([f.basename for f in expected_files]) == sorted([f.basename for f in actual_files]):
220        return
221    fail("""Error for %s, expected and actual file names are not the same:
222expected file names: %s
223actual files: %s
224""" % (error_msg_field_name, expected_files, actual_files))
225
226def _assert_file_depset(expected_file_paths, actual_depset, error_msg_field_name, ignore_label_prefix = ""):
227    """Asserts that expected file short_paths and actual depset of files is equal.
228
229    Args:
230      expected_file_paths: The expected file short_paths in depset order.
231      actual_depset: The actual depset produced.
232      error_msg_field_name: The field the actual depset is from.
233      ignore_label_prefix: Path prefix to ignore on actual file short_paths.
234    """
235    actual_paths = []  # = [f.short_path for f in actual_depset.to_list()]
236    for f in actual_depset.to_list():
237        path = f.short_path
238        if path.startswith(ignore_label_prefix):
239            path = path[len(ignore_label_prefix):]
240        actual_paths.append(path)
241
242    if len(expected_file_paths) != len(actual_paths):
243        fail("""Error for %s, expected %d items, got %d items
244expected: %s
245actual: %s""" % (
246            error_msg_field_name,
247            len(expected_file_paths),
248            len(actual_paths),
249            expected_file_paths,
250            actual_paths,
251        ))
252    for i in range(len(expected_file_paths)):
253        if expected_file_paths[i] != actual_paths[i]:
254            fail("""Error for %s, actual file depset ordering does not match expected ordering:
255expected ordering: %s
256actual ordering: %s
257""" % (error_msg_field_name, expected_file_paths, actual_paths))
258
259def _assert_empty(contents, error_msg_field_name):
260    """Asserts that the given is empty."""
261    if len(contents) == 0:
262        return
263    fail("Error %s is not empty: %s" % (error_msg_field_name, contents))
264
265def _assert_none(content, error_msg_field_name):
266    """Asserts that the given is None."""
267    if content == None:
268        return
269    fail("Error %s is not None: %s" % (error_msg_field_name, content))
270
271def _assert_java_info(expected, actual):
272    """Asserts that expected matches actual JavaInfo.
273
274    Args:
275      expected: A dict containing fields of a JavaInfo that are compared against
276        the actual given JavaInfo.
277      actual: A JavaInfo.
278    """
279    for key in expected.keys():
280        if not hasattr(actual, key):
281            fail("Actual JavaInfo does not have attribute %s:\n%s" % (key, actual))
282        actual_attr = getattr(actual, key)
283        expected_attr = expected[key]
284
285        # files based asserts.
286        if key in [
287            "compile_jars",
288            "runtime_output_jars",
289            "source_jars",
290            "transitive_compile_time_jars",
291            "transitive_runtime_jars",
292            "transitive_source_jars",
293        ]:
294            files = \
295                actual_attr if type(actual_attr) == "list" else actual_attr.to_list()
296            _assert_files(expected_attr, files, "JavaInfo.%s" % key)
297        else:
298            fail("Error validation of JavaInfo.%s not implemented." % key)
299
300def _assert_default_info(
301        expected,
302        actual):
303    """Asserts that the DefaultInfo contains the expected values."""
304    if not expected:
305        return
306
307    # DefaultInfo.data_runfiles Assertions
308    _assert_empty(
309        actual.data_runfiles.empty_filenames.to_list(),
310        "DefaultInfo.data_runfiles.empty_filenames",
311    )
312    _assert_files(
313        expected["runfiles"],
314        actual.data_runfiles.files.to_list(),
315        "DefaultInfo.data_runfiles.files",
316    )
317    _assert_empty(
318        actual.data_runfiles.symlinks.to_list(),
319        "DefaultInfo.data_runfiles.symlinks",
320    )
321
322    # DefaultInfo.default_runfile Assertions
323    _assert_empty(
324        actual.default_runfiles.empty_filenames.to_list(),
325        "DefaultInfo.default_runfiles.empty_filenames",
326    )
327    _assert_files(
328        expected["runfiles"],
329        actual.default_runfiles.files.to_list(),
330        "DefaultInfo.default_runfiles.files",
331    )
332    _assert_empty(
333        actual.default_runfiles.symlinks.to_list(),
334        "DefaultInfo.default_runfiles.symlinks",
335    )
336
337    # DefaultInfo.files Assertion
338    _assert_files(
339        expected["files"],
340        actual.files.to_list(),
341        "DefaultInfo.files",
342    )
343
344    # DefaultInfo.files_to_run Assertions
345    _assert_none(
346        actual.files_to_run.executable,
347        "DefaultInfo.files_to_run.executable",
348    )
349    _assert_none(
350        actual.files_to_run.runfiles_manifest,
351        "DefaultInfo.files_to_run.runfiles_manifest",
352    )
353
354def _assert_proguard_spec_provider(expected, actual):
355    """Asserts that expected matches actual ProguardSpecProvider.
356
357    Args:
358      expected: A dict containing fields of a ProguardSpecProvider that are
359        compared against the actual given ProguardSpecProvider.
360      actual: A ProguardSpecProvider.
361    """
362    for key in expected.keys():
363        if not hasattr(actual, key):
364            fail("Actual ProguardSpecProvider does not have attribute %s:\n%s" % (key, actual))
365        actual_attr = getattr(actual, key)
366        expected_attr = expected[key]
367        if key in ["specs"]:
368            _assert_files(
369                expected_attr,
370                actual_attr.to_list(),
371                "ProguardSpecProvider.%s" % key,
372            )
373        else:
374            fail("Error validation of ProguardSpecProvider.%s not implemented." % key)
375
376def _assert_string(expected, actual, error_msg):
377    if type(actual) != "string":
378        fail("Error for %s, actual value not of type string, got %s" % (error_msg, type(actual)))
379    if actual != expected:
380        fail("""Error for %s, expected and actual values are not the same:
381expected value: %s
382actual value: %s
383""" % (error_msg, expected, actual))
384
385def _assert_file(expected, actual, error_msg_field_name):
386    if actual == None and expected == None:
387        return
388
389    if actual == None and expected != None:
390        fail("Error at %s, expected %s but got None" % (error_msg_field_name, expected))
391
392    if type(actual) != "File":
393        fail("Error at %s, expected a File but got %s" % (error_msg_field_name, type(actual)))
394
395    if actual != None and expected == None:
396        fail("Error at %s, expected None but got %s" % (error_msg_field_name, actual.short_path))
397
398    ignore_label_prefix = actual.owner.package + "/"
399    actual_path = actual.short_path
400    if actual_path.startswith(ignore_label_prefix):
401        actual_path = actual_path[len(ignore_label_prefix):]
402    _assert_string(expected, actual_path, error_msg_field_name)
403
404def _assert_resources_node_info(expected, actual):
405    if type(actual.label) != "Label":
406        fail("Error for ResourcesNodeInfo.label, expected type Label, actual type is %s" % type(actual.label))
407    _assert_string(expected.label.name, actual.label.name, "ResourcesNodeInfo.label.name")
408
409    if type(actual.assets) != "depset":
410        fail("Error for ResourcesNodeInfo.assets, expected type depset, actual type is %s" % type(actual.assets))
411
412    # TODO(djwhang): Align _assert_file_objects and _assert_file_depset to work
413    # in a similar manner. For now, we will just call to_list() as this field
414    # was list prior to this change.
415    _assert_file_objects(expected.assets, actual.assets.to_list(), "ResourcesNodeInfo.assets")
416
417    _assert_string(expected.assets_dir, actual.assets_dir, "ResourcesNodeInfo.assets_dir")
418
419    _assert_file(
420        expected.assets_symbols,
421        actual.assets_symbols,
422        "ResourcesNodeInfo.assets_symbols",
423    )
424
425    _assert_file(
426        expected.compiled_assets,
427        actual.compiled_assets,
428        "ResourcesNodeInfo.compiled_assets",
429    )
430
431    _assert_file(
432        expected.compiled_resources,
433        actual.compiled_resources,
434        "ResourcesNodeInfo.compiled_resources",
435    )
436
437    _assert_file(
438        expected.r_txt,
439        actual.r_txt,
440        "ResourcesNodeInfo.r_txt",
441    )
442
443    _assert_file(
444        expected.manifest,
445        actual.manifest,
446        "ResourcesNodeInfo.manifest",
447    )
448
449    if type(actual.exports_manifest) != "bool":
450        fail("Error for ResourcesNodeInfo.exports_manifest, expected type bool, actual type is %s" % type(actual.exports_manifest))
451    if expected.exports_manifest != actual.exports_manifest:
452        fail("""Error for ResourcesNodeInfo.exports_manifest, expected and actual values are not the same:
453expected value: %s
454actual value: %s
455""" % (expected.exports_manifest, actual.exports_manifest))
456
457def _assert_resources_node_info_depset(expected_resources_node_infos, actual_depset, error_msg):
458    actual_resources_node_infos = actual_depset.to_list()
459    if len(expected_resources_node_infos) != len(actual_resources_node_infos):
460        fail(
461            "Error for StarlarkAndroidResourcesInfo.%s, expected size of list to be %d, got %d:\nExpected: %s\nActual: %s" %
462            (
463                error_msg,
464                len(expected_resources_node_infos),
465                len(actual_resources_node_infos),
466                [node.label for node in expected_resources_node_infos],
467                [node.label for node in actual_resources_node_infos],
468            ),
469        )
470    for i in range(len(actual_resources_node_infos)):
471        _assert_resources_node_info(expected_resources_node_infos[i], actual_resources_node_infos[i])
472
473def _assert_starlark_android_resources_info(expected, actual, label_under_test):
474    _assert_resources_node_info_depset(
475        expected.direct_resources_nodes,
476        actual.direct_resources_nodes,
477        "direct_resources_nodes",
478    )
479
480    _assert_resources_node_info_depset(
481        expected.transitive_resources_nodes,
482        actual.transitive_resources_nodes,
483        "transitive_resources_nodes",
484    )
485
486    # Use the package from the target under test to shrink actual paths being compared down to the
487    # name of the target.
488    ignore_label_prefix = label_under_test.package + "/"
489
490    _assert_file_depset(
491        expected.transitive_assets,
492        actual.transitive_assets,
493        "StarlarkAndroidResourcesInfo.transitive_assets",
494        ignore_label_prefix,
495    )
496    _assert_file_depset(
497        expected.transitive_assets_symbols,
498        actual.transitive_assets_symbols,
499        "StarlarkAndroidResourcesInfo.transitive_assets_symbols",
500        ignore_label_prefix,
501    )
502    _assert_file_depset(
503        expected.transitive_compiled_resources,
504        actual.transitive_compiled_resources,
505        "StarlarkAndroidResourcesInfo.transitive_compiled_resources",
506        ignore_label_prefix,
507    )
508    for pkg, value in expected.packages_to_r_txts.items():
509        if pkg in actual.packages_to_r_txts:
510            _assert_file_depset(
511                value,
512                actual.packages_to_r_txts[pkg],
513                "StarlarkAndroidResourcesInfo.packages_to_r_txts[%s]" % pkg,
514                ignore_label_prefix,
515            )
516        else:
517            fail("Error for StarlarkAndroidResourceInfo.packages_to_r_txts, expected key %s was not found" % pkg)
518
519_R_CLASS_ATTRS = dict(
520    _r_class_check = attr.label(
521        default = "//test/utils/java/com/google:RClassChecker_deploy.jar",
522        executable = True,
523        allow_files = True,
524        cfg = "exec",
525    ),
526    expected_r_class_fields = attr.string_list(),
527)
528
529def _assert_output_group_info(expected, actual):
530    for key in expected:
531        actual_attr = getattr(actual, key, None)
532        if actual_attr == None:  # both empty depset and list will fail.
533            fail("%s is not defined in OutputGroupInfo: %s" % (key, actual))
534        _assert_files(
535            expected[key],
536            actual_attr.to_list(),
537            "OutputGroupInfo." + key,
538        )
539
540def _assert_generated_extension_registry_provider(expected, actual):
541    if expected and not actual:
542        fail("GeneratedExtensionRegistryProvider was expected but not found!")
543    for key in expected:
544        actual_attr = getattr(actual, key, None)
545        if actual_attr == None:  # both empty depset and list will fail.
546            fail("%s is not defined in OutputGroupInfo: %s" % (key, actual))
547
548        _assert_files(
549            expected[key],
550            [actual_attr] if type(actual_attr) != "depset" else actual_attr.to_list(),
551            "GeneratedExtensionRegistryProvider." + key,
552        )
553
554def _is_suffix_sublist(full, suffixes):
555    """Returns whether suffixes is a sublist of suffixes of full."""
556    for (fi, _) in enumerate(full):
557        sublist_match = True
558        for (si, sv) in enumerate(suffixes):
559            if (fi + si >= len(full)) or not full[fi + si].endswith(sv):
560                sublist_match = False
561                break
562        if sublist_match:
563            return True
564    return False
565
566def _check_actions(inspect, actions):
567    for mnemonic, expected_argvs in inspect.items():
568        # Action mnemonic is not unique, even in the context of a target, hence
569        # it is necessary to find all actions and compare argv. If there are no
570        # matches among the actions that match the mnemonic, fail and present
571        # all the possible actions that could have matched.
572        mnemonic_matching_actions = []
573        mnemonic_match = False
574        for _, value in actions.by_file.items():
575            # TODO(b/130571505): Remove this after SpawnActionTemplate is supported in Starlark
576            if not (hasattr(value, "mnemonic") and hasattr(value, "argv")):
577                continue
578
579            if mnemonic != value.mnemonic:
580                continue
581            mnemonic_match = True
582
583            if _is_suffix_sublist(value.argv, expected_argvs):
584                # When there is a match, clear the actions stored for displaying
585                # an error messaage.
586                mnemonic_matching_actions = []
587                break
588            else:
589                mnemonic_matching_actions.append(value)
590
591        if not mnemonic_match:
592            fail("%s action not found." % mnemonic)
593        if mnemonic_matching_actions:
594            # If there are mnemonic_matching_actions, then the argvs did not
595            # align. Fail but show the other actions that were created.
596            error_message = (
597                "%s with the following argv not found: %s\nSimilar actions:\n" %
598                (mnemonic, expected_argvs)
599            )
600            for i, action in enumerate(mnemonic_matching_actions):
601                error_message += (
602                    "%d. Progress Message: %s\n   Argv:             %s\n\n" %
603                    (i + 1, action, action.argv)
604                )
605            fail(error_message)
606
607_ACTIONS_ATTRS = dict(
608    inspect_actions = attr.string_list_dict(),
609)
610
611asserts = struct(
612    provider = struct(
613        attrs = _ATTRS,
614        default_info = _assert_default_info,
615        java_info = _assert_java_info,
616        proguard_spec_provider = _assert_proguard_spec_provider,
617        starlark_android_resources_info = _assert_starlark_android_resources_info,
618        output_group_info = _assert_output_group_info,
619        native_libs_info = _assert_native_libs_info,
620        generated_extension_registry_provider = _assert_generated_extension_registry_provider,
621    ),
622    files = _assert_files,
623    r_class = struct(
624        attrs = _R_CLASS_ATTRS,
625    ),
626    actions = struct(
627        attrs = _ACTIONS_ATTRS,
628        check_actions = _check_actions,
629    ),
630)
631