0

[RTS] Add back in RTS support to GNI

- Add use_rts bool flag to ensure that a .filter
file exists or creates a dummy file while waiting
for the file to be written.

RTS = Regression Test Selection
Re-land of https://crrev.com/c/4859614

Bug: 375000947
Change-Id: I425e0891ac895ae043855e0372be8a0e43a96f0f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5954865
Reviewed-by: Dirk Pranke <dpranke@google.com>
Reviewed-by: Struan Shrimpton <sshrimp@google.com>
Commit-Queue: Tony Seaward <seawardt@google.com>
Cr-Commit-Position: refs/heads/main@{#1374746}
This commit is contained in:
Tony Seaward
2024-10-28 18:22:57 +00:00
committed by Chromium LUCI CQ
parent 1f8199ca92
commit 37c51bc8d9
4 changed files with 134 additions and 0 deletions

53
build/add_rts_filters.py Executable file

@ -0,0 +1,53 @@
#!/usr/bin/env python3
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Creates a dummy RTS filter file if a real ones do not exist yet.
Real filter files are generated for suites with skippable tests.
Not every test suite will have filter data to use and therefore
no filter file will be created. This ensures that a file exists
to avoid file not found errors. The files will contain no skippable
tests, so there is no effect.
Implementation uses try / except because the filter files are written
relatively close to when this code creates the dummy files.
The following type of implementation would have a race condition:
if not os.path.isfile(filter_file):
open(filter_file, 'w') as fp:
fp.write('*')
"""
import errno
import os
import sys
def main():
filter_file = sys.argv[1]
# '*' is a dummy that means run everything
write_filter_file(filter_file, '*')
def write_filter_file(filter_file, filter_string):
directory = os.path.dirname(filter_file)
try:
os.makedirs(directory)
except OSError as err:
if err.errno == errno.EEXIST:
pass
else:
raise
try:
fp = os.open(filter_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
except OSError as err:
if err.errno == errno.EEXIST:
pass
else:
raise
else:
with os.fdopen(fp, 'w') as file_obj:
file_obj.write(filter_string)
if __name__ == '__main__':
sys.exit(main())

@ -6,6 +6,7 @@ import("//build/config/android/abi.gni")
import("//build/config/android/copy_ex.gni")
import("//build/config/chrome_build.gni")
import("//build/config/clang/clang.gni")
import("//build/config/rts.gni")
import("//build/config/sanitizers/sanitizers.gni")
import("//build_overrides/build.gni")
@ -3629,6 +3630,14 @@ if (!is_robolectric && enable_java_templates) {
# apk_under_test: ":bar"
# }
template("instrumentation_test_runner") {
if (use_rts) {
action("${invoker.target_name}__rts_filters") {
script = "//build/add_rts_filters.py"
rts_file = "${root_build_dir}/gen/rts/${invoker.target_name}.filter"
args = [ rebase_path(rts_file, root_build_dir) ]
outputs = [ rts_file ]
}
}
_incremental_apk = !(defined(invoker.never_incremental) &&
invoker.never_incremental) && incremental_install
_apk_operations_target_name = "${target_name}__apk_operations"
@ -3742,6 +3751,12 @@ if (!is_robolectric && enable_java_templates) {
if (defined(invoker.additional_apks)) {
public_deps += invoker.additional_apks
}
if (use_rts) {
if (!defined(data_deps)) {
data_deps = []
}
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
}

7
build/config/rts.gni Normal file

@ -0,0 +1,7 @@
declare_args() {
# Regression Test Selection (RTS) will use a ML model
# to predict what test cases can be excluded from a
# given test suite based on historical data when using
# a .filter file.
use_rts = false
}

@ -10,6 +10,7 @@ import("//build/config/chromeos/args.gni")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/devtools.gni")
import("//build/config/gclient_args.gni")
import("//build/config/rts.gni")
import("//build/rust/rust_static_library.gni")
import("//build_overrides/build.gni")
@ -362,6 +363,17 @@ template("mixed_test") {
# This should be a list of the test names, for example
# fuzztests = [ "MyTestClass.MyTestName" ]
template("test") {
# Ensures a test filter file exists and if not, creates a dummy file.
# The Regression Test Selection (rts) flag is passed in mb.py and used
# to filter out test cases. Flag ensures that a file exists.
if (use_rts) {
action("${target_name}__rts_filters") {
script = "//build/add_rts_filters.py"
rts_file = "${root_build_dir}/gen/rts/${invoker.target_name}.filter"
args = [ rebase_path(rts_file, root_build_dir) ]
outputs = [ rts_file ]
}
}
testonly = true
if (!is_ios) {
assert(!defined(invoker.is_xctest) || !invoker.is_xctest,
@ -460,6 +472,10 @@ template("test") {
# Don't output to the root or else conflict with the group() below.
output_name = rebase_path(_exec_output, root_out_dir)
if (use_rts) {
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
create_native_executable_dist(_dist_target) {
@ -469,6 +485,12 @@ template("test") {
if (defined(invoker.extra_dist_files)) {
extra_files = invoker.extra_dist_files
}
if (use_rts) {
if (!defined(data_deps)) {
data_deps = []
}
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
} else {
_library_target_name = "${target_name}__library"
@ -598,6 +620,10 @@ template("test") {
if (defined(_unwind_table_asset_name)) {
deps += [ ":${_unwind_table_asset_name}" ]
}
if (use_rts) {
data_deps = [ ":${invoker.target_name}__rts_filters" ]
}
}
}
@ -618,6 +644,9 @@ template("test") {
test_name = _output_name
test_suite = _output_name
test_type = "gtest"
if (use_rts) {
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
# Create a wrapper script rather than using a group() in order to ensure
@ -652,6 +681,9 @@ template("test") {
if (tests_have_location_tags) {
data = [ "//testing/location_tags.json" ]
}
if (use_rts) {
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
} else if (is_fuchsia) {
assert(!defined(invoker.use_xvfb) || !invoker.use_xvfb)
@ -928,6 +960,9 @@ template("test") {
# Include the generate_wrapper as part of data_deps
data_deps += [ ":${_wrapper_output_name}" ]
write_runtime_deps = _runtime_deps_file
if (use_rts) {
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
if (!defined(deps)) {
deps = []
}
@ -955,6 +990,9 @@ template("test") {
if (tests_have_location_tags) {
data = [ "//testing/location_tags.json" ]
}
if (use_rts) {
data_deps = [ ":${invoker.target_name}__rts_filters" ]
}
}
}
@ -981,6 +1019,9 @@ template("test") {
}
data_deps += [ "//testing:test_scripts_shared" ]
if (use_rts) {
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
} else if (!is_nacl) {
if (is_mac || is_win) {
@ -1031,6 +1072,9 @@ template("test") {
if (fail_on_san_warnings) {
executable_args += [ "--fail-san=1" ]
}
if (use_rts) {
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
mixed_test(target_name) {
@ -1056,6 +1100,9 @@ template("test") {
}
data_deps += [ "//testing:test_scripts_shared" ]
if (use_rts) {
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
} else {
# This is a catch-all clause for NaCl toolchains and other random
@ -1076,6 +1123,9 @@ template("test") {
}
data_deps += [ "//testing:test_scripts_shared" ]
if (use_rts) {
data_deps += [ ":${invoker.target_name}__rts_filters" ]
}
}
}
}
@ -1110,6 +1160,15 @@ template("script_test") {
assert(!(_enable_logdog_wrapper && _enable_cros_wrapper),
"The logdog and cros_test wrappers are mutually exclusive")
if (use_rts) {
action("${target_name}__rts_filters") {
script = "//build/add_rts_filters.py"
rts_file = "${root_build_dir}/gen/rts/${invoker.target_name}.filter"
args = [ rebase_path(rts_file, root_build_dir) ]
outputs = [ rts_file ]
}
}
_shared_data = [ invoker.script ]
if (defined(invoker.data)) {
_shared_data += invoker.data