@nestia/e2e 14.0.1 → 14.0.3

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.
@@ -16,12 +16,45 @@ import { pathToFileURL } from "url";
16
16
  *
17
17
  * When you want to see some utilization cases, see the below example links.
18
18
  *
19
+ * Native boundary: Discovery lists directories with `readdir` and `stat` and
20
+ * joins children with `path.resolve`; symbolic links are followed, and each
21
+ * directory is entered once by its filesystem real path so aliases and ancestor
22
+ * cycles do not repeat discovery. The extension is matched as a file-name
23
+ * suffix and the prefix with `startsWith`, both case-sensitively as spelled, so
24
+ * name case is judged by the caller's spelling and not by the volume. A file is
25
+ * imported through a specifier: the path relative to this module in `/` form
26
+ * when `path.relative` can express one, and where it cannot, as on Windows
27
+ * between drive roots, the absolute path for a CommonJS build, whose `require`
28
+ * rejects a URL, or a `file:` URL from `pathToFileURL` for native ESM, which
29
+ * rejects a Windows path.
30
+ *
31
+ * Processing cost: Discovery visits each real directory once and performs one
32
+ * metadata lookup per listed entry. A monotone queue index dispatches each
33
+ * admitted module once without shifting the remaining queue; execution walks
34
+ * module exports once. Storage grows with discovered directories, modules and
35
+ * execution records; concurrency allocates the requested number of workers.
36
+ *
37
+ * Reuse: Discovery and module loading are shared by the tests of one run. New
38
+ * runs rediscover files; Node owns module caching, so editing an already
39
+ * imported module does not promise reload in the same process.
40
+ *
41
+ * Resource ownership: One run owns the discovered module queue, visited
42
+ * directory set and report. At most the requested number of module tasks are
43
+ * active, all are awaited before success or strict rejection, and returned
44
+ * records transfer to the caller. Listener failure also settles workers;
45
+ * cancellation and unloading Node cached modules are not provided.
46
+ *
19
47
  * @author Jeongho Nam - https://github.com/samchon
20
48
  * @example
21
49
  * https://github.com/samchon/nestia-start/blob/master/test/index.ts
22
50
  *
23
51
  * @example
24
52
  * https://github.com/samchon/backend/blob/master/test/index.ts
53
+ *
54
+ * @evidence contracts/common.md#principled-implementation The executor walks the location recursively, admits a file only when its basename ends with the extension and starts with the prefix and the filter accepts it, imports the admitted files, and runs every exported function whose name starts with the prefix, with at most `simultaneous` runs in flight; the file gate runs before the import so an excluded file is never loaded.
55
+ * @evidence contracts/common.md#clear-and-simple-design Two public entry points share one private pipeline: discovery (`iterate`), the import specifier (`specifier`), and execution (`execute`) are separate helpers, and the two modes differ by one flag.
56
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Selection is by prefix, extension, and the caller's filter, with no known file names; the import specifier logic distinguishes the module system in use rather than special-casing a platform.
57
+ * @evidence contracts/common.md#meaningful-documentation The namespace prose says what it runs and why, with links to example repositories, and the members document their options.
25
58
  */
26
59
  export namespace DynamicExecutor {
27
60
  /**
@@ -29,12 +62,27 @@ export namespace DynamicExecutor {
29
62
  *
30
63
  * @template Arguments Type of parameters
31
64
  * @template Ret Type of return value
65
+ * @evidence contracts/common.md#principled-implementation A dynamic function is an asynchronous function of the parameters the executor supplies.
66
+ * @evidence contracts/common.md#clear-and-simple-design A single call signature.
67
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts It is a type and adds no runtime behavior.
68
+ * @evidence contracts/common.md#meaningful-documentation The comment names the parameter and return type arguments.
32
69
  */
33
70
  export interface Closure<Arguments extends any[], Ret = any> {
34
71
  (...args: Arguments): Promise<Ret>;
35
72
  }
36
73
 
37
- /** Options for dynamic executor. */
74
+ /**
75
+ * Options for dynamic executor.
76
+ *
77
+ * Native boundary: The location is a native directory path resolved with
78
+ * `path.resolve`, the extension is a suffix without a dot, and `simultaneous`
79
+ * bounds in-process runs only, so no process or thread is created.
80
+ *
81
+ * @evidence contracts/common.md#principled-implementation The fields are what selection and execution need: the prefix, the location, the parameter factory, and optional hooks, concurrency, and extension; `simultaneous` and the extension have documented defaults.
82
+ * @evidence contracts/common.md#clear-and-simple-design A flat option record whose optional members are the extension points (listener, filter, wrapper).
83
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Every value is caller-supplied; the defaults are the documented ones (`js`, one at a time).
84
+ * @evidence contracts/common.md#meaningful-documentation Each option documents its meaning, including that the filter receives a file basename and never a function name.
85
+ */
38
86
  export interface IProps<Parameters extends any[], Ret = any> {
39
87
  /**
40
88
  * Prefix of function name.
@@ -56,6 +104,10 @@ export namespace DynamicExecutor {
56
104
  *
57
105
  * @param name Function name
58
106
  * @returns Parameters
107
+ * @evidence contracts/common.md#principled-implementation The function is called with the function name at each execution and returns the argument list, so each test gets fresh parameters keyed by its name.
108
+ * @evidence contracts/common.md#clear-and-simple-design One required callback with one input.
109
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Arguments come only from the caller's function.
110
+ * @evidence contracts/common.md#meaningful-documentation The comment documents the parameter and the return value.
59
111
  */
60
112
  parameters: (name: string) => Parameters;
61
113
 
@@ -64,7 +116,14 @@ export namespace DynamicExecutor {
64
116
  *
65
117
  * Listener of completion of a test function.
66
118
  *
119
+ * A thrown listener error rejects the run in either mode after active
120
+ * module tasks settle; listeners should handle their reporting errors.
121
+ *
67
122
  * @param exec Execution result of a test function
123
+ * @evidence contracts/common.md#principled-implementation The listener is called from the `finally` of each execution with the execution record, so it observes both success and failure, after the completion time is set.
124
+ * @evidence contracts/common.md#clear-and-simple-design One optional callback with one record argument.
125
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The completion hook receives the actual execution record in finally; its own exception is not swallowed and can reject either execution mode.
126
+ * @evidence contracts/common.md#meaningful-documentation The comment states that it listens to the completion of a test function.
68
127
  */
69
128
  onComplete?: (exec: IExecution) => void;
70
129
 
@@ -76,8 +135,16 @@ export namespace DynamicExecutor {
76
135
  * `false`, the file would never be imported, so that every function defined
77
136
  * in the file would never be executed either.
78
137
  *
138
+ * Native boundary: The predicate receives the entry name exactly as
139
+ * `readdir` lists it, without case or separator normalization, so a
140
+ * comparison on a case-insensitive volume is the caller's to make.
141
+ *
79
142
  * @param file File name (basename) of the dynamic functions
80
143
  * @returns Whether to run or not
144
+ * @evidence contracts/common.md#principled-implementation The predicate is evaluated on the file basename before the file is imported, so a `false` result prevents both the import and the execution of every function in it.
145
+ * @evidence contracts/common.md#clear-and-simple-design One optional predicate over one string.
146
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Selection follows the caller's predicate, not fixed names.
147
+ * @evidence contracts/common.md#meaningful-documentation The comment states the argument and the effect of a `false` answer.
81
148
  */
82
149
  filter?: (file: string) => boolean;
83
150
 
@@ -92,6 +159,10 @@ export namespace DynamicExecutor {
92
159
  * @param closure Function to be executed
93
160
  * @param parameters Parameters, result of options.parameters function.
94
161
  * @returns Wrapper function
162
+ * @evidence contracts/common.md#principled-implementation When present, the wrapper is called instead of the function directly with the name, the function, and the parameters, so a caller can add setup, retries, or measurement around each test.
163
+ * @evidence contracts/common.md#clear-and-simple-design One optional callback that replaces the direct call.
164
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The executor calls the wrapper for every function it runs, without a test-specific bypass.
165
+ * @evidence contracts/common.md#meaningful-documentation The comment documents each parameter and the return value.
95
166
  */
96
167
  wrapper?: (
97
168
  name: string,
@@ -119,7 +190,18 @@ export namespace DynamicExecutor {
119
190
  extension?: string;
120
191
  }
121
192
 
122
- /** Report, result of dynamic execution. */
193
+ /**
194
+ * Report, result of dynamic execution.
195
+ *
196
+ * Native boundary: The report preserves the caller's native directory
197
+ * spelling, including relative paths; discovered execution locations are
198
+ * resolved separately with Node path operations.
199
+ *
200
+ * @evidence contracts/common.md#principled-implementation The report keeps the location, every execution record in start order, and the elapsed time computed as the difference of two clock reads.
201
+ * @evidence contracts/common.md#clear-and-simple-design A flat record with no behavior.
202
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Every field is measured by the executor.
203
+ * @evidence contracts/common.md#meaningful-documentation Each field documents its meaning.
204
+ */
123
205
  export interface IReport {
124
206
  /** Location path of dynamic functions. */
125
207
  location: string;
@@ -131,7 +213,17 @@ export namespace DynamicExecutor {
131
213
  time: number;
132
214
  }
133
215
 
134
- /** Execution of a test function. */
216
+ /**
217
+ * Execution of a test function.
218
+ *
219
+ * Native boundary: The location field holds the native absolute path of the
220
+ * file as resolved, so it carries backslashes on Windows.
221
+ *
222
+ * @evidence contracts/common.md#principled-implementation An execution records the function name, its file, the returned value, the error or `null`, and the start and completion instants, which is what a listener needs to report a test.
223
+ * @evidence contracts/common.md#clear-and-simple-design A flat record with no behavior.
224
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Every field is measured by the executor.
225
+ * @evidence contracts/common.md#meaningful-documentation Each field documents its meaning, and the two time fields state that they hold ISO 8601 strings.
226
+ */
135
227
  export interface IExecution {
136
228
  /** Name of function. */
137
229
  name: string;
@@ -145,22 +237,40 @@ export namespace DynamicExecutor {
145
237
  /** Error when occurred. */
146
238
  error: Error | null;
147
239
 
148
- /** Elapsed time. */
240
+ /** Start time, as an ISO 8601 string. */
149
241
  started_at: string;
150
242
 
151
- /** Completion time. */
243
+ /** Completion time, as an ISO 8601 string. */
152
244
  completed_at: string;
153
245
  }
154
246
 
155
247
  /**
156
248
  * Prepare dynamic executor in strict mode.
157
249
  *
158
- * In strict mode, if any error occurs, the program will be terminated
159
- * directly. Otherwise, {@link validate} mode does not terminate when error
160
- * occurs, but just archive the error log.
250
+ * Strict mode stops dispatching further modules after a failure and rejects
251
+ * after active module tasks settle. {@link validate} records test failures and
252
+ * continues instead.
253
+ *
254
+ * Processing cost: The shared executor discovers modules once, dispatches
255
+ * each queue index once and walks each admitted export; this entry only
256
+ * selects the failure policy.
257
+ *
258
+ * Reuse: Discovery and module loading are shared by the tests of one run. New
259
+ * runs rediscover files; Node owns module caching, so editing an already
260
+ * imported module does not promise reload in the same process.
261
+ *
262
+ * Resource ownership: One run owns the discovered module queue, visited
263
+ * directory set and report. At most the requested number of module tasks are
264
+ * active, all are awaited before success or strict rejection, and returned
265
+ * records transfer to the caller. Listener failure also settles workers;
266
+ * cancellation and unloading Node cached modules are not provided.
161
267
  *
162
268
  * @param props Properties of dynamic execution
163
269
  * @returns Report of dynamic test functions execution
270
+ * @evidence contracts/common.md#principled-implementation Strict mode rethrows the first error of a function after the execution record and the listener have seen it; no further file is dispatched once a task has failed, and the returned promise rejects with that first failure only after the in-flight tasks have settled, so no test runs after the caller observes the failure.
271
+ * @evidence contracts/common.md#clear-and-simple-design A one-line binding of the shared pipeline with the strict flag.
272
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The mode changes only whether a failure is rethrown; nothing is silenced.
273
+ * @evidence contracts/common.md#meaningful-documentation The comment states the strict behavior, contrasts it with `validate`, and documents the parameter and the report.
164
274
  */
165
275
  export const assert = <Arguments extends any[]>(
166
276
  props: IProps<Arguments>,
@@ -169,12 +279,30 @@ export namespace DynamicExecutor {
169
279
  /**
170
280
  * Prepare dynamic executor in loose mode.
171
281
  *
172
- * In loose mode, the program would not be terminated even when error occurs.
173
- * Instead, the error would be archived and returns as a list. Otherwise,
174
- * {@link assert} mode terminates the program directly when error occurs.
282
+ * Loose mode records test failures and continues with subsequent tests.
283
+ * Discovery, import and completion-listener errors still reject the run.
284
+ * {@link assert} rejects on a test failure after active tasks settle.
285
+ *
286
+ * Processing cost: The shared executor discovers modules once, dispatches
287
+ * each queue index once and walks each admitted export; this entry only
288
+ * selects the failure policy.
289
+ *
290
+ * Reuse: Discovery and module loading are shared by the tests of one run. New
291
+ * runs rediscover files; Node owns module caching, so editing an already
292
+ * imported module does not promise reload in the same process.
293
+ *
294
+ * Resource ownership: One run owns the discovered module queue, visited
295
+ * directory set and report. At most the requested number of module tasks are
296
+ * active, all are awaited before success or strict rejection, and returned
297
+ * records transfer to the caller. Listener failure also settles workers;
298
+ * cancellation and unloading Node cached modules are not provided.
175
299
  *
176
300
  * @param props Properties of dynamic executor
177
301
  * @returns Report of dynamic test functions execution
302
+ * @evidence contracts/common.md#principled-implementation Loose mode records test errors and continues, returning them in execution records. Discovery, module import and completion-listener failures are outside that test-error policy and reject the run.
303
+ * @evidence contracts/common.md#clear-and-simple-design A one-line binding of the shared pipeline with the loose flag.
304
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The mode changes only whether a failure is rethrown; the error stays in the report.
305
+ * @evidence contracts/common.md#meaningful-documentation The comment states the loose behavior, contrasts it with `assert`, and documents the parameter and the report.
178
306
  */
179
307
  export const validate = <Arguments extends any[]>(
180
308
  props: IProps<Arguments>,
@@ -204,14 +332,24 @@ export namespace DynamicExecutor {
204
332
  filter: props.filter,
205
333
  executor,
206
334
  });
335
+ // A rejected task stops the dispatch of the remaining files, and the run
336
+ // settles only after the in-flight tasks end, so that no test keeps
337
+ // running after the caller has seen the failure.
338
+ let failure: { error: unknown } | null = null;
339
+ let next: number = 0;
207
340
  await Promise.all(
208
341
  new Array(simultaneous).fill(0).map(async () => {
209
- while (processes.length !== 0) {
210
- const task = processes.shift();
211
- await task?.();
342
+ while (next < processes.length && failure === null) {
343
+ const task = processes[next++];
344
+ try {
345
+ await task?.();
346
+ } catch (error) {
347
+ failure ??= { error };
348
+ }
212
349
  }
213
350
  }),
214
351
  );
352
+ if (failure !== null) throw (failure as { error: unknown }).error;
215
353
  report.time = Date.now() - report.time;
216
354
  return report;
217
355
  };
@@ -247,16 +385,21 @@ export namespace DynamicExecutor {
247
385
  executor: (path: string, modulo: Module<Arguments>) => Promise<void>;
248
386
  }): Promise<Array<() => Promise<void>>> => {
249
387
  const container: Array<() => Promise<void>> = [];
388
+ const visited: Set<string> = new Set();
250
389
  const visitor = async (path: string): Promise<void> => {
390
+ const identity: string = await fs.promises.realpath(path);
391
+ if (visited.has(identity)) return;
392
+ visited.add(identity);
251
393
  const directory: string[] = await fs.promises.readdir(path);
252
394
  for (const file of directory) {
253
395
  const location: string = NodePath.resolve(`${path}/${file}`);
254
- const stats: fs.Stats = await fs.promises.lstat(location);
396
+ const stats: fs.Stats = await fs.promises.stat(location);
255
397
 
256
398
  if (stats.isDirectory() === true) {
257
399
  await visitor(location);
258
400
  continue;
259
401
  }
402
+ if (stats.isFile() === false) continue;
260
403
  // Compare the whole suffix. A fixed-width slice silently assumed a
261
404
  // two-character extension, so every longer one ("mjs", "cjs", "tsx")
262
405
  // matched nothing and the run reported success with no test executed.