1#!/usr/bin/env python
2# Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
3#
4# Use of this source code is governed by a BSD-style license
5# that can be found in the LICENSE file in the root of the source
6# tree. An additional intellectual property rights grant can be found
7# in the file PATENTS.  All contributing project authors may
8# be found in the AUTHORS file in the root of the source tree.
9
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"""
50
51import sys
52
53DIRECTORY = 'out/coverage'
54
55TESTS = [
56  'audio_decoder_unittests',
57  'common_audio_unittests',
58  'common_video_unittests',
59  'modules_tests',
60  'modules_unittests',
61  'rtc_media_unittests',
62  'rtc_pc_unittests',
63  'rtc_stats_unittests',
64  'rtc_unittests',
65  'slow_tests',
66  'system_wrappers_unittests',
67  'test_support_unittests',
68  'tools_unittests',
69  'video_capture_tests',
70  'video_engine_tests',
71  'webrtc_nonparallel_tests',
72]
73
74XC_TESTS = [
75  'apprtcmobile_tests',
76  'sdk_framework_unittests',
77  'sdk_unittests',
78]
79
80
81def FormatIossimTest(test_name, is_xctest=False):
82  args = ['%s/%s.app' % (DIRECTORY, test_name)]
83  if is_xctest:
84    args += ['%s/%s_module.xctest' % (DIRECTORY, test_name)]
85
86  return '-c \'%s/iossim %s\'' % (DIRECTORY, ' '.join(args))
87
88
89def GetGNArgs(is_simulator):
90  target_cpu = 'x64' if is_simulator else 'arm64'
91  return ([] +
92          ['target_os="ios"'] +
93          ['target_cpu="%s"' % target_cpu] +
94          ['use_clang_coverage=true'] +
95          ['is_component_build=false'] +
96          ['dcheck_always_on=true'])
97
98
99def GenerateIOSSimulatorCommand():
100  gn_args_string = ' '.join(GetGNArgs(is_simulator=True))
101  gn_cmd = ['gn', 'gen', DIRECTORY, '--args=\'%s\'' % gn_args_string]
102
103  coverage_cmd = (
104      [sys.executable, 'tools/code_coverage/coverage.py'] +
105      ["%s.app" % t for t in XC_TESTS + TESTS] +
106      ['-b %s' % DIRECTORY, '-o out/report'] +
107      ['-i=\'.*/out/.*|.*/third_party/.*|.*test.*\''] +
108      [FormatIossimTest(t, is_xctest=True) for t in XC_TESTS] +
109      [FormatIossimTest(t, is_xctest=False) for t in TESTS]
110  )
111
112  print 'To get code coverage using iOS simulator just run following commands:'
113  print ''
114  print ' '.join(gn_cmd)
115  print ''
116  print ' '.join(coverage_cmd)
117  return 0
118
119
120def GenerateIOSDeviceCommand():
121  gn_args_string = ' '.join(GetGNArgs(is_simulator=False))
122
123  coverage_report_cmd = (
124      [sys.executable, 'tools/code_coverage/coverage.py'] +
125      ['%s.app' % t for t in TESTS] +
126      ['-b %s' % DIRECTORY] +
127      ['-o out/report'] +
128      ['-p %s/merged.profdata' % DIRECTORY] +
129      ['-i=\'.*/out/.*|.*/third_party/.*|.*test.*\'']
130  )
131
132  print 'Computing code coverage for real iOS device is a little bit tedious.'
133  print ''
134  print 'You will need:'
135  print ''
136  print '1. Generate xcode project and open it with Xcode 10+:'
137  print '  gn gen %s --ide=xcode --args=\'%s\'' % (DIRECTORY, gn_args_string)
138  print '  open %s/all.xcworkspace' % DIRECTORY
139  print ''
140  print '2. Execute these Run targets manually with Xcode Run button and '
141  print 'manually save generated coverage.profraw file to %s:' % DIRECTORY
142  print '\n'.join('- %s' % t for t in TESTS)
143  print ''
144  print '3. Execute these Test targets manually with Xcode Test button and '
145  print 'manually save generated coverage.profraw file to %s:' % DIRECTORY
146  print '\n'.join('- %s' % t for t in XC_TESTS)
147  print ''
148  print '4. Merge *.profraw files to *.profdata using llvm-profdata tool:'
149  print ('  build/mac_files/Xcode.app/Contents/Developer/Toolchains/' +
150         'XcodeDefault.xctoolchain/usr/bin/llvm-profdata merge ' +
151         '-o %s/merged.profdata ' % DIRECTORY +
152         '-sparse=true %s/*.profraw' % DIRECTORY)
153  print ''
154  print '5. Generate coverage report:'
155  print '  ' + ' '.join(coverage_report_cmd)
156  return 0
157
158
159def Main():
160  if len(sys.argv) < 2:
161    print 'Please specify type of coverage:'
162    print '  %s simulator' % sys.argv[0]
163    print '  %s device' % sys.argv[0]
164  elif sys.argv[1] == 'simulator':
165    GenerateIOSSimulatorCommand()
166  elif sys.argv[1] == 'device':
167    GenerateIOSDeviceCommand()
168  else:
169    print 'Unsupported type of coverage'
170
171  return 0
172
173if __name__ == '__main__':
174  sys.exit(Main())
175