879 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			879 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #! /usr/bin/env python3
 | |
| #
 | |
| # Copyright 2020 The Android Open Source Project
 | |
| #
 | |
| # 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.
 | |
| 
 | |
| # Regenerate some ART test related files.
 | |
| 
 | |
| # This script handles only a subset of ART run-tests at the moment; additional
 | |
| # cases will be added later.
 | |
| 
 | |
| import argparse
 | |
| import copy
 | |
| import collections
 | |
| import itertools
 | |
| import json
 | |
| import logging
 | |
| import os
 | |
| import re
 | |
| import sys
 | |
| import textwrap
 | |
| import xml.dom.minidom
 | |
| 
 | |
| logging.basicConfig(format='%(levelname)s: %(message)s')
 | |
| 
 | |
| ME = os.path.basename(sys.argv[0])
 | |
| 
 | |
| # Common advisory placed at the top of all generated files.
 | |
| ADVISORY = f"Generated by `{ME}`. Do not edit manually."
 | |
| 
 | |
| # Default indentation unit.
 | |
| INDENT = "  "
 | |
| 
 | |
| # Indentation unit for XML files.
 | |
| XML_INDENT = "    "
 | |
| 
 | |
| def reindent(str, indent = ""):
 | |
|   """Reindent literal string while removing common leading spaces."""
 | |
|   return textwrap.indent(textwrap.dedent(str), indent)
 | |
| 
 | |
| def copyright_header_text(year):
 | |
|   """Return the copyright header text used in XML files."""
 | |
|   return reindent(f"""\
 | |
|     Copyright (C) {year} The Android Open Source Project
 | |
| 
 | |
|         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.
 | |
|     """, " ")
 | |
| 
 | |
| def split_list(l, n):
 | |
|   """Return a list of `n` sublists of (contiguous) elements of list `l`."""
 | |
|   assert n > 0
 | |
|   (d, m) = divmod(len(l), n)
 | |
|   # If the length of `l` is divisible by `n`, use that that divisor (`d`) as size of each sublist;
 | |
|   # otherwise, the next integer value (`d + 1`).
 | |
|   s = d if m == 0 else d + 1
 | |
|   result = [l[i:i + s] for i in range(0, len(l), s)]
 | |
|   assert len(result) == n
 | |
|   return result
 | |
| 
 | |
| # The prefix used in the Soong module name of all ART run-tests.
 | |
| ART_RUN_TEST_MODULE_NAME_PREFIX = "art-run-test-"
 | |
| 
 | |
| # Number of shards used to declare ART run-tests in the sharded ART MTS test plan.
 | |
| NUM_MTS_ART_RUN_TEST_SHARDS = 1
 | |
| 
 | |
| # Curated list of tests that have a custom `run` script, but that are
 | |
| # known to work fine with the default test execution strategy (i.e.
 | |
| # when ignoring their `run` script), even if not exactly as they would
 | |
| # with the original ART run-test harness.
 | |
| runnable_test_exceptions = frozenset([
 | |
|   "055-enum-performance",
 | |
|   "059-finalizer-throw",
 | |
|   "080-oom-throw",
 | |
|   "1004-checker-volatile-ref-load",
 | |
|   "133-static-invoke-super",
 | |
|   "1338-gc-no-los",
 | |
|   "151-OpenFileLimit",
 | |
|   "159-app-image-fields",
 | |
|   "160-read-barrier-stress",
 | |
|   "163-app-image-methods",
 | |
|   "165-lock-owner-proxy",
 | |
|   "168-vmstack-annotated",
 | |
|   "176-app-image-string",
 | |
|   "2232-write-metrics-to-log",
 | |
|   "304-method-tracing",
 | |
|   "628-vdex",
 | |
|   "643-checker-bogus-ic",
 | |
|   "676-proxy-jit-at-first-use",
 | |
|   "677-fsi2",
 | |
|   "678-quickening",
 | |
|   "818-clinit-nterp",
 | |
|   "821-madvise-willneed",
 | |
| ])
 | |
| 
 | |
| known_slow_tests = frozenset([
 | |
|   "175-alloc-big-bignums",
 | |
| ])
 | |
| 
 | |
| # Known failing ART run-tests.
 | |
| # TODO(rpl): Investigate and address the causes of failures.
 | |
| known_failing_tests = frozenset([
 | |
|   "004-SignalTest",
 | |
|   "004-UnsafeTest",
 | |
|   "051-thread",
 | |
|   "086-null-super",
 | |
|   "087-gc-after-link",
 | |
|   # 1002-notify-startup: Dependency on `libarttest` + custom `check` script.
 | |
|   "1002-notify-startup",
 | |
|   "1337-gc-coverage",
 | |
|   "1339-dead-reference-safe",
 | |
|   "136-daemon-jni-shutdown",
 | |
|   "139-register-natives",
 | |
|   "148-multithread-gc-annotations",
 | |
|   "149-suspend-all-stress",
 | |
|   "150-loadlibrary",
 | |
|   "154-gc-loop",
 | |
|   "169-threadgroup-jni",
 | |
|   "177-visibly-initialized-deadlock",
 | |
|   "179-nonvirtual-jni",
 | |
|   "1945-proxy-method-arguments",
 | |
|   "2011-stack-walk-concurrent-instrument",
 | |
|   # 2040-huge-native-alloc: Fails with:
 | |
|   #
 | |
|   #   Test command execution failed with status FAILED: CommandResult: exit code=1, out=, err=Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
 | |
|   #           at Main.main(Main.java:56)
 | |
|   #
 | |
|   "2040-huge-native-alloc",
 | |
|   "203-multi-checkpoint",
 | |
|   "2033-shutdown-mechanics",
 | |
|   "2036-jni-filechannel",
 | |
|   "2037-thread-name-inherit",
 | |
|   "2235-JdkUnsafeTest",
 | |
|   "305-other-fault-handler",
 | |
|   # 449-checker-bce: Dependency on `libarttest`.
 | |
|   "449-checker-bce",
 | |
|   "454-get-vreg",
 | |
|   "461-get-reference-vreg",
 | |
|   "466-get-live-vreg",
 | |
|   "497-inlining-and-class-loader",
 | |
|   "530-regression-lse",
 | |
|   "555-UnsafeGetLong-regression",
 | |
|   # 596-monitor-inflation: Dependency on `libarttest`.
 | |
|   "596-monitor-inflation",
 | |
|   "602-deoptimizeable",
 | |
|   "604-hot-static-interface",
 | |
|   "616-cha-native",
 | |
|   "616-cha-regression-proxy-method",
 | |
|   # 623-checker-loop-regressions: Dependency on `libarttest`.
 | |
|   "623-checker-loop-regressions",
 | |
|   "626-set-resolved-string",
 | |
|   "642-fp-callees",
 | |
|   "647-jni-get-field-id",
 | |
|   "655-jit-clinit",
 | |
|   "656-loop-deopt",
 | |
|   "664-aget-verifier",
 | |
|   # 680-checker-deopt-dex-pc-0: Dependency on `libarttest`.
 | |
|   "680-checker-deopt-dex-pc-0",
 | |
|   "685-deoptimizeable",
 | |
|   "687-deopt",
 | |
|   "693-vdex-inmem-loader-evict",
 | |
|   "708-jit-cache-churn",
 | |
|   # 716-jli-jit-samples: Dependency on `libarttest`.
 | |
|   "716-jli-jit-samples",
 | |
|   "717-integer-value-of",
 | |
|   "720-thread-priority",
 | |
|   # 730-cha-deopt: Fails with:
 | |
|   #
 | |
|   #   Test command execution failed with status FAILED: CommandResult: exit code=1, out=, err=Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
 | |
|   #           at Main.main(Main.java:24)
 | |
|   #
 | |
|   "730-cha-deopt",
 | |
|   # 813-fp-args: Dependency on `libarttest`.
 | |
|   "813-fp-args",
 | |
|   # 821-many-args: Fails with:
 | |
|   #
 | |
|   #   Test command execution failed with status FAILED: CommandResult: exit code=1, out=, err=Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
 | |
|   #           at Main.main(Main.java:20)
 | |
|   #
 | |
|   "821-many-args",
 | |
|   # 826-infinite-loop: The test expects an argument passed to `Main.main` (the test library,
 | |
|   # usually `arttestd` or `arttest)`, but the ART run-test TradeFed test runner
 | |
|   # (`com.android.tradefed.testtype.ArtRunTest`) does not implement this yet.
 | |
|   "826-infinite-loop",
 | |
|   # 832-cha-recursive: Dependency on `libarttest`.
 | |
|   "832-cha-recursive",
 | |
| ])
 | |
| 
 | |
| # ART gtests that do not need root access to the device.
 | |
| art_gtest_user_module_names = [
 | |
|     "art_libnativebridge_cts_tests",
 | |
|     "art_standalone_cmdline_tests",
 | |
|     ###"art_standalone_compiler_tests",  # b/275289981
 | |
|     # Temporarily disable this test as it is failing with ART module prebuilts (see b/243510263).
 | |
|     ### "art_standalone_dex2oat_tests",
 | |
|     ###"art_standalone_dexdump_tests",  # b/275289981
 | |
|     ###"art_standalone_dexlist_tests",  # b/275289981
 | |
|     # Temporarily disable this test as it is failing with ART module prebuilts (see b/243507635).
 | |
|     ### "art_standalone_libartbase_tests",
 | |
|     "art_standalone_libartpalette_tests",
 | |
|     "art_standalone_libartservice_tests",
 | |
|     "art_standalone_libarttools_tests",
 | |
|     "art_standalone_libdexfile_support_tests",
 | |
|     "art_standalone_libdexfile_tests",
 | |
|     "art_standalone_libprofile_tests",
 | |
|     ###"art_standalone_oatdump_tests",  # b/275289981
 | |
|     ###"art_standalone_odrefresh_tests",  # b/275289981
 | |
|     ###"art_standalone_runtime_compiler_tests",  # b/275289981
 | |
|     ###"art_standalone_runtime_tests",  # b/275289981
 | |
|     "art_standalone_sigchain_tests",
 | |
|     "libnativeloader_test",
 | |
| ]
 | |
| 
 | |
| # ART gtests that need root access to the device.
 | |
| art_gtest_eng_only_module_names = [
 | |
|     ###"art_standalone_dexoptanalyzer_tests",  # b/275289981
 | |
|     ###"art_standalone_profman_tests",  # b/275289981
 | |
| ]
 | |
| 
 | |
| # All supported ART gtests.
 | |
| art_gtest_module_names = sorted(art_gtest_user_module_names + art_gtest_eng_only_module_names)
 | |
| 
 | |
| # ART gtests supported in MTS that do not need root access to the device.
 | |
| art_gtest_mts_user_module_names = copy.copy(art_gtest_user_module_names)
 | |
| # Temporarily disable `art_standalone_odrefresh_tests` in MTS,
 | |
| # as it is currently failing in Mainline testing
 | |
| # (b/206335809); a fix is in the works but may take some time
 | |
| # to land.
 | |
| #
 | |
| # TODO(b/206335809): Re-enable this test when the fix has landed.
 | |
| if "art_standalone_odrefresh_tests" in art_gtest_mts_user_module_names:
 | |
|   art_gtest_mts_user_module_names.remove("art_standalone_odrefresh_tests")
 | |
| 
 | |
| # ART gtests supported in Mainline presubmits.
 | |
| art_gtests_mainline_presubmit_module_names = copy.copy(art_gtest_module_names)
 | |
| # Temporarily disable `art_standalone_odrefresh_tests` in Mainline
 | |
| # presubmits, as it is currently failing in Mainline testing
 | |
| # (b/206335809); a fix is in the works but may take some time to
 | |
| # land.
 | |
| #
 | |
| # TODO(b/206335809): Re-enable this test when the fix has landed.
 | |
| if "art_standalone_odrefresh_tests" in art_gtests_mainline_presubmit_module_names:
 | |
|   art_gtests_mainline_presubmit_module_names.remove("art_standalone_odrefresh_tests")
 | |
| 
 | |
| # Tests exhibiting a flaky behavior, currently exluded from MTS for
 | |
| # the stake of stability / confidence (b/209958457).
 | |
| flaky_tests_excluded_from_mts = [
 | |
|     ("CtsLibcoreFileIOTestCases" +
 | |
|      " android.cts.FileChannelInterProcessLockTest#" + m) for m in [
 | |
|          "test_lockJJZ_Exclusive_asyncChannel",
 | |
|          "test_lockJJZ_Exclusive_syncChannel",
 | |
|          "test_lock_differentChannelTypes",
 | |
|          "test_lockJJZ_Shared_asyncChannel",
 | |
|          "test_lockJJZ_Shared_syncChannel",
 | |
|     ]
 | |
| ] + [
 | |
|     ("CtsLibcoreTestCases" +
 | |
|      " com.android.org.conscrypt.javax.net.ssl.SSLSocketVersionCompatibilityTest#" + m + c)
 | |
|     for (m, c) in itertools.product(
 | |
|          [
 | |
|              "test_SSLSocket_interrupt_read_withoutAutoClose",
 | |
|              "test_SSLSocket_setSoWriteTimeout",
 | |
|          ],
 | |
|          [
 | |
|              "[0: TLSv1.2 client, TLSv1.2 server]",
 | |
|              "[1: TLSv1.2 client, TLSv1.3 server]",
 | |
|              "[2: TLSv1.3 client, TLSv1.2 server]",
 | |
|              "[3: TLSv1.3 client, TLSv1.3 server]",
 | |
|          ]
 | |
|      )
 | |
| ] + [
 | |
|     ("CtsLibcoreTestCases" +
 | |
|      " libcore.dalvik.system.DelegateLastClassLoaderTest#" + m) for m in [
 | |
|          "testLookupOrderNodelegate_getResource",
 | |
|          "testLookupOrder_getResource",
 | |
|     ]
 | |
| ]
 | |
| 
 | |
| # Is `run_test` a Checker test (i.e. a test containing Checker
 | |
| # assertions)?
 | |
| def is_checker_test(run_test):
 | |
|   return re.match("^[0-9]+-checker-", run_test)
 | |
| 
 | |
| 
 | |
| class Generator:
 | |
|   def __init__(self, top_dir):
 | |
|     """Generator of ART test files for an Android source tree anchored at `top_dir`."""
 | |
|     # Path to the Android top source tree.
 | |
|     self.top_dir = top_dir
 | |
|     # Path to the ART directory
 | |
|     self.art_dir = os.path.join(top_dir, "art")
 | |
|     # Path to the ART tests directory.
 | |
|     self.art_test_dir = os.path.join(self.art_dir, "test")
 | |
|     # Path to the MTS configuration directory.
 | |
|     self.mts_config_dir = os.path.join(
 | |
|         top_dir, "test", "mts", "tools", "mts-tradefed", "res", "config")
 | |
| 
 | |
|   def enumerate_run_tests(self):
 | |
|     return sorted([run_test
 | |
|                    for run_test in os.listdir(self.art_test_dir)
 | |
|                    if re.match("^[0-9]{3,}-", run_test)])
 | |
| 
 | |
|   # Read build file (Bash script) and return a canonized version of it
 | |
|   # (without comments, blank lines, "debugging" statements, etc.).
 | |
|   def canonize_build_script(self, build_file):
 | |
| 
 | |
|     def is_comment(line):
 | |
|       return re.match("^\\s*#", line)
 | |
| 
 | |
|     def is_blank(line):
 | |
|       return re.match("^\\s*$", line)
 | |
| 
 | |
|     # Is `line` a `set -e` statement?
 | |
|     def is_set_e(line):
 | |
|       return re.match("^\\s*set -e\\s*", line)
 | |
| 
 | |
|     # Should `line` be kept in the canonized build script?
 | |
|     def keep_line(line):
 | |
|       return not (is_comment(line) or is_blank(line) or is_set_e(line))
 | |
| 
 | |
|     with open(build_file, "r") as f:
 | |
|       lines = f.readlines()
 | |
|     return list(filter(keep_line, lines))
 | |
| 
 | |
|   # Can the build script in `build_file` be safely ignored?
 | |
|   def can_ignore_build_script(self, build_file):
 | |
|     build_script = self.canonize_build_script(build_file)
 | |
|     if len(build_script) == 1:
 | |
|       if build_script[0] == "./default-build \"$@\" --experimental var-handles\n":
 | |
|         # Soong builds JARs with VarHandle support by default (i.e. by
 | |
|         # using an API level greater or equal to 28), so we can ignore
 | |
|         # build scripts that just request support for this feature.
 | |
|         return True
 | |
|     return False
 | |
| 
 | |
|   # Is building `run_test` supported?
 | |
|   # TODO(b/147814778): Add build support for more tests.
 | |
|   def is_buildable(self, run_test):
 | |
|     run_test_path = os.path.join(self.art_test_dir, run_test)
 | |
| 
 | |
|     # Skip tests with non-default build rules, unless these build
 | |
|     # rules can be safely ignored.
 | |
|     if os.path.isfile(os.path.join(run_test_path, "build")):
 | |
|       if not self.can_ignore_build_script(os.path.join(run_test_path, "build")):
 | |
|         return False
 | |
|     # Skip tests with sources outside the `src` directory.
 | |
|     for subdir in ["jasmin",
 | |
|                    "jasmin-multidex",
 | |
|                    "smali",
 | |
|                    "smali-ex",
 | |
|                    "smali-multidex",
 | |
|                    "src-aotex",
 | |
|                    "src-bcpex",
 | |
|                    "src-ex",
 | |
|                    "src-ex2",
 | |
|                    "src-multidex",
 | |
|                    "src2"]:
 | |
|       if os.path.isdir(os.path.join(run_test_path, subdir)):
 | |
|         return False
 | |
|     # Skip tests that have both an `src` directory and an `src-art` directory.
 | |
|     if os.path.isdir(os.path.join(run_test_path, "src")) and \
 | |
|        os.path.isdir(os.path.join(run_test_path, "src-art")):
 | |
|         return False
 | |
|     # Skip tests that have neither an `src` directory nor an `src-art` directory.
 | |
|     if not os.path.isdir(os.path.join(run_test_path, "src")) and \
 | |
|        not os.path.isdir(os.path.join(run_test_path, "src-art")):
 | |
|       return False
 | |
|     # Skip test with a copy of `sun.misc.Unsafe`.
 | |
|     if os.path.isfile(os.path.join(run_test_path, "src", "sun", "misc", "Unsafe.java")):
 | |
|       return False
 | |
|     # Skip tests with Hidden API specs.
 | |
|     if os.path.isfile(os.path.join(run_test_path, "hiddenapi-flags.csv")):
 | |
|       return False
 | |
|     # All other tests are considered buildable.
 | |
|     return True
 | |
| 
 | |
|   # Is (successfully) running `run_test` supported?
 | |
|   # TODO(b/147812905): Add run-time support for more tests.
 | |
|   def is_runnable(self, run_test):
 | |
|     run_test_path = os.path.join(self.art_test_dir, run_test)
 | |
|     # Unconditionally consider some identified tests that have a
 | |
|     # (not-yet-handled) custom `run` script as runnable.
 | |
|     # TODO(rpl): Get rid of this exception mechanism by supporting
 | |
|     # these tests' `run` scripts properly.
 | |
|     if run_test in runnable_test_exceptions:
 | |
|       return True
 | |
|     # Skip tests with a custom `run` script.
 | |
|     if os.path.isfile(os.path.join(run_test_path, "run")):
 | |
|       return False
 | |
|     # Skip tests known to fail.
 | |
|     if run_test in known_failing_tests:
 | |
|       return False
 | |
|     # All other tests are considered runnable.
 | |
|     return True
 | |
| 
 | |
|   def is_slow(self, run_test):
 | |
|     return run_test in known_slow_tests
 | |
| 
 | |
|   def regen_bp_files(self, run_tests, buildable_tests):
 | |
|     for run_test in run_tests:
 | |
|       # Remove any previously generated file.
 | |
|       bp_file = os.path.join(self.art_test_dir, run_test, "Android.bp")
 | |
|       if os.path.exists(bp_file):
 | |
|         logging.debug(f"Removing `{bp_file}`.")
 | |
|         os.remove(bp_file)
 | |
| 
 | |
|     for run_test in buildable_tests:
 | |
|       self.regen_bp_file(run_test)
 | |
| 
 | |
|   def regen_bp_file(self, run_test):
 | |
|     """Regenerate Blueprint file for an ART run-test."""
 | |
| 
 | |
|     run_test_path = os.path.join(self.art_test_dir, run_test)
 | |
|     bp_file = os.path.join(run_test_path, "Android.bp")
 | |
| 
 | |
|     # Optional test metadata (JSON file).
 | |
|     metadata_file = os.path.join(run_test_path, "test-metadata.json")
 | |
|     metadata = {}
 | |
|     if os.path.exists(metadata_file):
 | |
|       with open(metadata_file, "r") as f:
 | |
|         metadata = json.load(f)
 | |
| 
 | |
|     run_test_module_name = ART_RUN_TEST_MODULE_NAME_PREFIX + run_test
 | |
| 
 | |
|     # Set the test configuration template.
 | |
|     if self.is_runnable(run_test):
 | |
|       if "cts" in metadata.get("test_suites", []):
 | |
|         test_config_template = "art-run-test-target-cts-template"
 | |
|       elif self.is_slow(run_test):
 | |
|         test_config_template = "art-run-test-target-slow-template"
 | |
|       else:
 | |
|         test_config_template = "art-run-test-target-template"
 | |
|     else:
 | |
|       test_config_template = "art-run-test-target-no-test-suite-tag-template"
 | |
| 
 | |
|     # Define `test_suites`, if present in the test's metadata.
 | |
|     test_suites = ""
 | |
|     if metadata.get("test_suites"):
 | |
|       test_suites = f"""\
 | |
| 
 | |
|           test_suites: {json.dumps(metadata.get("test_suites"))},"""
 | |
| 
 | |
|     if is_checker_test(run_test):
 | |
|       include_src = """\
 | |
| 
 | |
|           // Include the Java source files in the test's artifacts, to make Checker assertions
 | |
|           // available to the TradeFed test runner.
 | |
|           include_srcs: true,"""
 | |
|     else:
 | |
|       include_src = ""
 | |
| 
 | |
|     # The default source directory is `src`, except if `src-art` exists.
 | |
|     if os.path.isdir(os.path.join(run_test_path, "src-art")):
 | |
|       source_dir = "src-art"
 | |
|     else:
 | |
|       source_dir = "src"
 | |
| 
 | |
|     with open(bp_file, "w") as f:
 | |
|       logging.debug(f"Writing `{bp_file}`.")
 | |
|       f.write(textwrap.dedent(f"""\
 | |
|       // {ADVISORY}
 | |
| 
 | |
|       // Build rules for ART run-test `{run_test}`.
 | |
| 
 | |
|       package {{
 | |
|           // See: http://go/android-license-faq
 | |
|           // A large-scale-change added 'default_applicable_licenses' to import
 | |
|           // all of the 'license_kinds' from "art_license"
 | |
|           // to get the below license kinds:
 | |
|           //   SPDX-license-identifier-Apache-2.0
 | |
|           default_applicable_licenses: ["art_license"],
 | |
|       }}
 | |
| 
 | |
|       // Test's Dex code.
 | |
|       java_test {{
 | |
|           name: "{run_test_module_name}",
 | |
|           defaults: ["art-run-test-defaults"],
 | |
|           test_config_template: ":{test_config_template}",
 | |
|           srcs: ["{source_dir}/**/*.java"],
 | |
|           data: [
 | |
|               ":{run_test_module_name}-expected-stdout",
 | |
|               ":{run_test_module_name}-expected-stderr",
 | |
|           ],{test_suites}{include_src}
 | |
|       }}
 | |
| 
 | |
|       // Test's expected standard output.
 | |
|       genrule {{
 | |
|           name: "{run_test_module_name}-expected-stdout",
 | |
|           out: ["{run_test_module_name}-expected-stdout.txt"],
 | |
|           srcs: ["expected-stdout.txt"],
 | |
|           cmd: "cp -f $(in) $(out)",
 | |
|       }}
 | |
| 
 | |
|       // Test's expected standard error.
 | |
|       genrule {{
 | |
|           name: "{run_test_module_name}-expected-stderr",
 | |
|           out: ["{run_test_module_name}-expected-stderr.txt"],
 | |
|           srcs: ["expected-stderr.txt"],
 | |
|           cmd: "cp -f $(in) $(out)",
 | |
|       }}
 | |
|       """))
 | |
| 
 | |
|   def regen_test_mapping_file(self, art_run_tests):
 | |
|     """Regenerate ART's `TEST_MAPPING`."""
 | |
| 
 | |
|     run_test_module_names = [ART_RUN_TEST_MODULE_NAME_PREFIX + t for t in art_run_tests]
 | |
| 
 | |
|     # Mainline presubmits.
 | |
|     mainline_other_presubmit_tests = [
 | |
|         "ComposHostTestCases",
 | |
|     ]
 | |
|     mainline_presubmit_tests = (mainline_other_presubmit_tests + run_test_module_names +
 | |
|                                 art_gtests_mainline_presubmit_module_names)
 | |
|     mainline_presubmit_tests_with_apex = [t + "[com.google.android.art.apex]"
 | |
|                                           for t
 | |
|                                           in mainline_presubmit_tests]
 | |
|     mainline_presubmit_tests_dict = [{"name": t} for t in mainline_presubmit_tests_with_apex]
 | |
| 
 | |
|     # Presubmits.
 | |
|     other_presubmit_tests = [
 | |
|         "CtsJdwpTestCases",
 | |
|         "BootImageProfileTest",
 | |
|         "ArtServiceTests",
 | |
|         "ComposHostTestCases",
 | |
|         "art_standalone_dexpreopt_tests",
 | |
|     ]
 | |
|     presubmit_tests = other_presubmit_tests + run_test_module_names + art_gtest_module_names
 | |
|     presubmit_tests_dict = [{"name": t} for t in presubmit_tests]
 | |
| 
 | |
|     # Use an `OrderedDict` container to preserve the order in which items are inserted.
 | |
|     # Do not produce an entry for a test group if it is empty.
 | |
|     test_mapping_dict = collections.OrderedDict([
 | |
|         (test_group_name, test_group_dict)
 | |
|         for (test_group_name, test_group_dict)
 | |
|         in [
 | |
|             ("mainline-presubmit", mainline_presubmit_tests_dict),
 | |
|             ("presubmit", presubmit_tests_dict),
 | |
|         ]
 | |
|         if test_group_dict
 | |
|     ])
 | |
|     test_mapping_contents = json.dumps(test_mapping_dict, indent = INDENT)
 | |
| 
 | |
|     test_mapping_file = os.path.join(self.art_dir, "TEST_MAPPING")
 | |
|     with open(test_mapping_file, "w") as f:
 | |
|       logging.debug(f"Writing `{test_mapping_file}`.")
 | |
|       f.write(f"// {ADVISORY}\n")
 | |
|       f.write(test_mapping_contents)
 | |
|       f.write("\n")
 | |
| 
 | |
|   def create_mts_test_shard(self, description, tests, shard_num, copyright_year, comments = []):
 | |
|     """Factory method instantiating an `MtsTestShard`."""
 | |
|     return self.MtsTestShard(self.mts_config_dir,
 | |
|                              description, tests, shard_num, copyright_year, comments)
 | |
| 
 | |
|   class MtsTestShard:
 | |
|     """Class encapsulating data and generation logic for an ART MTS test shard."""
 | |
| 
 | |
|     def __init__(self, mts_config_dir, description, tests, shard_num, copyright_year, comments):
 | |
|       self.mts_config_dir = mts_config_dir
 | |
|       self.description = description
 | |
|       self.tests = tests
 | |
|       self.shard_num = shard_num
 | |
|       self.copyright_year = copyright_year
 | |
|       self.comments = comments
 | |
| 
 | |
|     def shard_id(self):
 | |
|       return f"{self.shard_num:02}"
 | |
| 
 | |
|     def test_plan_name(self):
 | |
|       return "mts-art-shard-" + self.shard_id()
 | |
| 
 | |
|     def test_list_name(self):
 | |
|       return "mts-art-tests-list-user-shard-" + self.shard_id()
 | |
| 
 | |
|     def regen_test_plan_file(self):
 | |
|       """Regenerate ART MTS test plan file shard (`mts-art-shard-<shard_num>.xml`)."""
 | |
|       root = xml.dom.minidom.Document()
 | |
| 
 | |
|       advisory_header = root.createComment(f" {ADVISORY} ")
 | |
|       root.appendChild(advisory_header)
 | |
|       copyright_header = root.createComment(copyright_header_text(self.copyright_year))
 | |
|       root.appendChild(copyright_header)
 | |
| 
 | |
|       configuration = root.createElement("configuration")
 | |
|       root.appendChild(configuration)
 | |
|       configuration.setAttribute(
 | |
|           "description",
 | |
|           f"Run mts-art-shard-{self.shard_id()} from a preexisting MTS installation.")
 | |
| 
 | |
|       # Included XML files.
 | |
|       included_xml_files = ["mts", self.test_list_name()]
 | |
|       for xml_file in included_xml_files:
 | |
|         include = root.createElement("include")
 | |
|         include.setAttribute("name", xml_file)
 | |
|         configuration.appendChild(include)
 | |
| 
 | |
|       # Test plan name.
 | |
|       option = root.createElement("option")
 | |
|       option.setAttribute("name", "plan")
 | |
|       option.setAttribute("value", self.test_plan_name())
 | |
|       configuration.appendChild(option)
 | |
| 
 | |
|       xml_str = root.toprettyxml(indent = XML_INDENT, encoding = "utf-8")
 | |
| 
 | |
|       test_plan_file = os.path.join(self.mts_config_dir, self.test_plan_name() + ".xml")
 | |
|       with open(test_plan_file, "wb") as f:
 | |
|         logging.debug(f"Writing `{test_plan_file}`.")
 | |
|         f.write(xml_str)
 | |
| 
 | |
|     def regen_test_list_file(self):
 | |
|       """Regenerate ART MTS test list file (`mts-art-tests-list-user-shard-<shard_num>.xml`)."""
 | |
|       root = xml.dom.minidom.Document()
 | |
| 
 | |
|       advisory_header = root.createComment(f" {ADVISORY} ")
 | |
|       root.appendChild(advisory_header)
 | |
|       copyright_header = root.createComment(copyright_header_text(self.copyright_year))
 | |
|       root.appendChild(copyright_header)
 | |
| 
 | |
|       configuration = root.createElement("configuration")
 | |
|       root.appendChild(configuration)
 | |
|       configuration.setAttribute(
 | |
|           "description",
 | |
|           f"List of ART MTS tests that do not need root access (shard {self.shard_id()})"
 | |
|       )
 | |
| 
 | |
|       # Test declarations.
 | |
|       # ------------------
 | |
| 
 | |
|       def append_test_declaration(test):
 | |
|         option = root.createElement("option")
 | |
|         option.setAttribute("name", "compatibility:include-filter")
 | |
|         option.setAttribute("value", test)
 | |
|         configuration.appendChild(option)
 | |
| 
 | |
|       test_declarations_comments = [self.description + "."]
 | |
|       test_declarations_comments.extend(self.comments)
 | |
|       for c in test_declarations_comments:
 | |
|         xml_comment = root.createComment(f" {c} ")
 | |
|         configuration.appendChild(xml_comment)
 | |
|       for t in self.tests:
 | |
|         append_test_declaration(t)
 | |
| 
 | |
|       # `MainlineTestModuleController` configurations.
 | |
|       # ----------------------------------------------
 | |
| 
 | |
|       def append_module_controller_configuration(test):
 | |
|         option = root.createElement("option")
 | |
|         option.setAttribute("name", "compatibility:module-arg")
 | |
|         option.setAttribute("value", f"{test}:enable:true")
 | |
|         configuration.appendChild(option)
 | |
| 
 | |
|       module_controller_configuration_comments = [
 | |
|           f"Enable MainlineTestModuleController for {self.description}."]
 | |
|       module_controller_configuration_comments.extend(self.comments)
 | |
|       for c in module_controller_configuration_comments:
 | |
|         xml_comment = root.createComment(f" {c} ")
 | |
|         configuration.appendChild(xml_comment)
 | |
|       for t in self.tests:
 | |
|         append_module_controller_configuration(t)
 | |
| 
 | |
|       xml_str = root.toprettyxml(indent = XML_INDENT, encoding = "utf-8")
 | |
| 
 | |
|       test_list_file = os.path.join(self.mts_config_dir, self.test_list_name() + ".xml")
 | |
|       with open(test_list_file, "wb") as f:
 | |
|         logging.debug(f"Writing `{test_list_file}`.")
 | |
|         f.write(xml_str)
 | |
| 
 | |
|   def regen_mts_art_tests_list_user_file(self, num_mts_art_run_test_shards):
 | |
|     """Regenerate ART MTS test list file (`mts-art-tests-list-user.xml`)."""
 | |
|     root = xml.dom.minidom.Document()
 | |
| 
 | |
|     advisory_header = root.createComment(f" {ADVISORY} ")
 | |
|     root.appendChild(advisory_header)
 | |
|     copyright_header = root.createComment(copyright_header_text(2020))
 | |
|     root.appendChild(copyright_header)
 | |
| 
 | |
|     configuration = root.createElement("configuration")
 | |
|     root.appendChild(configuration)
 | |
|     configuration.setAttribute("description", "List of ART MTS tests that do not need root access.")
 | |
| 
 | |
|     # Included XML files.
 | |
|     for s in range(num_mts_art_run_test_shards):
 | |
|       include = root.createElement("include")
 | |
|       include.setAttribute("name", f"mts-art-tests-list-user-shard-{s:02}")
 | |
|       configuration.appendChild(include)
 | |
| 
 | |
|     # Excluded flaky tests.
 | |
|     xml_comment = root.createComment(f" Excluded flaky tests (b/209958457). ")
 | |
|     configuration.appendChild(xml_comment)
 | |
| 
 | |
|     def append_test_exclusion(test):
 | |
|       option = root.createElement("option")
 | |
|       option.setAttribute("name", "compatibility:exclude-filter")
 | |
|       option.setAttribute("value", test)
 | |
|       configuration.appendChild(option)
 | |
| 
 | |
|     for t in flaky_tests_excluded_from_mts:
 | |
|       append_test_exclusion(t)
 | |
| 
 | |
|     xml_str = root.toprettyxml(indent = XML_INDENT, encoding = "utf-8")
 | |
| 
 | |
|     mts_art_tests_list_user_file = os.path.join(self.mts_config_dir, "mts-art-tests-list-user.xml")
 | |
|     with open(mts_art_tests_list_user_file, "wb") as f:
 | |
|       logging.debug(f"Writing `{mts_art_tests_list_user_file}`.")
 | |
|       f.write(xml_str)
 | |
| 
 | |
|   def regen_art_mts_files(self, art_run_tests):
 | |
|     """Regenerate ART MTS definition files."""
 | |
| 
 | |
|     # Remove any previously MTS ART test plan shard (`mts-art-shard-[0-9]+.xml`)
 | |
|     # and any test list shard (`mts-art-tests-list-user-shard-[0-9]+.xml`).
 | |
|     old_test_plan_shards = sorted([
 | |
|         test_plan_shard
 | |
|         for test_plan_shard in os.listdir(self.mts_config_dir)
 | |
|         if re.match("^mts-art-(tests-list-user-)?shard-[0-9]+.xml$", test_plan_shard)])
 | |
|     for shard in old_test_plan_shards:
 | |
|       shard_path = os.path.join(self.mts_config_dir, shard)
 | |
|       if os.path.exists(shard_path):
 | |
|         logging.debug(f"Removing `{shard_path}`.")
 | |
|         os.remove(shard_path)
 | |
| 
 | |
|     mts_test_shards = []
 | |
| 
 | |
|     # ART test (gtest & run-test) shard(s).
 | |
|     # TODO: Also handle the case of gtests requiring root access to the device
 | |
|     # (`art_gtest_eng_only_module_names`).
 | |
|     art_run_test_module_names = [ART_RUN_TEST_MODULE_NAME_PREFIX + t for t in art_run_tests]
 | |
|     art_run_test_shards = split_list(art_run_test_module_names, NUM_MTS_ART_RUN_TEST_SHARDS)
 | |
|     for i in range(len(art_run_test_shards)):
 | |
|       art_tests_shard_i_tests = art_run_test_shards[i]
 | |
|       # Append ART gtests to the last ART run-test shard for now.
 | |
|       # If needed, consider moving them to their own shard to increase
 | |
|       # the parallelization of code coverage runs.
 | |
|       if i + 1 == len(art_run_test_shards):
 | |
|         art_tests_shard_i_tests.extend(art_gtest_mts_user_module_names)
 | |
|       art_tests_shard_i = self.create_mts_test_shard(
 | |
|           "ART run-tests", art_tests_shard_i_tests, i, 2020,
 | |
|           ["TODO(rpl): Find a way to express this list in a more concise fashion."])
 | |
|       mts_test_shards.append(art_tests_shard_i)
 | |
| 
 | |
|     # CTS Libcore non-OJ tests (`CtsLibcoreTestCases`) shard.
 | |
|     cts_libcore_tests_shard_num = len(mts_test_shards)
 | |
|     cts_libcore_tests_shard = self.create_mts_test_shard(
 | |
|         "CTS Libcore non-OJ tests", ["CtsLibcoreTestCases"], cts_libcore_tests_shard_num, 2020)
 | |
|     mts_test_shards.append(cts_libcore_tests_shard)
 | |
| 
 | |
|     # Other CTS Libcore tests shard.
 | |
|     other_cts_libcore_tests_shard_num = len(mts_test_shards)
 | |
|     other_cts_libcore_tests_shard_tests = [
 | |
|         "CtsLibcoreApiEvolutionTestCases",
 | |
|         "CtsLibcoreFileIOTestCases",
 | |
|         "CtsLibcoreJsr166TestCases",
 | |
|         "CtsLibcoreLegacy22TestCases",
 | |
|         "CtsLibcoreOjTestCases",
 | |
|         "CtsLibcoreWycheproofBCTestCases",
 | |
|         "MtsLibcoreOkHttpTestCases",
 | |
|     ]
 | |
|     other_cts_libcore_tests_shard = self.create_mts_test_shard(
 | |
|         "CTS Libcore OJ tests", other_cts_libcore_tests_shard_tests,
 | |
|         other_cts_libcore_tests_shard_num, 2021)
 | |
|     mts_test_shards.append(other_cts_libcore_tests_shard)
 | |
| 
 | |
|     for s in mts_test_shards:
 | |
|       s.regen_test_plan_file()
 | |
|       s.regen_test_list_file()
 | |
| 
 | |
|     self.regen_mts_art_tests_list_user_file(len(mts_test_shards))
 | |
| 
 | |
|   def regen_test_files(self, regen_art_mts):
 | |
|     """Regenerate ART test files.
 | |
| 
 | |
|     Args:
 | |
|       regen_art_mts: If true, also regenerate the ART MTS definition.
 | |
|     """
 | |
|     run_tests = self.enumerate_run_tests()
 | |
| 
 | |
|     # Create a list of the tests that can currently be built, and for
 | |
|     # which a Blueprint file is to be generated.
 | |
|     buildable_tests = list(filter(self.is_buildable, run_tests))
 | |
| 
 | |
|     # Create a list of the tests that can be built and run
 | |
|     # (successfully). These tests are to be added to ART's
 | |
|     # `TEST_MAPPING` file and also tagged as part of TradeFed's
 | |
|     # `art-target-run-test` test suite via the `test-suite-tag` option
 | |
|     # in their configuration file.
 | |
|     expected_succeeding_tests = list(filter(self.is_runnable, buildable_tests))
 | |
| 
 | |
|     # Regenerate Blueprint files.
 | |
|     # ---------------------------
 | |
| 
 | |
|     self.regen_bp_files(run_tests, buildable_tests)
 | |
| 
 | |
|     buildable_tests_percentage = int(len(buildable_tests) * 100 / len(run_tests))
 | |
| 
 | |
|     print(f"Generated Blueprint files for {len(buildable_tests)} ART run-tests out of"
 | |
|           f" {len(run_tests)} ({buildable_tests_percentage}%).")
 | |
| 
 | |
|     # Regenerate `TEST_MAPPING` file.
 | |
|     # -------------------------------
 | |
| 
 | |
|     # Note: We only include ART run-tests expected to succeed for now.
 | |
| 
 | |
|     num_presubmit_run_tests = len(expected_succeeding_tests)
 | |
|     num_mainline_presubmit_run_tests = len(expected_succeeding_tests)
 | |
| 
 | |
|     self.regen_test_mapping_file(expected_succeeding_tests)
 | |
| 
 | |
|     expected_succeeding_tests_percentage = int(
 | |
|         len(expected_succeeding_tests) * 100 / len(run_tests))
 | |
| 
 | |
|     mainline_presubmit_gtests_percentage = int(
 | |
|         len(art_gtests_mainline_presubmit_module_names) * 100 / len(art_gtest_module_names))
 | |
| 
 | |
|     print(f"Generated TEST_MAPPING entries for {len(expected_succeeding_tests)} ART run-tests out"
 | |
|           f" of {len(run_tests)} ({expected_succeeding_tests_percentage}%):")
 | |
|     for (num_tests, test_kind, tests_percentage, test_group_name) in [
 | |
|         (num_mainline_presubmit_run_tests, "ART run-tests", 100, "mainline-presubmit"),
 | |
|         (len(art_gtests_mainline_presubmit_module_names), "ART gtests",
 | |
|          mainline_presubmit_gtests_percentage, "mainline-presubmit"),
 | |
|         (num_presubmit_run_tests, "ART run-tests", 100, "presubmit"),
 | |
|         (len(art_gtest_module_names), "ART gtests", 100, "presubmit"),
 | |
|     ]:
 | |
|       print(
 | |
|           f"  {num_tests:3d} {test_kind} ({tests_percentage}%) in `{test_group_name}` test group.")
 | |
| 
 | |
|     # Regenerate ART MTS definition (optional).
 | |
|     # -----------------------------------------
 | |
| 
 | |
|     if regen_art_mts:
 | |
|       self.regen_art_mts_files(expected_succeeding_tests)
 | |
|       print(f"Generated ART MTS entries for {len(expected_succeeding_tests)} ART run-tests out"
 | |
|             f" of {len(run_tests)} ({expected_succeeding_tests_percentage}%).")
 | |
| 
 | |
| def main():
 | |
|   if "ANDROID_BUILD_TOP" not in os.environ:
 | |
|     logging.error("ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?")
 | |
|     sys.exit(1)
 | |
| 
 | |
|   parser = argparse.ArgumentParser(
 | |
|       formatter_class=argparse.RawDescriptionHelpFormatter,
 | |
|       description=textwrap.dedent("Regenerate some ART test related files."),
 | |
|       epilog=textwrap.dedent("""\
 | |
|         Regenerate ART run-tests Blueprint files, ART's `TEST_MAPPING` file, and
 | |
|         optionally the ART MTS (Mainline Test Suite) definition.
 | |
|         """))
 | |
|   parser.add_argument("-m", "--regen-art-mts", help="regenerate the ART MTS definition as well",
 | |
|                       action="store_true")
 | |
|   parser.add_argument("-v", "--verbose", help="enable verbose output", action="store_true")
 | |
|   args = parser.parse_args()
 | |
| 
 | |
|   if args.verbose:
 | |
|     logging.getLogger().setLevel(logging.DEBUG)
 | |
| 
 | |
|   generator = Generator(os.path.join(os.environ["ANDROID_BUILD_TOP"]))
 | |
|   generator.regen_test_files(args.regen_art_mts)
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|   main()
 |