Commit df93a886 authored by lpromero's avatar lpromero Committed by Commit bot

Add tools for code coverage support in iOS.

In this CL, support is added to all unit tests targets and the Showcase
Earl Grey tests.

BUG=676034
R=sdefresne@chromium.org,marq@chromium.org,lindsayw@chromium.org

Review-Url: https://codereview.chromium.org/2789433004
Cr-Commit-Position: refs/heads/master@{#469301}
parent b6bec5fd
......@@ -152,6 +152,8 @@ static char** g_argv;
}
- (void)runTests {
coverage_util::ConfigureCoverageReportPath();
int exitStatus = g_test_suite->Run();
if ([self shouldRedirectOutputToFile])
......@@ -169,8 +171,6 @@ static char** g_argv;
UIApplication* application = [UIApplication sharedApplication];
[application _terminateWithStatus:exitStatus];
coverage_util::FlushCoverageDataIfNecessary();
exit(exitStatus);
}
......
......@@ -112,6 +112,10 @@ config("runtime_library") {
cflags_cc = common_cc_flags
cflags_objcc = common_cc_flags
}
if (ios_enable_coverage) {
configs = [ ":enable_coverage" ]
}
}
config("ios_executable_flags") {
......@@ -136,6 +140,16 @@ config("xctest_config") {
]
}
# This enables support for LLVM code coverage. See
# http://llvm.org/docs/CoverageMappingFormat.html.
config("enable_coverage") {
cflags = [
"-fprofile-instr-generate",
"-fcoverage-mapping",
]
ldflags = [ "-fprofile-instr-generate" ]
}
group("xctest") {
public_configs = [ ":xctest_config" ]
}
......@@ -37,6 +37,10 @@ declare_args() {
# to avoid running out of certificates if using a free account.
ios_automatically_manage_certs = true
# Enabling this option makes clang compile for profiling to gather code
# coverage metrics.
ios_enable_coverage = false
# If non-empty, this list must contain valid cpu architecture, and the final
# build will be a multi-architecture build (aka fat build) supporting the
# main $target_cpu architecture and all of $additional_target_cpus.
......
......@@ -3,6 +3,10 @@
# found in the LICENSE file.
import("//build_overrides/gtest.gni")
if (is_ios) {
import("//build/config/ios/ios_sdk.gni")
import("//build/buildflag_header.gni")
}
config("gtest_config") {
visibility = [
......@@ -72,6 +76,7 @@ static_library("gtest") {
"src/gtest-typed-test.cc",
"src/gtest.cc",
]
deps = []
if (gtest_include_multiprocess) {
sources += [
......@@ -100,9 +105,10 @@ static_library("gtest") {
if (is_ios && gtest_include_ios_coverage) {
sources += [
"../coverage_util_ios.cc",
"../coverage_util_ios.h",
"../coverage_util_ios.mm",
]
deps += [ ":ios_enable_coverage" ]
}
include_dirs = [ "." ]
......@@ -128,3 +134,10 @@ source_set("gtest_main") {
":gtest",
]
}
if (is_ios) {
buildflag_header("ios_enable_coverage") {
header = "ios_enable_coverage.h"
flags = [ "IOS_ENABLE_COVERAGE=$ios_enable_coverage" ]
}
}
# Generate code coverage data
1. Build a test target for coverage:
```
ninja -C out/Coverage-iphonesimulator ios_chrome_unittests
```
Targets that support code coverage need to call
`coverage_util::ConfigureCoverageReportPath()`.
If you don't use `setup-gn.py`, you can set the gn argument
`ios_enable_coverage` to `true`.
1. Run the test target. If using Xcode, don't forget to set `Coverage` in the
target's scheme:
![](images/coverage_xcode.png)
1. Find the `coverage.profraw` in the `Documents` folder of the app. You can
look in the console output of the instrumented target. For example:
```
Coverage data at /Users/johndoe/Library/Developer/CoreSimulator/Devices/
82D642FA-FC18-4EDB-AFE0-A17454804BE4/data/Containers/Data/Application/
E6B2B898-CE13-4958-93F3-E8B500446381/Documents/coverage.profraw
```
1. Create a `coverage.profdata` file out of the `coverage.profraw` file:
```
xcrun llvm-profdata merge \
-o out/Coverage-iphonesimulator/coverage.profdata \
path/to/coverage.profraw
```
1. To see the **line coverage** for *all the instrumented source files*:
```
xcrun llvm-cov show \
out/Coverage-iphonesimulator/ios_chrome_unittests.app/ios_chrome_unittests \
-instr-profile=out/Coverage-iphonesimulator/coverage.profdata \
-arch=x86_64
```
![](images/llvm-cov_show.png)
To see the **line coverage** for a *specific instrumented source
file/folder* (e.g.
`ios/chrome/browser/ui/coordinators/browser_coordinator.mm`):
```
xcrun llvm-cov show \
out/Coverage-iphonesimulator/ios_chrome_unittests.app/ios_chrome_unittests \
-instr-profile=out/Coverage-iphonesimulator/coverage.profdata \
-arch=x86_64 ios/chrome/browser/ui/coordinators/browser_coordinator.mm
```
![](images/llvm-cov_show_file.png)
To see a **complete report**:
```
xcrun llvm-cov report \
out/Coverage-iphonesimulator/ios_chrome_unittests.app/ios_chrome_unittests \
-instr-profile=path/to/coverage.profdata -arch=x86_64
```
![](images/llvm-cov_report.png)
To see a **report** for a *folder/file* (e.g. `ios/chrome/browser` folder):
```
xcrun llvm-cov show \
out/Coverage-iphonesimulator/ios_chrome_unittests.app/ios_chrome_unittests \
-instr-profile=path/to/coverage.profdata -arch=x86_64 ios/chrome/browser
```
![](images/llvm-cov_report_folder.png)
......@@ -21,7 +21,7 @@ except ImportError:
SUPPORTED_TARGETS = ('iphoneos', 'iphonesimulator')
SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official')
SUPPORTED_CONFIGS = ('Debug', 'Release', 'Profile', 'Official', 'Coverage')
class ConfigParserWithStringInterpolation(ConfigParser.SafeConfigParser):
......@@ -90,12 +90,13 @@ class GnGenerator(object):
if goma_dir:
args.append(('goma_dir', '"%s"' % os.path.expanduser(goma_dir)))
args.append(('is_debug', self._config == 'Debug'))
args.append(('is_debug', self._config in ('Debug', 'Coverage')))
args.append(('enable_dsyms', self._config in ('Profile', 'Official')))
args.append(('enable_stripping', 'enable_dsyms'))
args.append(('is_official_build', self._config == 'Official'))
args.append(('is_chrome_branded', 'is_official_build'))
args.append(('use_xcode_clang', 'is_official_build'))
args.append(('ios_enable_coverage', self._config == 'Coverage'))
if os.environ.get('FORCE_MAC_TOOLCHAIN', '0') == '1':
args.append(('use_system_xcode', False))
......
......@@ -14,6 +14,7 @@ source_set("test") {
"//base",
"//ios/testing/earl_grey:earl_grey_support",
"//ios/third_party/earl_grey",
"//testing/gtest",
]
public_deps = [
"//build/config/ios:xctest",
......
......@@ -7,6 +7,7 @@
#import <EarlGrey/EarlGrey.h>
#import "base/logging.h"
#include "testing/coverage_util_ios.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
......@@ -14,6 +15,10 @@
@implementation ShowcaseTestCase
+ (void)setUp {
coverage_util::ConfigureCoverageReportPath();
}
// Overrides testInvocations so the set of tests run can be modified, as
// necessary.
+ (NSArray*)testInvocations {
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
extern "C" void __gcov_flush(void);
namespace coverage_util {
void FlushCoverageDataIfNecessary() {
#if !defined(NDEBUG) && defined(ENABLE_TEST_CODE_COVERAGE)
__gcov_flush();
#endif
}
} // namespace coverage_util
......@@ -7,10 +7,9 @@
namespace coverage_util {
// Flushes .gcda coverage files if ENABLE_TEST_CODE_COVERAGE is defined. iOS 7
// does not call any code at the "end" of an app so flushing should be
// performed manually.
void FlushCoverageDataIfNecessary();
// In debug builds, if IOS_ENABLE_COVERAGE is defined, sets the filename of the
// coverage file. Otherwise, it does nothing.
void ConfigureCoverageReportPath();
} // namespace coverage_util
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Foundation/Foundation.h>
#import "testing/gtest/ios_enable_coverage.h"
#if !defined(NDEBUG) && BUILDFLAG(IOS_ENABLE_COVERAGE)
extern "C" void __llvm_profile_set_filename(const char* name);
#endif
namespace coverage_util {
void ConfigureCoverageReportPath() {
#if !defined(NDEBUG) && BUILDFLAG(IOS_ENABLE_COVERAGE)
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
// Writes the profraw file to the Documents directory, where the app has
// write rights.
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString* documents_directory = [paths firstObject];
NSString* file_name = [documents_directory
stringByAppendingPathComponent:@"coverage.profraw"];
// For documentation, see:
// http://clang.llvm.org/docs/SourceBasedCodeCoverage.html
__llvm_profile_set_filename(
[file_name cStringUsingEncoding:NSUTF8StringEncoding]);
// Print the path for easier retrieval.
NSLog(@"Coverage data at %@.", file_name);
});
#endif // !defined(NDEBUG) && BUILDFLAG(IOS_ENABLE_COVERAGE)
}
} // namespace coverage_util
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment