mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
3 Commits
trash-erro
...
robo/add_w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3534815b0 | ||
|
|
8a394e16a4 | ||
|
|
e5a6036ce0 |
3
.github/actions/checkout/action.yml
vendored
3
.github/actions/checkout/action.yml
vendored
@@ -139,7 +139,10 @@ runs:
|
||||
run: |
|
||||
rm -rf src/android_webview
|
||||
rm -rf src/ios/chrome
|
||||
mv src/third_party/blink/web_tests/external/wpt ./wpt
|
||||
rm -rf src/third_party/blink/web_tests
|
||||
mkdir -p src/third_party/blink/web_tests/external
|
||||
mv ./wpt src/third_party/blink/web_tests/external/wpt
|
||||
rm -rf src/third_party/blink/perf_tests
|
||||
rm -rf src/chrome/test/data/xr/webvr_info
|
||||
rm -rf src/third_party/angle/third_party/VK-GL-CTS/src
|
||||
|
||||
@@ -161,3 +161,53 @@ jobs:
|
||||
do
|
||||
sleep 60
|
||||
done
|
||||
wpt-tests:
|
||||
name: Run WPT Tests
|
||||
runs-on: electron-arc-linux-amd64-4core
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
TARGET_ARCH: ${{ inputs.target-arch }}
|
||||
BUILD_TYPE: linux
|
||||
container: ${{ fromJSON(inputs.test-container) }}
|
||||
steps:
|
||||
- name: Checkout Electron
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
path: src/electron
|
||||
fetch-depth: 0
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
cd src/electron
|
||||
node script/yarn install --frozen-lockfile
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
|
||||
with:
|
||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16
|
||||
with:
|
||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
- name: Restore Generated Artifacts
|
||||
run: ./src/electron/script/actions/restore-artifacts.sh
|
||||
- name: Unzip Dist
|
||||
run: |
|
||||
cd src/out/Default
|
||||
unzip -:o dist.zip
|
||||
- name: Setup Linux for Headless Testing
|
||||
run: sh -e /etc/init.d/xvfb start
|
||||
- name: Run WPT Tests
|
||||
run: |
|
||||
cd src
|
||||
chown :builduser . && chmod g+w .
|
||||
chown -R :builduser ./electron && chmod -R g+w ./electron
|
||||
chmod 4755 ./out/Default/chrome-sandbox
|
||||
runuser -u builduser -- xvfb-run electron/script/actions/run-tests.sh electron/script/wpt-spec-runner.js
|
||||
- name: Wait for active SSH sessions
|
||||
if: always() && !cancelled()
|
||||
run: |
|
||||
while [ -f /var/.ssh-lock ]
|
||||
do
|
||||
sleep 60
|
||||
done
|
||||
|
||||
@@ -62,7 +62,8 @@ move_src_dirs_if_exist() {
|
||||
src/third_party/libc++ \
|
||||
src/third_party/libc++abi \
|
||||
src/out/Default/obj/buildtools/third_party \
|
||||
src/v8/tools/builtins-pgo
|
||||
src/v8/tools/builtins-pgo \
|
||||
src/third_party/blink/web_tests/external/wpt
|
||||
do
|
||||
if [ -d "$dir" ]; then
|
||||
mkdir -p src_artifacts/$(dirname $dir)
|
||||
|
||||
@@ -16,7 +16,8 @@ const HASH_VERSIONS = {
|
||||
const filesToHash = [
|
||||
path.resolve(__dirname, '../DEPS'),
|
||||
path.resolve(__dirname, '../yarn.lock'),
|
||||
path.resolve(__dirname, '../script/sysroots.json')
|
||||
path.resolve(__dirname, '../script/sysroots.json'),
|
||||
path.resolve(__dirname, '../.github/actions/checkout/action.yml')
|
||||
];
|
||||
|
||||
const addAllFiles = (dir) => {
|
||||
|
||||
29
script/wpt-spec-runner.js
Normal file
29
script/wpt-spec-runner.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const cp = require('node:child_process');
|
||||
const path = require('node:path');
|
||||
|
||||
const utils = require('./lib/utils');
|
||||
|
||||
const BASE = path.resolve(__dirname, '../..');
|
||||
const WPT_DIR = path.resolve(BASE, 'third_party', 'blink', 'web_tests', 'external', 'wpt');
|
||||
|
||||
if (!require.main) {
|
||||
throw new Error('Must call the wpt spec runner directly');
|
||||
}
|
||||
|
||||
async function main () {
|
||||
const testChild = cp.spawn(utils.getAbsoluteElectronExec(), [path.join(__dirname, 'wpt'), '--enable-logging=stderr'], {
|
||||
env: {
|
||||
...process.env,
|
||||
WPT_DIR
|
||||
},
|
||||
stdio: 'inherit'
|
||||
});
|
||||
testChild.on('exit', (testCode) => {
|
||||
process.exit(testCode);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('An unhandled error occurred in the wpt spec runner', err);
|
||||
process.exit(1);
|
||||
});
|
||||
5
script/wpt/package.json
Normal file
5
script/wpt/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "electron-test-wpt",
|
||||
"main": "start.mjs",
|
||||
"type": "module"
|
||||
}
|
||||
388
script/wpt/runner/runner.mjs
Normal file
388
script/wpt/runner/runner.mjs
Normal file
@@ -0,0 +1,388 @@
|
||||
import { utilityProcess } from 'electron';
|
||||
|
||||
import { EventEmitter, once } from 'node:events';
|
||||
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { isAbsolute, join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { colors, handlePipes, normalizeName, parseMeta, resolveStatusPath } from './util.mjs';
|
||||
|
||||
const basePath = fileURLToPath(join(import.meta.url, '../..'));
|
||||
const testPath = process.env.WPT_DIR;
|
||||
const statusPath = join(basePath, 'status');
|
||||
|
||||
// https://github.com/web-platform-tests/wpt/blob/b24eedd/resources/testharness.js#L3705
|
||||
function sanitizeUnpairedSurrogates (str) {
|
||||
return str.replace(
|
||||
/([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g,
|
||||
function (_, low, prefix, high) {
|
||||
let output = prefix || ''; // Prefix may be undefined
|
||||
const string = low || high; // Only one of these alternates can match
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
output += codeUnitStr(string[i]);
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
function codeUnitStr (char) {
|
||||
return 'U+' + char.charCodeAt(0).toString(16);
|
||||
}
|
||||
|
||||
export class WPTRunner extends EventEmitter {
|
||||
/** @type {string} */
|
||||
#folderName;
|
||||
|
||||
/** @type {string} */
|
||||
#folderPath;
|
||||
|
||||
/** @type {string[]} */
|
||||
#files = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
#initScripts = [];
|
||||
|
||||
/** @type {string} */
|
||||
#url;
|
||||
|
||||
/** @type {import('../../status/fetch.status.json')} */
|
||||
#status;
|
||||
|
||||
/** Tests that have expectedly failed mapped by file name */
|
||||
#statusOutput = {};
|
||||
|
||||
#uncaughtExceptions = [];
|
||||
|
||||
#stats = {
|
||||
completedTests: 0,
|
||||
failedTests: 0,
|
||||
passedTests: 0,
|
||||
expectedFailures: 0,
|
||||
failedFiles: 0,
|
||||
passedFiles: 0,
|
||||
skippedFiles: 0
|
||||
};
|
||||
|
||||
constructor (folder, url) {
|
||||
super();
|
||||
|
||||
this.#folderName = folder;
|
||||
this.#folderPath = join(testPath, folder);
|
||||
this.#files.push(
|
||||
...WPTRunner.walk(
|
||||
this.#folderPath,
|
||||
(file) => file.endsWith('.any.js')
|
||||
)
|
||||
);
|
||||
|
||||
this.#status = JSON.parse(readFileSync(join(statusPath, `${folder}.status.json`)));
|
||||
this.#url = url;
|
||||
|
||||
if (this.#files.length === 0) {
|
||||
queueMicrotask(() => {
|
||||
this.emit('completion');
|
||||
});
|
||||
}
|
||||
|
||||
this.once('completion', () => {
|
||||
for (const { error, test } of this.#uncaughtExceptions) {
|
||||
console.log(colors(`Uncaught exception in "${test}":`, 'red'));
|
||||
console.log(colors(`${error.stack}`, 'red'));
|
||||
console.log('='.repeat(96));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static walk (dir, fn) {
|
||||
const ini = new Set(readdirSync(dir));
|
||||
const files = new Set();
|
||||
|
||||
while (ini.size !== 0) {
|
||||
for (const d of ini) {
|
||||
const path = resolve(dir, d);
|
||||
ini.delete(d); // remove from set
|
||||
const stats = statSync(path);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
for (const f of readdirSync(path)) {
|
||||
ini.add(resolve(path, f));
|
||||
}
|
||||
} else if (stats.isFile() && fn(d)) {
|
||||
files.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...files].sort();
|
||||
}
|
||||
|
||||
async run () {
|
||||
const workerPath = fileURLToPath(join(import.meta.url, '../worker.mjs'));
|
||||
/** @type {Set<Worker>} */
|
||||
const activeWorkers = new Set();
|
||||
let finishedFiles = 1;
|
||||
let total = this.#files.length;
|
||||
|
||||
const files = this.#files.map((test) => {
|
||||
const code = test.includes('.sub.')
|
||||
? handlePipes(readFileSync(test, 'utf-8'), this.#url)
|
||||
: readFileSync(test, 'utf-8');
|
||||
const meta = this.resolveMeta(code, test);
|
||||
|
||||
if (meta.variant.length) {
|
||||
total += meta.variant.length - 1;
|
||||
}
|
||||
|
||||
return [test, code, meta];
|
||||
});
|
||||
|
||||
console.log('='.repeat(96));
|
||||
|
||||
for (const [test, code, meta] of files) {
|
||||
console.log(`Started ${test}`);
|
||||
|
||||
const status = resolveStatusPath(test, this.#status);
|
||||
|
||||
if (status.file.skip || status.topLevel.skip) {
|
||||
this.#stats.skippedFiles += 1;
|
||||
|
||||
console.log(colors(`[${finishedFiles}/${total}] SKIPPED - ${test}`, 'yellow'));
|
||||
console.log('='.repeat(96));
|
||||
|
||||
finishedFiles++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
for (const variant of meta.variant.length ? meta.variant : ['']) {
|
||||
const url = new URL(this.#url);
|
||||
if (variant) {
|
||||
url.search = variant;
|
||||
}
|
||||
const worker = new utilityProcess.fork(workerPath, [], {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
await once(worker, 'spawn');
|
||||
worker.postMessage({
|
||||
workerData: {
|
||||
// The test file.
|
||||
test: code,
|
||||
// Parsed META tag information
|
||||
meta,
|
||||
url: url.href,
|
||||
path: test
|
||||
}
|
||||
});
|
||||
|
||||
worker.stdout.pipe(process.stdout);
|
||||
worker.stderr.pipe(process.stderr);
|
||||
|
||||
const fileUrl = new URL(`/${this.#folderName}${test.slice(this.#folderPath.length)}`, 'http://wpt');
|
||||
fileUrl.pathname = fileUrl.pathname.replace(/\.js$/, '.html');
|
||||
fileUrl.search = variant;
|
||||
const result = {
|
||||
test: fileUrl.href.slice(fileUrl.origin.length),
|
||||
subtests: [],
|
||||
status: ''
|
||||
};
|
||||
|
||||
activeWorkers.add(worker);
|
||||
// These values come directly from the web-platform-tests
|
||||
const timeout = meta.timeout === 'long' ? 60_000 : 10_000;
|
||||
|
||||
worker.on('message', (message) => {
|
||||
if (message.type === 'result') {
|
||||
this.handleIndividualTestCompletion(message, status, test, meta, result);
|
||||
} else if (message.type === 'completion') {
|
||||
this.handleTestCompletion(worker, status, test);
|
||||
} else if (message.type === 'error') {
|
||||
this.#uncaughtExceptions.push({ error: message.error, test });
|
||||
this.#stats.failedTests += 1;
|
||||
this.#stats.passedTests -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
await once(worker, 'exit', {
|
||||
signal: AbortSignal.timeout(timeout)
|
||||
});
|
||||
|
||||
if (result.subtests.some((subtest) => subtest?.isExpectedFailure === false)) {
|
||||
this.#stats.failedFiles += 1;
|
||||
console.log(colors(`[${finishedFiles}/${total}] FAILED - ${test}`, 'red'));
|
||||
} else {
|
||||
this.#stats.passedFiles += 1;
|
||||
console.log(colors(`[${finishedFiles}/${total}] PASSED - ${test}`, 'green'));
|
||||
}
|
||||
|
||||
if (variant) console.log('Variant:', variant);
|
||||
console.log(`File took ${(performance.now() - start).toFixed(2)}ms`);
|
||||
console.log('='.repeat(96));
|
||||
} catch (_) {
|
||||
// If the worker is terminated by the timeout signal, the test is marked as failed
|
||||
this.#stats.failedFiles += 1;
|
||||
console.log(colors(`[${finishedFiles}/${total}] FAILED - ${test}`, 'red'));
|
||||
|
||||
if (variant) console.log('Variant:', variant);
|
||||
console.log(`File timed out after ${timeout}ms`);
|
||||
console.log('='.repeat(96));
|
||||
} finally {
|
||||
finishedFiles++;
|
||||
activeWorkers.delete(worker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.handleRunnerCompletion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a test has succeeded or failed.
|
||||
*/
|
||||
handleIndividualTestCompletion (message, status, path, meta, wptResult) {
|
||||
this.#stats.completedTests += 1;
|
||||
|
||||
const { file, topLevel } = status;
|
||||
const isFailure = message.result.status === 1;
|
||||
|
||||
const testResult = {
|
||||
status: isFailure ? 'FAIL' : 'PASS',
|
||||
name: sanitizeUnpairedSurrogates(message.result.name)
|
||||
};
|
||||
|
||||
if (isFailure) {
|
||||
let isExpectedFailure = false;
|
||||
this.#stats.failedTests += 1;
|
||||
|
||||
const name = normalizeName(message.result.name);
|
||||
const sanitizedMessage = sanitizeUnpairedSurrogates(message.result.message);
|
||||
|
||||
if (file.flaky?.includes(name)) {
|
||||
isExpectedFailure = true;
|
||||
this.#stats.expectedFailures += 1;
|
||||
wptResult?.subtests.push({ ...testResult, message: sanitizedMessage, isExpectedFailure });
|
||||
} else if (file.allowUnexpectedFailures || topLevel.allowUnexpectedFailures || file.fail?.includes(name)) {
|
||||
if (!file.allowUnexpectedFailures && !topLevel.allowUnexpectedFailures) {
|
||||
if (Array.isArray(file.fail)) {
|
||||
this.#statusOutput[path] ??= [];
|
||||
this.#statusOutput[path].push(name);
|
||||
}
|
||||
}
|
||||
|
||||
isExpectedFailure = true;
|
||||
this.#stats.expectedFailures += 1;
|
||||
wptResult?.subtests.push({ ...testResult, message: sanitizedMessage, isExpectedFailure });
|
||||
} else {
|
||||
wptResult?.subtests.push({ ...testResult, message: sanitizedMessage, isExpectedFailure });
|
||||
process.exitCode = 1;
|
||||
console.error(message.result);
|
||||
}
|
||||
if (!isExpectedFailure) {
|
||||
process._rawDebug(`Failed test: ${path}`);
|
||||
}
|
||||
} else {
|
||||
this.#stats.passedTests += 1;
|
||||
wptResult?.subtests.push(testResult);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after all the tests in a worker are completed.
|
||||
* @param {Worker} worker
|
||||
*/
|
||||
handleTestCompletion (worker, status, path) {
|
||||
worker.kill();
|
||||
|
||||
const { file } = status;
|
||||
const hasExpectedFailures = !!file.fail;
|
||||
const testHasFailures = !!this.#statusOutput?.[path];
|
||||
const failed = this.#statusOutput?.[path] ?? [];
|
||||
|
||||
if (hasExpectedFailures !== testHasFailures) {
|
||||
console.log({ expected: file.fail, failed });
|
||||
|
||||
if (failed.length === 0) {
|
||||
console.log(colors('Tests are marked as failure but did not fail, yay!', 'red'));
|
||||
} else if (!hasExpectedFailures) {
|
||||
console.log(colors('Test failed but there were no expected errors.', 'red'));
|
||||
}
|
||||
|
||||
process.exitCode = 1;
|
||||
} else if (hasExpectedFailures && testHasFailures) {
|
||||
const diff = [
|
||||
...file.fail.filter(x => !failed.includes(x)),
|
||||
...failed.filter(x => !file.fail.includes(x))
|
||||
];
|
||||
|
||||
if (diff.length) {
|
||||
console.log({ diff });
|
||||
console.log(colors('Expected failures did not match actual failures', 'red'));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after every test has completed.
|
||||
*/
|
||||
handleRunnerCompletion () {
|
||||
// tests that failed
|
||||
if (Object.keys(this.#statusOutput).length !== 0) {
|
||||
console.log(this.#statusOutput);
|
||||
}
|
||||
|
||||
this.emit('completion');
|
||||
|
||||
const { passedFiles, failedFiles, skippedFiles } = this.#stats;
|
||||
console.log(
|
||||
`File results for folder [${this.#folderName}]: ` +
|
||||
`completed: ${this.#files.length}, passed: ${passedFiles}, failed: ${failedFiles}, ` +
|
||||
`skipped: ${skippedFiles}`
|
||||
);
|
||||
|
||||
const { completedTests, failedTests, passedTests, expectedFailures } = this.#stats;
|
||||
console.log(
|
||||
`Test results for folder [${this.#folderName}]: ` +
|
||||
`completed: ${completedTests}, failed: ${failedTests}, passed: ${passedTests}, ` +
|
||||
`expected failures: ${expectedFailures}, ` +
|
||||
`unexpected failures: ${failedTests - expectedFailures}`
|
||||
);
|
||||
|
||||
process.exit(failedTests - expectedFailures ? 1 : process.exitCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses META tags and resolves any script file paths.
|
||||
* @param {string} code
|
||||
* @param {string} path The absolute path of the test
|
||||
*/
|
||||
resolveMeta (code, path) {
|
||||
const meta = parseMeta(code);
|
||||
const scripts = meta.scripts.map((filePath) => {
|
||||
let content = '';
|
||||
|
||||
if (filePath === '/resources/WebIDLParser.js') {
|
||||
// See https://github.com/web-platform-tests/wpt/pull/731
|
||||
return readFileSync(join(testPath, '/resources/webidl2/lib/webidl2.js'), 'utf-8');
|
||||
} else if (isAbsolute(filePath)) {
|
||||
content = readFileSync(join(testPath, filePath), 'utf-8');
|
||||
} else {
|
||||
content = readFileSync(resolve(path, '..', filePath), 'utf-8');
|
||||
}
|
||||
|
||||
// If the file has any built-in pipes.
|
||||
if (filePath.includes('.sub.')) {
|
||||
content = handlePipes(content, this.#url);
|
||||
}
|
||||
|
||||
return content;
|
||||
});
|
||||
|
||||
return {
|
||||
...meta,
|
||||
resourcePaths: meta.scripts,
|
||||
scripts
|
||||
};
|
||||
}
|
||||
}
|
||||
172
script/wpt/runner/util.mjs
Normal file
172
script/wpt/runner/util.mjs
Normal file
@@ -0,0 +1,172 @@
|
||||
import assert from 'node:assert';
|
||||
import { sep } from 'node:path';
|
||||
import { exit } from 'node:process';
|
||||
import tty from 'node:tty';
|
||||
import { inspect } from 'node:util';
|
||||
|
||||
/**
|
||||
* Parse the `Meta:` tags sometimes included in tests.
|
||||
* These can include resources to inject, how long it should
|
||||
* take to timeout, and which globals to expose.
|
||||
* @example
|
||||
* // META: timeout=long
|
||||
* // META: global=window,worker
|
||||
* // META: script=/common/utils.js
|
||||
* // META: script=/common/get-host-info.sub.js
|
||||
* // META: script=../request/request-error.js
|
||||
* @see https://nodejs.org/api/readline.html#readline_example_read_file_stream_line_by_line
|
||||
* @param {string} fileContents
|
||||
*/
|
||||
export function parseMeta (fileContents) {
|
||||
const lines = fileContents.split(/\r?\n/g);
|
||||
|
||||
const meta = {
|
||||
/** @type {string|null} */
|
||||
timeout: null,
|
||||
/** @type {string[]} */
|
||||
global: [],
|
||||
/** @type {string[]} */
|
||||
scripts: [],
|
||||
/** @type {string[]} */
|
||||
variant: []
|
||||
};
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.startsWith('// META: ')) {
|
||||
break;
|
||||
}
|
||||
|
||||
const groups = /^\/\/ META: (?<type>.*?)=(?<match>.*)$/.exec(line)?.groups;
|
||||
|
||||
if (!groups) {
|
||||
console.log(`Failed to parse META tag: ${line}`);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
switch (groups.type) {
|
||||
case 'variant':
|
||||
meta[groups.type].push(groups.match);
|
||||
break;
|
||||
case 'title':
|
||||
case 'timeout': {
|
||||
meta[groups.type] = groups.match;
|
||||
break;
|
||||
}
|
||||
case 'global': {
|
||||
// window,worker -> ['window', 'worker']
|
||||
meta.global.push(...groups.match.split(','));
|
||||
break;
|
||||
}
|
||||
case 'script': {
|
||||
// A relative or absolute file path to the resources
|
||||
// needed for the current test.
|
||||
meta.scripts.push(groups.match);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(`Unknown META tag: ${groups.type}`);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} sub
|
||||
*/
|
||||
function parseSubBlock (sub) {
|
||||
const subName = sub.includes('[') ? sub.slice(0, sub.indexOf('[')) : sub;
|
||||
const options = sub.matchAll(/\[(.*?)\]/gm);
|
||||
|
||||
return {
|
||||
sub: subName,
|
||||
options: [...options].map(match => match[1])
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://web-platform-tests.org/writing-tests/server-pipes.html?highlight=sub#built-in-pipes
|
||||
* @param {string} code
|
||||
* @param {string} url
|
||||
*/
|
||||
export function handlePipes (code, url) {
|
||||
const server = new URL(url);
|
||||
|
||||
// "Substitutions are marked in a file using a block delimited by
|
||||
// {{ and }}. Inside the block the following variables are available:"
|
||||
return code.replace(/{{(.*?)}}/gm, (_, match) => {
|
||||
const { sub } = parseSubBlock(match);
|
||||
|
||||
switch (sub) {
|
||||
// "The host name of the server excluding any subdomain part."
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'host':
|
||||
// "The domain name of a particular subdomain e.g.
|
||||
// {{domains[www]}} for the www subdomain."
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'domains':
|
||||
// "The domain name of a particular subdomain for a particular host.
|
||||
// The first key may be empty (designating the “default” host) or
|
||||
// the value alt; i.e., {{hosts[alt][]}} (designating the alternate
|
||||
// host)."
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'hosts': {
|
||||
return 'localhost';
|
||||
}
|
||||
// "The port number of servers, by protocol e.g. {{ports[http][0]}}
|
||||
// for the first (and, depending on setup, possibly only) http server"
|
||||
case 'ports': {
|
||||
return server.port;
|
||||
}
|
||||
default: {
|
||||
throw new TypeError(`Unknown substitute "${sub}".`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Some test names may contain characters that JSON cannot handle.
|
||||
* @param {string} name
|
||||
*/
|
||||
export function normalizeName (name) {
|
||||
return name.replace(/(\v)/g, (_, match) => {
|
||||
switch (inspect(match)) {
|
||||
case '\'\\x0B\'': return '\\x0B';
|
||||
default: return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function colors (str, color) {
|
||||
assert(Object.hasOwn(inspect.colors, color), `Missing color ${color}`);
|
||||
|
||||
if (!tty.WriteStream.prototype.hasColors()) {
|
||||
return str;
|
||||
}
|
||||
|
||||
const [start, end] = inspect.colors[color];
|
||||
|
||||
return `\u001b[${start}m${str}\u001b[${end}m`;
|
||||
}
|
||||
|
||||
/** @param {string} path */
|
||||
export function resolveStatusPath (path, status) {
|
||||
const paths = path
|
||||
.slice(process.cwd().length + sep.length)
|
||||
.split(sep)
|
||||
.slice(5); // [test, wpt, tests, fetch, b, c.js] -> [fetch, b, c.js]
|
||||
|
||||
// skip the first folder name
|
||||
for (let i = 1; i < paths.length - 1; i++) {
|
||||
status = status[paths[i]];
|
||||
|
||||
if (!status) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { fullPath: path, topLevel: status ?? {}, file: status?.[paths.at(-1)] ?? {} };
|
||||
}
|
||||
128
script/wpt/runner/worker.mjs
Normal file
128
script/wpt/runner/worker.mjs
Normal file
@@ -0,0 +1,128 @@
|
||||
import { net } from 'electron';
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { runInThisContext } from 'node:vm';
|
||||
|
||||
const basePath = process.env.WPT_DIR;
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.log('uncaughtException', err);
|
||||
process.parentPort.postMessage({
|
||||
type: 'error',
|
||||
error: {
|
||||
message: err.message,
|
||||
name: err.name,
|
||||
stack: err.stack
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const globalPropertyDescriptors = {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
};
|
||||
|
||||
Object.defineProperties(globalThis, {
|
||||
fetch: {
|
||||
...globalPropertyDescriptors,
|
||||
enumerable: true,
|
||||
value: net.fetch
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: remove once Float16Array is added. Otherwise a test throws with an uncaught exception.
|
||||
globalThis.Float16Array ??= class Float16Array {};
|
||||
|
||||
process.parentPort.on('message', (message) => {
|
||||
const { meta, test, url, path } = message.data.workerData;
|
||||
const urlPath = path.slice(basePath.length);
|
||||
|
||||
// self is required by testharness
|
||||
// GLOBAL is required by self
|
||||
runInThisContext(`
|
||||
globalThis.self = globalThis
|
||||
globalThis.GLOBAL = {
|
||||
isWorker () {
|
||||
return false
|
||||
},
|
||||
isShadowRealm () {
|
||||
return false
|
||||
},
|
||||
isWindow () {
|
||||
return false
|
||||
}
|
||||
}
|
||||
globalThis.window = globalThis
|
||||
globalThis.location = new URL('${urlPath.replace(/\\/g, '/')}', '${url}')
|
||||
globalThis.Window = Object.getPrototypeOf(globalThis).constructor
|
||||
`);
|
||||
|
||||
if (meta.title) {
|
||||
runInThisContext(`globalThis.META_TITLE = "${meta.title.replace(/"/g, '\\"')}"`);
|
||||
}
|
||||
|
||||
const harness = readFileSync(join(basePath, '/resources/testharness.js'), 'utf-8');
|
||||
runInThisContext(harness);
|
||||
|
||||
// add_*_callback comes from testharness
|
||||
// stolen from node's wpt test runner
|
||||
// eslint-disable-next-line no-undef
|
||||
add_result_callback((result) => {
|
||||
process.parentPort.postMessage({
|
||||
type: 'result',
|
||||
result: {
|
||||
status: result.status,
|
||||
name: result.name,
|
||||
message: result.message,
|
||||
stack: result.stack
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
add_completion_callback((_, status) => {
|
||||
process.parentPort.postMessage({
|
||||
type: 'completion',
|
||||
status
|
||||
});
|
||||
});
|
||||
|
||||
const globalOrigin = Symbol.for('undici.globalOrigin.1');
|
||||
function setGlobalOrigin (newOrigin) {
|
||||
if (newOrigin === undefined) {
|
||||
Object.defineProperty(globalThis, globalOrigin, {
|
||||
value: undefined,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedURL = new URL(newOrigin);
|
||||
|
||||
if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') {
|
||||
throw new TypeError(`Only http & https urls are allowed, received ${parsedURL.protocol}`);
|
||||
}
|
||||
|
||||
Object.defineProperty(globalThis, globalOrigin, {
|
||||
value: parsedURL,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
}
|
||||
|
||||
setGlobalOrigin(globalThis.location);
|
||||
|
||||
// Inject any files from the META tags
|
||||
for (const script of meta.scripts) {
|
||||
runInThisContext(script);
|
||||
}
|
||||
|
||||
// Finally, run the test.
|
||||
runInThisContext(test);
|
||||
});
|
||||
3
script/wpt/server/constants.mjs
Normal file
3
script/wpt/server/constants.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
export const symbols = {
|
||||
kContent: Symbol('content')
|
||||
};
|
||||
104
script/wpt/server/routes/redirect.mjs
Normal file
104
script/wpt/server/routes/redirect.mjs
Normal file
@@ -0,0 +1,104 @@
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
|
||||
const stash = new Map();
|
||||
|
||||
/**
|
||||
* @see https://github.com/web-platform-tests/wpt/blob/master/fetch/connection-pool/resources/network-partition-key.py
|
||||
* @param {Parameters<import('http').RequestListener>[0]} req
|
||||
* @param {Parameters<import('http').RequestListener>[1]} res
|
||||
* @param {URL} fullUrl
|
||||
*/
|
||||
export async function route (req, res, fullUrl) {
|
||||
const { searchParams } = fullUrl;
|
||||
|
||||
let stashedData = { count: 0, preflight: 0 };
|
||||
let status = 302;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
|
||||
if (Object.hasOwn(req.headers, 'origin')) {
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin ?? '');
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
} else {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
let token = null;
|
||||
if (searchParams.has('token')) {
|
||||
token = searchParams.get('token');
|
||||
const data = stash.get(token);
|
||||
stash.delete(token);
|
||||
if (data) {
|
||||
stashedData = data;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
if (searchParams.has('allow_headers')) {
|
||||
res.setHeader('Access-Control-Allow-Headers', searchParams.get('allow_headers'));
|
||||
}
|
||||
|
||||
stashedData.preflight = '1';
|
||||
|
||||
if (!searchParams.has('redirect_preflight')) {
|
||||
if (token) {
|
||||
stash.set(searchParams.get('token'), stashedData);
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.end('');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (searchParams.has('redirect_status')) {
|
||||
status = parseInt(searchParams.get('redirect_status'));
|
||||
}
|
||||
|
||||
stashedData.count += 1;
|
||||
|
||||
if (searchParams.has('location')) {
|
||||
let url = decodeURIComponent(searchParams.get('location'));
|
||||
|
||||
if (!searchParams.has('simple')) {
|
||||
const scheme = new URL(url, fullUrl).protocol;
|
||||
|
||||
if (scheme === 'http:' || scheme === 'https:') {
|
||||
url += url.includes('?') ? '&' : '?';
|
||||
|
||||
for (const [key, value] of searchParams) {
|
||||
url += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
||||
}
|
||||
|
||||
url += '&count=' + stashedData.count;
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader('location', url);
|
||||
}
|
||||
|
||||
if (searchParams.has('redirect_referrerpolicy')) {
|
||||
res.setHeader('Referrer-Policy', searchParams.get('redirect_referrerpolicy'));
|
||||
}
|
||||
|
||||
if (searchParams.has('delay')) {
|
||||
await setTimeout(parseFloat(searchParams.get('delay') ?? 0));
|
||||
}
|
||||
|
||||
if (token) {
|
||||
stash.set(searchParams.get('token'), stashedData);
|
||||
|
||||
if (searchParams.has('max_count')) {
|
||||
const maxCount = parseInt(searchParams.get('max_count'));
|
||||
|
||||
if (stashedData.count > maxCount) {
|
||||
res.end((stashedData.count - 1).toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.statusCode = status;
|
||||
res.end('');
|
||||
}
|
||||
350
script/wpt/server/server.mjs
Normal file
350
script/wpt/server/server.mjs
Normal file
@@ -0,0 +1,350 @@
|
||||
import { once } from 'node:events';
|
||||
import { createReadStream, readFileSync, existsSync } from 'node:fs';
|
||||
import { createServer } from 'node:http';
|
||||
import { join } from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { setTimeout as sleep } from 'node:timers/promises';
|
||||
|
||||
import { symbols } from './constants.mjs';
|
||||
import { route as redirectRoute } from './routes/redirect.mjs';
|
||||
|
||||
const tests = process.env.WPT_DIR;
|
||||
|
||||
// https://web-platform-tests.org/tools/wptserve/docs/stash.html
|
||||
class Stash extends Map {
|
||||
take (key) {
|
||||
if (this.has(key)) {
|
||||
const value = this.get(key);
|
||||
|
||||
this.delete(key);
|
||||
return value.value;
|
||||
}
|
||||
}
|
||||
|
||||
put (key, value, path) {
|
||||
this.set(key, { value, path });
|
||||
}
|
||||
}
|
||||
|
||||
const stash = new Stash();
|
||||
|
||||
const server = createServer(async (req, res) => {
|
||||
const fullUrl = new URL(req.url, `http://localhost:${server.address().port}`);
|
||||
|
||||
switch (fullUrl.pathname) {
|
||||
case '/fetch/content-encoding/resources/big.text.gz':
|
||||
case '/fetch/content-encoding/resources/foo.octetstream.gz':
|
||||
case '/fetch/content-encoding/resources/foo.text.gz':
|
||||
case '/fetch/api/resources/cors-top.txt':
|
||||
case '/fetch/api/resources/top.txt':
|
||||
case '/fetch/data-urls/resources/base64.json':
|
||||
case '/fetch/data-urls/resources/data-urls.json':
|
||||
case '/fetch/api/resources/empty.txt':
|
||||
case '/fetch/api/resources/data.json': {
|
||||
// If this specific resources requires custom headers
|
||||
const customHeadersPath = join(tests, fullUrl.pathname + '.headers');
|
||||
if (existsSync(customHeadersPath)) {
|
||||
const headers = readFileSync(customHeadersPath, 'utf-8')
|
||||
.trim()
|
||||
.split(/\r?\n/g)
|
||||
.map((h) => h.split(': '));
|
||||
|
||||
for (const [key, value] of headers) {
|
||||
if (!key || !value) {
|
||||
console.warn(`Skipping ${key}:${value} header pair`);
|
||||
continue;
|
||||
}
|
||||
res.setHeader(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/web-platform-tests/wpt/blob/6ae3f702a332e8399fab778c831db6b7dca3f1c6/fetch/api/resources/data.json
|
||||
createReadStream(join(tests, fullUrl.pathname))
|
||||
.on('end', () => res.end())
|
||||
.pipe(res);
|
||||
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/trickle.py': {
|
||||
// Note: python's time.sleep(...) takes seconds, while setTimeout
|
||||
// takes ms.
|
||||
const delay = parseFloat(fullUrl.searchParams.get('ms') ?? 500);
|
||||
const count = parseInt(fullUrl.searchParams.get('count') ?? 50);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for await (const chunk of req); // read request body
|
||||
|
||||
await sleep(delay);
|
||||
|
||||
if (!fullUrl.searchParams.has('notype')) {
|
||||
res.setHeader('Content-type', 'text/plain');
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
await sleep(delay);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
res.write('TEST_TRICKLE\n');
|
||||
await sleep(delay);
|
||||
}
|
||||
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/infinite-slow-response.py': {
|
||||
// https://github.com/web-platform-tests/wpt/blob/master/fetch/api/resources/infinite-slow-response.py
|
||||
const stateKey = fullUrl.searchParams.get('stateKey') ?? '';
|
||||
const abortKey = fullUrl.searchParams.get('abortKey') ?? '';
|
||||
|
||||
if (stateKey) {
|
||||
stash.put(stateKey, 'open', fullUrl.pathname);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.statusCode = 200;
|
||||
|
||||
res.write('.'.repeat(2048));
|
||||
|
||||
while (true) {
|
||||
if (!res.write('.')) {
|
||||
break;
|
||||
} else if (abortKey && stash.take(abortKey)) {
|
||||
break;
|
||||
}
|
||||
|
||||
await sleep(100);
|
||||
}
|
||||
|
||||
if (stateKey) {
|
||||
stash.put(stateKey, 'closed', fullUrl.pathname);
|
||||
}
|
||||
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/stash-take.py': {
|
||||
// https://github.com/web-platform-tests/wpt/blob/6ae3f702a332e8399fab778c831db6b7dca3f1c6/fetch/api/resources/stash-take.py
|
||||
|
||||
const key = fullUrl.searchParams.get('key');
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
const took = stash.take(key, fullUrl.pathname) ?? null;
|
||||
|
||||
res.write(JSON.stringify(took));
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/echo-content.py': {
|
||||
res.setHeader('X-Request-Method', req.method);
|
||||
res.setHeader('X-Request-Content-Length', req.headers['content-length'] ?? 'NO');
|
||||
res.setHeader('X-Request-Content-Type', req.headers['content-type'] ?? 'NO');
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
|
||||
for await (const chunk of req) {
|
||||
res.write(chunk);
|
||||
}
|
||||
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/cache.py': {
|
||||
if (req.headers['if-none-match'] === '"123abc"') {
|
||||
res.statusCode = 304;
|
||||
res.statusMessage = 'Not Modified';
|
||||
res.setHeader('X-HTTP-STATUS', '304');
|
||||
res.end();
|
||||
} else {
|
||||
// cache miss, so respond with the actual content
|
||||
res.statusCode = 200;
|
||||
res.statusMessage = 'OK';
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.setHeader('ETag', '"123abc"');
|
||||
res.end('lorem ipsum dolor sit amet');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/status.py': {
|
||||
const code = parseInt(fullUrl.searchParams.get('code') ?? 200);
|
||||
const text = fullUrl.searchParams.get('text') ?? 'OMG';
|
||||
const content = fullUrl.searchParams.get('content') ?? '';
|
||||
const type = fullUrl.searchParams.get('type') ?? '';
|
||||
res.statusCode = code;
|
||||
res.statusMessage = text;
|
||||
res.setHeader('Content-Type', type);
|
||||
res.setHeader('X-Request-Method', req.method);
|
||||
res.end(content);
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/inspect-headers.py': {
|
||||
const query = fullUrl.searchParams;
|
||||
const checkedHeaders = query.get('headers')
|
||||
?.split('|')
|
||||
.map(h => h.toLowerCase()) ?? [];
|
||||
|
||||
if (query.has('headers')) {
|
||||
for (const header of checkedHeaders) {
|
||||
if (Object.hasOwn(req.headers, header)) {
|
||||
res.setHeader(`x-request-${header}`, req.headers[header] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (query.has('cors')) {
|
||||
if (Object.hasOwn(req.headers, 'origin')) {
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin ?? '');
|
||||
} else {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, HEAD');
|
||||
const exposedHeaders = checkedHeaders.map(h => `x-request-${h}`).join(', ');
|
||||
res.setHeader('Access-Control-Expose-Headers', exposedHeaders);
|
||||
if (query.has('allow_headers')) {
|
||||
res.setHeader('Access-Control-Allow-Headers', query.get('allowed_headers'));
|
||||
} else {
|
||||
res.setHeader('Access-Control-Allow-Headers', Object.keys(req.headers).join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.end('');
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/bad-chunk-encoding.py': {
|
||||
const query = fullUrl.searchParams;
|
||||
|
||||
const delay = parseFloat(query.get('ms') ?? 1000);
|
||||
const count = parseInt(query.get('count') ?? 50);
|
||||
await sleep(delay);
|
||||
res.socket.write(
|
||||
'HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n'
|
||||
);
|
||||
await sleep(delay);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
res.socket.write('a\r\nTEST_CHUNK\r\n');
|
||||
await sleep(delay);
|
||||
}
|
||||
|
||||
res.end('garbage');
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/redirect.py': {
|
||||
redirectRoute(req, res, fullUrl);
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/method.py': {
|
||||
if (fullUrl.searchParams.has('cors')) {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, FOO');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'x-test, x-foo');
|
||||
res.setHeader('Access-Control-Expose-Headers', 'x-request-method');
|
||||
}
|
||||
|
||||
res.setHeader('x-request-method', req.method);
|
||||
res.setHeader('x-request-content-type', req.headers['content-type'] ?? 'NO');
|
||||
res.setHeader('x-request-content-length', req.headers['content-length'] ?? 'NO');
|
||||
res.setHeader('x-request-content-encoding', req.headers['content-encoding'] ?? 'NO');
|
||||
res.setHeader('x-request-content-language', req.headers['content-language'] ?? 'NO');
|
||||
res.setHeader('x-request-content-location', req.headers['content-location'] ?? 'NO');
|
||||
|
||||
for await (const chunk of req) {
|
||||
res.write(chunk);
|
||||
}
|
||||
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/clean-stash.py': {
|
||||
const token = fullUrl.searchParams.get('token');
|
||||
const took = stash.take(token);
|
||||
|
||||
if (took) {
|
||||
res.end('1');
|
||||
} else {
|
||||
res.end('0');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case '/fetch/content-encoding/resources/bad-gzip-body.py': {
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
res.end('not actually gzip');
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/dump-authorization-header.py': {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
|
||||
if (req.headers.origin) {
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
} else {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Authorization');
|
||||
res.statusCode = 200;
|
||||
|
||||
if (req.headers.authorization) {
|
||||
res.end(req.headers.authorization);
|
||||
break;
|
||||
}
|
||||
|
||||
res.end('none');
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/authentication.py': {
|
||||
const auth = Buffer.from(req.headers.authorization.slice('Basic '.length), 'base64');
|
||||
const [user, password] = auth.toString().split(':');
|
||||
|
||||
if (user === 'user' && password === 'password') {
|
||||
res.end('Authentication done');
|
||||
break;
|
||||
}
|
||||
|
||||
const realm = fullUrl.searchParams.get('realm') ?? 'test';
|
||||
|
||||
res.statusCode = 401;
|
||||
res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
|
||||
res.end('Please login with credentials \'user\' and \'password\'');
|
||||
break;
|
||||
}
|
||||
case '/fetch/api/resources/redirect-empty-location.py': {
|
||||
res.setHeader('location', '');
|
||||
res.statusCode = 302;
|
||||
res.end('');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
res.statusCode = 200;
|
||||
res.end(fullUrl.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (res[symbols.kContent]) {
|
||||
res.write(res[symbols.kContent]);
|
||||
}
|
||||
}).listen(0);
|
||||
|
||||
await once(server, 'listening');
|
||||
|
||||
const send = (message) => {
|
||||
if (typeof process.send === 'function') {
|
||||
process.send(message);
|
||||
}
|
||||
};
|
||||
|
||||
const url = `http://localhost:${server.address().port}`;
|
||||
console.log('server opened ' + url);
|
||||
send({ server: url });
|
||||
|
||||
process.on('message', (message) => {
|
||||
if (message === 'shutdown') {
|
||||
server.close((err) => process.exit(err ? 1 : 0));
|
||||
}
|
||||
});
|
||||
|
||||
export { server };
|
||||
32
script/wpt/start.mjs
Normal file
32
script/wpt/start.mjs
Normal file
@@ -0,0 +1,32 @@
|
||||
import { app } from 'electron';
|
||||
|
||||
import { fork } from 'node:child_process';
|
||||
import { on } from 'node:events';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { WPTRunner } from './runner/runner.mjs';
|
||||
|
||||
const serverPath = fileURLToPath(join(import.meta.url, '../server/server.mjs'));
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
const child = fork(serverPath, [], {
|
||||
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
|
||||
});
|
||||
|
||||
child.stdout.pipe(process.stdout);
|
||||
child.stderr.pipe(process.stderr);
|
||||
child.on('exit', (code) => process.exit(code));
|
||||
|
||||
for await (const [message] of on(child, 'message')) {
|
||||
if (message.server) {
|
||||
const runner = new WPTRunner('fetch', message.server);
|
||||
runner.run();
|
||||
runner.once('completion', () => {
|
||||
if (child.connected) {
|
||||
child.send('shutdown');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
578
script/wpt/status/fetch.status.json
Normal file
578
script/wpt/status/fetch.status.json
Normal file
@@ -0,0 +1,578 @@
|
||||
{
|
||||
"api": {
|
||||
"abort": {
|
||||
"general.any.js": {
|
||||
"fail": [
|
||||
"response.arrayBuffer() rejects if already aborted",
|
||||
"response.blob() rejects if already aborted",
|
||||
"response.formData() rejects if already aborted",
|
||||
"response.json() rejects if already aborted",
|
||||
"response.text() rejects if already aborted",
|
||||
"response.bytes() rejects if already aborted",
|
||||
"Call text() twice on aborted response",
|
||||
"Fetch aborted & connection closed when aborted after calling response.arrayBuffer()",
|
||||
"Fetch aborted & connection closed when aborted after calling response.blob()",
|
||||
"Fetch aborted & connection closed when aborted after calling response.formData()",
|
||||
"Fetch aborted & connection closed when aborted after calling response.json()",
|
||||
"Fetch aborted & connection closed when aborted after calling response.text()",
|
||||
"Fetch aborted & connection closed when aborted after calling response.bytes()",
|
||||
"Stream errors once aborted. Underlying connection closed.",
|
||||
"Stream errors once aborted, after reading. Underlying connection closed."
|
||||
]
|
||||
},
|
||||
"cache.https.any.js": {
|
||||
"note": "undici doesn't implement http caching",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"basic": {
|
||||
"accept-header.any.js": {
|
||||
"fail": [
|
||||
"Request through fetch should have 'accept' header with value '*/*'",
|
||||
"Request through fetch should have 'accept' header with value 'custom/*'",
|
||||
"Request through fetch should have a 'accept-language' header",
|
||||
"Request through fetch should have 'accept-language' header with value 'bzh'"
|
||||
]
|
||||
},
|
||||
"conditional-get.any.js": {
|
||||
"fail": [
|
||||
"Testing conditional GET with ETags"
|
||||
],
|
||||
"note": "undici doesn't keep track of etags"
|
||||
},
|
||||
"error-after-response.any.js": {
|
||||
"fail": [
|
||||
"Response reader read() promise should reject after a network error happening after resolving fetch promise",
|
||||
"Response reader closed promise should reject after a network error happening after resolving fetch promise"
|
||||
]
|
||||
},
|
||||
"header-value-combining.any.js": {
|
||||
"fail": [
|
||||
"response.headers.get('content-length') expects 0, 0",
|
||||
"response.headers.get('foo-test') expects 1, 2, 3",
|
||||
"response.headers.get('heya') expects , \\x0B\f, 1, , , 2"
|
||||
],
|
||||
"flaky": [
|
||||
"response.headers.get('content-length') expects 0",
|
||||
"response.headers.get('double-trouble') expects , ",
|
||||
"response.headers.get('www-authenticate') expects 1, 2, 3, 4"
|
||||
]
|
||||
},
|
||||
"header-value-null-byte.any.js": {
|
||||
"fail": [
|
||||
"Ensure fetch() rejects null bytes in headers"
|
||||
]
|
||||
},
|
||||
"http-response-code.any.js": {
|
||||
"fail": [
|
||||
"Fetch on 425 response should not be retried for non TLS early data."
|
||||
]
|
||||
},
|
||||
"integrity.sub.any.js": {
|
||||
"note": "Electron: integrity is not working",
|
||||
"skip": true
|
||||
},
|
||||
"keepalive.any.js": {
|
||||
"note": "document is not defined",
|
||||
"skip": true
|
||||
},
|
||||
"mode-no-cors.sub.any.js": {
|
||||
"note": "undici doesn't implement CORs",
|
||||
"skip": true
|
||||
},
|
||||
"mode-same-origin.any.js": {
|
||||
"note": "undici doesn't respect RequestInit.mode",
|
||||
"skip": true
|
||||
},
|
||||
"referrer.any.js": {
|
||||
"note": "Electron: fix referrrer handling",
|
||||
"skip": true
|
||||
},
|
||||
"request-forbidden-headers.any.js": {
|
||||
"note": "undici doesn't filter headers",
|
||||
"skip": true
|
||||
},
|
||||
"request-headers.any.js": {
|
||||
"note": "Electron: fix response type",
|
||||
"skip": true
|
||||
},
|
||||
"request-headers-case.any.js": {
|
||||
"note": "Electron: rework header generation",
|
||||
"skip": true
|
||||
},
|
||||
"request-private-network-headers.tentative.any.js": {
|
||||
"note": "undici doesn't filter headers",
|
||||
"skip": true
|
||||
},
|
||||
"request-referrer.any.js": {
|
||||
"note": "Electron: fix referrrer handling",
|
||||
"skip": true
|
||||
},
|
||||
"request-upload.any.js": {
|
||||
"note": "no Float16Array",
|
||||
"fail": [
|
||||
"Fetch with POST with Float16Array body",
|
||||
"Fetch with POST with text body on 421 response should be retried once on new connection."
|
||||
]
|
||||
},
|
||||
"request-upload.h2.any.js": {
|
||||
"note": "undici doesn't support http/2",
|
||||
"skip": true
|
||||
},
|
||||
"response-url.sub.any.js": {
|
||||
"note": "Electron: does not support response.url",
|
||||
"skip": true
|
||||
},
|
||||
"scheme-about.any.js": {
|
||||
"note": "Electron: does not handle about urls",
|
||||
"skip": true
|
||||
},
|
||||
"scheme-blob.sub.any.js": {
|
||||
"note": "Electron: does not support blob urls",
|
||||
"skip": true
|
||||
},
|
||||
"scheme-data.any.js": {
|
||||
"note": "Electron: does not support data urls",
|
||||
"skip": true
|
||||
},
|
||||
"scheme-others.sub.any.js": {
|
||||
"note": "Electron: does not support unknown urls",
|
||||
"skip": true
|
||||
},
|
||||
"status.h2.any.js": {
|
||||
"note": "undici doesn't support http/2",
|
||||
"skip": true
|
||||
},
|
||||
"stream-response.any.js": {
|
||||
"fail": [
|
||||
"Stream response's body when content-type is not present"
|
||||
]
|
||||
},
|
||||
"stream-safe-creation.any.js": {
|
||||
"note": "Electron: stream accessors are broken",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"mime-type.any.js": {
|
||||
"note": "fails on all platforms, https://wpt.fyi/results/fetch/api/body/mime-type.any.html?label=master&label=experimental&product=chrome&product=firefox&product=safari&product=node.js&product=deno&aligned",
|
||||
"fail": [
|
||||
"Response: Extract a MIME type with clone"
|
||||
]
|
||||
}
|
||||
},
|
||||
"cors": {
|
||||
"note": "undici doesn't implement CORs",
|
||||
"skip": true
|
||||
},
|
||||
"credentials": {
|
||||
"authentication-basic.any.js": {
|
||||
"note": "Electron: fix response type",
|
||||
"skip": true
|
||||
},
|
||||
"authentication-redirection.any.js": {
|
||||
"note": "connects to https server",
|
||||
"fail": [
|
||||
"getAuthorizationHeaderValue - cross origin redirection",
|
||||
"getAuthorizationHeaderValue - same origin redirection"
|
||||
]
|
||||
},
|
||||
"cookies.any.js": {
|
||||
"note": "Electron: fix response type",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"fetch-later": {
|
||||
"note": "this is not part of the spec, only a proposal",
|
||||
"skip": true
|
||||
},
|
||||
"headers": {
|
||||
"header-setcookie.any.js": {
|
||||
"note": "undici doesn't filter headers",
|
||||
"fail": [
|
||||
"Set-Cookie is a forbidden response header"
|
||||
]
|
||||
},
|
||||
"header-values-normalize.any.js": {
|
||||
"note": "TODO(@KhafraDev): https://github.com/nodejs/undici/issues/1680",
|
||||
"fail": [
|
||||
"XMLHttpRequest with value %00",
|
||||
"XMLHttpRequest with value %01",
|
||||
"XMLHttpRequest with value %02",
|
||||
"XMLHttpRequest with value %03",
|
||||
"XMLHttpRequest with value %04",
|
||||
"XMLHttpRequest with value %05",
|
||||
"XMLHttpRequest with value %06",
|
||||
"XMLHttpRequest with value %07",
|
||||
"XMLHttpRequest with value %08",
|
||||
"XMLHttpRequest with value %09",
|
||||
"XMLHttpRequest with value %0A",
|
||||
"XMLHttpRequest with value %0D",
|
||||
"XMLHttpRequest with value %0E",
|
||||
"XMLHttpRequest with value %0F",
|
||||
"XMLHttpRequest with value %10",
|
||||
"XMLHttpRequest with value %11",
|
||||
"XMLHttpRequest with value %12",
|
||||
"XMLHttpRequest with value %13",
|
||||
"XMLHttpRequest with value %14",
|
||||
"XMLHttpRequest with value %15",
|
||||
"XMLHttpRequest with value %16",
|
||||
"XMLHttpRequest with value %17",
|
||||
"XMLHttpRequest with value %18",
|
||||
"XMLHttpRequest with value %19",
|
||||
"XMLHttpRequest with value %1A",
|
||||
"XMLHttpRequest with value %1B",
|
||||
"XMLHttpRequest with value %1C",
|
||||
"XMLHttpRequest with value %1D",
|
||||
"XMLHttpRequest with value %1E",
|
||||
"XMLHttpRequest with value %1F",
|
||||
"XMLHttpRequest with value %20",
|
||||
"fetch() with value %01",
|
||||
"fetch() with value %02",
|
||||
"fetch() with value %03",
|
||||
"fetch() with value %04",
|
||||
"fetch() with value %05",
|
||||
"fetch() with value %06",
|
||||
"fetch() with value %07",
|
||||
"fetch() with value %08",
|
||||
"fetch() with value %0E",
|
||||
"fetch() with value %0F",
|
||||
"fetch() with value %10",
|
||||
"fetch() with value %11",
|
||||
"fetch() with value %12",
|
||||
"fetch() with value %13",
|
||||
"fetch() with value %14",
|
||||
"fetch() with value %15",
|
||||
"fetch() with value %16",
|
||||
"fetch() with value %17",
|
||||
"fetch() with value %18",
|
||||
"fetch() with value %19",
|
||||
"fetch() with value %1A",
|
||||
"fetch() with value %1B",
|
||||
"fetch() with value %1C",
|
||||
"fetch() with value %1D",
|
||||
"fetch() with value %1E",
|
||||
"fetch() with value %1F"
|
||||
]
|
||||
},
|
||||
"header-values.any.js": {
|
||||
"fail": [
|
||||
"XMLHttpRequest with value x%00x needs to throw",
|
||||
"XMLHttpRequest with value x%0Ax needs to throw",
|
||||
"XMLHttpRequest with value x%0Dx needs to throw",
|
||||
"XMLHttpRequest with all valid values",
|
||||
"fetch() with all valid values"
|
||||
]
|
||||
},
|
||||
"headers-no-cors.any.js": {
|
||||
"note": "undici doesn't implement CORs",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
"redirect-back-to-original-origin.any.js": {
|
||||
"note": "Electron: fix response type",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-count.any.js": {
|
||||
"note": "Electron: handle too many redirects",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-empty-location.any.js": {
|
||||
"note": "undici handles redirect: manual differently than browsers",
|
||||
"fail": [
|
||||
"redirect response with empty Location, manual mode",
|
||||
"redirect response with empty Location, follow mode"
|
||||
]
|
||||
},
|
||||
"redirect-keepalive.any.js": {
|
||||
"note": "document is not defined",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-keepalive.https.any.js": {
|
||||
"note": "document is not defined",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-location-escape.tentative.any.js": {
|
||||
"note": "TODO(@KhafraDev): crashes runner",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-location.any.js": {
|
||||
"note": "Electron: fix redirect handling",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-method.any.js": {
|
||||
"note": "Electron: fix response type",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-mode.any.js": {
|
||||
"note": "mode isn't respected",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-origin.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-referrer-override.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-referrer.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-schemes.any.js": {
|
||||
"note": "Electron: fix redirect handling",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-to-dataurl.any.js": {
|
||||
"note": "Electron: does not support data urls",
|
||||
"skip": true
|
||||
},
|
||||
"redirect-upload.h2.any.js": {
|
||||
"note": "undici doesn't support http/2",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"request": {
|
||||
"request-cache-default-conditional.any.js": {
|
||||
"note": "undici doesn't implement an http cache",
|
||||
"skip": true
|
||||
},
|
||||
"request-cache-default.any.js": {
|
||||
"note": "undici doesn't implement an http cache",
|
||||
"skip": true
|
||||
},
|
||||
"request-cache-force-cache.any.js": {
|
||||
"note": "undici doesn't implement an http cache",
|
||||
"skip": true
|
||||
},
|
||||
"request-cache-no-cache.any.js": {
|
||||
"note": "undici doesn't implement an http cache",
|
||||
"skip": true
|
||||
},
|
||||
"request-cache-no-store.any.js": {
|
||||
"note": "undici doesn't implement an http cache",
|
||||
"skip": true
|
||||
},
|
||||
"request-cache-only-if-cached.any.js": {
|
||||
"note": "undici doesn't implement an http cache",
|
||||
"skip": true
|
||||
},
|
||||
"request-cache-reload.any.js": {
|
||||
"note": "undici doesn't implement an http cache",
|
||||
"skip": true
|
||||
},
|
||||
"request-consume-empty.any.js": {
|
||||
"note": "the semantics about this test are being discussed - https://github.com/web-platform-tests/wpt/pull/3950",
|
||||
"fail": [
|
||||
"Consume empty FormData request body as text"
|
||||
]
|
||||
},
|
||||
"request-disturbed.any.js": {
|
||||
"note": "this test fails in all other platforms - https://wpt.fyi/results/fetch/api/request/request-disturbed.any.html?label=master&label=experimental&product=chrome&product=firefox&product=safari&product=deno&aligned&view=subtest",
|
||||
"fail": [
|
||||
"Input request used for creating new request became disturbed even if body is not used"
|
||||
]
|
||||
},
|
||||
"request-headers.any.js": {
|
||||
"note": "undici doesn't filter headers",
|
||||
"fail": [
|
||||
"Adding invalid request header \"Accept-Charset: KO\"",
|
||||
"Adding invalid request header \"accept-charset: KO\"",
|
||||
"Adding invalid request header \"ACCEPT-ENCODING: KO\"",
|
||||
"Adding invalid request header \"Accept-Encoding: KO\"",
|
||||
"Adding invalid request header \"Access-Control-Request-Headers: KO\"",
|
||||
"Adding invalid request header \"Access-Control-Request-Method: KO\"",
|
||||
"Adding invalid request header \"Connection: KO\"",
|
||||
"Adding invalid request header \"Content-Length: KO\"",
|
||||
"Adding invalid request header \"Cookie: KO\"",
|
||||
"Adding invalid request header \"Cookie2: KO\"",
|
||||
"Adding invalid request header \"Date: KO\"",
|
||||
"Adding invalid request header \"DNT: KO\"",
|
||||
"Adding invalid request header \"Expect: KO\"",
|
||||
"Adding invalid request header \"Host: KO\"",
|
||||
"Adding invalid request header \"Keep-Alive: KO\"",
|
||||
"Adding invalid request header \"Origin: KO\"",
|
||||
"Adding invalid request header \"Referer: KO\"",
|
||||
"Adding invalid request header \"Set-Cookie: KO\"",
|
||||
"Adding invalid request header \"TE: KO\"",
|
||||
"Adding invalid request header \"Trailer: KO\"",
|
||||
"Adding invalid request header \"Transfer-Encoding: KO\"",
|
||||
"Adding invalid request header \"Upgrade: KO\"",
|
||||
"Adding invalid request header \"Via: KO\"",
|
||||
"Adding invalid request header \"Proxy-: KO\"",
|
||||
"Adding invalid request header \"proxy-a: KO\"",
|
||||
"Adding invalid request header \"Sec-: KO\"",
|
||||
"Adding invalid request header \"sec-b: KO\"",
|
||||
"Adding invalid no-cors request header \"Content-Type: KO\"",
|
||||
"Adding invalid no-cors request header \"Potato: KO\"",
|
||||
"Adding invalid no-cors request header \"proxy: KO\"",
|
||||
"Adding invalid no-cors request header \"proxya: KO\"",
|
||||
"Adding invalid no-cors request header \"sec: KO\"",
|
||||
"Adding invalid no-cors request header \"secb: KO\"",
|
||||
"Adding invalid no-cors request header \"Empty-Value: \"",
|
||||
"Check that request constructor is filtering headers provided as init parameter",
|
||||
"Check that no-cors request constructor is filtering headers provided as init parameter",
|
||||
"Check that no-cors request constructor is filtering headers provided as part of request parameter"
|
||||
]
|
||||
},
|
||||
"request-init-priority.any.js": {
|
||||
"note": "undici doesn't implement priority hints, yet(?)",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"json.any.js": {
|
||||
"note": "Electron: does not support data urls",
|
||||
"skip": true
|
||||
},
|
||||
"response-blob-realm.any.js": {
|
||||
"note": "onload is not defined (globalThis does not extend EventTarget)",
|
||||
"fail": [
|
||||
"realm of the Uint8Array from Response bytes()"
|
||||
]
|
||||
},
|
||||
"response-clone.any.js": {
|
||||
"note": "Node streams are too buggy currently.",
|
||||
"skip": true
|
||||
},
|
||||
"response-consume-empty.any.js": {
|
||||
"fail": [
|
||||
"Consume empty FormData response body as text"
|
||||
]
|
||||
},
|
||||
"response-consume-stream.any.js": {
|
||||
"note": "only fail in node v18",
|
||||
"flaky": [
|
||||
"Read blob response's body as readableStream with mode=byob",
|
||||
"Read text response's body as readableStream with mode=byob",
|
||||
"Read URLSearchParams response's body as readableStream with mode=byob",
|
||||
"Read array buffer response's body as readableStream with mode=byob",
|
||||
"Read form data response's body as readableStream with mode=byob"
|
||||
]
|
||||
},
|
||||
"response-headers-guard.any.js": {
|
||||
"fail": [
|
||||
"Ensure response headers are immutable"
|
||||
]
|
||||
},
|
||||
"response-stream-with-broken-then.any.js": {
|
||||
"note": "this is a bug in webstreams, see https://github.com/nodejs/node/issues/46786",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"idlharness.any.js": {
|
||||
"note": "Electron: fix idl generation",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"content-encoding": {
|
||||
"br": {
|
||||
"bad-br-body.https.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate failure",
|
||||
"fail": [
|
||||
"Consuming the body of a resource with bad br content with arrayBuffer() should reject"
|
||||
]
|
||||
},
|
||||
"big-br-body.https.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate failure",
|
||||
"fail": [
|
||||
"large br data should be decompressed successfully",
|
||||
"large br data should be decompressed successfully with byte stream"
|
||||
]
|
||||
},
|
||||
"br-body.https.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate failure",
|
||||
"fail": [
|
||||
"fetched br data with content type text should be decompressed.",
|
||||
"fetched br data with content type octetstream should be decompressed."
|
||||
]
|
||||
}
|
||||
},
|
||||
"gzip": {
|
||||
"bad-gzip-body.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate failure",
|
||||
"fail": [
|
||||
"Consuming the body of a resource with bad gzip content with arrayBuffer() should reject",
|
||||
"Consuming the body of a resource with bad gzip content with blob() should reject",
|
||||
"Consuming the body of a resource with bad gzip content with json() should reject",
|
||||
"Consuming the body of a resource with bad gzip content with text() should reject"
|
||||
]
|
||||
},
|
||||
"gzip-body.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate failure",
|
||||
"fail": [
|
||||
"fetched gzip data with content type text should be decompressed.",
|
||||
"fetched gzip data with content type octetstream should be decompressed."
|
||||
]
|
||||
},
|
||||
"big-gzip-body.https.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate failure",
|
||||
"fail": [
|
||||
"large gzip data should be decompressed successfully",
|
||||
"large gzip data should be decompressed successfully with byte stream"
|
||||
]
|
||||
}
|
||||
},
|
||||
"zstd": {
|
||||
"note": "node does not have zstd yet",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"content-length": {
|
||||
"api-and-duplicate-headers.any.js": {
|
||||
"fail": [
|
||||
"XMLHttpRequest and duplicate Content-Length/Content-Type headers",
|
||||
"fetch() and duplicate Content-Length/Content-Type headers"
|
||||
]
|
||||
}
|
||||
},
|
||||
"cross-origin-resource-policy": {
|
||||
"note": "undici doesn't implement CORs",
|
||||
"skip": true
|
||||
},
|
||||
"data-urls": {
|
||||
"note": "Electron: does not support data urls",
|
||||
"skip": true
|
||||
},
|
||||
"http-cache": {
|
||||
"note": "undici doesn't implement http caching",
|
||||
"skip": true
|
||||
},
|
||||
"metadata": {
|
||||
"note": "undici doesn't respect RequestInit.mode",
|
||||
"skip": true
|
||||
},
|
||||
"orb": {
|
||||
"tentative": {
|
||||
"note": "undici doesn't implement orb",
|
||||
"skip": true
|
||||
}
|
||||
},
|
||||
"range": {
|
||||
"note": "undici doesn't respect range header",
|
||||
"skip": true
|
||||
},
|
||||
"security": {
|
||||
"1xx-response.any.js": {
|
||||
"note": "TODO(@KhafraDev): investigate timeout",
|
||||
"skip": true,
|
||||
"fail": [
|
||||
"Status(100) should be ignored.",
|
||||
"Status(101) should be accepted, with removing body.",
|
||||
"Status(103) should be ignored.",
|
||||
"Status(199) should be ignored."
|
||||
]
|
||||
}
|
||||
},
|
||||
"stale-while-revalidate": {
|
||||
"note": "undici doesn't implement http caching",
|
||||
"skip": true
|
||||
},
|
||||
"idlharness.any.js": {
|
||||
"flaky": [
|
||||
"Window interface: operation fetch(RequestInfo, optional RequestInit)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1508,15 +1508,6 @@ describe('net module', () => {
|
||||
});
|
||||
|
||||
describe('net.fetch', () => {
|
||||
// NB. there exist much more comprehensive tests for fetch() in the form of
|
||||
// the WPT: https://github.com/web-platform-tests/wpt/tree/master/fetch
|
||||
// It's possible to run these tests against net.fetch(), but the test
|
||||
// harness to do so is quite complex and hasn't been munged to smoothly run
|
||||
// inside the Electron test runner yet.
|
||||
//
|
||||
// In the meantime, here are some tests for basic functionality and
|
||||
// Electron-specific behavior.
|
||||
|
||||
describe('basic', () => {
|
||||
test('can fetch http urls', async () => {
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
|
||||
Reference in New Issue
Block a user