| # Copyright 2020 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Bazel testing library asserts.""" |
| |
| load( |
| "//rules:providers.bzl", |
| "ResourcesNodeInfo", |
| "StarlarkAndroidResourcesInfo", |
| ) |
| |
| _ATTRS = dict( |
| expected_default_info = attr.string_list_dict(), |
| expected_java_info = attr.string_list_dict(), |
| expected_proguard_spec_provider = attr.string_list_dict(), |
| expected_starlark_android_resources_info = attr.label(), |
| expected_output_group_info = attr.string_list_dict(), |
| expected_native_libs_info = attr.label(), |
| ) |
| |
| def _expected_resources_node_info_impl(ctx): |
| return [ |
| ResourcesNodeInfo( |
| label = ctx.attr.label.label, |
| assets = ctx.files.assets, |
| assets_dir = ctx.attr.assets_dir, |
| assets_symbols = ctx.attr.assets_symbols if ctx.attr.assets_symbols else None, |
| compiled_assets = ctx.attr.compiled_assets if ctx.attr.compiled_assets else None, |
| compiled_resources = ctx.attr.compiled_resources if ctx.attr.compiled_resources else None, |
| r_txt = ctx.attr.r_txt if ctx.attr.r_txt else None, |
| manifest = ctx.attr.manifest if ctx.attr.manifest else None, |
| exports_manifest = ctx.attr.exports_manifest, |
| ), |
| ] |
| |
| _expected_resources_node_info = rule( |
| implementation = _expected_resources_node_info_impl, |
| attrs = dict( |
| label = attr.label(), |
| assets = attr.label_list(allow_files = True), |
| assets_dir = attr.string(), |
| assets_symbols = attr.string(), |
| compiled_assets = attr.string(), |
| compiled_resources = attr.string(), |
| r_txt = attr.string(), |
| manifest = attr.string(), |
| exports_manifest = attr.bool(default = False), |
| ), |
| ) |
| |
| def ExpectedResourcesNodeInfo( |
| label, |
| assets = [], |
| assets_dir = "", |
| assets_symbols = None, |
| compiled_assets = None, |
| compiled_resources = None, |
| r_txt = None, |
| manifest = None, |
| exports_manifest = False, |
| name = "unused"): # appease linter |
| name = label + str(assets) + assets_dir + str(assets_symbols) + str(compiled_resources) + str(exports_manifest) |
| name = ":" + "".join([c for c in name.elems() if c != ":"]) |
| |
| _expected_resources_node_info( |
| name = name[1:], |
| label = label, |
| assets = assets, |
| assets_dir = assets_dir, |
| assets_symbols = assets_symbols, |
| compiled_assets = compiled_assets, |
| compiled_resources = compiled_resources, |
| r_txt = r_txt, |
| manifest = manifest, |
| exports_manifest = exports_manifest, |
| ) |
| return name |
| |
| def _expected_starlark_android_resources_info_impl(ctx): |
| return [ |
| StarlarkAndroidResourcesInfo( |
| direct_resources_nodes = [node[ResourcesNodeInfo] for node in ctx.attr.direct_resources_nodes], |
| transitive_resources_nodes = [node[ResourcesNodeInfo] for node in ctx.attr.transitive_resources_nodes], |
| transitive_assets = ctx.attr.transitive_assets, |
| transitive_assets_symbols = ctx.attr.transitive_assets_symbols, |
| transitive_compiled_resources = ctx.attr.transitive_compiled_resources, |
| packages_to_r_txts = ctx.attr.packages_to_r_txts, |
| ), |
| ] |
| |
| _expected_starlark_android_resources_info = rule( |
| implementation = _expected_starlark_android_resources_info_impl, |
| attrs = dict( |
| direct_resources_nodes = attr.label_list( |
| providers = [ResourcesNodeInfo], |
| ), |
| transitive_resources_nodes = attr.label_list( |
| providers = [ResourcesNodeInfo], |
| ), |
| transitive_assets = attr.string_list(), |
| transitive_assets_symbols = attr.string_list(), |
| transitive_compiled_resources = attr.string_list(), |
| packages_to_r_txts = attr.string_list_dict(), |
| ), |
| ) |
| |
| def ExpectedStarlarkAndroidResourcesInfo( |
| direct_resources_nodes = None, |
| transitive_resources_nodes = [], |
| transitive_assets = [], |
| transitive_assets_symbols = [], |
| transitive_compiled_resources = [], |
| packages_to_r_txts = {}, |
| name = "unused"): # appease linter |
| name = (str(direct_resources_nodes) + str(transitive_resources_nodes) + str(transitive_assets) + |
| str(transitive_assets_symbols) + str(transitive_compiled_resources)) |
| name = ":" + "".join([c for c in name.elems() if c not in [":", "\\"]]) |
| _expected_starlark_android_resources_info( |
| name = name[1:], |
| direct_resources_nodes = direct_resources_nodes, |
| transitive_resources_nodes = transitive_resources_nodes, |
| transitive_assets = transitive_assets, |
| transitive_assets_symbols = transitive_assets_symbols, |
| transitive_compiled_resources = transitive_compiled_resources, |
| packages_to_r_txts = packages_to_r_txts, |
| ) |
| return name |
| |
| def _build_expected_resources_node_info(string): |
| parts = string.split(":") |
| if len(parts) != 5: |
| fail("Error: malformed resources_node_info string: %s" % string) |
| return dict( |
| label = parts[0], |
| assets = parts[1].split(",") if parts[1] else [], |
| assets_dir = parts[2], |
| assets_symbols = parts[3], |
| compiled_resources = parts[4], |
| ) |
| |
| def _expected_android_binary_native_libs_info_impl(ctx): |
| return _ExpectedAndroidBinaryNativeInfo( |
| transitive_native_libs = ctx.attr.transitive_native_libs, |
| native_libs_name = ctx.attr.native_libs_name, |
| native_libs = ctx.attr.native_libs, |
| ) |
| |
| _expected_android_binary_native_libs_info = rule( |
| implementation = _expected_android_binary_native_libs_info_impl, |
| attrs = { |
| "transitive_native_libs": attr.string_list(), |
| "native_libs_name": attr.string(), |
| "native_libs": attr.string_list_dict(), |
| }, |
| ) |
| |
| def ExpectedAndroidBinaryNativeLibsInfo(**kwargs): |
| name = "".join([str(kwargs[param]) for param in kwargs]) |
| name = ":" + "".join([c for c in name.elems() if c not in [" ", "[", "]", ":", "\\", "{", "\""]]) |
| _expected_android_binary_native_libs_info(name = name[1:], **kwargs) |
| return name |
| |
| _ExpectedAndroidBinaryNativeInfo = provider( |
| "Test provider to compare native deps info", |
| fields = ["native_libs", "native_libs_name", "transitive_native_libs"], |
| ) |
| |
| def _assert_native_libs_info(expected, actual): |
| expected = expected[_ExpectedAndroidBinaryNativeInfo] |
| if expected.native_libs_name: |
| _assert_file( |
| expected.native_libs_name, |
| actual.native_libs_name, |
| "AndroidBinaryNativeInfo.native_libs_name", |
| ) |
| for config in expected.native_libs: |
| if config not in actual.native_libs: |
| fail("Error for AndroidBinaryNativeInfo.native_libs: expected key %s was not found" % config) |
| _assert_files( |
| expected.native_libs[config], |
| actual.native_libs[config].to_list(), |
| "AndroidBinaryNativeInfo.native_libs." + config, |
| ) |
| _assert_files( |
| expected.transitive_native_libs, |
| actual.transitive_native_libs.to_list(), |
| "AndroidBinaryNativeInfo.transitive_native_libs", |
| ) |
| |
| def _assert_files(expected_file_names, actual_files, error_msg_field_name): |
| """Asserts that expected file names and actual list of files is equal. |
| |
| Args: |
| expected_file_names: The expected names of file basenames (no path), |
| actual_files: The actual list of files produced. |
| error_msg_field_name: The field the actual list of files is from. |
| """ |
| actual_file_names = [f.basename for f in actual_files] |
| if sorted(actual_file_names) == sorted(expected_file_names): |
| return |
| fail("""Error for %s, expected and actual file names are not the same: |
| expected file names: %s |
| actual files: %s |
| """ % (error_msg_field_name, expected_file_names, actual_files)) |
| |
| def _assert_file_objects(expected_files, actual_files, error_msg_field_name): |
| if sorted([f.basename for f in expected_files]) == sorted([f.basename for f in actual_files]): |
| return |
| fail("""Error for %s, expected and actual file names are not the same: |
| expected file names: %s |
| actual files: %s |
| """ % (error_msg_field_name, expected_files, actual_files)) |
| |
| def _assert_file_depset(expected_file_paths, actual_depset, error_msg_field_name, ignore_label_prefix = ""): |
| """Asserts that expected file short_paths and actual depset of files is equal. |
| |
| Args: |
| expected_file_paths: The expected file short_paths in depset order. |
| actual_depset: The actual depset produced. |
| error_msg_field_name: The field the actual depset is from. |
| ignore_label_prefix: Path prefix to ignore on actual file short_paths. |
| """ |
| actual_paths = [] # = [f.short_path for f in actual_depset.to_list()] |
| for f in actual_depset.to_list(): |
| path = f.short_path |
| if path.startswith(ignore_label_prefix): |
| path = path[len(ignore_label_prefix):] |
| actual_paths.append(path) |
| |
| if len(expected_file_paths) != len(actual_paths): |
| fail("""Error for %s, expected %d items, got %d items |
| expected: %s |
| actual: %s""" % ( |
| error_msg_field_name, |
| len(expected_file_paths), |
| len(actual_paths), |
| expected_file_paths, |
| actual_paths, |
| )) |
| for i in range(len(expected_file_paths)): |
| if expected_file_paths[i] != actual_paths[i]: |
| fail("""Error for %s, actual file depset ordering does not match expected ordering: |
| expected ordering: %s |
| actual ordering: %s |
| """ % (error_msg_field_name, expected_file_paths, actual_paths)) |
| |
| def _assert_empty(contents, error_msg_field_name): |
| """Asserts that the given is empty.""" |
| if len(contents) == 0: |
| return |
| fail("Error %s is not empty: %s" % (error_msg_field_name, contents)) |
| |
| def _assert_none(content, error_msg_field_name): |
| """Asserts that the given is None.""" |
| if content == None: |
| return |
| fail("Error %s is not None: %s" % (error_msg_field_name, content)) |
| |
| def _assert_java_info(expected, actual): |
| """Asserts that expected matches actual JavaInfo. |
| |
| Args: |
| expected: A dict containing fields of a JavaInfo that are compared against |
| the actual given JavaInfo. |
| actual: A JavaInfo. |
| """ |
| for key in expected.keys(): |
| if not hasattr(actual, key): |
| fail("Actual JavaInfo does not have attribute %s:\n%s" % (key, actual)) |
| actual_attr = getattr(actual, key) |
| expected_attr = expected[key] |
| |
| # files based asserts. |
| if key in [ |
| "compile_jars", |
| "runtime_output_jars", |
| "source_jars", |
| "transitive_compile_time_jars", |
| "transitive_runtime_jars", |
| "transitive_source_jars", |
| ]: |
| files = \ |
| actual_attr if type(actual_attr) == "list" else actual_attr.to_list() |
| _assert_files(expected_attr, files, "JavaInfo.%s" % key) |
| else: |
| fail("Error validation of JavaInfo.%s not implemented." % key) |
| |
| def _assert_default_info( |
| expected, |
| actual): |
| """Asserts that the DefaultInfo contains the expected values.""" |
| if not expected: |
| return |
| |
| # DefaultInfo.data_runfiles Assertions |
| _assert_empty( |
| actual.data_runfiles.empty_filenames.to_list(), |
| "DefaultInfo.data_runfiles.empty_filenames", |
| ) |
| _assert_files( |
| expected["runfiles"], |
| actual.data_runfiles.files.to_list(), |
| "DefaultInfo.data_runfiles.files", |
| ) |
| _assert_empty( |
| actual.data_runfiles.symlinks.to_list(), |
| "DefaultInfo.data_runfiles.symlinks", |
| ) |
| |
| # DefaultInfo.default_runfile Assertions |
| _assert_empty( |
| actual.default_runfiles.empty_filenames.to_list(), |
| "DefaultInfo.default_runfiles.empty_filenames", |
| ) |
| _assert_files( |
| expected["runfiles"], |
| actual.default_runfiles.files.to_list(), |
| "DefaultInfo.default_runfiles.files", |
| ) |
| _assert_empty( |
| actual.default_runfiles.symlinks.to_list(), |
| "DefaultInfo.default_runfiles.symlinks", |
| ) |
| |
| # DefaultInfo.files Assertion |
| _assert_files( |
| expected["files"], |
| actual.files.to_list(), |
| "DefaultInfo.files", |
| ) |
| |
| # DefaultInfo.files_to_run Assertions |
| _assert_none( |
| actual.files_to_run.executable, |
| "DefaultInfo.files_to_run.executable", |
| ) |
| _assert_none( |
| actual.files_to_run.runfiles_manifest, |
| "DefaultInfo.files_to_run.runfiles_manifest", |
| ) |
| |
| def _assert_proguard_spec_provider(expected, actual): |
| """Asserts that expected matches actual ProguardSpecProvider. |
| |
| Args: |
| expected: A dict containing fields of a ProguardSpecProvider that are |
| compared against the actual given ProguardSpecProvider. |
| actual: A ProguardSpecProvider. |
| """ |
| for key in expected.keys(): |
| if not hasattr(actual, key): |
| fail("Actual ProguardSpecProvider does not have attribute %s:\n%s" % (key, actual)) |
| actual_attr = getattr(actual, key) |
| expected_attr = expected[key] |
| if key in ["specs"]: |
| _assert_files( |
| expected_attr, |
| actual_attr.to_list(), |
| "ProguardSpecProvider.%s" % key, |
| ) |
| else: |
| fail("Error validation of ProguardSpecProvider.%s not implemented." % key) |
| |
| def _assert_string(expected, actual, error_msg): |
| if type(actual) != "string": |
| fail("Error for %s, actual value not of type string, got %s" % (error_msg, type(actual))) |
| if actual != expected: |
| fail("""Error for %s, expected and actual values are not the same: |
| expected value: %s |
| actual value: %s |
| """ % (error_msg, expected, actual)) |
| |
| def _assert_file(expected, actual, error_msg_field_name): |
| if actual == None and expected == None: |
| return |
| |
| if actual == None and expected != None: |
| fail("Error at %s, expected %s but got None" % (error_msg_field_name, expected)) |
| |
| if type(actual) != "File": |
| fail("Error at %s, expected a File but got %s" % (error_msg_field_name, type(actual))) |
| |
| if actual != None and expected == None: |
| fail("Error at %s, expected None but got %s" % (error_msg_field_name, actual.short_path)) |
| |
| ignore_label_prefix = actual.owner.package + "/" |
| actual_path = actual.short_path |
| if actual_path.startswith(ignore_label_prefix): |
| actual_path = actual_path[len(ignore_label_prefix):] |
| _assert_string(expected, actual_path, error_msg_field_name) |
| |
| def _assert_resources_node_info(expected, actual): |
| if type(actual.label) != "Label": |
| fail("Error for ResourcesNodeInfo.label, expected type Label, actual type is %s" % type(actual.label)) |
| _assert_string(expected.label.name, actual.label.name, "ResourcesNodeInfo.label.name") |
| |
| if type(actual.assets) != "depset": |
| fail("Error for ResourcesNodeInfo.assets, expected type depset, actual type is %s" % type(actual.assets)) |
| |
| # TODO(djwhang): Align _assert_file_objects and _assert_file_depset to work |
| # in a similar manner. For now, we will just call to_list() as this field |
| # was list prior to this change. |
| _assert_file_objects(expected.assets, actual.assets.to_list(), "ResourcesNodeInfo.assets") |
| |
| _assert_string(expected.assets_dir, actual.assets_dir, "ResourcesNodeInfo.assets_dir") |
| |
| _assert_file( |
| expected.assets_symbols, |
| actual.assets_symbols, |
| "ResourcesNodeInfo.assets_symbols", |
| ) |
| |
| _assert_file( |
| expected.compiled_assets, |
| actual.compiled_assets, |
| "ResourcesNodeInfo.compiled_assets", |
| ) |
| |
| _assert_file( |
| expected.compiled_resources, |
| actual.compiled_resources, |
| "ResourcesNodeInfo.compiled_resources", |
| ) |
| |
| _assert_file( |
| expected.r_txt, |
| actual.r_txt, |
| "ResourcesNodeInfo.r_txt", |
| ) |
| |
| _assert_file( |
| expected.manifest, |
| actual.manifest, |
| "ResourcesNodeInfo.manifest", |
| ) |
| |
| if type(actual.exports_manifest) != "bool": |
| fail("Error for ResourcesNodeInfo.exports_manifest, expected type bool, actual type is %s" % type(actual.exports_manifest)) |
| if expected.exports_manifest != actual.exports_manifest: |
| fail("""Error for ResourcesNodeInfo.exports_manifest, expected and actual values are not the same: |
| expected value: %s |
| actual value: %s |
| """ % (expected.exports_manifest, actual.exports_manifest)) |
| |
| def _assert_resources_node_info_depset(expected_resources_node_infos, actual_depset, error_msg): |
| actual_resources_node_infos = actual_depset.to_list() |
| if len(expected_resources_node_infos) != len(actual_resources_node_infos): |
| fail( |
| "Error for StarlarkAndroidResourcesInfo.%s, expected size of list to be %d, got %d:\nExpected: %s\nActual: %s" % |
| ( |
| error_msg, |
| len(expected_resources_node_infos), |
| len(actual_resources_node_infos), |
| [node.label for node in expected_resources_node_infos], |
| [node.label for node in actual_resources_node_infos], |
| ), |
| ) |
| for i in range(len(actual_resources_node_infos)): |
| _assert_resources_node_info(expected_resources_node_infos[i], actual_resources_node_infos[i]) |
| |
| def _assert_starlark_android_resources_info(expected, actual, label_under_test): |
| _assert_resources_node_info_depset( |
| expected.direct_resources_nodes, |
| actual.direct_resources_nodes, |
| "direct_resources_nodes", |
| ) |
| |
| _assert_resources_node_info_depset( |
| expected.transitive_resources_nodes, |
| actual.transitive_resources_nodes, |
| "transitive_resources_nodes", |
| ) |
| |
| # Use the package from the target under test to shrink actual paths being compared down to the |
| # name of the target. |
| ignore_label_prefix = label_under_test.package + "/" |
| |
| _assert_file_depset( |
| expected.transitive_assets, |
| actual.transitive_assets, |
| "StarlarkAndroidResourcesInfo.transitive_assets", |
| ignore_label_prefix, |
| ) |
| _assert_file_depset( |
| expected.transitive_assets_symbols, |
| actual.transitive_assets_symbols, |
| "StarlarkAndroidResourcesInfo.transitive_assets_symbols", |
| ignore_label_prefix, |
| ) |
| _assert_file_depset( |
| expected.transitive_compiled_resources, |
| actual.transitive_compiled_resources, |
| "StarlarkAndroidResourcesInfo.transitive_compiled_resources", |
| ignore_label_prefix, |
| ) |
| for pkg, value in expected.packages_to_r_txts.items(): |
| if pkg in actual.packages_to_r_txts: |
| _assert_file_depset( |
| value, |
| actual.packages_to_r_txts[pkg], |
| "StarlarkAndroidResourcesInfo.packages_to_r_txts[%s]" % pkg, |
| ignore_label_prefix, |
| ) |
| else: |
| fail("Error for StarlarkAndroidResourceInfo.packages_to_r_txts, expected key %s was not found" % pkg) |
| |
| _R_CLASS_ATTRS = dict( |
| _r_class_check = attr.label( |
| default = "//test/utils/java/com/google:RClassChecker_deploy.jar", |
| executable = True, |
| allow_files = True, |
| cfg = "exec", |
| ), |
| expected_r_class_fields = attr.string_list(), |
| ) |
| |
| def _assert_output_group_info(expected, actual): |
| for key in expected: |
| actual_attr = getattr(actual, key, None) |
| if actual_attr == None: # both empty depset and list will fail. |
| fail("%s is not defined in OutputGroupInfo: %s" % (key, actual)) |
| _assert_files( |
| expected[key], |
| actual_attr.to_list(), |
| "OutputGroupInfo." + key, |
| ) |
| |
| def _is_suffix_sublist(full, suffixes): |
| """Returns whether suffixes is a sublist of suffixes of full.""" |
| for (fi, _) in enumerate(full): |
| sublist_match = True |
| for (si, sv) in enumerate(suffixes): |
| if (fi + si >= len(full)) or not full[fi + si].endswith(sv): |
| sublist_match = False |
| break |
| if sublist_match: |
| return True |
| return False |
| |
| def _check_actions(inspect, actions): |
| for mnemonic, expected_argvs in inspect.items(): |
| # Action mnemonic is not unique, even in the context of a target, hence |
| # it is necessary to find all actions and compare argv. If there are no |
| # matches among the actions that match the mnemonic, fail and present |
| # all the possible actions that could have matched. |
| mnemonic_matching_actions = [] |
| mnemonic_match = False |
| for _, value in actions.by_file.items(): |
| if mnemonic != value.mnemonic: |
| continue |
| mnemonic_match = True |
| |
| if _is_suffix_sublist(value.argv, expected_argvs): |
| # When there is a match, clear the actions stored for displaying |
| # an error messaage. |
| mnemonic_matching_actions = [] |
| break |
| else: |
| mnemonic_matching_actions.append(value) |
| |
| if not mnemonic_match: |
| fail("%s action not found." % mnemonic) |
| if mnemonic_matching_actions: |
| # If there are mnemonic_matching_actions, then the argvs did not |
| # align. Fail but show the other actions that were created. |
| error_message = ( |
| "%s with the following argv not found: %s\nSimilar actions:\n" % |
| (mnemonic, expected_argvs) |
| ) |
| for i, action in enumerate(mnemonic_matching_actions): |
| error_message += ( |
| "%d. Progress Message: %s\n Argv: %s\n\n" % |
| (i + 1, action, action.argv) |
| ) |
| fail(error_message) |
| |
| _ACTIONS_ATTRS = dict( |
| inspect_actions = attr.string_list_dict(), |
| ) |
| |
| asserts = struct( |
| provider = struct( |
| attrs = _ATTRS, |
| default_info = _assert_default_info, |
| java_info = _assert_java_info, |
| proguard_spec_provider = _assert_proguard_spec_provider, |
| starlark_android_resources_info = _assert_starlark_android_resources_info, |
| output_group_info = _assert_output_group_info, |
| native_libs_info = _assert_native_libs_info, |
| ), |
| files = _assert_files, |
| r_class = struct( |
| attrs = _R_CLASS_ATTRS, |
| ), |
| actions = struct( |
| attrs = _ACTIONS_ATTRS, |
| check_actions = _check_actions, |
| ), |
| ) |