@camperaid/watest 2.5.0 → 2.5.1
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/core/base.js +10 -3
- package/core/series.js +49 -8
- package/core/settings.js +1 -1
- package/core/system.js +68 -0
- package/eslint.config.js +1 -1
- package/index.js +10 -0
- package/interfaces/servicer.js +0 -1
- package/package.json +1 -1
- package/tests/base/t_system.js +59 -0
- package/tests/base/t_throws.js +67 -0
- package/tests/e2e/t_folder.js +1 -1
- package/tests/e2e/t_loader.js +2 -2
- package/tests/e2e/t_loader_mixed.js +2 -2
- package/tests/e2e/t_loader_multiple.js +3 -3
- package/tests/e2e/t_loader_multiple_patterns.js +3 -3
- package/tests/e2e/t_single.js +1 -1
- package/tests/e2e/t_wd_firefox.js +2 -2
- package/tests/e2e/t_wd_firefox_chrome.js +2 -2
- package/tests/e2e/t_wd_firefox_chrome_pattern.js +2 -2
- package/tests/e2e/t_wd_mixed_firefox.js +2 -2
- package/tests/e2e/t_wd_mixed_firefox_chrome.js +2 -2
- package/tests/series/build/t_pattern_filtering.js +175 -0
- package/tests/series/logging/t_verify.js +1 -1
- package/tests/series/meta.js +1 -0
- package/tests/series/perform/t_failure.js +1 -0
- package/tests/series/perform/t_failure_notest.js +1 -0
- package/tests/series/perform/t_intermittent.js +2 -0
- package/tests/series/perform/t_intermittent_global.js +1 -0
- package/tests/series/perform/t_missing_perma.js +2 -0
- package/tests/series/perform/t_nested.js +1 -0
- package/tests/series/perform/t_perma.js +1 -0
- package/tests/series/perform/t_success.js +2 -0
- package/tests/series/run/t_debunk_failure.js +1 -1
- package/tests/series/run/t_debunk_success.js +1 -1
- package/tests/series/run/t_nested.js +1 -1
- package/tests/series/run/t_verify.js +1 -1
- package/tests/series/run/t_verify_webdriver.js +1 -1
- package/tests/series/servicer/t_servicer.js +84 -0
package/core/base.js
CHANGED
|
@@ -551,9 +551,16 @@ function is_out(got, expected, msg) {
|
|
|
551
551
|
return false;
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
-
function throws(func,
|
|
555
|
-
const on_no_exception = () => fail(`${msg}: no '${
|
|
556
|
-
const on_exception = e =>
|
|
554
|
+
function throws(func, expected, msg) {
|
|
555
|
+
const on_no_exception = () => fail(`${msg}: no '${expected}' exception`);
|
|
556
|
+
const on_exception = e =>
|
|
557
|
+
is(
|
|
558
|
+
expected instanceof RegExp || typeof expected === 'string'
|
|
559
|
+
? e?.message
|
|
560
|
+
: e,
|
|
561
|
+
expected,
|
|
562
|
+
msg,
|
|
563
|
+
);
|
|
557
564
|
return throw_internal(func, on_no_exception, on_exception);
|
|
558
565
|
}
|
|
559
566
|
|
package/core/series.js
CHANGED
|
@@ -145,9 +145,6 @@ class Series {
|
|
|
145
145
|
await this.runFor(this.failures, '2');
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
|
|
149
|
-
await settings.servicer.shutdown();
|
|
150
|
-
|
|
151
148
|
log(`Elapsed: ${Date.now() - start_time}ms`);
|
|
152
149
|
}
|
|
153
150
|
|
|
@@ -181,6 +178,7 @@ class Series {
|
|
|
181
178
|
}
|
|
182
179
|
|
|
183
180
|
shutdown() {
|
|
181
|
+
console.log(`Testsuite: shutdown`);
|
|
184
182
|
testflow.unlock();
|
|
185
183
|
return this.failures.length > 0 ? this.failures : null;
|
|
186
184
|
}
|
|
@@ -367,8 +365,24 @@ class Series {
|
|
|
367
365
|
return [];
|
|
368
366
|
}
|
|
369
367
|
|
|
368
|
+
// Filter subfolders to only process those that match the patterns
|
|
369
|
+
let filteredSubfolders = subfolders;
|
|
370
|
+
if (patterns.length > 0) {
|
|
371
|
+
filteredSubfolders = subfolders.filter(subfolder => {
|
|
372
|
+
const subfolderPath = `${folder}/${subfolder}`;
|
|
373
|
+
return (
|
|
374
|
+
this.matchedPatterns({
|
|
375
|
+
path: subfolderPath,
|
|
376
|
+
webdriver,
|
|
377
|
+
patterns,
|
|
378
|
+
path_is_not_final: true,
|
|
379
|
+
}).length > 0
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
370
384
|
let subtests_for_subfolders = await Promise.all(
|
|
371
|
-
|
|
385
|
+
filteredSubfolders.map(subfolder =>
|
|
372
386
|
this.build({
|
|
373
387
|
patterns,
|
|
374
388
|
folder: `${folder}/${subfolder}`,
|
|
@@ -428,13 +442,21 @@ class Series {
|
|
|
428
442
|
test_module.expected_failures || [],
|
|
429
443
|
);
|
|
430
444
|
|
|
431
|
-
//
|
|
445
|
+
// Set services if given.
|
|
446
|
+
if (test_module.servicer) {
|
|
447
|
+
if (this.#servicerType) {
|
|
448
|
+
throw new Error(`No nested servicers are supported`);
|
|
449
|
+
}
|
|
450
|
+
this.#servicerType = test_module.servicer;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Initialize.
|
|
432
454
|
if (test_module.services || test_module.init) {
|
|
433
455
|
let init = async () => {
|
|
434
456
|
// Start services if any.
|
|
435
457
|
let chain = Promise.resolve();
|
|
436
458
|
for (let service of test_module.services || []) {
|
|
437
|
-
chain = chain.then(() =>
|
|
459
|
+
chain = chain.then(() => this.getServicer().start(service));
|
|
438
460
|
}
|
|
439
461
|
await chain;
|
|
440
462
|
|
|
@@ -460,7 +482,7 @@ class Series {
|
|
|
460
482
|
// A function to notify the servicer the test is about to start and then
|
|
461
483
|
// to invoke the test.
|
|
462
484
|
const test_wrap = () =>
|
|
463
|
-
Promise.resolve(
|
|
485
|
+
Promise.resolve(this.#servicer?.ontest(name)).then(test);
|
|
464
486
|
|
|
465
487
|
let failures_info = Series.failuresInfo({
|
|
466
488
|
failures: expected_failures,
|
|
@@ -494,8 +516,13 @@ class Series {
|
|
|
494
516
|
await Promise.all(
|
|
495
517
|
[...(test_module.services || [])]
|
|
496
518
|
.reverse()
|
|
497
|
-
.map(s =>
|
|
519
|
+
.map(s => this.getServicer().stop(s)),
|
|
498
520
|
);
|
|
521
|
+
|
|
522
|
+
// Clean up service-level servicer
|
|
523
|
+
await this.#servicer?.shutdown();
|
|
524
|
+
this.#servicer = null;
|
|
525
|
+
this.#servicerType = null;
|
|
499
526
|
};
|
|
500
527
|
tests.push({
|
|
501
528
|
name: `${virtual_folder}/uninit`,
|
|
@@ -1032,6 +1059,20 @@ class Series {
|
|
|
1032
1059
|
}
|
|
1033
1060
|
}
|
|
1034
1061
|
}
|
|
1062
|
+
|
|
1063
|
+
getServicer() {
|
|
1064
|
+
if (!this.#servicer) {
|
|
1065
|
+
this.#servicer = this.createServicer(this.#servicerType);
|
|
1066
|
+
}
|
|
1067
|
+
return this.#servicer;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
createServicer(servicerType) {
|
|
1071
|
+
return settings.getServicer(servicerType);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
#servicer;
|
|
1075
|
+
#servicerType;
|
|
1035
1076
|
}
|
|
1036
1077
|
|
|
1037
1078
|
export { Series };
|
package/core/settings.js
CHANGED
package/core/system.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { spawn } from './spawn.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Run a shell command and capture its output
|
|
5
|
+
* @param {string} cmd - Command to run
|
|
6
|
+
* @param {string[]} args - Command arguments
|
|
7
|
+
* @param {object} options - Spawn options
|
|
8
|
+
* @returns {Promise<{stdout: string, stderr: string, exitCode: number, stdoutLines: string[], stderrLines: string[]}>}
|
|
9
|
+
*/
|
|
10
|
+
export async function runCommand(cmd, args = [], options = {}) {
|
|
11
|
+
let stdout = [];
|
|
12
|
+
let stderr = [];
|
|
13
|
+
let exitCode = 0;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
exitCode = await spawn(cmd, args, options, buffer => {
|
|
17
|
+
for (let { str_data, is_stdout } of buffer) {
|
|
18
|
+
const lines = str_data.split('\n').filter(line => line);
|
|
19
|
+
if (is_stdout) {
|
|
20
|
+
stdout.push(...lines);
|
|
21
|
+
} else {
|
|
22
|
+
stderr.push(...lines);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
} catch (code) {
|
|
27
|
+
exitCode = code;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
stdout: stdout.join('\n'),
|
|
32
|
+
stderr: stderr.join('\n'),
|
|
33
|
+
exitCode,
|
|
34
|
+
stdoutLines: stdout,
|
|
35
|
+
stderrLines: stderr,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Run a bash script with proper error handling
|
|
41
|
+
* @param {string} script - Bash script content
|
|
42
|
+
* @param {string[]} args - Script arguments
|
|
43
|
+
* @param {object} options - Spawn options
|
|
44
|
+
* @returns {Promise<{stdout: string, stderr: string, exitCode: number, stdoutLines: string[], stderrLines: string[]}>}
|
|
45
|
+
*/
|
|
46
|
+
export async function runBashScript(script, args = [], options = {}) {
|
|
47
|
+
return runCommand('bash', ['-c', script, '--', ...args], options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Run a shell command and return only stdout (for simple cases)
|
|
52
|
+
* @param {string} cmd - Command to run
|
|
53
|
+
* @param {string[]} args - Command arguments
|
|
54
|
+
* @param {object} options - Spawn options
|
|
55
|
+
* @returns {Promise<string>} stdout content
|
|
56
|
+
*/
|
|
57
|
+
export async function execCommand(cmd, args = [], options = {}) {
|
|
58
|
+
const result = await runCommand(cmd, args, options);
|
|
59
|
+
if (result.exitCode !== 0) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Command failed with exit code ${result.exitCode}: ${result.stderr}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return result.stdout;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Export the raw spawn function for advanced use cases
|
|
68
|
+
export { spawn };
|
package/eslint.config.js
CHANGED
package/index.js
CHANGED
|
@@ -26,12 +26,19 @@ import { inspect } from './core/util.js';
|
|
|
26
26
|
import { AppDriver } from './webdriver/app_driver.js';
|
|
27
27
|
import { ControlDriver } from './webdriver/control_driver.js';
|
|
28
28
|
import { start_session, scope } from './webdriver/session.js';
|
|
29
|
+
import {
|
|
30
|
+
runCommand,
|
|
31
|
+
runBashScript,
|
|
32
|
+
execCommand,
|
|
33
|
+
spawn,
|
|
34
|
+
} from './core/system.js';
|
|
29
35
|
|
|
30
36
|
export {
|
|
31
37
|
AppDriver,
|
|
32
38
|
ControlDriver,
|
|
33
39
|
assert,
|
|
34
40
|
contains,
|
|
41
|
+
execCommand,
|
|
35
42
|
failed,
|
|
36
43
|
fail,
|
|
37
44
|
group,
|
|
@@ -42,7 +49,10 @@ export {
|
|
|
42
49
|
not_reached,
|
|
43
50
|
no_throws,
|
|
44
51
|
ok,
|
|
52
|
+
runBashScript,
|
|
53
|
+
runCommand,
|
|
45
54
|
scope,
|
|
55
|
+
spawn,
|
|
46
56
|
start_session,
|
|
47
57
|
success,
|
|
48
58
|
todo,
|
package/interfaces/servicer.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ok, contains } from '../../index.js';
|
|
2
|
+
import { runCommand, runBashScript, execCommand } from '../../core/system.js';
|
|
3
|
+
|
|
4
|
+
export async function test() {
|
|
5
|
+
// Test 1: runCommand with echo
|
|
6
|
+
{
|
|
7
|
+
const result = await runCommand('echo', ['hello', 'world']);
|
|
8
|
+
ok(result.exitCode === 0, 'echo command should succeed');
|
|
9
|
+
ok(result.stdout === 'hello world', 'stdout should contain echo output');
|
|
10
|
+
ok(result.stderr === '', 'stderr should be empty');
|
|
11
|
+
ok(result.stdoutLines.length === 1, 'should have one stdout line');
|
|
12
|
+
ok(result.stdoutLines[0] === 'hello world', 'stdout line should match');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Test 2: runBashScript with simple script
|
|
16
|
+
{
|
|
17
|
+
const script = 'echo "test script"; echo "line 2"';
|
|
18
|
+
const result = await runBashScript(script);
|
|
19
|
+
ok(result.exitCode === 0, 'bash script should succeed');
|
|
20
|
+
contains(result.stdout, 'test script', 'should contain first echo');
|
|
21
|
+
contains(result.stdout, 'line 2', 'should contain second echo');
|
|
22
|
+
ok(result.stdoutLines.length === 2, 'should have two stdout lines');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Test 3: execCommand for simple cases
|
|
26
|
+
{
|
|
27
|
+
const output = await execCommand('echo', ['simple test']);
|
|
28
|
+
ok(output === 'simple test', 'execCommand should return stdout directly');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Test 4: runCommand with failing command
|
|
32
|
+
{
|
|
33
|
+
const result = await runCommand('bash', ['-c', 'echo "error" >&2; exit 1']);
|
|
34
|
+
ok(result.exitCode === 1, 'failing command should return exit code 1');
|
|
35
|
+
ok(result.stdout === '', 'stdout should be empty');
|
|
36
|
+
ok(result.stderr === 'error', 'stderr should contain error message');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Test 5: execCommand should throw on failure
|
|
40
|
+
{
|
|
41
|
+
let threw = false;
|
|
42
|
+
try {
|
|
43
|
+
await execCommand('bash', ['-c', 'exit 1']);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
threw = true;
|
|
46
|
+
contains(error.message, 'exit code 1', 'error should mention exit code');
|
|
47
|
+
}
|
|
48
|
+
ok(threw, 'execCommand should throw on non-zero exit code');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Test 6: runBashScript with arguments
|
|
52
|
+
{
|
|
53
|
+
const script = 'echo "arg1: $1, arg2: $2"';
|
|
54
|
+
const result = await runBashScript(script, ['first', 'second']);
|
|
55
|
+
ok(result.exitCode === 0, 'bash script with args should succeed');
|
|
56
|
+
contains(result.stdout, 'arg1: first', 'should pass first argument');
|
|
57
|
+
contains(result.stdout, 'arg2: second', 'should pass second argument');
|
|
58
|
+
}
|
|
59
|
+
}
|
package/tests/base/t_throws.js
CHANGED
|
@@ -136,4 +136,71 @@ unexpected character: '2' at 6 pos, expected: '1' at '' line
|
|
|
136
136
|
],
|
|
137
137
|
`no_throws fail (async)`,
|
|
138
138
|
);
|
|
139
|
+
|
|
140
|
+
// throws: accept object descriptor (statusCode + JSON body)
|
|
141
|
+
await is_output(
|
|
142
|
+
() =>
|
|
143
|
+
throws(
|
|
144
|
+
() => {
|
|
145
|
+
const err = new Error(
|
|
146
|
+
'HTTP error code 422 for https://api.digitalocean.com/v2/droplets',
|
|
147
|
+
);
|
|
148
|
+
err.statusCode = 422;
|
|
149
|
+
err.responseBody = JSON.stringify({
|
|
150
|
+
id: 'unprocessable_entity',
|
|
151
|
+
message: 'You specified an invalid image for Droplet creation.',
|
|
152
|
+
});
|
|
153
|
+
throw err;
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
statusCode: 422,
|
|
157
|
+
responseBody: JSON.stringify({
|
|
158
|
+
id: 'unprocessable_entity',
|
|
159
|
+
message: 'You specified an invalid image for Droplet creation.',
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
`Throws error descriptor`,
|
|
163
|
+
),
|
|
164
|
+
[
|
|
165
|
+
`Ok: Throws error descriptor, got: {statusCode: 422, responseBody: '{"id":"unprocessable_entity","message":"You specified an invalid image for Droplet creation."}'}`,
|
|
166
|
+
],
|
|
167
|
+
[],
|
|
168
|
+
`throws accept object descriptor`,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// throws: accept RegExp for message matching
|
|
172
|
+
await is_output(
|
|
173
|
+
() =>
|
|
174
|
+
throws(
|
|
175
|
+
() => {
|
|
176
|
+
throw new Error(
|
|
177
|
+
'Unexpected 404 response code for https://example.com/test from droplet 1',
|
|
178
|
+
);
|
|
179
|
+
},
|
|
180
|
+
/404|Unexpected/,
|
|
181
|
+
`Throws with RegExp`,
|
|
182
|
+
),
|
|
183
|
+
[
|
|
184
|
+
`Ok: Throws with RegExp 'Unexpected 404 response code for https://example.com/test from droplet 1' matches /404|Unexpected/ regexp`,
|
|
185
|
+
],
|
|
186
|
+
[],
|
|
187
|
+
`throws accept RegExp pattern`,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// throws: fail RegExp that doesn't match
|
|
191
|
+
await is_output(
|
|
192
|
+
() =>
|
|
193
|
+
throws(
|
|
194
|
+
() => {
|
|
195
|
+
throw new Error('Some other error message');
|
|
196
|
+
},
|
|
197
|
+
/404|Unexpected/,
|
|
198
|
+
`RegExp should match`,
|
|
199
|
+
),
|
|
200
|
+
[],
|
|
201
|
+
[
|
|
202
|
+
`Failed: RegExp should match 'Some other error message' doesn't match /404|Unexpected/ regexp`,
|
|
203
|
+
],
|
|
204
|
+
`throws fail RegExp no match`,
|
|
205
|
+
);
|
|
139
206
|
}
|
package/tests/e2e/t_folder.js
CHANGED
package/tests/e2e/t_loader.js
CHANGED
|
@@ -11,12 +11,12 @@ export async function test() {
|
|
|
11
11
|
'\x1B[32mOk:\x1B[0m Mocked!, got: mocked',
|
|
12
12
|
'>sample/t_test.js completed in',
|
|
13
13
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
14
|
-
'Testsuite: shutdown',
|
|
15
14
|
'Elapsed:',
|
|
15
|
+
'Testsuite: shutdown',
|
|
16
16
|
'\x1B[102mSuccess!\x1B[0m Total: 1',
|
|
17
17
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
18
|
-
'Testsuite: shutdown',
|
|
19
18
|
'Elapsed:',
|
|
19
|
+
'Testsuite: shutdown',
|
|
20
20
|
],
|
|
21
21
|
'stdout',
|
|
22
22
|
);
|
|
@@ -16,12 +16,12 @@ export async function test() {
|
|
|
16
16
|
'\x1B[32mOk:\x1B[0m Mocked!, got: mocked',
|
|
17
17
|
'>sample/ui/t_test.js completed in',
|
|
18
18
|
'\x1B[38;5;243mCompleted\x1B[0m sample/ui',
|
|
19
|
-
'Testsuite: shutdown',
|
|
20
19
|
'Elapsed:',
|
|
20
|
+
'Testsuite: shutdown',
|
|
21
21
|
'\x1B[102mSuccess!\x1B[0m Total: 2',
|
|
22
22
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
23
|
-
'Testsuite: shutdown',
|
|
24
23
|
'Elapsed:',
|
|
24
|
+
'Testsuite: shutdown',
|
|
25
25
|
],
|
|
26
26
|
'stdout',
|
|
27
27
|
);
|
|
@@ -11,19 +11,19 @@ export async function test() {
|
|
|
11
11
|
'\x1B[32mOk:\x1B[0m Mocked!, got: mocked',
|
|
12
12
|
'>sample/base/t_btest.js completed in',
|
|
13
13
|
'\x1B[38;5;243mCompleted\x1B[0m sample/base',
|
|
14
|
-
'Testsuite: shutdown',
|
|
15
14
|
'Elapsed:',
|
|
15
|
+
'Testsuite: shutdown',
|
|
16
16
|
'\x1B[38;5;99mStarted\x1B[0m sample/core',
|
|
17
17
|
'!Running: sample/core/t_ctest.js, path: tests/core/t_ctest.js',
|
|
18
18
|
'\x1B[32mOk:\x1B[0m Mocked!, got: mocked',
|
|
19
19
|
'>sample/core/t_ctest.js completed in',
|
|
20
20
|
'\x1B[38;5;243mCompleted\x1B[0m sample/core',
|
|
21
|
-
'Testsuite: shutdown',
|
|
22
21
|
'Elapsed:',
|
|
22
|
+
'Testsuite: shutdown',
|
|
23
23
|
'\x1B[102mSuccess!\x1B[0m Total: 2',
|
|
24
24
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
25
|
-
'Testsuite: shutdown',
|
|
26
25
|
'Elapsed:',
|
|
26
|
+
'Testsuite: shutdown',
|
|
27
27
|
],
|
|
28
28
|
'stdout',
|
|
29
29
|
);
|
|
@@ -13,19 +13,19 @@ export async function test() {
|
|
|
13
13
|
'\x1B[32mOk:\x1B[0m Mocked!, got: mocked',
|
|
14
14
|
'>sample/base/t_btest.js completed in',
|
|
15
15
|
'\x1B[38;5;243mCompleted\x1B[0m sample/base',
|
|
16
|
-
'Testsuite: shutdown',
|
|
17
16
|
'Elapsed:',
|
|
17
|
+
'Testsuite: shutdown',
|
|
18
18
|
'\x1B[38;5;99mStarted\x1B[0m sample/core',
|
|
19
19
|
'!Running: sample/core/t_ctest.js, path: tests/core/t_ctest.js',
|
|
20
20
|
'\x1B[32mOk:\x1B[0m Mocked!, got: mocked',
|
|
21
21
|
'>sample/core/t_ctest.js completed in',
|
|
22
22
|
'\x1B[38;5;243mCompleted\x1B[0m sample/core',
|
|
23
|
-
'Testsuite: shutdown',
|
|
24
23
|
'Elapsed:',
|
|
24
|
+
'Testsuite: shutdown',
|
|
25
25
|
'\x1B[102mSuccess!\x1B[0m Total: 2',
|
|
26
26
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
27
|
-
'Testsuite: shutdown',
|
|
28
27
|
'Elapsed:',
|
|
28
|
+
'Testsuite: shutdown',
|
|
29
29
|
],
|
|
30
30
|
'stdout',
|
|
31
31
|
);
|
package/tests/e2e/t_single.js
CHANGED
|
@@ -13,12 +13,12 @@ export async function test() {
|
|
|
13
13
|
'\x1B[32mOk:\x1B[0m Webdriver Works!',
|
|
14
14
|
'>sample/firefox/t_test.js completed in',
|
|
15
15
|
'\x1B[38;5;243mCompleted\x1B[0m sample/firefox',
|
|
16
|
-
'Testsuite: shutdown',
|
|
17
16
|
'Elapsed:',
|
|
17
|
+
'Testsuite: shutdown',
|
|
18
18
|
'\x1B[102mSuccess!\x1B[0m Total: 1',
|
|
19
19
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
20
|
-
'Testsuite: shutdown',
|
|
21
20
|
'Elapsed:',
|
|
21
|
+
'Testsuite: shutdown',
|
|
22
22
|
],
|
|
23
23
|
'stdout',
|
|
24
24
|
);
|
|
@@ -13,8 +13,8 @@ export async function test() {
|
|
|
13
13
|
'\x1B[32mOk:\x1B[0m Webdriver Works!',
|
|
14
14
|
'>sample/firefox/t_test.js completed in',
|
|
15
15
|
'\x1B[38;5;243mCompleted\x1B[0m sample/firefox',
|
|
16
|
-
'Testsuite: shutdown',
|
|
17
16
|
'Elapsed:',
|
|
17
|
+
'Testsuite: shutdown',
|
|
18
18
|
'\x1B[38;5;99mStarted\x1B[0m sample/chrome',
|
|
19
19
|
'!Running: sample/chrome/t_test.js, path: tests/t_test.js',
|
|
20
20
|
'\x1B[32mOk:\x1B[0m Webdriver Works!',
|
|
@@ -22,8 +22,8 @@ export async function test() {
|
|
|
22
22
|
'\x1B[38;5;243mCompleted\x1B[0m sample/chrome',
|
|
23
23
|
'\x1B[102mSuccess!\x1B[0m Total: 2',
|
|
24
24
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
25
|
-
'Testsuite: shutdown',
|
|
26
25
|
'Elapsed:',
|
|
26
|
+
'Testsuite: shutdown',
|
|
27
27
|
],
|
|
28
28
|
'stdout',
|
|
29
29
|
);
|
|
@@ -14,8 +14,8 @@ export async function test() {
|
|
|
14
14
|
'\x1B[32mOk:\x1B[0m Webdriver Works!',
|
|
15
15
|
'>sample/firefox/t_test.js completed in',
|
|
16
16
|
'\x1B[38;5;243mCompleted\x1B[0m sample/firefox',
|
|
17
|
-
'Testsuite: shutdown',
|
|
18
17
|
'Elapsed:',
|
|
18
|
+
'Testsuite: shutdown',
|
|
19
19
|
'\x1B[38;5;99mStarted\x1B[0m sample/chrome',
|
|
20
20
|
'!Running: sample/chrome/t_test.js, path: tests/t_test.js',
|
|
21
21
|
'\x1B[32mOk:\x1B[0m Webdriver Works!',
|
|
@@ -23,8 +23,8 @@ export async function test() {
|
|
|
23
23
|
'\x1B[38;5;243mCompleted\x1B[0m sample/chrome',
|
|
24
24
|
'\x1B[102mSuccess!\x1B[0m Total: 2',
|
|
25
25
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
26
|
-
'Testsuite: shutdown',
|
|
27
26
|
'Elapsed:',
|
|
27
|
+
'Testsuite: shutdown',
|
|
28
28
|
],
|
|
29
29
|
'stdout',
|
|
30
30
|
);
|
|
@@ -19,13 +19,13 @@ export async function test() {
|
|
|
19
19
|
'\x1B[32mOk:\x1B[0m Webdriver Works!',
|
|
20
20
|
'>sample/ui/firefox/t_test.js completed in',
|
|
21
21
|
'\x1B[38;5;243mCompleted\x1B[0m sample/ui/firefox',
|
|
22
|
-
'Testsuite: shutdown',
|
|
23
22
|
'Elapsed:',
|
|
23
|
+
'Testsuite: shutdown',
|
|
24
24
|
'\x1B[38;5;243mCompleted\x1B[0m sample/ui',
|
|
25
25
|
'\x1B[102mSuccess!\x1B[0m Total: 2',
|
|
26
26
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
27
|
-
'Testsuite: shutdown',
|
|
28
27
|
'Elapsed:',
|
|
28
|
+
'Testsuite: shutdown',
|
|
29
29
|
],
|
|
30
30
|
'stdout',
|
|
31
31
|
);
|
|
@@ -19,8 +19,8 @@ export async function test() {
|
|
|
19
19
|
'\x1B[32mOk:\x1B[0m Webdriver Works!',
|
|
20
20
|
'>sample/ui/firefox/t_test.js completed in',
|
|
21
21
|
'\x1B[38;5;243mCompleted\x1B[0m sample/ui/firefox',
|
|
22
|
-
'Testsuite: shutdown',
|
|
23
22
|
'Elapsed:',
|
|
23
|
+
'Testsuite: shutdown',
|
|
24
24
|
'\x1B[38;5;99mStarted\x1B[0m sample/ui/chrome',
|
|
25
25
|
'!Running: sample/ui/chrome/t_test.js, path: tests/ui/t_test.js',
|
|
26
26
|
'\x1B[32mOk:\x1B[0m Webdriver Works!',
|
|
@@ -29,8 +29,8 @@ export async function test() {
|
|
|
29
29
|
'\x1B[38;5;243mCompleted\x1B[0m sample/ui',
|
|
30
30
|
'\x1B[102mSuccess!\x1B[0m Total: 3',
|
|
31
31
|
'\x1B[38;5;243mCompleted\x1B[0m sample/',
|
|
32
|
-
'Testsuite: shutdown',
|
|
33
32
|
'Elapsed:',
|
|
33
|
+
'Testsuite: shutdown',
|
|
34
34
|
],
|
|
35
35
|
'stdout',
|
|
36
36
|
);
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { is, MockSeries } from '../test.js';
|
|
2
|
+
|
|
3
|
+
export async function test() {
|
|
4
|
+
// Test that buildSubtests only processes subfolders that match patterns
|
|
5
|
+
// This test tracks which directories are actually accessed to verify the optimization
|
|
6
|
+
|
|
7
|
+
const accessedDirs = [];
|
|
8
|
+
|
|
9
|
+
class TrackingMockSeries extends MockSeries {
|
|
10
|
+
loadTestMeta(folder) {
|
|
11
|
+
accessedDirs.push(folder);
|
|
12
|
+
return super.loadTestMeta(folder);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getTestFileList(folder) {
|
|
16
|
+
accessedDirs.push(folder);
|
|
17
|
+
return super.getTestFileList(folder);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ts = {
|
|
22
|
+
'tests': {
|
|
23
|
+
meta: {
|
|
24
|
+
folders: ['unit', 'integration', 'e2e'],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
'tests/unit': {
|
|
28
|
+
meta: {
|
|
29
|
+
folders: ['base', 'core'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
'tests/unit/base': {
|
|
33
|
+
files: ['t_test1.js'],
|
|
34
|
+
},
|
|
35
|
+
'tests/unit/base/t_test1.js': {
|
|
36
|
+
test() {},
|
|
37
|
+
},
|
|
38
|
+
'tests/unit/core': {
|
|
39
|
+
files: ['t_test2.js'],
|
|
40
|
+
},
|
|
41
|
+
'tests/unit/core/t_test2.js': {
|
|
42
|
+
test() {},
|
|
43
|
+
},
|
|
44
|
+
'tests/integration': {
|
|
45
|
+
files: ['t_integration.js'],
|
|
46
|
+
},
|
|
47
|
+
'tests/integration/t_integration.js': {
|
|
48
|
+
test() {},
|
|
49
|
+
},
|
|
50
|
+
'tests/e2e': {
|
|
51
|
+
files: ['t_e2e.js'],
|
|
52
|
+
},
|
|
53
|
+
'tests/e2e/t_e2e.js': {
|
|
54
|
+
test() {},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const series = new TrackingMockSeries([], { ts });
|
|
59
|
+
|
|
60
|
+
// Test 1: When targeting a specific test, only relevant folders should be processed
|
|
61
|
+
accessedDirs.length = 0; // Clear the array
|
|
62
|
+
|
|
63
|
+
const tests = await series.build({
|
|
64
|
+
patterns: [
|
|
65
|
+
{
|
|
66
|
+
path: 'tests/unit/base/t_test1.js',
|
|
67
|
+
webdriver: '',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
folder: 'tests',
|
|
71
|
+
virtual_folder: 'mac',
|
|
72
|
+
});
|
|
73
|
+
series.shutdown();
|
|
74
|
+
|
|
75
|
+
// Should only build the unit folder and its relevant subfolders
|
|
76
|
+
// Should NOT build integration or e2e folders
|
|
77
|
+
const testNames = getAllTestNames(tests);
|
|
78
|
+
|
|
79
|
+
// Should include the targeted test and its path
|
|
80
|
+
is(
|
|
81
|
+
testNames.includes('mac/unit/base/t_test1.js'),
|
|
82
|
+
true,
|
|
83
|
+
'Should include the targeted test',
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Should NOT include tests from unrelated folders
|
|
87
|
+
is(
|
|
88
|
+
testNames.includes('mac/integration/t_integration.js'),
|
|
89
|
+
false,
|
|
90
|
+
'Should not include integration tests when targeting unit test',
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
is(
|
|
94
|
+
testNames.includes('mac/e2e/t_e2e.js'),
|
|
95
|
+
false,
|
|
96
|
+
'Should not include e2e tests when targeting unit test',
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Check directory access optimization - this will fail with commented optimization
|
|
100
|
+
is(
|
|
101
|
+
accessedDirs.includes('tests/integration'),
|
|
102
|
+
false,
|
|
103
|
+
'Should not access integration directory when targeting unit test',
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
is(
|
|
107
|
+
accessedDirs.includes('tests/e2e'),
|
|
108
|
+
false,
|
|
109
|
+
'Should not access e2e directory when targeting unit test',
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Test 2: When no patterns specified, all folders should be processed
|
|
113
|
+
accessedDirs.length = 0; // Clear the array
|
|
114
|
+
|
|
115
|
+
const allTests = await series.build({
|
|
116
|
+
patterns: [],
|
|
117
|
+
folder: 'tests',
|
|
118
|
+
virtual_folder: 'mac',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const allTestNames = getAllTestNames(allTests);
|
|
122
|
+
|
|
123
|
+
// Should include all tests when no patterns specified
|
|
124
|
+
is(
|
|
125
|
+
allTestNames.includes('mac/unit/base/t_test1.js'),
|
|
126
|
+
true,
|
|
127
|
+
'Should include unit test when no patterns',
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
is(
|
|
131
|
+
allTestNames.includes('mac/integration/t_integration.js'),
|
|
132
|
+
true,
|
|
133
|
+
'Should include integration test when no patterns',
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
is(
|
|
137
|
+
allTestNames.includes('mac/e2e/t_e2e.js'),
|
|
138
|
+
true,
|
|
139
|
+
'Should include e2e test when no patterns',
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// When no patterns, all directories should be accessed
|
|
143
|
+
is(
|
|
144
|
+
accessedDirs.includes('tests/integration'),
|
|
145
|
+
true,
|
|
146
|
+
'Should access integration directory when no patterns specified',
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
is(
|
|
150
|
+
accessedDirs.includes('tests/e2e'),
|
|
151
|
+
true,
|
|
152
|
+
'Should access e2e directory when no patterns specified',
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getAllTestNames(tests) {
|
|
157
|
+
const names = [];
|
|
158
|
+
|
|
159
|
+
function collectNames(testList) {
|
|
160
|
+
for (const test of testList) {
|
|
161
|
+
if (test.subtests) {
|
|
162
|
+
collectNames(test.subtests);
|
|
163
|
+
} else if (
|
|
164
|
+
test.name &&
|
|
165
|
+
!test.name.includes('/init') &&
|
|
166
|
+
!test.name.includes('/uninit')
|
|
167
|
+
) {
|
|
168
|
+
names.push(test.name);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
collectNames(tests);
|
|
174
|
+
return names;
|
|
175
|
+
}
|
package/tests/series/meta.js
CHANGED
|
@@ -26,6 +26,7 @@ export async function test() {
|
|
|
26
26
|
'>t_testo.js has 1 warnings(s)',
|
|
27
27
|
'>t_testo.js completed in',
|
|
28
28
|
'\x1B[38;5;243mCompleted\x1B[0m tests/',
|
|
29
|
+
'Testsuite: shutdown',
|
|
29
30
|
];
|
|
30
31
|
let expected_stderr = [];
|
|
31
32
|
|
|
@@ -77,6 +78,7 @@ export async function test() {
|
|
|
77
78
|
'>t_testo_2.js has 3 warnings(s)',
|
|
78
79
|
'>t_testo_2.js completed in',
|
|
79
80
|
'\x1B[38;5;243mCompleted\x1B[0m tests/',
|
|
81
|
+
'Testsuite: shutdown',
|
|
80
82
|
];
|
|
81
83
|
expected_stderr = [];
|
|
82
84
|
|
|
@@ -33,6 +33,7 @@ export async function test() {
|
|
|
33
33
|
'!Running: t_testo.js, path: t_testo.js',
|
|
34
34
|
'>t_testo.js completed in',
|
|
35
35
|
'\x1B[38;5;243mCompleted\x1B[0m tests/',
|
|
36
|
+
'Testsuite: shutdown',
|
|
36
37
|
],
|
|
37
38
|
[
|
|
38
39
|
'\x1B[31mFailed:\x1B[0m Server busy',
|
|
@@ -79,6 +80,7 @@ export async function test() {
|
|
|
79
80
|
'>t_testo.js has 1 warnings(s)',
|
|
80
81
|
'>t_testo.js completed in',
|
|
81
82
|
'\x1B[38;5;243mCompleted\x1B[0m tests/',
|
|
83
|
+
'Testsuite: shutdown',
|
|
82
84
|
],
|
|
83
85
|
[],
|
|
84
86
|
'no perma but intermittent',
|
|
@@ -18,6 +18,7 @@ export async function test() {
|
|
|
18
18
|
'\x1B[32mOk:\x1B[0m Successio!',
|
|
19
19
|
'>t_testo.js completed in',
|
|
20
20
|
'\x1B[38;5;243mCompleted\x1B[0m tests/',
|
|
21
|
+
'Testsuite: shutdown',
|
|
21
22
|
],
|
|
22
23
|
[],
|
|
23
24
|
'success',
|
|
@@ -53,6 +54,7 @@ export async function test() {
|
|
|
53
54
|
'\x1B[32mOk:\x1B[0m Successio!',
|
|
54
55
|
'>t_testo.js completed in',
|
|
55
56
|
'\x1B[38;5;243mCompleted\x1B[0m tests/',
|
|
57
|
+
'Testsuite: shutdown',
|
|
56
58
|
],
|
|
57
59
|
[],
|
|
58
60
|
'success #2',
|
|
@@ -31,9 +31,9 @@ export async function test() {
|
|
|
31
31
|
'\x1B[41m\x1B[37mFailed!\x1B[0m Passed: 0. Failed: 1',
|
|
32
32
|
'\x1B[38;5;243mCompleted\x1B[0m mac/',
|
|
33
33
|
'Logs are written to',
|
|
34
|
-
'Testsuite: shutdown',
|
|
35
34
|
'Elapsed:',
|
|
36
35
|
'Logs are written to',
|
|
36
|
+
'Testsuite: shutdown',
|
|
37
37
|
];
|
|
38
38
|
|
|
39
39
|
const expected_stderr = [
|
|
@@ -55,9 +55,9 @@ export async function test() {
|
|
|
55
55
|
'\x1B[102mSuccess!\x1B[0m Total: 2',
|
|
56
56
|
'\x1B[38;5;243mCompleted\x1B[0m mac/',
|
|
57
57
|
'Logs are written to',
|
|
58
|
-
'Testsuite: shutdown',
|
|
59
58
|
'Elapsed:',
|
|
60
59
|
'Logs are written to',
|
|
60
|
+
'Testsuite: shutdown',
|
|
61
61
|
],
|
|
62
62
|
[],
|
|
63
63
|
'nested',
|
|
@@ -66,9 +66,9 @@ export async function test() {
|
|
|
66
66
|
'\x1B[41m\x1B[37mFailed!\x1B[0m Passed: 0. Failed: 1',
|
|
67
67
|
'\x1B[38;5;243mCompleted\x1B[0m mac/',
|
|
68
68
|
'Logs are written to',
|
|
69
|
-
'Testsuite: shutdown',
|
|
70
69
|
'Elapsed:',
|
|
71
70
|
'Logs are written to',
|
|
71
|
+
'Testsuite: shutdown',
|
|
72
72
|
];
|
|
73
73
|
|
|
74
74
|
const expected_stderr = [
|
|
@@ -84,9 +84,9 @@ export async function test() {
|
|
|
84
84
|
'\x1B[102mSuccess!\x1B[0m Total: 1',
|
|
85
85
|
'\x1B[38;5;243mCompleted\x1B[0m mac/',
|
|
86
86
|
'Logs are written to',
|
|
87
|
-
'Testsuite: shutdown',
|
|
88
87
|
'Elapsed:',
|
|
89
88
|
'Logs are written to',
|
|
89
|
+
'Testsuite: shutdown',
|
|
90
90
|
];
|
|
91
91
|
|
|
92
92
|
const expected_stderr = [
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { is_test_output, success } from '../../base/test.js';
|
|
2
|
+
import { log } from '../../../logging/logging.js';
|
|
3
|
+
import { MockSeries } from '../mock_series.js';
|
|
4
|
+
|
|
5
|
+
// Mock servicer that logs to console for output validation
|
|
6
|
+
class MockServicer {
|
|
7
|
+
constructor(type) {
|
|
8
|
+
this.type = type;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async start(service) {
|
|
12
|
+
log(`MockServicer:${this.type} starting ${service}`);
|
|
13
|
+
return { started: service, type: this.type };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async stop(service) {
|
|
17
|
+
log(`MockServicer:${this.type} stopping ${service}`);
|
|
18
|
+
return { stopped: service, type: this.type };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async shutdown() {
|
|
22
|
+
log(`MockServicer:${this.type} shutdown`);
|
|
23
|
+
return { shutdown: true, type: this.type };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async ontest() {
|
|
27
|
+
// Optional: log test notifications
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Extended MockSeries that uses our logging servicer
|
|
33
|
+
class MockSeriesWithServicer extends MockSeries {
|
|
34
|
+
createServicer(type) {
|
|
35
|
+
return new MockServicer(type || 'default');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function test() {
|
|
40
|
+
const ts = {
|
|
41
|
+
'tests': {
|
|
42
|
+
meta: {
|
|
43
|
+
servicer: 'docker',
|
|
44
|
+
services: ['mysql', 'redis'],
|
|
45
|
+
},
|
|
46
|
+
files: ['t_example.js'],
|
|
47
|
+
},
|
|
48
|
+
'tests/t_example.js': {
|
|
49
|
+
test() {
|
|
50
|
+
success('Servicer test example works');
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
await is_test_output(
|
|
56
|
+
() => MockSeriesWithServicer.run([], { ts }),
|
|
57
|
+
[
|
|
58
|
+
'Settings: no temporary storage dir',
|
|
59
|
+
'Settings: logging into /tmp',
|
|
60
|
+
'Settings: chrome webdrivers',
|
|
61
|
+
'\x1B[38;5;99mStarted\x1B[0m mac/',
|
|
62
|
+
'!Running: mac/init, path: tests/meta.js',
|
|
63
|
+
'MockServicer:docker starting mysql',
|
|
64
|
+
'MockServicer:docker starting redis',
|
|
65
|
+
'>mac/init completed in',
|
|
66
|
+
'!Running: mac/t_example.js, path: tests/t_example.js',
|
|
67
|
+
'\x1B[32mOk:\x1B[0m Servicer test example works',
|
|
68
|
+
'>mac/t_example.js completed in',
|
|
69
|
+
'!Running: mac/uninit, path: tests/meta.js',
|
|
70
|
+
'MockServicer:docker stopping redis',
|
|
71
|
+
'MockServicer:docker stopping mysql',
|
|
72
|
+
'MockServicer:docker shutdown',
|
|
73
|
+
'>mac/uninit completed in',
|
|
74
|
+
'\x1B[102mSuccess!\x1B[0m Total: 1',
|
|
75
|
+
'\x1B[38;5;243mCompleted\x1B[0m mac/',
|
|
76
|
+
'Logs are written to',
|
|
77
|
+
'Elapsed:',
|
|
78
|
+
'Logs are written to',
|
|
79
|
+
'Testsuite: shutdown'
|
|
80
|
+
],
|
|
81
|
+
[],
|
|
82
|
+
'servicer lifecycle',
|
|
83
|
+
);
|
|
84
|
+
}
|