@wp-tester/phpunit 0.0.1 → 0.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.
- package/dist/cache-fetch.d.ts +41 -0
- package/dist/cache-fetch.d.ts.map +1 -0
- package/dist/cache-fetch.js +114 -0
- package/dist/cache-fetch.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runner.d.ts +2 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +116 -49
- package/dist/runner.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/wordpress-test-lib.d.ts +25 -0
- package/dist/wordpress-test-lib.d.ts.map +1 -0
- package/dist/wordpress-test-lib.js +280 -0
- package/dist/wordpress-test-lib.js.map +1 -0
- package/package.json +8 -5
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache expiration constants for common use cases
|
|
3
|
+
*/
|
|
4
|
+
export declare const CACHE_FOREVER = 0;
|
|
5
|
+
export declare const CACHE_DISABLED = -1;
|
|
6
|
+
export declare const CACHE_1_HOUR: number;
|
|
7
|
+
export declare const CACHE_1_DAY: number;
|
|
8
|
+
export declare const CACHE_1_WEEK: number;
|
|
9
|
+
export interface CacheFetchOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Base directory for caching (defaults to ~/.wp-tester/cache)
|
|
12
|
+
*/
|
|
13
|
+
baseCacheDir?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Subdirectory within the cache (e.g., 'test-lib', 'wp-cli')
|
|
16
|
+
*/
|
|
17
|
+
cacheKey: string;
|
|
18
|
+
/**
|
|
19
|
+
* URL to download from
|
|
20
|
+
*/
|
|
21
|
+
url: string;
|
|
22
|
+
/**
|
|
23
|
+
* Cache expiration time in milliseconds (defaults to 24 hours)
|
|
24
|
+
* - Positive number: cache expires after that many milliseconds
|
|
25
|
+
* - 0 (CACHE_FOREVER): cache never expires
|
|
26
|
+
* - -1 (CACHE_DISABLED): always re-download, no caching
|
|
27
|
+
*/
|
|
28
|
+
cacheExpiration?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Maximum number of retry attempts
|
|
31
|
+
*/
|
|
32
|
+
maxRetries?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Download a file and cache it locally, with retry logic
|
|
36
|
+
*
|
|
37
|
+
* @param options - Configuration options for cache fetch
|
|
38
|
+
* @returns Path to the cached file
|
|
39
|
+
*/
|
|
40
|
+
export declare function cacheFetch(options: CacheFetchOptions): Promise<string>;
|
|
41
|
+
//# sourceMappingURL=cache-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-fetch.d.ts","sourceRoot":"","sources":["../src/cache-fetch.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,YAAY,QAAiB,CAAC;AAC3C,eAAO,MAAM,WAAW,QAAsB,CAAC;AAC/C,eAAO,MAAM,YAAY,QAA0B,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D5E"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as https from 'node:https';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { createWriteStream } from 'node:fs';
|
|
7
|
+
/**
|
|
8
|
+
* Cache expiration constants for common use cases
|
|
9
|
+
*/
|
|
10
|
+
export const CACHE_FOREVER = 0;
|
|
11
|
+
export const CACHE_DISABLED = -1;
|
|
12
|
+
export const CACHE_1_HOUR = 60 * 60 * 1000;
|
|
13
|
+
export const CACHE_1_DAY = 24 * 60 * 60 * 1000;
|
|
14
|
+
export const CACHE_1_WEEK = 7 * 24 * 60 * 60 * 1000;
|
|
15
|
+
/**
|
|
16
|
+
* Download a file and cache it locally, with retry logic
|
|
17
|
+
*
|
|
18
|
+
* @param options - Configuration options for cache fetch
|
|
19
|
+
* @returns Path to the cached file
|
|
20
|
+
*/
|
|
21
|
+
export async function cacheFetch(options) {
|
|
22
|
+
const { baseCacheDir = path.join(os.homedir(), '.wp-tester', 'cache'), cacheKey, url, cacheExpiration = CACHE_1_DAY, maxRetries = 1, } = options;
|
|
23
|
+
const cacheDir = path.join(baseCacheDir, cacheKey);
|
|
24
|
+
const cacheFilePath = path.join(cacheDir, 'download');
|
|
25
|
+
// Check if cached file exists and is still valid
|
|
26
|
+
if (fs.existsSync(cacheFilePath)) {
|
|
27
|
+
// If cacheExpiration is -1 (CACHE_DISABLED), always re-download
|
|
28
|
+
if (cacheExpiration === -1) {
|
|
29
|
+
// Delete existing cache
|
|
30
|
+
fs.unlinkSync(cacheFilePath);
|
|
31
|
+
}
|
|
32
|
+
// If cacheExpiration is 0 (CACHE_FOREVER), cache never expires
|
|
33
|
+
else if (cacheExpiration === 0) {
|
|
34
|
+
return cacheFilePath;
|
|
35
|
+
}
|
|
36
|
+
// For positive values, check if cache has expired
|
|
37
|
+
else {
|
|
38
|
+
const stats = fs.statSync(cacheFilePath);
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const fileAge = now - stats.mtimeMs;
|
|
41
|
+
if (fileAge < cacheExpiration) {
|
|
42
|
+
// Cache is still valid
|
|
43
|
+
return cacheFilePath;
|
|
44
|
+
}
|
|
45
|
+
// Cache has expired, delete it
|
|
46
|
+
fs.unlinkSync(cacheFilePath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Ensure cache directory exists
|
|
50
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
51
|
+
try {
|
|
52
|
+
await downloadFileWithRetry(url, cacheFilePath, maxRetries);
|
|
53
|
+
return cacheFilePath;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// Clean up partial download and cache directory
|
|
57
|
+
if (fs.existsSync(cacheFilePath)) {
|
|
58
|
+
fs.unlinkSync(cacheFilePath);
|
|
59
|
+
}
|
|
60
|
+
if (fs.existsSync(cacheDir)) {
|
|
61
|
+
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Failed to download from ${url}: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Download a file from URL with redirect support
|
|
68
|
+
*/
|
|
69
|
+
async function downloadFile(url, destPath) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
https.get(url, { headers: { 'User-Agent': 'wp-tester' } }, (response) => {
|
|
72
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
73
|
+
// Follow redirect
|
|
74
|
+
const redirectUrl = response.headers.location;
|
|
75
|
+
if (!redirectUrl) {
|
|
76
|
+
reject(new Error('Redirect without location header'));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
downloadFile(redirectUrl, destPath).then(resolve).catch(reject);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (response.statusCode !== 200) {
|
|
83
|
+
reject(new Error(`Download failed: ${response.statusCode}`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const fileStream = createWriteStream(destPath);
|
|
87
|
+
pipeline(response, fileStream)
|
|
88
|
+
.then(() => resolve())
|
|
89
|
+
.catch(reject);
|
|
90
|
+
}).on('error', reject);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Download file with retry logic and exponential backoff
|
|
95
|
+
*/
|
|
96
|
+
async function downloadFileWithRetry(url, destPath, maxRetries) {
|
|
97
|
+
let lastError = null;
|
|
98
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
99
|
+
try {
|
|
100
|
+
await downloadFile(url, destPath);
|
|
101
|
+
return; // Success
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
lastError = error;
|
|
105
|
+
if (attempt < maxRetries - 1) {
|
|
106
|
+
// Exponential backoff: 1s, 2s, 4s
|
|
107
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
108
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw lastError || new Error('Download failed after retries');
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=cache-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-fetch.js","sourceRoot":"","sources":["../src/cache-fetch.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/runner.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { WPTesterConfig } from "@wp-tester/config";
|
|
2
2
|
import type { Report } from "@wp-tester/results";
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function shouldRunPhpunitTests(config: WPTesterConfig): boolean;
|
|
4
4
|
/**
|
|
5
5
|
* Run PHPUnit tests in WordPress Playground environment
|
|
6
6
|
*
|
|
7
7
|
* @param config - Test configuration or path to config file
|
|
8
8
|
* @returns CTRF report with test results
|
|
9
9
|
*/
|
|
10
|
-
export declare function
|
|
10
|
+
export declare function runPhpunitTests(config: WPTesterConfig | string): Promise<Report>;
|
|
11
11
|
//# sourceMappingURL=runner.d.ts.map
|
package/dist/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA+C,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA+C,MAAM,mBAAmB,CAAC;AAOrG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAMjD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAErE;AAoOD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,GAAG,MAAM,GAC9B,OAAO,CAAC,MAAM,CAAC,CA6CjB"}
|
package/dist/runner.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { resolveConfig, parseBootstrapPath, hostToVfs } from "@wp-tester/config";
|
|
2
|
-
import { EMPTY_REPORT, mergeReports } from "@wp-tester/results";
|
|
1
|
+
import { resolveConfig, parseBootstrapPath, hostToVfs, resolveAbsolute, } from "@wp-tester/config";
|
|
2
|
+
import { EMPTY_REPORT, mergeReports, StreamingReporter, TeamCityParser } from "@wp-tester/results";
|
|
3
3
|
import { startPlayground } from "@wp-tester/runtime";
|
|
4
|
-
import {
|
|
4
|
+
import { mountWordPressTestLibrary } from "./wordpress-test-lib.js";
|
|
5
5
|
import { access } from "fs/promises";
|
|
6
|
-
|
|
7
|
-
export function shouldRunPhpUnitTests(config) {
|
|
6
|
+
export function shouldRunPhpunitTests(config) {
|
|
8
7
|
return config.tests?.phpunit !== undefined;
|
|
9
8
|
}
|
|
10
9
|
/**
|
|
@@ -15,17 +14,21 @@ export function shouldRunPhpUnitTests(config) {
|
|
|
15
14
|
* @param hostPhpunitConfigPath - Absolute path to PHPUnit config on host
|
|
16
15
|
* @returns CTRF report with test results
|
|
17
16
|
*/
|
|
18
|
-
async function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
async function runPhpunitTestsForEnvironment(config, environment, hostPhpunitConfigPath) {
|
|
18
|
+
const testMode = config.tests.phpunit.testMode;
|
|
19
|
+
let phpUnitEnvironmentVariables = {};
|
|
20
|
+
let environmentWithMount = environment;
|
|
21
|
+
// Prepare environment with WordPress test library for unit tests only
|
|
22
|
+
// Integration tests use standard WordPress installation via wp-load.php
|
|
23
|
+
if (testMode === "unit") {
|
|
24
|
+
environmentWithMount = await mountWordPressTestLibrary(environment);
|
|
25
|
+
phpUnitEnvironmentVariables = {
|
|
26
|
+
WP_TESTS_DIR: "/tmp/wordpress-tests-lib",
|
|
27
|
+
};
|
|
28
28
|
}
|
|
29
|
+
// Start playground with all mounts and steps including test library initialization
|
|
30
|
+
const runtime = await startPlayground(environmentWithMount);
|
|
31
|
+
const playground = runtime.playground;
|
|
29
32
|
try {
|
|
30
33
|
// Determine the plugin or theme being tested
|
|
31
34
|
if (!config.projectType) {
|
|
@@ -38,6 +41,8 @@ async function runPhpUnitTestsForEnvironment(config, environment, hostPhpunitCon
|
|
|
38
41
|
}
|
|
39
42
|
// Map host PHPUnit config path to VFS
|
|
40
43
|
const vfsPhpunitConfigPath = hostToVfs(hostPhpunitConfigPath, config);
|
|
44
|
+
// Map host PHPUnit executable path to VFS
|
|
45
|
+
const vfsPhpunitPath = hostToVfs(config.tests.phpunit.phpunitPath, config);
|
|
41
46
|
// Only create WordPress bootstrap in integration mode
|
|
42
47
|
let bootstrapFilePath = null;
|
|
43
48
|
if (testMode === "integration") {
|
|
@@ -47,7 +52,7 @@ async function runPhpUnitTestsForEnvironment(config, environment, hostPhpunitCon
|
|
|
47
52
|
let userBootstrap = `${config.projectVFSPath}/tests/bootstrap.php`; // Default fallback
|
|
48
53
|
if (bootstrapPath) {
|
|
49
54
|
// Bootstrap path from parseBootstrapPath is relative, convert to absolute
|
|
50
|
-
const absoluteBootstrapPath =
|
|
55
|
+
const absoluteBootstrapPath = resolveAbsolute(bootstrapPath, config.projectHostPath);
|
|
51
56
|
userBootstrap = hostToVfs(absoluteBootstrapPath, config);
|
|
52
57
|
}
|
|
53
58
|
// Create a custom bootstrap file that loads WordPress, then user's bootstrap if it exists
|
|
@@ -65,50 +70,105 @@ async function runPhpUnitTestsForEnvironment(config, environment, hostPhpunitCon
|
|
|
65
70
|
}
|
|
66
71
|
`);
|
|
67
72
|
}
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
// Build PHP CLI arguments
|
|
73
|
+
// Build PHP CLI arguments with environment variables
|
|
74
|
+
// Use TeamCity format for streaming output
|
|
71
75
|
const cliArgs = [
|
|
72
76
|
"php",
|
|
73
|
-
|
|
77
|
+
// Set variables_order to EGPCS to ensure environment variables are accessible.
|
|
78
|
+
// This is required for WordPress test library to access WP_TESTS_DIR via getenv().
|
|
79
|
+
// Default PHP settings often exclude 'E' (Environment), which would break test setup.
|
|
80
|
+
"-d",
|
|
81
|
+
"variables_order=EGPCS",
|
|
82
|
+
vfsPhpunitPath,
|
|
74
83
|
"-c",
|
|
75
84
|
vfsPhpunitConfigPath,
|
|
76
|
-
"--
|
|
77
|
-
logFilePath
|
|
85
|
+
"--teamcity", // Use TeamCity format for streaming
|
|
78
86
|
];
|
|
79
87
|
// Override bootstrap file with our custom one (only in integration mode)
|
|
80
88
|
if (bootstrapFilePath !== null) {
|
|
81
89
|
cliArgs.push("--bootstrap", bootstrapFilePath);
|
|
82
90
|
}
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
write(chunk) {
|
|
88
|
-
process.stdout.write(chunk);
|
|
89
|
-
}
|
|
90
|
-
}));
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
// Still need to consume the stream even if not displaying
|
|
94
|
-
await result.stdout.cancel();
|
|
91
|
+
// Append additional PHPUnit arguments from config
|
|
92
|
+
const phpunitArgs = config.tests.phpunit.phpunitArgs;
|
|
93
|
+
if (phpunitArgs && phpunitArgs.length > 0) {
|
|
94
|
+
cliArgs.push(...phpunitArgs);
|
|
95
95
|
}
|
|
96
|
+
// Create streaming reporter
|
|
97
|
+
// Disable summary since the CLI will print a combined summary
|
|
98
|
+
const useStreaming = config.reporters?.includes("default") ?? true;
|
|
99
|
+
const reporter = new StreamingReporter({
|
|
100
|
+
enabled: useStreaming,
|
|
101
|
+
showSummary: false,
|
|
102
|
+
});
|
|
103
|
+
// Signal test run start to initialize timing
|
|
104
|
+
reporter.onEvent({
|
|
105
|
+
type: "run:start",
|
|
106
|
+
toolName: "wp-tester-phpunit",
|
|
107
|
+
});
|
|
108
|
+
reporter.onEvent({
|
|
109
|
+
type: "suite:start",
|
|
110
|
+
name: `PHPUnit ${testMode} tests - '${environment.name}'`,
|
|
111
|
+
});
|
|
112
|
+
// Create TeamCity parser connected to the reporter
|
|
113
|
+
const parser = TeamCityParser.withReporter(reporter);
|
|
114
|
+
// Run PHPUnit tests
|
|
115
|
+
const result = await playground.cli(cliArgs, {
|
|
116
|
+
env: phpUnitEnvironmentVariables,
|
|
117
|
+
});
|
|
118
|
+
// Process stdout and stderr with TeamCity parser for streaming results
|
|
119
|
+
// PHPUnit outputs TeamCity format to both stdout and stderr
|
|
120
|
+
const textDecoder = new TextDecoder();
|
|
121
|
+
const stderrDecoder = new TextDecoder();
|
|
122
|
+
await Promise.all([
|
|
123
|
+
result.stdout.pipeTo(new WritableStream({
|
|
124
|
+
write(chunk) {
|
|
125
|
+
const text = textDecoder.decode(chunk, { stream: true });
|
|
126
|
+
parser.write(text);
|
|
127
|
+
},
|
|
128
|
+
close() {
|
|
129
|
+
parser.flush();
|
|
130
|
+
},
|
|
131
|
+
})),
|
|
132
|
+
result.stderr.pipeTo(new WritableStream({
|
|
133
|
+
write(chunk) {
|
|
134
|
+
const text = stderrDecoder.decode(chunk, { stream: true });
|
|
135
|
+
// Try to parse as TeamCity format first
|
|
136
|
+
parser.write(text);
|
|
137
|
+
// If streaming is disabled, also write non-TeamCity lines to stderr
|
|
138
|
+
// This ensures error messages still appear
|
|
139
|
+
if (!useStreaming) {
|
|
140
|
+
const lines = text.split("\n");
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
if (!line.trim().startsWith("##teamcity[")) {
|
|
143
|
+
process.stderr.write(line + "\n");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
close() {
|
|
149
|
+
parser.flush();
|
|
150
|
+
},
|
|
151
|
+
})),
|
|
152
|
+
]);
|
|
153
|
+
// Close the outer suite that was started earlier
|
|
154
|
+
reporter.onEvent({
|
|
155
|
+
type: "suite:end",
|
|
156
|
+
name: `PHPUnit ${testMode} tests - '${environment.name}'`,
|
|
157
|
+
});
|
|
158
|
+
// Signal test run end to finalize timing
|
|
159
|
+
reporter.onEvent({
|
|
160
|
+
type: "run:end",
|
|
161
|
+
});
|
|
96
162
|
const exitCode = await result.exitCode;
|
|
97
163
|
if (exitCode !== 0 && exitCode !== 1 && exitCode !== 2) {
|
|
98
164
|
// Exit code 1 indicates test failures, exit code 2 indicates errors - both are acceptable
|
|
99
165
|
console.error(`PHPUnit exited with code ${exitCode}`);
|
|
100
166
|
return EMPTY_REPORT;
|
|
101
167
|
}
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
// Read the JUnit XML log file
|
|
108
|
-
const xmlString = await playground.readFileAsText(logFilePath);
|
|
109
|
-
// Parse the JUnit XML output to CTRF format
|
|
110
|
-
const environmentName = environment.name;
|
|
111
|
-
const report = await parseJUnitXml(xmlString, environmentName);
|
|
168
|
+
// Get the report from the streaming reporter
|
|
169
|
+
const report = reporter.getReport();
|
|
170
|
+
// Update tool name with environment
|
|
171
|
+
report.results.tool.name = `wp-tester-phpunit (${environment.name})`;
|
|
112
172
|
return report;
|
|
113
173
|
}
|
|
114
174
|
catch (error) {
|
|
@@ -116,8 +176,15 @@ async function runPhpUnitTestsForEnvironment(config, environment, hostPhpunitCon
|
|
|
116
176
|
return EMPTY_REPORT;
|
|
117
177
|
}
|
|
118
178
|
finally {
|
|
119
|
-
// Cleanup
|
|
120
|
-
|
|
179
|
+
// Cleanup - ensure server closes even on errors
|
|
180
|
+
try {
|
|
181
|
+
await new Promise((resolve) => {
|
|
182
|
+
runtime.server.close(() => resolve());
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error("Error closing server:", error);
|
|
187
|
+
}
|
|
121
188
|
}
|
|
122
189
|
}
|
|
123
190
|
/**
|
|
@@ -126,11 +193,11 @@ async function runPhpUnitTestsForEnvironment(config, environment, hostPhpunitCon
|
|
|
126
193
|
* @param config - Test configuration or path to config file
|
|
127
194
|
* @returns CTRF report with test results
|
|
128
195
|
*/
|
|
129
|
-
export async function
|
|
196
|
+
export async function runPhpunitTests(config) {
|
|
130
197
|
// Resolve config (loads from path if string, resolves paths)
|
|
131
198
|
const resolvedConfig = await resolveConfig(config);
|
|
132
199
|
// Check if PHPUnit tests are configured
|
|
133
|
-
if (!
|
|
200
|
+
if (!shouldRunPhpunitTests(resolvedConfig)) {
|
|
134
201
|
return Promise.resolve(EMPTY_REPORT);
|
|
135
202
|
}
|
|
136
203
|
// Get PHPUnit config path from resolved config (already absolute after resolveConfig)
|
|
@@ -146,7 +213,7 @@ export async function runPhpUnitTests(config) {
|
|
|
146
213
|
// Run tests for all environments
|
|
147
214
|
const reports = [];
|
|
148
215
|
for (const environment of resolvedConfig.environments) {
|
|
149
|
-
const report = await
|
|
216
|
+
const report = await runPhpunitTestsForEnvironment(resolvedConfig, environment, hostPhpunitConfigPath);
|
|
150
217
|
if (report.results.summary.tests > 0) {
|
|
151
218
|
reports.push(report);
|
|
152
219
|
}
|
package/dist/runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
|