xref: /aosp_15_r20/tools/asuite/atest/test_mapping.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1*c2e18aaaSAndroid Build Coastguard Worker# Copyright 2018, 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"""Classes for test mapping related objects."""
16*c2e18aaaSAndroid Build Coastguard Worker
17*c2e18aaaSAndroid Build Coastguard Worker
18*c2e18aaaSAndroid Build Coastguard Workerimport copy
19*c2e18aaaSAndroid Build Coastguard Workerimport fnmatch
20*c2e18aaaSAndroid Build Coastguard Workerimport os
21*c2e18aaaSAndroid Build Coastguard Workerimport re
22*c2e18aaaSAndroid Build Coastguard Worker
23*c2e18aaaSAndroid Build Coastguard Workerfrom atest import atest_utils
24*c2e18aaaSAndroid Build Coastguard Workerfrom atest import constants
25*c2e18aaaSAndroid Build Coastguard Worker
26*c2e18aaaSAndroid Build Coastguard WorkerTEST_MAPPING = 'TEST_MAPPING'
27*c2e18aaaSAndroid Build Coastguard Worker
28*c2e18aaaSAndroid Build Coastguard Worker
29*c2e18aaaSAndroid Build Coastguard Workerclass TestDetail:
30*c2e18aaaSAndroid Build Coastguard Worker  """Stores the test details set in a TEST_MAPPING file."""
31*c2e18aaaSAndroid Build Coastguard Worker
32*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self, details):
33*c2e18aaaSAndroid Build Coastguard Worker    """TestDetail constructor
34*c2e18aaaSAndroid Build Coastguard Worker
35*c2e18aaaSAndroid Build Coastguard Worker    Parse test detail from a dictionary, e.g.,
36*c2e18aaaSAndroid Build Coastguard Worker    {
37*c2e18aaaSAndroid Build Coastguard Worker      "name": "SettingsUnitTests",
38*c2e18aaaSAndroid Build Coastguard Worker      "host": true,
39*c2e18aaaSAndroid Build Coastguard Worker      "options": [
40*c2e18aaaSAndroid Build Coastguard Worker        {
41*c2e18aaaSAndroid Build Coastguard Worker          "instrumentation-arg":
42*c2e18aaaSAndroid Build Coastguard Worker              "annotation=android.platform.test.annotations.Presubmit"
43*c2e18aaaSAndroid Build Coastguard Worker        },
44*c2e18aaaSAndroid Build Coastguard Worker      "file_patterns": ["(/|^)Window[^/]*\\.java",
45*c2e18aaaSAndroid Build Coastguard Worker                       "(/|^)Activity[^/]*\\.java"]
46*c2e18aaaSAndroid Build Coastguard Worker    }
47*c2e18aaaSAndroid Build Coastguard Worker
48*c2e18aaaSAndroid Build Coastguard Worker    Args:
49*c2e18aaaSAndroid Build Coastguard Worker        details: A dictionary of test detail.
50*c2e18aaaSAndroid Build Coastguard Worker    """
51*c2e18aaaSAndroid Build Coastguard Worker    self.name = details['name']
52*c2e18aaaSAndroid Build Coastguard Worker    self.options = []
53*c2e18aaaSAndroid Build Coastguard Worker    # True if the test should run on host and require no device.
54*c2e18aaaSAndroid Build Coastguard Worker    self.host = details.get('host', False)
55*c2e18aaaSAndroid Build Coastguard Worker    assert isinstance(self.host, bool), 'host can only have boolean value.'
56*c2e18aaaSAndroid Build Coastguard Worker    options = details.get('options', [])
57*c2e18aaaSAndroid Build Coastguard Worker    for option in options:
58*c2e18aaaSAndroid Build Coastguard Worker      assert len(option) == 1, 'Each option can only have one key.'
59*c2e18aaaSAndroid Build Coastguard Worker      self.options.append(copy.deepcopy(option).popitem())
60*c2e18aaaSAndroid Build Coastguard Worker    self.options.sort(key=lambda o: o[0])
61*c2e18aaaSAndroid Build Coastguard Worker    self.file_patterns = details.get('file_patterns', [])
62*c2e18aaaSAndroid Build Coastguard Worker
63*c2e18aaaSAndroid Build Coastguard Worker  def __str__(self):
64*c2e18aaaSAndroid Build Coastguard Worker    """String value of the TestDetail object."""
65*c2e18aaaSAndroid Build Coastguard Worker    host_info = ', runs on host without device required.' if self.host else ''
66*c2e18aaaSAndroid Build Coastguard Worker    if not self.options:
67*c2e18aaaSAndroid Build Coastguard Worker      return self.name + host_info
68*c2e18aaaSAndroid Build Coastguard Worker    options = ''
69*c2e18aaaSAndroid Build Coastguard Worker    for option in self.options:
70*c2e18aaaSAndroid Build Coastguard Worker      options += '%s: %s, ' % option
71*c2e18aaaSAndroid Build Coastguard Worker
72*c2e18aaaSAndroid Build Coastguard Worker    return '%s (%s)%s' % (self.name, options.strip(', '), host_info)
73*c2e18aaaSAndroid Build Coastguard Worker
74*c2e18aaaSAndroid Build Coastguard Worker  def __hash__(self):
75*c2e18aaaSAndroid Build Coastguard Worker    """Get the hash of TestDetail based on the details"""
76*c2e18aaaSAndroid Build Coastguard Worker    return hash(str(self))
77*c2e18aaaSAndroid Build Coastguard Worker
78*c2e18aaaSAndroid Build Coastguard Worker  def __eq__(self, other):
79*c2e18aaaSAndroid Build Coastguard Worker    return str(self) == str(other)
80*c2e18aaaSAndroid Build Coastguard Worker
81*c2e18aaaSAndroid Build Coastguard Worker
82*c2e18aaaSAndroid Build Coastguard Workerclass Import:
83*c2e18aaaSAndroid Build Coastguard Worker  """Store test mapping import details."""
84*c2e18aaaSAndroid Build Coastguard Worker
85*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self, test_mapping_file, details):
86*c2e18aaaSAndroid Build Coastguard Worker    """Import constructor
87*c2e18aaaSAndroid Build Coastguard Worker
88*c2e18aaaSAndroid Build Coastguard Worker    Parse import details from a dictionary, e.g.,
89*c2e18aaaSAndroid Build Coastguard Worker    {
90*c2e18aaaSAndroid Build Coastguard Worker        "path": "..\folder1"
91*c2e18aaaSAndroid Build Coastguard Worker    }
92*c2e18aaaSAndroid Build Coastguard Worker    in which, project is the name of the project, by default it's the
93*c2e18aaaSAndroid Build Coastguard Worker    current project of the containing TEST_MAPPING file.
94*c2e18aaaSAndroid Build Coastguard Worker
95*c2e18aaaSAndroid Build Coastguard Worker    Args:
96*c2e18aaaSAndroid Build Coastguard Worker        test_mapping_file: Path to the TEST_MAPPING file that contains the
97*c2e18aaaSAndroid Build Coastguard Worker          import.
98*c2e18aaaSAndroid Build Coastguard Worker        details: A dictionary of details about importing another TEST_MAPPING
99*c2e18aaaSAndroid Build Coastguard Worker          file.
100*c2e18aaaSAndroid Build Coastguard Worker    """
101*c2e18aaaSAndroid Build Coastguard Worker    self.test_mapping_file = test_mapping_file
102*c2e18aaaSAndroid Build Coastguard Worker    self.path = details['path']
103*c2e18aaaSAndroid Build Coastguard Worker
104*c2e18aaaSAndroid Build Coastguard Worker  def __str__(self):
105*c2e18aaaSAndroid Build Coastguard Worker    """String value of the Import object."""
106*c2e18aaaSAndroid Build Coastguard Worker    return 'Source: %s, path: %s' % (self.test_mapping_file, self.path)
107*c2e18aaaSAndroid Build Coastguard Worker
108*c2e18aaaSAndroid Build Coastguard Worker  def get_path(self):
109*c2e18aaaSAndroid Build Coastguard Worker    """Get the path to TEST_MAPPING import directory."""
110*c2e18aaaSAndroid Build Coastguard Worker    path = os.path.realpath(
111*c2e18aaaSAndroid Build Coastguard Worker        os.path.join(os.path.dirname(self.test_mapping_file), self.path)
112*c2e18aaaSAndroid Build Coastguard Worker    )
113*c2e18aaaSAndroid Build Coastguard Worker    if os.path.exists(path):
114*c2e18aaaSAndroid Build Coastguard Worker      return path
115*c2e18aaaSAndroid Build Coastguard Worker    root_dir = os.environ.get(constants.ANDROID_BUILD_TOP, os.sep)
116*c2e18aaaSAndroid Build Coastguard Worker    path = os.path.realpath(os.path.join(root_dir, self.path))
117*c2e18aaaSAndroid Build Coastguard Worker    if os.path.exists(path):
118*c2e18aaaSAndroid Build Coastguard Worker      return path
119*c2e18aaaSAndroid Build Coastguard Worker    # The import path can't be located.
120*c2e18aaaSAndroid Build Coastguard Worker    return None
121*c2e18aaaSAndroid Build Coastguard Worker
122*c2e18aaaSAndroid Build Coastguard Worker
123*c2e18aaaSAndroid Build Coastguard Workerdef is_match_file_patterns(test_mapping_file, test_detail):
124*c2e18aaaSAndroid Build Coastguard Worker  """Check if the changed file names match the regex pattern defined in
125*c2e18aaaSAndroid Build Coastguard Worker
126*c2e18aaaSAndroid Build Coastguard Worker  file_patterns of TEST_MAPPING files.
127*c2e18aaaSAndroid Build Coastguard Worker
128*c2e18aaaSAndroid Build Coastguard Worker  Args:
129*c2e18aaaSAndroid Build Coastguard Worker      test_mapping_file: Path to a TEST_MAPPING file.
130*c2e18aaaSAndroid Build Coastguard Worker      test_detail: A TestDetail object.
131*c2e18aaaSAndroid Build Coastguard Worker
132*c2e18aaaSAndroid Build Coastguard Worker  Returns:
133*c2e18aaaSAndroid Build Coastguard Worker      True if the test's file_patterns setting is not set or contains a
134*c2e18aaaSAndroid Build Coastguard Worker      pattern matches any of the modified files.
135*c2e18aaaSAndroid Build Coastguard Worker  """
136*c2e18aaaSAndroid Build Coastguard Worker  # Only check if the altered files are located in the same or sub directory
137*c2e18aaaSAndroid Build Coastguard Worker  # of the TEST_MAPPING file. Extract the relative path of the modified files
138*c2e18aaaSAndroid Build Coastguard Worker  # which match file patterns.
139*c2e18aaaSAndroid Build Coastguard Worker  file_patterns = test_detail.get('file_patterns', [])
140*c2e18aaaSAndroid Build Coastguard Worker  if not file_patterns:
141*c2e18aaaSAndroid Build Coastguard Worker    return True
142*c2e18aaaSAndroid Build Coastguard Worker  test_mapping_dir = os.path.dirname(test_mapping_file)
143*c2e18aaaSAndroid Build Coastguard Worker  modified_files = atest_utils.get_modified_files(test_mapping_dir)
144*c2e18aaaSAndroid Build Coastguard Worker  if not modified_files:
145*c2e18aaaSAndroid Build Coastguard Worker    return False
146*c2e18aaaSAndroid Build Coastguard Worker  modified_files_in_source_dir = [
147*c2e18aaaSAndroid Build Coastguard Worker      os.path.relpath(filepath, test_mapping_dir)
148*c2e18aaaSAndroid Build Coastguard Worker      for filepath in fnmatch.filter(
149*c2e18aaaSAndroid Build Coastguard Worker          modified_files, os.path.join(test_mapping_dir, '*')
150*c2e18aaaSAndroid Build Coastguard Worker      )
151*c2e18aaaSAndroid Build Coastguard Worker  ]
152*c2e18aaaSAndroid Build Coastguard Worker  for modified_file in modified_files_in_source_dir:
153*c2e18aaaSAndroid Build Coastguard Worker    # Force to run the test if it's in a TEST_MAPPING file included in the
154*c2e18aaaSAndroid Build Coastguard Worker    # changesets.
155*c2e18aaaSAndroid Build Coastguard Worker    if modified_file == constants.TEST_MAPPING:
156*c2e18aaaSAndroid Build Coastguard Worker      return True
157*c2e18aaaSAndroid Build Coastguard Worker    for pattern in file_patterns:
158*c2e18aaaSAndroid Build Coastguard Worker      if re.search(pattern, modified_file):
159*c2e18aaaSAndroid Build Coastguard Worker        return True
160*c2e18aaaSAndroid Build Coastguard Worker  return False
161