@git.zone/tstest 1.5.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.js +9 -2
- package/dist_ts/tstest.classes.tap.parser.d.ts +4 -0
- package/dist_ts/tstest.classes.tap.parser.js +114 -24
- package/dist_ts/tstest.classes.testdirectory.d.ts +10 -0
- package/dist_ts/tstest.classes.testdirectory.js +31 -1
- package/dist_ts/tstest.classes.tstest.d.ts +3 -1
- package/dist_ts/tstest.classes.tstest.js +52 -27
- package/dist_ts_tapbundle/index.d.ts +1 -0
- package/dist_ts_tapbundle/index.js +2 -1
- package/dist_ts_tapbundle/tapbundle.classes.tap.d.ts +54 -1
- package/dist_ts_tapbundle/tapbundle.classes.tap.js +288 -24
- package/dist_ts_tapbundle/tapbundle.classes.taptest.d.ts +7 -1
- package/dist_ts_tapbundle/tapbundle.classes.taptest.js +75 -27
- package/dist_ts_tapbundle/tapbundle.classes.taptools.d.ts +81 -1
- package/dist_ts_tapbundle/tapbundle.classes.taptools.js +180 -2
- package/dist_ts_tapbundle/ts_tapbundle/00_commitinfo_data.d.ts +8 -0
- package/dist_ts_tapbundle/ts_tapbundle/00_commitinfo_data.js +9 -0
- package/dist_ts_tapbundle/ts_tapbundle/index.d.ts +6 -0
- package/dist_ts_tapbundle/ts_tapbundle/index.js +7 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.pretask.d.ts +10 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.pretask.js +13 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.tap.d.ts +104 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.tap.js +401 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.taptest.d.ts +38 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.taptest.js +110 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.taptools.d.ts +109 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.taptools.js +241 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.tapwrap.d.ts +8 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.classes.tapwrap.js +7 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.plugins.d.ts +8 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.plugins.js +10 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.tapcreator.d.ts +3 -0
- package/dist_ts_tapbundle/ts_tapbundle/tapbundle.tapcreator.js +5 -0
- package/dist_ts_tapbundle/ts_tapbundle/webhelpers.d.ts +7 -0
- package/dist_ts_tapbundle/ts_tapbundle/webhelpers.js +35 -0
- package/dist_ts_tapbundle/ts_tapbundle_node/classes.pathinject.d.ts +5 -0
- package/dist_ts_tapbundle/ts_tapbundle_node/classes.pathinject.js +13 -0
- package/dist_ts_tapbundle/ts_tapbundle_node/plugins.d.ts +11 -0
- package/dist_ts_tapbundle/ts_tapbundle_node/plugins.js +14 -0
- package/dist_ts_tapbundle_node/ts_tapbundle/tapbundle.classes.taptest.d.ts +38 -0
- package/dist_ts_tapbundle_node/ts_tapbundle/tapbundle.classes.taptest.js +110 -0
- package/dist_ts_tapbundle_node/ts_tapbundle/tapbundle.classes.taptools.d.ts +109 -0
- package/dist_ts_tapbundle_node/ts_tapbundle/tapbundle.classes.taptools.js +241 -0
- package/dist_ts_tapbundle_node/ts_tapbundle/tapbundle.plugins.d.ts +8 -0
- package/dist_ts_tapbundle_node/ts_tapbundle/tapbundle.plugins.js +10 -0
- package/dist_ts_tapbundle_node/ts_tapbundle/tapbundle.tapcreator.d.ts +3 -0
- package/dist_ts_tapbundle_node/ts_tapbundle/tapbundle.tapcreator.js +5 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/classes.pathinject.d.ts +5 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/classes.pathinject.js +13 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/classes.tapnodetools.d.ts +25 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/classes.tapnodetools.js +81 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/classes.testfileprovider.d.ts +6 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/classes.testfileprovider.js +16 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/index.d.ts +2 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/index.js +3 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/paths.d.ts +2 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/paths.js +4 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/plugins.d.ts +11 -0
- package/dist_ts_tapbundle_node/ts_tapbundle_node/plugins.js +14 -0
- package/package.json +11 -8
- package/readme.md +141 -0
- package/readme.plan.md +253 -30
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +8 -1
- package/ts/tstest.classes.tap.parser.ts +111 -25
- package/ts/tstest.classes.testdirectory.ts +39 -0
- package/ts/tstest.classes.tstest.ts +61 -27
|
@@ -16,7 +16,7 @@ export class TapParser {
|
|
|
16
16
|
expectedTests: number;
|
|
17
17
|
receivedTests: number;
|
|
18
18
|
|
|
19
|
-
testStatusRegex = /(ok|not\sok)\s([0-9]+)\s-\s(.*)\s#\
|
|
19
|
+
testStatusRegex = /(ok|not\sok)\s([0-9]+)\s-\s(.*)(\s#\s(.*))?$/;
|
|
20
20
|
activeTapTestResult: TapTestResult;
|
|
21
21
|
collectingErrorDetails: boolean = false;
|
|
22
22
|
currentTestError: string[] = [];
|
|
@@ -78,14 +78,33 @@ export class TapParser {
|
|
|
78
78
|
})();
|
|
79
79
|
|
|
80
80
|
const testSubject = regexResult[3];
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
const testMetadata = regexResult[5]; // This will be either "time=XXXms" or "SKIP reason" or "TODO reason"
|
|
82
|
+
|
|
83
|
+
let testDuration = 0;
|
|
84
|
+
let isSkipped = false;
|
|
85
|
+
let isTodo = false;
|
|
86
|
+
|
|
87
|
+
if (testMetadata) {
|
|
88
|
+
const timeMatch = testMetadata.match(/time=(\d+)ms/);
|
|
89
|
+
const skipMatch = testMetadata.match(/SKIP\s*(.*)/);
|
|
90
|
+
const todoMatch = testMetadata.match(/TODO\s*(.*)/);
|
|
91
|
+
|
|
92
|
+
if (timeMatch) {
|
|
93
|
+
testDuration = parseInt(timeMatch[1]);
|
|
94
|
+
} else if (skipMatch) {
|
|
95
|
+
isSkipped = true;
|
|
96
|
+
} else if (todoMatch) {
|
|
97
|
+
isTodo = true;
|
|
87
98
|
}
|
|
88
99
|
}
|
|
100
|
+
|
|
101
|
+
// test for protocol error - disabled as it's not critical
|
|
102
|
+
// The test ID mismatch can occur when tests are filtered, skipped, or use todo
|
|
103
|
+
// if (testId !== this.activeTapTestResult.id) {
|
|
104
|
+
// if (this.logger) {
|
|
105
|
+
// this.logger.error('Something is strange! Test Ids are not equal!');
|
|
106
|
+
// }
|
|
107
|
+
// }
|
|
89
108
|
this.activeTapTestResult.setTestResult(testOk);
|
|
90
109
|
|
|
91
110
|
if (testOk) {
|
|
@@ -107,27 +126,41 @@ export class TapParser {
|
|
|
107
126
|
this.activeTapTestResult.addLogLine(logLine);
|
|
108
127
|
}
|
|
109
128
|
|
|
110
|
-
// Check
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
// Check for snapshot communication
|
|
130
|
+
const snapshotMatch = logLine.match(/###SNAPSHOT###(.+)###SNAPSHOT###/);
|
|
131
|
+
if (snapshotMatch) {
|
|
132
|
+
const base64Data = snapshotMatch[1];
|
|
133
|
+
try {
|
|
134
|
+
const snapshotData = JSON.parse(Buffer.from(base64Data, 'base64').toString());
|
|
135
|
+
this.handleSnapshot(snapshotData);
|
|
136
|
+
} catch (error) {
|
|
118
137
|
if (this.logger) {
|
|
119
|
-
this.logger.
|
|
138
|
+
this.logger.testConsoleOutput(`Error parsing snapshot data: ${error.message}`);
|
|
120
139
|
}
|
|
121
|
-
this.collectingErrorDetails = false;
|
|
122
|
-
this.currentTestError = [];
|
|
123
140
|
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.
|
|
141
|
+
} else {
|
|
142
|
+
// Check if we're collecting error details
|
|
143
|
+
if (this.collectingErrorDetails) {
|
|
144
|
+
// Check if this line is an error detail (starts with Error: or has stack trace characteristics)
|
|
145
|
+
if (logLine.trim().startsWith('Error:') || logLine.trim().match(/^\s*at\s/)) {
|
|
146
|
+
this.currentTestError.push(logLine);
|
|
147
|
+
} else if (this.currentTestError.length > 0) {
|
|
148
|
+
// End of error details, show the error
|
|
149
|
+
const errorMessage = this.currentTestError.join('\n');
|
|
150
|
+
if (this.logger) {
|
|
151
|
+
this.logger.testErrorDetails(errorMessage);
|
|
152
|
+
}
|
|
153
|
+
this.collectingErrorDetails = false;
|
|
154
|
+
this.currentTestError = [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Don't output TAP error details as console output when we're collecting them
|
|
159
|
+
if (!this.collectingErrorDetails || (!logLine.trim().startsWith('Error:') && !logLine.trim().match(/^\s*at\s/))) {
|
|
160
|
+
if (this.logger) {
|
|
161
|
+
// This is console output from the test file, not TAP protocol
|
|
162
|
+
this.logger.testConsoleOutput(logLine);
|
|
163
|
+
}
|
|
131
164
|
}
|
|
132
165
|
}
|
|
133
166
|
}
|
|
@@ -205,6 +238,59 @@ export class TapParser {
|
|
|
205
238
|
public async handleTapLog(tapLog: string) {
|
|
206
239
|
this._processLog(tapLog);
|
|
207
240
|
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Handle snapshot data from the test
|
|
244
|
+
*/
|
|
245
|
+
private async handleSnapshot(snapshotData: { path: string; content: string; action: string }) {
|
|
246
|
+
try {
|
|
247
|
+
const smartfile = await import('@push.rocks/smartfile');
|
|
248
|
+
|
|
249
|
+
if (snapshotData.action === 'compare') {
|
|
250
|
+
// Try to read existing snapshot
|
|
251
|
+
try {
|
|
252
|
+
const existingSnapshot = await smartfile.fs.toStringSync(snapshotData.path);
|
|
253
|
+
if (existingSnapshot !== snapshotData.content) {
|
|
254
|
+
// Snapshot mismatch
|
|
255
|
+
if (this.logger) {
|
|
256
|
+
this.logger.testConsoleOutput(`Snapshot mismatch: ${snapshotData.path}`);
|
|
257
|
+
this.logger.testConsoleOutput(`Expected:\n${existingSnapshot}`);
|
|
258
|
+
this.logger.testConsoleOutput(`Received:\n${snapshotData.content}`);
|
|
259
|
+
}
|
|
260
|
+
// TODO: Communicate failure back to the test
|
|
261
|
+
} else {
|
|
262
|
+
if (this.logger) {
|
|
263
|
+
this.logger.testConsoleOutput(`Snapshot matched: ${snapshotData.path}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} catch (error: any) {
|
|
267
|
+
if (error.code === 'ENOENT') {
|
|
268
|
+
// Snapshot doesn't exist, create it
|
|
269
|
+
const dirPath = snapshotData.path.substring(0, snapshotData.path.lastIndexOf('/'));
|
|
270
|
+
await smartfile.fs.ensureDir(dirPath);
|
|
271
|
+
await smartfile.memory.toFs(snapshotData.content, snapshotData.path);
|
|
272
|
+
if (this.logger) {
|
|
273
|
+
this.logger.testConsoleOutput(`Snapshot created: ${snapshotData.path}`);
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else if (snapshotData.action === 'update') {
|
|
280
|
+
// Update snapshot
|
|
281
|
+
const dirPath = snapshotData.path.substring(0, snapshotData.path.lastIndexOf('/'));
|
|
282
|
+
await smartfile.fs.ensureDir(dirPath);
|
|
283
|
+
await smartfile.memory.toFs(snapshotData.content, snapshotData.path);
|
|
284
|
+
if (this.logger) {
|
|
285
|
+
this.logger.testConsoleOutput(`Snapshot updated: ${snapshotData.path}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} catch (error: any) {
|
|
289
|
+
if (this.logger) {
|
|
290
|
+
this.logger.testConsoleOutput(`Error handling snapshot: ${error.message}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
208
294
|
|
|
209
295
|
public async evaluateFinalResult() {
|
|
210
296
|
this.receivedTests = this.testStore.length;
|
|
@@ -99,4 +99,43 @@ export class TestDirectory {
|
|
|
99
99
|
}
|
|
100
100
|
return testFilePaths;
|
|
101
101
|
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get test files organized by parallel execution groups
|
|
105
|
+
* @returns An object with grouped tests
|
|
106
|
+
*/
|
|
107
|
+
async getTestFileGroups(): Promise<{
|
|
108
|
+
serial: string[];
|
|
109
|
+
parallelGroups: { [groupName: string]: string[] };
|
|
110
|
+
}> {
|
|
111
|
+
await this._init();
|
|
112
|
+
|
|
113
|
+
const result = {
|
|
114
|
+
serial: [] as string[],
|
|
115
|
+
parallelGroups: {} as { [groupName: string]: string[] }
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
for (const testFile of this.testfileArray) {
|
|
119
|
+
const filePath = testFile.path;
|
|
120
|
+
const fileName = plugins.path.basename(filePath);
|
|
121
|
+
|
|
122
|
+
// Check if file has parallel group pattern
|
|
123
|
+
const parallelMatch = fileName.match(/\.para__(\d+)\./);
|
|
124
|
+
|
|
125
|
+
if (parallelMatch) {
|
|
126
|
+
const groupNumber = parallelMatch[1];
|
|
127
|
+
const groupName = `para__${groupNumber}`;
|
|
128
|
+
|
|
129
|
+
if (!result.parallelGroups[groupName]) {
|
|
130
|
+
result.parallelGroups[groupName] = [];
|
|
131
|
+
}
|
|
132
|
+
result.parallelGroups[groupName].push(filePath);
|
|
133
|
+
} else {
|
|
134
|
+
// File runs serially
|
|
135
|
+
result.serial.push(filePath);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
102
141
|
}
|
|
@@ -15,6 +15,7 @@ export class TsTest {
|
|
|
15
15
|
public testDir: TestDirectory;
|
|
16
16
|
public executionMode: TestExecutionMode;
|
|
17
17
|
public logger: TsTestLogger;
|
|
18
|
+
public filterTags: string[];
|
|
18
19
|
|
|
19
20
|
public smartshellInstance = new plugins.smartshell.Smartshell({
|
|
20
21
|
executor: 'bash',
|
|
@@ -25,53 +26,81 @@ export class TsTest {
|
|
|
25
26
|
|
|
26
27
|
public tsbundleInstance = new plugins.tsbundle.TsBundle();
|
|
27
28
|
|
|
28
|
-
constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}) {
|
|
29
|
+
constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = []) {
|
|
29
30
|
this.executionMode = executionModeArg;
|
|
30
31
|
this.testDir = new TestDirectory(cwdArg, testPathArg, executionModeArg);
|
|
31
32
|
this.logger = new TsTestLogger(logOptions);
|
|
33
|
+
this.filterTags = tags;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
async run() {
|
|
35
|
-
const
|
|
37
|
+
const testGroups = await this.testDir.getTestFileGroups();
|
|
38
|
+
const allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()];
|
|
36
39
|
|
|
37
40
|
// Log test discovery
|
|
38
41
|
this.logger.testDiscovery(
|
|
39
|
-
|
|
42
|
+
allFiles.length,
|
|
40
43
|
this.testDir.testPath,
|
|
41
44
|
this.executionMode
|
|
42
45
|
);
|
|
43
46
|
|
|
44
47
|
const tapCombinator = new TapCombinator(this.logger); // lets create the TapCombinator
|
|
45
48
|
let fileIndex = 0;
|
|
46
|
-
|
|
49
|
+
|
|
50
|
+
// Execute serial tests first
|
|
51
|
+
for (const fileNameArg of testGroups.serial) {
|
|
47
52
|
fileIndex++;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
default:
|
|
68
|
-
const tapParserNode = await this.runInNode(fileNameArg, fileIndex, fileNamesToRun.length);
|
|
69
|
-
tapCombinator.addTapParser(tapParserNode);
|
|
70
|
-
break;
|
|
53
|
+
await this.runSingleTest(fileNameArg, fileIndex, allFiles.length, tapCombinator);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Execute parallel groups sequentially
|
|
57
|
+
const groupNames = Object.keys(testGroups.parallelGroups).sort();
|
|
58
|
+
for (const groupName of groupNames) {
|
|
59
|
+
const groupFiles = testGroups.parallelGroups[groupName];
|
|
60
|
+
|
|
61
|
+
if (groupFiles.length > 0) {
|
|
62
|
+
this.logger.sectionStart(`Parallel Group: ${groupName}`);
|
|
63
|
+
|
|
64
|
+
// Run all tests in this group in parallel
|
|
65
|
+
const parallelPromises = groupFiles.map(async (fileNameArg) => {
|
|
66
|
+
fileIndex++;
|
|
67
|
+
return this.runSingleTest(fileNameArg, fileIndex, allFiles.length, tapCombinator);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
await Promise.all(parallelPromises);
|
|
71
|
+
this.logger.sectionEnd();
|
|
71
72
|
}
|
|
72
73
|
}
|
|
74
|
+
|
|
73
75
|
tapCombinator.evaluate();
|
|
74
76
|
}
|
|
77
|
+
|
|
78
|
+
private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) {
|
|
79
|
+
switch (true) {
|
|
80
|
+
case process.env.CI && fileNameArg.includes('.nonci.'):
|
|
81
|
+
this.logger.tapOutput(`Skipping ${fileNameArg} - marked as non-CI`);
|
|
82
|
+
break;
|
|
83
|
+
case fileNameArg.endsWith('.browser.ts') || fileNameArg.endsWith('.browser.nonci.ts'):
|
|
84
|
+
const tapParserBrowser = await this.runInChrome(fileNameArg, fileIndex, totalFiles);
|
|
85
|
+
tapCombinator.addTapParser(tapParserBrowser);
|
|
86
|
+
break;
|
|
87
|
+
case fileNameArg.endsWith('.both.ts') || fileNameArg.endsWith('.both.nonci.ts'):
|
|
88
|
+
this.logger.sectionStart('Part 1: Chrome');
|
|
89
|
+
const tapParserBothBrowser = await this.runInChrome(fileNameArg, fileIndex, totalFiles);
|
|
90
|
+
tapCombinator.addTapParser(tapParserBothBrowser);
|
|
91
|
+
this.logger.sectionEnd();
|
|
92
|
+
|
|
93
|
+
this.logger.sectionStart('Part 2: Node');
|
|
94
|
+
const tapParserBothNode = await this.runInNode(fileNameArg, fileIndex, totalFiles);
|
|
95
|
+
tapCombinator.addTapParser(tapParserBothNode);
|
|
96
|
+
this.logger.sectionEnd();
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
const tapParserNode = await this.runInNode(fileNameArg, fileIndex, totalFiles);
|
|
100
|
+
tapCombinator.addTapParser(tapParserNode);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
75
104
|
|
|
76
105
|
public async runInNode(fileNameArg: string, index: number, total: number): Promise<TapParser> {
|
|
77
106
|
this.logger.testFileStart(fileNameArg, 'node.js', index, total);
|
|
@@ -82,6 +111,11 @@ export class TsTest {
|
|
|
82
111
|
if (process.argv.includes('--web')) {
|
|
83
112
|
tsrunOptions += ' --web';
|
|
84
113
|
}
|
|
114
|
+
|
|
115
|
+
// Set filter tags as environment variable
|
|
116
|
+
if (this.filterTags.length > 0) {
|
|
117
|
+
process.env.TSTEST_FILTER_TAGS = this.filterTags.join(',');
|
|
118
|
+
}
|
|
85
119
|
|
|
86
120
|
const execResultStreaming = await this.smartshellInstance.execStreamingSilent(
|
|
87
121
|
`tsrun ${fileNameArg}${tsrunOptions}`
|