• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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