Do not generate empty Rust tests.
cargo2android.py currently builds whatever cargo does, which includes
empty tests. We can filter those out by parsing the output of "cargo
test" and looking for things with no tests.
Fixes: 159039990
Test: Run on a crate with empty integration tests, one with empty unit
tests, and one with no empty tests.
Change-Id: If8b1126d82ce6c2851b90de738ea24b129b81feb
diff --git a/scripts/cargo2android.py b/scripts/cargo2android.py
index 7abf897..2a4bfdf 100755
--- a/scripts/cargo2android.py
+++ b/scripts/cargo2android.py
@@ -125,6 +125,12 @@
# Rustc output of file location path pattern for a warning message.
WARNING_FILE_PAT = re.compile('^ *--> ([^:]*):[0-9]+')
+# cargo test --list output of the start of running a binary.
+CARGO_TEST_LIST_START_PAT = re.compile('^\s*Running (.*) \(.*\)$')
+
+# cargo test --list output of the end of running a binary.
+CARGO_TEST_LIST_END_PAT = re.compile('^(\d+) tests, (\d+) benchmarks$')
+
# Rust package name with suffix -d1.d2.d3(+.*)?.
VERSION_SUFFIX_PAT = re.compile(r'^(.*)-[0-9]+\.[0-9]+\.[0-9]+(?:\+.*)?$')
@@ -313,8 +319,8 @@
# which can be changed if self is a merged test module.
self.decide_module_type()
if should_merge_test:
- if (self.main_src in self.runner.args.test_blocklist and
- not other.main_src in self.runner.args.test_blocklist):
+ if (self.runner.should_ignore_test(self.main_src)
+ and not self.runner.should_ignore_test(other.main_src)):
self.main_src = other.main_src
self.srcs.append(other.main_src)
# use a short unique name as the merged module name.
@@ -667,7 +673,7 @@
return
# Dump one test module per source file, and separate host and device tests.
# crate_type == 'test'
- self.srcs = [src for src in self.srcs if not src in self.runner.args.test_blocklist]
+ self.srcs = [src for src in self.srcs if not self.runner.should_ignore_test(src)]
if ((self.host_supported and self.device_supported and len(self.srcs) > 0) or
len(self.srcs) > 1):
self.srcs = sorted(set(self.srcs))
@@ -1145,6 +1151,8 @@
self.cargo = ['clean', 'build ' + default_target]
if args.tests:
self.cargo.append('build --tests ' + default_target)
+ self.empty_tests = set()
+ self.empty_unittests = False
def setup_cargo_path(self):
"""Find cargo in the --cargo_bin or prebuilt rust bin directory."""
@@ -1329,7 +1337,8 @@
os.remove(cargo_out)
if not self.args.use_cargo_lock and had_cargo_lock: # save it
os.rename(cargo_lock, cargo_lock_saved)
- cmd_tail = ' --target-dir ' + TARGET_TMP + ' >> ' + cargo_out + ' 2>&1'
+ cmd_tail_target = ' --target-dir ' + TARGET_TMP
+ cmd_tail_redir = ' >> ' + cargo_out + ' 2>&1'
# set up search PATH for cargo to find the correct rustc
saved_path = os.environ['PATH']
os.environ['PATH'] = os.path.dirname(self.cargo_path) + ':' + saved_path
@@ -1356,20 +1365,13 @@
features += ' --features ' + self.args.features
cmd_v_flag = ' -vv ' if self.args.vv else ' -v '
cmd = self.cargo_path + cmd_v_flag
- cmd += c + features + cmd_tail
+ cmd += c + features + cmd_tail_target + cmd_tail_redir
if self.args.rustflags and c != 'clean':
cmd = 'RUSTFLAGS="' + self.args.rustflags + '" ' + cmd
- if self.dry_run:
- print('Dry-run skip:', cmd)
- else:
- if self.args.verbose:
- print('Running:', cmd)
- with open(cargo_out, 'a') as out_file:
- out_file.write('### Running: ' + cmd + '\n')
- ret = os.system(cmd)
- if ret != 0:
- print('*** There was an error while running cargo. ' +
- 'See the cargo.out file for details.')
+ self.run_cmd(cmd, cargo_out)
+ if self.args.tests:
+ cmd = self.cargo_path + ' test' + features + cmd_tail_target + ' -- --list' + cmd_tail_redir
+ self.run_cmd(cmd, cargo_out)
if added_workspace: # restore original Cargo.toml
with open(cargo_toml, 'w') as out_file:
out_file.writelines(cargo_toml_lines)
@@ -1383,6 +1385,19 @@
os.rename(cargo_lock_saved, cargo_lock)
return self
+ def run_cmd(self, cmd, cargo_out):
+ if self.dry_run:
+ print('Dry-run skip:', cmd)
+ else:
+ if self.args.verbose:
+ print('Running:', cmd)
+ with open(cargo_out, 'a') as out_file:
+ out_file.write('### Running: ' + cmd + '\n')
+ ret = os.system(cmd)
+ if ret != 0:
+ print('*** There was an error while running cargo. ' +
+ 'See the cargo.out file for details.')
+
def dump_pkg_obj2cc(self):
"""Dump debug info of the pkg_obj2cc map."""
if not self.args.debug:
@@ -1522,9 +1537,36 @@
self.append_to_bp('ERROR -vv line: ' + line)
return ''
+ def add_empty_test(self, name):
+ if name == 'unittests':
+ self.empty_unittests = True
+ else:
+ self.empty_tests.add(name)
+
+ def should_ignore_test(self, src):
+ # cargo test outputs the source file for integration tests but "unittests"
+ # for unit tests. To figure out to which crate this corresponds, we check
+ # if the current source file is the main source of a non-test crate, e.g.,
+ # a library or a binary.
+ return (src in self.args.test_blocklist or src in self.empty_tests
+ or (self.empty_unittests
+ and src in [c.main_src for c in self.crates if c.crate_types != ['test']]))
+
def parse(self, inf, outf_name):
- """Parse rustc and warning messages in inf, return a list of Crates."""
+ """Parse rustc, test, and warning messages in inf, return a list of Crates."""
n = 0 # line number
+ # We read the file in two passes, where the first simply checks for empty tests.
+ # Otherwise we would add and merge tests before seeing they're empty.
+ cur_test_name = None
+ for line in inf:
+ if CARGO_TEST_LIST_START_PAT.match(line):
+ cur_test_name = CARGO_TEST_LIST_START_PAT.match(line).group(1)
+ elif cur_test_name and CARGO_TEST_LIST_END_PAT.match(line):
+ match = CARGO_TEST_LIST_END_PAT.match(line)
+ if int(match.group(1)) + int(match.group(2)) == 0:
+ self.add_empty_test(cur_test_name)
+ cur_test_name = None
+ inf.seek(0)
prev_warning = False # true if the previous line was warning: ...
rustc_line = '' # previous line(s) matching RUSTC_VV_PAT
for line in inf: