1*c2e18aaaSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*c2e18aaaSAndroid Build Coastguard Worker# 3*c2e18aaaSAndroid Build Coastguard Worker# Copyright 2018, The Android Open Source Project 4*c2e18aaaSAndroid Build Coastguard Worker# 5*c2e18aaaSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*c2e18aaaSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*c2e18aaaSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*c2e18aaaSAndroid Build Coastguard Worker# 9*c2e18aaaSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*c2e18aaaSAndroid Build Coastguard Worker# 11*c2e18aaaSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*c2e18aaaSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*c2e18aaaSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*c2e18aaaSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*c2e18aaaSAndroid Build Coastguard Worker# limitations under the License. 16*c2e18aaaSAndroid Build Coastguard Worker 17*c2e18aaaSAndroid Build Coastguard Worker"""ATest Integration Test Class. 18*c2e18aaaSAndroid Build Coastguard Worker 19*c2e18aaaSAndroid Build Coastguard WorkerThe purpose is to prevent potential side-effects from breaking ATest at the 20*c2e18aaaSAndroid Build Coastguard Workerearly stage while landing CLs with potential side-effects. 21*c2e18aaaSAndroid Build Coastguard Worker 22*c2e18aaaSAndroid Build Coastguard WorkerIt forks a subprocess with ATest commands to validate if it can pass all the 23*c2e18aaaSAndroid Build Coastguard Workerfinding, running logic of the python code, and waiting for TF to exit properly. 24*c2e18aaaSAndroid Build Coastguard Worker - When running with ROBOLECTRIC tests, it runs without TF, and will exit 25*c2e18aaaSAndroid Build Coastguard Worker the subprocess with the message "All tests passed" 26*c2e18aaaSAndroid Build Coastguard Worker - If FAIL, it means something breaks ATest unexpectedly! 27*c2e18aaaSAndroid Build Coastguard Worker""" 28*c2e18aaaSAndroid Build Coastguard Worker 29*c2e18aaaSAndroid Build Coastguard Workerfrom __future__ import print_function 30*c2e18aaaSAndroid Build Coastguard Worker 31*c2e18aaaSAndroid Build Coastguard Workerimport os 32*c2e18aaaSAndroid Build Coastguard Workerimport subprocess 33*c2e18aaaSAndroid Build Coastguard Workerimport sys 34*c2e18aaaSAndroid Build Coastguard Workerimport tempfile 35*c2e18aaaSAndroid Build Coastguard Workerimport time 36*c2e18aaaSAndroid Build Coastguard Workerimport unittest 37*c2e18aaaSAndroid Build Coastguard Worker 38*c2e18aaaSAndroid Build Coastguard Worker 39*c2e18aaaSAndroid Build Coastguard Worker_TEST_RUN_DIR_PREFIX = 'atest_integration_tests_%s_' 40*c2e18aaaSAndroid Build Coastguard Worker_LOG_FILE = 'integration_tests.log' 41*c2e18aaaSAndroid Build Coastguard Worker_FAILED_LINE_LIMIT = 50 42*c2e18aaaSAndroid Build Coastguard Worker_EXIT_TEST_FAILED = 1 43*c2e18aaaSAndroid Build Coastguard Worker_ALTERNATIVES = {'-dev'} 44*c2e18aaaSAndroid Build Coastguard Worker_INTEGRATION_TESTS = [ 45*c2e18aaaSAndroid Build Coastguard Worker os.path.join( 46*c2e18aaaSAndroid Build Coastguard Worker os.environ.get('ANDROID_BUILD_TOP', os.getcwd()), 47*c2e18aaaSAndroid Build Coastguard Worker 'tools/asuite/atest/test_plans/INTEGRATION_TESTS', 48*c2e18aaaSAndroid Build Coastguard Worker ) 49*c2e18aaaSAndroid Build Coastguard Worker] 50*c2e18aaaSAndroid Build Coastguard Worker 51*c2e18aaaSAndroid Build Coastguard Worker 52*c2e18aaaSAndroid Build Coastguard Workerclass ATestIntegrationTest(unittest.TestCase): 53*c2e18aaaSAndroid Build Coastguard Worker """ATest Integration Test Class.""" 54*c2e18aaaSAndroid Build Coastguard Worker 55*c2e18aaaSAndroid Build Coastguard Worker NAME = 'ATestIntegrationTest' 56*c2e18aaaSAndroid Build Coastguard Worker EXECUTABLE = 'atest' 57*c2e18aaaSAndroid Build Coastguard Worker OPTIONS = '' 58*c2e18aaaSAndroid Build Coastguard Worker _RUN_CMD = '{exe} {options} {test}' 59*c2e18aaaSAndroid Build Coastguard Worker _PASSED_CRITERIA = ['will be rescheduled', 'All tests passed'] 60*c2e18aaaSAndroid Build Coastguard Worker 61*c2e18aaaSAndroid Build Coastguard Worker def setUp(self): 62*c2e18aaaSAndroid Build Coastguard Worker """Set up stuff for testing.""" 63*c2e18aaaSAndroid Build Coastguard Worker self.full_env_vars = os.environ.copy() 64*c2e18aaaSAndroid Build Coastguard Worker self.test_passed = False 65*c2e18aaaSAndroid Build Coastguard Worker self.log = [] 66*c2e18aaaSAndroid Build Coastguard Worker 67*c2e18aaaSAndroid Build Coastguard Worker def run_test(self, testcase): 68*c2e18aaaSAndroid Build Coastguard Worker """Create a subprocess to execute the test command. 69*c2e18aaaSAndroid Build Coastguard Worker 70*c2e18aaaSAndroid Build Coastguard Worker Strategy: 71*c2e18aaaSAndroid Build Coastguard Worker Fork a subprocess to wait for TF exit properly, and log the error 72*c2e18aaaSAndroid Build Coastguard Worker if the exit code isn't 0. 73*c2e18aaaSAndroid Build Coastguard Worker 74*c2e18aaaSAndroid Build Coastguard Worker Args: 75*c2e18aaaSAndroid Build Coastguard Worker testcase: A string of testcase name. 76*c2e18aaaSAndroid Build Coastguard Worker """ 77*c2e18aaaSAndroid Build Coastguard Worker run_cmd_dict = { 78*c2e18aaaSAndroid Build Coastguard Worker 'exe': self.EXECUTABLE, 79*c2e18aaaSAndroid Build Coastguard Worker 'options': self.OPTIONS, 80*c2e18aaaSAndroid Build Coastguard Worker 'test': testcase, 81*c2e18aaaSAndroid Build Coastguard Worker } 82*c2e18aaaSAndroid Build Coastguard Worker run_command = self._RUN_CMD.format(**run_cmd_dict) 83*c2e18aaaSAndroid Build Coastguard Worker try: 84*c2e18aaaSAndroid Build Coastguard Worker subprocess.check_output( 85*c2e18aaaSAndroid Build Coastguard Worker run_command, 86*c2e18aaaSAndroid Build Coastguard Worker stderr=subprocess.PIPE, 87*c2e18aaaSAndroid Build Coastguard Worker env=self.full_env_vars, 88*c2e18aaaSAndroid Build Coastguard Worker shell=True, 89*c2e18aaaSAndroid Build Coastguard Worker ) 90*c2e18aaaSAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 91*c2e18aaaSAndroid Build Coastguard Worker self.log.append(e.output.decode()) 92*c2e18aaaSAndroid Build Coastguard Worker return False 93*c2e18aaaSAndroid Build Coastguard Worker return True 94*c2e18aaaSAndroid Build Coastguard Worker 95*c2e18aaaSAndroid Build Coastguard Worker def get_failed_log(self): 96*c2e18aaaSAndroid Build Coastguard Worker """Get a trimmed failed log. 97*c2e18aaaSAndroid Build Coastguard Worker 98*c2e18aaaSAndroid Build Coastguard Worker Strategy: 99*c2e18aaaSAndroid Build Coastguard Worker In order not to show the unnecessary log such as build log, 100*c2e18aaaSAndroid Build Coastguard Worker it's better to get a trimmed failed log that contains the 101*c2e18aaaSAndroid Build Coastguard Worker most important information. 102*c2e18aaaSAndroid Build Coastguard Worker 103*c2e18aaaSAndroid Build Coastguard Worker Returns: 104*c2e18aaaSAndroid Build Coastguard Worker A trimmed failed log. 105*c2e18aaaSAndroid Build Coastguard Worker """ 106*c2e18aaaSAndroid Build Coastguard Worker failed_log = '\n'.join(filter(None, self.log[-_FAILED_LINE_LIMIT:])) 107*c2e18aaaSAndroid Build Coastguard Worker return failed_log 108*c2e18aaaSAndroid Build Coastguard Worker 109*c2e18aaaSAndroid Build Coastguard Worker 110*c2e18aaaSAndroid Build Coastguard Workerdef create_test_method(testcase, log_path): 111*c2e18aaaSAndroid Build Coastguard Worker """Create a test method according to the testcase. 112*c2e18aaaSAndroid Build Coastguard Worker 113*c2e18aaaSAndroid Build Coastguard Worker Args: 114*c2e18aaaSAndroid Build Coastguard Worker testcase: A testcase name. 115*c2e18aaaSAndroid Build Coastguard Worker log_path: A file path for storing the test result. 116*c2e18aaaSAndroid Build Coastguard Worker 117*c2e18aaaSAndroid Build Coastguard Worker Returns: 118*c2e18aaaSAndroid Build Coastguard Worker A created test method, and a test function name. 119*c2e18aaaSAndroid Build Coastguard Worker """ 120*c2e18aaaSAndroid Build Coastguard Worker test_function_name = 'test_%s' % testcase.replace(' ', '_') 121*c2e18aaaSAndroid Build Coastguard Worker 122*c2e18aaaSAndroid Build Coastguard Worker # pylint: disable=missing-docstring 123*c2e18aaaSAndroid Build Coastguard Worker def template_test_method(self): 124*c2e18aaaSAndroid Build Coastguard Worker self.test_passed = self.run_test(testcase) 125*c2e18aaaSAndroid Build Coastguard Worker open(log_path, 'a').write('\n'.join(self.log)) 126*c2e18aaaSAndroid Build Coastguard Worker failed_message = 'Running command: %s failed.\n' % testcase 127*c2e18aaaSAndroid Build Coastguard Worker failed_message += '' if self.test_passed else self.get_failed_log() 128*c2e18aaaSAndroid Build Coastguard Worker self.assertTrue(self.test_passed, failed_message) 129*c2e18aaaSAndroid Build Coastguard Worker 130*c2e18aaaSAndroid Build Coastguard Worker return test_function_name, template_test_method 131*c2e18aaaSAndroid Build Coastguard Worker 132*c2e18aaaSAndroid Build Coastguard Worker 133*c2e18aaaSAndroid Build Coastguard Workerdef create_test_run_dir(): 134*c2e18aaaSAndroid Build Coastguard Worker """Create the test run directory in tmp. 135*c2e18aaaSAndroid Build Coastguard Worker 136*c2e18aaaSAndroid Build Coastguard Worker Returns: 137*c2e18aaaSAndroid Build Coastguard Worker A string of the directory path. 138*c2e18aaaSAndroid Build Coastguard Worker """ 139*c2e18aaaSAndroid Build Coastguard Worker utc_epoch_time = int(time.time()) 140*c2e18aaaSAndroid Build Coastguard Worker prefix = _TEST_RUN_DIR_PREFIX % utc_epoch_time 141*c2e18aaaSAndroid Build Coastguard Worker return tempfile.mkdtemp(prefix=prefix) 142*c2e18aaaSAndroid Build Coastguard Worker 143*c2e18aaaSAndroid Build Coastguard Worker 144*c2e18aaaSAndroid Build Coastguard Workerif __name__ == '__main__': 145*c2e18aaaSAndroid Build Coastguard Worker # TODO(b/129029189) Implement detail comparison check for dry-run mode. 146*c2e18aaaSAndroid Build Coastguard Worker ARGS = sys.argv[1:] 147*c2e18aaaSAndroid Build Coastguard Worker if ARGS: 148*c2e18aaaSAndroid Build Coastguard Worker for exe in _ALTERNATIVES: 149*c2e18aaaSAndroid Build Coastguard Worker if exe in ARGS: 150*c2e18aaaSAndroid Build Coastguard Worker ARGS.remove(exe) 151*c2e18aaaSAndroid Build Coastguard Worker ATestIntegrationTest.EXECUTABLE += exe 152*c2e18aaaSAndroid Build Coastguard Worker ATestIntegrationTest.OPTIONS = ' '.join(ARGS) 153*c2e18aaaSAndroid Build Coastguard Worker print('Running tests with {}\n'.format(ATestIntegrationTest.EXECUTABLE)) 154*c2e18aaaSAndroid Build Coastguard Worker try: 155*c2e18aaaSAndroid Build Coastguard Worker LOG_PATH = os.path.join(create_test_run_dir(), _LOG_FILE) 156*c2e18aaaSAndroid Build Coastguard Worker for TEST_PLANS in _INTEGRATION_TESTS: 157*c2e18aaaSAndroid Build Coastguard Worker with open(TEST_PLANS) as test_plans: 158*c2e18aaaSAndroid Build Coastguard Worker for test in test_plans: 159*c2e18aaaSAndroid Build Coastguard Worker # Skip test when the line startswith #. 160*c2e18aaaSAndroid Build Coastguard Worker if not test.strip() or test.strip().startswith('#'): 161*c2e18aaaSAndroid Build Coastguard Worker continue 162*c2e18aaaSAndroid Build Coastguard Worker test_func_name, test_func = create_test_method(test.strip(), LOG_PATH) 163*c2e18aaaSAndroid Build Coastguard Worker setattr(ATestIntegrationTest, test_func_name, test_func) 164*c2e18aaaSAndroid Build Coastguard Worker SUITE = unittest.TestLoader().loadTestsFromTestCase(ATestIntegrationTest) 165*c2e18aaaSAndroid Build Coastguard Worker RESULTS = unittest.TextTestRunner(verbosity=2).run(SUITE) 166*c2e18aaaSAndroid Build Coastguard Worker finally: 167*c2e18aaaSAndroid Build Coastguard Worker if RESULTS.failures: 168*c2e18aaaSAndroid Build Coastguard Worker print('Full test log is saved to %s' % LOG_PATH) 169*c2e18aaaSAndroid Build Coastguard Worker sys.exit(_EXIT_TEST_FAILED) 170*c2e18aaaSAndroid Build Coastguard Worker else: 171*c2e18aaaSAndroid Build Coastguard Worker os.remove(LOG_PATH) 172