@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celilo/e2e",
3
- "version": "0.7.13",
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.5",
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 = ['--keep', '--reuse', '--live', '--published', '--all-modules'];
102
+ export const RUN_FLAGS = ['--all', '--all-modules', '--keep', '--reuse', '--live', '--published'];
97
103
 
98
104
  export const DOWN_FLAGS = ['--keep', '--all'];
99
105
 
@@ -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}`);