xref: /aosp_15_r20/tools/asuite/atest/atest_utils.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1*c2e18aaaSAndroid Build Coastguard Worker# Copyright 2017, The Android Open Source Project
2*c2e18aaaSAndroid Build Coastguard Worker#
3*c2e18aaaSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*c2e18aaaSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*c2e18aaaSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*c2e18aaaSAndroid Build Coastguard Worker#
7*c2e18aaaSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*c2e18aaaSAndroid Build Coastguard Worker#
9*c2e18aaaSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*c2e18aaaSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*c2e18aaaSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*c2e18aaaSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*c2e18aaaSAndroid Build Coastguard Worker# limitations under the License.
14*c2e18aaaSAndroid Build Coastguard Worker
15*c2e18aaaSAndroid Build Coastguard Worker"""Utility functions for atest."""
16*c2e18aaaSAndroid Build Coastguard Worker
17*c2e18aaaSAndroid Build Coastguard Worker
18*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=import-outside-toplevel
19*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=too-many-lines
20*c2e18aaaSAndroid Build Coastguard Worker
21*c2e18aaaSAndroid Build Coastguard Workerfrom __future__ import print_function
22*c2e18aaaSAndroid Build Coastguard Worker
23*c2e18aaaSAndroid Build Coastguard Workerfrom collections import deque
24*c2e18aaaSAndroid Build Coastguard Workerfrom dataclasses import dataclass
25*c2e18aaaSAndroid Build Coastguard Workerimport datetime
26*c2e18aaaSAndroid Build Coastguard Workerimport enum
27*c2e18aaaSAndroid Build Coastguard Workerimport fnmatch
28*c2e18aaaSAndroid Build Coastguard Workerimport hashlib
29*c2e18aaaSAndroid Build Coastguard Workerimport html
30*c2e18aaaSAndroid Build Coastguard Workerimport importlib.resources
31*c2e18aaaSAndroid Build Coastguard Workerimport importlib.util
32*c2e18aaaSAndroid Build Coastguard Workerimport io
33*c2e18aaaSAndroid Build Coastguard Workerimport itertools
34*c2e18aaaSAndroid Build Coastguard Workerimport json
35*c2e18aaaSAndroid Build Coastguard Workerimport logging
36*c2e18aaaSAndroid Build Coastguard Workerfrom multiprocessing import Process
37*c2e18aaaSAndroid Build Coastguard Workerimport os
38*c2e18aaaSAndroid Build Coastguard Workerfrom pathlib import Path
39*c2e18aaaSAndroid Build Coastguard Workerimport pickle
40*c2e18aaaSAndroid Build Coastguard Workerimport platform
41*c2e18aaaSAndroid Build Coastguard Workerimport re
42*c2e18aaaSAndroid Build Coastguard Workerimport shutil
43*c2e18aaaSAndroid Build Coastguard Workerimport subprocess
44*c2e18aaaSAndroid Build Coastguard Workerimport sys
45*c2e18aaaSAndroid Build Coastguard Workerimport threading
46*c2e18aaaSAndroid Build Coastguard Workerfrom threading import Thread
47*c2e18aaaSAndroid Build Coastguard Workerimport traceback
48*c2e18aaaSAndroid Build Coastguard Workerfrom typing import Any, Dict, IO, List, Set, Tuple
49*c2e18aaaSAndroid Build Coastguard Workerimport urllib
50*c2e18aaaSAndroid Build Coastguard Workerimport xml.etree.ElementTree as ET
51*c2e18aaaSAndroid Build Coastguard Workerimport zipfile
52*c2e18aaaSAndroid Build Coastguard Worker
53*c2e18aaaSAndroid Build Coastguard Workerfrom atest import atest_decorator
54*c2e18aaaSAndroid Build Coastguard Workerfrom atest import constants
55*c2e18aaaSAndroid Build Coastguard Workerfrom atest.atest_enum import DetectType, ExitCode, FilterType
56*c2e18aaaSAndroid Build Coastguard Workerfrom atest.metrics import metrics
57*c2e18aaaSAndroid Build Coastguard Workerfrom atest.metrics import metrics_utils
58*c2e18aaaSAndroid Build Coastguard Workerfrom atest.tf_proto import test_record_pb2
59*c2e18aaaSAndroid Build Coastguard Worker
60*c2e18aaaSAndroid Build Coastguard WorkerDEFAULT_OUTPUT_ROLLING_LINES = 6
61*c2e18aaaSAndroid Build Coastguard Worker_BASH_CLEAR_PREVIOUS_LINE_CODE = '\033[F\033[K'
62*c2e18aaaSAndroid Build Coastguard Worker_BASH_RESET_CODE = '\033[0m'
63*c2e18aaaSAndroid Build Coastguard WorkerDIST_OUT_DIR = Path(
64*c2e18aaaSAndroid Build Coastguard Worker    os.environ.get(constants.ANDROID_BUILD_TOP, os.getcwd()) + '/out/dist/'
65*c2e18aaaSAndroid Build Coastguard Worker)
66*c2e18aaaSAndroid Build Coastguard WorkerMAINLINE_MODULES_EXT_RE = re.compile(r'\.(apex|apks|apk)$')
67*c2e18aaaSAndroid Build Coastguard WorkerTEST_WITH_MAINLINE_MODULES_RE = re.compile(
68*c2e18aaaSAndroid Build Coastguard Worker    r'(?P<test>.*)\[(?P<mainline_modules>.*' r'[.](apk|apks|apex))\]$'
69*c2e18aaaSAndroid Build Coastguard Worker)
70*c2e18aaaSAndroid Build Coastguard Worker
71*c2e18aaaSAndroid Build Coastguard Worker# Arbitrary number to limit stdout for failed runs in run_limited_output.
72*c2e18aaaSAndroid Build Coastguard Worker# Reason for its use is that the make command itself has its own carriage
73*c2e18aaaSAndroid Build Coastguard Worker# return output mechanism that when collected line by line causes the streaming
74*c2e18aaaSAndroid Build Coastguard Worker# full_output list to be extremely large.
75*c2e18aaaSAndroid Build Coastguard Worker_FAILED_OUTPUT_LINE_LIMIT = 100
76*c2e18aaaSAndroid Build Coastguard Worker# Regular expression to match the start of a ninja compile:
77*c2e18aaaSAndroid Build Coastguard Worker# ex: [ 99% 39710/39711]
78*c2e18aaaSAndroid Build Coastguard Worker_BUILD_COMPILE_STATUS = re.compile(r'\[\s*(\d{1,3}%\s+)?\d+/\d+\]')
79*c2e18aaaSAndroid Build Coastguard Worker_BUILD_FAILURE = 'FAILED: '
80*c2e18aaaSAndroid Build Coastguard WorkerBUILD_TOP_HASH = hashlib.md5(
81*c2e18aaaSAndroid Build Coastguard Worker    os.environ.get(constants.ANDROID_BUILD_TOP, '').encode()
82*c2e18aaaSAndroid Build Coastguard Worker).hexdigest()
83*c2e18aaaSAndroid Build Coastguard Worker_DEFAULT_TERMINAL_WIDTH = 80
84*c2e18aaaSAndroid Build Coastguard Worker_DEFAULT_TERMINAL_HEIGHT = 25
85*c2e18aaaSAndroid Build Coastguard Worker_BUILD_CMD = 'build/soong/soong_ui.bash'
86*c2e18aaaSAndroid Build Coastguard Worker_FIND_MODIFIED_FILES_CMDS = (
87*c2e18aaaSAndroid Build Coastguard Worker    'cd {};'
88*c2e18aaaSAndroid Build Coastguard Worker    'local_branch=$(git rev-parse --abbrev-ref HEAD);'
89*c2e18aaaSAndroid Build Coastguard Worker    "remote_branch=$(git branch -r | grep '\\->' | awk '{{print $1}}');"
90*c2e18aaaSAndroid Build Coastguard Worker    # Get the number of commits from local branch to remote branch.
91*c2e18aaaSAndroid Build Coastguard Worker    'ahead=$(git rev-list --left-right --count $local_branch...$remote_branch '
92*c2e18aaaSAndroid Build Coastguard Worker    "| awk '{{print $1}}');"
93*c2e18aaaSAndroid Build Coastguard Worker    # Get the list of modified files from HEAD to previous $ahead generation.
94*c2e18aaaSAndroid Build Coastguard Worker    'git diff HEAD~$ahead --name-only'
95*c2e18aaaSAndroid Build Coastguard Worker)
96*c2e18aaaSAndroid Build Coastguard Worker_ANDROID_BUILD_EXT = ('.bp', '.mk')
97*c2e18aaaSAndroid Build Coastguard Worker
98*c2e18aaaSAndroid Build Coastguard Worker# Set of special chars for various purposes.
99*c2e18aaaSAndroid Build Coastguard Worker_REGEX_CHARS = {'[', '(', '{', '|', '\\', '*', '?', '+', '^'}
100*c2e18aaaSAndroid Build Coastguard Worker_WILDCARD_CHARS = {'?', '*'}
101*c2e18aaaSAndroid Build Coastguard Worker
102*c2e18aaaSAndroid Build Coastguard Worker_WILDCARD_FILTER_RE = re.compile(r'.*[?|*]$')
103*c2e18aaaSAndroid Build Coastguard Worker_REGULAR_FILTER_RE = re.compile(r'.*\w$')
104*c2e18aaaSAndroid Build Coastguard Worker
105*c2e18aaaSAndroid Build Coastguard WorkerSUGGESTIONS = {
106*c2e18aaaSAndroid Build Coastguard Worker    # (b/177626045) If Atest does not install target application properly.
107*c2e18aaaSAndroid Build Coastguard Worker    'Runner reported an invalid method': 'Please reflash the device(s).',
108*c2e18aaaSAndroid Build Coastguard Worker}
109*c2e18aaaSAndroid Build Coastguard Worker
110*c2e18aaaSAndroid Build Coastguard Worker_BUILD_ENV = {}
111*c2e18aaaSAndroid Build Coastguard Worker
112*c2e18aaaSAndroid Build Coastguard WorkerCACHE_VERSION = 1
113*c2e18aaaSAndroid Build Coastguard Worker
114*c2e18aaaSAndroid Build Coastguard Worker_original_sys_stdout = sys.stdout
115*c2e18aaaSAndroid Build Coastguard Worker
116*c2e18aaaSAndroid Build Coastguard Worker
117*c2e18aaaSAndroid Build Coastguard Worker@dataclass
118*c2e18aaaSAndroid Build Coastguard Workerclass BuildEnvProfiler:
119*c2e18aaaSAndroid Build Coastguard Worker  """Represents the condition before and after trigging build."""
120*c2e18aaaSAndroid Build Coastguard Worker
121*c2e18aaaSAndroid Build Coastguard Worker  ninja_file: Path
122*c2e18aaaSAndroid Build Coastguard Worker  ninja_file_mtime: float
123*c2e18aaaSAndroid Build Coastguard Worker  variable_file: Path
124*c2e18aaaSAndroid Build Coastguard Worker  variable_file_md5: str
125*c2e18aaaSAndroid Build Coastguard Worker  clean_out: bool
126*c2e18aaaSAndroid Build Coastguard Worker  build_files_integrity: bool
127*c2e18aaaSAndroid Build Coastguard Worker
128*c2e18aaaSAndroid Build Coastguard Worker
129*c2e18aaaSAndroid Build Coastguard Worker@enum.unique
130*c2e18aaaSAndroid Build Coastguard Workerclass BuildOutputMode(enum.Enum):
131*c2e18aaaSAndroid Build Coastguard Worker  'Represents the different ways to display build output.'
132*c2e18aaaSAndroid Build Coastguard Worker
133*c2e18aaaSAndroid Build Coastguard Worker  STREAMED = 'streamed'
134*c2e18aaaSAndroid Build Coastguard Worker  LOGGED = 'logged'
135*c2e18aaaSAndroid Build Coastguard Worker
136*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self, arg_name: str):
137*c2e18aaaSAndroid Build Coastguard Worker    self._description = arg_name
138*c2e18aaaSAndroid Build Coastguard Worker
139*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=missing-function-docstring
140*c2e18aaaSAndroid Build Coastguard Worker  def description(self):
141*c2e18aaaSAndroid Build Coastguard Worker    return self._description
142*c2e18aaaSAndroid Build Coastguard Worker
143*c2e18aaaSAndroid Build Coastguard Worker
144*c2e18aaaSAndroid Build Coastguard Worker@dataclass
145*c2e18aaaSAndroid Build Coastguard Workerclass AndroidVariables:
146*c2e18aaaSAndroid Build Coastguard Worker  """Class that stores the value of environment variables."""
147*c2e18aaaSAndroid Build Coastguard Worker
148*c2e18aaaSAndroid Build Coastguard Worker  build_top: str
149*c2e18aaaSAndroid Build Coastguard Worker  product_out: str
150*c2e18aaaSAndroid Build Coastguard Worker  target_out_cases: str
151*c2e18aaaSAndroid Build Coastguard Worker  host_out: str
152*c2e18aaaSAndroid Build Coastguard Worker  host_out_cases: str
153*c2e18aaaSAndroid Build Coastguard Worker  target_product: str
154*c2e18aaaSAndroid Build Coastguard Worker  build_variant: str
155*c2e18aaaSAndroid Build Coastguard Worker
156*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self):
157*c2e18aaaSAndroid Build Coastguard Worker    self.build_top = os.getenv('ANDROID_BUILD_TOP')
158*c2e18aaaSAndroid Build Coastguard Worker    self.product_out = os.getenv('ANDROID_PRODUCT_OUT')
159*c2e18aaaSAndroid Build Coastguard Worker    self.target_out_cases = os.getenv('ANDROID_TARGET_OUT_TESTCASES')
160*c2e18aaaSAndroid Build Coastguard Worker    self.host_out = os.getenv('ANDROID_HOST_OUT')
161*c2e18aaaSAndroid Build Coastguard Worker    self.host_out_cases = os.getenv('ANDROID_HOST_OUT_TESTCASES')
162*c2e18aaaSAndroid Build Coastguard Worker    self.target_product = os.getenv('TARGET_PRODUCT')
163*c2e18aaaSAndroid Build Coastguard Worker    self.build_variant = os.getenv('TARGET_BUILD_VARIANT')
164*c2e18aaaSAndroid Build Coastguard Worker
165*c2e18aaaSAndroid Build Coastguard Worker
166*c2e18aaaSAndroid Build Coastguard Workerdef get_build_top(*joinpaths: Any) -> Path:
167*c2e18aaaSAndroid Build Coastguard Worker  """Get the absolute path from the given repo path."""
168*c2e18aaaSAndroid Build Coastguard Worker  return Path(AndroidVariables().build_top, *joinpaths)
169*c2e18aaaSAndroid Build Coastguard Worker
170*c2e18aaaSAndroid Build Coastguard Worker
171*c2e18aaaSAndroid Build Coastguard Workerdef get_host_out(*joinpaths: Any) -> Path:
172*c2e18aaaSAndroid Build Coastguard Worker  """Get the absolute host out path from the given path."""
173*c2e18aaaSAndroid Build Coastguard Worker  return Path(AndroidVariables().host_out, *joinpaths)
174*c2e18aaaSAndroid Build Coastguard Worker
175*c2e18aaaSAndroid Build Coastguard Worker
176*c2e18aaaSAndroid Build Coastguard Workerdef get_product_out(*joinpaths: Any) -> Path:
177*c2e18aaaSAndroid Build Coastguard Worker  """Get the absolute product out path from the given path."""
178*c2e18aaaSAndroid Build Coastguard Worker  return Path(AndroidVariables().product_out, *joinpaths)
179*c2e18aaaSAndroid Build Coastguard Worker
180*c2e18aaaSAndroid Build Coastguard Worker
181*c2e18aaaSAndroid Build Coastguard Workerdef get_index_path(*filename: Any) -> Path:
182*c2e18aaaSAndroid Build Coastguard Worker  """Get absolute path of the desired index file."""
183*c2e18aaaSAndroid Build Coastguard Worker  return get_host_out('indices', *filename)
184*c2e18aaaSAndroid Build Coastguard Worker
185*c2e18aaaSAndroid Build Coastguard Worker
186*c2e18aaaSAndroid Build Coastguard Workerdef getenv_abs_path(env: str, suffix: str = None) -> Path:
187*c2e18aaaSAndroid Build Coastguard Worker  """Translate the environment variable to an absolute path.
188*c2e18aaaSAndroid Build Coastguard Worker
189*c2e18aaaSAndroid Build Coastguard Worker  Args:
190*c2e18aaaSAndroid Build Coastguard Worker      env: string of the given environment variable.
191*c2e18aaaSAndroid Build Coastguard Worker      suffix: string that will be appended to.
192*c2e18aaaSAndroid Build Coastguard Worker
193*c2e18aaaSAndroid Build Coastguard Worker  Returns:
194*c2e18aaaSAndroid Build Coastguard Worker      Absolute Path of the given environment variable.
195*c2e18aaaSAndroid Build Coastguard Worker  """
196*c2e18aaaSAndroid Build Coastguard Worker  env_value = os.getenv(env)
197*c2e18aaaSAndroid Build Coastguard Worker  if not env_value:
198*c2e18aaaSAndroid Build Coastguard Worker    return None
199*c2e18aaaSAndroid Build Coastguard Worker
200*c2e18aaaSAndroid Build Coastguard Worker  env_path = Path(env_value)
201*c2e18aaaSAndroid Build Coastguard Worker  if env_path.is_absolute():
202*c2e18aaaSAndroid Build Coastguard Worker    return env_path.joinpath(suffix) if suffix else env_path
203*c2e18aaaSAndroid Build Coastguard Worker
204*c2e18aaaSAndroid Build Coastguard Worker  return get_build_top(env_path, suffix) if suffix else get_build_top(env_path)
205*c2e18aaaSAndroid Build Coastguard Worker
206*c2e18aaaSAndroid Build Coastguard Worker
207*c2e18aaaSAndroid Build Coastguard Workerdef get_build_cmd(dump=False):
208*c2e18aaaSAndroid Build Coastguard Worker  """Compose build command with no-absolute path and flag "--make-mode".
209*c2e18aaaSAndroid Build Coastguard Worker
210*c2e18aaaSAndroid Build Coastguard Worker  Args:
211*c2e18aaaSAndroid Build Coastguard Worker      dump: boolean that determines the option of build/soong/soong_iu.bash.
212*c2e18aaaSAndroid Build Coastguard Worker            True: used to dump build variables, equivalent to printconfig. e.g.
213*c2e18aaaSAndroid Build Coastguard Worker              build/soong/soong_iu.bash --dumpvar-mode <VAR_NAME>
214*c2e18aaaSAndroid Build Coastguard Worker            False: (default) used to build targets in make mode. e.g.
215*c2e18aaaSAndroid Build Coastguard Worker              build/soong/soong_iu.bash --make-mode <MOD_NAME>
216*c2e18aaaSAndroid Build Coastguard Worker
217*c2e18aaaSAndroid Build Coastguard Worker  Returns:
218*c2e18aaaSAndroid Build Coastguard Worker      A list of soong build command.
219*c2e18aaaSAndroid Build Coastguard Worker  """
220*c2e18aaaSAndroid Build Coastguard Worker  make_cmd = '%s/%s' % (
221*c2e18aaaSAndroid Build Coastguard Worker      os.path.relpath(
222*c2e18aaaSAndroid Build Coastguard Worker          os.environ.get(constants.ANDROID_BUILD_TOP, os.getcwd()), os.getcwd()
223*c2e18aaaSAndroid Build Coastguard Worker      ),
224*c2e18aaaSAndroid Build Coastguard Worker      _BUILD_CMD,
225*c2e18aaaSAndroid Build Coastguard Worker  )
226*c2e18aaaSAndroid Build Coastguard Worker  if dump:
227*c2e18aaaSAndroid Build Coastguard Worker    return [make_cmd, '--dumpvar-mode', 'report_config']
228*c2e18aaaSAndroid Build Coastguard Worker  return [
229*c2e18aaaSAndroid Build Coastguard Worker      make_cmd,
230*c2e18aaaSAndroid Build Coastguard Worker      '--make-mode',
231*c2e18aaaSAndroid Build Coastguard Worker      'WRAPPER_TOOL=atest',
232*c2e18aaaSAndroid Build Coastguard Worker      f'ATEST_RUN_ID={metrics.get_run_id()}',
233*c2e18aaaSAndroid Build Coastguard Worker  ]
234*c2e18aaaSAndroid Build Coastguard Worker
235*c2e18aaaSAndroid Build Coastguard Worker
236*c2e18aaaSAndroid Build Coastguard Workerdef _capture_fail_section(full_log):
237*c2e18aaaSAndroid Build Coastguard Worker  """Return the error message from the build output.
238*c2e18aaaSAndroid Build Coastguard Worker
239*c2e18aaaSAndroid Build Coastguard Worker  Args:
240*c2e18aaaSAndroid Build Coastguard Worker      full_log: List of strings representing full output of build.
241*c2e18aaaSAndroid Build Coastguard Worker
242*c2e18aaaSAndroid Build Coastguard Worker  Returns:
243*c2e18aaaSAndroid Build Coastguard Worker      capture_output: List of strings that are build errors.
244*c2e18aaaSAndroid Build Coastguard Worker  """
245*c2e18aaaSAndroid Build Coastguard Worker  am_capturing = False
246*c2e18aaaSAndroid Build Coastguard Worker  capture_output = []
247*c2e18aaaSAndroid Build Coastguard Worker  for line in full_log:
248*c2e18aaaSAndroid Build Coastguard Worker    if am_capturing and _BUILD_COMPILE_STATUS.match(line):
249*c2e18aaaSAndroid Build Coastguard Worker      break
250*c2e18aaaSAndroid Build Coastguard Worker    if am_capturing or line.startswith(_BUILD_FAILURE):
251*c2e18aaaSAndroid Build Coastguard Worker      capture_output.append(line)
252*c2e18aaaSAndroid Build Coastguard Worker      am_capturing = True
253*c2e18aaaSAndroid Build Coastguard Worker      continue
254*c2e18aaaSAndroid Build Coastguard Worker  return capture_output
255*c2e18aaaSAndroid Build Coastguard Worker
256*c2e18aaaSAndroid Build Coastguard Worker
257*c2e18aaaSAndroid Build Coastguard Workerdef _capture_limited_output(full_log):
258*c2e18aaaSAndroid Build Coastguard Worker  """Return the limited error message from capture_failed_section.
259*c2e18aaaSAndroid Build Coastguard Worker
260*c2e18aaaSAndroid Build Coastguard Worker  Args:
261*c2e18aaaSAndroid Build Coastguard Worker      full_log: List of strings representing full output of build.
262*c2e18aaaSAndroid Build Coastguard Worker
263*c2e18aaaSAndroid Build Coastguard Worker  Returns:
264*c2e18aaaSAndroid Build Coastguard Worker      output: List of strings that are build errors.
265*c2e18aaaSAndroid Build Coastguard Worker  """
266*c2e18aaaSAndroid Build Coastguard Worker  # Parse out the build error to output.
267*c2e18aaaSAndroid Build Coastguard Worker  output = _capture_fail_section(full_log)
268*c2e18aaaSAndroid Build Coastguard Worker  if not output:
269*c2e18aaaSAndroid Build Coastguard Worker    output = full_log
270*c2e18aaaSAndroid Build Coastguard Worker  if len(output) >= _FAILED_OUTPUT_LINE_LIMIT:
271*c2e18aaaSAndroid Build Coastguard Worker    output = output[-_FAILED_OUTPUT_LINE_LIMIT:]
272*c2e18aaaSAndroid Build Coastguard Worker  output = 'Output (may be trimmed):\n%s' % ''.join(output)
273*c2e18aaaSAndroid Build Coastguard Worker  return output
274*c2e18aaaSAndroid Build Coastguard Worker
275*c2e18aaaSAndroid Build Coastguard Worker
276*c2e18aaaSAndroid Build Coastguard Workerdef stream_io_output(
277*c2e18aaaSAndroid Build Coastguard Worker    io_input: IO,
278*c2e18aaaSAndroid Build Coastguard Worker    max_lines=None,
279*c2e18aaaSAndroid Build Coastguard Worker    full_output_receiver: IO = None,
280*c2e18aaaSAndroid Build Coastguard Worker    io_output: IO = None,
281*c2e18aaaSAndroid Build Coastguard Worker    is_io_output_atty=None,
282*c2e18aaaSAndroid Build Coastguard Worker):
283*c2e18aaaSAndroid Build Coastguard Worker  """Stream an IO output with max number of rolling lines to display if set.
284*c2e18aaaSAndroid Build Coastguard Worker
285*c2e18aaaSAndroid Build Coastguard Worker  Args:
286*c2e18aaaSAndroid Build Coastguard Worker      input: The file-like object to read the output from.
287*c2e18aaaSAndroid Build Coastguard Worker      max_lines: The maximum number of rolling lines to display. If None, all
288*c2e18aaaSAndroid Build Coastguard Worker        lines will be displayed.
289*c2e18aaaSAndroid Build Coastguard Worker      full_output_receiver: Optional io to receive the full output.
290*c2e18aaaSAndroid Build Coastguard Worker      io_output: The file-like object to write the output to.
291*c2e18aaaSAndroid Build Coastguard Worker      is_io_output_atty: Whether the io_output is a TTY.
292*c2e18aaaSAndroid Build Coastguard Worker  """
293*c2e18aaaSAndroid Build Coastguard Worker  if io_output is None:
294*c2e18aaaSAndroid Build Coastguard Worker    io_output = _original_sys_stdout
295*c2e18aaaSAndroid Build Coastguard Worker  if is_io_output_atty is None:
296*c2e18aaaSAndroid Build Coastguard Worker    is_io_output_atty = _has_colors(io_output)
297*c2e18aaaSAndroid Build Coastguard Worker  if not max_lines or not is_io_output_atty:
298*c2e18aaaSAndroid Build Coastguard Worker    for line in iter(io_input.readline, ''):
299*c2e18aaaSAndroid Build Coastguard Worker      if not line:
300*c2e18aaaSAndroid Build Coastguard Worker        break
301*c2e18aaaSAndroid Build Coastguard Worker      if full_output_receiver is not None:
302*c2e18aaaSAndroid Build Coastguard Worker        full_output_receiver.write(
303*c2e18aaaSAndroid Build Coastguard Worker            line if isinstance(line, str) else line.decode('utf-8')
304*c2e18aaaSAndroid Build Coastguard Worker        )
305*c2e18aaaSAndroid Build Coastguard Worker      io_output.write(line)
306*c2e18aaaSAndroid Build Coastguard Worker      io_output.flush()
307*c2e18aaaSAndroid Build Coastguard Worker    return
308*c2e18aaaSAndroid Build Coastguard Worker
309*c2e18aaaSAndroid Build Coastguard Worker  term_width, _ = get_terminal_size()
310*c2e18aaaSAndroid Build Coastguard Worker  last_lines = deque(maxlen=max_lines)
311*c2e18aaaSAndroid Build Coastguard Worker  is_rolling = True
312*c2e18aaaSAndroid Build Coastguard Worker
313*c2e18aaaSAndroid Build Coastguard Worker  def reset_output():
314*c2e18aaaSAndroid Build Coastguard Worker    if is_rolling and last_lines:
315*c2e18aaaSAndroid Build Coastguard Worker      io_output.write(_BASH_CLEAR_PREVIOUS_LINE_CODE * (len(last_lines) + 2))
316*c2e18aaaSAndroid Build Coastguard Worker
317*c2e18aaaSAndroid Build Coastguard Worker  def write_output(new_lines: list[str]):
318*c2e18aaaSAndroid Build Coastguard Worker    if not is_rolling:
319*c2e18aaaSAndroid Build Coastguard Worker      return
320*c2e18aaaSAndroid Build Coastguard Worker    last_lines.extend(new_lines)
321*c2e18aaaSAndroid Build Coastguard Worker    lines = ['========== Rolling subprocess output ==========']
322*c2e18aaaSAndroid Build Coastguard Worker    lines.extend(last_lines)
323*c2e18aaaSAndroid Build Coastguard Worker    lines.append('-----------------------------------------------')
324*c2e18aaaSAndroid Build Coastguard Worker    io_output.write('\n'.join(lines))
325*c2e18aaaSAndroid Build Coastguard Worker    io_output.write('\n')
326*c2e18aaaSAndroid Build Coastguard Worker    io_output.flush()
327*c2e18aaaSAndroid Build Coastguard Worker
328*c2e18aaaSAndroid Build Coastguard Worker  original_stdout = sys.stdout
329*c2e18aaaSAndroid Build Coastguard Worker  original_stderr = sys.stderr
330*c2e18aaaSAndroid Build Coastguard Worker
331*c2e18aaaSAndroid Build Coastguard Worker  lock = threading.Lock()
332*c2e18aaaSAndroid Build Coastguard Worker
333*c2e18aaaSAndroid Build Coastguard Worker  class SafeStdout:
334*c2e18aaaSAndroid Build Coastguard Worker
335*c2e18aaaSAndroid Build Coastguard Worker    def __init__(self):
336*c2e18aaaSAndroid Build Coastguard Worker      self._buffers = []
337*c2e18aaaSAndroid Build Coastguard Worker
338*c2e18aaaSAndroid Build Coastguard Worker    def write(self, buf: str) -> None:
339*c2e18aaaSAndroid Build Coastguard Worker      if len(buf) == 1 and buf[0] == '\n' and self._buffers:
340*c2e18aaaSAndroid Build Coastguard Worker        with lock:
341*c2e18aaaSAndroid Build Coastguard Worker          reset_output()
342*c2e18aaaSAndroid Build Coastguard Worker          original_stdout.write(''.join(self._buffers))
343*c2e18aaaSAndroid Build Coastguard Worker          original_stdout.write('\n')
344*c2e18aaaSAndroid Build Coastguard Worker          original_stdout.flush()
345*c2e18aaaSAndroid Build Coastguard Worker          write_output([])
346*c2e18aaaSAndroid Build Coastguard Worker          self._buffers.clear()
347*c2e18aaaSAndroid Build Coastguard Worker      else:
348*c2e18aaaSAndroid Build Coastguard Worker        self._buffers.append(buf)
349*c2e18aaaSAndroid Build Coastguard Worker
350*c2e18aaaSAndroid Build Coastguard Worker    def flush(self) -> None:
351*c2e18aaaSAndroid Build Coastguard Worker      original_stdout.flush()
352*c2e18aaaSAndroid Build Coastguard Worker
353*c2e18aaaSAndroid Build Coastguard Worker  sys.stdout = SafeStdout()
354*c2e18aaaSAndroid Build Coastguard Worker  sys.stderr = sys.stdout
355*c2e18aaaSAndroid Build Coastguard Worker
356*c2e18aaaSAndroid Build Coastguard Worker  for line in iter(io_input.readline, ''):
357*c2e18aaaSAndroid Build Coastguard Worker    if not line:
358*c2e18aaaSAndroid Build Coastguard Worker      break
359*c2e18aaaSAndroid Build Coastguard Worker    line = line.decode('utf-8') if isinstance(line, bytes) else line
360*c2e18aaaSAndroid Build Coastguard Worker    if full_output_receiver is not None:
361*c2e18aaaSAndroid Build Coastguard Worker      full_output_receiver.write(line)
362*c2e18aaaSAndroid Build Coastguard Worker    line = line.rstrip().replace('\t', '  ')
363*c2e18aaaSAndroid Build Coastguard Worker    # Split the line if it's longer than the terminal width
364*c2e18aaaSAndroid Build Coastguard Worker    wrapped_lines = (
365*c2e18aaaSAndroid Build Coastguard Worker        [line]
366*c2e18aaaSAndroid Build Coastguard Worker        if len(line) <= term_width
367*c2e18aaaSAndroid Build Coastguard Worker        else [line[i : i + term_width] for i in range(0, len(line), term_width)]
368*c2e18aaaSAndroid Build Coastguard Worker    )
369*c2e18aaaSAndroid Build Coastguard Worker    with lock:
370*c2e18aaaSAndroid Build Coastguard Worker      reset_output()
371*c2e18aaaSAndroid Build Coastguard Worker      write_output(wrapped_lines)
372*c2e18aaaSAndroid Build Coastguard Worker
373*c2e18aaaSAndroid Build Coastguard Worker  with lock:
374*c2e18aaaSAndroid Build Coastguard Worker    reset_output()
375*c2e18aaaSAndroid Build Coastguard Worker    is_rolling = False
376*c2e18aaaSAndroid Build Coastguard Worker    io_output.write(_BASH_RESET_CODE)
377*c2e18aaaSAndroid Build Coastguard Worker    io_output.flush()
378*c2e18aaaSAndroid Build Coastguard Worker
379*c2e18aaaSAndroid Build Coastguard Worker  sys.stdout = original_stdout
380*c2e18aaaSAndroid Build Coastguard Worker  sys.stderr = original_stderr
381*c2e18aaaSAndroid Build Coastguard Worker
382*c2e18aaaSAndroid Build Coastguard Worker  io_input.close()
383*c2e18aaaSAndroid Build Coastguard Worker
384*c2e18aaaSAndroid Build Coastguard Worker
385*c2e18aaaSAndroid Build Coastguard Workerdef run_limited_output(
386*c2e18aaaSAndroid Build Coastguard Worker    cmd, env_vars=None, shell=False, start_new_session=False
387*c2e18aaaSAndroid Build Coastguard Worker):
388*c2e18aaaSAndroid Build Coastguard Worker  """Runs a given command and streams the output on a single line in stdout.
389*c2e18aaaSAndroid Build Coastguard Worker
390*c2e18aaaSAndroid Build Coastguard Worker  Args:
391*c2e18aaaSAndroid Build Coastguard Worker      cmd: A list of strings representing the command to run.
392*c2e18aaaSAndroid Build Coastguard Worker      env_vars: Optional arg. Dict of env vars to set during build.
393*c2e18aaaSAndroid Build Coastguard Worker      shell: Optional arg. Whether to use shell to run the command.
394*c2e18aaaSAndroid Build Coastguard Worker      start_new_session: Optional arg. Whether to start a new session for the
395*c2e18aaaSAndroid Build Coastguard Worker        command.
396*c2e18aaaSAndroid Build Coastguard Worker
397*c2e18aaaSAndroid Build Coastguard Worker  Raises:
398*c2e18aaaSAndroid Build Coastguard Worker      subprocess.CalledProcessError: When the command exits with a non-0
399*c2e18aaaSAndroid Build Coastguard Worker          exitcode.
400*c2e18aaaSAndroid Build Coastguard Worker  """
401*c2e18aaaSAndroid Build Coastguard Worker  # Send stderr to stdout so we only have to deal with a single pipe.
402*c2e18aaaSAndroid Build Coastguard Worker  with subprocess.Popen(
403*c2e18aaaSAndroid Build Coastguard Worker      cmd,
404*c2e18aaaSAndroid Build Coastguard Worker      stdout=subprocess.PIPE,
405*c2e18aaaSAndroid Build Coastguard Worker      stderr=subprocess.STDOUT,
406*c2e18aaaSAndroid Build Coastguard Worker      env=env_vars,
407*c2e18aaaSAndroid Build Coastguard Worker      shell=shell,
408*c2e18aaaSAndroid Build Coastguard Worker      start_new_session=start_new_session,
409*c2e18aaaSAndroid Build Coastguard Worker      text=True,
410*c2e18aaaSAndroid Build Coastguard Worker  ) as proc:
411*c2e18aaaSAndroid Build Coastguard Worker    full_output_receiver = io.StringIO()
412*c2e18aaaSAndroid Build Coastguard Worker    stream_io_output(
413*c2e18aaaSAndroid Build Coastguard Worker        proc.stdout,
414*c2e18aaaSAndroid Build Coastguard Worker        DEFAULT_OUTPUT_ROLLING_LINES,
415*c2e18aaaSAndroid Build Coastguard Worker        full_output_receiver,
416*c2e18aaaSAndroid Build Coastguard Worker        _original_sys_stdout,
417*c2e18aaaSAndroid Build Coastguard Worker    )
418*c2e18aaaSAndroid Build Coastguard Worker    returncode = proc.wait()
419*c2e18aaaSAndroid Build Coastguard Worker    if returncode:
420*c2e18aaaSAndroid Build Coastguard Worker      raise subprocess.CalledProcessError(
421*c2e18aaaSAndroid Build Coastguard Worker          returncode, cmd, full_output_receiver.getvalue()
422*c2e18aaaSAndroid Build Coastguard Worker      )
423*c2e18aaaSAndroid Build Coastguard Worker
424*c2e18aaaSAndroid Build Coastguard Worker
425*c2e18aaaSAndroid Build Coastguard Workerdef get_build_out_dir(*joinpaths) -> Path:
426*c2e18aaaSAndroid Build Coastguard Worker  """Get android build out directory.
427*c2e18aaaSAndroid Build Coastguard Worker
428*c2e18aaaSAndroid Build Coastguard Worker  The order of the rules are:
429*c2e18aaaSAndroid Build Coastguard Worker  1. OUT_DIR
430*c2e18aaaSAndroid Build Coastguard Worker  2. OUT_DIR_COMMON_BASE
431*c2e18aaaSAndroid Build Coastguard Worker  3. ANDROID_BUILD_TOP/out
432*c2e18aaaSAndroid Build Coastguard Worker
433*c2e18aaaSAndroid Build Coastguard Worker  e.g. OUT_DIR='/disk1/out' -> '/disk1/out'
434*c2e18aaaSAndroid Build Coastguard Worker       OUT_DIR='out_dir'    -> '<build_top>/out_dir'
435*c2e18aaaSAndroid Build Coastguard Worker
436*c2e18aaaSAndroid Build Coastguard Worker       Assume the branch name is 'aosp-main':
437*c2e18aaaSAndroid Build Coastguard Worker       OUT_DIR_COMMON_BASE='/disk2/out' -> '/disk1/out/aosp-main'
438*c2e18aaaSAndroid Build Coastguard Worker       OUT_DIR_COMMON_BASE='out_dir'    -> '<build_top>/out_dir/aosp-main'
439*c2e18aaaSAndroid Build Coastguard Worker
440*c2e18aaaSAndroid Build Coastguard Worker  Returns:
441*c2e18aaaSAndroid Build Coastguard Worker      Absolute Path of the out directory.
442*c2e18aaaSAndroid Build Coastguard Worker  """
443*c2e18aaaSAndroid Build Coastguard Worker  out_dir = getenv_abs_path('OUT_DIR')
444*c2e18aaaSAndroid Build Coastguard Worker  if out_dir:
445*c2e18aaaSAndroid Build Coastguard Worker    return out_dir.joinpath(*joinpaths)
446*c2e18aaaSAndroid Build Coastguard Worker
447*c2e18aaaSAndroid Build Coastguard Worker  # https://source.android.com/setup/build/initializing#using-a-separate-output-directory
448*c2e18aaaSAndroid Build Coastguard Worker  basename = get_build_top().name
449*c2e18aaaSAndroid Build Coastguard Worker  out_dir_common_base = getenv_abs_path('OUT_DIR_COMMON_BASE', basename)
450*c2e18aaaSAndroid Build Coastguard Worker  if out_dir_common_base:
451*c2e18aaaSAndroid Build Coastguard Worker    return out_dir_common_base.joinpath(*joinpaths)
452*c2e18aaaSAndroid Build Coastguard Worker
453*c2e18aaaSAndroid Build Coastguard Worker  return get_build_top('out').joinpath(*joinpaths)
454*c2e18aaaSAndroid Build Coastguard Worker
455*c2e18aaaSAndroid Build Coastguard Worker
456*c2e18aaaSAndroid Build Coastguard Workerdef update_build_env(env: Dict[str, str]):
457*c2e18aaaSAndroid Build Coastguard Worker  """Method that updates build environment variables."""
458*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=global-statement, global-variable-not-assigned
459*c2e18aaaSAndroid Build Coastguard Worker  global _BUILD_ENV
460*c2e18aaaSAndroid Build Coastguard Worker  _BUILD_ENV.update(env)
461*c2e18aaaSAndroid Build Coastguard Worker
462*c2e18aaaSAndroid Build Coastguard Worker
463*c2e18aaaSAndroid Build Coastguard Workerdef build(build_targets: Set[str]):
464*c2e18aaaSAndroid Build Coastguard Worker  """Shell out and invoke run_build_cmd to make build_targets.
465*c2e18aaaSAndroid Build Coastguard Worker
466*c2e18aaaSAndroid Build Coastguard Worker  Args:
467*c2e18aaaSAndroid Build Coastguard Worker      build_targets: A set of strings of build targets to make.
468*c2e18aaaSAndroid Build Coastguard Worker
469*c2e18aaaSAndroid Build Coastguard Worker  Returns:
470*c2e18aaaSAndroid Build Coastguard Worker      Boolean of whether build command was successful, True if nothing to
471*c2e18aaaSAndroid Build Coastguard Worker      build.
472*c2e18aaaSAndroid Build Coastguard Worker  """
473*c2e18aaaSAndroid Build Coastguard Worker  if not build_targets:
474*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('No build targets, skipping build.')
475*c2e18aaaSAndroid Build Coastguard Worker    return True
476*c2e18aaaSAndroid Build Coastguard Worker
477*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=global-statement, global-variable-not-assigned
478*c2e18aaaSAndroid Build Coastguard Worker  global _BUILD_ENV
479*c2e18aaaSAndroid Build Coastguard Worker  full_env_vars = os.environ.copy()
480*c2e18aaaSAndroid Build Coastguard Worker  update_build_env(full_env_vars)
481*c2e18aaaSAndroid Build Coastguard Worker  print(
482*c2e18aaaSAndroid Build Coastguard Worker      '\n%s\n%s'
483*c2e18aaaSAndroid Build Coastguard Worker      % (mark_cyan('Building Dependencies...'), ', '.join(build_targets))
484*c2e18aaaSAndroid Build Coastguard Worker  )
485*c2e18aaaSAndroid Build Coastguard Worker  logging.debug('Building Dependencies: %s', ' '.join(build_targets))
486*c2e18aaaSAndroid Build Coastguard Worker  cmd = get_build_cmd() + list(build_targets)
487*c2e18aaaSAndroid Build Coastguard Worker  return _run_build_cmd(cmd, _BUILD_ENV)
488*c2e18aaaSAndroid Build Coastguard Worker
489*c2e18aaaSAndroid Build Coastguard Worker
490*c2e18aaaSAndroid Build Coastguard Workerdef _run_build_cmd_with_limited_output(
491*c2e18aaaSAndroid Build Coastguard Worker    cmd: List[str], env_vars: Dict[str, str] = None
492*c2e18aaaSAndroid Build Coastguard Worker) -> None:
493*c2e18aaaSAndroid Build Coastguard Worker  """Runs the build command and streams the output on a single line in stdout.
494*c2e18aaaSAndroid Build Coastguard Worker
495*c2e18aaaSAndroid Build Coastguard Worker  Args:
496*c2e18aaaSAndroid Build Coastguard Worker      cmd: A list of strings representing the command to run.
497*c2e18aaaSAndroid Build Coastguard Worker      env_vars: Optional arg. Dict of env vars to set during build.
498*c2e18aaaSAndroid Build Coastguard Worker
499*c2e18aaaSAndroid Build Coastguard Worker  Raises:
500*c2e18aaaSAndroid Build Coastguard Worker      subprocess.CalledProcessError: When the command exits with a non-0
501*c2e18aaaSAndroid Build Coastguard Worker          exitcode.
502*c2e18aaaSAndroid Build Coastguard Worker  """
503*c2e18aaaSAndroid Build Coastguard Worker  try:
504*c2e18aaaSAndroid Build Coastguard Worker    run_limited_output(cmd, env_vars=env_vars)
505*c2e18aaaSAndroid Build Coastguard Worker  except subprocess.CalledProcessError as e:
506*c2e18aaaSAndroid Build Coastguard Worker    # get error log from "OUT_DIR/error.log"
507*c2e18aaaSAndroid Build Coastguard Worker    error_log_file = get_build_out_dir('error.log')
508*c2e18aaaSAndroid Build Coastguard Worker    output = []
509*c2e18aaaSAndroid Build Coastguard Worker    if error_log_file.is_file():
510*c2e18aaaSAndroid Build Coastguard Worker      if error_log_file.stat().st_size > 0:
511*c2e18aaaSAndroid Build Coastguard Worker        with open(error_log_file, encoding='utf-8') as f:
512*c2e18aaaSAndroid Build Coastguard Worker          output = f.read()
513*c2e18aaaSAndroid Build Coastguard Worker    if not output:
514*c2e18aaaSAndroid Build Coastguard Worker      output = _capture_limited_output(e.output)
515*c2e18aaaSAndroid Build Coastguard Worker    raise subprocess.CalledProcessError(e.returncode, e.cmd, output)
516*c2e18aaaSAndroid Build Coastguard Worker
517*c2e18aaaSAndroid Build Coastguard Worker
518*c2e18aaaSAndroid Build Coastguard Workerdef _run_build_cmd(cmd: List[str], env_vars: Dict[str, str]):
519*c2e18aaaSAndroid Build Coastguard Worker  """The main process of building targets.
520*c2e18aaaSAndroid Build Coastguard Worker
521*c2e18aaaSAndroid Build Coastguard Worker  Args:
522*c2e18aaaSAndroid Build Coastguard Worker      cmd: A list of soong command.
523*c2e18aaaSAndroid Build Coastguard Worker      env_vars: Dict of environment variables used for build.
524*c2e18aaaSAndroid Build Coastguard Worker
525*c2e18aaaSAndroid Build Coastguard Worker  Returns:
526*c2e18aaaSAndroid Build Coastguard Worker      Boolean of whether build command was successful, True if nothing to
527*c2e18aaaSAndroid Build Coastguard Worker      build.
528*c2e18aaaSAndroid Build Coastguard Worker  """
529*c2e18aaaSAndroid Build Coastguard Worker  logging.debug('Executing command: %s', cmd)
530*c2e18aaaSAndroid Build Coastguard Worker  build_profiler = _build_env_profiling()
531*c2e18aaaSAndroid Build Coastguard Worker  try:
532*c2e18aaaSAndroid Build Coastguard Worker    if env_vars.get('BUILD_OUTPUT_MODE') == BuildOutputMode.STREAMED.value:
533*c2e18aaaSAndroid Build Coastguard Worker      print()
534*c2e18aaaSAndroid Build Coastguard Worker      subprocess.check_call(cmd, stderr=subprocess.STDOUT, env=env_vars)
535*c2e18aaaSAndroid Build Coastguard Worker    else:
536*c2e18aaaSAndroid Build Coastguard Worker      # Note that piping stdout forces Soong to switch to 'dumb terminal
537*c2e18aaaSAndroid Build Coastguard Worker      # mode' which only prints completed actions. This gives users the
538*c2e18aaaSAndroid Build Coastguard Worker      # impression that actions are taking longer than they really are.
539*c2e18aaaSAndroid Build Coastguard Worker      # See b/233044822 for more details.
540*c2e18aaaSAndroid Build Coastguard Worker      log_path = get_build_out_dir('verbose.log.gz')
541*c2e18aaaSAndroid Build Coastguard Worker      print(
542*c2e18aaaSAndroid Build Coastguard Worker          '\n(Build log may not reflect actual status in simple output'
543*c2e18aaaSAndroid Build Coastguard Worker          'mode; check {} for detail after build finishes.)'.format(
544*c2e18aaaSAndroid Build Coastguard Worker              mark_cyan(f'{log_path}')
545*c2e18aaaSAndroid Build Coastguard Worker          ),
546*c2e18aaaSAndroid Build Coastguard Worker          end='',
547*c2e18aaaSAndroid Build Coastguard Worker      )
548*c2e18aaaSAndroid Build Coastguard Worker      _run_build_cmd_with_limited_output(cmd, env_vars=env_vars)
549*c2e18aaaSAndroid Build Coastguard Worker    _send_build_condition_metrics(build_profiler, cmd)
550*c2e18aaaSAndroid Build Coastguard Worker    print_and_log_info('Build successful')
551*c2e18aaaSAndroid Build Coastguard Worker    return True
552*c2e18aaaSAndroid Build Coastguard Worker  except subprocess.CalledProcessError as err:
553*c2e18aaaSAndroid Build Coastguard Worker    print_and_log_error('Build failure when running: %s', ' '.join(cmd))
554*c2e18aaaSAndroid Build Coastguard Worker    if err.output:
555*c2e18aaaSAndroid Build Coastguard Worker      print_and_log_error(err.output)
556*c2e18aaaSAndroid Build Coastguard Worker    return False
557*c2e18aaaSAndroid Build Coastguard Worker
558*c2e18aaaSAndroid Build Coastguard Worker
559*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=unused-argument
560*c2e18aaaSAndroid Build Coastguard Workerdef get_result_server_args(for_test_mapping=False):
561*c2e18aaaSAndroid Build Coastguard Worker  """Return list of args for communication with result server.
562*c2e18aaaSAndroid Build Coastguard Worker
563*c2e18aaaSAndroid Build Coastguard Worker  Args:
564*c2e18aaaSAndroid Build Coastguard Worker      for_test_mapping: True if the test run is for Test Mapping to include
565*c2e18aaaSAndroid Build Coastguard Worker        additional reporting args. Default is False.
566*c2e18aaaSAndroid Build Coastguard Worker  """
567*c2e18aaaSAndroid Build Coastguard Worker  # Customize test mapping argument here if needed.
568*c2e18aaaSAndroid Build Coastguard Worker  return constants.RESULT_SERVER_ARGS
569*c2e18aaaSAndroid Build Coastguard Worker
570*c2e18aaaSAndroid Build Coastguard Worker
571*c2e18aaaSAndroid Build Coastguard Workerdef sort_and_group(iterable, key):
572*c2e18aaaSAndroid Build Coastguard Worker  """Sort and group helper function."""
573*c2e18aaaSAndroid Build Coastguard Worker  return itertools.groupby(sorted(iterable, key=key), key=key)
574*c2e18aaaSAndroid Build Coastguard Worker
575*c2e18aaaSAndroid Build Coastguard Worker
576*c2e18aaaSAndroid Build Coastguard Workerdef is_supported_mainline_module(installed_path: str) -> re.Match:
577*c2e18aaaSAndroid Build Coastguard Worker  """Determine whether the given path is supported."""
578*c2e18aaaSAndroid Build Coastguard Worker  return re.search(MAINLINE_MODULES_EXT_RE, installed_path)
579*c2e18aaaSAndroid Build Coastguard Worker
580*c2e18aaaSAndroid Build Coastguard Worker
581*c2e18aaaSAndroid Build Coastguard Workerdef get_test_and_mainline_modules(test_name: str) -> re.Match:
582*c2e18aaaSAndroid Build Coastguard Worker  """Return test name and mainline modules from the given test."""
583*c2e18aaaSAndroid Build Coastguard Worker  return TEST_WITH_MAINLINE_MODULES_RE.match(test_name)
584*c2e18aaaSAndroid Build Coastguard Worker
585*c2e18aaaSAndroid Build Coastguard Worker
586*c2e18aaaSAndroid Build Coastguard Workerdef is_test_mapping(args):
587*c2e18aaaSAndroid Build Coastguard Worker  """Check if the atest command intends to run tests in test mapping.
588*c2e18aaaSAndroid Build Coastguard Worker
589*c2e18aaaSAndroid Build Coastguard Worker  When atest runs tests in test mapping, it must have at most one test
590*c2e18aaaSAndroid Build Coastguard Worker  specified. If a test is specified, it must be started with  `:`,
591*c2e18aaaSAndroid Build Coastguard Worker  which means the test value is a test group name in TEST_MAPPING file, e.g.,
592*c2e18aaaSAndroid Build Coastguard Worker  `:postsubmit`.
593*c2e18aaaSAndroid Build Coastguard Worker
594*c2e18aaaSAndroid Build Coastguard Worker  If --host-unit-test-only or --smart-testing-local was applied, it doesn't
595*c2e18aaaSAndroid Build Coastguard Worker  intend to be a test_mapping test.
596*c2e18aaaSAndroid Build Coastguard Worker  If any test mapping options is specified, the atest command must also be
597*c2e18aaaSAndroid Build Coastguard Worker  set to run tests in test mapping files.
598*c2e18aaaSAndroid Build Coastguard Worker
599*c2e18aaaSAndroid Build Coastguard Worker  Args:
600*c2e18aaaSAndroid Build Coastguard Worker      args: arg parsed object.
601*c2e18aaaSAndroid Build Coastguard Worker
602*c2e18aaaSAndroid Build Coastguard Worker  Returns:
603*c2e18aaaSAndroid Build Coastguard Worker      True if the args indicates atest shall run tests in test mapping. False
604*c2e18aaaSAndroid Build Coastguard Worker      otherwise.
605*c2e18aaaSAndroid Build Coastguard Worker  """
606*c2e18aaaSAndroid Build Coastguard Worker  if args.host_unit_test_only:
607*c2e18aaaSAndroid Build Coastguard Worker    return False
608*c2e18aaaSAndroid Build Coastguard Worker  if any((args.test_mapping, args.include_subdirs, not args.tests)):
609*c2e18aaaSAndroid Build Coastguard Worker    return True
610*c2e18aaaSAndroid Build Coastguard Worker  # ':postsubmit' implicitly indicates running in test-mapping mode.
611*c2e18aaaSAndroid Build Coastguard Worker  return all((len(args.tests) == 1, args.tests[0][0] == ':'))
612*c2e18aaaSAndroid Build Coastguard Worker
613*c2e18aaaSAndroid Build Coastguard Worker
614*c2e18aaaSAndroid Build Coastguard Workerdef is_atty_terminal() -> bool:
615*c2e18aaaSAndroid Build Coastguard Worker  """Check if the current process is running in a TTY."""
616*c2e18aaaSAndroid Build Coastguard Worker  return getattr(_original_sys_stdout, 'isatty', lambda: False)()
617*c2e18aaaSAndroid Build Coastguard Worker
618*c2e18aaaSAndroid Build Coastguard Worker
619*c2e18aaaSAndroid Build Coastguard Workerdef _has_colors(stream):
620*c2e18aaaSAndroid Build Coastguard Worker  """Check the output stream is colorful.
621*c2e18aaaSAndroid Build Coastguard Worker
622*c2e18aaaSAndroid Build Coastguard Worker  Args:
623*c2e18aaaSAndroid Build Coastguard Worker      stream: The standard file stream.
624*c2e18aaaSAndroid Build Coastguard Worker
625*c2e18aaaSAndroid Build Coastguard Worker  Returns:
626*c2e18aaaSAndroid Build Coastguard Worker      True if the file stream can interpreter the ANSI color code.
627*c2e18aaaSAndroid Build Coastguard Worker  """
628*c2e18aaaSAndroid Build Coastguard Worker  # Following from Python cookbook, #475186
629*c2e18aaaSAndroid Build Coastguard Worker  # Auto color only on TTYs
630*c2e18aaaSAndroid Build Coastguard Worker  # curses.tigetnum() cannot be used for telling supported color numbers
631*c2e18aaaSAndroid Build Coastguard Worker  # because it does not come with the prebuilt py3-cmd.
632*c2e18aaaSAndroid Build Coastguard Worker  return getattr(stream, 'isatty', lambda: False)()
633*c2e18aaaSAndroid Build Coastguard Worker
634*c2e18aaaSAndroid Build Coastguard Worker
635*c2e18aaaSAndroid Build Coastguard Workerdef colorize(text, color, bp_color=None):
636*c2e18aaaSAndroid Build Coastguard Worker  """Convert to colorful string with ANSI escape code.
637*c2e18aaaSAndroid Build Coastguard Worker
638*c2e18aaaSAndroid Build Coastguard Worker  Args:
639*c2e18aaaSAndroid Build Coastguard Worker      text: A string to print.
640*c2e18aaaSAndroid Build Coastguard Worker      color: Forground(Text) color which is an ANSI code shift for colorful
641*c2e18aaaSAndroid Build Coastguard Worker        print. They are defined in constants_default.py.
642*c2e18aaaSAndroid Build Coastguard Worker      bp_color: Backgroud color which is an ANSI code shift for colorful print.
643*c2e18aaaSAndroid Build Coastguard Worker
644*c2e18aaaSAndroid Build Coastguard Worker  Returns:
645*c2e18aaaSAndroid Build Coastguard Worker      Colorful string with ANSI escape code.
646*c2e18aaaSAndroid Build Coastguard Worker  """
647*c2e18aaaSAndroid Build Coastguard Worker  clr_pref = '\033[1;'
648*c2e18aaaSAndroid Build Coastguard Worker  clr_suff = '\033[0m'
649*c2e18aaaSAndroid Build Coastguard Worker  has_colors = _has_colors(_original_sys_stdout)
650*c2e18aaaSAndroid Build Coastguard Worker  if has_colors:
651*c2e18aaaSAndroid Build Coastguard Worker    background_color = ''
652*c2e18aaaSAndroid Build Coastguard Worker    if bp_color:
653*c2e18aaaSAndroid Build Coastguard Worker      # Foreground(Text) ranges from 30-37
654*c2e18aaaSAndroid Build Coastguard Worker      text_color = 30 + color
655*c2e18aaaSAndroid Build Coastguard Worker      # Background ranges from 40-47
656*c2e18aaaSAndroid Build Coastguard Worker      background_color = ';%d' % (40 + bp_color)
657*c2e18aaaSAndroid Build Coastguard Worker    else:
658*c2e18aaaSAndroid Build Coastguard Worker      text_color = 30 + color
659*c2e18aaaSAndroid Build Coastguard Worker    clr_str = '%s%d%sm%s%s' % (
660*c2e18aaaSAndroid Build Coastguard Worker        clr_pref,
661*c2e18aaaSAndroid Build Coastguard Worker        text_color,
662*c2e18aaaSAndroid Build Coastguard Worker        background_color,
663*c2e18aaaSAndroid Build Coastguard Worker        text,
664*c2e18aaaSAndroid Build Coastguard Worker        clr_suff,
665*c2e18aaaSAndroid Build Coastguard Worker    )
666*c2e18aaaSAndroid Build Coastguard Worker  else:
667*c2e18aaaSAndroid Build Coastguard Worker    clr_str = text
668*c2e18aaaSAndroid Build Coastguard Worker  return clr_str
669*c2e18aaaSAndroid Build Coastguard Worker
670*c2e18aaaSAndroid Build Coastguard Worker
671*c2e18aaaSAndroid Build Coastguard Workerdef mark_red(text):
672*c2e18aaaSAndroid Build Coastguard Worker  """Wrap colorized function and print in red."""
673*c2e18aaaSAndroid Build Coastguard Worker  return colorize(text, constants.RED)
674*c2e18aaaSAndroid Build Coastguard Worker
675*c2e18aaaSAndroid Build Coastguard Worker
676*c2e18aaaSAndroid Build Coastguard Workerdef mark_yellow(text):
677*c2e18aaaSAndroid Build Coastguard Worker  """Wrap colorized function and print in yellow."""
678*c2e18aaaSAndroid Build Coastguard Worker  return colorize(text, constants.YELLOW)
679*c2e18aaaSAndroid Build Coastguard Worker
680*c2e18aaaSAndroid Build Coastguard Worker
681*c2e18aaaSAndroid Build Coastguard Workerdef mark_green(text):
682*c2e18aaaSAndroid Build Coastguard Worker  """Wrap colorized function and print in green."""
683*c2e18aaaSAndroid Build Coastguard Worker  return colorize(text, constants.GREEN)
684*c2e18aaaSAndroid Build Coastguard Worker
685*c2e18aaaSAndroid Build Coastguard Worker
686*c2e18aaaSAndroid Build Coastguard Workerdef mark_magenta(text):
687*c2e18aaaSAndroid Build Coastguard Worker  """Wrap colorized function and print in magenta."""
688*c2e18aaaSAndroid Build Coastguard Worker  return colorize(text, constants.MAGENTA)
689*c2e18aaaSAndroid Build Coastguard Worker
690*c2e18aaaSAndroid Build Coastguard Worker
691*c2e18aaaSAndroid Build Coastguard Workerdef mark_cyan(text):
692*c2e18aaaSAndroid Build Coastguard Worker  """Wrap colorized function and print in cyan."""
693*c2e18aaaSAndroid Build Coastguard Worker  return colorize(text, constants.CYAN)
694*c2e18aaaSAndroid Build Coastguard Worker
695*c2e18aaaSAndroid Build Coastguard Worker
696*c2e18aaaSAndroid Build Coastguard Workerdef mark_blue(text):
697*c2e18aaaSAndroid Build Coastguard Worker  """Wrap colorized function and print in blue."""
698*c2e18aaaSAndroid Build Coastguard Worker  return colorize(text, constants.BLUE)
699*c2e18aaaSAndroid Build Coastguard Worker
700*c2e18aaaSAndroid Build Coastguard Worker
701*c2e18aaaSAndroid Build Coastguard Workerdef colorful_print(text, color=None, bp_color=None, auto_wrap=True):
702*c2e18aaaSAndroid Build Coastguard Worker  """Print out the text with color.
703*c2e18aaaSAndroid Build Coastguard Worker
704*c2e18aaaSAndroid Build Coastguard Worker  Args:
705*c2e18aaaSAndroid Build Coastguard Worker      text: A string to print.
706*c2e18aaaSAndroid Build Coastguard Worker      color: Forground(Text) color which is an ANSI code shift for colorful
707*c2e18aaaSAndroid Build Coastguard Worker        print. They are defined in constants_default.py.
708*c2e18aaaSAndroid Build Coastguard Worker      bp_color: Backgroud color which is an ANSI code shift for colorful print.
709*c2e18aaaSAndroid Build Coastguard Worker      auto_wrap: If True, Text wraps while print.
710*c2e18aaaSAndroid Build Coastguard Worker  """
711*c2e18aaaSAndroid Build Coastguard Worker  output = colorize(text, color, bp_color) if color else text
712*c2e18aaaSAndroid Build Coastguard Worker  if auto_wrap:
713*c2e18aaaSAndroid Build Coastguard Worker    print(output)
714*c2e18aaaSAndroid Build Coastguard Worker  else:
715*c2e18aaaSAndroid Build Coastguard Worker    print(output, end='')
716*c2e18aaaSAndroid Build Coastguard Worker
717*c2e18aaaSAndroid Build Coastguard Worker
718*c2e18aaaSAndroid Build Coastguard Workerdef _print_to_console(
719*c2e18aaaSAndroid Build Coastguard Worker    prefix: str, msg: Any, *fmt_args: list[Any], color: int = None
720*c2e18aaaSAndroid Build Coastguard Worker) -> None:
721*c2e18aaaSAndroid Build Coastguard Worker  """Print a message to the console.
722*c2e18aaaSAndroid Build Coastguard Worker
723*c2e18aaaSAndroid Build Coastguard Worker  Args:
724*c2e18aaaSAndroid Build Coastguard Worker    msg: The message to format.
725*c2e18aaaSAndroid Build Coastguard Worker    *fmt_args: Format arguments for the message.
726*c2e18aaaSAndroid Build Coastguard Worker  """
727*c2e18aaaSAndroid Build Coastguard Worker  if not fmt_args:
728*c2e18aaaSAndroid Build Coastguard Worker    evaluated_msg = str(msg)
729*c2e18aaaSAndroid Build Coastguard Worker  else:
730*c2e18aaaSAndroid Build Coastguard Worker    try:
731*c2e18aaaSAndroid Build Coastguard Worker      evaluated_msg = msg % fmt_args
732*c2e18aaaSAndroid Build Coastguard Worker    except (TypeError, ValueError):
733*c2e18aaaSAndroid Build Coastguard Worker      traceback.print_exc()
734*c2e18aaaSAndroid Build Coastguard Worker      return
735*c2e18aaaSAndroid Build Coastguard Worker  colorful_print(f'{prefix}{evaluated_msg}', color)
736*c2e18aaaSAndroid Build Coastguard Worker
737*c2e18aaaSAndroid Build Coastguard Worker
738*c2e18aaaSAndroid Build Coastguard Workerdef print_and_log_error(msg, *fmt_args):
739*c2e18aaaSAndroid Build Coastguard Worker  """Print error message to the console and log it.
740*c2e18aaaSAndroid Build Coastguard Worker
741*c2e18aaaSAndroid Build Coastguard Worker  Args:
742*c2e18aaaSAndroid Build Coastguard Worker    msg: The message to print.
743*c2e18aaaSAndroid Build Coastguard Worker    *fmt_args: Format arguments for the message.
744*c2e18aaaSAndroid Build Coastguard Worker  """
745*c2e18aaaSAndroid Build Coastguard Worker  logging.error(msg, *fmt_args)
746*c2e18aaaSAndroid Build Coastguard Worker  _print_to_console('Error: ', msg, *fmt_args, color=constants.RED)
747*c2e18aaaSAndroid Build Coastguard Worker
748*c2e18aaaSAndroid Build Coastguard Worker
749*c2e18aaaSAndroid Build Coastguard Workerdef print_and_log_warning(msg, *fmt_args):
750*c2e18aaaSAndroid Build Coastguard Worker  """Print warning message to the console and log it.
751*c2e18aaaSAndroid Build Coastguard Worker
752*c2e18aaaSAndroid Build Coastguard Worker  Args:
753*c2e18aaaSAndroid Build Coastguard Worker    msg: The message to print.
754*c2e18aaaSAndroid Build Coastguard Worker    *fmt_args: Format arguments for the message.
755*c2e18aaaSAndroid Build Coastguard Worker  """
756*c2e18aaaSAndroid Build Coastguard Worker  logging.warning(msg, *fmt_args)
757*c2e18aaaSAndroid Build Coastguard Worker  _print_to_console('Warning: ', msg, *fmt_args, color=constants.MAGENTA)
758*c2e18aaaSAndroid Build Coastguard Worker
759*c2e18aaaSAndroid Build Coastguard Worker
760*c2e18aaaSAndroid Build Coastguard Workerdef print_and_log_info(msg, *fmt_args):
761*c2e18aaaSAndroid Build Coastguard Worker  """Print info message to the console and log it.
762*c2e18aaaSAndroid Build Coastguard Worker
763*c2e18aaaSAndroid Build Coastguard Worker  Args:
764*c2e18aaaSAndroid Build Coastguard Worker    msg: The message to print.
765*c2e18aaaSAndroid Build Coastguard Worker    *fmt_args: Format arguments for the message.
766*c2e18aaaSAndroid Build Coastguard Worker  """
767*c2e18aaaSAndroid Build Coastguard Worker  logging.info(msg, *fmt_args)
768*c2e18aaaSAndroid Build Coastguard Worker  _print_to_console(mark_cyan('Info: '), msg, *fmt_args)
769*c2e18aaaSAndroid Build Coastguard Worker
770*c2e18aaaSAndroid Build Coastguard Worker
771*c2e18aaaSAndroid Build Coastguard Workerdef get_terminal_size():
772*c2e18aaaSAndroid Build Coastguard Worker  """Get terminal size and return a tuple.
773*c2e18aaaSAndroid Build Coastguard Worker
774*c2e18aaaSAndroid Build Coastguard Worker  Returns:
775*c2e18aaaSAndroid Build Coastguard Worker      2 integers: the size of X(columns) and Y(lines/rows).
776*c2e18aaaSAndroid Build Coastguard Worker  """
777*c2e18aaaSAndroid Build Coastguard Worker  # Determine the width of the terminal. We'll need to clear this many
778*c2e18aaaSAndroid Build Coastguard Worker  # characters when carriage returning. Set default value as 80.
779*c2e18aaaSAndroid Build Coastguard Worker  columns, rows = shutil.get_terminal_size(
780*c2e18aaaSAndroid Build Coastguard Worker      fallback=(_DEFAULT_TERMINAL_WIDTH, _DEFAULT_TERMINAL_HEIGHT)
781*c2e18aaaSAndroid Build Coastguard Worker  )
782*c2e18aaaSAndroid Build Coastguard Worker  return columns, rows
783*c2e18aaaSAndroid Build Coastguard Worker
784*c2e18aaaSAndroid Build Coastguard Worker
785*c2e18aaaSAndroid Build Coastguard Workerdef _get_hashed_file_name(main_file_name):
786*c2e18aaaSAndroid Build Coastguard Worker  """Convert the input string to a md5-hashed string.
787*c2e18aaaSAndroid Build Coastguard Worker
788*c2e18aaaSAndroid Build Coastguard Worker  If file_extension is
789*c2e18aaaSAndroid Build Coastguard Worker
790*c2e18aaaSAndroid Build Coastguard Worker     given, returns $(hashed_string).$(file_extension), otherwise
791*c2e18aaaSAndroid Build Coastguard Worker     $(hashed_string).cache.
792*c2e18aaaSAndroid Build Coastguard Worker
793*c2e18aaaSAndroid Build Coastguard Worker  Args:
794*c2e18aaaSAndroid Build Coastguard Worker      main_file_name: The input string need to be hashed.
795*c2e18aaaSAndroid Build Coastguard Worker
796*c2e18aaaSAndroid Build Coastguard Worker  Returns:
797*c2e18aaaSAndroid Build Coastguard Worker      A string as hashed file name with .cache file extension.
798*c2e18aaaSAndroid Build Coastguard Worker  """
799*c2e18aaaSAndroid Build Coastguard Worker  hashed_fn = hashlib.md5(str(main_file_name).encode())
800*c2e18aaaSAndroid Build Coastguard Worker  hashed_name = hashed_fn.hexdigest()
801*c2e18aaaSAndroid Build Coastguard Worker  return hashed_name + '.cache'
802*c2e18aaaSAndroid Build Coastguard Worker
803*c2e18aaaSAndroid Build Coastguard Worker
804*c2e18aaaSAndroid Build Coastguard Workerdef md5sum(filename):
805*c2e18aaaSAndroid Build Coastguard Worker  """Generate MD5 checksum of a file.
806*c2e18aaaSAndroid Build Coastguard Worker
807*c2e18aaaSAndroid Build Coastguard Worker  Args:
808*c2e18aaaSAndroid Build Coastguard Worker      name: A string of a filename.
809*c2e18aaaSAndroid Build Coastguard Worker
810*c2e18aaaSAndroid Build Coastguard Worker  Returns:
811*c2e18aaaSAndroid Build Coastguard Worker      A string of hashed MD5 checksum.
812*c2e18aaaSAndroid Build Coastguard Worker  """
813*c2e18aaaSAndroid Build Coastguard Worker  filename = Path(filename)
814*c2e18aaaSAndroid Build Coastguard Worker  if not filename.is_file():
815*c2e18aaaSAndroid Build Coastguard Worker    return ''
816*c2e18aaaSAndroid Build Coastguard Worker  with open(filename, 'rb') as target:
817*c2e18aaaSAndroid Build Coastguard Worker    content = target.read()
818*c2e18aaaSAndroid Build Coastguard Worker  if not isinstance(content, bytes):
819*c2e18aaaSAndroid Build Coastguard Worker    content = content.encode('utf-8')
820*c2e18aaaSAndroid Build Coastguard Worker  return hashlib.md5(content).hexdigest()
821*c2e18aaaSAndroid Build Coastguard Worker
822*c2e18aaaSAndroid Build Coastguard Worker
823*c2e18aaaSAndroid Build Coastguard Workerdef check_md5(check_file, missing_ok=False):
824*c2e18aaaSAndroid Build Coastguard Worker  """Method equivalent to 'md5sum --check /file/to/check'.
825*c2e18aaaSAndroid Build Coastguard Worker
826*c2e18aaaSAndroid Build Coastguard Worker  Args:
827*c2e18aaaSAndroid Build Coastguard Worker      check_file: A string of filename that stores filename and its md5
828*c2e18aaaSAndroid Build Coastguard Worker        checksum.
829*c2e18aaaSAndroid Build Coastguard Worker      missing_ok: A boolean that considers OK even when the check_file does not
830*c2e18aaaSAndroid Build Coastguard Worker        exist. Using missing_ok=True allows ignoring md5 check especially for
831*c2e18aaaSAndroid Build Coastguard Worker        initial run that the check_file has not yet generated. Using
832*c2e18aaaSAndroid Build Coastguard Worker        missing_ok=False ensures the consistency of files, and guarantees the
833*c2e18aaaSAndroid Build Coastguard Worker        process is successfully completed.
834*c2e18aaaSAndroid Build Coastguard Worker
835*c2e18aaaSAndroid Build Coastguard Worker  Returns:
836*c2e18aaaSAndroid Build Coastguard Worker      When missing_ok is True (soft check):
837*c2e18aaaSAndroid Build Coastguard Worker        - True if the checksum is consistent with the actual MD5, even the
838*c2e18aaaSAndroid Build Coastguard Worker          check_file is missing or not a valid JSON.
839*c2e18aaaSAndroid Build Coastguard Worker        - False when the checksum is inconsistent with the actual MD5.
840*c2e18aaaSAndroid Build Coastguard Worker      When missing_ok is False (ensure the process completed properly):
841*c2e18aaaSAndroid Build Coastguard Worker        - True if the checksum is consistent with the actual MD5.
842*c2e18aaaSAndroid Build Coastguard Worker        - False otherwise.
843*c2e18aaaSAndroid Build Coastguard Worker  """
844*c2e18aaaSAndroid Build Coastguard Worker  if not Path(check_file).is_file():
845*c2e18aaaSAndroid Build Coastguard Worker    if not missing_ok:
846*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Unable to verify: %s not found.', check_file)
847*c2e18aaaSAndroid Build Coastguard Worker    return missing_ok
848*c2e18aaaSAndroid Build Coastguard Worker  content = load_json_safely(check_file)
849*c2e18aaaSAndroid Build Coastguard Worker  if content:
850*c2e18aaaSAndroid Build Coastguard Worker    for filename, md5 in content.items():
851*c2e18aaaSAndroid Build Coastguard Worker      if md5sum(filename) != md5:
852*c2e18aaaSAndroid Build Coastguard Worker        logging.debug('%s has altered.', filename)
853*c2e18aaaSAndroid Build Coastguard Worker        return False
854*c2e18aaaSAndroid Build Coastguard Worker    return True
855*c2e18aaaSAndroid Build Coastguard Worker  return False
856*c2e18aaaSAndroid Build Coastguard Worker
857*c2e18aaaSAndroid Build Coastguard Worker
858*c2e18aaaSAndroid Build Coastguard Workerdef save_md5(filenames, save_file):
859*c2e18aaaSAndroid Build Coastguard Worker  """Method equivalent to 'md5sum file1 file2 > /file/to/check'
860*c2e18aaaSAndroid Build Coastguard Worker
861*c2e18aaaSAndroid Build Coastguard Worker  Args:
862*c2e18aaaSAndroid Build Coastguard Worker      filenames: A list of filenames.
863*c2e18aaaSAndroid Build Coastguard Worker      save_file: Filename for storing files and their md5 checksums.
864*c2e18aaaSAndroid Build Coastguard Worker  """
865*c2e18aaaSAndroid Build Coastguard Worker  data = {}
866*c2e18aaaSAndroid Build Coastguard Worker  for f in filenames:
867*c2e18aaaSAndroid Build Coastguard Worker    name = Path(f)
868*c2e18aaaSAndroid Build Coastguard Worker    if not name.is_file():
869*c2e18aaaSAndroid Build Coastguard Worker      print_and_log_warning(' ignore %s: not a file.', name)
870*c2e18aaaSAndroid Build Coastguard Worker    data.update({str(name): md5sum(name)})
871*c2e18aaaSAndroid Build Coastguard Worker  with open(save_file, 'w+', encoding='utf-8') as _file:
872*c2e18aaaSAndroid Build Coastguard Worker    json.dump(data, _file)
873*c2e18aaaSAndroid Build Coastguard Worker
874*c2e18aaaSAndroid Build Coastguard Worker
875*c2e18aaaSAndroid Build Coastguard Workerdef get_cache_root():
876*c2e18aaaSAndroid Build Coastguard Worker  """Get the root path dir for cache.
877*c2e18aaaSAndroid Build Coastguard Worker
878*c2e18aaaSAndroid Build Coastguard Worker  Use branch and target information as cache_root.
879*c2e18aaaSAndroid Build Coastguard Worker  The path will look like:
880*c2e18aaaSAndroid Build Coastguard Worker     $(ANDROID_PRODUCT_OUT)/atest_cache/$CACHE_VERSION
881*c2e18aaaSAndroid Build Coastguard Worker
882*c2e18aaaSAndroid Build Coastguard Worker  Returns:
883*c2e18aaaSAndroid Build Coastguard Worker      A string of the path of the root dir of cache.
884*c2e18aaaSAndroid Build Coastguard Worker  """
885*c2e18aaaSAndroid Build Coastguard Worker  # Note that the cache directory is stored in the build output directory. We
886*c2e18aaaSAndroid Build Coastguard Worker  # do this because this directory is periodically cleaned and don't have to
887*c2e18aaaSAndroid Build Coastguard Worker  # worry about the files growing without bound. The files are also much
888*c2e18aaaSAndroid Build Coastguard Worker  # smaller than typical build output and less of an issue. Use build out to
889*c2e18aaaSAndroid Build Coastguard Worker  # save caches which is next to atest_bazel_workspace which is easy for user
890*c2e18aaaSAndroid Build Coastguard Worker  # to manually clean up if need. Use product out folder's base name as part
891*c2e18aaaSAndroid Build Coastguard Worker  # of directory because of there may be different module-info in the same
892*c2e18aaaSAndroid Build Coastguard Worker  # branch but different lunch target.
893*c2e18aaaSAndroid Build Coastguard Worker  return os.path.join(
894*c2e18aaaSAndroid Build Coastguard Worker      get_build_out_dir(),
895*c2e18aaaSAndroid Build Coastguard Worker      'atest_cache',
896*c2e18aaaSAndroid Build Coastguard Worker      f'ver_{CACHE_VERSION}',
897*c2e18aaaSAndroid Build Coastguard Worker      os.path.basename(
898*c2e18aaaSAndroid Build Coastguard Worker          os.environ.get(
899*c2e18aaaSAndroid Build Coastguard Worker              constants.ANDROID_PRODUCT_OUT, constants.ANDROID_PRODUCT_OUT
900*c2e18aaaSAndroid Build Coastguard Worker          )
901*c2e18aaaSAndroid Build Coastguard Worker      ),
902*c2e18aaaSAndroid Build Coastguard Worker  )
903*c2e18aaaSAndroid Build Coastguard Worker
904*c2e18aaaSAndroid Build Coastguard Worker
905*c2e18aaaSAndroid Build Coastguard Workerdef get_test_info_cache_path(test_reference, cache_root=None):
906*c2e18aaaSAndroid Build Coastguard Worker  """Get the cache path of the desired test_infos.
907*c2e18aaaSAndroid Build Coastguard Worker
908*c2e18aaaSAndroid Build Coastguard Worker  Args:
909*c2e18aaaSAndroid Build Coastguard Worker      test_reference: A string of the test.
910*c2e18aaaSAndroid Build Coastguard Worker      cache_root: Folder path where stores caches.
911*c2e18aaaSAndroid Build Coastguard Worker
912*c2e18aaaSAndroid Build Coastguard Worker  Returns:
913*c2e18aaaSAndroid Build Coastguard Worker      A string of the path of test_info cache.
914*c2e18aaaSAndroid Build Coastguard Worker  """
915*c2e18aaaSAndroid Build Coastguard Worker  if not cache_root:
916*c2e18aaaSAndroid Build Coastguard Worker    cache_root = get_cache_root()
917*c2e18aaaSAndroid Build Coastguard Worker  return os.path.join(cache_root, _get_hashed_file_name(test_reference))
918*c2e18aaaSAndroid Build Coastguard Worker
919*c2e18aaaSAndroid Build Coastguard Worker
920*c2e18aaaSAndroid Build Coastguard Workerdef update_test_info_cache(test_reference, test_infos, cache_root=None):
921*c2e18aaaSAndroid Build Coastguard Worker  """Update cache content which stores a set of test_info objects through
922*c2e18aaaSAndroid Build Coastguard Worker
923*c2e18aaaSAndroid Build Coastguard Worker     pickle module, each test_reference will be saved as a cache file.
924*c2e18aaaSAndroid Build Coastguard Worker
925*c2e18aaaSAndroid Build Coastguard Worker  Args:
926*c2e18aaaSAndroid Build Coastguard Worker      test_reference: A string referencing a test.
927*c2e18aaaSAndroid Build Coastguard Worker      test_infos: A set of TestInfos.
928*c2e18aaaSAndroid Build Coastguard Worker      cache_root: Folder path for saving caches.
929*c2e18aaaSAndroid Build Coastguard Worker  """
930*c2e18aaaSAndroid Build Coastguard Worker  if not cache_root:
931*c2e18aaaSAndroid Build Coastguard Worker    cache_root = get_cache_root()
932*c2e18aaaSAndroid Build Coastguard Worker  if not os.path.isdir(cache_root):
933*c2e18aaaSAndroid Build Coastguard Worker    os.makedirs(cache_root)
934*c2e18aaaSAndroid Build Coastguard Worker  cache_path = get_test_info_cache_path(test_reference, cache_root)
935*c2e18aaaSAndroid Build Coastguard Worker  # Save test_info to files.
936*c2e18aaaSAndroid Build Coastguard Worker  try:
937*c2e18aaaSAndroid Build Coastguard Worker    with open(cache_path, 'wb') as test_info_cache_file:
938*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Saving cache for %s as %s.', test_reference, cache_path)
939*c2e18aaaSAndroid Build Coastguard Worker      pickle.dump(test_infos, test_info_cache_file, protocol=2)
940*c2e18aaaSAndroid Build Coastguard Worker  except (pickle.PicklingError, TypeError, IOError) as err:
941*c2e18aaaSAndroid Build Coastguard Worker    # Won't break anything, just log this error, and collect the exception
942*c2e18aaaSAndroid Build Coastguard Worker    # by metrics.
943*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('Exception raised: %s', err)
944*c2e18aaaSAndroid Build Coastguard Worker    metrics_utils.handle_exc_and_send_exit_event(constants.ACCESS_CACHE_FAILURE)
945*c2e18aaaSAndroid Build Coastguard Worker
946*c2e18aaaSAndroid Build Coastguard Worker
947*c2e18aaaSAndroid Build Coastguard Workerdef load_test_info_cache(test_reference, cache_root=None):
948*c2e18aaaSAndroid Build Coastguard Worker  """Load cache by test_reference to a set of test_infos object.
949*c2e18aaaSAndroid Build Coastguard Worker
950*c2e18aaaSAndroid Build Coastguard Worker  Args:
951*c2e18aaaSAndroid Build Coastguard Worker      test_reference: A string referencing a test.
952*c2e18aaaSAndroid Build Coastguard Worker      cache_root: Folder path for finding caches.
953*c2e18aaaSAndroid Build Coastguard Worker
954*c2e18aaaSAndroid Build Coastguard Worker  Returns:
955*c2e18aaaSAndroid Build Coastguard Worker      A list of TestInfo namedtuple if cache found, else None.
956*c2e18aaaSAndroid Build Coastguard Worker  """
957*c2e18aaaSAndroid Build Coastguard Worker  if not cache_root:
958*c2e18aaaSAndroid Build Coastguard Worker    cache_root = get_cache_root()
959*c2e18aaaSAndroid Build Coastguard Worker
960*c2e18aaaSAndroid Build Coastguard Worker  cache_file = get_test_info_cache_path(test_reference, cache_root)
961*c2e18aaaSAndroid Build Coastguard Worker  if os.path.isfile(cache_file):
962*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('Loading cache %s from %s.', test_reference, cache_file)
963*c2e18aaaSAndroid Build Coastguard Worker    try:
964*c2e18aaaSAndroid Build Coastguard Worker      with open(cache_file, 'rb') as config_dictionary_file:
965*c2e18aaaSAndroid Build Coastguard Worker        return pickle.load(config_dictionary_file, encoding='utf-8')
966*c2e18aaaSAndroid Build Coastguard Worker    except (
967*c2e18aaaSAndroid Build Coastguard Worker        pickle.UnpicklingError,
968*c2e18aaaSAndroid Build Coastguard Worker        ValueError,
969*c2e18aaaSAndroid Build Coastguard Worker        TypeError,
970*c2e18aaaSAndroid Build Coastguard Worker        EOFError,
971*c2e18aaaSAndroid Build Coastguard Worker        IOError,
972*c2e18aaaSAndroid Build Coastguard Worker        ImportError,
973*c2e18aaaSAndroid Build Coastguard Worker    ) as err:
974*c2e18aaaSAndroid Build Coastguard Worker      # Won't break anything, just remove the old cache, log this error,
975*c2e18aaaSAndroid Build Coastguard Worker      # and collect the exception by metrics.
976*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Exception raised: %s', err)
977*c2e18aaaSAndroid Build Coastguard Worker      os.remove(cache_file)
978*c2e18aaaSAndroid Build Coastguard Worker      metrics_utils.handle_exc_and_send_exit_event(
979*c2e18aaaSAndroid Build Coastguard Worker          constants.ACCESS_CACHE_FAILURE
980*c2e18aaaSAndroid Build Coastguard Worker      )
981*c2e18aaaSAndroid Build Coastguard Worker  return None
982*c2e18aaaSAndroid Build Coastguard Worker
983*c2e18aaaSAndroid Build Coastguard Worker
984*c2e18aaaSAndroid Build Coastguard Workerdef clean_test_info_caches(tests, cache_root=None):
985*c2e18aaaSAndroid Build Coastguard Worker  """Clean caches of input tests.
986*c2e18aaaSAndroid Build Coastguard Worker
987*c2e18aaaSAndroid Build Coastguard Worker  Args:
988*c2e18aaaSAndroid Build Coastguard Worker      tests: A list of test references.
989*c2e18aaaSAndroid Build Coastguard Worker      cache_root: Folder path for finding caches.
990*c2e18aaaSAndroid Build Coastguard Worker  """
991*c2e18aaaSAndroid Build Coastguard Worker  if not cache_root:
992*c2e18aaaSAndroid Build Coastguard Worker    cache_root = get_cache_root()
993*c2e18aaaSAndroid Build Coastguard Worker  for test in tests:
994*c2e18aaaSAndroid Build Coastguard Worker    cache_file = get_test_info_cache_path(test, cache_root)
995*c2e18aaaSAndroid Build Coastguard Worker    if os.path.isfile(cache_file):
996*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Removing cache: %s', cache_file)
997*c2e18aaaSAndroid Build Coastguard Worker      try:
998*c2e18aaaSAndroid Build Coastguard Worker        os.remove(cache_file)
999*c2e18aaaSAndroid Build Coastguard Worker      except IOError as err:
1000*c2e18aaaSAndroid Build Coastguard Worker        logging.debug('Exception raised: %s', err)
1001*c2e18aaaSAndroid Build Coastguard Worker        metrics_utils.handle_exc_and_send_exit_event(
1002*c2e18aaaSAndroid Build Coastguard Worker            constants.ACCESS_CACHE_FAILURE
1003*c2e18aaaSAndroid Build Coastguard Worker        )
1004*c2e18aaaSAndroid Build Coastguard Worker
1005*c2e18aaaSAndroid Build Coastguard Worker
1006*c2e18aaaSAndroid Build Coastguard Workerdef get_modified_files(root_dir):
1007*c2e18aaaSAndroid Build Coastguard Worker  """Get the git modified files.
1008*c2e18aaaSAndroid Build Coastguard Worker
1009*c2e18aaaSAndroid Build Coastguard Worker  The git path here is git top level of the root_dir. It's inevitable to utilise
1010*c2e18aaaSAndroid Build Coastguard Worker  different commands to fulfill 2 scenario:
1011*c2e18aaaSAndroid Build Coastguard Worker
1012*c2e18aaaSAndroid Build Coastguard Worker      1. locate unstaged/staged files
1013*c2e18aaaSAndroid Build Coastguard Worker      2. locate committed files but not yet merged.
1014*c2e18aaaSAndroid Build Coastguard Worker  the 'git_status_cmd' fulfils the former while the 'find_modified_files'
1015*c2e18aaaSAndroid Build Coastguard Worker  fulfils the latter.
1016*c2e18aaaSAndroid Build Coastguard Worker
1017*c2e18aaaSAndroid Build Coastguard Worker  Args:
1018*c2e18aaaSAndroid Build Coastguard Worker      root_dir: the root where it starts finding.
1019*c2e18aaaSAndroid Build Coastguard Worker
1020*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1021*c2e18aaaSAndroid Build Coastguard Worker      A set of modified files altered since last commit.
1022*c2e18aaaSAndroid Build Coastguard Worker  """
1023*c2e18aaaSAndroid Build Coastguard Worker  modified_files = set()
1024*c2e18aaaSAndroid Build Coastguard Worker  try:
1025*c2e18aaaSAndroid Build Coastguard Worker    # TODO: (@jimtang) abandon using git command within Atest.
1026*c2e18aaaSAndroid Build Coastguard Worker    find_git_cmd = f'cd {root_dir}; git rev-parse --show-toplevel 2>/dev/null'
1027*c2e18aaaSAndroid Build Coastguard Worker    git_paths = (
1028*c2e18aaaSAndroid Build Coastguard Worker        subprocess.check_output(find_git_cmd, shell=True).decode().splitlines()
1029*c2e18aaaSAndroid Build Coastguard Worker    )
1030*c2e18aaaSAndroid Build Coastguard Worker    for git_path in git_paths:
1031*c2e18aaaSAndroid Build Coastguard Worker      # Find modified files from git working tree status.
1032*c2e18aaaSAndroid Build Coastguard Worker      git_status_cmd = (
1033*c2e18aaaSAndroid Build Coastguard Worker          "repo forall {} -c git status --short | awk '{{print $NF}}'"
1034*c2e18aaaSAndroid Build Coastguard Worker      ).format(git_path)
1035*c2e18aaaSAndroid Build Coastguard Worker      modified_wo_commit = (
1036*c2e18aaaSAndroid Build Coastguard Worker          subprocess.check_output(git_status_cmd, shell=True)
1037*c2e18aaaSAndroid Build Coastguard Worker          .decode()
1038*c2e18aaaSAndroid Build Coastguard Worker          .rstrip()
1039*c2e18aaaSAndroid Build Coastguard Worker          .splitlines()
1040*c2e18aaaSAndroid Build Coastguard Worker      )
1041*c2e18aaaSAndroid Build Coastguard Worker      for change in modified_wo_commit:
1042*c2e18aaaSAndroid Build Coastguard Worker        modified_files.add(os.path.normpath('{}/{}'.format(git_path, change)))
1043*c2e18aaaSAndroid Build Coastguard Worker      # Find modified files that are committed but not yet merged.
1044*c2e18aaaSAndroid Build Coastguard Worker      find_modified_files = _FIND_MODIFIED_FILES_CMDS.format(git_path)
1045*c2e18aaaSAndroid Build Coastguard Worker      commit_modified_files = (
1046*c2e18aaaSAndroid Build Coastguard Worker          subprocess.check_output(find_modified_files, shell=True)
1047*c2e18aaaSAndroid Build Coastguard Worker          .decode()
1048*c2e18aaaSAndroid Build Coastguard Worker          .splitlines()
1049*c2e18aaaSAndroid Build Coastguard Worker      )
1050*c2e18aaaSAndroid Build Coastguard Worker      for line in commit_modified_files:
1051*c2e18aaaSAndroid Build Coastguard Worker        modified_files.add(os.path.normpath('{}/{}'.format(git_path, line)))
1052*c2e18aaaSAndroid Build Coastguard Worker  except (OSError, subprocess.CalledProcessError) as err:
1053*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('Exception raised: %s', err)
1054*c2e18aaaSAndroid Build Coastguard Worker  return modified_files
1055*c2e18aaaSAndroid Build Coastguard Worker
1056*c2e18aaaSAndroid Build Coastguard Worker
1057*c2e18aaaSAndroid Build Coastguard Workerdef delimiter(char, length=_DEFAULT_TERMINAL_WIDTH, prenl=0, postnl=0):
1058*c2e18aaaSAndroid Build Coastguard Worker  """A handy delimiter printer.
1059*c2e18aaaSAndroid Build Coastguard Worker
1060*c2e18aaaSAndroid Build Coastguard Worker  Args:
1061*c2e18aaaSAndroid Build Coastguard Worker      char: A string used for delimiter.
1062*c2e18aaaSAndroid Build Coastguard Worker      length: An integer for the replication.
1063*c2e18aaaSAndroid Build Coastguard Worker      prenl: An integer that insert '\n' before delimiter.
1064*c2e18aaaSAndroid Build Coastguard Worker      postnl: An integer that insert '\n' after delimiter.
1065*c2e18aaaSAndroid Build Coastguard Worker
1066*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1067*c2e18aaaSAndroid Build Coastguard Worker      A string of delimiter.
1068*c2e18aaaSAndroid Build Coastguard Worker  """
1069*c2e18aaaSAndroid Build Coastguard Worker  return prenl * '\n' + char * length + postnl * '\n'
1070*c2e18aaaSAndroid Build Coastguard Worker
1071*c2e18aaaSAndroid Build Coastguard Worker
1072*c2e18aaaSAndroid Build Coastguard Workerdef find_files(path, file_name=constants.TEST_MAPPING, followlinks=False):
1073*c2e18aaaSAndroid Build Coastguard Worker  """Find all files with given name under the given path.
1074*c2e18aaaSAndroid Build Coastguard Worker
1075*c2e18aaaSAndroid Build Coastguard Worker  Args:
1076*c2e18aaaSAndroid Build Coastguard Worker      path: A string of path in source.
1077*c2e18aaaSAndroid Build Coastguard Worker      file_name: The file name pattern for finding matched files.
1078*c2e18aaaSAndroid Build Coastguard Worker      followlinks: A boolean to indicate whether to follow symbolic links.
1079*c2e18aaaSAndroid Build Coastguard Worker
1080*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1081*c2e18aaaSAndroid Build Coastguard Worker      A list of paths of the files with the matching name under the given
1082*c2e18aaaSAndroid Build Coastguard Worker      path.
1083*c2e18aaaSAndroid Build Coastguard Worker  """
1084*c2e18aaaSAndroid Build Coastguard Worker  match_files = []
1085*c2e18aaaSAndroid Build Coastguard Worker  for root, _, filenames in os.walk(path, followlinks=followlinks):
1086*c2e18aaaSAndroid Build Coastguard Worker    try:
1087*c2e18aaaSAndroid Build Coastguard Worker      for filename in fnmatch.filter(filenames, file_name):
1088*c2e18aaaSAndroid Build Coastguard Worker        match_files.append(os.path.join(root, filename))
1089*c2e18aaaSAndroid Build Coastguard Worker    except re.error as e:
1090*c2e18aaaSAndroid Build Coastguard Worker      msg = 'Unable to locate %s among %s' % (file_name, filenames)
1091*c2e18aaaSAndroid Build Coastguard Worker      logging.debug(msg)
1092*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Exception: %s', e)
1093*c2e18aaaSAndroid Build Coastguard Worker      metrics.AtestExitEvent(
1094*c2e18aaaSAndroid Build Coastguard Worker          duration=metrics_utils.convert_duration(0),
1095*c2e18aaaSAndroid Build Coastguard Worker          exit_code=ExitCode.COLLECT_ONLY_FILE_NOT_FOUND,
1096*c2e18aaaSAndroid Build Coastguard Worker          stacktrace=msg,
1097*c2e18aaaSAndroid Build Coastguard Worker          logs=str(e),
1098*c2e18aaaSAndroid Build Coastguard Worker      )
1099*c2e18aaaSAndroid Build Coastguard Worker  return match_files
1100*c2e18aaaSAndroid Build Coastguard Worker
1101*c2e18aaaSAndroid Build Coastguard Worker
1102*c2e18aaaSAndroid Build Coastguard Workerdef extract_zip_text(zip_path):
1103*c2e18aaaSAndroid Build Coastguard Worker  """Extract the text files content for input zip file.
1104*c2e18aaaSAndroid Build Coastguard Worker
1105*c2e18aaaSAndroid Build Coastguard Worker  Args:
1106*c2e18aaaSAndroid Build Coastguard Worker      zip_path: The file path of zip.
1107*c2e18aaaSAndroid Build Coastguard Worker
1108*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1109*c2e18aaaSAndroid Build Coastguard Worker      The string in input zip file.
1110*c2e18aaaSAndroid Build Coastguard Worker  """
1111*c2e18aaaSAndroid Build Coastguard Worker  content = ''
1112*c2e18aaaSAndroid Build Coastguard Worker  try:
1113*c2e18aaaSAndroid Build Coastguard Worker    with zipfile.ZipFile(zip_path) as zip_file:
1114*c2e18aaaSAndroid Build Coastguard Worker      for filename in zip_file.namelist():
1115*c2e18aaaSAndroid Build Coastguard Worker        if os.path.isdir(filename):
1116*c2e18aaaSAndroid Build Coastguard Worker          continue
1117*c2e18aaaSAndroid Build Coastguard Worker        # Force change line if multiple text files in zip
1118*c2e18aaaSAndroid Build Coastguard Worker        content = content + '\n'
1119*c2e18aaaSAndroid Build Coastguard Worker        # read the file
1120*c2e18aaaSAndroid Build Coastguard Worker        with zip_file.open(filename) as extract_file:
1121*c2e18aaaSAndroid Build Coastguard Worker          for line in extract_file:
1122*c2e18aaaSAndroid Build Coastguard Worker            if matched_tf_error_log(line.decode()):
1123*c2e18aaaSAndroid Build Coastguard Worker              content = content + line.decode()
1124*c2e18aaaSAndroid Build Coastguard Worker  except zipfile.BadZipfile as err:
1125*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('Exception raised: %s', err)
1126*c2e18aaaSAndroid Build Coastguard Worker  return content
1127*c2e18aaaSAndroid Build Coastguard Worker
1128*c2e18aaaSAndroid Build Coastguard Worker
1129*c2e18aaaSAndroid Build Coastguard Workerdef matched_tf_error_log(content):
1130*c2e18aaaSAndroid Build Coastguard Worker  """Check if the input content matched tradefed log pattern.
1131*c2e18aaaSAndroid Build Coastguard Worker
1132*c2e18aaaSAndroid Build Coastguard Worker  The format will look like this. 05-25 17:37:04 W/XXXXXX 05-25 17:37:04
1133*c2e18aaaSAndroid Build Coastguard Worker  E/XXXXXX
1134*c2e18aaaSAndroid Build Coastguard Worker
1135*c2e18aaaSAndroid Build Coastguard Worker  Args:
1136*c2e18aaaSAndroid Build Coastguard Worker      content: Log string.
1137*c2e18aaaSAndroid Build Coastguard Worker
1138*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1139*c2e18aaaSAndroid Build Coastguard Worker      True if the content matches the regular expression for tradefed error or
1140*c2e18aaaSAndroid Build Coastguard Worker      warning log.
1141*c2e18aaaSAndroid Build Coastguard Worker  """
1142*c2e18aaaSAndroid Build Coastguard Worker  reg = (
1143*c2e18aaaSAndroid Build Coastguard Worker      '^((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[0-1])) '
1144*c2e18aaaSAndroid Build Coastguard Worker      '(([0-1][0-9])|([2][0-3])):([0-5][0-9]):([0-5][0-9]) (E|W/)'
1145*c2e18aaaSAndroid Build Coastguard Worker  )
1146*c2e18aaaSAndroid Build Coastguard Worker  if re.search(reg, content):
1147*c2e18aaaSAndroid Build Coastguard Worker    return True
1148*c2e18aaaSAndroid Build Coastguard Worker  return False
1149*c2e18aaaSAndroid Build Coastguard Worker
1150*c2e18aaaSAndroid Build Coastguard Worker
1151*c2e18aaaSAndroid Build Coastguard Workerdef read_test_record(path):
1152*c2e18aaaSAndroid Build Coastguard Worker  """A Helper to read test record proto.
1153*c2e18aaaSAndroid Build Coastguard Worker
1154*c2e18aaaSAndroid Build Coastguard Worker  Args:
1155*c2e18aaaSAndroid Build Coastguard Worker      path: The proto file path.
1156*c2e18aaaSAndroid Build Coastguard Worker
1157*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1158*c2e18aaaSAndroid Build Coastguard Worker      The test_record proto instance.
1159*c2e18aaaSAndroid Build Coastguard Worker  """
1160*c2e18aaaSAndroid Build Coastguard Worker  with open(path, 'rb') as proto_file:
1161*c2e18aaaSAndroid Build Coastguard Worker    msg = test_record_pb2.TestRecord()
1162*c2e18aaaSAndroid Build Coastguard Worker    msg.ParseFromString(proto_file.read())
1163*c2e18aaaSAndroid Build Coastguard Worker  return msg
1164*c2e18aaaSAndroid Build Coastguard Worker
1165*c2e18aaaSAndroid Build Coastguard Worker
1166*c2e18aaaSAndroid Build Coastguard Workerdef has_python_module(module_name):
1167*c2e18aaaSAndroid Build Coastguard Worker  """Detect if the module can be loaded without importing it in real.
1168*c2e18aaaSAndroid Build Coastguard Worker
1169*c2e18aaaSAndroid Build Coastguard Worker  Args:
1170*c2e18aaaSAndroid Build Coastguard Worker      cmd: A string of the tested module name.
1171*c2e18aaaSAndroid Build Coastguard Worker
1172*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1173*c2e18aaaSAndroid Build Coastguard Worker      True if found, False otherwise.
1174*c2e18aaaSAndroid Build Coastguard Worker  """
1175*c2e18aaaSAndroid Build Coastguard Worker  return bool(importlib.util.find_spec(module_name))
1176*c2e18aaaSAndroid Build Coastguard Worker
1177*c2e18aaaSAndroid Build Coastguard Worker
1178*c2e18aaaSAndroid Build Coastguard Workerdef load_json_safely(jsonfile):
1179*c2e18aaaSAndroid Build Coastguard Worker  """Load the given json file as an object.
1180*c2e18aaaSAndroid Build Coastguard Worker
1181*c2e18aaaSAndroid Build Coastguard Worker  Args:
1182*c2e18aaaSAndroid Build Coastguard Worker      jsonfile: The json file path.
1183*c2e18aaaSAndroid Build Coastguard Worker
1184*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1185*c2e18aaaSAndroid Build Coastguard Worker      The content of the give json file. Null dict when:
1186*c2e18aaaSAndroid Build Coastguard Worker      1. the given path doesn't exist.
1187*c2e18aaaSAndroid Build Coastguard Worker      2. the given path is not a json or invalid format.
1188*c2e18aaaSAndroid Build Coastguard Worker  """
1189*c2e18aaaSAndroid Build Coastguard Worker  if isinstance(jsonfile, bytes):
1190*c2e18aaaSAndroid Build Coastguard Worker    jsonfile = jsonfile.decode('utf-8')
1191*c2e18aaaSAndroid Build Coastguard Worker  if Path(jsonfile).is_file():
1192*c2e18aaaSAndroid Build Coastguard Worker    try:
1193*c2e18aaaSAndroid Build Coastguard Worker      with open(jsonfile, 'r', encoding='utf-8') as cache:
1194*c2e18aaaSAndroid Build Coastguard Worker        return json.load(cache)
1195*c2e18aaaSAndroid Build Coastguard Worker    except json.JSONDecodeError:
1196*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Exception happened while loading %s.', jsonfile)
1197*c2e18aaaSAndroid Build Coastguard Worker  else:
1198*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('%s: File not found.', jsonfile)
1199*c2e18aaaSAndroid Build Coastguard Worker  return {}
1200*c2e18aaaSAndroid Build Coastguard Worker
1201*c2e18aaaSAndroid Build Coastguard Worker
1202*c2e18aaaSAndroid Build Coastguard Workerdef get_atest_version():
1203*c2e18aaaSAndroid Build Coastguard Worker  """Get atest version.
1204*c2e18aaaSAndroid Build Coastguard Worker
1205*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1206*c2e18aaaSAndroid Build Coastguard Worker      Version string from the VERSION file, e.g. prebuilt
1207*c2e18aaaSAndroid Build Coastguard Worker          2022-11-24_9314547  (<release_date>_<build_id>)
1208*c2e18aaaSAndroid Build Coastguard Worker
1209*c2e18aaaSAndroid Build Coastguard Worker      If VERSION does not exist (src or local built):
1210*c2e18aaaSAndroid Build Coastguard Worker          2022-11-24_5d448c50 (<commit_date>_<commit_id>)
1211*c2e18aaaSAndroid Build Coastguard Worker
1212*c2e18aaaSAndroid Build Coastguard Worker      If the git command fails for unexpected reason:
1213*c2e18aaaSAndroid Build Coastguard Worker          2022-11-24_unknown  (<today_date>_unknown)
1214*c2e18aaaSAndroid Build Coastguard Worker  """
1215*c2e18aaaSAndroid Build Coastguard Worker  try:
1216*c2e18aaaSAndroid Build Coastguard Worker    with importlib.resources.as_file(
1217*c2e18aaaSAndroid Build Coastguard Worker        importlib.resources.files('atest').joinpath('VERSION')
1218*c2e18aaaSAndroid Build Coastguard Worker    ) as version_file_path:
1219*c2e18aaaSAndroid Build Coastguard Worker      return version_file_path.read_text(encoding='utf-8')
1220*c2e18aaaSAndroid Build Coastguard Worker  except (ModuleNotFoundError, FileNotFoundError):
1221*c2e18aaaSAndroid Build Coastguard Worker    logging.debug(
1222*c2e18aaaSAndroid Build Coastguard Worker        'Failed to load package resource atest/VERSION, possibly due to running'
1223*c2e18aaaSAndroid Build Coastguard Worker        ' from atest-dev, atest-src, a prebuilt without embedded launcher, or a'
1224*c2e18aaaSAndroid Build Coastguard Worker        ' prebuilt not created by the asuite release tool. Falling back to'
1225*c2e18aaaSAndroid Build Coastguard Worker        ' legacy source search.'
1226*c2e18aaaSAndroid Build Coastguard Worker    )
1227*c2e18aaaSAndroid Build Coastguard Worker    version_file = Path(__file__).resolve().parent.joinpath('VERSION')
1228*c2e18aaaSAndroid Build Coastguard Worker    if Path(version_file).is_file():
1229*c2e18aaaSAndroid Build Coastguard Worker      return open(version_file, encoding='utf-8').read()
1230*c2e18aaaSAndroid Build Coastguard Worker
1231*c2e18aaaSAndroid Build Coastguard Worker  # Try fetching commit date (%ci) and commit hash (%h).
1232*c2e18aaaSAndroid Build Coastguard Worker  git_cmd = 'git log -1 --pretty=format:"%ci;%h"'
1233*c2e18aaaSAndroid Build Coastguard Worker  try:
1234*c2e18aaaSAndroid Build Coastguard Worker    # commit date/hash are only available when running from the source
1235*c2e18aaaSAndroid Build Coastguard Worker    # and the local built.
1236*c2e18aaaSAndroid Build Coastguard Worker    result = subprocess.run(
1237*c2e18aaaSAndroid Build Coastguard Worker        git_cmd,
1238*c2e18aaaSAndroid Build Coastguard Worker        shell=True,
1239*c2e18aaaSAndroid Build Coastguard Worker        check=False,
1240*c2e18aaaSAndroid Build Coastguard Worker        capture_output=True,
1241*c2e18aaaSAndroid Build Coastguard Worker        cwd=Path(os.getenv(constants.ANDROID_BUILD_TOP), '').joinpath(
1242*c2e18aaaSAndroid Build Coastguard Worker            'tools/asuite/atest'
1243*c2e18aaaSAndroid Build Coastguard Worker        ),
1244*c2e18aaaSAndroid Build Coastguard Worker    )
1245*c2e18aaaSAndroid Build Coastguard Worker    if result.stderr:
1246*c2e18aaaSAndroid Build Coastguard Worker      raise subprocess.CalledProcessError(returncode=0, cmd=git_cmd)
1247*c2e18aaaSAndroid Build Coastguard Worker    raw_date, commit = result.stdout.decode().split(';')
1248*c2e18aaaSAndroid Build Coastguard Worker    date = datetime.datetime.strptime(raw_date, '%Y-%m-%d %H:%M:%S %z').date()
1249*c2e18aaaSAndroid Build Coastguard Worker  # atest_dir doesn't exist will throw FileNotFoundError.
1250*c2e18aaaSAndroid Build Coastguard Worker  except (subprocess.CalledProcessError, FileNotFoundError):
1251*c2e18aaaSAndroid Build Coastguard Worker    # Use today as the commit date for unexpected conditions.
1252*c2e18aaaSAndroid Build Coastguard Worker    date = datetime.datetime.today().date()
1253*c2e18aaaSAndroid Build Coastguard Worker    commit = 'unknown'
1254*c2e18aaaSAndroid Build Coastguard Worker  return f'{date}_{commit}'
1255*c2e18aaaSAndroid Build Coastguard Worker
1256*c2e18aaaSAndroid Build Coastguard Worker
1257*c2e18aaaSAndroid Build Coastguard Workerdef get_manifest_branch(show_aosp=False):
1258*c2e18aaaSAndroid Build Coastguard Worker  """Get the manifest branch.
1259*c2e18aaaSAndroid Build Coastguard Worker
1260*c2e18aaaSAndroid Build Coastguard Worker  Args:
1261*c2e18aaaSAndroid Build Coastguard Worker      show_aosp: A boolean that shows 'aosp' prefix by checking the 'remote'
1262*c2e18aaaSAndroid Build Coastguard Worker        attribute.
1263*c2e18aaaSAndroid Build Coastguard Worker
1264*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1265*c2e18aaaSAndroid Build Coastguard Worker      The value of 'revision' of the included xml or default.xml.
1266*c2e18aaaSAndroid Build Coastguard Worker
1267*c2e18aaaSAndroid Build Coastguard Worker      None when no ANDROID_BUILD_TOP or unable to access default.xml.
1268*c2e18aaaSAndroid Build Coastguard Worker  """
1269*c2e18aaaSAndroid Build Coastguard Worker  #      (portal xml)                            (default xml)
1270*c2e18aaaSAndroid Build Coastguard Worker  # +--------------------+ _get_include() +-----------------------------+
1271*c2e18aaaSAndroid Build Coastguard Worker  # | .repo/manifest.xml |--------------->| .repo/manifests/default.xml |
1272*c2e18aaaSAndroid Build Coastguard Worker  # +--------------------+                +---------------+-------------+
1273*c2e18aaaSAndroid Build Coastguard Worker  #                          <default revision="master" |
1274*c2e18aaaSAndroid Build Coastguard Worker  #                                   remote="aosp"     | _get_revision()
1275*c2e18aaaSAndroid Build Coastguard Worker  #                                   sync-j="4"/>      V
1276*c2e18aaaSAndroid Build Coastguard Worker  #                                                 +--------+
1277*c2e18aaaSAndroid Build Coastguard Worker  #                                                 | master |
1278*c2e18aaaSAndroid Build Coastguard Worker  #                                                 +--------+
1279*c2e18aaaSAndroid Build Coastguard Worker  build_top = os.getenv(constants.ANDROID_BUILD_TOP)
1280*c2e18aaaSAndroid Build Coastguard Worker  if not build_top:
1281*c2e18aaaSAndroid Build Coastguard Worker    return None
1282*c2e18aaaSAndroid Build Coastguard Worker  portal_xml = Path(build_top).joinpath('.repo', 'manifest.xml')
1283*c2e18aaaSAndroid Build Coastguard Worker  default_xml = Path(build_top).joinpath('.repo/manifests', 'default.xml')
1284*c2e18aaaSAndroid Build Coastguard Worker
1285*c2e18aaaSAndroid Build Coastguard Worker  def _get_revision(xml):
1286*c2e18aaaSAndroid Build Coastguard Worker    try:
1287*c2e18aaaSAndroid Build Coastguard Worker      xml_root = ET.parse(xml).getroot()
1288*c2e18aaaSAndroid Build Coastguard Worker    except (IOError, OSError, ET.ParseError):
1289*c2e18aaaSAndroid Build Coastguard Worker      # TODO(b/274989179) Change back to warning once warning if not going
1290*c2e18aaaSAndroid Build Coastguard Worker      # to be treat as test failure. Or test_get_manifest_branch unit test
1291*c2e18aaaSAndroid Build Coastguard Worker      # could be fix if return None if portal_xml or default_xml not
1292*c2e18aaaSAndroid Build Coastguard Worker      # exist.
1293*c2e18aaaSAndroid Build Coastguard Worker      logging.info('%s could not be read.', xml)
1294*c2e18aaaSAndroid Build Coastguard Worker      return ''
1295*c2e18aaaSAndroid Build Coastguard Worker    default_tags = xml_root.findall('./default')
1296*c2e18aaaSAndroid Build Coastguard Worker    if default_tags:
1297*c2e18aaaSAndroid Build Coastguard Worker      prefix = ''
1298*c2e18aaaSAndroid Build Coastguard Worker      for tag in default_tags:
1299*c2e18aaaSAndroid Build Coastguard Worker        branch = tag.attrib.get('revision')
1300*c2e18aaaSAndroid Build Coastguard Worker        if show_aosp and tag.attrib.get('remote') == 'aosp':
1301*c2e18aaaSAndroid Build Coastguard Worker          prefix = 'aosp-'
1302*c2e18aaaSAndroid Build Coastguard Worker        return f'{prefix}{branch}'
1303*c2e18aaaSAndroid Build Coastguard Worker    return ''
1304*c2e18aaaSAndroid Build Coastguard Worker
1305*c2e18aaaSAndroid Build Coastguard Worker  def _get_include(xml):
1306*c2e18aaaSAndroid Build Coastguard Worker    try:
1307*c2e18aaaSAndroid Build Coastguard Worker      xml_root = ET.parse(xml).getroot()
1308*c2e18aaaSAndroid Build Coastguard Worker    except (IOError, OSError, ET.ParseError):
1309*c2e18aaaSAndroid Build Coastguard Worker      # TODO(b/274989179) Change back to warning once warning if not going
1310*c2e18aaaSAndroid Build Coastguard Worker      # to be treat as test failure. Or test_get_manifest_branch unit test
1311*c2e18aaaSAndroid Build Coastguard Worker      # could be fix if return None if portal_xml or default_xml not
1312*c2e18aaaSAndroid Build Coastguard Worker      # exist.
1313*c2e18aaaSAndroid Build Coastguard Worker      logging.info('%s could not be read.', xml)
1314*c2e18aaaSAndroid Build Coastguard Worker      return Path()
1315*c2e18aaaSAndroid Build Coastguard Worker    include_tags = xml_root.findall('./include')
1316*c2e18aaaSAndroid Build Coastguard Worker    if include_tags:
1317*c2e18aaaSAndroid Build Coastguard Worker      for tag in include_tags:
1318*c2e18aaaSAndroid Build Coastguard Worker        name = tag.attrib.get('name')
1319*c2e18aaaSAndroid Build Coastguard Worker        if name:
1320*c2e18aaaSAndroid Build Coastguard Worker          return Path(build_top).joinpath('.repo/manifests', name)
1321*c2e18aaaSAndroid Build Coastguard Worker    return default_xml
1322*c2e18aaaSAndroid Build Coastguard Worker
1323*c2e18aaaSAndroid Build Coastguard Worker  # 1. Try getting revision from .repo/manifests/default.xml
1324*c2e18aaaSAndroid Build Coastguard Worker  if default_xml.is_file():
1325*c2e18aaaSAndroid Build Coastguard Worker    return _get_revision(default_xml)
1326*c2e18aaaSAndroid Build Coastguard Worker  # 2. Try getting revision from the included xml of .repo/manifest.xml
1327*c2e18aaaSAndroid Build Coastguard Worker  include_xml = _get_include(portal_xml)
1328*c2e18aaaSAndroid Build Coastguard Worker  if include_xml.is_file():
1329*c2e18aaaSAndroid Build Coastguard Worker    return _get_revision(include_xml)
1330*c2e18aaaSAndroid Build Coastguard Worker  # 3. Try getting revision directly from manifest.xml (unlikely to happen)
1331*c2e18aaaSAndroid Build Coastguard Worker  return _get_revision(portal_xml)
1332*c2e18aaaSAndroid Build Coastguard Worker
1333*c2e18aaaSAndroid Build Coastguard Worker
1334*c2e18aaaSAndroid Build Coastguard Workerdef get_build_target():
1335*c2e18aaaSAndroid Build Coastguard Worker  """Get the build target form system environment TARGET_PRODUCT."""
1336*c2e18aaaSAndroid Build Coastguard Worker  build_target = '%s-%s-%s' % (
1337*c2e18aaaSAndroid Build Coastguard Worker      os.getenv(constants.ANDROID_TARGET_PRODUCT, None),
1338*c2e18aaaSAndroid Build Coastguard Worker      os.getenv('TARGET_RELEASE', None),
1339*c2e18aaaSAndroid Build Coastguard Worker      os.getenv(constants.TARGET_BUILD_VARIANT, None),
1340*c2e18aaaSAndroid Build Coastguard Worker  )
1341*c2e18aaaSAndroid Build Coastguard Worker  return build_target
1342*c2e18aaaSAndroid Build Coastguard Worker
1343*c2e18aaaSAndroid Build Coastguard Worker
1344*c2e18aaaSAndroid Build Coastguard Workerdef has_wildcard(test_name):
1345*c2e18aaaSAndroid Build Coastguard Worker  """Tell whether the test_name(either a list or string) contains wildcard
1346*c2e18aaaSAndroid Build Coastguard Worker
1347*c2e18aaaSAndroid Build Coastguard Worker  symbols.
1348*c2e18aaaSAndroid Build Coastguard Worker
1349*c2e18aaaSAndroid Build Coastguard Worker  Args:
1350*c2e18aaaSAndroid Build Coastguard Worker      test_name: A list or a str.
1351*c2e18aaaSAndroid Build Coastguard Worker
1352*c2e18aaaSAndroid Build Coastguard Worker  Return:
1353*c2e18aaaSAndroid Build Coastguard Worker      True if test_name contains wildcard, False otherwise.
1354*c2e18aaaSAndroid Build Coastguard Worker  """
1355*c2e18aaaSAndroid Build Coastguard Worker  if isinstance(test_name, str):
1356*c2e18aaaSAndroid Build Coastguard Worker    return any(char in test_name for char in _WILDCARD_CHARS)
1357*c2e18aaaSAndroid Build Coastguard Worker  if isinstance(test_name, list):
1358*c2e18aaaSAndroid Build Coastguard Worker    for name in test_name:
1359*c2e18aaaSAndroid Build Coastguard Worker      if has_wildcard(name):
1360*c2e18aaaSAndroid Build Coastguard Worker        return True
1361*c2e18aaaSAndroid Build Coastguard Worker  return False
1362*c2e18aaaSAndroid Build Coastguard Worker
1363*c2e18aaaSAndroid Build Coastguard Worker
1364*c2e18aaaSAndroid Build Coastguard Workerdef is_build_file(path):
1365*c2e18aaaSAndroid Build Coastguard Worker  """If input file is one of an android build file.
1366*c2e18aaaSAndroid Build Coastguard Worker
1367*c2e18aaaSAndroid Build Coastguard Worker  Args:
1368*c2e18aaaSAndroid Build Coastguard Worker      path: A string of file path.
1369*c2e18aaaSAndroid Build Coastguard Worker
1370*c2e18aaaSAndroid Build Coastguard Worker  Return:
1371*c2e18aaaSAndroid Build Coastguard Worker      True if path is android build file, False otherwise.
1372*c2e18aaaSAndroid Build Coastguard Worker  """
1373*c2e18aaaSAndroid Build Coastguard Worker  return bool(os.path.splitext(path)[-1] in _ANDROID_BUILD_EXT)
1374*c2e18aaaSAndroid Build Coastguard Worker
1375*c2e18aaaSAndroid Build Coastguard Worker
1376*c2e18aaaSAndroid Build Coastguard Workerdef quote(input_str):
1377*c2e18aaaSAndroid Build Coastguard Worker  """If the input string -- especially in custom args -- contains shell-aware
1378*c2e18aaaSAndroid Build Coastguard Worker
1379*c2e18aaaSAndroid Build Coastguard Worker  characters, insert a pair of "\" to the input string.
1380*c2e18aaaSAndroid Build Coastguard Worker
1381*c2e18aaaSAndroid Build Coastguard Worker  e.g. unit(test|testing|testing) -> 'unit(test|testing|testing)'
1382*c2e18aaaSAndroid Build Coastguard Worker
1383*c2e18aaaSAndroid Build Coastguard Worker  Args:
1384*c2e18aaaSAndroid Build Coastguard Worker      input_str: A string from user input.
1385*c2e18aaaSAndroid Build Coastguard Worker
1386*c2e18aaaSAndroid Build Coastguard Worker  Returns: A string with single quotes if regex chars were detected.
1387*c2e18aaaSAndroid Build Coastguard Worker  """
1388*c2e18aaaSAndroid Build Coastguard Worker  if has_chars(input_str, _REGEX_CHARS):
1389*c2e18aaaSAndroid Build Coastguard Worker    return "'" + input_str + "'"
1390*c2e18aaaSAndroid Build Coastguard Worker  return input_str
1391*c2e18aaaSAndroid Build Coastguard Worker
1392*c2e18aaaSAndroid Build Coastguard Worker
1393*c2e18aaaSAndroid Build Coastguard Workerdef has_chars(input_str, chars):
1394*c2e18aaaSAndroid Build Coastguard Worker  """Check if the input string contains one of the designated characters.
1395*c2e18aaaSAndroid Build Coastguard Worker
1396*c2e18aaaSAndroid Build Coastguard Worker  Args:
1397*c2e18aaaSAndroid Build Coastguard Worker      input_str: A string from user input.
1398*c2e18aaaSAndroid Build Coastguard Worker      chars: An iterable object.
1399*c2e18aaaSAndroid Build Coastguard Worker
1400*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1401*c2e18aaaSAndroid Build Coastguard Worker      True if the input string contains one of the special chars.
1402*c2e18aaaSAndroid Build Coastguard Worker  """
1403*c2e18aaaSAndroid Build Coastguard Worker  for char in chars:
1404*c2e18aaaSAndroid Build Coastguard Worker    if char in input_str:
1405*c2e18aaaSAndroid Build Coastguard Worker      return True
1406*c2e18aaaSAndroid Build Coastguard Worker  return False
1407*c2e18aaaSAndroid Build Coastguard Worker
1408*c2e18aaaSAndroid Build Coastguard Worker
1409*c2e18aaaSAndroid Build Coastguard Workerdef prompt_with_yn_result(msg, default=True):
1410*c2e18aaaSAndroid Build Coastguard Worker  """Prompt message and get yes or no result.
1411*c2e18aaaSAndroid Build Coastguard Worker
1412*c2e18aaaSAndroid Build Coastguard Worker  Args:
1413*c2e18aaaSAndroid Build Coastguard Worker      msg: The question you want asking.
1414*c2e18aaaSAndroid Build Coastguard Worker      default: boolean to True/Yes or False/No
1415*c2e18aaaSAndroid Build Coastguard Worker
1416*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1417*c2e18aaaSAndroid Build Coastguard Worker      default value if get KeyboardInterrupt or ValueError exception.
1418*c2e18aaaSAndroid Build Coastguard Worker  """
1419*c2e18aaaSAndroid Build Coastguard Worker  suffix = '[Y/n]: ' if default else '[y/N]: '
1420*c2e18aaaSAndroid Build Coastguard Worker  try:
1421*c2e18aaaSAndroid Build Coastguard Worker    return strtobool(input(msg + suffix))
1422*c2e18aaaSAndroid Build Coastguard Worker  except (ValueError, KeyboardInterrupt):
1423*c2e18aaaSAndroid Build Coastguard Worker    return default
1424*c2e18aaaSAndroid Build Coastguard Worker
1425*c2e18aaaSAndroid Build Coastguard Worker
1426*c2e18aaaSAndroid Build Coastguard Workerdef strtobool(val):
1427*c2e18aaaSAndroid Build Coastguard Worker  """Convert a string representation of truth to True or False.
1428*c2e18aaaSAndroid Build Coastguard Worker
1429*c2e18aaaSAndroid Build Coastguard Worker  Args:
1430*c2e18aaaSAndroid Build Coastguard Worker      val: a string of input value.
1431*c2e18aaaSAndroid Build Coastguard Worker
1432*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1433*c2e18aaaSAndroid Build Coastguard Worker      True when values are 'y', 'yes', 't', 'true', 'on', and '1';
1434*c2e18aaaSAndroid Build Coastguard Worker      False when 'n', 'no', 'f', 'false', 'off', and '0'.
1435*c2e18aaaSAndroid Build Coastguard Worker      Raises ValueError if 'val' is anything else.
1436*c2e18aaaSAndroid Build Coastguard Worker  """
1437*c2e18aaaSAndroid Build Coastguard Worker  if val.lower() in ('y', 'yes', 't', 'true', 'on', '1'):
1438*c2e18aaaSAndroid Build Coastguard Worker    return True
1439*c2e18aaaSAndroid Build Coastguard Worker  if val.lower() in ('n', 'no', 'f', 'false', 'off', '0'):
1440*c2e18aaaSAndroid Build Coastguard Worker    return False
1441*c2e18aaaSAndroid Build Coastguard Worker  raise ValueError('invalid truth value %r' % (val,))
1442*c2e18aaaSAndroid Build Coastguard Worker
1443*c2e18aaaSAndroid Build Coastguard Worker
1444*c2e18aaaSAndroid Build Coastguard Workerdef get_android_junit_config_filters(test_config):
1445*c2e18aaaSAndroid Build Coastguard Worker  """Get the dictionary of a input config for junit config's filters
1446*c2e18aaaSAndroid Build Coastguard Worker
1447*c2e18aaaSAndroid Build Coastguard Worker  Args:
1448*c2e18aaaSAndroid Build Coastguard Worker      test_config: The path of the test config.
1449*c2e18aaaSAndroid Build Coastguard Worker
1450*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1451*c2e18aaaSAndroid Build Coastguard Worker      A dictionary include all the filters in the input config.
1452*c2e18aaaSAndroid Build Coastguard Worker  """
1453*c2e18aaaSAndroid Build Coastguard Worker  filter_dict = {}
1454*c2e18aaaSAndroid Build Coastguard Worker  xml_root = ET.parse(test_config).getroot()
1455*c2e18aaaSAndroid Build Coastguard Worker  option_tags = xml_root.findall('.//option')
1456*c2e18aaaSAndroid Build Coastguard Worker  for tag in option_tags:
1457*c2e18aaaSAndroid Build Coastguard Worker    name = tag.attrib['name'].strip()
1458*c2e18aaaSAndroid Build Coastguard Worker    if name in constants.SUPPORTED_FILTERS:
1459*c2e18aaaSAndroid Build Coastguard Worker      filter_values = filter_dict.get(name, [])
1460*c2e18aaaSAndroid Build Coastguard Worker      value = tag.attrib['value'].strip()
1461*c2e18aaaSAndroid Build Coastguard Worker      filter_values.append(value)
1462*c2e18aaaSAndroid Build Coastguard Worker      filter_dict.update({name: filter_values})
1463*c2e18aaaSAndroid Build Coastguard Worker  return filter_dict
1464*c2e18aaaSAndroid Build Coastguard Worker
1465*c2e18aaaSAndroid Build Coastguard Worker
1466*c2e18aaaSAndroid Build Coastguard Workerdef get_config_parameter(test_config):
1467*c2e18aaaSAndroid Build Coastguard Worker  """Get all the parameter values for the input config
1468*c2e18aaaSAndroid Build Coastguard Worker
1469*c2e18aaaSAndroid Build Coastguard Worker  Args:
1470*c2e18aaaSAndroid Build Coastguard Worker      test_config: The path of the test config.
1471*c2e18aaaSAndroid Build Coastguard Worker
1472*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1473*c2e18aaaSAndroid Build Coastguard Worker      A set include all the parameters of the input config.
1474*c2e18aaaSAndroid Build Coastguard Worker  """
1475*c2e18aaaSAndroid Build Coastguard Worker  parameters = set()
1476*c2e18aaaSAndroid Build Coastguard Worker  xml_root = ET.parse(test_config).getroot()
1477*c2e18aaaSAndroid Build Coastguard Worker  option_tags = xml_root.findall('.//option')
1478*c2e18aaaSAndroid Build Coastguard Worker  for tag in option_tags:
1479*c2e18aaaSAndroid Build Coastguard Worker    name = tag.attrib['name'].strip()
1480*c2e18aaaSAndroid Build Coastguard Worker    if name == constants.CONFIG_DESCRIPTOR:
1481*c2e18aaaSAndroid Build Coastguard Worker      key = tag.attrib['key'].strip()
1482*c2e18aaaSAndroid Build Coastguard Worker      if key == constants.PARAMETER_KEY:
1483*c2e18aaaSAndroid Build Coastguard Worker        value = tag.attrib['value'].strip()
1484*c2e18aaaSAndroid Build Coastguard Worker        parameters.add(value)
1485*c2e18aaaSAndroid Build Coastguard Worker  return parameters
1486*c2e18aaaSAndroid Build Coastguard Worker
1487*c2e18aaaSAndroid Build Coastguard Worker
1488*c2e18aaaSAndroid Build Coastguard Workerdef get_config_device(test_config):
1489*c2e18aaaSAndroid Build Coastguard Worker  """Get all the device names from the input config
1490*c2e18aaaSAndroid Build Coastguard Worker
1491*c2e18aaaSAndroid Build Coastguard Worker  Args:
1492*c2e18aaaSAndroid Build Coastguard Worker      test_config: The path of the test config.
1493*c2e18aaaSAndroid Build Coastguard Worker
1494*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1495*c2e18aaaSAndroid Build Coastguard Worker      A set include all the device name of the input config.
1496*c2e18aaaSAndroid Build Coastguard Worker  """
1497*c2e18aaaSAndroid Build Coastguard Worker  devices = set()
1498*c2e18aaaSAndroid Build Coastguard Worker  try:
1499*c2e18aaaSAndroid Build Coastguard Worker    xml_root = ET.parse(test_config).getroot()
1500*c2e18aaaSAndroid Build Coastguard Worker    device_tags = xml_root.findall('.//device')
1501*c2e18aaaSAndroid Build Coastguard Worker    for tag in device_tags:
1502*c2e18aaaSAndroid Build Coastguard Worker      name = tag.attrib['name'].strip()
1503*c2e18aaaSAndroid Build Coastguard Worker      devices.add(name)
1504*c2e18aaaSAndroid Build Coastguard Worker  except ET.ParseError as e:
1505*c2e18aaaSAndroid Build Coastguard Worker    colorful_print('Config has invalid format.', constants.RED)
1506*c2e18aaaSAndroid Build Coastguard Worker    colorful_print('File %s : %s' % (test_config, str(e)), constants.YELLOW)
1507*c2e18aaaSAndroid Build Coastguard Worker    sys.exit(ExitCode.CONFIG_INVALID_FORMAT)
1508*c2e18aaaSAndroid Build Coastguard Worker  return devices
1509*c2e18aaaSAndroid Build Coastguard Worker
1510*c2e18aaaSAndroid Build Coastguard Worker
1511*c2e18aaaSAndroid Build Coastguard Workerdef get_mainline_param(test_config):
1512*c2e18aaaSAndroid Build Coastguard Worker  """Get all the mainline-param values for the input config
1513*c2e18aaaSAndroid Build Coastguard Worker
1514*c2e18aaaSAndroid Build Coastguard Worker  Args:
1515*c2e18aaaSAndroid Build Coastguard Worker      test_config: The path of the test config.
1516*c2e18aaaSAndroid Build Coastguard Worker
1517*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1518*c2e18aaaSAndroid Build Coastguard Worker      A set include all the parameters of the input config.
1519*c2e18aaaSAndroid Build Coastguard Worker  """
1520*c2e18aaaSAndroid Build Coastguard Worker  mainline_param = set()
1521*c2e18aaaSAndroid Build Coastguard Worker  xml_root = ET.parse(test_config).getroot()
1522*c2e18aaaSAndroid Build Coastguard Worker  option_tags = xml_root.findall('.//option')
1523*c2e18aaaSAndroid Build Coastguard Worker  for tag in option_tags:
1524*c2e18aaaSAndroid Build Coastguard Worker    name = tag.attrib['name'].strip()
1525*c2e18aaaSAndroid Build Coastguard Worker    if name == constants.CONFIG_DESCRIPTOR:
1526*c2e18aaaSAndroid Build Coastguard Worker      key = tag.attrib['key'].strip()
1527*c2e18aaaSAndroid Build Coastguard Worker      if key == constants.MAINLINE_PARAM_KEY:
1528*c2e18aaaSAndroid Build Coastguard Worker        value = tag.attrib['value'].strip()
1529*c2e18aaaSAndroid Build Coastguard Worker        mainline_param.add(value)
1530*c2e18aaaSAndroid Build Coastguard Worker  return mainline_param
1531*c2e18aaaSAndroid Build Coastguard Worker
1532*c2e18aaaSAndroid Build Coastguard Worker
1533*c2e18aaaSAndroid Build Coastguard Workerdef get_adb_devices():
1534*c2e18aaaSAndroid Build Coastguard Worker  """Run `adb devices` and return a list of devices.
1535*c2e18aaaSAndroid Build Coastguard Worker
1536*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1537*c2e18aaaSAndroid Build Coastguard Worker      A list of devices. e.g.
1538*c2e18aaaSAndroid Build Coastguard Worker      ['127.0.0.1:40623', '127.0.0.1:40625']
1539*c2e18aaaSAndroid Build Coastguard Worker  """
1540*c2e18aaaSAndroid Build Coastguard Worker  probe_cmd = 'adb devices | egrep -v "^List|^$"||true'
1541*c2e18aaaSAndroid Build Coastguard Worker  suts = subprocess.check_output(probe_cmd, shell=True).decode().splitlines()
1542*c2e18aaaSAndroid Build Coastguard Worker  return [sut.split('\t')[0] for sut in suts]
1543*c2e18aaaSAndroid Build Coastguard Worker
1544*c2e18aaaSAndroid Build Coastguard Worker
1545*c2e18aaaSAndroid Build Coastguard Workerdef get_android_config():
1546*c2e18aaaSAndroid Build Coastguard Worker  """Get Android config as "printconfig" shows.
1547*c2e18aaaSAndroid Build Coastguard Worker
1548*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1549*c2e18aaaSAndroid Build Coastguard Worker      A dict of Android configurations.
1550*c2e18aaaSAndroid Build Coastguard Worker  """
1551*c2e18aaaSAndroid Build Coastguard Worker  dump_cmd = get_build_cmd(dump=True)
1552*c2e18aaaSAndroid Build Coastguard Worker  raw_config = subprocess.check_output(dump_cmd).decode('utf-8')
1553*c2e18aaaSAndroid Build Coastguard Worker  android_config = {}
1554*c2e18aaaSAndroid Build Coastguard Worker  for element in raw_config.splitlines():
1555*c2e18aaaSAndroid Build Coastguard Worker    if not element.startswith('='):
1556*c2e18aaaSAndroid Build Coastguard Worker      key, value = tuple(element.split('=', 1))
1557*c2e18aaaSAndroid Build Coastguard Worker      android_config.setdefault(key, value)
1558*c2e18aaaSAndroid Build Coastguard Worker  return android_config
1559*c2e18aaaSAndroid Build Coastguard Worker
1560*c2e18aaaSAndroid Build Coastguard Worker
1561*c2e18aaaSAndroid Build Coastguard Workerdef get_config_gtest_args(test_config):
1562*c2e18aaaSAndroid Build Coastguard Worker  """Get gtest's module-name and device-path option from the input config
1563*c2e18aaaSAndroid Build Coastguard Worker
1564*c2e18aaaSAndroid Build Coastguard Worker  Args:
1565*c2e18aaaSAndroid Build Coastguard Worker      test_config: The path of the test config.
1566*c2e18aaaSAndroid Build Coastguard Worker
1567*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1568*c2e18aaaSAndroid Build Coastguard Worker      A string of gtest's module name.
1569*c2e18aaaSAndroid Build Coastguard Worker      A string of gtest's device path.
1570*c2e18aaaSAndroid Build Coastguard Worker  """
1571*c2e18aaaSAndroid Build Coastguard Worker  module_name = ''
1572*c2e18aaaSAndroid Build Coastguard Worker  device_path = ''
1573*c2e18aaaSAndroid Build Coastguard Worker  xml_root = ET.parse(test_config).getroot()
1574*c2e18aaaSAndroid Build Coastguard Worker  option_tags = xml_root.findall('.//option')
1575*c2e18aaaSAndroid Build Coastguard Worker  for tag in option_tags:
1576*c2e18aaaSAndroid Build Coastguard Worker    name = tag.attrib['name'].strip()
1577*c2e18aaaSAndroid Build Coastguard Worker    value = tag.attrib['value'].strip()
1578*c2e18aaaSAndroid Build Coastguard Worker    if name == 'native-test-device-path':
1579*c2e18aaaSAndroid Build Coastguard Worker      device_path = value
1580*c2e18aaaSAndroid Build Coastguard Worker    elif name == 'module-name':
1581*c2e18aaaSAndroid Build Coastguard Worker      module_name = value
1582*c2e18aaaSAndroid Build Coastguard Worker  return module_name, device_path
1583*c2e18aaaSAndroid Build Coastguard Worker
1584*c2e18aaaSAndroid Build Coastguard Worker
1585*c2e18aaaSAndroid Build Coastguard Workerdef get_arch_name(module_name, is_64=False):
1586*c2e18aaaSAndroid Build Coastguard Worker  """Get the arch folder name for the input module.
1587*c2e18aaaSAndroid Build Coastguard Worker
1588*c2e18aaaSAndroid Build Coastguard Worker  Scan the test case folders to get the matched arch folder name.
1589*c2e18aaaSAndroid Build Coastguard Worker
1590*c2e18aaaSAndroid Build Coastguard Worker  Args:
1591*c2e18aaaSAndroid Build Coastguard Worker      module_name: The module_name of test
1592*c2e18aaaSAndroid Build Coastguard Worker      is_64: If need 64 bit arch name, False otherwise.
1593*c2e18aaaSAndroid Build Coastguard Worker
1594*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1595*c2e18aaaSAndroid Build Coastguard Worker      A string of the arch name.
1596*c2e18aaaSAndroid Build Coastguard Worker  """
1597*c2e18aaaSAndroid Build Coastguard Worker  arch_32 = ['arm', 'x86']
1598*c2e18aaaSAndroid Build Coastguard Worker  arch_64 = ['arm64', 'x86_64']
1599*c2e18aaaSAndroid Build Coastguard Worker  arch_list = arch_32
1600*c2e18aaaSAndroid Build Coastguard Worker  if is_64:
1601*c2e18aaaSAndroid Build Coastguard Worker    arch_list = arch_64
1602*c2e18aaaSAndroid Build Coastguard Worker  test_case_root = os.path.join(
1603*c2e18aaaSAndroid Build Coastguard Worker      os.environ.get(constants.ANDROID_TARGET_OUT_TESTCASES, ''), module_name
1604*c2e18aaaSAndroid Build Coastguard Worker  )
1605*c2e18aaaSAndroid Build Coastguard Worker  if not os.path.isdir(test_case_root):
1606*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('%s does not exist.', test_case_root)
1607*c2e18aaaSAndroid Build Coastguard Worker    return ''
1608*c2e18aaaSAndroid Build Coastguard Worker  for f in os.listdir(test_case_root):
1609*c2e18aaaSAndroid Build Coastguard Worker    if f in arch_list:
1610*c2e18aaaSAndroid Build Coastguard Worker      return f
1611*c2e18aaaSAndroid Build Coastguard Worker  return ''
1612*c2e18aaaSAndroid Build Coastguard Worker
1613*c2e18aaaSAndroid Build Coastguard Worker
1614*c2e18aaaSAndroid Build Coastguard Workerdef copy_single_arch_native_symbols(
1615*c2e18aaaSAndroid Build Coastguard Worker    symbol_root, module_name, device_path, is_64=False
1616*c2e18aaaSAndroid Build Coastguard Worker):
1617*c2e18aaaSAndroid Build Coastguard Worker  """Copy symbol files for native tests which belong to input arch.
1618*c2e18aaaSAndroid Build Coastguard Worker
1619*c2e18aaaSAndroid Build Coastguard Worker  Args:
1620*c2e18aaaSAndroid Build Coastguard Worker      module_name: The module_name of test
1621*c2e18aaaSAndroid Build Coastguard Worker      device_path: The device path define in test config.
1622*c2e18aaaSAndroid Build Coastguard Worker      is_64: True if need to copy 64bit symbols, False otherwise.
1623*c2e18aaaSAndroid Build Coastguard Worker  """
1624*c2e18aaaSAndroid Build Coastguard Worker  src_symbol = os.path.join(symbol_root, 'data', 'nativetest', module_name)
1625*c2e18aaaSAndroid Build Coastguard Worker  if is_64:
1626*c2e18aaaSAndroid Build Coastguard Worker    src_symbol = os.path.join(symbol_root, 'data', 'nativetest64', module_name)
1627*c2e18aaaSAndroid Build Coastguard Worker  dst_symbol = os.path.join(
1628*c2e18aaaSAndroid Build Coastguard Worker      symbol_root,
1629*c2e18aaaSAndroid Build Coastguard Worker      device_path[1:],
1630*c2e18aaaSAndroid Build Coastguard Worker      module_name,
1631*c2e18aaaSAndroid Build Coastguard Worker      get_arch_name(module_name, is_64),
1632*c2e18aaaSAndroid Build Coastguard Worker  )
1633*c2e18aaaSAndroid Build Coastguard Worker  if os.path.isdir(src_symbol):
1634*c2e18aaaSAndroid Build Coastguard Worker    # TODO: Use shutil.copytree(src, dst, dirs_exist_ok=True) after
1635*c2e18aaaSAndroid Build Coastguard Worker    #  python3.8
1636*c2e18aaaSAndroid Build Coastguard Worker    if os.path.isdir(dst_symbol):
1637*c2e18aaaSAndroid Build Coastguard Worker      shutil.rmtree(dst_symbol)
1638*c2e18aaaSAndroid Build Coastguard Worker    shutil.copytree(src_symbol, dst_symbol)
1639*c2e18aaaSAndroid Build Coastguard Worker
1640*c2e18aaaSAndroid Build Coastguard Worker
1641*c2e18aaaSAndroid Build Coastguard Workerdef copy_native_symbols(module_name, device_path):
1642*c2e18aaaSAndroid Build Coastguard Worker  """Copy symbol files for native tests to match with tradefed file structure.
1643*c2e18aaaSAndroid Build Coastguard Worker
1644*c2e18aaaSAndroid Build Coastguard Worker  The original symbols will locate at
1645*c2e18aaaSAndroid Build Coastguard Worker  $(PRODUCT_OUT)/symbols/data/nativetest(64)/$(module)/$(stem).
1646*c2e18aaaSAndroid Build Coastguard Worker  From TF, the test binary will locate at
1647*c2e18aaaSAndroid Build Coastguard Worker  /data/local/tmp/$(module)/$(arch)/$(stem).
1648*c2e18aaaSAndroid Build Coastguard Worker  In order to make trace work need to copy the original symbol to
1649*c2e18aaaSAndroid Build Coastguard Worker  $(PRODUCT_OUT)/symbols/data/local/tmp/$(module)/$(arch)/$(stem)
1650*c2e18aaaSAndroid Build Coastguard Worker
1651*c2e18aaaSAndroid Build Coastguard Worker  Args:
1652*c2e18aaaSAndroid Build Coastguard Worker      module_name: The module_name of test
1653*c2e18aaaSAndroid Build Coastguard Worker      device_path: The device path define in test config.
1654*c2e18aaaSAndroid Build Coastguard Worker  """
1655*c2e18aaaSAndroid Build Coastguard Worker  symbol_root = os.path.join(
1656*c2e18aaaSAndroid Build Coastguard Worker      os.environ.get(constants.ANDROID_PRODUCT_OUT, ''), 'symbols'
1657*c2e18aaaSAndroid Build Coastguard Worker  )
1658*c2e18aaaSAndroid Build Coastguard Worker  if not os.path.isdir(symbol_root):
1659*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('Symbol dir:%s not exist, skip copy symbols.', symbol_root)
1660*c2e18aaaSAndroid Build Coastguard Worker    return
1661*c2e18aaaSAndroid Build Coastguard Worker  # Copy 32 bit symbols
1662*c2e18aaaSAndroid Build Coastguard Worker  if get_arch_name(module_name, is_64=False):
1663*c2e18aaaSAndroid Build Coastguard Worker    copy_single_arch_native_symbols(
1664*c2e18aaaSAndroid Build Coastguard Worker        symbol_root, module_name, device_path, is_64=False
1665*c2e18aaaSAndroid Build Coastguard Worker    )
1666*c2e18aaaSAndroid Build Coastguard Worker  # Copy 64 bit symbols
1667*c2e18aaaSAndroid Build Coastguard Worker  if get_arch_name(module_name, is_64=True):
1668*c2e18aaaSAndroid Build Coastguard Worker    copy_single_arch_native_symbols(
1669*c2e18aaaSAndroid Build Coastguard Worker        symbol_root, module_name, device_path, is_64=True
1670*c2e18aaaSAndroid Build Coastguard Worker    )
1671*c2e18aaaSAndroid Build Coastguard Worker
1672*c2e18aaaSAndroid Build Coastguard Worker
1673*c2e18aaaSAndroid Build Coastguard Workerdef get_config_preparer_options(test_config, class_name):
1674*c2e18aaaSAndroid Build Coastguard Worker  """Get all the parameter values for the input config
1675*c2e18aaaSAndroid Build Coastguard Worker
1676*c2e18aaaSAndroid Build Coastguard Worker  Args:
1677*c2e18aaaSAndroid Build Coastguard Worker      test_config: The path of the test config.
1678*c2e18aaaSAndroid Build Coastguard Worker      class_name: A string of target_preparer
1679*c2e18aaaSAndroid Build Coastguard Worker
1680*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1681*c2e18aaaSAndroid Build Coastguard Worker      A set include all the parameters of the input config.
1682*c2e18aaaSAndroid Build Coastguard Worker  """
1683*c2e18aaaSAndroid Build Coastguard Worker  options = {}
1684*c2e18aaaSAndroid Build Coastguard Worker  xml_root = ET.parse(test_config).getroot()
1685*c2e18aaaSAndroid Build Coastguard Worker  option_tags = xml_root.findall(
1686*c2e18aaaSAndroid Build Coastguard Worker      './/target_preparer[@class="%s"]/option' % class_name
1687*c2e18aaaSAndroid Build Coastguard Worker  )
1688*c2e18aaaSAndroid Build Coastguard Worker  for tag in option_tags:
1689*c2e18aaaSAndroid Build Coastguard Worker    name = tag.attrib['name'].strip()
1690*c2e18aaaSAndroid Build Coastguard Worker    value = tag.attrib['value'].strip()
1691*c2e18aaaSAndroid Build Coastguard Worker    options[name] = value
1692*c2e18aaaSAndroid Build Coastguard Worker  return options
1693*c2e18aaaSAndroid Build Coastguard Worker
1694*c2e18aaaSAndroid Build Coastguard Worker
1695*c2e18aaaSAndroid Build Coastguard Workerdef get_verify_key(tests, extra_args):
1696*c2e18aaaSAndroid Build Coastguard Worker  """Compose test command key.
1697*c2e18aaaSAndroid Build Coastguard Worker
1698*c2e18aaaSAndroid Build Coastguard Worker  Args:
1699*c2e18aaaSAndroid Build Coastguard Worker      test_name: A list of input tests.
1700*c2e18aaaSAndroid Build Coastguard Worker      extra_args: Dict of extra args to add to test run.
1701*c2e18aaaSAndroid Build Coastguard Worker
1702*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1703*c2e18aaaSAndroid Build Coastguard Worker      A composed test commands.
1704*c2e18aaaSAndroid Build Coastguard Worker  """
1705*c2e18aaaSAndroid Build Coastguard Worker  # test_commands is a concatenated string of sorted test_ref+extra_args.
1706*c2e18aaaSAndroid Build Coastguard Worker  # For example, "ITERATIONS=5 hello_world_test"
1707*c2e18aaaSAndroid Build Coastguard Worker  test_commands = tests
1708*c2e18aaaSAndroid Build Coastguard Worker  for key, value in extra_args.items():
1709*c2e18aaaSAndroid Build Coastguard Worker    test_commands.append('%s=%s' % (key, str(value)))
1710*c2e18aaaSAndroid Build Coastguard Worker  test_commands.sort()
1711*c2e18aaaSAndroid Build Coastguard Worker  return ' '.join(test_commands)
1712*c2e18aaaSAndroid Build Coastguard Worker
1713*c2e18aaaSAndroid Build Coastguard Worker
1714*c2e18aaaSAndroid Build Coastguard Workerdef save_build_files_timestamp():
1715*c2e18aaaSAndroid Build Coastguard Worker  """Method that generate timestamp of Android.{bp,mk} files.
1716*c2e18aaaSAndroid Build Coastguard Worker
1717*c2e18aaaSAndroid Build Coastguard Worker  The checksum of build files are stores in
1718*c2e18aaaSAndroid Build Coastguard Worker      $ANDROID_HOST_OUT/indices/buildfiles.stp
1719*c2e18aaaSAndroid Build Coastguard Worker  """
1720*c2e18aaaSAndroid Build Coastguard Worker  plocate_db = get_index_path(constants.LOCATE_CACHE)
1721*c2e18aaaSAndroid Build Coastguard Worker  plocate_db_exist = plocate_db.is_file()
1722*c2e18aaaSAndroid Build Coastguard Worker  logging.debug(
1723*c2e18aaaSAndroid Build Coastguard Worker      'Build files timestamp db file %s exists: %s',
1724*c2e18aaaSAndroid Build Coastguard Worker      plocate_db,
1725*c2e18aaaSAndroid Build Coastguard Worker      plocate_db_exist,
1726*c2e18aaaSAndroid Build Coastguard Worker  )
1727*c2e18aaaSAndroid Build Coastguard Worker
1728*c2e18aaaSAndroid Build Coastguard Worker  if plocate_db_exist:
1729*c2e18aaaSAndroid Build Coastguard Worker    cmd = f'locate -d{plocate_db} --existing ' r'--regex "/Android\.(bp|mk)$"'
1730*c2e18aaaSAndroid Build Coastguard Worker    results = subprocess.getoutput(cmd)
1731*c2e18aaaSAndroid Build Coastguard Worker    if results:
1732*c2e18aaaSAndroid Build Coastguard Worker      timestamp = {}
1733*c2e18aaaSAndroid Build Coastguard Worker      for build_file in results.splitlines():
1734*c2e18aaaSAndroid Build Coastguard Worker        timestamp.update({build_file: Path(build_file).stat().st_mtime})
1735*c2e18aaaSAndroid Build Coastguard Worker
1736*c2e18aaaSAndroid Build Coastguard Worker      timestamp_file = get_index_path(constants.BUILDFILES_STP)
1737*c2e18aaaSAndroid Build Coastguard Worker      logging.debug('Writing to build files timestamp db %s', timestamp_file)
1738*c2e18aaaSAndroid Build Coastguard Worker      with open(timestamp_file, 'w', encoding='utf-8') as _file:
1739*c2e18aaaSAndroid Build Coastguard Worker        json.dump(timestamp, _file)
1740*c2e18aaaSAndroid Build Coastguard Worker
1741*c2e18aaaSAndroid Build Coastguard Worker
1742*c2e18aaaSAndroid Build Coastguard Workerdef run_multi_proc(func, *args, **kwargs) -> Process:
1743*c2e18aaaSAndroid Build Coastguard Worker  """Start a process with multiprocessing and return Process object.
1744*c2e18aaaSAndroid Build Coastguard Worker
1745*c2e18aaaSAndroid Build Coastguard Worker  Args:
1746*c2e18aaaSAndroid Build Coastguard Worker      func: A string of function name which will be the target name.
1747*c2e18aaaSAndroid Build Coastguard Worker        args/kwargs: check doc page:
1748*c2e18aaaSAndroid Build Coastguard Worker      https://docs.python.org/3.8/library/multiprocessing.html#process-and-exceptions
1749*c2e18aaaSAndroid Build Coastguard Worker
1750*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1751*c2e18aaaSAndroid Build Coastguard Worker      multiprocessing.Process object.
1752*c2e18aaaSAndroid Build Coastguard Worker  """
1753*c2e18aaaSAndroid Build Coastguard Worker  proc = Process(target=func, *args, **kwargs)
1754*c2e18aaaSAndroid Build Coastguard Worker  proc.start()
1755*c2e18aaaSAndroid Build Coastguard Worker  return proc
1756*c2e18aaaSAndroid Build Coastguard Worker
1757*c2e18aaaSAndroid Build Coastguard Worker
1758*c2e18aaaSAndroid Build Coastguard Workerdef start_threading(target, *args, **kwargs) -> Thread:
1759*c2e18aaaSAndroid Build Coastguard Worker  """Start a Thread-based parallelism.
1760*c2e18aaaSAndroid Build Coastguard Worker
1761*c2e18aaaSAndroid Build Coastguard Worker  Args:
1762*c2e18aaaSAndroid Build Coastguard Worker      target: A string of function name which will be the target name.
1763*c2e18aaaSAndroid Build Coastguard Worker        args/kwargs: check doc page:
1764*c2e18aaaSAndroid Build Coastguard Worker        https://docs.python.org/3/library/threading.html#threading.Thread
1765*c2e18aaaSAndroid Build Coastguard Worker
1766*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1767*c2e18aaaSAndroid Build Coastguard Worker      threading.Thread object.
1768*c2e18aaaSAndroid Build Coastguard Worker  """
1769*c2e18aaaSAndroid Build Coastguard Worker  proc = Thread(target=target, *args, **kwargs)
1770*c2e18aaaSAndroid Build Coastguard Worker  proc.start()
1771*c2e18aaaSAndroid Build Coastguard Worker  return proc
1772*c2e18aaaSAndroid Build Coastguard Worker
1773*c2e18aaaSAndroid Build Coastguard Worker
1774*c2e18aaaSAndroid Build Coastguard Workerdef get_prebuilt_sdk_tools_dir():
1775*c2e18aaaSAndroid Build Coastguard Worker  """Get the path for the prebuilt sdk tools root dir.
1776*c2e18aaaSAndroid Build Coastguard Worker
1777*c2e18aaaSAndroid Build Coastguard Worker  Returns: The absolute path of prebuilt sdk tools directory.
1778*c2e18aaaSAndroid Build Coastguard Worker  """
1779*c2e18aaaSAndroid Build Coastguard Worker  build_top = Path(os.environ.get(constants.ANDROID_BUILD_TOP, ''))
1780*c2e18aaaSAndroid Build Coastguard Worker  return build_top.joinpath(
1781*c2e18aaaSAndroid Build Coastguard Worker      'prebuilts/sdk/tools/', str(platform.system()).lower(), 'bin'
1782*c2e18aaaSAndroid Build Coastguard Worker  )
1783*c2e18aaaSAndroid Build Coastguard Worker
1784*c2e18aaaSAndroid Build Coastguard Worker
1785*c2e18aaaSAndroid Build Coastguard Workerdef is_writable(path):
1786*c2e18aaaSAndroid Build Coastguard Worker  """Check if the given path is writable.
1787*c2e18aaaSAndroid Build Coastguard Worker
1788*c2e18aaaSAndroid Build Coastguard Worker  Returns: True if input path is writable, False otherwise.
1789*c2e18aaaSAndroid Build Coastguard Worker  """
1790*c2e18aaaSAndroid Build Coastguard Worker  if not os.path.exists(path):
1791*c2e18aaaSAndroid Build Coastguard Worker    return is_writable(os.path.dirname(path))
1792*c2e18aaaSAndroid Build Coastguard Worker  return os.access(path, os.W_OK)
1793*c2e18aaaSAndroid Build Coastguard Worker
1794*c2e18aaaSAndroid Build Coastguard Worker
1795*c2e18aaaSAndroid Build Coastguard Workerdef get_misc_dir():
1796*c2e18aaaSAndroid Build Coastguard Worker  """Get the path for the ATest data root dir.
1797*c2e18aaaSAndroid Build Coastguard Worker
1798*c2e18aaaSAndroid Build Coastguard Worker  Returns: The absolute path of the ATest data root dir.
1799*c2e18aaaSAndroid Build Coastguard Worker  """
1800*c2e18aaaSAndroid Build Coastguard Worker  home_dir = os.path.expanduser('~')
1801*c2e18aaaSAndroid Build Coastguard Worker  if is_writable(home_dir):
1802*c2e18aaaSAndroid Build Coastguard Worker    return home_dir
1803*c2e18aaaSAndroid Build Coastguard Worker  return get_build_out_dir()
1804*c2e18aaaSAndroid Build Coastguard Worker
1805*c2e18aaaSAndroid Build Coastguard Worker
1806*c2e18aaaSAndroid Build Coastguard Workerdef get_config_folder() -> Path:
1807*c2e18aaaSAndroid Build Coastguard Worker  """Returns the config folder path where upload config is stored."""
1808*c2e18aaaSAndroid Build Coastguard Worker  return Path(get_misc_dir()).joinpath('.atest')
1809*c2e18aaaSAndroid Build Coastguard Worker
1810*c2e18aaaSAndroid Build Coastguard Worker
1811*c2e18aaaSAndroid Build Coastguard Workerdef get_full_annotation_class_name(module_info, class_name):
1812*c2e18aaaSAndroid Build Coastguard Worker  """Get fully qualified class name from a class name.
1813*c2e18aaaSAndroid Build Coastguard Worker
1814*c2e18aaaSAndroid Build Coastguard Worker  If the given keyword(class_name) is "smalltest", this method can search
1815*c2e18aaaSAndroid Build Coastguard Worker  among source codes and grep the accurate annotation class name:
1816*c2e18aaaSAndroid Build Coastguard Worker
1817*c2e18aaaSAndroid Build Coastguard Worker      androidx.test.filters.SmallTest
1818*c2e18aaaSAndroid Build Coastguard Worker
1819*c2e18aaaSAndroid Build Coastguard Worker  Args:
1820*c2e18aaaSAndroid Build Coastguard Worker      module_info: A dict of module_info.
1821*c2e18aaaSAndroid Build Coastguard Worker      class_name: A string of class name.
1822*c2e18aaaSAndroid Build Coastguard Worker
1823*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1824*c2e18aaaSAndroid Build Coastguard Worker      A string of fully qualified class name, empty string otherwise.
1825*c2e18aaaSAndroid Build Coastguard Worker  """
1826*c2e18aaaSAndroid Build Coastguard Worker  fullname_re = re.compile(
1827*c2e18aaaSAndroid Build Coastguard Worker      r'import\s+(?P<fqcn>{})(|;)$'.format(class_name), re.I
1828*c2e18aaaSAndroid Build Coastguard Worker  )
1829*c2e18aaaSAndroid Build Coastguard Worker  keyword_re = re.compile(
1830*c2e18aaaSAndroid Build Coastguard Worker      r'import\s+(?P<fqcn>.*\.{})(|;)$'.format(class_name), re.I
1831*c2e18aaaSAndroid Build Coastguard Worker  )
1832*c2e18aaaSAndroid Build Coastguard Worker  build_top = Path(os.environ.get(constants.ANDROID_BUILD_TOP, ''))
1833*c2e18aaaSAndroid Build Coastguard Worker  for f in module_info.get(constants.MODULE_SRCS, []):
1834*c2e18aaaSAndroid Build Coastguard Worker    full_path = build_top.joinpath(f)
1835*c2e18aaaSAndroid Build Coastguard Worker    with open(full_path, 'r', encoding='utf-8') as cache:
1836*c2e18aaaSAndroid Build Coastguard Worker      for line in cache.readlines():
1837*c2e18aaaSAndroid Build Coastguard Worker        # Accept full class name.
1838*c2e18aaaSAndroid Build Coastguard Worker        match = fullname_re.match(line)
1839*c2e18aaaSAndroid Build Coastguard Worker        if match:
1840*c2e18aaaSAndroid Build Coastguard Worker          return match.group('fqcn')
1841*c2e18aaaSAndroid Build Coastguard Worker        # Search annotation class from keyword.
1842*c2e18aaaSAndroid Build Coastguard Worker        match = keyword_re.match(line)
1843*c2e18aaaSAndroid Build Coastguard Worker        if match:
1844*c2e18aaaSAndroid Build Coastguard Worker          return match.group('fqcn')
1845*c2e18aaaSAndroid Build Coastguard Worker  return ''
1846*c2e18aaaSAndroid Build Coastguard Worker
1847*c2e18aaaSAndroid Build Coastguard Worker
1848*c2e18aaaSAndroid Build Coastguard Workerdef has_mixed_type_filters(test_infos):
1849*c2e18aaaSAndroid Build Coastguard Worker  """There are different types in a test module.
1850*c2e18aaaSAndroid Build Coastguard Worker
1851*c2e18aaaSAndroid Build Coastguard Worker  Dict test_to_types is mapping module name and the set of types.
1852*c2e18aaaSAndroid Build Coastguard Worker  For example,
1853*c2e18aaaSAndroid Build Coastguard Worker  {
1854*c2e18aaaSAndroid Build Coastguard Worker      'module_1': {'wildcard class_method'},
1855*c2e18aaaSAndroid Build Coastguard Worker      'module_2': {'wildcard class_method', 'regular class_method'},
1856*c2e18aaaSAndroid Build Coastguard Worker      'module_3': set()
1857*c2e18aaaSAndroid Build Coastguard Worker      }
1858*c2e18aaaSAndroid Build Coastguard Worker
1859*c2e18aaaSAndroid Build Coastguard Worker  Args:
1860*c2e18aaaSAndroid Build Coastguard Worker      test_infos: A set of TestInfos.
1861*c2e18aaaSAndroid Build Coastguard Worker
1862*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1863*c2e18aaaSAndroid Build Coastguard Worker      True if more than one filter type in a test module, False otherwise.
1864*c2e18aaaSAndroid Build Coastguard Worker  """
1865*c2e18aaaSAndroid Build Coastguard Worker  test_to_types = {}
1866*c2e18aaaSAndroid Build Coastguard Worker  for test_info in test_infos:
1867*c2e18aaaSAndroid Build Coastguard Worker    filters = test_info.data.get(constants.TI_FILTER, [])
1868*c2e18aaaSAndroid Build Coastguard Worker    filter_types = set()
1869*c2e18aaaSAndroid Build Coastguard Worker    for flt in filters:
1870*c2e18aaaSAndroid Build Coastguard Worker      filter_types |= get_filter_types(flt.to_list_of_tf_strings())
1871*c2e18aaaSAndroid Build Coastguard Worker    filter_types |= test_to_types.get(test_info.test_name, set())
1872*c2e18aaaSAndroid Build Coastguard Worker    test_to_types[test_info.test_name] = filter_types
1873*c2e18aaaSAndroid Build Coastguard Worker  for _, types in test_to_types.items():
1874*c2e18aaaSAndroid Build Coastguard Worker    if len(types) > 1:
1875*c2e18aaaSAndroid Build Coastguard Worker      return True
1876*c2e18aaaSAndroid Build Coastguard Worker  return False
1877*c2e18aaaSAndroid Build Coastguard Worker
1878*c2e18aaaSAndroid Build Coastguard Worker
1879*c2e18aaaSAndroid Build Coastguard Workerdef get_filter_types(tf_filter_set):
1880*c2e18aaaSAndroid Build Coastguard Worker  """Get filter types.
1881*c2e18aaaSAndroid Build Coastguard Worker
1882*c2e18aaaSAndroid Build Coastguard Worker  Args:
1883*c2e18aaaSAndroid Build Coastguard Worker      tf_filter_set: A list of tf filter strings.
1884*c2e18aaaSAndroid Build Coastguard Worker
1885*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1886*c2e18aaaSAndroid Build Coastguard Worker      A set of FilterType.
1887*c2e18aaaSAndroid Build Coastguard Worker  """
1888*c2e18aaaSAndroid Build Coastguard Worker  type_set = set()
1889*c2e18aaaSAndroid Build Coastguard Worker  for tf_filter in tf_filter_set:
1890*c2e18aaaSAndroid Build Coastguard Worker    if _WILDCARD_FILTER_RE.match(tf_filter):
1891*c2e18aaaSAndroid Build Coastguard Worker      logging.debug(
1892*c2e18aaaSAndroid Build Coastguard Worker          'Filter and type: (%s, %s)',
1893*c2e18aaaSAndroid Build Coastguard Worker          tf_filter,
1894*c2e18aaaSAndroid Build Coastguard Worker          FilterType.WILDCARD_FILTER.value,
1895*c2e18aaaSAndroid Build Coastguard Worker      )
1896*c2e18aaaSAndroid Build Coastguard Worker      type_set.add(FilterType.WILDCARD_FILTER.value)
1897*c2e18aaaSAndroid Build Coastguard Worker    if _REGULAR_FILTER_RE.match(tf_filter):
1898*c2e18aaaSAndroid Build Coastguard Worker      logging.debug(
1899*c2e18aaaSAndroid Build Coastguard Worker          'Filter and type: (%s, %s)',
1900*c2e18aaaSAndroid Build Coastguard Worker          tf_filter,
1901*c2e18aaaSAndroid Build Coastguard Worker          FilterType.REGULAR_FILTER.value,
1902*c2e18aaaSAndroid Build Coastguard Worker      )
1903*c2e18aaaSAndroid Build Coastguard Worker      type_set.add(FilterType.REGULAR_FILTER.value)
1904*c2e18aaaSAndroid Build Coastguard Worker  return type_set
1905*c2e18aaaSAndroid Build Coastguard Worker
1906*c2e18aaaSAndroid Build Coastguard Worker
1907*c2e18aaaSAndroid Build Coastguard Workerdef has_command(cmd: str) -> bool:
1908*c2e18aaaSAndroid Build Coastguard Worker  """Detect if the command is available in PATH.
1909*c2e18aaaSAndroid Build Coastguard Worker
1910*c2e18aaaSAndroid Build Coastguard Worker  Args:
1911*c2e18aaaSAndroid Build Coastguard Worker      cmd: A string of the tested command.
1912*c2e18aaaSAndroid Build Coastguard Worker
1913*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1914*c2e18aaaSAndroid Build Coastguard Worker      True if found, False otherwise.
1915*c2e18aaaSAndroid Build Coastguard Worker  """
1916*c2e18aaaSAndroid Build Coastguard Worker  return bool(shutil.which(cmd))
1917*c2e18aaaSAndroid Build Coastguard Worker
1918*c2e18aaaSAndroid Build Coastguard Worker
1919*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=anomalous-backslash-in-string,too-many-branches
1920*c2e18aaaSAndroid Build Coastguard Workerdef get_bp_content(filename: Path, module_type: str) -> Dict:
1921*c2e18aaaSAndroid Build Coastguard Worker  """Get essential content info from an Android.bp.
1922*c2e18aaaSAndroid Build Coastguard Worker
1923*c2e18aaaSAndroid Build Coastguard Worker  By specifying module_type (e.g. 'android_test', 'android_app'), this method
1924*c2e18aaaSAndroid Build Coastguard Worker  can parse the given starting point and grab 'name', 'instrumentation_for' and
1925*c2e18aaaSAndroid Build Coastguard Worker  'manifest'.
1926*c2e18aaaSAndroid Build Coastguard Worker
1927*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1928*c2e18aaaSAndroid Build Coastguard Worker      A dict of mapping test module and target module; e.g.
1929*c2e18aaaSAndroid Build Coastguard Worker      {
1930*c2e18aaaSAndroid Build Coastguard Worker       'FooUnitTests':
1931*c2e18aaaSAndroid Build Coastguard Worker           {'manifest': 'AndroidManifest.xml', 'target_module': 'Foo'},
1932*c2e18aaaSAndroid Build Coastguard Worker       'Foo':
1933*c2e18aaaSAndroid Build Coastguard Worker           {'manifest': 'AndroidManifest-common.xml', 'target_module': ''}
1934*c2e18aaaSAndroid Build Coastguard Worker      }
1935*c2e18aaaSAndroid Build Coastguard Worker      Null dict if there is no content of the given module_type.
1936*c2e18aaaSAndroid Build Coastguard Worker  """
1937*c2e18aaaSAndroid Build Coastguard Worker  build_file = Path(filename)
1938*c2e18aaaSAndroid Build Coastguard Worker  if not any((build_file.suffix == '.bp', build_file.is_file())):
1939*c2e18aaaSAndroid Build Coastguard Worker    return {}
1940*c2e18aaaSAndroid Build Coastguard Worker  start_from = re.compile(f'^{module_type}\s*\{{')
1941*c2e18aaaSAndroid Build Coastguard Worker  end_with = re.compile(r'^\}$')
1942*c2e18aaaSAndroid Build Coastguard Worker  context_re = re.compile(
1943*c2e18aaaSAndroid Build Coastguard Worker      r'\s*(?P<key>(name|manifest|instrumentation_for))\s*:'
1944*c2e18aaaSAndroid Build Coastguard Worker      r'\s*\"(?P<value>.*)\"\s*,',
1945*c2e18aaaSAndroid Build Coastguard Worker      re.M,
1946*c2e18aaaSAndroid Build Coastguard Worker  )
1947*c2e18aaaSAndroid Build Coastguard Worker  with open(build_file, 'r', encoding='utf-8') as cache:
1948*c2e18aaaSAndroid Build Coastguard Worker    data = cache.readlines()
1949*c2e18aaaSAndroid Build Coastguard Worker  content_dict = {}
1950*c2e18aaaSAndroid Build Coastguard Worker  start_recording = False
1951*c2e18aaaSAndroid Build Coastguard Worker  for _line in data:
1952*c2e18aaaSAndroid Build Coastguard Worker    line = _line.strip()
1953*c2e18aaaSAndroid Build Coastguard Worker    if re.match(start_from, line):
1954*c2e18aaaSAndroid Build Coastguard Worker      start_recording = True
1955*c2e18aaaSAndroid Build Coastguard Worker      _dict = {}
1956*c2e18aaaSAndroid Build Coastguard Worker      continue
1957*c2e18aaaSAndroid Build Coastguard Worker    if start_recording:
1958*c2e18aaaSAndroid Build Coastguard Worker      if not re.match(end_with, line):
1959*c2e18aaaSAndroid Build Coastguard Worker        match = re.match(context_re, line)
1960*c2e18aaaSAndroid Build Coastguard Worker        if match:
1961*c2e18aaaSAndroid Build Coastguard Worker          _dict.update({match.group('key'): match.group('value')})
1962*c2e18aaaSAndroid Build Coastguard Worker      else:
1963*c2e18aaaSAndroid Build Coastguard Worker        start_recording = False
1964*c2e18aaaSAndroid Build Coastguard Worker        module_name = _dict.get('name')
1965*c2e18aaaSAndroid Build Coastguard Worker        if module_name:
1966*c2e18aaaSAndroid Build Coastguard Worker          content_dict.update({
1967*c2e18aaaSAndroid Build Coastguard Worker              module_name: {
1968*c2e18aaaSAndroid Build Coastguard Worker                  'manifest': _dict.get('manifest', 'AndroidManifest.xml'),
1969*c2e18aaaSAndroid Build Coastguard Worker                  'target_module': _dict.get('instrumentation_for', ''),
1970*c2e18aaaSAndroid Build Coastguard Worker              }
1971*c2e18aaaSAndroid Build Coastguard Worker          })
1972*c2e18aaaSAndroid Build Coastguard Worker  return content_dict
1973*c2e18aaaSAndroid Build Coastguard Worker
1974*c2e18aaaSAndroid Build Coastguard Worker
1975*c2e18aaaSAndroid Build Coastguard Workerdef get_manifest_info(manifest: Path) -> Dict[str, Any]:
1976*c2e18aaaSAndroid Build Coastguard Worker  """Get the essential info from the given manifest file.
1977*c2e18aaaSAndroid Build Coastguard Worker
1978*c2e18aaaSAndroid Build Coastguard Worker  This method cares only three attributes:
1979*c2e18aaaSAndroid Build Coastguard Worker
1980*c2e18aaaSAndroid Build Coastguard Worker      * package
1981*c2e18aaaSAndroid Build Coastguard Worker      * targetPackage
1982*c2e18aaaSAndroid Build Coastguard Worker      * persistent
1983*c2e18aaaSAndroid Build Coastguard Worker  For an instrumentation test, the result will be like:
1984*c2e18aaaSAndroid Build Coastguard Worker  {
1985*c2e18aaaSAndroid Build Coastguard Worker      'package': 'com.android.foo.tests.unit',
1986*c2e18aaaSAndroid Build Coastguard Worker      'targetPackage': 'com.android.foo',
1987*c2e18aaaSAndroid Build Coastguard Worker      'persistent': False
1988*c2e18aaaSAndroid Build Coastguard Worker  }
1989*c2e18aaaSAndroid Build Coastguard Worker  For a target module of the instrumentation test:
1990*c2e18aaaSAndroid Build Coastguard Worker  {
1991*c2e18aaaSAndroid Build Coastguard Worker      'package': 'com.android.foo',
1992*c2e18aaaSAndroid Build Coastguard Worker      'targetPackage': '',
1993*c2e18aaaSAndroid Build Coastguard Worker      'persistent': True
1994*c2e18aaaSAndroid Build Coastguard Worker  }
1995*c2e18aaaSAndroid Build Coastguard Worker  """
1996*c2e18aaaSAndroid Build Coastguard Worker  mdict = {'package': '', 'target_package': '', 'persistent': False}
1997*c2e18aaaSAndroid Build Coastguard Worker  try:
1998*c2e18aaaSAndroid Build Coastguard Worker    xml_root = ET.parse(manifest).getroot()
1999*c2e18aaaSAndroid Build Coastguard Worker  except (ET.ParseError, FileNotFoundError):
2000*c2e18aaaSAndroid Build Coastguard Worker    return mdict
2001*c2e18aaaSAndroid Build Coastguard Worker  manifest_package_re = re.compile(r'[a-z][\w]+(\.[\w]+)*')
2002*c2e18aaaSAndroid Build Coastguard Worker  # 1. Must probe 'package' name from the top.
2003*c2e18aaaSAndroid Build Coastguard Worker  for item in xml_root.findall('.'):
2004*c2e18aaaSAndroid Build Coastguard Worker    if 'package' in item.attrib.keys():
2005*c2e18aaaSAndroid Build Coastguard Worker      pkg = item.attrib.get('package')
2006*c2e18aaaSAndroid Build Coastguard Worker      match = manifest_package_re.match(pkg)
2007*c2e18aaaSAndroid Build Coastguard Worker      if match:
2008*c2e18aaaSAndroid Build Coastguard Worker        mdict['package'] = pkg
2009*c2e18aaaSAndroid Build Coastguard Worker        break
2010*c2e18aaaSAndroid Build Coastguard Worker  for item in xml_root.findall('*'):
2011*c2e18aaaSAndroid Build Coastguard Worker    # 2. Probe 'targetPackage' in 'instrumentation' tag.
2012*c2e18aaaSAndroid Build Coastguard Worker    if item.tag == 'instrumentation':
2013*c2e18aaaSAndroid Build Coastguard Worker      for key, value in item.attrib.items():
2014*c2e18aaaSAndroid Build Coastguard Worker        if 'targetPackage' in key:
2015*c2e18aaaSAndroid Build Coastguard Worker          mdict['target_package'] = value
2016*c2e18aaaSAndroid Build Coastguard Worker          break
2017*c2e18aaaSAndroid Build Coastguard Worker    # 3. Probe 'persistent' in any tags.
2018*c2e18aaaSAndroid Build Coastguard Worker    for key, value in item.attrib.items():
2019*c2e18aaaSAndroid Build Coastguard Worker      if 'persistent' in key:
2020*c2e18aaaSAndroid Build Coastguard Worker        mdict['persistent'] = value.lower() == 'true'
2021*c2e18aaaSAndroid Build Coastguard Worker        break
2022*c2e18aaaSAndroid Build Coastguard Worker  return mdict
2023*c2e18aaaSAndroid Build Coastguard Worker
2024*c2e18aaaSAndroid Build Coastguard Worker
2025*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=broad-except
2026*c2e18aaaSAndroid Build Coastguard Workerdef generate_result_html(result_file: Path) -> Path:
2027*c2e18aaaSAndroid Build Coastguard Worker  """Generate a html that collects all log files."""
2028*c2e18aaaSAndroid Build Coastguard Worker  result_file = Path(result_file)
2029*c2e18aaaSAndroid Build Coastguard Worker  search_dir = Path(result_file).parent
2030*c2e18aaaSAndroid Build Coastguard Worker  result_html = Path(result_file.parent, 'local_log_file_list.html')
2031*c2e18aaaSAndroid Build Coastguard Worker  try:
2032*c2e18aaaSAndroid Build Coastguard Worker    logs = sorted(find_files(str(search_dir), file_name='*', followlinks=True))
2033*c2e18aaaSAndroid Build Coastguard Worker    with open(result_html, 'w', encoding='utf-8') as cache:
2034*c2e18aaaSAndroid Build Coastguard Worker      cache.write('<!DOCTYPE html><html><body>')
2035*c2e18aaaSAndroid Build Coastguard Worker      result = load_json_safely(result_file)
2036*c2e18aaaSAndroid Build Coastguard Worker      if result:
2037*c2e18aaaSAndroid Build Coastguard Worker        cache.write(f'<h1>{"atest " + result.get("args")}</h1>')
2038*c2e18aaaSAndroid Build Coastguard Worker        timestamp = datetime.datetime.fromtimestamp(result_file.stat().st_ctime)
2039*c2e18aaaSAndroid Build Coastguard Worker        cache.write(f'<h2>{timestamp}</h2>')
2040*c2e18aaaSAndroid Build Coastguard Worker      for log in logs:
2041*c2e18aaaSAndroid Build Coastguard Worker        cache.write(
2042*c2e18aaaSAndroid Build Coastguard Worker            f'<p><a href="{urllib.parse.quote(log)}">'
2043*c2e18aaaSAndroid Build Coastguard Worker            f'{html.escape(Path(log).relative_to(search_dir).as_posix())}</a></p>'
2044*c2e18aaaSAndroid Build Coastguard Worker        )
2045*c2e18aaaSAndroid Build Coastguard Worker      cache.write('</body></html>')
2046*c2e18aaaSAndroid Build Coastguard Worker    send_tradeded_elapsed_time_metric(search_dir)
2047*c2e18aaaSAndroid Build Coastguard Worker    return result_html
2048*c2e18aaaSAndroid Build Coastguard Worker  except Exception as e:
2049*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('Did not generate log html for reason: %s', e)
2050*c2e18aaaSAndroid Build Coastguard Worker    return None
2051*c2e18aaaSAndroid Build Coastguard Worker
2052*c2e18aaaSAndroid Build Coastguard Worker
2053*c2e18aaaSAndroid Build Coastguard Workerdef send_tradeded_elapsed_time_metric(search_dir: Path):
2054*c2e18aaaSAndroid Build Coastguard Worker  """Method which sends Tradefed elapsed time to the metrics."""
2055*c2e18aaaSAndroid Build Coastguard Worker  test, prep, teardown = get_tradefed_invocation_time(search_dir)
2056*c2e18aaaSAndroid Build Coastguard Worker  metrics.LocalDetectEvent(
2057*c2e18aaaSAndroid Build Coastguard Worker      detect_type=DetectType.TF_TOTAL_RUN_MS, result=test + prep + teardown
2058*c2e18aaaSAndroid Build Coastguard Worker  )
2059*c2e18aaaSAndroid Build Coastguard Worker  metrics.LocalDetectEvent(
2060*c2e18aaaSAndroid Build Coastguard Worker      detect_type=DetectType.TF_PREPARATION_MS, result=prep
2061*c2e18aaaSAndroid Build Coastguard Worker  )
2062*c2e18aaaSAndroid Build Coastguard Worker  metrics.LocalDetectEvent(detect_type=DetectType.TF_TEST_MS, result=test)
2063*c2e18aaaSAndroid Build Coastguard Worker  metrics.LocalDetectEvent(
2064*c2e18aaaSAndroid Build Coastguard Worker      detect_type=DetectType.TF_TEARDOWN_MS, result=teardown
2065*c2e18aaaSAndroid Build Coastguard Worker  )
2066*c2e18aaaSAndroid Build Coastguard Worker
2067*c2e18aaaSAndroid Build Coastguard Worker
2068*c2e18aaaSAndroid Build Coastguard Workerdef get_tradefed_invocation_time(search_dir: Path) -> Tuple[int, int, int]:
2069*c2e18aaaSAndroid Build Coastguard Worker  """Return a tuple of testing, preparation and teardown time."""
2070*c2e18aaaSAndroid Build Coastguard Worker  test, prep, teardown = 0, 0, 0
2071*c2e18aaaSAndroid Build Coastguard Worker  end_host_log_files = find_files(
2072*c2e18aaaSAndroid Build Coastguard Worker      path=search_dir, file_name='end_host_log_*.txt', followlinks=True
2073*c2e18aaaSAndroid Build Coastguard Worker  )
2074*c2e18aaaSAndroid Build Coastguard Worker  for log in end_host_log_files:
2075*c2e18aaaSAndroid Build Coastguard Worker    with open(log, 'r', encoding='utf-8') as cache:
2076*c2e18aaaSAndroid Build Coastguard Worker      contents = cache.read().splitlines()
2077*c2e18aaaSAndroid Build Coastguard Worker
2078*c2e18aaaSAndroid Build Coastguard Worker    parse_test_time, parse_prep_time = False, False
2079*c2e18aaaSAndroid Build Coastguard Worker    # ============================================
2080*c2e18aaaSAndroid Build Coastguard Worker    # ================= Results ==================
2081*c2e18aaaSAndroid Build Coastguard Worker    # =============== Consumed Time ==============
2082*c2e18aaaSAndroid Build Coastguard Worker    #     x86_64 HelloWorldTests: 1s
2083*c2e18aaaSAndroid Build Coastguard Worker    #     x86_64 hallo-welt: 866 ms
2084*c2e18aaaSAndroid Build Coastguard Worker    # Total aggregated tests run time: 1s
2085*c2e18aaaSAndroid Build Coastguard Worker    # ============== Modules Preparation Times ==============
2086*c2e18aaaSAndroid Build Coastguard Worker    #     x86_64 HelloWorldTests => prep = 2483 ms || clean = 294 ms
2087*c2e18aaaSAndroid Build Coastguard Worker    #     x86_64 hallo-welt => prep = 1845 ms || clean = 292 ms
2088*c2e18aaaSAndroid Build Coastguard Worker    # Total preparation time: 4s  ||  Total tear down time: 586 ms
2089*c2e18aaaSAndroid Build Coastguard Worker    # =======================================================
2090*c2e18aaaSAndroid Build Coastguard Worker    # =============== Summary ===============
2091*c2e18aaaSAndroid Build Coastguard Worker    # Total Run time: 6s
2092*c2e18aaaSAndroid Build Coastguard Worker    # 2/2 modules completed
2093*c2e18aaaSAndroid Build Coastguard Worker    # Total Tests       : 3
2094*c2e18aaaSAndroid Build Coastguard Worker    # PASSED            : 3
2095*c2e18aaaSAndroid Build Coastguard Worker    # FAILED            : 0
2096*c2e18aaaSAndroid Build Coastguard Worker    # ============== End of Results ==============
2097*c2e18aaaSAndroid Build Coastguard Worker    # ============================================
2098*c2e18aaaSAndroid Build Coastguard Worker    for line in contents:
2099*c2e18aaaSAndroid Build Coastguard Worker      if re.match(r'[=]+.*consumed.*time.*[=]+', line, re.I):
2100*c2e18aaaSAndroid Build Coastguard Worker        parse_test_time, parse_prep_time = True, False
2101*c2e18aaaSAndroid Build Coastguard Worker        continue
2102*c2e18aaaSAndroid Build Coastguard Worker      if re.match(r'[=]+.*preparation.*time.*[=]+', line, re.I):
2103*c2e18aaaSAndroid Build Coastguard Worker        parse_test_time, parse_prep_time = False, True
2104*c2e18aaaSAndroid Build Coastguard Worker        continue
2105*c2e18aaaSAndroid Build Coastguard Worker      # Close parsing when `Total` keyword starts at the beginning.
2106*c2e18aaaSAndroid Build Coastguard Worker      if re.match(r'^(Total.*)', line, re.I):
2107*c2e18aaaSAndroid Build Coastguard Worker        parse_test_time, parse_prep_time = False, False
2108*c2e18aaaSAndroid Build Coastguard Worker        continue
2109*c2e18aaaSAndroid Build Coastguard Worker      if parse_test_time:
2110*c2e18aaaSAndroid Build Coastguard Worker        match = re.search(r'^[\s]+\w.*:\s+(?P<timestr>.*)$', line, re.I)
2111*c2e18aaaSAndroid Build Coastguard Worker        if match:
2112*c2e18aaaSAndroid Build Coastguard Worker          test += convert_timestr_to_ms(match.group('timestr'))
2113*c2e18aaaSAndroid Build Coastguard Worker        continue
2114*c2e18aaaSAndroid Build Coastguard Worker      if parse_prep_time:
2115*c2e18aaaSAndroid Build Coastguard Worker        # SuiteResultReporter.java defines elapsed prep time only in ms.
2116*c2e18aaaSAndroid Build Coastguard Worker        match = re.search(
2117*c2e18aaaSAndroid Build Coastguard Worker            r'prep = (?P<prep>\d+ ms) \|\| clean = (?P<clean>\d+ ms)$',
2118*c2e18aaaSAndroid Build Coastguard Worker            line,
2119*c2e18aaaSAndroid Build Coastguard Worker            re.I,
2120*c2e18aaaSAndroid Build Coastguard Worker        )
2121*c2e18aaaSAndroid Build Coastguard Worker        if match:
2122*c2e18aaaSAndroid Build Coastguard Worker          prep += convert_timestr_to_ms(match.group('prep'))
2123*c2e18aaaSAndroid Build Coastguard Worker          teardown += convert_timestr_to_ms(match.group('clean'))
2124*c2e18aaaSAndroid Build Coastguard Worker        continue
2125*c2e18aaaSAndroid Build Coastguard Worker
2126*c2e18aaaSAndroid Build Coastguard Worker  return test, prep, teardown
2127*c2e18aaaSAndroid Build Coastguard Worker
2128*c2e18aaaSAndroid Build Coastguard Worker
2129*c2e18aaaSAndroid Build Coastguard Workerdef convert_timestr_to_ms(time_string: str = None) -> int:
2130*c2e18aaaSAndroid Build Coastguard Worker  """Convert time string to an integer in millisecond.
2131*c2e18aaaSAndroid Build Coastguard Worker
2132*c2e18aaaSAndroid Build Coastguard Worker  Possible time strings are:
2133*c2e18aaaSAndroid Build Coastguard Worker      1h 21m 15s
2134*c2e18aaaSAndroid Build Coastguard Worker      1m 5s
2135*c2e18aaaSAndroid Build Coastguard Worker      25s
2136*c2e18aaaSAndroid Build Coastguard Worker  If elapsed time is less than 1 sec, the time will be in millisecond.
2137*c2e18aaaSAndroid Build Coastguard Worker      233 ms
2138*c2e18aaaSAndroid Build Coastguard Worker  """
2139*c2e18aaaSAndroid Build Coastguard Worker  if not time_string:
2140*c2e18aaaSAndroid Build Coastguard Worker    return 0
2141*c2e18aaaSAndroid Build Coastguard Worker
2142*c2e18aaaSAndroid Build Coastguard Worker  hours, minutes, seconds = 0, 0, 0
2143*c2e18aaaSAndroid Build Coastguard Worker  # Extract hour(<h>), minute(<m>), second(<s>), or millisecond(<ms>).
2144*c2e18aaaSAndroid Build Coastguard Worker  match = re.match(
2145*c2e18aaaSAndroid Build Coastguard Worker      r'(((?P<h>\d+)h\s+)?(?P<m>\d+)m\s+)?(?P<s>\d+)s|(?P<ms>\d+)\s*ms',
2146*c2e18aaaSAndroid Build Coastguard Worker      time_string,
2147*c2e18aaaSAndroid Build Coastguard Worker  )
2148*c2e18aaaSAndroid Build Coastguard Worker  if match:
2149*c2e18aaaSAndroid Build Coastguard Worker    hours = int(match.group('h')) if match.group('h') else 0
2150*c2e18aaaSAndroid Build Coastguard Worker    minutes = int(match.group('m')) if match.group('m') else 0
2151*c2e18aaaSAndroid Build Coastguard Worker    seconds = int(match.group('s')) if match.group('s') else 0
2152*c2e18aaaSAndroid Build Coastguard Worker    milliseconds = int(match.group('ms')) if match.group('ms') else 0
2153*c2e18aaaSAndroid Build Coastguard Worker
2154*c2e18aaaSAndroid Build Coastguard Worker  return (
2155*c2e18aaaSAndroid Build Coastguard Worker      hours * 3600 * 1000 + minutes * 60 * 1000 + seconds * 1000 + milliseconds
2156*c2e18aaaSAndroid Build Coastguard Worker  )
2157*c2e18aaaSAndroid Build Coastguard Worker
2158*c2e18aaaSAndroid Build Coastguard Worker
2159*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=broad-except
2160*c2e18aaaSAndroid Build Coastguard Workerdef prompt_suggestions(result_file: Path):
2161*c2e18aaaSAndroid Build Coastguard Worker  """Generate suggestions when detecting keywords in logs."""
2162*c2e18aaaSAndroid Build Coastguard Worker  result_file = Path(result_file)
2163*c2e18aaaSAndroid Build Coastguard Worker  search_dir = Path(result_file).parent.joinpath('log')
2164*c2e18aaaSAndroid Build Coastguard Worker  logs = sorted(find_files(str(search_dir), file_name='*'))
2165*c2e18aaaSAndroid Build Coastguard Worker  for log in logs:
2166*c2e18aaaSAndroid Build Coastguard Worker    for keyword, suggestion in SUGGESTIONS.items():
2167*c2e18aaaSAndroid Build Coastguard Worker      try:
2168*c2e18aaaSAndroid Build Coastguard Worker        with open(log, 'r', encoding='utf-8') as cache:
2169*c2e18aaaSAndroid Build Coastguard Worker          content = cache.read()
2170*c2e18aaaSAndroid Build Coastguard Worker          if keyword in content:
2171*c2e18aaaSAndroid Build Coastguard Worker            colorful_print('[Suggestion] ' + suggestion, color=constants.RED)
2172*c2e18aaaSAndroid Build Coastguard Worker            break
2173*c2e18aaaSAndroid Build Coastguard Worker      # If the given is not a plain text, just ignore it.
2174*c2e18aaaSAndroid Build Coastguard Worker      except Exception:
2175*c2e18aaaSAndroid Build Coastguard Worker        pass
2176*c2e18aaaSAndroid Build Coastguard Worker
2177*c2e18aaaSAndroid Build Coastguard Worker
2178*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=invalid-name
2179*c2e18aaaSAndroid Build Coastguard Workerdef get_rbe_and_customized_out_state() -> int:
2180*c2e18aaaSAndroid Build Coastguard Worker  """Return decimal state of RBE and customized out.
2181*c2e18aaaSAndroid Build Coastguard Worker
2182*c2e18aaaSAndroid Build Coastguard Worker  Customizing out dir (OUT_DIR/OUT_DIR_COMMON_BASE) dramatically slows down
2183*c2e18aaaSAndroid Build Coastguard Worker  the RBE performance; by collecting the combined state of the two states,
2184*c2e18aaaSAndroid Build Coastguard Worker  we can profile the performance relationship between RBE and the build time.
2185*c2e18aaaSAndroid Build Coastguard Worker
2186*c2e18aaaSAndroid Build Coastguard Worker  Returns:
2187*c2e18aaaSAndroid Build Coastguard Worker      An integer that describes the combined state.
2188*c2e18aaaSAndroid Build Coastguard Worker  """
2189*c2e18aaaSAndroid Build Coastguard Worker  #    RBE  | out_dir |  decimal
2190*c2e18aaaSAndroid Build Coastguard Worker  # --------+---------+---------
2191*c2e18aaaSAndroid Build Coastguard Worker  #     0   |    0    |    0
2192*c2e18aaaSAndroid Build Coastguard Worker  #     0   |    1    |    1
2193*c2e18aaaSAndroid Build Coastguard Worker  #     1   |    0    |    2
2194*c2e18aaaSAndroid Build Coastguard Worker  #     1   |    1    |    3    --> Caution for poor performance.
2195*c2e18aaaSAndroid Build Coastguard Worker  ON = '1'
2196*c2e18aaaSAndroid Build Coastguard Worker  OFF = '0'
2197*c2e18aaaSAndroid Build Coastguard Worker  # 1. ensure RBE is enabled during the build.
2198*c2e18aaaSAndroid Build Coastguard Worker  actual_out_dir = get_build_out_dir()
2199*c2e18aaaSAndroid Build Coastguard Worker  log_path = actual_out_dir.joinpath('soong.log')
2200*c2e18aaaSAndroid Build Coastguard Worker  rbe_enabled = not bool(
2201*c2e18aaaSAndroid Build Coastguard Worker      subprocess.call(f'grep -q USE_RBE=true {log_path}'.split())
2202*c2e18aaaSAndroid Build Coastguard Worker  )
2203*c2e18aaaSAndroid Build Coastguard Worker  rbe_state = ON if rbe_enabled else OFF
2204*c2e18aaaSAndroid Build Coastguard Worker
2205*c2e18aaaSAndroid Build Coastguard Worker  # 2. The customized out path will be different from the regular one.
2206*c2e18aaaSAndroid Build Coastguard Worker  regular_out_dir = Path(os.getenv(constants.ANDROID_BUILD_TOP), 'out')
2207*c2e18aaaSAndroid Build Coastguard Worker  customized_out = OFF if actual_out_dir == regular_out_dir else ON
2208*c2e18aaaSAndroid Build Coastguard Worker
2209*c2e18aaaSAndroid Build Coastguard Worker  return int(rbe_state + customized_out, 2)
2210*c2e18aaaSAndroid Build Coastguard Worker
2211*c2e18aaaSAndroid Build Coastguard Worker
2212*c2e18aaaSAndroid Build Coastguard Workerdef build_files_integrity_is_ok() -> bool:
2213*c2e18aaaSAndroid Build Coastguard Worker  """Return Whether the integrity of build files is OK."""
2214*c2e18aaaSAndroid Build Coastguard Worker  # 0. Missing timestamp file or plocate.db means a fresh repo sync.
2215*c2e18aaaSAndroid Build Coastguard Worker  timestamp_file = get_index_path(constants.BUILDFILES_STP)
2216*c2e18aaaSAndroid Build Coastguard Worker  locate_cache = get_index_path(constants.LOCATE_CACHE)
2217*c2e18aaaSAndroid Build Coastguard Worker  if not timestamp_file.is_file():
2218*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('timestamp_file %s is missing', timestamp_file)
2219*c2e18aaaSAndroid Build Coastguard Worker    return False
2220*c2e18aaaSAndroid Build Coastguard Worker  if not locate_cache.is_file():
2221*c2e18aaaSAndroid Build Coastguard Worker    logging.debug('locate_cache file %s is missing', locate_cache)
2222*c2e18aaaSAndroid Build Coastguard Worker    return False
2223*c2e18aaaSAndroid Build Coastguard Worker
2224*c2e18aaaSAndroid Build Coastguard Worker  # 1. Ensure no build files were added/deleted.
2225*c2e18aaaSAndroid Build Coastguard Worker  recorded_amount = len(load_json_safely(timestamp_file).keys())
2226*c2e18aaaSAndroid Build Coastguard Worker  cmd_out = subprocess.getoutput(
2227*c2e18aaaSAndroid Build Coastguard Worker      f'locate -e -d{locate_cache} --regex ' r'"/Android\.(bp|mk)$" | wc -l'
2228*c2e18aaaSAndroid Build Coastguard Worker  )
2229*c2e18aaaSAndroid Build Coastguard Worker  if int(cmd_out) != recorded_amount:
2230*c2e18aaaSAndroid Build Coastguard Worker    logging.debug(
2231*c2e18aaaSAndroid Build Coastguard Worker        'Some build files are added/deleted. Recorded number of files: %s,'
2232*c2e18aaaSAndroid Build Coastguard Worker        ' actual: %s',
2233*c2e18aaaSAndroid Build Coastguard Worker        recorded_amount,
2234*c2e18aaaSAndroid Build Coastguard Worker        cmd_out,
2235*c2e18aaaSAndroid Build Coastguard Worker    )
2236*c2e18aaaSAndroid Build Coastguard Worker    return False
2237*c2e18aaaSAndroid Build Coastguard Worker
2238*c2e18aaaSAndroid Build Coastguard Worker  # 2. Ensure the consistency of all build files.
2239*c2e18aaaSAndroid Build Coastguard Worker  for file, timestamp in load_json_safely(timestamp_file).items():
2240*c2e18aaaSAndroid Build Coastguard Worker    if Path(file).exists() and Path(file).stat().st_mtime != timestamp:
2241*c2e18aaaSAndroid Build Coastguard Worker      logging.debug(
2242*c2e18aaaSAndroid Build Coastguard Worker          'A build file is changed: %s. Recorded timestamp: %s, actual'
2243*c2e18aaaSAndroid Build Coastguard Worker          ' timestamp: %s',
2244*c2e18aaaSAndroid Build Coastguard Worker          file,
2245*c2e18aaaSAndroid Build Coastguard Worker          timestamp,
2246*c2e18aaaSAndroid Build Coastguard Worker          Path(file).stat().st_mtime,
2247*c2e18aaaSAndroid Build Coastguard Worker      )
2248*c2e18aaaSAndroid Build Coastguard Worker      return False
2249*c2e18aaaSAndroid Build Coastguard Worker  return True
2250*c2e18aaaSAndroid Build Coastguard Worker
2251*c2e18aaaSAndroid Build Coastguard Worker
2252*c2e18aaaSAndroid Build Coastguard Workerdef _build_env_profiling() -> BuildEnvProfiler:
2253*c2e18aaaSAndroid Build Coastguard Worker  """Determine the status profile before build.
2254*c2e18aaaSAndroid Build Coastguard Worker
2255*c2e18aaaSAndroid Build Coastguard Worker  The BuildEnvProfiler object can help use determine whether a build is:
2256*c2e18aaaSAndroid Build Coastguard Worker      1. clean build. (empty out/ dir)
2257*c2e18aaaSAndroid Build Coastguard Worker      2. Build files Integrity (Android.bp/Android.mk changes).
2258*c2e18aaaSAndroid Build Coastguard Worker      3. Environment variables consistency.
2259*c2e18aaaSAndroid Build Coastguard Worker      4. New Ninja file generated. (mtime of soong/build.ninja)
2260*c2e18aaaSAndroid Build Coastguard Worker
2261*c2e18aaaSAndroid Build Coastguard Worker  Returns:
2262*c2e18aaaSAndroid Build Coastguard Worker      the BuildProfile object.
2263*c2e18aaaSAndroid Build Coastguard Worker  """
2264*c2e18aaaSAndroid Build Coastguard Worker  out_dir = get_build_out_dir()
2265*c2e18aaaSAndroid Build Coastguard Worker  ninja_file = out_dir.joinpath('soong/build.ninja')
2266*c2e18aaaSAndroid Build Coastguard Worker  mtime = ninja_file.stat().st_mtime if ninja_file.is_file() else 0
2267*c2e18aaaSAndroid Build Coastguard Worker  variables_file = out_dir.joinpath('soong/soong.environment.used.build')
2268*c2e18aaaSAndroid Build Coastguard Worker
2269*c2e18aaaSAndroid Build Coastguard Worker  return BuildEnvProfiler(
2270*c2e18aaaSAndroid Build Coastguard Worker      ninja_file=ninja_file,
2271*c2e18aaaSAndroid Build Coastguard Worker      ninja_file_mtime=mtime,
2272*c2e18aaaSAndroid Build Coastguard Worker      variable_file=variables_file,
2273*c2e18aaaSAndroid Build Coastguard Worker      variable_file_md5=md5sum(variables_file),
2274*c2e18aaaSAndroid Build Coastguard Worker      clean_out=not ninja_file.exists(),
2275*c2e18aaaSAndroid Build Coastguard Worker      build_files_integrity=build_files_integrity_is_ok(),
2276*c2e18aaaSAndroid Build Coastguard Worker  )
2277*c2e18aaaSAndroid Build Coastguard Worker
2278*c2e18aaaSAndroid Build Coastguard Worker
2279*c2e18aaaSAndroid Build Coastguard Workerdef _send_build_condition_metrics(
2280*c2e18aaaSAndroid Build Coastguard Worker    build_profile: BuildEnvProfiler, cmd: List[str]
2281*c2e18aaaSAndroid Build Coastguard Worker):
2282*c2e18aaaSAndroid Build Coastguard Worker  """Send build conditions by comparing build env profilers."""
2283*c2e18aaaSAndroid Build Coastguard Worker
2284*c2e18aaaSAndroid Build Coastguard Worker  # when build module-info.json only, 'module-info.json' will be
2285*c2e18aaaSAndroid Build Coastguard Worker  # the last element.
2286*c2e18aaaSAndroid Build Coastguard Worker  m_mod_info_only = 'module-info.json' in cmd.pop()
2287*c2e18aaaSAndroid Build Coastguard Worker
2288*c2e18aaaSAndroid Build Coastguard Worker  def ninja_file_is_changed(env_profiler: BuildEnvProfiler) -> bool:
2289*c2e18aaaSAndroid Build Coastguard Worker    """Determine whether the ninja file had been renewal."""
2290*c2e18aaaSAndroid Build Coastguard Worker    if not env_profiler.ninja_file.is_file():
2291*c2e18aaaSAndroid Build Coastguard Worker      return True
2292*c2e18aaaSAndroid Build Coastguard Worker    return (
2293*c2e18aaaSAndroid Build Coastguard Worker        env_profiler.ninja_file.stat().st_mtime != env_profiler.ninja_file_mtime
2294*c2e18aaaSAndroid Build Coastguard Worker    )
2295*c2e18aaaSAndroid Build Coastguard Worker
2296*c2e18aaaSAndroid Build Coastguard Worker  def env_var_is_changed(env_profiler: BuildEnvProfiler) -> bool:
2297*c2e18aaaSAndroid Build Coastguard Worker    """Determine whether soong-related variables had changed."""
2298*c2e18aaaSAndroid Build Coastguard Worker    return md5sum(env_profiler.variable_file) != env_profiler.variable_file_md5
2299*c2e18aaaSAndroid Build Coastguard Worker
2300*c2e18aaaSAndroid Build Coastguard Worker  def send_data(detect_type, value=1):
2301*c2e18aaaSAndroid Build Coastguard Worker    """A simple wrapper of metrics.LocalDetectEvent."""
2302*c2e18aaaSAndroid Build Coastguard Worker    metrics.LocalDetectEvent(detect_type=detect_type, result=value)
2303*c2e18aaaSAndroid Build Coastguard Worker
2304*c2e18aaaSAndroid Build Coastguard Worker  send_data(DetectType.RBE_STATE, get_rbe_and_customized_out_state())
2305*c2e18aaaSAndroid Build Coastguard Worker
2306*c2e18aaaSAndroid Build Coastguard Worker  # Determine the correct detect type before profiling.
2307*c2e18aaaSAndroid Build Coastguard Worker  # (build module-info.json or build dependencies.)
2308*c2e18aaaSAndroid Build Coastguard Worker  clean_out = (
2309*c2e18aaaSAndroid Build Coastguard Worker      DetectType.MODULE_INFO_CLEAN_OUT
2310*c2e18aaaSAndroid Build Coastguard Worker      if m_mod_info_only
2311*c2e18aaaSAndroid Build Coastguard Worker      else DetectType.BUILD_CLEAN_OUT
2312*c2e18aaaSAndroid Build Coastguard Worker  )
2313*c2e18aaaSAndroid Build Coastguard Worker  ninja_generation = (
2314*c2e18aaaSAndroid Build Coastguard Worker      DetectType.MODULE_INFO_GEN_NINJA
2315*c2e18aaaSAndroid Build Coastguard Worker      if m_mod_info_only
2316*c2e18aaaSAndroid Build Coastguard Worker      else DetectType.BUILD_GEN_NINJA
2317*c2e18aaaSAndroid Build Coastguard Worker  )
2318*c2e18aaaSAndroid Build Coastguard Worker  bpmk_change = (
2319*c2e18aaaSAndroid Build Coastguard Worker      DetectType.MODULE_INFO_BPMK_CHANGE
2320*c2e18aaaSAndroid Build Coastguard Worker      if m_mod_info_only
2321*c2e18aaaSAndroid Build Coastguard Worker      else DetectType.BUILD_BPMK_CHANGE
2322*c2e18aaaSAndroid Build Coastguard Worker  )
2323*c2e18aaaSAndroid Build Coastguard Worker  env_change = (
2324*c2e18aaaSAndroid Build Coastguard Worker      DetectType.MODULE_INFO_ENV_CHANGE
2325*c2e18aaaSAndroid Build Coastguard Worker      if m_mod_info_only
2326*c2e18aaaSAndroid Build Coastguard Worker      else DetectType.BUILD_ENV_CHANGE
2327*c2e18aaaSAndroid Build Coastguard Worker  )
2328*c2e18aaaSAndroid Build Coastguard Worker  src_change = (
2329*c2e18aaaSAndroid Build Coastguard Worker      DetectType.MODULE_INFO_SRC_CHANGE
2330*c2e18aaaSAndroid Build Coastguard Worker      if m_mod_info_only
2331*c2e18aaaSAndroid Build Coastguard Worker      else DetectType.BUILD_SRC_CHANGE
2332*c2e18aaaSAndroid Build Coastguard Worker  )
2333*c2e18aaaSAndroid Build Coastguard Worker  other = (
2334*c2e18aaaSAndroid Build Coastguard Worker      DetectType.MODULE_INFO_OTHER
2335*c2e18aaaSAndroid Build Coastguard Worker      if m_mod_info_only
2336*c2e18aaaSAndroid Build Coastguard Worker      else DetectType.BUILD_OTHER
2337*c2e18aaaSAndroid Build Coastguard Worker  )
2338*c2e18aaaSAndroid Build Coastguard Worker  incremental = (
2339*c2e18aaaSAndroid Build Coastguard Worker      DetectType.MODULE_INFO_INCREMENTAL
2340*c2e18aaaSAndroid Build Coastguard Worker      if m_mod_info_only
2341*c2e18aaaSAndroid Build Coastguard Worker      else DetectType.BUILD_INCREMENTAL
2342*c2e18aaaSAndroid Build Coastguard Worker  )
2343*c2e18aaaSAndroid Build Coastguard Worker
2344*c2e18aaaSAndroid Build Coastguard Worker  if build_profile.clean_out:
2345*c2e18aaaSAndroid Build Coastguard Worker    send_data(clean_out)
2346*c2e18aaaSAndroid Build Coastguard Worker  else:
2347*c2e18aaaSAndroid Build Coastguard Worker    send_data(incremental)
2348*c2e18aaaSAndroid Build Coastguard Worker
2349*c2e18aaaSAndroid Build Coastguard Worker  if ninja_file_is_changed(build_profile):
2350*c2e18aaaSAndroid Build Coastguard Worker    send_data(ninja_generation)
2351*c2e18aaaSAndroid Build Coastguard Worker
2352*c2e18aaaSAndroid Build Coastguard Worker  other_condition = True
2353*c2e18aaaSAndroid Build Coastguard Worker  if not build_profile.build_files_integrity:
2354*c2e18aaaSAndroid Build Coastguard Worker    send_data(bpmk_change)
2355*c2e18aaaSAndroid Build Coastguard Worker    other_condition = False
2356*c2e18aaaSAndroid Build Coastguard Worker  if env_var_is_changed(build_profile):
2357*c2e18aaaSAndroid Build Coastguard Worker    send_data(env_change)
2358*c2e18aaaSAndroid Build Coastguard Worker    other_condition = False
2359*c2e18aaaSAndroid Build Coastguard Worker  if bool(get_modified_files(os.getcwd())):
2360*c2e18aaaSAndroid Build Coastguard Worker    send_data(src_change)
2361*c2e18aaaSAndroid Build Coastguard Worker    other_condition = False
2362*c2e18aaaSAndroid Build Coastguard Worker  if other_condition:
2363*c2e18aaaSAndroid Build Coastguard Worker    send_data(other)
2364