@celilo/e2e 0.7.13 → 0.7.14
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/package.json +2 -2
- package/src/cli/command-registry.ts +9 -3
- package/src/cli/completion.ts +12 -2
- package/src/cli/index.ts +105 -1
- package/src/runner.ts +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celilo/e2e",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.14",
|
|
4
4
|
"description": "E2E test infrastructure for Celilo-deployed applications. Provides a simulated internet with DNS hierarchy, ACME server, firewalls, and target machines in Docker.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@celilo/cli-display": "^0.1.9",
|
|
40
|
-
"@celilo/event-bus": "^0.1.
|
|
40
|
+
"@celilo/event-bus": "^0.1.6",
|
|
41
41
|
"yaml": "^2.8.0",
|
|
42
42
|
"zod": "^3.24.1"
|
|
43
43
|
},
|
|
@@ -23,15 +23,21 @@ export interface FlagDef {
|
|
|
23
23
|
export const COMMANDS: CommandDef[] = [
|
|
24
24
|
{
|
|
25
25
|
name: 'run',
|
|
26
|
-
description: 'Run e2e tests — pass a module path or pattern, or --all-modules',
|
|
26
|
+
description: 'Run e2e tests — pass a module path or pattern, or --all / --all-modules',
|
|
27
27
|
flags: [
|
|
28
|
+
{ name: '--all', description: 'Run the full regression: every module suite + top-level e2e/tests/' },
|
|
29
|
+
{ name: '--all-modules', description: 'Run e2e tests for all modules with e2e/ directories' },
|
|
28
30
|
{ name: '--keep', description: 'Keep network running after tests' },
|
|
29
31
|
{ name: '--reuse', description: 'Reuse existing network if running' },
|
|
30
32
|
{ name: '--live', description: 'Use live (non-simulated) internet' },
|
|
31
33
|
{ name: '--published', description: 'Use published .netapp packages' },
|
|
32
|
-
{ name: '--all-modules', description: 'Run e2e tests for all modules with e2e/ directories' },
|
|
33
34
|
],
|
|
34
35
|
},
|
|
36
|
+
{
|
|
37
|
+
name: 'list',
|
|
38
|
+
description: 'List every discoverable Docker e2e test (module + top-level)',
|
|
39
|
+
flags: [{ name: '--paths', description: 'Also show each test file path' }],
|
|
40
|
+
},
|
|
35
41
|
{
|
|
36
42
|
name: 'up',
|
|
37
43
|
description: 'Start interactive test network',
|
|
@@ -93,7 +99,7 @@ export const COMMANDS: CommandDef[] = [
|
|
|
93
99
|
|
|
94
100
|
export const UP_PRESETS = ['--caddy', '--full-stack', '--infrastructure'];
|
|
95
101
|
|
|
96
|
-
export const RUN_FLAGS = ['--
|
|
102
|
+
export const RUN_FLAGS = ['--all', '--all-modules', '--keep', '--reuse', '--live', '--published'];
|
|
97
103
|
|
|
98
104
|
export const DOWN_FLAGS = ['--keep', '--all'];
|
|
99
105
|
|
package/src/cli/completion.ts
CHANGED
|
@@ -56,6 +56,11 @@ export function getCompletions(words: string[], current: number): string[] {
|
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
if (command === 'list' && currentIndex >= 1) {
|
|
60
|
+
const used = new Set(args.slice(1));
|
|
61
|
+
return filterSuggestions(['--paths'].filter((f) => !used.has(f)), args[currentIndex] || '');
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
if (command === 'completion' && currentIndex === 1) {
|
|
60
65
|
return filterSuggestions(['zsh', 'bash'], args[1] || '');
|
|
61
66
|
}
|
|
@@ -108,13 +113,17 @@ _cele2e() {
|
|
|
108
113
|
case $line[1] in
|
|
109
114
|
run)
|
|
110
115
|
_arguments \\
|
|
116
|
+
'--all[Run the full regression: every module suite + top-level e2e/tests/]' \\
|
|
117
|
+
'--all-modules[Run e2e tests for all modules with e2e/ directories]' \\
|
|
111
118
|
'--keep[Keep network running after tests]' \\
|
|
112
119
|
'--reuse[Reuse existing network if running]' \\
|
|
113
120
|
'--live[Use live (non-simulated) internet]' \\
|
|
114
121
|
'--published[Use published .netapp packages]' \\
|
|
115
|
-
'--all-modules[Run e2e tests for all modules with e2e/ directories]' \\
|
|
116
122
|
'*::test name or module path:_cele2e_run_args'
|
|
117
123
|
;;
|
|
124
|
+
list)
|
|
125
|
+
_arguments '--paths[Also show each test file path]'
|
|
126
|
+
;;
|
|
118
127
|
up)
|
|
119
128
|
_arguments '1: :_cele2e_up_presets'
|
|
120
129
|
;;
|
|
@@ -137,7 +146,7 @@ _cele2e() {
|
|
|
137
146
|
completion)
|
|
138
147
|
_arguments '1: :_cele2e_completion_shells'
|
|
139
148
|
;;
|
|
140
|
-
status|load|clear-timing)
|
|
149
|
+
status|load|clear-timing|list)
|
|
141
150
|
;;
|
|
142
151
|
esac
|
|
143
152
|
;;
|
|
@@ -147,6 +156,7 @@ _cele2e() {
|
|
|
147
156
|
_cele2e_commands() {
|
|
148
157
|
local commands=(
|
|
149
158
|
'run:Run e2e tests (pattern filters by test file name)'
|
|
159
|
+
'list:List every discoverable Docker e2e test (module + top-level)'
|
|
150
160
|
'up:Start interactive test network'
|
|
151
161
|
'down:Tear down running network'
|
|
152
162
|
'build-infra:Rebuild @celilo/e2e Docker images and standard module .netapps'
|
package/src/cli/index.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { spawnSync } from 'node:child_process';
|
|
19
|
-
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
19
|
+
import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
20
20
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
21
21
|
import { parse as parseYaml } from 'yaml';
|
|
22
22
|
import { runBuild } from './build';
|
|
@@ -55,6 +55,53 @@ function detectTestDir(): { stateDir: string; testsPath: string } {
|
|
|
55
55
|
return { stateDir: cwd, testsPath: join(cwd, 'tests') };
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the repo root to scan for the full e2e suite (`run --all` / `list`).
|
|
60
|
+
*
|
|
61
|
+
* The workspace `./cele2e` wrapper cd's into packages/e2e before exec'ing, so
|
|
62
|
+
* process.cwd() points at the e2e package — useless for module discovery. The
|
|
63
|
+
* wrapper exports E2E_INVOKE_CWD with the operator's real PWD; we walk up from
|
|
64
|
+
* there (or process.cwd() as a fallback) to the nearest ancestor that has a
|
|
65
|
+
* `modules/` directory, which is the infra repo root. This is what makes the
|
|
66
|
+
* full-regression command immune to the wrapper's internal cd (ISS-0013).
|
|
67
|
+
*/
|
|
68
|
+
function resolveSuiteRoot(): string {
|
|
69
|
+
const start = resolve(process.env.E2E_INVOKE_CWD || process.cwd());
|
|
70
|
+
let dir = start;
|
|
71
|
+
for (;;) {
|
|
72
|
+
if (existsSync(join(dir, 'modules'))) return dir;
|
|
73
|
+
const parent = dirname(dir);
|
|
74
|
+
if (parent === dir) break;
|
|
75
|
+
dir = parent;
|
|
76
|
+
}
|
|
77
|
+
return start;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Enumerate every discoverable Docker e2e test (all module suites + the repo's
|
|
82
|
+
* top-level e2e/tests/) as `{ name, file }`, sorted, with module tests first.
|
|
83
|
+
*/
|
|
84
|
+
function discoverSuiteTests(suiteRoot: string): { name: string; file: string; group: string }[] {
|
|
85
|
+
const out: { name: string; file: string; group: string }[] = [];
|
|
86
|
+
for (const moduleDir of findAllModules(suiteRoot)) {
|
|
87
|
+
const tp = join(moduleDir, 'e2e');
|
|
88
|
+
const testsDir = existsSync(join(moduleDir, 'manifest.yml'))
|
|
89
|
+
? resolve(moduleDir, readManifestTestsDir(join(moduleDir, 'manifest.yml')) ?? 'e2e')
|
|
90
|
+
: tp;
|
|
91
|
+
if (!existsSync(testsDir)) continue;
|
|
92
|
+
for (const f of readdirSync(testsDir).filter((f) => f.endsWith('.test.ts')).sort()) {
|
|
93
|
+
out.push({ name: f.replace(/\.test\.ts$/, ''), file: join(testsDir, f), group: basename(moduleDir) });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const topLevel = join(suiteRoot, 'e2e', 'tests');
|
|
97
|
+
if (existsSync(topLevel)) {
|
|
98
|
+
for (const f of readdirSync(topLevel).filter((f) => f.endsWith('.test.ts')).sort()) {
|
|
99
|
+
out.push({ name: f.replace(/\.test\.ts$/, ''), file: join(topLevel, f), group: '(top-level)' });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
|
|
58
105
|
function runScript(script: string, args: string[], env?: Record<string, string>): never {
|
|
59
106
|
const result = spawnSync(join(BIN_DIR, script), args, {
|
|
60
107
|
stdio: 'inherit',
|
|
@@ -72,6 +119,8 @@ Usage:
|
|
|
72
119
|
|
|
73
120
|
Commands:
|
|
74
121
|
run [pattern] Run e2e tests (pattern filters by test file name)
|
|
122
|
+
run --all Run the FULL regression: every module suite + top-level e2e/tests/
|
|
123
|
+
list List every discoverable Docker e2e test (module + top-level)
|
|
75
124
|
up [preset] Start interactive test network
|
|
76
125
|
down Tear down running network
|
|
77
126
|
build-infra [dirs...] Rebuild Docker images and package all modules as .netapps
|
|
@@ -84,6 +133,8 @@ Commands:
|
|
|
84
133
|
completion <shell> Generate shell completion script (zsh|bash)
|
|
85
134
|
|
|
86
135
|
Options for \`run\`:
|
|
136
|
+
--all Run every module suite AND the top-level e2e/tests/ (full regression)
|
|
137
|
+
--all-modules Run every module suite (no top-level tests)
|
|
87
138
|
--keep Keep network running after tests
|
|
88
139
|
--reuse Reuse existing network if running
|
|
89
140
|
--live Use live (non-simulated) internet
|
|
@@ -103,6 +154,8 @@ Options for \`build-infra\`:
|
|
|
103
154
|
--skip-modules Skip .netapp packaging (only rebuild Docker images)
|
|
104
155
|
|
|
105
156
|
Examples:
|
|
157
|
+
cele2e run --all # FULL regression: every module suite + top-level e2e/tests/
|
|
158
|
+
cele2e list # enumerate every discoverable Docker e2e test
|
|
106
159
|
cele2e run # in a module: run its tests; outside: run all modules' tests
|
|
107
160
|
cele2e run deploy # run tests matching "deploy"
|
|
108
161
|
cele2e run caddy-direct-internet # from modules/, finds the matching module automatically
|
|
@@ -130,6 +183,34 @@ if (command === '--get-completions') {
|
|
|
130
183
|
|
|
131
184
|
switch (command) {
|
|
132
185
|
case 'run': {
|
|
186
|
+
// Full-suite modes (ISS-0013). `--all` runs every module suite AND the
|
|
187
|
+
// repo's top-level e2e/tests/ in one invocation; `--all-modules` runs just
|
|
188
|
+
// the module suites. Both resolve the repo root via resolveSuiteRoot() so
|
|
189
|
+
// they work through the `./cele2e` wrapper's cd into packages/e2e.
|
|
190
|
+
if (args.includes('--all') || args.includes('--all-modules')) {
|
|
191
|
+
const wantTopLevel = args.includes('--all');
|
|
192
|
+
const suiteRoot = resolveSuiteRoot();
|
|
193
|
+
const allModules = findAllModules(suiteRoot);
|
|
194
|
+
const env: Record<string, string> = {
|
|
195
|
+
E2E_TEST_DIR: join(suiteRoot, 'e2e'),
|
|
196
|
+
E2E_MODULE_DIRS: allModules.join('\n'),
|
|
197
|
+
};
|
|
198
|
+
const topLevel = join(suiteRoot, 'e2e', 'tests');
|
|
199
|
+
if (wantTopLevel && existsSync(topLevel)) {
|
|
200
|
+
env.E2E_TOP_LEVEL_TESTS = topLevel;
|
|
201
|
+
}
|
|
202
|
+
if (allModules.length === 0 && !env.E2E_TOP_LEVEL_TESTS) {
|
|
203
|
+
console.error(`No e2e tests found under ${suiteRoot}`);
|
|
204
|
+
console.error('Run from the infra repo root (or set E2E_INVOKE_CWD to it).');
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
runScript(
|
|
208
|
+
'e2e-run',
|
|
209
|
+
args.filter((a) => a !== '--all' && a !== '--all-modules'),
|
|
210
|
+
env,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
133
214
|
// Check if any non-flag arg is a module path (has manifest.yml)
|
|
134
215
|
const modulePath = args
|
|
135
216
|
.filter((a) => !a.startsWith('--'))
|
|
@@ -180,6 +261,29 @@ switch (command) {
|
|
|
180
261
|
break;
|
|
181
262
|
}
|
|
182
263
|
|
|
264
|
+
case 'list': {
|
|
265
|
+
// Enumerate every discoverable Docker e2e test (module + top-level) so the
|
|
266
|
+
// suite is inspectable without reading runner.ts (ISS-0013).
|
|
267
|
+
const suiteRoot = resolveSuiteRoot();
|
|
268
|
+
const tests = discoverSuiteTests(suiteRoot);
|
|
269
|
+
if (tests.length === 0) {
|
|
270
|
+
console.error(`No e2e tests found under ${suiteRoot}`);
|
|
271
|
+
console.error('Run from the infra repo root (or set E2E_INVOKE_CWD to it).');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
const wantPaths = args.includes('--paths');
|
|
275
|
+
let lastGroup = '';
|
|
276
|
+
for (const t of tests) {
|
|
277
|
+
if (t.group !== lastGroup) {
|
|
278
|
+
console.log(`\n${t.group}:`);
|
|
279
|
+
lastGroup = t.group;
|
|
280
|
+
}
|
|
281
|
+
console.log(wantPaths ? ` ${t.name} ${t.file}` : ` ${t.name}`);
|
|
282
|
+
}
|
|
283
|
+
console.log(`\n${tests.length} tests. Run all: cele2e run --all`);
|
|
284
|
+
process.exit(0);
|
|
285
|
+
}
|
|
286
|
+
|
|
183
287
|
case 'up':
|
|
184
288
|
runScript('e2e-up', args);
|
|
185
289
|
|
package/src/runner.ts
CHANGED
|
@@ -58,6 +58,8 @@ function resolveModuleTestsDir(dir: string): string {
|
|
|
58
58
|
const testsPath = moduleDir
|
|
59
59
|
? resolveModuleTestsDir(moduleDir)
|
|
60
60
|
: (process.env.E2E_TESTS_PATH ?? join(testDir, 'tests'));
|
|
61
|
+
/** Set by `cele2e run --all` so a module-dirs run ALSO picks up the repo's top-level e2e/tests/. */
|
|
62
|
+
const topLevelTestsPath = process.env.E2E_TOP_LEVEL_TESTS;
|
|
61
63
|
const timingFile = join(testDir, '.e2e-timing.json');
|
|
62
64
|
const persistentFile = join(testDir, '.e2e-persistent.json');
|
|
63
65
|
|
|
@@ -427,6 +429,22 @@ async function main() {
|
|
|
427
429
|
.map((f) => resolve(tp, f));
|
|
428
430
|
testFiles.push(...files);
|
|
429
431
|
}
|
|
432
|
+
// `cele2e run --all` sets E2E_MODULE_DIRS (every module) AND
|
|
433
|
+
// E2E_TOP_LEVEL_TESTS (the repo's top-level e2e/tests/), so one invocation
|
|
434
|
+
// covers BOTH the module suites and the top-level tests. Without this, the
|
|
435
|
+
// module branch and the testsPath branch below are mutually exclusive and
|
|
436
|
+
// no single run can cover everything (ISS-0013).
|
|
437
|
+
if (topLevelTestsPath) {
|
|
438
|
+
if (!existsSync(topLevelTestsPath)) {
|
|
439
|
+
console.error(`${red}No top-level tests directory found at ${topLevelTestsPath}${reset}`);
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
const files = readdirSync(topLevelTestsPath)
|
|
443
|
+
.filter((f) => f.endsWith('.test.ts'))
|
|
444
|
+
.sort()
|
|
445
|
+
.map((f) => resolve(topLevelTestsPath, f));
|
|
446
|
+
testFiles.push(...files);
|
|
447
|
+
}
|
|
430
448
|
} else {
|
|
431
449
|
if (!existsSync(testsPath)) {
|
|
432
450
|
console.error(`${red}No tests/ directory found at ${testsPath}${reset}`);
|