xref: /aosp_15_r20/external/webrtc/tools_webrtc/coverage/generate_ios_coverage_command.py (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1#!/usr/bin/env vpython3
2
3# Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
4#
5# Use of this source code is governed by a BSD-style license
6# that can be found in the LICENSE file in the root of the source
7# tree. An additional intellectual property rights grant can be found
8# in the file PATENTS.  All contributing project authors may
9# be found in the AUTHORS file in the root of the source tree.
10"""Generates command-line instructions to produce one-time iOS coverage using
11coverage.py.
12
13This script is usable for both real devices and simulator.
14But for real devices actual execution should be done manually from Xcode
15and coverage.profraw files should be also copied manually from the device.
16
17Additional prerequisites:
18
191. Xcode 10+ with iPhone Simulator SDK. Can be installed by command:
20   $ mac_toolchain install -kind ios -xcode-version 10l232m \
21     -output-dir build/mac_files/Xcode.app
22
232. For computing coverage on real device you probably also need to apply
24following patch to code_coverage/coverage.py script:
25
26========== BEGINNING OF PATCH ==========
27--- a/code_coverage/coverage.py
28+++ b/code_coverage/coverage.py
29@@ -693,8 +693,7 @@ def _AddArchArgumentForIOSIfNeeded(cmd_list, num_archs):
30   to use, and one architecture needs to be specified for each binary.
31   "" "
32if _IsIOS():
33-    cmd_list.extend(['-arch=x86_64'] * num_archs)
34+    cmd_list.extend(['-arch=arm64'] * num_archs)
35
36
37def _GetBinaryPath(command):
38@@ -836,8 +835,8 @@ def _GetBinaryPathsFromTargets(targets, build_dir):
39  binary_path = os.path.join(build_dir, target)
40  if coverage_utils.GetHostPlatform() == 'win':
41    binary_path += '.exe'
42+    elif coverage_utils.GetHostPlatform() == 'mac':
43+      binary_path += '.app/%s' % target
44
45if os.path.exists(binary_path):
46  binary_paths.append(binary_path)
47========== ENDING OF PATCH ==========
48
49"""
50import sys
51
52DIRECTORY = 'out/coverage'
53
54TESTS = [
55    'audio_decoder_unittests',
56    'common_audio_unittests',
57    'common_video_unittests',
58    'modules_tests',
59    'modules_unittests',
60    'rtc_media_unittests',
61    'rtc_pc_unittests',
62    'rtc_stats_unittests',
63    'rtc_unittests',
64    'system_wrappers_unittests',
65    'test_support_unittests',
66    'tools_unittests',
67    'video_capture_tests',
68    'video_engine_tests',
69    'webrtc_nonparallel_tests',
70]
71
72XC_TESTS = [
73    'apprtcmobile_tests',
74    'sdk_framework_unittests',
75    'sdk_unittests',
76]
77
78
79def FormatIossimTest(test_name, is_xctest=False):
80  args = ['%s/%s.app' % (DIRECTORY, test_name)]
81  if is_xctest:
82    args += ['%s/%s_module.xctest' % (DIRECTORY, test_name)]
83
84  return '-c \'%s/iossim %s\'' % (DIRECTORY, ' '.join(args))
85
86
87def GetGNArgs(is_simulator):
88  target_cpu = 'x64' if is_simulator else 'arm64'
89  return ([] + ['target_os="ios"'] + ['target_cpu="%s"' % target_cpu] +
90          ['use_clang_coverage=true'] + ['is_component_build=false'] +
91          ['dcheck_always_on=true'])
92
93
94def GenerateIOSSimulatorCommand():
95  gn_args_string = ' '.join(GetGNArgs(is_simulator=True))
96  gn_cmd = ['gn', 'gen', DIRECTORY, '--args=\'%s\'' % gn_args_string]
97
98  coverage_cmd = ([sys.executable, 'tools/code_coverage/coverage.py'] +
99                  ["%s.app" % t for t in XC_TESTS + TESTS] +
100                  ['-b %s' % DIRECTORY, '-o out/report'] +
101                  ['-i=\'.*/out/.*|.*/third_party/.*|.*test.*\''] +
102                  [FormatIossimTest(t, is_xctest=True) for t in XC_TESTS] +
103                  [FormatIossimTest(t, is_xctest=False) for t in TESTS])
104
105  print('To get code coverage using iOS sim just run following commands:')
106  print('')
107  print(' '.join(gn_cmd))
108  print('')
109  print(' '.join(coverage_cmd))
110  return 0
111
112
113def GenerateIOSDeviceCommand():
114  gn_args_string = ' '.join(GetGNArgs(is_simulator=False))
115
116  coverage_report_cmd = ([sys.executable, 'tools/code_coverage/coverage.py'] +
117                         ['%s.app' % t for t in TESTS] + ['-b %s' % DIRECTORY] +
118                         ['-o out/report'] +
119                         ['-p %s/merged.profdata' % DIRECTORY] +
120                         ['-i=\'.*/out/.*|.*/third_party/.*|.*test.*\''])
121
122  print('Computing code coverage for real iOS device is a little bit tedious.')
123  print('')
124  print('You will need:')
125  print('')
126  print('1. Generate xcode project and open it with Xcode 10+:')
127  print('  gn gen %s --ide=xcode --args=\'%s\'' % (DIRECTORY, gn_args_string))
128  print('  open %s/all.xcworkspace' % DIRECTORY)
129  print('')
130  print('2. Execute these Run targets manually with Xcode Run button and ')
131  print('manually save generated coverage.profraw file to %s:' % DIRECTORY)
132  print('\n'.join('- %s' % t for t in TESTS))
133  print('')
134  print('3. Execute these Test targets manually with Xcode Test button and ')
135  print('manually save generated coverage.profraw file to %s:' % DIRECTORY)
136  print('\n'.join('- %s' % t for t in XC_TESTS))
137  print('')
138  print('4. Merge *.profraw files to *.profdata using llvm-profdata tool:')
139  print(('  build/mac_files/Xcode.app/Contents/Developer/Toolchains/' +
140         'XcodeDefault.xctoolchain/usr/bin/llvm-profdata merge ' +
141         '-o %s/merged.profdata ' % DIRECTORY +
142         '-sparse=true %s/*.profraw' % DIRECTORY))
143  print('')
144  print('5. Generate coverage report:')
145  print('  ' + ' '.join(coverage_report_cmd))
146  return 0
147
148
149def main():
150  if len(sys.argv) < 2:
151    print('Please specify type of coverage:')
152    print('  %s simulator' % sys.argv[0])
153    print('  %s device' % sys.argv[0])
154  elif sys.argv[1] == 'simulator':
155    GenerateIOSSimulatorCommand()
156  elif sys.argv[1] == 'device':
157    GenerateIOSDeviceCommand()
158  else:
159    print('Unsupported type of coverage')
160
161  return 0
162
163
164if __name__ == '__main__':
165  sys.exit(main())
166