react-native-coverage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Coverage.podspec +20 -0
- package/LICENSE +202 -0
- package/README.md +125 -0
- package/android/build.gradle +51 -0
- package/android/rn-coverage-jacoco.gradle +132 -0
- package/android/rn-coverage.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/coverage/CoverageModule.kt +53 -0
- package/android/src/main/java/com/coverage/CoveragePackage.kt +35 -0
- package/app.plugin.js +1 -0
- package/bin/rn-coverage.js +11 -0
- package/cocoapods/coverage_post_install.rb +197 -0
- package/docs/cli.md +51 -0
- package/docs/config.md +27 -0
- package/docs/index.md +16 -0
- package/docs/integration/android.md +83 -0
- package/docs/integration/ci-appium.md +41 -0
- package/docs/integration/e2e-timing.md +20 -0
- package/docs/integration/ios.md +77 -0
- package/docs/integration/js.md +38 -0
- package/docs/pattern-c.md +5 -0
- package/docs/releasing.md +88 -0
- package/docs.json +24 -0
- package/ios/Coverage.h +5 -0
- package/ios/Coverage.mm +47 -0
- package/ios/CoverageConfig.h +21 -0
- package/ios/CoverageProfile.h +22 -0
- package/ios/CoverageProfile.mm +358 -0
- package/lib/module/NativeCoverage.js +10 -0
- package/lib/module/NativeCoverage.js.map +1 -0
- package/lib/module/assert-coverage.js +181 -0
- package/lib/module/assert-coverage.js.map +1 -0
- package/lib/module/cli/index.js +229 -0
- package/lib/module/cli/index.js.map +1 -0
- package/lib/module/config.js +83 -0
- package/lib/module/config.js.map +1 -0
- package/lib/module/exit-codes.js +22 -0
- package/lib/module/exit-codes.js.map +1 -0
- package/lib/module/index.js +35 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/js-coverage.js +172 -0
- package/lib/module/js-coverage.js.map +1 -0
- package/lib/module/node.js +15 -0
- package/lib/module/node.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/path-rewrite.js +44 -0
- package/lib/module/path-rewrite.js.map +1 -0
- package/lib/module/process-ios-native-coverage.js +258 -0
- package/lib/module/process-ios-native-coverage.js.map +1 -0
- package/lib/module/pull-native-coverage.js +210 -0
- package/lib/module/pull-native-coverage.js.map +1 -0
- package/lib/typescript/example-dynamic/App.d.ts +6 -0
- package/lib/typescript/example-dynamic/App.d.ts.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeCoverage.d.ts +16 -0
- package/lib/typescript/src/NativeCoverage.d.ts.map +1 -0
- package/lib/typescript/src/assert-coverage.d.ts +53 -0
- package/lib/typescript/src/assert-coverage.d.ts.map +1 -0
- package/lib/typescript/src/cli/index.d.ts +4 -0
- package/lib/typescript/src/cli/index.d.ts.map +1 -0
- package/lib/typescript/src/config.d.ts +93 -0
- package/lib/typescript/src/config.d.ts.map +1 -0
- package/lib/typescript/src/exit-codes.d.ts +16 -0
- package/lib/typescript/src/exit-codes.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/js-coverage.d.ts +36 -0
- package/lib/typescript/src/js-coverage.d.ts.map +1 -0
- package/lib/typescript/src/node.d.ts +14 -0
- package/lib/typescript/src/node.d.ts.map +1 -0
- package/lib/typescript/src/path-rewrite.d.ts +12 -0
- package/lib/typescript/src/path-rewrite.d.ts.map +1 -0
- package/lib/typescript/src/process-ios-native-coverage.d.ts +61 -0
- package/lib/typescript/src/process-ios-native-coverage.d.ts.map +1 -0
- package/lib/typescript/src/pull-native-coverage.d.ts +38 -0
- package/lib/typescript/src/pull-native-coverage.d.ts.map +1 -0
- package/package.json +184 -0
- package/plugin/build/index.d.ts +21 -0
- package/plugin/build/index.js +153 -0
- package/react-native-coverage.config.js.example +47 -0
- package/react-native.config.js +9 -0
- package/src/NativeCoverage.ts +16 -0
- package/src/assert-coverage.ts +260 -0
- package/src/cli/index.ts +420 -0
- package/src/config.ts +189 -0
- package/src/exit-codes.ts +20 -0
- package/src/index.tsx +35 -0
- package/src/js-coverage.ts +213 -0
- package/src/node.ts +52 -0
- package/src/path-rewrite.ts +52 -0
- package/src/process-ios-native-coverage.ts +425 -0
- package/src/pull-native-coverage.ts +269 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
Operator guide for publishing `react-native-coverage`. Releases are **manual only** (`workflow_dispatch`). There is no auto-publish on push to `main`.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
| Item | Notes |
|
|
8
|
+
|------|--------|
|
|
9
|
+
| GitHub repo | `invertase/react-native-coverage` (see [Create the GitHub repo](#create-the-github-repo) if missing) |
|
|
10
|
+
| Conventional Commits | PR titles enforced by `.github/workflows/pr-title.yml`; squash-merge preferred |
|
|
11
|
+
| Green `main` | Unit + Appium e2e CI from `.github/workflows/ci.yml` |
|
|
12
|
+
| First npm publish | **Human OTP bootstrap** — first human publish, not this release scaffolding |
|
|
13
|
+
| npm OIDC | Configure Trusted Publisher **after** bootstrap; release workflow already has `id-token: write` |
|
|
14
|
+
|
|
15
|
+
## Create the GitHub repo
|
|
16
|
+
|
|
17
|
+
Local clone has **no `origin` remote** until a human creates the GitHub repository. From the package root:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
cd /path/to/react-native-coverage
|
|
21
|
+
|
|
22
|
+
# Create under invertase and set origin (public OSS / Apache-2.0):
|
|
23
|
+
gh repo create invertase/react-native-coverage \
|
|
24
|
+
--public \
|
|
25
|
+
--source=. \
|
|
26
|
+
--remote=origin \
|
|
27
|
+
--description "Native code coverage tooling for React Native test apps (Pattern C)"
|
|
28
|
+
|
|
29
|
+
# Push main (after reviewing uncommitted scaffolding / approved commits):
|
|
30
|
+
git push -u origin main
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Optional: create empty on GitHub first, then:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
git remote add origin git@github.com:invertase/react-native-coverage.git
|
|
37
|
+
git push -u origin main
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Confirm: `gh repo view invertase/react-native-coverage`.
|
|
41
|
+
|
|
42
|
+
## Conventional Commits
|
|
43
|
+
|
|
44
|
+
- Prefer **squash merge**; the PR title becomes the release-relevant commit.
|
|
45
|
+
- PR titles must match [Conventional Commits](https://www.conventionalcommits.org/) (enforced in CI).
|
|
46
|
+
- Local check (optional):
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
echo "feat: add assert command" | yarn commitlint
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Examples: `feat: …`, `fix: …`, `docs: …`, `chore: …`. Breaking changes: `feat!: …` or a `BREAKING CHANGE:` footer.
|
|
53
|
+
|
|
54
|
+
## Release tooling
|
|
55
|
+
|
|
56
|
+
| Piece | Path / command |
|
|
57
|
+
|-------|----------------|
|
|
58
|
+
| Config | `release.config.js` |
|
|
59
|
+
| Workflow | `.github/workflows/release.yml` (`workflow_dispatch` only) |
|
|
60
|
+
| Analyzer | Conventional Commits via `@semantic-release/commit-analyzer` |
|
|
61
|
+
| Publish | `@semantic-release/npm` + `@semantic-release/github` |
|
|
62
|
+
| **Not used** | `@semantic-release/git` — dropped so releases do not need a bot push past branch protection; version tags + GitHub Releases carry the release, not in-repo `package.json` bumps |
|
|
63
|
+
|
|
64
|
+
Dry-run (no publish; needs network + git history):
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
yarn release:dry-run
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Operator: CI release (after bootstrap)
|
|
71
|
+
|
|
72
|
+
Do **not** run a real publish until human bootstrap + OIDC Trusted Publisher setup are complete.
|
|
73
|
+
|
|
74
|
+
1. Ensure `main` is green.
|
|
75
|
+
2. GitHub → **Actions** → **Release** → **Run workflow** (branch `main`).
|
|
76
|
+
3. Confirm the run created a GitHub Release + npm version with provenance.
|
|
77
|
+
4. If nothing published: semantic-release found no releasable commits since the last tag (expected when only `chore`/`docs` landed).
|
|
78
|
+
|
|
79
|
+
## First human publish and OIDC setup (do not do via this workflow alone)
|
|
80
|
+
|
|
81
|
+
- First human `npm publish` (OTP), including bootstrap version / dist-tag so tags do not collide with later semantic-release versions
|
|
82
|
+
- npm package settings → **Trusted Publisher** for `invertase/react-native-coverage` workflow `.github/workflows/release.yml`
|
|
83
|
+
- Second publish via this Release workflow to prove OIDC + provenance
|
|
84
|
+
|
|
85
|
+
## Rollback notes
|
|
86
|
+
|
|
87
|
+
- npm: deprecate or unpublish only within npm policy; prefer a follow-up patch release.
|
|
88
|
+
- Git: delete a mistaken `v*` tag only if it was never consumed; coordinate with maintainers.
|
package/docs.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-coverage",
|
|
3
|
+
"description": "Native + JS/TS coverage tooling for React Native test apps",
|
|
4
|
+
"sidebar": [
|
|
5
|
+
{
|
|
6
|
+
"group": "Getting Started",
|
|
7
|
+
"pages": ["index", "pattern-c"]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"group": "Integration",
|
|
11
|
+
"pages": [
|
|
12
|
+
"integration/android",
|
|
13
|
+
"integration/ios",
|
|
14
|
+
"integration/js",
|
|
15
|
+
"integration/e2e-timing",
|
|
16
|
+
"integration/ci-appium"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"group": "Reference",
|
|
21
|
+
"pages": ["cli", "config", "releasing"]
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
package/ios/Coverage.h
ADDED
package/ios/Coverage.mm
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#import "Coverage.h"
|
|
2
|
+
#import "CoverageProfile.h"
|
|
3
|
+
|
|
4
|
+
@implementation Coverage
|
|
5
|
+
|
|
6
|
+
- (void)flush
|
|
7
|
+
{
|
|
8
|
+
int status = CoverageFlushProfile();
|
|
9
|
+
if (status != 0) {
|
|
10
|
+
NSLog(@"[ios-native-coverage] TurboModule flush returned status=%d", status);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
- (void)dumpJsCoverage:(NSString *)json
|
|
15
|
+
{
|
|
16
|
+
NSArray<NSString *> *paths =
|
|
17
|
+
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
18
|
+
NSString *docs = paths.firstObject;
|
|
19
|
+
if (docs == nil) {
|
|
20
|
+
NSLog(@"[ios-native-coverage] dumpJsCoverage: Documents directory missing");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
NSString *path = [docs stringByAppendingPathComponent:@"coverage-final.json"];
|
|
24
|
+
NSError *error = nil;
|
|
25
|
+
BOOL ok = [json writeToFile:path
|
|
26
|
+
atomically:YES
|
|
27
|
+
encoding:NSUTF8StringEncoding
|
|
28
|
+
error:&error];
|
|
29
|
+
if (!ok) {
|
|
30
|
+
NSLog(@"[ios-native-coverage] dumpJsCoverage failed: %@", error);
|
|
31
|
+
} else {
|
|
32
|
+
NSLog(@"[ios-native-coverage] wrote JS coverage to %@", path);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
37
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
38
|
+
{
|
|
39
|
+
return std::make_shared<facebook::react::NativeCoverageSpecJSI>(params);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
+ (NSString *)moduleName
|
|
43
|
+
{
|
|
44
|
+
return @"Coverage";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated by react-native-coverage CocoaPods helper.
|
|
3
|
+
* Do not edit by hand — regenerate via apply_post_install! /
|
|
4
|
+
* Expo config plugin `frameworkNamePrefixes`.
|
|
5
|
+
*/
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#ifndef COVERAGE_ENABLED
|
|
9
|
+
#define COVERAGE_ENABLED 1
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
#ifndef COVERAGE_PROFILE_FILE_PATTERN
|
|
13
|
+
#define COVERAGE_PROFILE_FILE_PATTERN "coverage-%m.profraw"
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
#ifndef COVERAGE_FRAMEWORK_PREFIX_COUNT
|
|
17
|
+
#define COVERAGE_FRAMEWORK_PREFIX_COUNT 1
|
|
18
|
+
static const char *const COVERAGE_FRAMEWORK_PREFIXES[] = {
|
|
19
|
+
"CoverageFixture"
|
|
20
|
+
};
|
|
21
|
+
#endif
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
#ifdef __cplusplus
|
|
4
|
+
extern "C" {
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
/** Set LLVM_PROFILE_FILE / profile filename to Documents/coverage-%m.profraw. */
|
|
8
|
+
void CoverageConfigureProfilePath(void);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mode (c) multi-image flush:
|
|
12
|
+
* - LINKEDIT dump for configured framework prefixes (e.g. CoverageFixture*)
|
|
13
|
+
* - LINKEDIT dump for MH_EXECUTE main
|
|
14
|
+
* - direct write for this Pod's own profile runtime last
|
|
15
|
+
*
|
|
16
|
+
* Returns 0 on success (at least one useful write), non-zero otherwise.
|
|
17
|
+
*/
|
|
18
|
+
int CoverageFlushProfile(void);
|
|
19
|
+
|
|
20
|
+
#ifdef __cplusplus
|
|
21
|
+
}
|
|
22
|
+
#endif
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#import "CoverageProfile.h"
|
|
2
|
+
#import "CoverageConfig.h"
|
|
3
|
+
|
|
4
|
+
#import <mach-o/dyld.h>
|
|
5
|
+
#import <mach-o/loader.h>
|
|
6
|
+
#import <mach-o/nlist.h>
|
|
7
|
+
#import <stdlib.h>
|
|
8
|
+
#import <string.h>
|
|
9
|
+
|
|
10
|
+
extern "C" {
|
|
11
|
+
int __llvm_profile_write_file(void);
|
|
12
|
+
void __llvm_profile_set_filename(const char *Name);
|
|
13
|
+
const char *__llvm_profile_get_filename(void);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
typedef int (*CoverageProfileWriteFn)(void);
|
|
17
|
+
typedef void (*CoverageProfileSetFilenameFn)(const char *);
|
|
18
|
+
|
|
19
|
+
typedef struct {
|
|
20
|
+
const struct mach_header *header;
|
|
21
|
+
intptr_t slide;
|
|
22
|
+
CoverageProfileWriteFn writeFile;
|
|
23
|
+
const char *imageName;
|
|
24
|
+
} CoverageTrackedProfileImage;
|
|
25
|
+
|
|
26
|
+
enum { CoverageMaxTrackedProfileImages = 64 };
|
|
27
|
+
|
|
28
|
+
static CoverageTrackedProfileImage gCoverageTracked[CoverageMaxTrackedProfileImages];
|
|
29
|
+
static uint32_t gCoverageTrackedCount = 0;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `%m` expands to a unique module signature so each instrumented Mach-O image
|
|
33
|
+
* writes its own profraw instead of overwriting a single shared file.
|
|
34
|
+
*/
|
|
35
|
+
static NSString *CoverageProfilePattern(void)
|
|
36
|
+
{
|
|
37
|
+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
38
|
+
return [[paths firstObject] stringByAppendingPathComponent:@COVERAGE_PROFILE_FILE_PATTERN];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* clang_rt.profile symbols are linked into each instrumented framework as local
|
|
43
|
+
* (`t`) symbols — dlsym cannot see them. Resolve via the on-disk symbol table
|
|
44
|
+
* mapped through __LINKEDIT.
|
|
45
|
+
*/
|
|
46
|
+
static const void *CoverageFindSymbolInHeader(
|
|
47
|
+
const struct mach_header *header, intptr_t slide, const char *symbolName)
|
|
48
|
+
{
|
|
49
|
+
if (header == NULL || symbolName == NULL) {
|
|
50
|
+
return NULL;
|
|
51
|
+
}
|
|
52
|
+
if (header->magic != MH_MAGIC_64 && header->magic != MH_CIGAM_64) {
|
|
53
|
+
return NULL;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const struct mach_header_64 *header64 = (const struct mach_header_64 *)header;
|
|
57
|
+
const uint8_t *cursor = (const uint8_t *)(header64 + 1);
|
|
58
|
+
const struct symtab_command *symtab = NULL;
|
|
59
|
+
const struct segment_command_64 *linkedit = NULL;
|
|
60
|
+
|
|
61
|
+
for (uint32_t i = 0; i < header64->ncmds; i++) {
|
|
62
|
+
const struct load_command *command = (const struct load_command *)cursor;
|
|
63
|
+
if (command->cmd == LC_SYMTAB) {
|
|
64
|
+
symtab = (const struct symtab_command *)command;
|
|
65
|
+
} else if (command->cmd == LC_SEGMENT_64) {
|
|
66
|
+
const struct segment_command_64 *segment = (const struct segment_command_64 *)command;
|
|
67
|
+
if (strcmp(segment->segname, SEG_LINKEDIT) == 0) {
|
|
68
|
+
linkedit = segment;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
cursor += command->cmdsize;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (symtab == NULL || linkedit == NULL || symtab->nsyms == 0) {
|
|
75
|
+
return NULL;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const uintptr_t linkeditBase =
|
|
79
|
+
(uintptr_t)slide + (uintptr_t)linkedit->vmaddr - (uintptr_t)linkedit->fileoff;
|
|
80
|
+
const char *strings = (const char *)(linkeditBase + symtab->stroff);
|
|
81
|
+
const struct nlist_64 *symbols =
|
|
82
|
+
(const struct nlist_64 *)(linkeditBase + symtab->symoff);
|
|
83
|
+
const size_t nameLength = strlen(symbolName);
|
|
84
|
+
|
|
85
|
+
for (uint32_t i = 0; i < symtab->nsyms; i++) {
|
|
86
|
+
uint32_t stringIndex = symbols[i].n_un.n_strx;
|
|
87
|
+
if (stringIndex == 0) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const char *name = strings + stringIndex;
|
|
91
|
+
if (strncmp(name, symbolName, nameLength) != 0 || name[nameLength] != '\0') {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if ((symbols[i].n_type & N_TYPE) == N_UNDF || symbols[i].n_value == 0) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
return (const void *)((uintptr_t)symbols[i].n_value + (uintptr_t)slide);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return NULL;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static const char *CoverageImageNameForHeader(const struct mach_header *header)
|
|
104
|
+
{
|
|
105
|
+
uint32_t imageCount = _dyld_image_count();
|
|
106
|
+
for (uint32_t index = 0; index < imageCount; index += 1) {
|
|
107
|
+
if (_dyld_get_image_header(index) == header) {
|
|
108
|
+
return _dyld_get_image_name(index);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return NULL;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static bool CoverageIsConfiguredFrameworkImage(const char *imageName)
|
|
115
|
+
{
|
|
116
|
+
if (imageName == NULL) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
#if !COVERAGE_ENABLED
|
|
120
|
+
return false;
|
|
121
|
+
#endif
|
|
122
|
+
if (COVERAGE_FRAMEWORK_PREFIX_COUNT <= 0) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const char *framework = strstr(imageName, ".framework");
|
|
126
|
+
if (framework == NULL) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const char *basename = framework;
|
|
130
|
+
while (basename > imageName && basename[-1] != '/') {
|
|
131
|
+
basename -= 1;
|
|
132
|
+
}
|
|
133
|
+
for (int i = 0; i < COVERAGE_FRAMEWORK_PREFIX_COUNT; i++) {
|
|
134
|
+
const char *prefix = COVERAGE_FRAMEWORK_PREFIXES[i];
|
|
135
|
+
if (prefix == NULL) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
size_t prefixLen = strlen(prefix);
|
|
139
|
+
if (prefixLen > 0 && strncmp(basename, prefix, prefixLen) == 0) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* React Native / Expo Debug builds often place app + static-pod object code in
|
|
148
|
+
* `AppName.debug.dylib` (MH_DYLIB), not MH_EXECUTE. Flush that companion image
|
|
149
|
+
* via LINKEDIT so fixture/static-lib counters are not missed when Expo forces
|
|
150
|
+
* MACH_O_TYPE=staticlib on pods under use_frameworks.
|
|
151
|
+
*/
|
|
152
|
+
static bool CoverageIsAppCompanionDebugDylib(const char *imageName)
|
|
153
|
+
{
|
|
154
|
+
if (imageName == NULL) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (strstr(imageName, ".app/") == NULL) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return strstr(imageName, ".debug.dylib") != NULL;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static bool CoverageShouldTrackImage(const char *imageName)
|
|
164
|
+
{
|
|
165
|
+
return CoverageIsConfiguredFrameworkImage(imageName) ||
|
|
166
|
+
CoverageIsAppCompanionDebugDylib(imageName);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static bool CoverageIsMainExecutable(const struct mach_header *header)
|
|
170
|
+
{
|
|
171
|
+
return header != NULL && header->filetype == MH_EXECUTE;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
static void CoverageSetFilenameOnImage(
|
|
175
|
+
const struct mach_header *header, intptr_t slide, const char *pattern)
|
|
176
|
+
{
|
|
177
|
+
CoverageProfileSetFilenameFn setFilename =
|
|
178
|
+
(CoverageProfileSetFilenameFn)CoverageFindSymbolInHeader(
|
|
179
|
+
header, slide, "___llvm_profile_set_filename");
|
|
180
|
+
if (setFilename != NULL && pattern != NULL) {
|
|
181
|
+
setFilename(pattern);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
char **filenameVar = (char **)CoverageFindSymbolInHeader(
|
|
185
|
+
header, slide, "___llvm_profile_filename");
|
|
186
|
+
if (filenameVar != NULL && pattern != NULL) {
|
|
187
|
+
*filenameVar = (char *)pattern;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static void CoverageTrackProfileImage(const struct mach_header *header, intptr_t slide)
|
|
192
|
+
{
|
|
193
|
+
const char *imageName = CoverageImageNameForHeader(header);
|
|
194
|
+
if (!CoverageShouldTrackImage(imageName)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
CoverageProfileWriteFn writeFile = (CoverageProfileWriteFn)CoverageFindSymbolInHeader(
|
|
199
|
+
header, slide, "___llvm_profile_write_file");
|
|
200
|
+
// Skip if missing or if it resolves to *this* Pod's runtime (not a distinct image).
|
|
201
|
+
if (writeFile == NULL || writeFile == __llvm_profile_write_file) {
|
|
202
|
+
NSLog(@"[ios-native-coverage] skip image (no distinct profile runtime): %s", imageName);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (uint32_t i = 0; i < gCoverageTrackedCount; i++) {
|
|
207
|
+
if (gCoverageTracked[i].writeFile == writeFile) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (gCoverageTrackedCount >= CoverageMaxTrackedProfileImages) {
|
|
213
|
+
NSLog(@"[ios-native-coverage] tracked profile image cap reached (%d)",
|
|
214
|
+
CoverageMaxTrackedProfileImages);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
gCoverageTracked[gCoverageTrackedCount].header = header;
|
|
219
|
+
gCoverageTracked[gCoverageTrackedCount].slide = slide;
|
|
220
|
+
gCoverageTracked[gCoverageTrackedCount].writeFile = writeFile;
|
|
221
|
+
gCoverageTracked[gCoverageTrackedCount].imageName = imageName;
|
|
222
|
+
gCoverageTrackedCount += 1;
|
|
223
|
+
NSLog(@"[ios-native-coverage] tracked profile image[%u]: %s",
|
|
224
|
+
gCoverageTrackedCount - 1,
|
|
225
|
+
imageName);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static void CoverageOnAddImage(const struct mach_header *header, intptr_t slide)
|
|
229
|
+
{
|
|
230
|
+
CoverageTrackProfileImage(header, slide);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
__attribute__((constructor)) static void CoverageProfileInit(void)
|
|
234
|
+
{
|
|
235
|
+
_dyld_register_func_for_add_image(CoverageOnAddImage);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static void CoverageRefreshTrackedFromDyld(void)
|
|
239
|
+
{
|
|
240
|
+
uint32_t imageCount = _dyld_image_count();
|
|
241
|
+
for (uint32_t index = 0; index < imageCount; index += 1) {
|
|
242
|
+
CoverageTrackProfileImage(
|
|
243
|
+
_dyld_get_image_header(index), _dyld_get_image_vmaddr_slide(index));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static int CoverageFlushTracked(const char *pattern)
|
|
248
|
+
{
|
|
249
|
+
int worstStatus = 0;
|
|
250
|
+
int wrote = 0;
|
|
251
|
+
|
|
252
|
+
for (uint32_t i = 0; i < gCoverageTrackedCount; i++) {
|
|
253
|
+
CoverageSetFilenameOnImage(
|
|
254
|
+
gCoverageTracked[i].header, gCoverageTracked[i].slide, pattern);
|
|
255
|
+
int status = gCoverageTracked[i].writeFile();
|
|
256
|
+
NSLog(
|
|
257
|
+
@"[ios-native-coverage] flush tracked[%u] status=%d image=%s",
|
|
258
|
+
i,
|
|
259
|
+
status,
|
|
260
|
+
gCoverageTracked[i].imageName ?: "(null)");
|
|
261
|
+
if (status != 0) {
|
|
262
|
+
worstStatus = status;
|
|
263
|
+
} else {
|
|
264
|
+
wrote += 1;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return wrote > 0 ? 0 : worstStatus;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
static int CoverageFlushMainViaLinkedit(const char *pattern)
|
|
272
|
+
{
|
|
273
|
+
uint32_t imageCount = _dyld_image_count();
|
|
274
|
+
for (uint32_t index = 0; index < imageCount; index += 1) {
|
|
275
|
+
const struct mach_header *header = _dyld_get_image_header(index);
|
|
276
|
+
intptr_t slide = _dyld_get_image_vmaddr_slide(index);
|
|
277
|
+
if (!CoverageIsMainExecutable(header)) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const char *imageName = CoverageImageNameForHeader(header);
|
|
282
|
+
CoverageProfileWriteFn writeFile = (CoverageProfileWriteFn)CoverageFindSymbolInHeader(
|
|
283
|
+
header, slide, "___llvm_profile_write_file");
|
|
284
|
+
NSLog(
|
|
285
|
+
@"[ios-native-coverage] main-exe image=%s writeFile=%p podWriteFile=%p same=%d",
|
|
286
|
+
imageName ?: "(null)",
|
|
287
|
+
(void *)writeFile,
|
|
288
|
+
(void *)__llvm_profile_write_file,
|
|
289
|
+
writeFile == __llvm_profile_write_file ? 1 : 0);
|
|
290
|
+
|
|
291
|
+
if (writeFile == NULL) {
|
|
292
|
+
NSLog(@"[ios-native-coverage] main executable has no ___llvm_profile_write_file via LINKEDIT");
|
|
293
|
+
return -1;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
CoverageSetFilenameOnImage(header, slide, pattern);
|
|
297
|
+
int status = writeFile();
|
|
298
|
+
NSLog(@"[ios-native-coverage] flush main-via-LINKEDIT status=%d", status);
|
|
299
|
+
return status;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
NSLog(@"[ios-native-coverage] no MH_EXECUTE image found");
|
|
303
|
+
return -1;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
extern "C" void CoverageConfigureProfilePath(void)
|
|
307
|
+
{
|
|
308
|
+
const char *pattern = CoverageProfilePattern().UTF8String;
|
|
309
|
+
// Dynamic frameworks often lack set_filename (dead-stripped). getenv is consulted
|
|
310
|
+
// by every runtime copy on write.
|
|
311
|
+
setenv("LLVM_PROFILE_FILE", pattern, 1);
|
|
312
|
+
__llvm_profile_set_filename(pattern);
|
|
313
|
+
NSLog(
|
|
314
|
+
@"[ios-native-coverage] configure pattern=%@ podRuntimePath=%s",
|
|
315
|
+
CoverageProfilePattern(),
|
|
316
|
+
__llvm_profile_get_filename() ?: "(null)");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Mode (c): Pod LINKEDIT for configured frameworks + main executable, then pod write.
|
|
321
|
+
* Prefer this under use_frameworks! :linkage => :dynamic (05S1 verdict).
|
|
322
|
+
*/
|
|
323
|
+
extern "C" int CoverageFlushProfile(void)
|
|
324
|
+
{
|
|
325
|
+
#if !COVERAGE_ENABLED
|
|
326
|
+
NSLog(@"[ios-native-coverage] flush skipped (COVERAGE_ENABLED=0)");
|
|
327
|
+
return -1;
|
|
328
|
+
#endif
|
|
329
|
+
|
|
330
|
+
CoverageConfigureProfilePath();
|
|
331
|
+
CoverageRefreshTrackedFromDyld();
|
|
332
|
+
const char *pattern = getenv("LLVM_PROFILE_FILE");
|
|
333
|
+
|
|
334
|
+
int trackedStatus = CoverageFlushTracked(pattern);
|
|
335
|
+
int mainStatus = CoverageFlushMainViaLinkedit(pattern);
|
|
336
|
+
int podStatus = __llvm_profile_write_file();
|
|
337
|
+
|
|
338
|
+
NSLog(
|
|
339
|
+
@"[ios-native-coverage] flush complete tracked=%u trackedStatus=%d mainStatus=%d podStatus=%d pattern=%s",
|
|
340
|
+
gCoverageTrackedCount,
|
|
341
|
+
trackedStatus,
|
|
342
|
+
mainStatus,
|
|
343
|
+
podStatus,
|
|
344
|
+
pattern ?: "(null)");
|
|
345
|
+
|
|
346
|
+
int okTracked = (trackedStatus == 0 && gCoverageTrackedCount > 0) ? 1 : 0;
|
|
347
|
+
int okMain = (mainStatus == 0) ? 1 : 0;
|
|
348
|
+
// Under Expo static-pod merge, fixture counters live in the same image as this
|
|
349
|
+
// Pod's profile runtime — pod write is the success path.
|
|
350
|
+
int okPod = (podStatus == 0) ? 1 : 0;
|
|
351
|
+
if (okTracked || okMain || okPod) {
|
|
352
|
+
return 0;
|
|
353
|
+
}
|
|
354
|
+
if (mainStatus != 0) {
|
|
355
|
+
return mainStatus;
|
|
356
|
+
}
|
|
357
|
+
return trackedStatus != 0 ? trackedStatus : podStatus;
|
|
358
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TurboModule surface for native coverage flush (New Architecture only).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export default TurboModuleRegistry.getEnforcing('Coverage');
|
|
10
|
+
//# sourceMappingURL=NativeCoverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeCoverage.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;;AAEpE;AACA;AACA;;AAWA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,UAAU,CAAC","ignoreList":[]}
|