rooibos-roku 5.16.6 → 5.17.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/CHANGELOG.md +9 -0
- package/audit-ci.jsonc +9 -1
- package/dist/cli.js +160 -19
- package/dist/lib/framework/components/rooibos/CodeCoverage.brs +109 -34
- package/dist/lib/framework/components/rooibos/CodeCoverage.xml +6 -4
- package/dist/lib/framework/source/rooibos/Coverage.bs +231 -129
- package/dist/lib/framework/source/rooibos/Rooibos.bs +20 -5
- package/dist/lib/framework/source/rooibos/TestRunner.bs +14 -5
- package/dist/lib/rooibos/CodeCoverageProcessor.js +1271 -92
- package/dist/lib/rooibos/CoverageReporter.js +304 -0
- package/dist/lib/rooibos/FileFactory.js +17 -5
- package/dist/lib/rooibos/RooibosConfig.js +45 -1
- package/dist/lib/rooibos/RooibosSession.js +1 -1
- package/dist/lib/rooibos/TestGroup.js +8 -2
- package/dist/lib/rooibos/resolveCliDevice.js +57 -0
- package/dist/plugin.js +11 -8
- package/package.json +11 -8
- package/dist/lib/framework/source/rooibos/CodeCoverageSupport.brs +0 -53
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [5.17.0](https://github.com/rokucommunity/rooibos/compare/5.16.6...v5.17.0) - 2026-10-06
|
|
10
|
+
### Added
|
|
11
|
+
- Add Roku Cloud Emulator (RCE) support via CLI ([#447](https://github.com/rokucommunity/rooibos/pull/447))
|
|
12
|
+
### Changed
|
|
13
|
+
- Istanbul HTML code coverage reports and improved coverage accuracy ([#275](https://github.com/rokucommunity/rooibos/pull/275))
|
|
14
|
+
- Security enhancements ([#444](https://github.com/rokucommunity/rooibos/pull/444), [#446](https://github.com/rokucommunity/rooibos/pull/446))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
9
18
|
## [5.16.6](https://github.com/rokucommunity/rooibos/compare/5.16.5...v5.16.6) - 2026-10-01
|
|
10
19
|
### Changed
|
|
11
20
|
- Process every test suite in a file, not just the first ([#441](https://github.com/rokucommunity/rooibos/pull/441))
|
package/audit-ci.jsonc
CHANGED
|
@@ -3,5 +3,13 @@
|
|
|
3
3
|
// why each allowlisted entry is safe to defer.
|
|
4
4
|
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
|
5
5
|
"high": true,
|
|
6
|
-
"allowlist": [
|
|
6
|
+
"allowlist": [
|
|
7
|
+
{
|
|
8
|
+
"GHSA-vfj7-8cjw-p6xm": {
|
|
9
|
+
// Added: 2026-10-03
|
|
10
|
+
"active": true,
|
|
11
|
+
"notes": "`braces` <=3.0.3 (prod, via `brighterscript`/`roku-debug`/`roku-deploy`/`fast-glob`): stack exhaustion on deeply nested brace patterns. No patched version is published, so it cannot be upgraded or overridden. The only patterns reaching it are globs from the caller's own project config; no untrusted party supplies them, and a crash only affects the caller's own local process."
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
]
|
|
7
15
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
4
|
+
var t = {};
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
8
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
9
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
10
|
+
t[p[i]] = s[p[i]];
|
|
11
|
+
}
|
|
12
|
+
return t;
|
|
13
|
+
};
|
|
3
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
15
|
const roku_debug_1 = require("roku-debug");
|
|
5
16
|
const brighterscript_1 = require("brighterscript");
|
|
6
17
|
const yargs = require("yargs");
|
|
7
18
|
const roku_deploy_1 = require("roku-deploy");
|
|
8
19
|
const fs = require("fs");
|
|
20
|
+
const fsExtra = require("fs-extra");
|
|
9
21
|
const path = require("path");
|
|
22
|
+
const RooibosConfig_1 = require("./lib/rooibos/RooibosConfig");
|
|
23
|
+
const resolveCliDevice_1 = require("./lib/rooibos/resolveCliDevice");
|
|
24
|
+
const CoverageReporter_1 = require("./lib/rooibos/CoverageReporter");
|
|
10
25
|
/**
|
|
11
26
|
* Load simple `KEY=value` pairs from a .env file into process.env, without
|
|
12
27
|
* overwriting variables that are already set in the real environment.
|
|
@@ -33,25 +48,34 @@ function loadDotEnv(envPath = '.env') {
|
|
|
33
48
|
}
|
|
34
49
|
}
|
|
35
50
|
loadDotEnv();
|
|
51
|
+
// Resolved once by the argument check below, so main() can rely on a valid target and password.
|
|
52
|
+
let password;
|
|
53
|
+
let target;
|
|
36
54
|
let options = yargs
|
|
37
55
|
.usage('$0', 'Rooibos: a simple, flexible, fun Brightscript test framework for Roku Scenegraph apps')
|
|
38
56
|
.help('help', 'View help information about this tool.')
|
|
39
57
|
.option('project', { type: 'string', description: 'Path to a bsconfig.json project file.' })
|
|
40
|
-
.option('host', { type: 'string', description: 'Host of the Roku device to connect to.
|
|
41
|
-
.option('
|
|
58
|
+
.option('host', { type: 'string', description: 'Host of the Roku device to connect to. Falls back to ROKU_HOST.' })
|
|
59
|
+
.option('esn', { type: 'string', description: 'ESN of a Roku Cloud Emulator device to run on. Requires --token or ROKU_RCE_TOKEN. Cannot be combined with --host or --instance-url.' })
|
|
60
|
+
.option('instance-url', { type: 'string', description: 'URL of a Roku Cloud Emulator instance to run on. Requires --token or ROKU_RCE_TOKEN. Cannot be combined with --host or --esn.' })
|
|
61
|
+
.option('token', { type: 'string', description: 'Roku Cloud Emulator access token (used with --esn or --instance-url). Falls back to the ROKU_RCE_TOKEN environment variable.' })
|
|
62
|
+
.option('password', { type: 'string', description: 'Password of the Roku device to connect to. Falls back to ROKU_PASSWORD.' })
|
|
42
63
|
.option('log-level', { type: 'string', defaultDescription: '"log"', description: 'The log level. Value can be "error", "warn", "log", "info", "debug".' })
|
|
64
|
+
.option('coverage-dir', { type: 'string', default: './coverage', description: 'Directory to write coverage reports into when codeCoverage is on: lcov.info, coverage-final.json and an html/ report.' })
|
|
65
|
+
.option('code-coverage', { type: 'boolean', description: 'Turn code coverage on (or off with --no-code-coverage) for the CLI\'s build, overriding the rooibos block of the bsconfig. Has no effect with --no-build.' })
|
|
66
|
+
.option('staging-dir', { type: 'string', description: 'Path to the built package directory (staging output). With --no-build this is zipped and deployed as-is; otherwise it overrides where the build stages. Coverage models are read from here.' })
|
|
67
|
+
.option('build', { type: 'boolean', default: true, description: 'Pass --no-build to skip the internal bsc build and deploy an existing staging directory (from --staging-dir or the bsconfig). Assumes it was built with the rooibos plugin so coverage helpers are present.' })
|
|
43
68
|
.check((argv) => {
|
|
44
|
-
var _a;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
if (!argv.password && !process.env.ROKU_PASSWORD) {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
password = (_a = argv.password) !== null && _a !== void 0 ? _a : process.env.ROKU_PASSWORD;
|
|
71
|
+
if (!password) {
|
|
49
72
|
return new Error('You must provide a password. (--password, or ROKU_PASSWORD in .env)');
|
|
50
73
|
}
|
|
74
|
+
target = (0, resolveCliDevice_1.resolveCliDevice)({ host: argv.host, esn: argv.esn, instanceUrl: argv['instance-url'], token: argv.token }, process.env);
|
|
51
75
|
if (!argv.project) {
|
|
52
76
|
console.log('No project file specified. Using "./bsconfig.json"');
|
|
53
77
|
}
|
|
54
|
-
let bsconfigPath = (
|
|
78
|
+
let bsconfigPath = (_b = argv.project) !== null && _b !== void 0 ? _b : './bsconfig.json';
|
|
55
79
|
if (!fs.existsSync(bsconfigPath)) {
|
|
56
80
|
return new Error(`Unable to load ${bsconfigPath}`);
|
|
57
81
|
}
|
|
@@ -59,20 +83,74 @@ let options = yargs
|
|
|
59
83
|
})
|
|
60
84
|
.argv;
|
|
61
85
|
async function main() {
|
|
62
|
-
var _a, _b
|
|
86
|
+
var _a, _b;
|
|
63
87
|
let currentErrorCode = 0;
|
|
64
88
|
let bsconfigPath = (_a = options.project) !== null && _a !== void 0 ? _a : 'bsconfig.json';
|
|
65
89
|
console.log(`Using bsconfig: ${bsconfigPath}`);
|
|
66
90
|
const rawConfig = brighterscript_1.util.loadConfigFile(bsconfigPath);
|
|
67
91
|
const bsConfig = brighterscript_1.util.normalizeConfig(rawConfig);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
const rooibosConfig = (0, RooibosConfig_1.resolveCliRooibosConfig)(rawConfig.rooibos, { codeCoverage: options['code-coverage'] });
|
|
93
|
+
if (options['code-coverage'] !== undefined && options.build === false) {
|
|
94
|
+
console.warn('[rooibos] --code-coverage/--no-code-coverage has no effect with --no-build: the existing package was already built, so its coverage setting is whatever it was built with');
|
|
95
|
+
}
|
|
96
|
+
const { device, label: deviceLabel } = target;
|
|
97
|
+
const logLevel = (_b = brighterscript_1.LogLevel[options['log-level']]) !== null && _b !== void 0 ? _b : bsConfig.logLevel;
|
|
98
|
+
// roku-deploy v4 and roku-debug 0.24 address the target via a device config (resolved
|
|
99
|
+
// by the argument check) rather than a bare `host` string.
|
|
75
100
|
const rokuDeploy = new roku_deploy_1.RokuDeploy();
|
|
101
|
+
const skipBuild = options.build === false;
|
|
102
|
+
/**
|
|
103
|
+
* Ordered candidate locations for the built package contents: the --staging-dir
|
|
104
|
+
* override, then the bsconfig staging fields, then roku-deploy's default staging
|
|
105
|
+
* location (used when no staging dir is configured anywhere).
|
|
106
|
+
*/
|
|
107
|
+
function stagingDirCandidates() {
|
|
108
|
+
const candidates = [];
|
|
109
|
+
if (options['staging-dir']) {
|
|
110
|
+
candidates.push(path.resolve(String(options['staging-dir'])));
|
|
111
|
+
}
|
|
112
|
+
for (const staging of [bsConfig.stagingDir, bsConfig.stagingFolderPath]) {
|
|
113
|
+
if (staging) {
|
|
114
|
+
candidates.push(path.resolve(String(staging)));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const outDir = bsConfig.outFile ? path.dirname(String(bsConfig.outFile)) : './out';
|
|
118
|
+
candidates.push(path.resolve(outDir, '.roku-deploy-staging'));
|
|
119
|
+
return candidates;
|
|
120
|
+
}
|
|
121
|
+
// Resolved path to the .zip we'll actually deploy when skipping the build. We zip the
|
|
122
|
+
// staging dir into out/rooibos-prebuilt.zip ourselves rather than handing the directory
|
|
123
|
+
// straight to sideload({ dir }), because that path does not forward file patterns and
|
|
124
|
+
// would drop the source-map exclusion below. A directory is required either way - the
|
|
125
|
+
// coverage model (components/rooibos/CodeCoverage.json) must be readable from it.
|
|
126
|
+
let deployZipPath;
|
|
127
|
+
if (skipBuild) {
|
|
128
|
+
const stagingDir = stagingDirCandidates().find((c) => fs.existsSync(c));
|
|
129
|
+
if (!stagingDir) {
|
|
130
|
+
console.error('[rooibos] --no-build requires an existing staging directory: pass --staging-dir or set one in the bsconfig');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
if (!fs.statSync(stagingDir).isDirectory()) {
|
|
134
|
+
console.error(`[rooibos] the staging dir must be a directory, not a file: ${stagingDir}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const zipped = path.resolve('out/rooibos-prebuilt.zip');
|
|
138
|
+
fs.mkdirSync(path.dirname(zipped), { recursive: true });
|
|
139
|
+
console.log(`Zipping pre-built staging dir ${stagingDir} -> ${zipped}`);
|
|
140
|
+
// Exclude source maps - they're useful in the staging dir but shouldn't ship
|
|
141
|
+
// in the package (they bloat channel size and Roku has no use for them).
|
|
142
|
+
// roku-deploy v4 replaced zipFolder(src, out, logger, files) with zip({...});
|
|
143
|
+
// the file patterns still resolve relative to `dir`.
|
|
144
|
+
await rokuDeploy.zip({ dir: stagingDir, out: zipped, files: ['**/*', '!**/*.map'] });
|
|
145
|
+
deployZipPath = zipped;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const builder = new brighterscript_1.ProgramBuilder();
|
|
149
|
+
builder.logger.logLevel = logLevel;
|
|
150
|
+
// --staging-dir (if given) flows into bsc as its stagingDir via the spread
|
|
151
|
+
const { token: _token, esn: _esn, 'instance-url': _instanceUrlKebab, instanceUrl: _instanceUrl } = options, buildOptions = __rest(options, ["token", "esn", 'instance-url', "instanceUrl"]);
|
|
152
|
+
await builder.run(Object.assign(Object.assign(Object.assign({}, buildOptions), (rooibosConfig ? { rooibos: rooibosConfig } : {})), { retainStagingDir: true, createPackage: true }));
|
|
153
|
+
}
|
|
76
154
|
const deviceInfo = await rokuDeploy.getDeviceInfo({ device: device });
|
|
77
155
|
const rendezvousTracker = new roku_debug_1.RendezvousTracker({ softwareVersion: deviceInfo['software-version'] }, { device: device, remotePort: 8085 });
|
|
78
156
|
const telnet = new roku_debug_1.TelnetAdapter({ device: device }, rendezvousTracker);
|
|
@@ -81,7 +159,52 @@ async function main() {
|
|
|
81
159
|
await telnet.connect();
|
|
82
160
|
const failRegex = /\[Rooibos Result\]: (FAIL|PASS)/g;
|
|
83
161
|
const endRegex = /\[Rooibos Shutdown\]/g;
|
|
162
|
+
const outputDir = path.resolve(options['coverage-dir']);
|
|
163
|
+
let capturingCounts = false;
|
|
164
|
+
let coverageBuffer = [];
|
|
165
|
+
let coverageReportPromise;
|
|
166
|
+
/**
|
|
167
|
+
* The bsc plugin writes the static coverage model (line/function/branch shape plus
|
|
168
|
+
* repo-relative source paths) into components/rooibos/CodeCoverage.json; read it back
|
|
169
|
+
* from wherever the deployed package contents live.
|
|
170
|
+
*/
|
|
171
|
+
function findCoverageModel() {
|
|
172
|
+
for (const dir of stagingDirCandidates()) {
|
|
173
|
+
const candidate = path.join(dir, 'components', 'rooibos', 'CodeCoverage.json');
|
|
174
|
+
const model = (0, CoverageReporter_1.loadCoverageModel)(candidate);
|
|
175
|
+
if (model) {
|
|
176
|
+
console.log(`[rooibos] using coverage model from ${candidate}`);
|
|
177
|
+
return model;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
/** Dumps the raw device stream into the output dir so a capture is never lost. */
|
|
183
|
+
function saveRawCapture(raw) {
|
|
184
|
+
const rawPath = path.join(outputDir, 'coverage-counts.raw');
|
|
185
|
+
fsExtra.outputFileSync(rawPath, raw);
|
|
186
|
+
console.error(`[rooibos] raw coverage output saved to ${rawPath}`);
|
|
187
|
+
}
|
|
188
|
+
/** The device printed the condensed hit-counts stream. */
|
|
189
|
+
function writeCoverageFromCounts(rawCounts) {
|
|
190
|
+
const model = findCoverageModel();
|
|
191
|
+
if (!model) {
|
|
192
|
+
console.error('[rooibos] the device sent condensed coverage counts but no components/rooibos/CodeCoverage.json was found in the package or staging dir - cannot build coverage reports');
|
|
193
|
+
saveRawCapture(rawCounts);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
coverageReportPromise = (0, CoverageReporter_1.writeCoverageReportsFromCounts)({
|
|
197
|
+
rawCounts: rawCounts,
|
|
198
|
+
model: model,
|
|
199
|
+
outputDir: outputDir
|
|
200
|
+
}).catch(e => {
|
|
201
|
+
console.error('[rooibos] failed to write coverage reports:', e);
|
|
202
|
+
saveRawCapture(rawCounts);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
84
205
|
async function doExit(emitAppExit = false) {
|
|
206
|
+
// don't kill the process while coverage reports are still being written
|
|
207
|
+
await coverageReportPromise;
|
|
85
208
|
if (emitAppExit) {
|
|
86
209
|
telnet.beginAppExit();
|
|
87
210
|
}
|
|
@@ -90,6 +213,21 @@ async function main() {
|
|
|
90
213
|
}
|
|
91
214
|
telnet.on('console-output', (output) => {
|
|
92
215
|
console.log(output);
|
|
216
|
+
for (const line of output.split('\n')) {
|
|
217
|
+
if (line.includes('+-=-coverage-counts:start')) {
|
|
218
|
+
capturingCounts = true;
|
|
219
|
+
coverageBuffer = [];
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (line.includes('+-=-coverage-counts:end')) {
|
|
223
|
+
capturingCounts = false;
|
|
224
|
+
writeCoverageFromCounts(coverageBuffer.join('\n'));
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (capturingCounts) {
|
|
228
|
+
coverageBuffer.push(line);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
93
231
|
//check for Fails or Crashes
|
|
94
232
|
let failMatches = failRegex.exec(output);
|
|
95
233
|
if (failMatches && failMatches.length > 0) {
|
|
@@ -122,12 +260,15 @@ async function main() {
|
|
|
122
260
|
// Actually start the unit tests
|
|
123
261
|
//deploy a .zip package of your project to a roku device
|
|
124
262
|
async function deployBuiltFiles() {
|
|
125
|
-
|
|
126
|
-
|
|
263
|
+
// With --no-build, deploy the staging dir we just zipped; otherwise fall back to the
|
|
264
|
+
// bsconfig-driven outFile that the rooibos build just produced.
|
|
265
|
+
const packagePath = deployZipPath !== null && deployZipPath !== void 0 ? deployZipPath : path.resolve(process.cwd(), bsConfig.outFile);
|
|
266
|
+
console.log(`Deploying ${packagePath} to ${deviceLabel}`);
|
|
267
|
+
// roku-deploy v4 replaced publish({ host, outDir, outFile }) with sideload({ device, zip }).
|
|
127
268
|
await rokuDeploy.sideload({
|
|
128
269
|
password: password,
|
|
129
270
|
device: device,
|
|
130
|
-
zip:
|
|
271
|
+
zip: packagePath
|
|
131
272
|
});
|
|
132
273
|
}
|
|
133
274
|
await deployBuiltFiles();
|
|
@@ -1,44 +1,119 @@
|
|
|
1
|
-
' @ignore
|
|
2
1
|
function init()
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
m.
|
|
6
|
-
m.
|
|
2
|
+
' The coverage map ships as a JSON asset rather than an embedded literal: a real app's
|
|
3
|
+
' map can exceed Roku's 2MiB-per-.brs-file compile limit (&hb9) when written into code.
|
|
4
|
+
m.coverageMap = ParseJson(ReadAsciiFile("pkg:/components/rooibos/CodeCoverage.json"))
|
|
5
|
+
m.port = createObject("roMessagePort")
|
|
6
|
+
m.top.observeFieldScoped("entry", m.port)
|
|
7
|
+
' save arrives on the same port so the task thread can block on wait() instead of
|
|
8
|
+
' polling m.top.save (a cross-thread rendezvous) on a sleep loop
|
|
9
|
+
m.top.observeFieldScoped("save", m.port)
|
|
10
|
+
m.top.functionName = "runTaskThread"
|
|
11
|
+
m.top.control = "RUN"
|
|
7
12
|
end function
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
function runTaskThread() as void
|
|
15
|
+
' Per-file line lookups keyed by line number, built lazily on first hit (on the task
|
|
16
|
+
' thread, so the render thread never pays for index construction). The values are
|
|
17
|
+
' references to the same line AAs as file.lines, so mutating them updates the model.
|
|
18
|
+
m.lineIndexByFile = {}
|
|
19
|
+
' Entries whose ids don't resolve in the model. Any nonzero count means the package
|
|
20
|
+
' mixes instrumentation passes (e.g. stale staged files from an earlier build whose
|
|
21
|
+
' baked-in ids alias different files in this build's CodeCoverage.json) - skip and
|
|
22
|
+
' tally rather than crash the task.
|
|
23
|
+
m.unmatchedEntries = 0
|
|
24
|
+
while true
|
|
25
|
+
events = []
|
|
26
|
+
saving = false
|
|
27
|
+
' blocking wait: zero CPU and no cross-thread field reads while idle
|
|
28
|
+
message = wait(0, m.port)
|
|
29
|
+
if message.getField() = "save" then
|
|
30
|
+
saving = (message.getData() = true)
|
|
31
|
+
else
|
|
32
|
+
events.push(message)
|
|
33
|
+
end if
|
|
34
|
+
if saving = true then
|
|
35
|
+
? "Saving unprocessed code cov events..."
|
|
36
|
+
'Drain all the unprocessed messages (3ms grace per message, then done)
|
|
37
|
+
while true
|
|
38
|
+
message = wait(3, m.port)
|
|
39
|
+
if message = invalid then
|
|
40
|
+
exit while
|
|
41
|
+
else if message.getField() <> "save" then
|
|
42
|
+
events.push(message)
|
|
43
|
+
end if
|
|
44
|
+
end while
|
|
13
45
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
m.top.filePathMap = "#FILE_PATH_MAP#"
|
|
17
|
-
end function
|
|
46
|
+
? "Found" events.count() " unprocessed events..."
|
|
47
|
+
end if
|
|
18
48
|
|
|
19
|
-
'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
m.results.push(entry)
|
|
24
|
-
end function
|
|
49
|
+
' entry.r values below are force-substituted at build time from the TS
|
|
50
|
+
' CodeCoverageLineType enum (FileFactory.createCoverageComponent keys on the
|
|
51
|
+
' #LINE_TYPE_*# markers), so producer and consumer cannot drift. The literals
|
|
52
|
+
' here only matter for parsing this template standalone.
|
|
25
53
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
for each event in events
|
|
55
|
+
entry = event.getData()
|
|
56
|
+
if entry <> invalid then
|
|
57
|
+
file = m.coverageMap.files[entry.f]
|
|
58
|
+
if file = invalid then
|
|
59
|
+
m.unmatchedEntries++
|
|
60
|
+
else if entry.r = 4 then ' #LINE_TYPE_FUNCTION#
|
|
61
|
+
func = file.functions[entry.fn]
|
|
62
|
+
if func <> invalid then
|
|
63
|
+
if func.totalHit = 0 then
|
|
64
|
+
file.functionTotalHit++
|
|
65
|
+
end if
|
|
66
|
+
func.totalHit++
|
|
67
|
+
else
|
|
68
|
+
m.unmatchedEntries++
|
|
69
|
+
end if
|
|
70
|
+
else if entry.r = 3 then ' #LINE_TYPE_BRANCH#
|
|
71
|
+
' branch ids are assigned as array indexes at build time, so index directly
|
|
72
|
+
block = file.blocks[entry.bl]
|
|
73
|
+
branch = invalid
|
|
74
|
+
if block <> invalid then
|
|
75
|
+
branch = block.branches[entry.br]
|
|
76
|
+
end if
|
|
77
|
+
if branch <> invalid then
|
|
78
|
+
if branch.totalHit = 0 then
|
|
79
|
+
file.branchTotalHit++
|
|
80
|
+
end if
|
|
81
|
+
branch.totalHit++
|
|
82
|
+
else
|
|
83
|
+
m.unmatchedEntries++
|
|
84
|
+
end if
|
|
85
|
+
else if entry.r = 1 then ' #LINE_TYPE_CODE#
|
|
86
|
+
fileKey = stri(entry.f).trim()
|
|
87
|
+
lineIndex = m.lineIndexByFile[fileKey]
|
|
88
|
+
if lineIndex = invalid then
|
|
89
|
+
lineIndex = {}
|
|
90
|
+
for each line in file.lines
|
|
91
|
+
lineIndex[stri(line.lineNumber).trim()] = line
|
|
92
|
+
end for
|
|
93
|
+
m.lineIndexByFile[fileKey] = lineIndex
|
|
94
|
+
end if
|
|
95
|
+
line = lineIndex[stri(entry.l).trim()]
|
|
96
|
+
if line <> invalid then
|
|
97
|
+
if line.totalHit = 0 then
|
|
98
|
+
file.lineTotalHit++
|
|
99
|
+
end if
|
|
100
|
+
line.totalHit++
|
|
101
|
+
else
|
|
102
|
+
m.unmatchedEntries++
|
|
103
|
+
end if
|
|
104
|
+
end if
|
|
105
|
+
' no write-back needed: file and its members are references into m.coverageMap
|
|
106
|
+
end if
|
|
107
|
+
end for
|
|
33
108
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
109
|
+
if saving = true then
|
|
110
|
+
if m.unmatchedEntries > 0 then
|
|
111
|
+
? "[rooibos coverage] WARNING: " ; stri(m.unmatchedEntries).trim() ; " coverage entries did not match the coverage model and were skipped."
|
|
112
|
+
? "[rooibos coverage] This usually means the package mixes files from different builds - rebuild into a clean staging directory."
|
|
37
113
|
end if
|
|
38
|
-
|
|
114
|
+
m.top.coverageResults = m.coverageMap
|
|
115
|
+
return
|
|
39
116
|
end if
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
setExpectedMap()
|
|
43
|
-
setFilePathMap()
|
|
117
|
+
end while
|
|
118
|
+
|
|
44
119
|
end function
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<component name="CodeCoverage"
|
|
3
|
+
extends="Task"
|
|
4
|
+
>
|
|
2
5
|
<script type="text/brightscript" uri="CodeCoverage.brs" />
|
|
3
6
|
|
|
4
7
|
<interface>
|
|
5
8
|
<field id="entry" type="assocarray" />
|
|
9
|
+
<field id="coverageResults" type="assocarray" />
|
|
10
|
+
|
|
6
11
|
<field id="save" type="boolean" />
|
|
7
|
-
<field id="expectedMap" type="assocarray" />
|
|
8
|
-
<field id="resolvedMap" type="assocarray" />
|
|
9
|
-
<field id="filePathMap" type="assocarray" />
|
|
10
12
|
</interface>
|
|
11
13
|
</component>
|