Merge "A update: Add rest of make push aliases" into main
diff --git a/OWNERS_ADTE_TEAM b/OWNERS_ADTE_TEAM
index f4fee56..76e26bc 100644
--- a/OWNERS_ADTE_TEAM
+++ b/OWNERS_ADTE_TEAM
@@ -1,5 +1,4 @@
# ADTE team
-agueeva@google.com
davidjames@google.com
hwj@google.com
hzalek@google.com
diff --git a/adevice/src/metrics.rs b/adevice/src/metrics.rs
index 2bc3722..dd614fd 100644
--- a/adevice/src/metrics.rs
+++ b/adevice/src/metrics.rs
@@ -14,14 +14,14 @@
use std::fs;
use std::process::{Command, Stdio};
use std::time::UNIX_EPOCH;
-use tracing::debug;
+use tracing::info;
use uuid::Uuid;
const ENV_OUT: &str = "OUT";
const ENV_USER: &str = "USER";
const ENV_TARGET: &str = "TARGET_PRODUCT";
const ENV_SURVEY_BANNER: &str = "ADEVICE_SURVEY_BANNER";
-const METRICS_UPLOADER: &str = "/google/bin/releases/adevice-dev/";
+const METRICS_UPLOADER: &str = "/google/bin/releases/adevice-dev/metrics_uploader";
const ADEVICE_LOG_SOURCE: i32 = 2265;
pub trait MetricSender {
@@ -219,7 +219,7 @@
fn drop(&mut self) {
match self.send() {
Ok(_) => (),
- Err(e) => debug!("Failed to send metrics: {}", e),
+ Err(e) => info!("Failed to send metrics: {}", e),
};
}
}
diff --git a/atest/atest_execution_info.py b/atest/atest_execution_info.py
index dd4188b..0f35b98 100644
--- a/atest/atest_execution_info.py
+++ b/atest/atest_execution_info.py
@@ -349,6 +349,24 @@
self._proc_usb_speed.terminate()
log_path = pathlib.Path(self.work_dir)
+
+ build_log_path = log_path / 'build_logs'
+ build_log_path.mkdir()
+ AtestExecutionInfo._copy_build_artifacts_to_log_dir(
+ self._start_time,
+ time.time(),
+ self._repo_out_dir,
+ build_log_path,
+ 'build.trace',
+ )
+ AtestExecutionInfo._copy_build_artifacts_to_log_dir(
+ self._start_time,
+ time.time(),
+ self._repo_out_dir,
+ build_log_path,
+ 'verbose.log',
+ )
+
html_path = None
if self.result_file_obj and not has_non_test_options(self.args_ns):
@@ -386,9 +404,6 @@
logging.debug('handle_exc_and_send_exit_event:%s', main_exit_code)
metrics_utils.handle_exc_and_send_exit_event(main_exit_code)
- AtestExecutionInfo._copy_build_trace_to_log_dir(
- self._start_time, time.time(), self._repo_out_dir, log_path
- )
if log_uploader.is_uploading_logs():
log_uploader.upload_logs_detached(log_path)
@@ -430,20 +445,29 @@
return f'http://go/from-atest-runid/{metrics.get_run_id()}'
@staticmethod
- def _copy_build_trace_to_log_dir(
+ def _copy_build_artifacts_to_log_dir(
start_time: float,
end_time: float,
repo_out_path: pathlib.Path,
log_path: pathlib.Path,
+ file_name_prefix: str,
):
+ """Copy build trace files to log directory.
+ Params:
+ start_time: The start time of the build.
+ end_time: The end time of the build.
+ repo_out_path: The path to the repo out directory.
+ log_path: The path to the log directory.
+ file_name_prefix: The prefix of the file name.
+ """
for file in repo_out_path.iterdir():
if (
file.is_file()
- and file.name.startswith('build.trace')
+ and file.name.startswith(file_name_prefix)
and start_time <= file.stat().st_mtime <= end_time
):
- shutil.copy(file, log_path)
+ shutil.copy(file, log_path / file.name)
@staticmethod
def _generate_execution_detail(args):
diff --git a/atest/atest_execution_info_unittest.py b/atest/atest_execution_info_unittest.py
index 3c176f3..af61454 100755
--- a/atest/atest_execution_info_unittest.py
+++ b/atest/atest_execution_info_unittest.py
@@ -52,7 +52,7 @@
self.setUpPyfakefs()
self.fs.create_dir(constants.ATEST_RESULT_ROOT)
- def test_copy_build_trace_to_log_dir_new_trace_copy(self):
+ def test_copy_build_artifacts_to_log_dir_new_trace_copy(self):
start_time = 10
log_path = pathlib.Path('/logs')
self.fs.create_dir(log_path)
@@ -63,15 +63,15 @@
os.utime(build_trace_path, (20, 20))
end_time = 30
- aei.AtestExecutionInfo._copy_build_trace_to_log_dir(
- start_time, end_time, out_path, log_path
+ aei.AtestExecutionInfo._copy_build_artifacts_to_log_dir(
+ start_time, end_time, out_path, log_path, 'build.trace'
)
self.assertTrue(
self._is_dir_contains_files_with_prefix(log_path, 'build.trace')
)
- def test_copy_build_trace_to_log_dir_old_trace_does_not_copy(self):
+ def test_copy_build_artifacts_to_log_dir_old_trace_does_not_copy(self):
start_time = 10
log_path = pathlib.Path('/logs')
self.fs.create_dir(log_path)
@@ -82,8 +82,8 @@
os.utime(build_trace_path, (5, 5))
end_time = 30
- aei.AtestExecutionInfo._copy_build_trace_to_log_dir(
- start_time, end_time, out_path, log_path
+ aei.AtestExecutionInfo._copy_build_artifacts_to_log_dir(
+ start_time, end_time, out_path, log_path, 'build.trace'
)
self.assertFalse(
@@ -95,8 +95,8 @@
log_path = pathlib.Path('/logs')
self.fs.create_dir(log_path)
out_path = pathlib.Path('/out')
- build_trace_path1 = out_path / 'build.trace.1'
- build_trace_path2 = out_path / 'build.trace.2'
+ build_trace_path1 = out_path / 'build.trace.1.gz'
+ build_trace_path2 = out_path / 'build.trace.2.gz'
self.fs.create_file(build_trace_path1)
self.fs.create_file(build_trace_path2)
# Set the trace file's mtime greater than start time
@@ -104,15 +104,15 @@
os.utime(build_trace_path2, (20, 20))
end_time = 30
- aei.AtestExecutionInfo._copy_build_trace_to_log_dir(
- start_time, end_time, out_path, log_path
+ aei.AtestExecutionInfo._copy_build_artifacts_to_log_dir(
+ start_time, end_time, out_path, log_path, 'build.trace'
)
self.assertTrue(
- self._is_dir_contains_files_with_prefix(log_path, 'build.trace.1')
+ self._is_dir_contains_files_with_prefix(log_path, 'build.trace.1.gz')
)
self.assertTrue(
- self._is_dir_contains_files_with_prefix(log_path, 'build.trace.2')
+ self._is_dir_contains_files_with_prefix(log_path, 'build.trace.2.gz')
)
def _is_dir_contains_files_with_prefix(
diff --git a/atest/atest_utils.py b/atest/atest_utils.py
index 485a102..a893d60 100644
--- a/atest/atest_utils.py
+++ b/atest/atest_utils.py
@@ -20,6 +20,7 @@
from __future__ import print_function
+from collections import deque
from dataclasses import dataclass
import datetime
import enum
@@ -41,7 +42,7 @@
import sys
from threading import Thread
import traceback
-from typing import Any, Dict, List, Set, Tuple
+from typing import Any, Dict, IO, List, Set, Tuple
import urllib
import xml.etree.ElementTree as ET
import zipfile
@@ -53,7 +54,9 @@
from atest.metrics import metrics_utils
from atest.tf_proto import test_record_pb2
-_BASH_RESET_CODE = '\033[0m\n'
+_BUILD_OUTPUT_ROLLING_LINES = 6
+_BASH_CLEAR_PREVIOUS_LINE_CODE = '\033[F\033[K'
+_BASH_RESET_CODE = '\033[0m'
DIST_OUT_DIR = Path(
os.environ.get(constants.ANDROID_BUILD_TOP, os.getcwd()) + '/out/dist/'
)
@@ -267,13 +270,57 @@
return output
-# TODO: b/187122993 refine subprocess with 'with-statement' in fixit week.
-def run_limited_output(cmd, env_vars=None):
+def _stream_io_output(io_input: IO, io_output: IO, max_lines=None):
+ """Stream an IO output with max number of rolling lines to display if set.
+
+ Args:
+ input: The file-like object to read the output from.
+ output: The file-like object to write the output to.
+ max_lines: The maximum number of rolling lines to display. If None, all
+ lines will be displayed.
+ """
+ print('\n----------------------------------------------------')
+ term_width, _ = get_terminal_size()
+ full_output = []
+ last_lines = None if not max_lines else deque(maxlen=max_lines)
+ last_number_of_lines = 0
+ for line in iter(io_input.readline, ''):
+ full_output.append(line)
+ line = line.rstrip()
+ if last_lines is None:
+ io_output.write(line)
+ io_output.write('\n')
+ io_output.flush()
+ continue
+ # Split the line if it's longer than the terminal width
+ wrapped_lines = (
+ [line]
+ if len(line) <= term_width
+ else [line[i : i + term_width] for i in range(0, len(line), term_width)]
+ )
+ last_lines.extend(wrapped_lines)
+ io_output.write(_BASH_CLEAR_PREVIOUS_LINE_CODE * last_number_of_lines)
+ io_output.write('\n'.join(last_lines))
+ io_output.write('\n')
+ io_output.flush()
+ last_number_of_lines = len(last_lines)
+ io_input.close()
+ io_output.write(_BASH_RESET_CODE)
+ io_output.flush()
+ print('----------------------------------------------------')
+
+
+def run_limited_output(
+ cmd, env_vars=None, shell=False, start_new_session=False
+):
"""Runs a given command and streams the output on a single line in stdout.
Args:
cmd: A list of strings representing the command to run.
env_vars: Optional arg. Dict of env vars to set during build.
+ shell: Optional arg. Whether to use shell to run the command.
+ start_new_session: Optional arg. Whether to start a new session for the
+ command.
Raises:
subprocess.CalledProcessError: When the command exits with a non-0
@@ -281,35 +328,20 @@
"""
# Send stderr to stdout so we only have to deal with a single pipe.
with subprocess.Popen(
- cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env_vars
+ cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ env=env_vars,
+ shell=shell,
+ start_new_session=start_new_session,
+ text=True,
) as proc:
- sys.stdout.write('\n')
- term_width, _ = get_terminal_size()
- white_space = ' ' * int(term_width)
- full_output = []
- while proc.poll() is None:
- line = proc.stdout.readline().decode('utf-8')
- # Readline will often return empty strings.
- if not line:
- continue
- full_output.append(line)
- # Trim the line to the width of the terminal.
- # Note: Does not handle terminal resizing, which is probably not
- # worth checking the width every loop.
- if len(line) >= term_width:
- line = line[: term_width - 1]
- # Clear the last line we outputted.
- sys.stdout.write('\r%s\r' % white_space)
- sys.stdout.write('%s' % line.strip())
- sys.stdout.flush()
- # Reset stdout (on bash) to remove any custom formatting and newline.
- sys.stdout.write(_BASH_RESET_CODE)
- sys.stdout.flush()
- # Wait for the Popen to finish completely before checking the
- # returncode.
- proc.wait()
- if proc.returncode != 0:
- raise subprocess.CalledProcessError(proc.returncode, cmd, full_output)
+ _stream_io_output(
+ proc.stdout, _original_sys_stdout, _BUILD_OUTPUT_ROLLING_LINES
+ )
+ returncode = proc.wait()
+ if returncode:
+ raise subprocess.CalledProcessError(returncode, cmd, full_output)
def get_build_out_dir(*joinpaths) -> Path:
diff --git a/atest/atest_utils_unittest.py b/atest/atest_utils_unittest.py
index 75bc523..0b8d6a3 100755
--- a/atest/atest_utils_unittest.py
+++ b/atest/atest_utils_unittest.py
@@ -66,6 +66,62 @@
----------------------------
"""
+class StreamIoOutputTest(unittest.TestCase):
+ """Class that tests the _stream_io_output function."""
+
+ def test_stream_io_output_no_max_lines_no_clear_line_code(self):
+ """Test when max_lines is None, no clear line code is written to the stream."""
+ io_input = StringIO()
+ io_input.write(f'1\n' * 10)
+ io_input.seek(0)
+ io_output = StringIO()
+
+ atest_utils._stream_io_output(io_input, io_output, max_lines=None)
+
+ self.assertNotIn(atest_utils._BASH_CLEAR_PREVIOUS_LINE_CODE, io_output.getvalue())
+
+ @mock.patch.object(atest_utils, 'get_terminal_size', return_value=(5, -1))
+ def test_stream_io_output_wrap_long_lines(self, _):
+ """Test when max_lines is set, long lines will be wrapped."""
+ io_input = StringIO()
+ io_input.write(f'1' * 10)
+ io_input.seek(0)
+ io_output = StringIO()
+
+ atest_utils._stream_io_output(io_input, io_output, max_lines=10)
+
+ self.assertIn('11111\n11111', io_output.getvalue())
+
+ @mock.patch.object(atest_utils, 'get_terminal_size', return_value=(5, -1))
+ def test_stream_io_output_clear_lines_over_max_lines(self, _):
+ """Test when line exceeds max_lines, the previous lines are cleared."""
+ io_input = StringIO()
+ io_input.write('1\n2\n3\n')
+ io_input.seek(0)
+ io_output = StringIO()
+
+ atest_utils._stream_io_output(io_input, io_output, max_lines=2)
+
+ self.assertIn(
+ atest_utils._BASH_CLEAR_PREVIOUS_LINE_CODE * 2 + '2\n3\n',
+ io_output.getvalue(),
+ )
+
+ @mock.patch.object(atest_utils, 'get_terminal_size', return_value=(5, -1))
+ def test_stream_io_output_no_clear_lines_under_max_lines(self, _):
+ """Test when line is under max_lines, the previous lines are not cleared."""
+ io_input = StringIO()
+ io_input.write('1\n2\n3\n')
+ io_input.seek(0)
+ io_output = StringIO()
+
+ atest_utils._stream_io_output(io_input, io_output, max_lines=4)
+
+ self.assertIn(
+ atest_utils._BASH_CLEAR_PREVIOUS_LINE_CODE * 2 + '1\n2\n3\n',
+ io_output.getvalue(),
+ )
+
class ConcatenatePathTest(unittest.TestCase):
"""Class that tests path concatenation."""
diff --git a/atest/coverage/coverage.py b/atest/coverage/coverage.py
index b99cbb4..b1c49f0 100644
--- a/atest/coverage/coverage.py
+++ b/atest/coverage/coverage.py
@@ -249,7 +249,7 @@
continue
module_dir = soong_intermediates.joinpath(path, module)
# Check for unstripped binaries to report coverage.
- report_binaries.update(_find_native_binaries(module_dir))
+ report_binaries.update(module_dir.glob('*cov*/**/unstripped/*'))
# Host tests use the test itself to generate the coverage report.
info = mod_info.get_module_info(module)
@@ -264,26 +264,21 @@
str(f) for f in mod_info.get_installed_paths(module)
)
- return report_binaries
+ return _strip_irrelevant_objects(report_binaries)
-def _find_native_binaries(module_dir):
- files = module_dir.glob('*cov*/**/unstripped/*')
-
- # Exclude .rsp files. These are files containing the command line used to
- # generate the unstripped binaries, but are stored in the same directory as
- # the actual output binary.
- # Exclude .d and .d.raw files. These are Rust dependency files and are also
- # stored in the unstripped directory.
- # Exclude .toc files. These are just a table of conents of a shared library,
- # but are also stored in the unstripped directory.
- return [
- str(file)
- for file in files
- if '.rsp' not in file.suffixes
- and '.d' not in file.suffixes
- and '.toc' not in file.suffixes
- ]
+def _strip_irrelevant_objects(files):
+ objects = set()
+ for file in files:
+ cmd = ['llvm-readobj', file]
+ try:
+ subprocess.run(
+ cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+ )
+ objects.add(file)
+ except subprocess.CalledProcessError:
+ logging.debug(f'{file} is not a valid object file, skipping.')
+ return objects
def _get_all_src_paths(modules, mod_info):
diff --git a/atest/coverage/coverage_unittest.py b/atest/coverage/coverage_unittest.py
index 36194ae..690f997 100755
--- a/atest/coverage/coverage_unittest.py
+++ b/atest/coverage/coverage_unittest.py
@@ -149,7 +149,14 @@
return_value=PosixPath('/out/soong/.intermediates'),
)
@mock.patch.object(PosixPath, 'glob')
- def test_native_binary(self, _glob, _get_build_out_dir):
+ @mock.patch.object(
+ coverage,
+ '_strip_irrelevant_objects',
+ return_value={
+ '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
+ },
+ )
+ def test_native_binary(self, _strip_irrelevant_objects, _glob, _get_build_out_dir):
_glob.return_value = [
PosixPath(
'/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
@@ -175,7 +182,14 @@
return_value=PosixPath('/out/soong/.intermediates'),
)
@mock.patch.object(PosixPath, 'glob')
- def test_skip_rsp_and_d_and_toc_files(self, _glob, _get_build_out_dir):
+ @mock.patch.object(
+ coverage,
+ '_strip_irrelevant_objects',
+ return_value={
+ '/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
+ },
+ )
+ def test_skip_rsp_and_d_and_toc_files(self, _strip_irrelevant_objects, _glob, _get_build_out_dir):
_glob.return_value = [
PosixPath(
'/out/soong/.intermediates/path/to/native_bin/variant-name-cov/unstripped/native_bin'
@@ -204,7 +218,14 @@
},
)
- def test_host_test_includes_installed(self):
+ @mock.patch.object(
+ coverage,
+ '_strip_irrelevant_objects',
+ return_value={
+ '/out/host/nativetests/native_host_test'
+ },
+ )
+ def test_host_test_includes_installed(self, _strip_irrelevant_objects):
code_under_test = {'native_host_test'}
mod_info = create_module_info([
module(
diff --git a/atest/result_reporter.py b/atest/result_reporter.py
index 32a76e8..4e175d3 100644
--- a/atest/result_reporter.py
+++ b/atest/result_reporter.py
@@ -400,7 +400,12 @@
for runner_name, groups in self.runners.items():
for group_name, stats in groups.items():
name = group_name if group_name else runner_name
- summary = self.process_summary(name, stats)
+ test_run_name = (
+ self.all_test_results[-1].test_run_name
+ if self.all_test_results[-1].test_run_name != name
+ else None
+ )
+ summary = self.process_summary(name, stats, test_run_name=test_run_name)
run_summary.append(summary)
summary_list = ITER_SUMMARY.get(iteration_num, [])
summary_list.extend(run_summary)
@@ -616,7 +621,7 @@
for test_name in self.failed_tests:
print(test_name)
- def process_summary(self, name, stats):
+ def process_summary(self, name, stats, test_run_name=None):
"""Process the summary line.
Strategy:
@@ -632,6 +637,7 @@
Args:
name: A string of test name.
stats: A RunStat instance for a test group.
+ test_run_name: A string of test run name (optional)
Returns:
A summary of the test result.
@@ -687,8 +693,9 @@
)
ITER_COUNTS[name] = temp
+ summary_name = f'{name}:{test_run_name}' if test_run_name else name
summary = '%s: %s: %s, %s: %s, %s: %s, %s: %s %s %s' % (
- name,
+ summary_name,
passed_label,
stats.passed,
failed_label,