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