1#!/usr/bin/env python3 2# 3# Copyright 2021 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Incremental dEQP 18 19This script will run a subset of dEQP test on device to get dEQP dependency. 20 21Usage 1: Compare with a base build to check if any dEQP dependency has 22changed. Output a decision if dEQP could be skipped, and a cts-tradefed 23command to be used based on the decision. 24 25python3 incremental_deqp.py -s [device serial] -t [test directory] -b 26[base build target file] -c [current build target file] 27 28Usage 2: Generate a file containing a list of dEQP dependencies for the 29build on device. 30 31python3 incremental_deqp.py -s [device serial] -t [test directory] 32--generate_deps_only 33 34""" 35import argparse 36import importlib 37import json 38import logging 39import os 40import pkgutil 41import re 42import subprocess 43import tempfile 44import time 45import uuid 46from target_file_handler import TargetFileHandler 47from custom_build_file_handler import CustomBuildFileHandler 48from zipfile import ZipFile 49 50 51DEFAULT_CTS_XML = ('<?xml version="1.0" encoding="utf-8"?>\n' 52 '<configuration description="Runs CTS from a pre-existing CTS installation">\n' 53 ' <include name="cts-common" />\n' 54 ' <include name="cts-exclude" />\n' 55 ' <include name="cts-exclude-instant" />\n' 56 ' <option name="enable-token-sharding" ' 57 'value="true" />\n' 58 ' <option name="plan" value="cts" />\n' 59 '</configuration>\n') 60 61INCREMENTAL_DEQP_XML = ('<?xml version="1.0" encoding="utf-8"?>\n' 62 '<configuration description="Runs CTS with incremental dEQP">\n' 63 ' <include name="cts-common" />\n' 64 ' <include name="cts-exclude" />\n' 65 ' <include name="cts-exclude-instant" />\n' 66 ' <option name="enable-token-sharding" ' 67 'value="true" />\n' 68 ' <option name="compatibility:exclude-filter" ' 69 'value="CtsDeqpTestCases" />\n' 70 ' <option name="plan" value="cts" />\n' 71 '</configuration>\n') 72 73REPORT_FILENAME = 'incremental_dEQP_report.json' 74 75logger = logging.getLogger() 76 77 78class AtsError(Exception): 79 """Error when running incremental dEQP with Android Test Station""" 80 pass 81 82class AdbError(Exception): 83 """Error when running adb command.""" 84 pass 85 86class TestError(Exception): 87 """Error when running dEQP test.""" 88 pass 89 90class TestResourceError(Exception): 91 """Error with test resource. """ 92 pass 93 94class BuildHelper(object): 95 """Helper class for analyzing build.""" 96 97 def __init__(self, custom_handler=False): 98 """Init BuildHelper. 99 100 Args: 101 custom_handler: use custom build handler. 102 """ 103 self._build_file_handler = TargetFileHandler 104 if custom_handler: 105 self._build_file_handler = CustomBuildFileHandler 106 107 108 def compare_base_build_with_current_build(self, deqp_deps, current_build_file, 109 base_build_file): 110 """Compare the difference of current build and base build with dEQP dependency. 111 112 If the difference doesn't involve dEQP dependency, current build could skip dEQP test if 113 base build has passed test. 114 115 Args: 116 deqp_deps: a set of dEQP dependency. 117 current_build_file: current build's file name. 118 base_build_file: base build's file name. 119 Returns: 120 True if current build could skip dEQP, otherwise False. 121 Dictionary of changed dependencies and their details. 122 """ 123 print('Comparing base build and current build...') 124 current_build_handler = self._build_file_handler(current_build_file) 125 current_build_hash = current_build_handler.get_file_hash(deqp_deps) 126 127 base_build_handler = self._build_file_handler(base_build_file) 128 base_build_hash = base_build_handler.get_file_hash(deqp_deps) 129 130 return self._compare_build_hash(current_build_hash, base_build_hash) 131 132 133 def compare_base_build_with_device_files(self, deqp_deps, adb, base_build_file): 134 """Compare the difference of files on device and base build with dEQP dependency. 135 136 If the difference doesn't involve dEQP dependency, current build could skip dEQP test if 137 base build has passed test. 138 139 Args: 140 deqp_deps: a set of dEQP dependency. 141 adb: an instance of AdbHelper for current device under test. 142 base_build_file: base build file name. 143 Returns: 144 True if current build could skip dEQP, otherwise False. 145 Dictionary of changed dependencies and their details. 146 """ 147 print('Comparing base build and current build on the device...') 148 # Get current build's hash. 149 current_build_hash = dict() 150 for dep in deqp_deps: 151 content = adb.run_shell_command('cat ' + dep) 152 current_build_hash[dep] = hash(content) 153 154 base_build_handler = self._build_file_handler(base_build_file) 155 base_build_hash = base_build_handler.get_file_hash(deqp_deps) 156 157 return self._compare_build_hash(current_build_hash, base_build_hash) 158 159 def get_system_fingerprint(self, build_file): 160 """Get build fingerprint in SYSTEM partition. 161 162 Returns: 163 String of build fingerprint. 164 """ 165 return self._build_file_handler(build_file).get_system_fingerprint() 166 167 168 def _compare_build_hash(self, current_build_hash, base_build_hash): 169 """Compare the hash value of current build and base build. 170 171 Args: 172 current_build_hash: map of current build where key is file name, and value is content hash. 173 base_build_hash: map of base build where key is file name and value is content hash. 174 Returns: 175 Boolean about if two builds' hash is the same. 176 Dictionary of changed dependencies and their details. 177 """ 178 changes = {} 179 if current_build_hash == base_build_hash: 180 print('Done!') 181 return True, changes 182 183 for key, val in current_build_hash.items(): 184 if key not in base_build_hash: 185 detail = 'File:{build_file} was not found in base build'.format(build_file=key) 186 changes[key] = detail 187 logger.info(detail) 188 elif base_build_hash[key] != val: 189 detail = ('Detected dEQP dependency file difference:{deps}. Base build hash:{base}, ' 190 'current build hash:{current}'.format(deps=key, base=base_build_hash[key], 191 current=val)) 192 changes[key] = detail 193 logger.info(detail) 194 195 print('Done!') 196 return False, changes 197 198 199class AdbHelper(object): 200 """Helper class for running adb.""" 201 202 def __init__(self, device_serial=None): 203 """Initialize AdbHelper. 204 205 Args: 206 device_serial: A string of device serial number, optional. 207 """ 208 self._device_serial = device_serial 209 210 def _run_adb_command(self, *args): 211 """Run adb command.""" 212 adb_cmd = ['adb'] 213 if self._device_serial: 214 adb_cmd.extend(['-s', self._device_serial]) 215 adb_cmd.extend(args) 216 adb_cmd = ' '.join(adb_cmd) 217 completed = subprocess.run(adb_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 218 if completed.returncode != 0: 219 raise AdbError('adb command: {cmd} failed with error: {error}' 220 .format(cmd=adb_cmd, error=completed.stderr)) 221 222 return completed.stdout 223 224 def push_file(self, source_file, destination_file): 225 """Push a file from device to host. 226 227 Args: 228 source_file: A string representing file to push. 229 destination_file: A string representing target on device to push to. 230 """ 231 return self._run_adb_command('push', source_file, destination_file) 232 233 def pull_file(self, source_file, destination_file): 234 """Pull a file to device. 235 236 Args: 237 source_file: A string representing file on device to pull. 238 destination_file: A string representing target on host to pull to. 239 """ 240 return self._run_adb_command('pull', source_file, destination_file) 241 242 def get_fingerprint(self): 243 """Get fingerprint of the device.""" 244 return self._run_adb_command('shell', 'getprop ro.build.fingerprint').decode("utf-8").strip() 245 246 def run_shell_command(self, command): 247 """Run a adb shell command. 248 249 Args: 250 command: A string of command to run, executed through 'adb shell' 251 """ 252 return self._run_adb_command('shell', command) 253 254 255class DeqpDependencyCollector(object): 256 """Collect dEQP dependency from device under test.""" 257 258 def __init__(self, work_dir, test_dir, adb): 259 """Init DeqpDependencyCollector. 260 261 Args: 262 work_dir: path of directory for saving script result and logs. 263 test_dir: path of directory for incremental dEQP test file. 264 adb: an instance of AdbHelper. 265 """ 266 self._work_dir = work_dir 267 self._test_dir = test_dir 268 self._adb = adb 269 # dEQP dependency with pattern below are not an actual file: 270 # files has prefix /data/ are not system files, e.g. intermediate files. 271 # [vdso] is virtual dynamic shared object. 272 # /dmabuf is a temporary file. 273 self._exclude_deqp_pattern = re.compile('(^/data/|^\[vdso\]|^/dmabuf)') 274 275 def check_test_log(self, test_file, log_file): 276 """Check test's log to see if all tests are executed. 277 278 Args: 279 test_file: Name of test .txt file. 280 log_content: Name of log file. 281 Returns: 282 True if all tests are executed, otherwise False. 283 """ 284 test_cnt = 0 285 with open(test_file, 'r') as f: 286 for _ in f: 287 test_cnt += 1 288 289 executed_test_cnt = 0 290 291 with open(log_file, 'r') as f: 292 for line in f: 293 # 'NotSupported' status means test is not supported in device. 294 # TODO(yichunli): Check with graphics team if failed test is allowed. 295 if ('StatusCode="Pass"' in line or 'StatusCode="NotSupported"' in line or 296 'StatusCode="Fail"' in line): 297 executed_test_cnt += 1 298 return executed_test_cnt == test_cnt 299 300 def update_dependency(self, deps, dump): 301 """Parse perf dump file and update dEQP dependency. 302 303 Below is an example of how dump file looks like: 304 630 record comm: type 3, misc 0, size 64 305 631 pid 23365, tid 23365, comm simpleperf 306 632 sample_id: pid 0, tid 0 307 633 sample_id: time 0 308 634 sample_id: id 23804 309 635 sample_id: cpu 0, res 0 310 ....... 311 684 record comm: type 3, misc 8192, size 64 312 685 pid 23365, tid 23365, comm deqp-binary64 313 686 sample_id: pid 23365, tid 23365 314 687 sample_id: time 595063921159958 315 688 sample_id: id 23808 316 689 sample_id: cpu 4, res 0 317 ....... 318 698 record mmap2: type 10, misc 8194, size 136 319 699 pid 23365, tid 23365, addr 0x58b817b000, len 0x3228000 320 700 pgoff 0x0, maj 253, min 9, ino 14709, ino_generation 2575019956 321 701 prot 1, flags 6146, filename /data/local/tmp/deqp-binary64 322 702 sample_id: pid 23365, tid 23365 323 703 sample_id: time 595063921188552 324 704 sample_id: id 23808 325 705 sample_id: cpu 4, res 0 326 327 Args: 328 deps: a set of string containing dEQP dependency. 329 dump: perf dump file's name. 330 """ 331 binary_executed = False 332 correct_mmap = False 333 with open(dump, 'r') as f: 334 for line in f: 335 # It means dEQP binary starts to be executed. 336 if re.search(' comm .*deqp-binary', line): 337 binary_executed = True 338 if not binary_executed: 339 continue 340 # We get a new perf event 341 if not line.startswith(' '): 342 # mmap with misc 1 is not for deqp binary. 343 correct_mmap = line.startswith('record mmap') and 'misc 1,' not in line 344 # Get file name in memory map. 345 if 'filename' in line and correct_mmap: 346 deps_file = line[line.find('filename') + 9:].strip() 347 if not re.search(self._exclude_deqp_pattern, deps_file): 348 deps.add(deps_file) 349 350 351 def get_test_binary_name(self, test_name): 352 """Get dEQP binary's name based on test name. 353 354 Args: 355 test_name: name of test. 356 Returns: 357 dEQP binary's name. 358 """ 359 if test_name.endswith('32'): 360 return 'deqp-binary' 361 elif test_name.endswith('64'): 362 return 'deqp-binary64' 363 else: 364 raise TestError('Fail to get dEQP binary due to unknonw test name: ' + test_name) 365 366 def get_test_log_name(self, test_name): 367 """Get test log's name based on test name. 368 369 Args: 370 test_name: name of test. 371 Returns: 372 test log's name when running dEQP test. 373 """ 374 return test_name + '.qpa' 375 376 def get_test_perf_name(self, test_name): 377 """Get perf file's name based on test name. 378 379 Args: 380 test_name: name of test. 381 Returns: 382 perf file's name. 383 """ 384 return test_name + '.data' 385 386 def get_perf_dump_name(self, test_name): 387 """Get perf dump file's name based on test name. 388 389 Args: 390 test_name: name of test. 391 Returns: 392 perf dump file's name. 393 """ 394 return test_name + '-perf-dump.txt' 395 396 def get_test_list_name(self, test_name): 397 """Get test list file's name based on test name. 398 399 test list file is used to run dEQP test. 400 401 Args: 402 test_name: name of test. 403 Returns: 404 test list file's name. 405 """ 406 if test_name.startswith('vk'): 407 return 'vk-master-subset.txt' 408 elif test_name.startswith('gles3'): 409 return 'gles3-master-subset.txt' 410 else: 411 raise TestError('Fail to get test list due to unknown test name: ' + test_name) 412 413 def get_deqp_dependency(self): 414 """Get dEQP dependency. 415 416 Returns: 417 A set of dEQP dependency. 418 """ 419 device_deqp_dir = '/data/local/tmp' 420 device_deqp_out_dir = '/data/local/tmp/out' 421 test_list = ['vk-32', 'vk-64', 'gles3-32', 'gles3-64'] 422 423 # Clean up the device. 424 self._adb.run_shell_command('mkdir -p ' + device_deqp_out_dir) 425 426 # Copy test resources to device. 427 logger.info(self._adb.push_file(self._test_dir + '/*', device_deqp_dir)) 428 429 # Run the dEQP binary with simpleperf 430 print('Running a subset of dEQP tests as binary on the device...') 431 deqp_deps = set() 432 for test in test_list: 433 test_file = os.path.join(device_deqp_dir, self.get_test_list_name(test)) 434 log_file = os.path.join(device_deqp_out_dir, self.get_test_log_name(test)) 435 perf_file = os.path.join(device_deqp_out_dir, self.get_test_perf_name(test)) 436 deqp_binary = os.path.join(device_deqp_dir, self.get_test_binary_name(test)) 437 simpleperf_command = ('"cd {device_deqp_dir} && simpleperf record -o {perf_file} {binary} ' 438 '--deqp-caselist-file={test_list} --deqp-log-images=disable ' 439 '--deqp-log-shader-sources=disable --deqp-log-filename={log_file} ' 440 '--deqp-surface-type=fbo --deqp-surface-width=2048 ' 441 '--deqp-surface-height=2048"') 442 self._adb.run_shell_command( 443 simpleperf_command.format(device_deqp_dir=device_deqp_dir, binary=deqp_binary, 444 perf_file=perf_file, test_list=test_file, log_file=log_file)) 445 446 # Check test log. 447 host_log_file = os.path.join(self._work_dir, self.get_test_log_name(test)) 448 self._adb.pull_file(log_file, host_log_file ) 449 if not self.check_test_log(os.path.join(self._test_dir, self.get_test_list_name(test)), 450 host_log_file): 451 error_msg = ('Fail to run incremental dEQP because of crashed test. Check test' 452 'log {} for more detail.').format(host_log_file) 453 logger.error(error_msg) 454 raise TestError(error_msg) 455 print('Tests are all passed!') 456 457 # Parse perf dump result to get dependency. 458 print('Analyzing dEQP dependency...') 459 for test in test_list: 460 perf_file = os.path.join(device_deqp_out_dir, self.get_test_perf_name(test)) 461 dump_file = os.path.join(self._work_dir, self.get_perf_dump_name(test)) 462 self._adb.run_shell_command('simpleperf dump {perf_file} > {dump_file}' 463 .format(perf_file=perf_file, dump_file=dump_file)) 464 self.update_dependency(deqp_deps, dump_file) 465 print('Done!') 466 return deqp_deps 467 468def _is_deqp_dependency(dependency_name): 469 """Check if dependency is related to dEQP.""" 470 # dEQP dependency with pattern below will not be used to compare build: 471 # files has /apex/ prefix are not related to dEQP. 472 return not re.search(re.compile('^/apex/'), dependency_name) 473 474def _get_parser(): 475 parser = argparse.ArgumentParser(description='Run incremental dEQP on devices.') 476 parser.add_argument('-s', '--serial', help='Optional. Use device with given serial.') 477 parser.add_argument('-t', '--test', help=('Optional. Directory of incremental deqp test file. ' 478 'This directory should have test resources and dEQP ' 479 'binaries.')) 480 parser.add_argument('-b', '--base_build', help=('Target file of base build that has passed dEQP ' 481 'test, e.g. flame-target_files-6935423.zip.')) 482 parser.add_argument('-c', '--current_build', 483 help=('Optional. When empty, the script will read files in the build from ' 484 'the device via adb. When set, the script will read build files from ' 485 'the file provided by this argument. And this file should be the ' 486 'current build that is flashed to device, such as a target file ' 487 'like flame-target_files-6935424.zip. This argument can be used when ' 488 'some dependencies files are not accessible via adb.')) 489 parser.add_argument('--generate_deps_only', action='store_true', 490 help=('Run test and generate dEQP dependency list only ' 491 'without comparing build.')) 492 parser.add_argument('--custom_handler', action='store_true', 493 help='Use custome build file handler') 494 parser.add_argument('--ats_mode', action='store_true', 495 help=('Run incremental dEQP with Android Test Station.')) 496 parser.add_argument('--userdebug_build', action='store_true', 497 help=('ATS mode option. Current build on device is userdebug.')) 498 return parser 499 500def _create_logger(log_file_name): 501 """Create logger. 502 503 Args: 504 log_file_name: absolute path of the log file. 505 Returns: 506 a logging.Logger 507 """ 508 logging.basicConfig(filename=log_file_name) 509 logger = logging.getLogger() 510 logger.setLevel(level=logging.NOTSET) 511 return logger 512 513def _save_deqp_deps(deqp_deps, file_name): 514 """Save dEQP dependency to file. 515 516 Args: 517 deqp_deps: a set of dEQP dependency. 518 file_name: name of the file to save dEQP dependency. 519 Returns: 520 name of the file that saves dEQP dependency. 521 """ 522 with open(file_name, 'w') as f: 523 for dep in sorted(deqp_deps): 524 f.write(dep+'\n') 525 return file_name 526 527def _generate_report( 528 report_name, 529 base_build_fingerprint, 530 current_build_fingerprint, 531 deqp_deps, 532 extra_deqp_deps, 533 deqp_deps_changes): 534 """Generate a json report. 535 536 Args: 537 report_name: absolute file name of report. 538 base_build_fingerprint: fingerprint of the base build. 539 current_build_fingerprint: fingerprint of the current build. 540 deqp_deps: list of dEQP dependencies generated by the tool. 541 extra_deqp_deps: list of extra dEQP dependencies. 542 deqp_deps_changes: dictionary of dependency changes. 543 """ 544 data = {} 545 data['base_build_fingerprint'] = base_build_fingerprint 546 data['current_build_fingerprint'] = current_build_fingerprint 547 data['deqp_deps'] = sorted(list(deqp_deps)) 548 data['extra_deqp_deps'] = sorted(list(extra_deqp_deps)) 549 data['deqp_deps_changes'] = deqp_deps_changes 550 551 with open(report_name, 'w') as f: 552 json.dump(data, f, indent=4) 553 554 print('Incremental dEQP report is generated at: ' + report_name) 555 556 557def _local_run(args, work_dir): 558 """Run incremental dEQP locally. 559 560 Args: 561 args: return of parser.parse_args(). 562 work_dir: path of directory for saving script result and logs. 563 """ 564 print('Logs and simpleperf results will be copied to: ' + work_dir) 565 if args.test: 566 test_dir = args.test 567 else: 568 test_dir = os.path.dirname(os.path.abspath(__file__)) 569 # Extra dEQP dependencies are the files can't be loaded to memory such as firmware. 570 extra_deqp_deps = set() 571 extra_deqp_deps_file = os.path.join(test_dir, 'extra_deqp_dependency.txt') 572 if not os.path.exists(extra_deqp_deps_file): 573 if not args.generate_deps_only: 574 raise TestResourceError('{test_resource} doesn\'t exist' 575 .format(test_resource=extra_deqp_deps_file)) 576 else: 577 with open(extra_deqp_deps_file, 'r') as f: 578 for line in f: 579 extra_deqp_deps.add(line.strip()) 580 581 if args.serial: 582 adb = AdbHelper(args.serial) 583 else: 584 adb = AdbHelper() 585 586 dependency_collector = DeqpDependencyCollector(work_dir, test_dir, adb) 587 deqp_deps = dependency_collector.get_deqp_dependency() 588 aggregated_deqp_deps = deqp_deps.union(extra_deqp_deps) 589 590 deqp_deps_file_name = _save_deqp_deps(aggregated_deqp_deps, 591 os.path.join(work_dir, 'dEQP-dependency.txt')) 592 print('dEQP dependency list has been generated in: ' + deqp_deps_file_name) 593 594 if args.generate_deps_only: 595 return 596 597 # Compare the build difference with dEQP dependency 598 valid_deqp_deps = [dep for dep in aggregated_deqp_deps if _is_deqp_dependency(dep)] 599 build_helper = BuildHelper(args.custom_handler) 600 if args.current_build: 601 skip_dEQP, changes = build_helper.compare_base_build_with_current_build( 602 valid_deqp_deps, args.current_build, args.base_build) 603 else: 604 skip_dEQP, changes = build_helper.compare_base_build_with_device_files( 605 valid_deqp_deps, adb, args.base_build) 606 if skip_dEQP: 607 print('Congratulations, current build could skip dEQP test.\n' 608 'If you run CTS through suite, you could pass filter like ' 609 '\'--exclude-filter CtsDeqpTestCases\'.') 610 else: 611 print('Sorry, current build can\'t skip dEQP test because dEQP dependency has been ' 612 'changed.\nPlease check logs for more details.') 613 614 _generate_report(os.path.join(work_dir, REPORT_FILENAME), 615 build_helper.get_system_fingerprint(args.base_build), 616 adb.get_fingerprint(), 617 deqp_deps, 618 extra_deqp_deps, 619 changes) 620 621def _generate_cts_xml(out_dir, content): 622 """Generate cts configuration for Android Test Station. 623 624 Args: 625 out_dir: output directory for cts confiugration. 626 content: configuration content. 627 """ 628 with open(os.path.join(out_dir, 'incremental_deqp.xml'), 'w') as f: 629 f.write(content) 630 631 632def _ats_run(args, work_dir): 633 """Run incremental dEQP with Android Test Station. 634 635 Args: 636 args: return of parser.parse_args(). 637 work_dir: path of directory for saving script result and logs. 638 """ 639 # Extra dEQP dependencies are the files can't be loaded to memory such as firmware. 640 extra_deqp_deps = set() 641 with open(os.path.join(work_dir, 'extra_deqp_dependency.txt'), 'r') as f: 642 for line in f: 643 if line.strip(): 644 extra_deqp_deps.add(line.strip()) 645 646 android_serials = os.getenv('ANDROID_SERIALS') 647 if not android_serials: 648 raise AtsError('Fail to read environment variable ANDROID_SERIALS.') 649 first_device_serial = android_serials.split(',')[0] 650 adb = AdbHelper(first_device_serial) 651 652 dependency_collector = DeqpDependencyCollector(work_dir, 653 os.path.join(work_dir, 'test_resources'), adb) 654 deqp_deps = dependency_collector.get_deqp_dependency() 655 aggregated_deqp_deps = deqp_deps.union(extra_deqp_deps) 656 657 deqp_deps_file_name = _save_deqp_deps(aggregated_deqp_deps, 658 os.path.join(work_dir, 'dEQP-dependency.txt')) 659 660 if args.generate_deps_only: 661 _generate_cts_xml(work_dir, DEFAULT_CTS_XML) 662 return 663 664 # Compare the build difference with dEQP dependency 665 valid_deqp_deps = [dep for dep in aggregated_deqp_deps if _is_deqp_dependency(dep)] 666 667 # base build target file is from test resources. 668 base_build_target = os.path.join(work_dir, 'base_build_target_files') 669 build_helper = BuildHelper(args.custom_handler) 670 if args.userdebug_build: 671 current_build_fingerprint = adb.get_fingerprint() 672 skip_dEQP, changes = build_helper.compare_base_build_with_device_files( 673 valid_deqp_deps, adb, base_build_target) 674 else: 675 current_build_target = os.path.join(work_dir, 'current_build_target_files') 676 current_build_fingerprint = build_helper.get_system_fingerprint(current_build_target) 677 skip_dEQP, changes = build_helper.compare_base_build_with_current_build( 678 valid_deqp_deps, current_build_target, base_build_target) 679 if skip_dEQP: 680 _generate_cts_xml(work_dir, INCREMENTAL_DEQP_XML) 681 else: 682 _generate_cts_xml(work_dir, DEFAULT_CTS_XML) 683 684 _generate_report(os.path.join(*[work_dir, 'logs', REPORT_FILENAME]), 685 build_helper.get_system_fingerprint(base_build_target), 686 current_build_fingerprint, 687 deqp_deps, 688 extra_deqp_deps, 689 changes) 690 691def main(): 692 parser = _get_parser() 693 args = parser.parse_args() 694 if not args.generate_deps_only and not args.base_build and not args.ats_mode: 695 parser.error('Base build argument: \'-b [file] or --base_build [file]\' ' 696 'is required to compare build.') 697 698 work_dir = '' 699 log_file_name = '' 700 if args.ats_mode: 701 work_dir = os.getenv('TF_WORK_DIR') 702 log_file_name = os.path.join(*[work_dir, 'logs', 'incremental-deqp-log-'+str(uuid.uuid4())]) 703 else: 704 work_dir = tempfile.mkdtemp(prefix='incremental-deqp-' 705 + time.strftime("%Y%m%d-%H%M%S")) 706 log_file_name = os.path.join(work_dir, 'incremental-deqp-log') 707 global logger 708 logger = _create_logger(log_file_name) 709 710 if args.ats_mode: 711 _ats_run(args, work_dir) 712 else: 713 _local_run(args, work_dir) 714 715if __name__ == '__main__': 716 main() 717 718