mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-05-17 21:10:43 +00:00
### Description <!-- Describe your changes. --> This PR tries to fix #22615. (see detailed description in the issue) A perfect solution would be too difficult to make, because there are a huge number of combinations of usage scenarios, including combinations of development framework, bundler, dev/prod mode, and so on. This PR is using the following approach: - Introduce a new type of end to end test: export test. This type of tests are complete web apps that use popular web development frameworks, and the tests are using puppeteer to run the apps and check if the apps can run without error. - added one nextjs based web app and one vite based web app. - In the test, perform the following test steps: - `npm install` for packages built locally - `npm run dev` to start dev server and use puppeteer to launch the browser to test - `npm run build && npm run start` to test prod build and use puppeteer to launch the browser to test - Make changes to ort-web, including: - special handling on Webpack's behavior of rewriting `import.meta.url` to a `file://` string - revise build definitions - fix wasm URL for proxy, if used in a bundled build
244 lines
8.7 KiB
JavaScript
244 lines
8.7 KiB
JavaScript
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
'use strict';
|
|
|
|
const path = require('path');
|
|
const fs = require('fs-extra');
|
|
const { spawn } = require('child_process');
|
|
const startServer = require('./simple-http-server');
|
|
const minimist = require('minimist');
|
|
|
|
const { NODEJS_TEST_CASES, BROWSER_TEST_CASES, BUNDLER_TEST_CASES } = require('./run-data');
|
|
|
|
// commandline arguments
|
|
const parsedArgs = minimist(process.argv.slice(2));
|
|
const BROWSER = parsedArgs.browser || 'Chrome_headless';
|
|
// Preserve the existing test folder
|
|
// When this flag is set, the test folder will not be cleared before running the tests.
|
|
// NPM install will not be run again.
|
|
// This is useful for debugging test failures as it makes it much faster to re-run.
|
|
const PRESERVE = parsedArgs.preserve || parsedArgs.p;
|
|
|
|
// copy whole folder to out-side of <ORT_ROOT>/js/ because we need to test in a folder that no `package.json` file
|
|
// exists in its parent folder.
|
|
// here we use <ORT_ROOT>/build/js/e2e/ for the test
|
|
|
|
const TEST_E2E_SRC_FOLDER = __dirname;
|
|
const JS_ROOT_FOLDER = path.resolve(__dirname, '../../..');
|
|
const TEST_E2E_RUN_FOLDER = path.resolve(JS_ROOT_FOLDER, '../build/js/e2e');
|
|
const NPM_CACHE_FOLDER = path.resolve(TEST_E2E_RUN_FOLDER, '../npm_cache');
|
|
const CHROME_USER_DATA_FOLDER = path.resolve(TEST_E2E_RUN_FOLDER, '../user_data');
|
|
if (!PRESERVE) {
|
|
fs.emptyDirSync(TEST_E2E_RUN_FOLDER);
|
|
fs.emptyDirSync(NPM_CACHE_FOLDER);
|
|
fs.emptyDirSync(CHROME_USER_DATA_FOLDER);
|
|
}
|
|
fs.copySync(TEST_E2E_SRC_FOLDER, TEST_E2E_RUN_FOLDER);
|
|
|
|
// always use a new folder as user-data-dir
|
|
let nextUserDataDirId = 0;
|
|
function getNextUserDataDir() {
|
|
const dir = path.resolve(CHROME_USER_DATA_FOLDER, nextUserDataDirId.toString());
|
|
nextUserDataDirId++;
|
|
fs.emptyDirSync(dir);
|
|
return dir;
|
|
}
|
|
|
|
async function main() {
|
|
// find packed package
|
|
const { globbySync } = await import('globby');
|
|
|
|
const ORT_COMMON_FOLDER = path.resolve(JS_ROOT_FOLDER, 'common');
|
|
const ORT_COMMON_PACKED_FILEPATH_CANDIDATES = globbySync('onnxruntime-common-*.tgz', { cwd: ORT_COMMON_FOLDER });
|
|
|
|
const PACKAGES_TO_INSTALL = [];
|
|
|
|
if (ORT_COMMON_PACKED_FILEPATH_CANDIDATES.length === 1) {
|
|
PACKAGES_TO_INSTALL.push(path.resolve(ORT_COMMON_FOLDER, ORT_COMMON_PACKED_FILEPATH_CANDIDATES[0]));
|
|
} else if (ORT_COMMON_PACKED_FILEPATH_CANDIDATES.length > 1) {
|
|
throw new Error('multiple packages found for onnxruntime-common.');
|
|
}
|
|
|
|
const ORT_WEB_FOLDER = path.resolve(JS_ROOT_FOLDER, 'web');
|
|
const ORT_WEB_PACKED_FILEPATH_CANDIDATES = globbySync('onnxruntime-web-*.tgz', { cwd: ORT_WEB_FOLDER });
|
|
if (ORT_WEB_PACKED_FILEPATH_CANDIDATES.length !== 1) {
|
|
throw new Error('cannot find exactly single package for onnxruntime-web.');
|
|
}
|
|
PACKAGES_TO_INSTALL.push(path.resolve(ORT_WEB_FOLDER, ORT_WEB_PACKED_FILEPATH_CANDIDATES[0]));
|
|
|
|
// we start here:
|
|
|
|
if (!PRESERVE) {
|
|
// install dev dependencies
|
|
await runInShell(`npm install`);
|
|
|
|
// npm install with "--cache" to install packed packages with an empty cache folder
|
|
await runInShell(`npm install --cache "${NPM_CACHE_FOLDER}" ${PACKAGES_TO_INSTALL.map((i) => `"${i}"`).join(' ')}`);
|
|
}
|
|
|
|
// perform exports testing
|
|
{
|
|
const testExportsMain = require(path.join(TEST_E2E_RUN_FOLDER, './exports/main'));
|
|
await testExportsMain(PRESERVE, PACKAGES_TO_INSTALL);
|
|
}
|
|
|
|
// prepare .wasm files for path override testing
|
|
prepareWasmPathOverrideFiles();
|
|
|
|
// Setup the wwwroot folder for hosting .wasm files (for cross-origin testing)
|
|
const serverWwwRoot = path.resolve(TEST_E2E_RUN_FOLDER, 'wwwroot');
|
|
fs.emptyDirSync(serverWwwRoot);
|
|
|
|
// prepare ESM loaders
|
|
prepareEsmLoaderFiles();
|
|
|
|
await fs.symlink(
|
|
path.resolve(TEST_E2E_RUN_FOLDER, 'node_modules', 'onnxruntime-web', 'dist'),
|
|
path.join(serverWwwRoot, 'dist'),
|
|
'junction',
|
|
);
|
|
await fs.symlink(
|
|
path.resolve(TEST_E2E_RUN_FOLDER, 'test-wasm-path-override'),
|
|
path.join(serverWwwRoot, 'test-wasm-path-override'),
|
|
'junction',
|
|
);
|
|
|
|
// start a HTTP server for hosting .wasm files (for cross-origin testing)
|
|
const server = startServer(serverWwwRoot, 8081);
|
|
|
|
// await delay(1000 * 3600); // wait for 1 hour
|
|
|
|
try {
|
|
// test case run in Node.js
|
|
await testAllNodejsCases();
|
|
|
|
// test cases with self-host (ort hosted in same origin)
|
|
await testAllBrowserCases({ hostInKarma: true });
|
|
|
|
// test cases without self-host (ort hosted in different origin)
|
|
await testAllBrowserCases({ hostInKarma: false });
|
|
|
|
// run bundlers
|
|
await runInShell(`npm run build`);
|
|
|
|
// test package consuming test
|
|
await testAllBrowserPackagesConsumingCases();
|
|
} finally {
|
|
// close the server after all tests
|
|
await server.close();
|
|
}
|
|
}
|
|
|
|
function prepareEsmLoaderFiles() {
|
|
const allEsmFiles = [...new Set(BROWSER_TEST_CASES.map((i) => i[3]).filter((i) => i && i.endsWith('.mjs')))];
|
|
|
|
// self-hosted
|
|
fs.emptyDirSync(path.join(TEST_E2E_RUN_FOLDER, 'esm-loaders'));
|
|
fs.emptyDirSync(path.join(TEST_E2E_RUN_FOLDER, 'wwwroot', 'esm-loaders'));
|
|
allEsmFiles.forEach((i) => {
|
|
fs.writeFileSync(
|
|
path.join(TEST_E2E_RUN_FOLDER, 'esm-loaders', i),
|
|
`import * as x from '../node_modules/onnxruntime-web/dist/${i}'; globalThis.ort = x;`,
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(TEST_E2E_RUN_FOLDER, 'wwwroot', 'esm-loaders', i),
|
|
`import * as x from '../dist/${i}'; globalThis.ort = x;`,
|
|
);
|
|
});
|
|
}
|
|
|
|
function prepareWasmPathOverrideFiles() {
|
|
const folder = path.join(TEST_E2E_RUN_FOLDER, 'test-wasm-path-override');
|
|
const sourceFile = path.join(
|
|
TEST_E2E_RUN_FOLDER,
|
|
'node_modules',
|
|
'onnxruntime-web',
|
|
'dist',
|
|
'ort-wasm-simd-threaded',
|
|
);
|
|
fs.emptyDirSync(folder);
|
|
fs.copyFileSync(`${sourceFile}.mjs`, path.join(folder, 'ort-wasm-simd-threaded.mjs'));
|
|
fs.copyFileSync(`${sourceFile}.wasm`, path.join(folder, 'ort-wasm-simd-threaded.wasm'));
|
|
fs.copyFileSync(`${sourceFile}.mjs`, path.join(folder, 'renamed.mjs'));
|
|
fs.copyFileSync(`${sourceFile}.wasm`, path.join(folder, 'renamed.wasm'));
|
|
fs.copyFileSync(`${sourceFile}.jsep.mjs`, path.join(folder, 'ort-wasm-simd-threaded.jsep.mjs'));
|
|
fs.copyFileSync(`${sourceFile}.jsep.wasm`, path.join(folder, 'ort-wasm-simd-threaded.jsep.wasm'));
|
|
fs.copyFileSync(`${sourceFile}.jsep.mjs`, path.join(folder, 'jsep-renamed.mjs'));
|
|
fs.copyFileSync(`${sourceFile}.jsep.wasm`, path.join(folder, 'jsep-renamed.wasm'));
|
|
}
|
|
|
|
async function testAllNodejsCases() {
|
|
for (const caseName of NODEJS_TEST_CASES) {
|
|
await runInShell(`node ./node_modules/mocha/bin/mocha --timeout 10000 ${caseName}`);
|
|
}
|
|
}
|
|
|
|
async function testAllBrowserCases({ hostInKarma }) {
|
|
for (const [testForSameOrigin, testForCrossOrigin, main, ortMain, args] of BROWSER_TEST_CASES) {
|
|
if (hostInKarma && testForSameOrigin) {
|
|
await runKarma({ hostInKarma, main, ortMain, args });
|
|
await runKarma({ hostInKarma, main, ortMain, args, enableSharedArrayBuffer: true });
|
|
}
|
|
if (!hostInKarma && testForCrossOrigin) {
|
|
await runKarma({ hostInKarma, main, ortMain, args });
|
|
await runKarma({ hostInKarma, main, ortMain, args, enableSharedArrayBuffer: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
async function testAllBrowserPackagesConsumingCases() {
|
|
for (const [main, format] of BUNDLER_TEST_CASES) {
|
|
await runKarma({ hostInKarma: true, main, ortMain: '', format });
|
|
await runKarma({ hostInKarma: true, main, ortMain: '', format, enableSharedArrayBuffer: true });
|
|
}
|
|
}
|
|
|
|
async function runKarma({
|
|
hostInKarma,
|
|
main,
|
|
browser = BROWSER,
|
|
ortMain = 'ort.min.js',
|
|
format = 'iife',
|
|
enableSharedArrayBuffer = false,
|
|
args = [],
|
|
}) {
|
|
const selfHostFlag = hostInKarma ? '--self-host' : '';
|
|
const argsStr = args.map((i) => `--test-args=${i}`).join(' ');
|
|
const formatFlag = `--format=${format}`;
|
|
const enableSharedArrayBufferFlag = enableSharedArrayBuffer ? '--enable-shared-array-buffer' : '';
|
|
await runInShell(
|
|
`npx karma start --single-run --browsers ${browser} ${selfHostFlag} --ort-main=${ortMain} --test-main=${
|
|
main
|
|
} --user-data=${getNextUserDataDir()} ${argsStr} ${formatFlag} ${enableSharedArrayBufferFlag}`,
|
|
);
|
|
}
|
|
|
|
async function runInShell(cmd) {
|
|
console.log('===============================================================');
|
|
console.log(' Running command in shell:');
|
|
console.log(' > ' + cmd);
|
|
console.log('===============================================================');
|
|
let complete = false;
|
|
const childProcess = spawn(cmd, { shell: true, stdio: 'inherit', cwd: TEST_E2E_RUN_FOLDER });
|
|
childProcess.on('close', function (code) {
|
|
if (code !== 0) {
|
|
process.exit(code);
|
|
} else {
|
|
complete = true;
|
|
}
|
|
});
|
|
while (!complete) {
|
|
await delay(100);
|
|
}
|
|
}
|
|
|
|
async function delay(ms) {
|
|
return new Promise(function (resolve) {
|
|
setTimeout(function () {
|
|
resolve();
|
|
}, ms);
|
|
});
|
|
}
|
|
|
|
main();
|