Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/build/wsl_unison_environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ ways to get visibility, in order of how much they cost:
**1. Watch the result XMLs (no config change, works on a build already running)**

```bash
./scripts/watch_test_progress.sh
yarn watch:tests
# or: node scripts/watch_test_progress.js --results <dir> --interval 15
```

Each test class writes its JUnit XML as it finishes, so this reports
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"android:sign:debug-apks": "node scripts/sign_android.js",
"android:build:debug:signed": "yarn android:build:debug && yarn android:sign:debug-apks",
"clean-logs": "npx ts-node scripts/clean_clipboard_logs.ts",
"watch:tests": "node scripts/watch_test_progress.js",
"test": "jest"
},
"dependencies": {
Expand Down Expand Up @@ -94,6 +95,7 @@
"eslint-plugin-node": "latest",
"eslint-plugin-prettier": "latest",
"eslint-plugin-react-hooks": "^5.2.0",
"fast-xml-parser": "^4.5.3",
"gts": "^5.0.0",
"husky": "^9.1.6",
"jest": "^29.6.2",
Expand Down
120 changes: 120 additions & 0 deletions scripts/watch_test_progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env node

/**
* Live progress for a running Gradle unit-test build.
*
* @description Gradle's Test task prints nothing per-test by default, so a long
* run looks identical to a hung one: `--console=plain` shows
* `> Task :app:testX8664DebugUnitTest` and then silence for many
* minutes. This reads the JUnit XML files as they land instead,
* which works on a build that is ALREADY RUNNING -- no config
* change and no restart needed.
*
* The build.gradle `testLogging` block covers new runs. This is for
* the case where a build is already in flight, or where you want a
* compact rolling summary rather than a full Gradle log.
*
* @usage node scripts/watch_test_progress.js [--results <dir>] [--interval <seconds>] [--once]
*
* @param {string} --results - Directory holding JUnit XML results.
* Defaults to the Windows-side x86_64 debug output.
* @param {number} --interval - Seconds between polls (default 15).
* @param {boolean} --once - Print a single snapshot and exit.
*/

const fs = require('fs');
const path = require('path');
const { XMLParser } = require('fast-xml-parser');

const DEFAULT_RESULTS =
'/mnt/c/dev/CN/android/app/build/test-results/testX8664DebugUnitTest';
const DEFAULT_INTERVAL_SECONDS = 15;

/** Parse `--flag value` and bare `--flag` pairs out of argv. */
function parseArgs(argv) {
const options = { results: DEFAULT_RESULTS, interval: DEFAULT_INTERVAL_SECONDS, once: false };

for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === '--once') {
options.once = true;
} else if (arg === '--results') {
options.results = argv[++i];
} else if (arg === '--interval') {
options.interval = Number(argv[++i]) || DEFAULT_INTERVAL_SECONDS;
}
}
return options;
}

const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });

/**
* Pull the counts off a JUnit `<testsuite>` element.
*
* Gradle writes these files while the build runs, so a partially-written file
* is normal rather than exceptional -- both the read and the parse are allowed
* to fail, yielding null until the file is complete.
*
* @returns {{name: string, tests: number, failures: number, time: string}|null}
*/
function readSuite(file) {
let suite;
try {
suite = parser.parse(fs.readFileSync(file, 'utf8')).testsuite;
} catch {
return null; // unreadable or half-written
}
if (!suite) return null;

const num = (key) => Number(suite[key]) || 0;

return {
name: String(suite['@_name'] || '').split('.').pop() || '?',
tests: num('@_tests'),
failures: num('@_failures') + num('@_errors'),
time: suite['@_time'],
};
}

/** Read every result file in the directory, oldest-written first. */
function collectSuites(resultsDir) {
return fs
.readdirSync(resultsDir)
.filter((f) => f.endsWith('.xml'))
.map((f) => path.join(resultsDir, f))
.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs)
.map(readSuite)
.filter(Boolean);
}

function printSnapshot(resultsDir) {
const suites = collectSuites(resultsDir);

const tests = suites.reduce((sum, s) => sum + s.tests, 0);
const failures = suites.reduce((sum, s) => sum + s.failures, 0);

const stamp = new Date().toTimeString().slice(0, 8);
const failed = failures ? ` FAILURES=${failures}` : '';
console.log(`[${stamp}] classes=${suites.length} tests=${tests}${failed}`);

for (const suite of suites.slice(-2)) {
console.log(` ${suite.name}: ${suite.tests} tests, ${suite.time}s`);
}
}

function main() {
const options = parseArgs(process.argv.slice(2));

if (!fs.existsSync(options.results)) {
console.error(`No results directory yet: ${options.results}`);
process.exit(1);
}

printSnapshot(options.results);
if (options.once) return;

setInterval(() => printSnapshot(options.results), options.interval * 1000);
}

main();
47 changes: 0 additions & 47 deletions scripts/watch_test_progress.sh

This file was deleted.

14 changes: 13 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6702,6 +6702,7 @@ __metadata:
expo: ~54.0.0
expo-application: ~7.0.0
expo-status-bar: ~3.0.9
fast-xml-parser: ^4.5.3
gts: ^5.0.0
husky: ^9.1.6
jest: ^29.6.2
Expand Down Expand Up @@ -10314,6 +10315,17 @@ __metadata:
languageName: node
linkType: hard

"fast-xml-parser@npm:^4.5.3":
version: 4.5.7
resolution: "fast-xml-parser@npm:4.5.7"
dependencies:
strnum: ^1.0.5
bin:
fxparser: src/cli/cli.js
checksum: 5d79fa19eb0f6e6f5e0cb660e91ccf695146b0333124fc4bd7d95a6ce0a07cee602532c1cdfc606fafbda6100d822df9247381a42f4b7156b5df17d1395e1fb7
languageName: node
linkType: hard

"fastest-levenshtein@npm:^1.0.16":
version: 1.0.16
resolution: "fastest-levenshtein@npm:1.0.16"
Expand Down Expand Up @@ -18290,7 +18302,7 @@ __metadata:
languageName: node
linkType: hard

"strnum@npm:^1.1.1":
"strnum@npm:^1.0.5, strnum@npm:^1.1.1":
version: 1.1.2
resolution: "strnum@npm:1.1.2"
checksum: a85219eda13e97151c95e343a9e5960eacfb0a0ff98104b4c9cb7a212e3008bddf0c9714c9c37c2e508be78e741a04afc80027c2dc18509d1b5ffd4c37191fc2
Expand Down
Loading