Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6d7da602 authored by Charlie Boutier's avatar Charlie Boutier Committed by Automerger Merge Worker
Browse files

Merge "[Pandora] Add helper script to generate coverage" am: bf4488d5 am:...

Merge "[Pandora] Add helper script to generate coverage" am: bf4488d5 am: 16289b29 am: 4ce16598 am: 4d9c1cb9 am: 46aff994

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/2240898



Change-Id: I29c7cd2d784d133034c6a8a6dec4ceed11797935
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 97a2dd31 46aff994
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
trace*
log*
out*
+320 −0
Original line number Diff line number Diff line
#!/usr/bin/env python

import argparse
import os
from pathlib import Path
import shutil
import subprocess
import sys
import xml.etree.ElementTree as ET


def run_pts_bot():
  run_pts_bot_cmd = [
      # atest command with verbose mode.
      'atest',
      '-d',
      '-v',
      'pts-bot',
      # Coverage tool chains and specify that coverage should be flush to the
      # disk between each tests.
      '--',
      '--coverage',
      '--coverage-toolchain JACOCO',
      '--coverage-toolchain CLANG',
      '--coverage-flush',
  ]
  subprocess.run(run_pts_bot_cmd).returncode


def run_unit_tests():

  # Output logs directory
  logs_out = Path('logs_bt_tests')
  logs_out.mkdir(exist_ok=True)

  mts_tests = []
  android_build_top = os.getenv('ANDROID_BUILD_TOP')
  mts_xml = ET.parse(
      f'{android_build_top}/test/mts/tools/mts-tradefed/res/config/mts-bluetooth-tests-list.xml'
  )

  for child in mts_xml.getroot():
    value = child.attrib['value']
    if 'enable:true' in value:
      test = value.replace(':enable:true', '')
      mts_tests.append(test)

  for test in mts_tests:
    print(f'Test started: {test}')

    # Env variables necessary for native unit tests.
    env = os.environ.copy()
    env['CLANG_COVERAGE_CONTINUOUS_MODE'] = 'true'
    env['CLANG_COVERAGE'] = 'true'
    env['NATIVE_COVERAGE_PATHS'] = 'packages/modules/Bluetooth'
    run_test_cmd = [
        # atest command with verbose mode.
        'atest',
        '-d',
        '-v',
        test,
        # Coverage tool chains and specify that coverage should be flush to the
        # disk between each tests.
        '--',
        '--coverage',
        '--coverage-toolchain JACOCO',
        '--coverage-toolchain CLANG',
        '--coverage-flush',
        # Allows tests to use hidden APIs.
        '--test-arg '
        'com.android.compatibility.testtype.LibcoreTest:hidden-api-checks:false',
        '--test-arg '
        'com.android.tradefed.testtype.AndroidJUnitTest:hidden-api-checks:false',
        '--test-arg '
        'com.android.tradefed.testtype.InstrumentationTest:hidden-api-checks:false',
        '--skip-system-status-check '
        'com.android.tradefed.suite.checker.ShellStatusChecker',
    ]
    with open(f'{logs_out}/{test}.txt', 'w') as f:
      returncode = subprocess.run(
          run_test_cmd, env=env, stdout=f, stderr=subprocess.STDOUT).returncode
      print(
          f'Test ended [{"Success" if returncode == 0 else "Failed"}]: {test}')


def generate_java_coverage(bt_apex_name, trace_path, coverage_out):

  out = os.getenv('OUT')
  android_host_out = os.getenv('ANDROID_HOST_OUT')

  java_coverage_out = Path(f'{coverage_out}/java')
  temp_path = Path(f'{coverage_out}/temp')
  if temp_path.exists():
    shutil.rmtree(temp_path, ignore_errors=True)
  temp_path.mkdir()

  framework_jar_path = Path(
      f'{out}/obj/PACKAGING/jacoco_intermediates/JAVA_LIBRARIES/framework-bluetooth.{bt_apex_name}_intermediates'
  )
  service_jar_path = Path(
      f'{out}/obj/PACKAGING/jacoco_intermediates/JAVA_LIBRARIES/service-bluetooth.{bt_apex_name}_intermediates'
  )
  app_jar_path = Path(
      f'{out}/obj/PACKAGING/jacoco_intermediates/ETC/Bluetooth{"Google" if "com.google" in bt_apex_name else ""}.{bt_apex_name}_intermediates'
  )

  # From google3/configs/wireless/android/testing/atp/prod/mainline-engprod/templates/modules/bluetooth.gcl.
  framework_exclude_classes = [
      '**/com/android/bluetooth/x/**/*.class',
      '**/*Test$*.class',
      '**/android/bluetooth/I*$Default.class',
      '**/android/bluetooth/**/I*$Default.class',
      '**/android/bluetooth/I*$Stub.class',
      '**/android/bluetooth/**/I*$Stub.class',
      '**/android/bluetooth/I*$Stub$Proxy.class',
      '**/android/bluetooth/**/I*$Stub$Proxy.class',
      '**/com/android/internal/util/**/*.class',
      '**/android/net/**/*.class',
  ]
  service_exclude_classes = [
      '**/com/android/bluetooth/x/**/*.class',
      '**/androidx/**/*.class',
      '**/android/net/**/*.class',
      '**/android/support/**/*.class',
      '**/kotlin/**/*.class',
      '**/*Test$*.class',
      '**/com/android/internal/annotations/**/*.class',
      '**/android/annotation/**/*.class',
      '**/android/net/**/*.class',
  ]
  app_exclude_classes = [
      '**/*Test$*.class',
      '**/com/android/bluetooth/x/**/*.class',
      '**/com/android/internal/annotations/**/*.class',
      '**/com/android/internal/util/**/*.class',
      '**/android/annotation/**/*.class',
      '**/android/net/**/*.class',
      '**/android/support/v4/**/*.class',
      '**/androidx/**/*.class',
      '**/kotlin/**/*.class',
      '**/com/google/**/*.class',
      '**/javax/**/*.class',
      '**/android/hardware/**/*.class',  # Added
      '**/android/hidl/**/*.class',  # Added
      '**/com/android/bluetooth/**/BluetoothMetrics*.class',  # Added
  ]

  # Merged ec files.
  merged_ec_path = Path(f'{temp_path}/merged.ec')
  subprocess.run((
      f'java -jar {android_host_out}/framework/jacoco-cli.jar merge {trace_path.absolute()}/*.ec '
      f'--destfile {merged_ec_path.absolute()}'),
                 shell=True)

  # Copy and extract jar files.
  framework_temp_path = Path(f'{temp_path}/{framework_jar_path.name}')
  service_temp_path = Path(f'{temp_path}/{service_jar_path.name}')
  app_temp_path = Path(f'{temp_path}/{app_jar_path.name}')

  shutil.copytree(framework_jar_path, framework_temp_path)
  shutil.copytree(service_jar_path, service_temp_path)
  shutil.copytree(app_jar_path, app_temp_path)

  current_dir_path = Path.cwd()
  for p in [framework_temp_path, service_temp_path, app_temp_path]:
    os.chdir(p.absolute())
    os.system('jar xf jacoco-report-classes.jar')
    os.chdir(current_dir_path)

  os.remove(f'{framework_temp_path}/jacoco-report-classes.jar')
  os.remove(f'{service_temp_path}/jacoco-report-classes.jar')
  os.remove(f'{app_temp_path}/jacoco-report-classes.jar')

  # Generate coverage report.
  exclude_classes = []
  for glob in framework_exclude_classes:
    exclude_classes.extend(list(framework_temp_path.glob(glob)))
  for glob in service_exclude_classes:
    exclude_classes.extend(list(service_temp_path.glob(glob)))
  for glob in app_exclude_classes:
    exclude_classes.extend(list(app_temp_path.glob(glob)))

  for c in exclude_classes:
    if c.exists():
      os.remove(c.absolute())

  gen_java_cov_report_cmd = [
      f'java',
      f'-jar',
      f'{android_host_out}/framework/jacoco-cli.jar',
      f'report',
      f'{merged_ec_path.absolute()}',
      f'--classfiles',
      f'{temp_path.absolute()}',
      f'--html',
      f'{java_coverage_out.absolute()}',
      f'--name',
      f'{java_coverage_out.absolute()}.html',
  ]
  subprocess.run(gen_java_cov_report_cmd)

  # Cleanup.
  shutil.rmtree(temp_path, ignore_errors=True)


def generate_native_coverage(bt_apex_name, trace_path, coverage_out):

  out = os.getenv('OUT')
  android_build_top = os.getenv('ANDROID_BUILD_TOP')

  native_coverage_out = Path(f'{coverage_out}/native')
  temp_path = Path(f'{coverage_out}/temp')
  if temp_path.exists():
    shutil.rmtree(temp_path, ignore_errors=True)
  temp_path.mkdir()

  # From google3/configs/wireless/android/testing/atp/prod/mainline-engprod/templates/modules/bluetooth.gcl.
  exclude_files = {
      'system/.*_aidl.*',
      'system/.*_test.*',
      'system/.*_mock.*',
      'system/.*_unittest.*',
      'system/binder/',
      'system/blueberry/',
      'system/build/',
      'system/conf/',
      'system/doc/',
      'system/test/',
      'system/gd/l2cap/',
      'system/gd/security/',
      'system/gd/neighbor/',
      # 'android/', # Should not be excluded
  }

  # Merge profdata files.
  profdata_path = Path(f'{temp_path}/coverage.profdata')
  subprocess.run(
      f'llvm-profdata merge --sparse -o {profdata_path.absolute()} {trace_path.absolute()}/*.profraw',
      shell=True)

  gen_native_cov_report_cmd = [
      f'llvm-cov',
      f'show',
      f'-format=html',
      f'-output-dir={native_coverage_out.absolute()}',
      f'-instr-profile={profdata_path.absolute()}',
      f'{out}/symbols/apex/{bt_apex_name}/lib64/libbluetooth_jni.so',
      f'-path-equivalence=/proc/self/cwd,{android_build_top}',
      f'/proc/self/cwd/packages/modules/Bluetooth',
  ]
  for f in exclude_files:
    gen_native_cov_report_cmd.append(f'-ignore-filename-regex={f}')
  subprocess.run(gen_native_cov_report_cmd, cwd=android_build_top)

  # Cleanup.
  shutil.rmtree(temp_path, ignore_errors=True)


if __name__ == '__main__':

  parser = argparse.ArgumentParser()
  parser.add_argument(
      '--apex-name',
      default='com.android.btservices',
      help='bluetooth apex name. Default: com.android.btservices')
  parser.add_argument(
      '--java', action='store_true', help='generate Java coverage')
  parser.add_argument(
      '--native', action='store_true', help='generate native coverage')
  parser.add_argument(
      '--out',
      type=str,
      default='out_coverage',
      help='out directory for coverage reports. Default: ./out_coverage')
  parser.add_argument(
      '--trace',
      type=str,
      default='trace',
      help='trace directory with .ec and .profraw files. Default: ./trace')
  parser.add_argument(
      '--full-report',
      action='store_true',
      help='run all tests and compute coverage report')
  args = parser.parse_args()

  coverage_out = Path(args.out)
  shutil.rmtree(coverage_out, ignore_errors=True)
  coverage_out.mkdir()

  if not args.full_report:
    trace_path = Path(args.trace)
    if (not trace_path.exists() or not trace_path.is_dir()):
      sys.exit('Trace directory does not exist')

    if (args.java):
      generate_java_coverage(args.bt_apex_name, trace_path, coverage_out)
    if (args.native):
      generate_native_coverage(args.bt_apex_name, trace_path, coverage_out)

  else:
    # Compute Pandora coverage.
    run_pts_bot()
    coverage_out_pandora = Path(f'{coverage_out}/pandora')
    coverage_out_pandora.mkdir()
    trace_pandora = Path('trace_pandora')
    subprocess.run(['adb', 'pull', '/data/misc/trace', trace_pandora])
    generate_java_coverage(args.bt_apex_name, trace_pandora,
                           coverage_out_pandora)
    generate_native_coverage(args.bt_apex_name, trace_pandora,
                             coverage_out_pandora)

    # Compute all coverage.
    run_unit_tests()
    coverage_out_mainline = Path(f'{coverage_out}/mainline')
    coverage_out_pandora.mkdir()
    trace_all = Path('trace_all')
    subprocess.run(['adb', 'pull', '/data/misc/trace', trace_all])
    generate_java_coverage(args.bt_apex_name, trace_all, coverage_out_mainline)
    generate_native_coverage(args.bt_apex_name, trace_all,
                             coverage_out_mainline)