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