@vitronai/themis 0.1.0-beta.0 → 0.1.0-beta.2
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/CHANGELOG.md +16 -1
- package/README.md +173 -11
- package/docs/api.md +272 -4
- package/docs/schemas/agent-result.v1.json +7 -4
- package/docs/schemas/fix-handoff.v1.json +105 -0
- package/docs/schemas/generate-backlog.v1.json +117 -0
- package/docs/schemas/generate-handoff.v1.json +191 -0
- package/docs/schemas/generate-map.v1.json +95 -0
- package/docs/schemas/generate-result.v1.json +341 -0
- package/docs/vscode-extension.md +6 -0
- package/docs/why-themis.md +1 -1
- package/globals.d.ts +31 -1
- package/index.d.ts +405 -2
- package/index.js +14 -0
- package/package.json +1 -1
- package/src/artifacts.js +180 -2
- package/src/assets/exampleThemisReport.png +0 -0
- package/src/cli.js +390 -11
- package/src/config.js +21 -3
- package/src/discovery.js +32 -3
- package/src/expect.js +33 -4
- package/src/generate.js +5313 -0
- package/src/migrate.js +280 -0
- package/src/module-loader.js +22 -3
- package/src/reporter.js +4 -3
- package/src/runner.js +74 -13
- package/src/runtime.js +175 -66
- package/src/test-utils.js +607 -1
- package/src/watch.js +9 -0
- package/src/worker.js +1 -2
- package/src/snapshots.js +0 -90
package/src/migrate.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { DEFAULT_CONFIG, loadConfig } = require('./config');
|
|
4
|
+
|
|
5
|
+
const SUPPORTED_MIGRATION_SOURCES = new Set(['jest', 'vitest']);
|
|
6
|
+
const THEMIS_SETUP_FILE = path.join('tests', 'setup.themis.js');
|
|
7
|
+
const THEMIS_COMPAT_FILE = 'themis.compat.js';
|
|
8
|
+
const MIGRATION_REPORT_FILE = path.join('.themis', 'migration-report.json');
|
|
9
|
+
const SCANNABLE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
|
|
10
|
+
const IGNORED_DIRECTORIES = new Set(['node_modules', '.git', '.themis']);
|
|
11
|
+
|
|
12
|
+
function runMigrate(cwd, framework, options = {}) {
|
|
13
|
+
const source = String(framework || '').trim().toLowerCase();
|
|
14
|
+
if (!SUPPORTED_MIGRATION_SOURCES.has(source)) {
|
|
15
|
+
throw new Error(`Unsupported migrate source: ${String(framework)}. Use "jest" or "vitest".`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const projectRoot = path.resolve(cwd || process.cwd());
|
|
19
|
+
const configPath = path.join(projectRoot, 'themis.config.json');
|
|
20
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
21
|
+
const setupPath = path.join(projectRoot, THEMIS_SETUP_FILE);
|
|
22
|
+
const compatPath = path.join(projectRoot, THEMIS_COMPAT_FILE);
|
|
23
|
+
const reportPath = path.join(projectRoot, MIGRATION_REPORT_FILE);
|
|
24
|
+
|
|
25
|
+
const existingConfig = fs.existsSync(configPath) ? loadConfig(projectRoot) : { ...DEFAULT_CONFIG, setupFiles: [], testIgnore: [] };
|
|
26
|
+
const nextSetupFiles = Array.isArray(existingConfig.setupFiles) ? [...existingConfig.setupFiles] : [];
|
|
27
|
+
if (!nextSetupFiles.includes(THEMIS_SETUP_FILE)) {
|
|
28
|
+
nextSetupFiles.push(THEMIS_SETUP_FILE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const nextConfig = {
|
|
32
|
+
...existingConfig,
|
|
33
|
+
setupFiles: nextSetupFiles
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
fs.mkdirSync(path.dirname(setupPath), { recursive: true });
|
|
37
|
+
if (!fs.existsSync(setupPath)) {
|
|
38
|
+
fs.writeFileSync(setupPath, buildMigrationSetupSource(source), 'utf8');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(compatPath)) {
|
|
42
|
+
fs.writeFileSync(compatPath, buildMigrationCompatSource(), 'utf8');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf8');
|
|
46
|
+
|
|
47
|
+
let packageUpdated = false;
|
|
48
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
49
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
50
|
+
const scripts = parsed.scripts && typeof parsed.scripts === 'object' ? { ...parsed.scripts } : {};
|
|
51
|
+
if (!scripts['test:themis']) {
|
|
52
|
+
scripts['test:themis'] = 'themis test';
|
|
53
|
+
parsed.scripts = scripts;
|
|
54
|
+
fs.writeFileSync(packageJsonPath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
|
|
55
|
+
packageUpdated = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const scan = scanMigrationFiles(projectRoot);
|
|
60
|
+
const rewriteSummary = options.rewriteImports
|
|
61
|
+
? rewriteMigrationImports(projectRoot, scan.matches, compatPath)
|
|
62
|
+
: { rewrittenFiles: [], rewrittenImports: 0 };
|
|
63
|
+
const report = buildMigrationReport(projectRoot, source, scan.matches, rewriteSummary);
|
|
64
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
65
|
+
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
source,
|
|
69
|
+
configPath,
|
|
70
|
+
setupPath,
|
|
71
|
+
compatPath,
|
|
72
|
+
packageJsonPath: fs.existsSync(packageJsonPath) ? packageJsonPath : null,
|
|
73
|
+
packageUpdated,
|
|
74
|
+
reportPath,
|
|
75
|
+
report,
|
|
76
|
+
rewriteImports: Boolean(options.rewriteImports),
|
|
77
|
+
rewrittenFiles: rewriteSummary.rewrittenFiles
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildMigrationSetupSource(source) {
|
|
82
|
+
return `// Themis migration bridge for ${source} suites.
|
|
83
|
+
// Themis runtime supports imports from "@jest/globals", "vitest",
|
|
84
|
+
// and "@testing-library/react" directly, so this file can stay minimal.
|
|
85
|
+
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
restoreAllMocks();
|
|
88
|
+
cleanup();
|
|
89
|
+
});
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildMigrationCompatSource() {
|
|
94
|
+
return `const themisCompat = {
|
|
95
|
+
describe,
|
|
96
|
+
test,
|
|
97
|
+
it: test,
|
|
98
|
+
expect,
|
|
99
|
+
beforeAll,
|
|
100
|
+
beforeEach,
|
|
101
|
+
afterEach,
|
|
102
|
+
afterAll,
|
|
103
|
+
render,
|
|
104
|
+
screen,
|
|
105
|
+
fireEvent,
|
|
106
|
+
waitFor,
|
|
107
|
+
cleanup,
|
|
108
|
+
act: async (callback) => (typeof callback === 'function' ? callback() : undefined)
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const jestLike = {
|
|
112
|
+
fn,
|
|
113
|
+
spyOn,
|
|
114
|
+
mock,
|
|
115
|
+
unmock,
|
|
116
|
+
clearAllMocks,
|
|
117
|
+
resetAllMocks,
|
|
118
|
+
restoreAllMocks,
|
|
119
|
+
useFakeTimers,
|
|
120
|
+
useRealTimers,
|
|
121
|
+
advanceTimersByTime,
|
|
122
|
+
runAllTimers
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
...themisCompat,
|
|
127
|
+
jest: jestLike,
|
|
128
|
+
vi: jestLike
|
|
129
|
+
};
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function scanMigrationFiles(projectRoot) {
|
|
134
|
+
const files = [];
|
|
135
|
+
walkProject(projectRoot, files);
|
|
136
|
+
const matches = [];
|
|
137
|
+
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
const sourceText = fs.readFileSync(file, 'utf8');
|
|
140
|
+
const relativeFile = path.relative(projectRoot, file).split(path.sep).join('/');
|
|
141
|
+
const detected = detectMigrationImports(sourceText);
|
|
142
|
+
if (detected.length === 0) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
matches.push({
|
|
147
|
+
file: relativeFile,
|
|
148
|
+
imports: detected
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
files,
|
|
154
|
+
matches
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function buildMigrationReport(projectRoot, source, matches, rewriteSummary = { rewrittenFiles: [], rewrittenImports: 0 }) {
|
|
159
|
+
return {
|
|
160
|
+
schema: 'themis.migration.report.v1',
|
|
161
|
+
source,
|
|
162
|
+
createdAt: new Date().toISOString(),
|
|
163
|
+
summary: {
|
|
164
|
+
matchedFiles: matches.length,
|
|
165
|
+
jestGlobals: matches.filter((entry) => entry.imports.includes('@jest/globals')).length,
|
|
166
|
+
vitest: matches.filter((entry) => entry.imports.includes('vitest')).length,
|
|
167
|
+
testingLibraryReact: matches.filter((entry) => entry.imports.includes('@testing-library/react')).length,
|
|
168
|
+
rewrittenFiles: Array.isArray(rewriteSummary.rewrittenFiles) ? rewriteSummary.rewrittenFiles.length : 0,
|
|
169
|
+
rewrittenImports: Number(rewriteSummary.rewrittenImports || 0)
|
|
170
|
+
},
|
|
171
|
+
files: matches,
|
|
172
|
+
nextActions: [
|
|
173
|
+
'Run npx themis test to execute migrated suites under the Themis runtime.',
|
|
174
|
+
'Replace any unsupported Jest/Vitest-only helpers with Themis built-ins or project setup utilities.',
|
|
175
|
+
'Use npx themis generate src for source-driven unit-layer coverage alongside migrated suites.'
|
|
176
|
+
],
|
|
177
|
+
rewrites: Array.isArray(rewriteSummary.rewrittenFiles)
|
|
178
|
+
? rewriteSummary.rewrittenFiles
|
|
179
|
+
: []
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function rewriteMigrationImports(projectRoot, matches, compatPath) {
|
|
184
|
+
const rewrittenFiles = [];
|
|
185
|
+
let rewrittenImports = 0;
|
|
186
|
+
|
|
187
|
+
for (const match of matches) {
|
|
188
|
+
const absoluteFile = path.join(projectRoot, match.file);
|
|
189
|
+
const original = fs.readFileSync(absoluteFile, 'utf8');
|
|
190
|
+
const compatSpecifier = toPortableRelativeSpecifier(path.relative(path.dirname(absoluteFile), compatPath));
|
|
191
|
+
const rewritten = rewriteMigrationSourceText(original, compatSpecifier);
|
|
192
|
+
if (rewritten.source !== original) {
|
|
193
|
+
fs.writeFileSync(absoluteFile, rewritten.source, 'utf8');
|
|
194
|
+
rewrittenFiles.push(match.file);
|
|
195
|
+
rewrittenImports += rewritten.rewrites;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
rewrittenFiles,
|
|
201
|
+
rewrittenImports
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function rewriteMigrationSourceText(sourceText, compatSpecifier) {
|
|
206
|
+
const patterns = [
|
|
207
|
+
/(['"])@jest\/globals\1/g,
|
|
208
|
+
/(['"])vitest\1/g,
|
|
209
|
+
/(['"])@testing-library\/react\1/g
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
let rewrites = 0;
|
|
213
|
+
let nextSource = sourceText;
|
|
214
|
+
for (const pattern of patterns) {
|
|
215
|
+
nextSource = nextSource.replace(pattern, (match, quote) => {
|
|
216
|
+
rewrites += 1;
|
|
217
|
+
return `${quote}${compatSpecifier}${quote}`;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
source: nextSource,
|
|
223
|
+
rewrites
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function walkProject(dir, files) {
|
|
228
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
229
|
+
for (const entry of entries) {
|
|
230
|
+
if (entry.isDirectory()) {
|
|
231
|
+
if (IGNORED_DIRECTORIES.has(entry.name)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
walkProject(path.join(dir, entry.name), files);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!entry.isFile()) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const extension = path.extname(entry.name).toLowerCase();
|
|
243
|
+
if (!SCANNABLE_EXTENSIONS.has(extension)) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
files.push(path.join(dir, entry.name));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function detectMigrationImports(sourceText) {
|
|
252
|
+
const matches = [];
|
|
253
|
+
if (hasModuleReference(sourceText, '@jest/globals')) {
|
|
254
|
+
matches.push('@jest/globals');
|
|
255
|
+
}
|
|
256
|
+
if (hasModuleReference(sourceText, 'vitest')) {
|
|
257
|
+
matches.push('vitest');
|
|
258
|
+
}
|
|
259
|
+
if (hasModuleReference(sourceText, '@testing-library/react')) {
|
|
260
|
+
matches.push('@testing-library/react');
|
|
261
|
+
}
|
|
262
|
+
return matches;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function hasModuleReference(sourceText, moduleName) {
|
|
266
|
+
const escaped = moduleName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
267
|
+
return new RegExp(`(?:import\\s+[^;]*?from\\s+['"]${escaped}['"]|import\\s*\\(\\s*['"]${escaped}['"]\\s*\\)|require\\(\\s*['"]${escaped}['"]\\s*\\))`).test(sourceText);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function toPortableRelativeSpecifier(relativePath) {
|
|
271
|
+
const normalized = String(relativePath || '').split(path.sep).join('/');
|
|
272
|
+
if (normalized.startsWith('.')) {
|
|
273
|
+
return normalized;
|
|
274
|
+
}
|
|
275
|
+
return `./${normalized}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = {
|
|
279
|
+
runMigrate
|
|
280
|
+
};
|
package/src/module-loader.js
CHANGED
|
@@ -13,6 +13,9 @@ const DEFAULT_TS_COMPILER_OPTIONS = {
|
|
|
13
13
|
|
|
14
14
|
function createModuleLoader(options = {}) {
|
|
15
15
|
const projectRoot = safeRealpath(path.resolve(options.cwd || process.cwd()));
|
|
16
|
+
const virtualModules = options.virtualModules && typeof options.virtualModules === 'object'
|
|
17
|
+
? options.virtualModules
|
|
18
|
+
: {};
|
|
16
19
|
const tsconfigPath = options.tsconfigPath === null
|
|
17
20
|
? null
|
|
18
21
|
: resolveTsconfigPath(projectRoot, options.tsconfigPath);
|
|
@@ -53,6 +56,10 @@ function createModuleLoader(options = {}) {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
Module._resolveFilename = function themisResolveFilename(request, parent, isMain, resolutionOptions) {
|
|
59
|
+
if (Object.prototype.hasOwnProperty.call(virtualModules, request)) {
|
|
60
|
+
return request;
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
const aliasedRequest = resolveConfiguredRequest({
|
|
57
64
|
request,
|
|
58
65
|
parentFile: parent && parent.filename,
|
|
@@ -70,13 +77,19 @@ function createModuleLoader(options = {}) {
|
|
|
70
77
|
};
|
|
71
78
|
|
|
72
79
|
Module._load = function themisModuleLoad(request, parent, isMain) {
|
|
80
|
+
if (Object.prototype.hasOwnProperty.call(virtualModules, request)) {
|
|
81
|
+
const virtualValue = virtualModules[request];
|
|
82
|
+
return typeof virtualValue === 'function' ? virtualValue() : virtualValue;
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
const resolvedRequest = resolveRequestValue({
|
|
74
86
|
request,
|
|
75
87
|
parentFile: parent && parent.filename,
|
|
76
88
|
projectRoot,
|
|
77
89
|
compilerContext,
|
|
78
90
|
originalResolveFilename,
|
|
79
|
-
isMain
|
|
91
|
+
isMain,
|
|
92
|
+
virtualModules
|
|
80
93
|
});
|
|
81
94
|
|
|
82
95
|
if (mockRegistry.has(resolvedRequest)) {
|
|
@@ -116,7 +129,8 @@ function createModuleLoader(options = {}) {
|
|
|
116
129
|
parentFile,
|
|
117
130
|
projectRoot,
|
|
118
131
|
compilerContext,
|
|
119
|
-
originalResolveFilename
|
|
132
|
+
originalResolveFilename,
|
|
133
|
+
virtualModules
|
|
120
134
|
});
|
|
121
135
|
},
|
|
122
136
|
registerMock(request, parentFile, factoryOrExports) {
|
|
@@ -158,8 +172,13 @@ function resolveRequestValue({
|
|
|
158
172
|
projectRoot,
|
|
159
173
|
compilerContext,
|
|
160
174
|
originalResolveFilename,
|
|
161
|
-
isMain = false
|
|
175
|
+
isMain = false,
|
|
176
|
+
virtualModules = null
|
|
162
177
|
}) {
|
|
178
|
+
if (virtualModules && Object.prototype.hasOwnProperty.call(virtualModules, request)) {
|
|
179
|
+
return request;
|
|
180
|
+
}
|
|
181
|
+
|
|
163
182
|
const normalizedParent = parentFile ? safeRealpath(path.resolve(parentFile)) : path.join(projectRoot, '__themis_entry__.js');
|
|
164
183
|
const parentModule = {
|
|
165
184
|
id: normalizedParent,
|
package/src/reporter.js
CHANGED
|
@@ -66,7 +66,8 @@ function printAgent(result) {
|
|
|
66
66
|
lastRun: '.themis/last-run.json',
|
|
67
67
|
failedTests: '.themis/failed-tests.json',
|
|
68
68
|
runDiff: '.themis/run-diff.json',
|
|
69
|
-
runHistory: '.themis/run-history.json'
|
|
69
|
+
runHistory: '.themis/run-history.json',
|
|
70
|
+
fixHandoff: '.themis/fix-handoff.json'
|
|
70
71
|
};
|
|
71
72
|
|
|
72
73
|
const payload = {
|
|
@@ -84,8 +85,8 @@ function printAgent(result) {
|
|
|
84
85
|
hints: {
|
|
85
86
|
rerunFailed: 'npx themis test --rerun-failed',
|
|
86
87
|
targetedRerun: 'npx themis test --match "<regex>"',
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
diffLastRun: 'cat .themis/run-diff.json',
|
|
89
|
+
repairGenerated: 'cat .themis/fix-handoff.json'
|
|
89
90
|
}
|
|
90
91
|
};
|
|
91
92
|
|
package/src/runner.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
1
2
|
const path = require('path');
|
|
2
3
|
const { Worker } = require('worker_threads');
|
|
3
4
|
const { performance } = require('perf_hooks');
|
|
5
|
+
const { collectAndRun } = require('./runtime');
|
|
6
|
+
|
|
7
|
+
const inProcessResultCache = new Map();
|
|
4
8
|
|
|
5
9
|
async function runTests(files, options = {}) {
|
|
6
10
|
const startedAt = performance.now();
|
|
7
11
|
const startedAtIso = new Date().toISOString();
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
for (let i = 0; i < Math.min(maxWorkers, files.length); i += 1) {
|
|
14
|
-
workers.push(runNext(queue, fileResults, options));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
await Promise.all(workers);
|
|
12
|
+
const isolation = resolveIsolationMode(options);
|
|
13
|
+
const maxWorkers = isolation === 'in-process' ? 1 : resolveMaxWorkers(options.maxWorkers);
|
|
14
|
+
const fileResults = isolation === 'in-process'
|
|
15
|
+
? await runFilesInProcess(files, options)
|
|
16
|
+
: await runFilesInWorkers(files, options);
|
|
18
17
|
|
|
19
18
|
fileResults.sort((a, b) => a.file.localeCompare(b.file));
|
|
20
19
|
|
|
@@ -41,6 +40,27 @@ async function runTests(files, options = {}) {
|
|
|
41
40
|
};
|
|
42
41
|
}
|
|
43
42
|
|
|
43
|
+
async function runFilesInWorkers(files, options) {
|
|
44
|
+
const queue = [...files];
|
|
45
|
+
const workers = [];
|
|
46
|
+
const fileResults = [];
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < Math.min(resolveMaxWorkers(options.maxWorkers), files.length); i += 1) {
|
|
49
|
+
workers.push(runNext(queue, fileResults, options));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await Promise.all(workers);
|
|
53
|
+
return fileResults;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runFilesInProcess(files, options) {
|
|
57
|
+
const fileResults = [];
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
fileResults.push(await runFileInProcess(file, options));
|
|
60
|
+
}
|
|
61
|
+
return fileResults;
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
async function runNext(queue, fileResults, options) {
|
|
45
65
|
while (queue.length > 0) {
|
|
46
66
|
const file = queue.shift();
|
|
@@ -63,8 +83,7 @@ function runFileInWorker(file, options = {}) {
|
|
|
63
83
|
cwd: options.cwd || process.cwd(),
|
|
64
84
|
environment: options.environment || 'node',
|
|
65
85
|
setupFiles: Array.isArray(options.setupFiles) ? options.setupFiles : [],
|
|
66
|
-
tsconfigPath: options.tsconfigPath === undefined ? undefined : options.tsconfigPath
|
|
67
|
-
updateSnapshots: Boolean(options.updateSnapshots)
|
|
86
|
+
tsconfigPath: options.tsconfigPath === undefined ? undefined : options.tsconfigPath
|
|
68
87
|
}
|
|
69
88
|
});
|
|
70
89
|
|
|
@@ -155,6 +174,47 @@ function runFileInWorker(file, options = {}) {
|
|
|
155
174
|
});
|
|
156
175
|
}
|
|
157
176
|
|
|
177
|
+
async function runFileInProcess(file, options = {}) {
|
|
178
|
+
const cacheKey = options.cache ? buildInProcessCacheKey(file, options) : null;
|
|
179
|
+
if (cacheKey && inProcessResultCache.has(cacheKey)) {
|
|
180
|
+
return cloneResult(inProcessResultCache.get(cacheKey));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const result = await collectAndRun(file, options);
|
|
184
|
+
if (cacheKey) {
|
|
185
|
+
inProcessResultCache.set(cacheKey, cloneResult(result));
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildInProcessCacheKey(file, options) {
|
|
191
|
+
const stats = fs.statSync(file);
|
|
192
|
+
return JSON.stringify({
|
|
193
|
+
file: path.resolve(file),
|
|
194
|
+
size: stats.size,
|
|
195
|
+
mtimeMs: Math.round(stats.mtimeMs),
|
|
196
|
+
match: options.match || null,
|
|
197
|
+
allowedFullNames: Array.isArray(options.allowedFullNames) ? options.allowedFullNames : null,
|
|
198
|
+
noMemes: Boolean(options.noMemes),
|
|
199
|
+
cwd: options.cwd || process.cwd(),
|
|
200
|
+
environment: options.environment || 'node',
|
|
201
|
+
setupFiles: Array.isArray(options.setupFiles) ? options.setupFiles : [],
|
|
202
|
+
tsconfigPath: options.tsconfigPath === undefined ? '__default__' : options.tsconfigPath
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function cloneResult(result) {
|
|
207
|
+
return JSON.parse(JSON.stringify(result));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function clearRunCache() {
|
|
211
|
+
inProcessResultCache.clear();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function resolveIsolationMode(options = {}) {
|
|
215
|
+
return options.isolation === 'in-process' ? 'in-process' : 'worker';
|
|
216
|
+
}
|
|
217
|
+
|
|
158
218
|
function resolveMaxWorkers(value) {
|
|
159
219
|
const parsed = Number(value);
|
|
160
220
|
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
@@ -164,5 +224,6 @@ function resolveMaxWorkers(value) {
|
|
|
164
224
|
}
|
|
165
225
|
|
|
166
226
|
module.exports = {
|
|
167
|
-
runTests
|
|
227
|
+
runTests,
|
|
228
|
+
clearRunCache
|
|
168
229
|
};
|