sxy-test-runner 1.0.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/cli/index.js +33 -0
- package/dist/cli/init.js +170 -0
- package/dist/cli/init.unitTest.js +0 -0
- package/dist/cli/lib/addBasePath.js +15 -0
- package/dist/cli/lib/addBasePath.unitTest.js +31 -0
- package/dist/cli/lib/addExports.js +12 -0
- package/dist/cli/lib/addExports.unitTest.js +29 -0
- package/dist/cli/lib/applyConfigDefaults.js +41 -0
- package/dist/cli/lib/applyConfigDefaults.unitTest.js +25 -0
- package/dist/cli/lib/buildDependencyTree.js +32 -0
- package/dist/cli/lib/buildDependencyTree.unitTest.js +15 -0
- package/dist/cli/lib/buildDependencyTrees.js +34 -0
- package/dist/cli/lib/buildDependencyTrees.unitTest.js +17 -0
- package/dist/cli/lib/defaultConfig.js +22 -0
- package/dist/cli/lib/defaultConfig.test.js +7 -0
- package/dist/cli/lib/doUserSetup.js +30 -0
- package/dist/cli/lib/doUserSetup.unitTest.js +39 -0
- package/dist/cli/lib/doUserTeardown.js +30 -0
- package/dist/cli/lib/doUserTeardown.unitTest.js +39 -0
- package/dist/cli/lib/findTestFiles.js +24 -0
- package/dist/cli/lib/findTestFiles.unitTest.js +22 -0
- package/dist/cli/lib/getAllowedFlags.js +11 -0
- package/dist/cli/lib/getAllowedFlags.unitTest.js +20 -0
- package/dist/cli/lib/globArgCombine.js +1 -0
- package/dist/cli/lib/globArgCombine.unitTest.js +18 -0
- package/dist/cli/lib/injectProperties.js +11 -0
- package/dist/cli/lib/injectProperties.unitTest.js +75 -0
- package/dist/cli/lib/load.js +3 -0
- package/dist/cli/lib/load.unitTest.js +7 -0
- package/dist/cli/lib/loadConfig.js +21 -0
- package/dist/cli/lib/loadConfig.unitTest.js +15 -0
- package/dist/cli/lib/loadTestFile.js +75 -0
- package/dist/cli/lib/loadTestFile.unitTest.js +45 -0
- package/dist/cli/lib/loadTestFileStatic.js +40 -0
- package/dist/cli/lib/loadTestFileStatic.unitTest.js +29 -0
- package/dist/cli/lib/parseCommandLineArguments.js +57 -0
- package/dist/cli/lib/parseCommandLineArguments.unitTest.js +21 -0
- package/dist/cli/lib/parseDescribe.js +88 -0
- package/dist/cli/lib/parseDescribe.unitTest.js +17 -0
- package/dist/cli/lib/parseItsFromDescribe.js +115 -0
- package/dist/cli/lib/processWatchEvent.js +0 -0
- package/dist/cli/lib/processWatchEvent.unitTest.js +7 -0
- package/dist/cli/lib/reservedExportKeys.js +1 -0
- package/dist/cli/lib/reservedExportKeys.unitTest.js +7 -0
- package/dist/cli/lib/runDescribe.js +29 -0
- package/dist/cli/lib/runDescribe.old copy.js +115 -0
- package/dist/cli/lib/runDescribe.old.js +115 -0
- package/dist/cli/lib/runDescribe.old.unitTest.js +440 -0
- package/dist/cli/lib/runDescribe.unitTest.js +287 -0
- package/dist/cli/lib/runDescribeParseIts.js +87 -0
- package/dist/cli/lib/runDescribeParseIts.unitTest.js +0 -0
- package/dist/cli/lib/runDescribeRun.js +59 -0
- package/dist/cli/lib/runDescribeRun.unitTest.js +293 -0
- package/dist/cli/lib/runDescribeRunIts.js +56 -0
- package/dist/cli/lib/runDescribeRunIts.unitTest.js +0 -0
- package/dist/cli/lib/runIt.js +79 -0
- package/dist/cli/lib/runIt.unitTest.js +294 -0
- package/dist/cli/lib/runTasks.js +42 -0
- package/dist/cli/lib/runTasks.unitTest.js +30 -0
- package/dist/cli/lib/runTest-old.js +159 -0
- package/dist/cli/lib/runTest-old.unitTest.js +183 -0
- package/dist/cli/lib/runTest.js +71 -0
- package/dist/cli/lib/runTest.unitTest.js +269 -0
- package/dist/cli/lib/runTests.js +137 -0
- package/dist/cli/lib/runTests.unitTest.js +59 -0
- package/dist/cli/lib/showTestLoadingError.js +22 -0
- package/dist/cli/lib/showTestLoadingError.unitTest.js +25 -0
- package/dist/cli/lib/showTestResult.js +87 -0
- package/dist/cli/lib/showTestResult.unitTest.js +180 -0
- package/dist/cli/lib/timeProfier.js +7 -0
- package/dist/cli/lib/validateConfig.js +29 -0
- package/dist/cli/lib/validateConfig.unitTest.js +22 -0
- package/dist/cli/lib/watchFiles.js +1 -0
- package/dist/cli/lib/watchFiles.unitTest.js +48 -0
- package/dist/cli/lib/watchFilesAndRunTests.js +2 -0
- package/dist/cli/lib/watchFilesAndRunTests.unitTest.js +129 -0
- package/dist/cli/lib/watchFilesAndRunTestsChokidar.js +142 -0
- package/dist/cli/lib/watchFilesAndRunTestsChokidar.unitTest.js +133 -0
- package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.js +162 -0
- package/dist/cli/lib/watchFilesAndRunTestsNodeWatch.unitTest.js +131 -0
- package/dist/cli/lib/watchFilesChokidar.js +56 -0
- package/dist/cli/lib/watchFilesChokidar.unitTest.js +85 -0
- package/dist/cli/lib/watchFilesNodeWatch.js +42 -0
- package/dist/cli/lib/watchFilesNodeWatch.unitTest.js +48 -0
- package/dist/cli/once.js +47 -0
- package/dist/cli/once.unitTest.js +39 -0
- package/dist/cli/watch.js +100 -0
- package/dist/cli/watch.unitTest.js +117 -0
- package/dist/config.js +7 -0
- package/dist/describe-old.js +257 -0
- package/dist/describe-old.unitTest.js +21 -0
- package/dist/describe.js +24 -0
- package/dist/describe.unitTest.js +27 -0
- package/dist/exported.eslintrc.js +7 -0
- package/dist/index.js +1 -0
- package/dist/indexNumberToLetters.js +13 -0
- package/dist/load.js +3 -0
- package/dist/output.js +17 -0
- package/dist/testing/example.test.js +26 -0
- package/dist/testing/example2.test.js +19 -0
- package/dist/testing/example3.test.js +16 -0
- package/dist/testing/importTimeTarget.js +9 -0
- package/dist/testing/importTimeTarget2.js +11 -0
- package/dist/testing/importTimeTest.js +8 -0
- package/dist/testsState.js +4 -0
- package/dist/timeProfiling.js +9 -0
- package/package.json +68 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createTimeProfiler } from 'sxy-dev-tools';
|
|
2
|
+
import { showTimeProfiling } from '../../config.js';
|
|
3
|
+
import chalk from 'chalk-extensions';
|
|
4
|
+
import minimatch from 'minimatch';
|
|
5
|
+
import slash from 'slash';
|
|
6
|
+
import watch from 'node-watch'; //import chokidar from 'chokidar'
|
|
7
|
+
//import nsfw from 'nsfw'
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import { out, log, debug } from '../../output.js';
|
|
11
|
+
import { runTests } from './runTests.js';
|
|
12
|
+
import { buildDependencyTree } from './buildDependencyTree.js';
|
|
13
|
+
import { globArgCombine } from './globArgCombine.js';
|
|
14
|
+
import { load } from './load.js';
|
|
15
|
+
const {
|
|
16
|
+
timeProfileAsync
|
|
17
|
+
} = await createTimeProfiler(showTimeProfiling);
|
|
18
|
+
import { watchFilesChokidar } from './watchFilesChokidar.js';
|
|
19
|
+
export async function watchFilesAndRunTestsChokidar(config, testFiles, dependencyTrees, failingTests, customOut = undefined) {
|
|
20
|
+
const projectDir = await process.cwd();
|
|
21
|
+
await timeProfileAsync('start watching', async () => {
|
|
22
|
+
let changedFiles = [];
|
|
23
|
+
let timeout = null;
|
|
24
|
+
|
|
25
|
+
const testFilter = file => minimatch(file, globArgCombine(config.tests)) && (!config.testsIgnore || !minimatch(file, globArgCombine(config.testsIgnore))); // start watching
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
await watchFilesChokidar(config, watchEvent);
|
|
29
|
+
|
|
30
|
+
async function watchEvent(filename, event) {
|
|
31
|
+
await debug('chokidar watch event', filename, event); //filename = slash(filename)
|
|
32
|
+
// remove dep tree for any tests that are deleted, avoiding trying to run it later
|
|
33
|
+
|
|
34
|
+
if (event === 'remove' && filename in dependencyTrees) {
|
|
35
|
+
await testFiles.remove(filename);
|
|
36
|
+
delete dependencyTrees[filename];
|
|
37
|
+
} // info on the event found
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
await out(chalk.grey`change: ${filename} (${event})`); // add the file to changed files
|
|
41
|
+
|
|
42
|
+
await changedFiles.push([filename, event]); // wait 100ms after any change before processing, to avoid multiple execution?
|
|
43
|
+
|
|
44
|
+
if (timeout) {
|
|
45
|
+
await clearTimeout(timeout);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
timeout = await setTimeout(async () => {
|
|
49
|
+
await timeProfileAsync('handle changed files', async () => {
|
|
50
|
+
await debug('changed files', changedFiles); //out('changed files', changedFiles)
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
// build related test list
|
|
54
|
+
const tests = [];
|
|
55
|
+
|
|
56
|
+
for (const [file, event] of changedFiles) {
|
|
57
|
+
await debug('check', file);
|
|
58
|
+
await debug('1 tests', tests);
|
|
59
|
+
|
|
60
|
+
if (event !== 'remove') {
|
|
61
|
+
// only check to see if its a test if it exists (has not just been removed)
|
|
62
|
+
if (await testFilter(file)) {
|
|
63
|
+
// it is a test file itself, add to run list
|
|
64
|
+
await debug('its a test', file);
|
|
65
|
+
await tests.push(file);
|
|
66
|
+
|
|
67
|
+
if (!(file in dependencyTrees)) {
|
|
68
|
+
// if its a new test
|
|
69
|
+
await debug('try to add a new test', file), await addNewTest(file);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await debug('dtrees', dependencyTrees);
|
|
75
|
+
await debug('2 tests', tests);
|
|
76
|
+
|
|
77
|
+
for (const f of await Object.getOwnPropertyNames(dependencyTrees)) {
|
|
78
|
+
await debug('dep tree', f, dependencyTrees[f]);
|
|
79
|
+
|
|
80
|
+
if (await dependencyTrees[f].some(dep => {
|
|
81
|
+
debug('checking dep', dep, file);
|
|
82
|
+
return dep === file;
|
|
83
|
+
})) {
|
|
84
|
+
// a test depends on this file
|
|
85
|
+
await debug('its a dependency', file);
|
|
86
|
+
await debug('its a dependency', file);
|
|
87
|
+
await tests.push(f);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
changedFiles = [];
|
|
93
|
+
await debug('tests', tests);
|
|
94
|
+
await debug('3 tests', tests);
|
|
95
|
+
|
|
96
|
+
if (tests.length) {
|
|
97
|
+
await timeProfileAsync('run tests related to changes', async () => {
|
|
98
|
+
await debug('tests', tests);
|
|
99
|
+
|
|
100
|
+
if (config.reRunFailingTests) {
|
|
101
|
+
for (const failingTest of failingTests) {
|
|
102
|
+
if (!(await fs.existsSync(failingTest))) {
|
|
103
|
+
//debug('remove failing test', failingTest)
|
|
104
|
+
await failingTests.remove(failingTest); //(failingTests)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await debug('tests', tests);
|
|
110
|
+
await debug('failingTests', failingTests);
|
|
111
|
+
await debug('reRunFailingTests', config.watch.reRunFailingTests);
|
|
112
|
+
const toTest = config.watch.reRunFailingTests ? await (await [...tests, ...failingTests].dedupeSlow()).reverse() // dedupe return new? // reverse in place
|
|
113
|
+
: await [...tests].reverse(); // reverse in place
|
|
114
|
+
|
|
115
|
+
const results = {
|
|
116
|
+
total: 0,
|
|
117
|
+
passed: 0
|
|
118
|
+
};
|
|
119
|
+
await debug('run tests on change');
|
|
120
|
+
await debug('toTest', toTest); // clear load cache to allow accesing the new tets
|
|
121
|
+
|
|
122
|
+
await load.cache.clear();
|
|
123
|
+
const testResults = await runTests(config, toTest, failingTests, customOut);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
await log('watch handler error', e);
|
|
128
|
+
} finally {//
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}, 33);
|
|
132
|
+
} // end of watch event
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async function addNewTest(testFile) {
|
|
136
|
+
await debug('new test', testFile);
|
|
137
|
+
const dependencyTree = await buildDependencyTree(config, testFile);
|
|
138
|
+
dependencyTrees[testFile] = await (await dependencyTree.map(async file => await (await slash(file)).slice(projectDir.length + 1))).filter(async f => f !== testFile);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
await out(chalk.brightblue`watching...`);
|
|
142
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { promises as fs, existsSync } from 'fs';
|
|
2
|
+
import figures from 'figures';
|
|
3
|
+
import { watchFilesAndRunTestsChokidar } from './watchFilesAndRunTestsChokidar.js';
|
|
4
|
+
import { applyConfigDefaults } from './applyConfigDefaults.js'; // watchFiles(config, testFiles, dependencyTrees, failingTests)
|
|
5
|
+
|
|
6
|
+
await mochaDescribe('watchFilesAndRunTestsChokidar() function', async function () {
|
|
7
|
+
const tempFolder = 'temp/watchFilesAndRunTestsChokidar';
|
|
8
|
+
const baseConfig = await applyConfigDefaults({
|
|
9
|
+
testsBase: tempFolder,
|
|
10
|
+
tests: '**/*.test.js',
|
|
11
|
+
watch: {
|
|
12
|
+
watchFilesBase: tempFolder,
|
|
13
|
+
watchFiles: '**/*.js'
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
await mochaIt('should run a test when a test file is changed', async function () {
|
|
17
|
+
//this.timeout(100000)
|
|
18
|
+
const config = await Object.copy(baseConfig); // ignore the file from the last test, causing load errors
|
|
19
|
+
|
|
20
|
+
config.testsIgnore = '**/c.test.js'; // create a custom out function to capture the output
|
|
21
|
+
|
|
22
|
+
let outBuffer = '';
|
|
23
|
+
|
|
24
|
+
const customOut = (...texts) => {
|
|
25
|
+
outBuffer += texts.join(' ') + '\n'; // join and save to the buffer
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const testFileName = 'temp/watchFilesAndRunTestsChokidar/a.test.js';
|
|
29
|
+
const thing = 'Sample test 1';
|
|
30
|
+
const should = '1 should equal 1';
|
|
31
|
+
await watchFilesAndRunTestsChokidar(config, [], {}, [], customOut);
|
|
32
|
+
await outBuffer.should.equal('');
|
|
33
|
+
await fs.writeFile(testFileName, `import { describe } from '../../dist/index.js'
|
|
34
|
+
import { expect, should } from 'chai'
|
|
35
|
+
should()
|
|
36
|
+
|
|
37
|
+
describe('${thing}', ({it}) => {
|
|
38
|
+
|
|
39
|
+
it('${should}', () => {
|
|
40
|
+
expect(1).to.equal(1)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
})`);
|
|
44
|
+
await new Promise(async (resolve, reject) => await setTimeout(resolve, 1000));
|
|
45
|
+
await outBuffer.should.include(testFileName);
|
|
46
|
+
await outBuffer.should.include(thing);
|
|
47
|
+
await outBuffer.should.include(should);
|
|
48
|
+
await outBuffer.should.include(figures.tick);
|
|
49
|
+
});
|
|
50
|
+
await mochaIt('should run a test when a new test file is changed', async function () {
|
|
51
|
+
//this.timeout(100000)
|
|
52
|
+
const config = await Object.copy(baseConfig); // ignore the file from the last test, causing load errors
|
|
53
|
+
|
|
54
|
+
config.testsIgnore = '**/c.test.js'; // create a custom out function to capture the output
|
|
55
|
+
|
|
56
|
+
let outBuffer = '';
|
|
57
|
+
|
|
58
|
+
const customOut = (...texts) => {
|
|
59
|
+
outBuffer += texts.join(' ') + '\n'; // join and save to the buffer
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const testFileName = `${tempFolder}/b.test.js`;
|
|
63
|
+
const thing = 'Sample test 2';
|
|
64
|
+
const should = '2 should equal 2';
|
|
65
|
+
|
|
66
|
+
if (await existsSync(testFileName)) {
|
|
67
|
+
await fs.unlink(testFileName);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await (await existsSync(testFileName)).should.equal(false);
|
|
71
|
+
await watchFilesAndRunTestsChokidar(config, [], {}, [], customOut);
|
|
72
|
+
await outBuffer.should.equal('');
|
|
73
|
+
await fs.writeFile(testFileName, `import { describe } from '../../dist/index.js'
|
|
74
|
+
import { expect, should } from 'chai'
|
|
75
|
+
should()
|
|
76
|
+
|
|
77
|
+
describe('${thing}', ({it}) => {
|
|
78
|
+
|
|
79
|
+
it('${should}', () => {
|
|
80
|
+
expect(2).to.equal(2)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
})`);
|
|
84
|
+
await new Promise(async (resolve, reject) => await setTimeout(resolve, 1000));
|
|
85
|
+
await outBuffer.should.include(testFileName);
|
|
86
|
+
await outBuffer.should.include(thing);
|
|
87
|
+
await outBuffer.should.include(should);
|
|
88
|
+
await outBuffer.should.include(figures.tick);
|
|
89
|
+
});
|
|
90
|
+
await mochaIt('removes a test from tests and dependencyTrees when it is deleted', async function () {
|
|
91
|
+
//this.timeout(100000)
|
|
92
|
+
const config = await Object.copy(baseConfig); // create a custom out function to capture the output
|
|
93
|
+
|
|
94
|
+
let outBuffer = '';
|
|
95
|
+
|
|
96
|
+
const customOut = (...texts) => {
|
|
97
|
+
outBuffer += texts.join(' ') + '\n'; // join and save to the buffer
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const testFileName = `${tempFolder}/c.test.js`;
|
|
101
|
+
const thing = 'Sample test 3';
|
|
102
|
+
const should = '3 should equal 3';
|
|
103
|
+
|
|
104
|
+
if (!(await existsSync(testFileName))) {
|
|
105
|
+
await fs.writeFile(testFileName, `import { describe } from '../../dist/index.js'
|
|
106
|
+
import { expect, should } from 'chai'
|
|
107
|
+
should()
|
|
108
|
+
|
|
109
|
+
describe('${thing}', ({it}) => {
|
|
110
|
+
|
|
111
|
+
it('${should}', () => {
|
|
112
|
+
expect(3).to.equal(3)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
})`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
(await existsSync(testFileName)).should.be.true;
|
|
119
|
+
const originalTests = [testFileName];
|
|
120
|
+
const originalDependencyTrees = {
|
|
121
|
+
[testFileName]: ['dep-1.js', 'dep-2.js']
|
|
122
|
+
};
|
|
123
|
+
const tests = await Object.copy(originalTests);
|
|
124
|
+
const dependencyTrees = await Object.copy(originalDependencyTrees);
|
|
125
|
+
await tests.should.include.members([testFileName]);
|
|
126
|
+
await dependencyTrees.should.include.keys([testFileName]);
|
|
127
|
+
await watchFilesAndRunTestsChokidar(config, tests, dependencyTrees, [], customOut);
|
|
128
|
+
await fs.unlink(testFileName);
|
|
129
|
+
await new Promise(async (resolve, reject) => await setTimeout(resolve, 1000));
|
|
130
|
+
await tests.should.not.include.members([testFileName]);
|
|
131
|
+
await dependencyTrees.should.not.include.keys([testFileName]);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createTimeProfiler } from 'sxy-dev-tools';
|
|
2
|
+
import { showTimeProfiling } from '../../config.js';
|
|
3
|
+
import chalk from 'chalk-extensions';
|
|
4
|
+
import minimatch from 'minimatch';
|
|
5
|
+
import slash from 'slash'; //import watch from 'node-watch'
|
|
6
|
+
//import chokidar from 'chokidar'
|
|
7
|
+
//import nsfw from 'nsfw'
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import { out, log, debug } from '../../output.js';
|
|
11
|
+
import { runTests } from './runTests.js';
|
|
12
|
+
import { buildDependencyTree } from './buildDependencyTree.js';
|
|
13
|
+
import { globArgCombine } from './globArgCombine.js';
|
|
14
|
+
import { load } from './load.js';
|
|
15
|
+
const {
|
|
16
|
+
timeProfileAsync
|
|
17
|
+
} = await createTimeProfiler(showTimeProfiling);
|
|
18
|
+
import { addBasePath } from './addBasePath.js';
|
|
19
|
+
import { watchFilesNodeWatch } from './watchFilesNodeWatch.js';
|
|
20
|
+
export async function watchFilesAndRunTestsNodeWatch(config, testFiles, dependencyTrees, failingTests, customOut = undefined) {
|
|
21
|
+
const projectDir = await process.cwd();
|
|
22
|
+
let watcher;
|
|
23
|
+
await timeProfileAsync('init watching', async () => {
|
|
24
|
+
await debug('failingTests', failingTests);
|
|
25
|
+
const testsGlobs = await addBasePath(config.testsBase, config.tests);
|
|
26
|
+
const testsIgnoreGlobs = await addBasePath(config.testsBase, config.testsIgnore); // indentify a test file
|
|
27
|
+
|
|
28
|
+
const testFilter = //file => minimatch(file, globArgCombine(config.tests))
|
|
29
|
+
// && ! minimatch(file, globArgCombine(config.testsIgnore))
|
|
30
|
+
file => {
|
|
31
|
+
debug('test filter', file, globArgCombine(testsGlobs), globArgCombine(testsIgnoreGlobs), minimatch(file, globArgCombine(testsGlobs)), minimatch(file, globArgCombine(testsIgnoreGlobs)), minimatch(file, globArgCombine(testsGlobs)) && !minimatch(file, globArgCombine(testsIgnoreGlobs)));
|
|
32
|
+
return minimatch(file, globArgCombine(testsGlobs)) && !minimatch(file, globArgCombine(testsIgnoreGlobs));
|
|
33
|
+
}; //log('tests glob', globArgCombine(testsGlobs))
|
|
34
|
+
//log('testsIgnore glob', globArgCombine(testsIgnoreGlobs))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
let changedFiles = [];
|
|
38
|
+
let timeout = null;
|
|
39
|
+
|
|
40
|
+
if (!config.watch.watchFilesBase) {
|
|
41
|
+
await out(`No config.watch.watchFilesBase provided, defaulting to '.'`);
|
|
42
|
+
config.watch.watchFilesBase = '.';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
watcher = await watchFilesNodeWatch(config, watchEvent);
|
|
46
|
+
|
|
47
|
+
async function watchEvent(filename, event) {
|
|
48
|
+
await debug('handle watch event ', filename, event);
|
|
49
|
+
filename = await slash(filename); // remove dep tree for any tests that are deleted, avoiding trying to run it later
|
|
50
|
+
|
|
51
|
+
if (event === 'remove') {
|
|
52
|
+
await debug('removing test', filename);
|
|
53
|
+
|
|
54
|
+
if (filename in dependencyTrees) {
|
|
55
|
+
await testFiles.remove(filename);
|
|
56
|
+
delete dependencyTrees[filename];
|
|
57
|
+
}
|
|
58
|
+
} // info on the event found
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
await out(chalk.grey`change: ${filename} (${event})`); // add the file to changed files
|
|
62
|
+
|
|
63
|
+
await changedFiles.push([filename, event]); // wait 100ms after any change before processing, to avoid multiple execution?
|
|
64
|
+
|
|
65
|
+
if (timeout) {
|
|
66
|
+
await clearTimeout(timeout);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
timeout = await setTimeout(async () => {
|
|
70
|
+
await timeProfileAsync('handle changed files', async () => {
|
|
71
|
+
await debug('changedFiles', changedFiles);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// build related test list
|
|
75
|
+
const tests = [];
|
|
76
|
+
|
|
77
|
+
for (const [file, event] of changedFiles) {
|
|
78
|
+
await debug('check', file);
|
|
79
|
+
await debug('1 tests', tests);
|
|
80
|
+
|
|
81
|
+
if (event !== 'remove') {
|
|
82
|
+
// only check to see if its a test if it exists (has not just been removed)
|
|
83
|
+
if (await testFilter(file)) {
|
|
84
|
+
// it is a test file itself, add to run list
|
|
85
|
+
await debug('its a test', file);
|
|
86
|
+
await tests.push(file);
|
|
87
|
+
|
|
88
|
+
if (!(file in dependencyTrees)) {
|
|
89
|
+
// if its a new test
|
|
90
|
+
await debug('try to add a new test', file), await addNewTest(file);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await debug('dtrees', dependencyTrees);
|
|
96
|
+
await debug('2 tests', tests);
|
|
97
|
+
|
|
98
|
+
for (const f of await Object.getOwnPropertyNames(dependencyTrees)) {
|
|
99
|
+
await debug('dep tree', f, dependencyTrees[f]);
|
|
100
|
+
|
|
101
|
+
if (await dependencyTrees[f].some(dep => {
|
|
102
|
+
debug('checking dep', dep, file);
|
|
103
|
+
return dep === file;
|
|
104
|
+
})) {
|
|
105
|
+
// a test depends on this file
|
|
106
|
+
await debug('its a dependency', file);
|
|
107
|
+
await debug('its a dependency', file);
|
|
108
|
+
await tests.push(f);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
changedFiles = [];
|
|
114
|
+
await debug('tests', tests);
|
|
115
|
+
await debug('3 tests', tests);
|
|
116
|
+
|
|
117
|
+
if (tests.length) {
|
|
118
|
+
await timeProfileAsync('run tests related to changes', async () => {
|
|
119
|
+
await debug('tests', tests);
|
|
120
|
+
|
|
121
|
+
if (config.reRunFailingTests) {
|
|
122
|
+
for (const failingTest of failingTests) {
|
|
123
|
+
if (!(await fs.existsSync(failingTest))) {
|
|
124
|
+
//debug('remove failing test', failingTest)
|
|
125
|
+
await failingTests.remove(failingTest); //(failingTests)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await debug('tests', tests);
|
|
131
|
+
await debug('failingTests', failingTests);
|
|
132
|
+
await debug('reRunFailingTests', config.watch.reRunFailingTests);
|
|
133
|
+
const toTest = config.watch.reRunFailingTests ? await (await [...tests, ...failingTests].dedupeSlow()).reverse() // dedupe return new? // reverse in place
|
|
134
|
+
: await [...tests].reverse(); // reverse in place
|
|
135
|
+
|
|
136
|
+
const results = {
|
|
137
|
+
total: 0,
|
|
138
|
+
passed: 0
|
|
139
|
+
};
|
|
140
|
+
await debug('run tests on change');
|
|
141
|
+
await debug('toTest', toTest); // clear load cache to allow accesing the new tets
|
|
142
|
+
|
|
143
|
+
await load.cache.clear();
|
|
144
|
+
const testResults = await runTests(config, toTest, failingTests, customOut);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
await log('watch handler error', e);
|
|
149
|
+
} finally {//
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}, 33);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
await out(chalk.brightblue`watching...`); //return new Promise( () => {} ) // infinite promise to let us watch
|
|
156
|
+
|
|
157
|
+
async function addNewTest(testFile) {
|
|
158
|
+
await debug('new test', testFile);
|
|
159
|
+
const dependencyTree = await buildDependencyTree(config, testFile);
|
|
160
|
+
dependencyTrees[testFile] = await (await dependencyTree.map(async file => await (await slash(file)).slice(projectDir.length + 1))).filter(async f => f !== testFile);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { promises as fs, existsSync } from 'fs';
|
|
2
|
+
import figures from 'figures';
|
|
3
|
+
import { watchFilesAndRunTestsNodeWatch } from './watchFilesAndRunTestsNodeWatch.js';
|
|
4
|
+
import { applyConfigDefaults } from './applyConfigDefaults.js'; // watchFiles(config, testFiles, dependencyTrees, failingTests)
|
|
5
|
+
|
|
6
|
+
await mochaDescribe('watchFilesAndRunTestsNodeWatch() function', async function () {
|
|
7
|
+
const tempFolder = 'temp/watchFilesAndRunTestsNodeWatch';
|
|
8
|
+
const baseConfig = await applyConfigDefaults({
|
|
9
|
+
testsBase: tempFolder,
|
|
10
|
+
tests: '**/*.test.js',
|
|
11
|
+
watch: {
|
|
12
|
+
watchFilesBase: tempFolder,
|
|
13
|
+
watchFiles: '**/*.js'
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
await mochaIt('should run a test when a test file is changed', async function () {
|
|
17
|
+
//this.timeout(100000)
|
|
18
|
+
const config = await Object.copy(baseConfig); // create a custom out function to capture the output
|
|
19
|
+
|
|
20
|
+
let outBuffer = '';
|
|
21
|
+
|
|
22
|
+
const customOut = (...texts) => {
|
|
23
|
+
outBuffer += texts.join(' ') + '\n'; // join and save to the buffer
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
watchFilesAndRunTestsNodeWatch(config, [], {}, [], customOut); //console.log('outBuffer', outBuffer)
|
|
27
|
+
|
|
28
|
+
await outBuffer.should.equal('');
|
|
29
|
+
const testFileName = `${tempFolder}/a.test.js`;
|
|
30
|
+
const thing = 'Sample test 1';
|
|
31
|
+
const should = '1 should equal 1';
|
|
32
|
+
await fs.writeFile(testFileName, `import { describe } from '../../dist/index.js'
|
|
33
|
+
import { expect, should } from 'chai'
|
|
34
|
+
should()
|
|
35
|
+
|
|
36
|
+
describe('${thing}', ({it}) => {
|
|
37
|
+
|
|
38
|
+
it('${should}', () => {
|
|
39
|
+
expect(1).to.equal(1)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
})`);
|
|
43
|
+
await new Promise(async (resolve, reject) => await setTimeout(resolve, 1000));
|
|
44
|
+
await outBuffer.should.not.equal('');
|
|
45
|
+
await outBuffer.should.include(testFileName);
|
|
46
|
+
await outBuffer.should.include(thing);
|
|
47
|
+
await outBuffer.should.include(should);
|
|
48
|
+
await outBuffer.should.include(figures.tick);
|
|
49
|
+
});
|
|
50
|
+
await mochaIt('should run a test when a new test file is changed', async function () {
|
|
51
|
+
const config = await Object.copy(baseConfig); // create a custom out function to capture the output
|
|
52
|
+
|
|
53
|
+
let outBuffer = '';
|
|
54
|
+
|
|
55
|
+
const customOut = (...texts) => {
|
|
56
|
+
outBuffer += texts.join(' ') + '\n'; // join and save to the buffer
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const testFileName = `${tempFolder}/b.test.js`;
|
|
60
|
+
const thing = 'Sample test 2';
|
|
61
|
+
const should = '2 should equal 2';
|
|
62
|
+
|
|
63
|
+
if (await existsSync(testFileName)) {
|
|
64
|
+
await fs.unlink(testFileName);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await (await existsSync(testFileName)).should.equal(false);
|
|
68
|
+
await watchFilesAndRunTestsNodeWatch(config, [], {}, [], customOut);
|
|
69
|
+
await outBuffer.should.equal('');
|
|
70
|
+
await fs.writeFile(testFileName, `import { describe } from '../../dist/index.js'
|
|
71
|
+
import { expect, should } from 'chai'
|
|
72
|
+
should()
|
|
73
|
+
|
|
74
|
+
describe('${thing}', ({it}) => {
|
|
75
|
+
|
|
76
|
+
it('${should}', () => {
|
|
77
|
+
expect(2).to.equal(2)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
})`);
|
|
81
|
+
await new Promise(async (resolve, reject) => await setTimeout(resolve, 1000)); //console.log('out', outBuffer)
|
|
82
|
+
|
|
83
|
+
await outBuffer.should.include(testFileName);
|
|
84
|
+
await outBuffer.should.include(thing);
|
|
85
|
+
await outBuffer.should.include(should);
|
|
86
|
+
await outBuffer.should.include(figures.tick);
|
|
87
|
+
});
|
|
88
|
+
await mochaIt('removes a test from tests and dependencyTrees when it is deleted', async function () {
|
|
89
|
+
//this.timeout(100000)
|
|
90
|
+
const config = await Object.copy(baseConfig); // create a custom out function to capture the output
|
|
91
|
+
|
|
92
|
+
let outBuffer = '';
|
|
93
|
+
|
|
94
|
+
const customOut = (...texts) => {
|
|
95
|
+
outBuffer += texts.join(' ') + '\n'; // join and save to the buffer
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const testFileName = `${tempFolder}/c.test.js`;
|
|
99
|
+
const thing = 'Sample test 3';
|
|
100
|
+
const should = '3 should equal 3';
|
|
101
|
+
|
|
102
|
+
if (!(await existsSync(testFileName))) {
|
|
103
|
+
await fs.writeFile(testFileName, `import { describe } from '../../dist/index.js'
|
|
104
|
+
import { expect, should } from 'chai'
|
|
105
|
+
should()
|
|
106
|
+
|
|
107
|
+
describe('${thing}', ({it}) => {
|
|
108
|
+
|
|
109
|
+
it('${should}', () => {
|
|
110
|
+
expect(3).to.equal(3)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
})`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
(await existsSync(testFileName)).should.be.true;
|
|
117
|
+
const originalTests = [testFileName];
|
|
118
|
+
const originalDependencyTrees = {
|
|
119
|
+
[testFileName]: ['dep-1.js', 'dep-2.js']
|
|
120
|
+
};
|
|
121
|
+
const tests = await Object.copy(originalTests);
|
|
122
|
+
const dependencyTrees = await Object.copy(originalDependencyTrees);
|
|
123
|
+
await tests.should.include.members([testFileName]);
|
|
124
|
+
await dependencyTrees.should.include.keys([testFileName]);
|
|
125
|
+
await watchFilesAndRunTestsNodeWatch(config, tests, dependencyTrees, [], customOut);
|
|
126
|
+
await fs.unlink(testFileName);
|
|
127
|
+
await new Promise(async (resolve, reject) => await setTimeout(resolve, 1000));
|
|
128
|
+
await tests.should.not.include.members([testFileName]);
|
|
129
|
+
await dependencyTrees.should.not.include.keys([testFileName]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createTimeProfiler } from 'sxy-dev-tools';
|
|
2
|
+
import { showTimeProfiling } from '../../config.js';
|
|
3
|
+
import chalk from 'chalk-extensions';
|
|
4
|
+
import minimatch from 'minimatch';
|
|
5
|
+
import slash from 'slash'; //import watch from 'node-watch'
|
|
6
|
+
|
|
7
|
+
import chokidar from 'chokidar'; //import nsfw from 'nsfw'
|
|
8
|
+
|
|
9
|
+
import fs, { watch } from 'fs';
|
|
10
|
+
import { out, log, debug } from '../../output.js';
|
|
11
|
+
import { globArgCombine } from './globArgCombine.js';
|
|
12
|
+
import { addBasePath } from './addBasePath.js';
|
|
13
|
+
const {
|
|
14
|
+
timeProfileAsync
|
|
15
|
+
} = await createTimeProfiler(showTimeProfiling);
|
|
16
|
+
export async function watchFilesChokidar(config, onEvent) {
|
|
17
|
+
return await timeProfileAsync('start watching', async () => {
|
|
18
|
+
//const testGlobs = addBaseToGlobs(config.testsBase, config.tests)
|
|
19
|
+
//const ignoreGlobs = addBaseToGlobs(config.testsBase, config.testsIgnore)
|
|
20
|
+
// indentify a test file
|
|
21
|
+
const testFilter = file => minimatch(file, globArgCombine(config.tests)) && (!config.testsIgnore || !minimatch(file, globArgCombine(config.testsIgnore))); // identify a file we should be watching and responding to
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const watchFilter = file => (!config.watch.watchFiles || minimatch(file, globArgCombine(config.watch.watchFiles))) && (!config.watch.watchFilesIgnore || !minimatch(file, globArgCombine(config.watch.watchFilesIgnore)));
|
|
25
|
+
|
|
26
|
+
if (!config.watch.watchFilesBase) {
|
|
27
|
+
await out(`No config.watch.watchFilesBase provided, defaulting to '.'`);
|
|
28
|
+
config.watch.watchFilesBase = '.';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ignoreGlob = config.watch.watchFilesIgnore ? await addBasePath(config.watch.watchFilesBase, config.watch.watchFilesIgnore) : null; //log(`watch: ${config.watch.watchFilesBase}`)
|
|
32
|
+
// log(`ignore: ${ignoreGlob}`)
|
|
33
|
+
|
|
34
|
+
const watcher = await chokidar.watch(config.watch.watchFilesBase, {
|
|
35
|
+
ignored: ignoreGlob,
|
|
36
|
+
ignoreInitial: true
|
|
37
|
+
});
|
|
38
|
+
await watcher.on('all', async (event, filename) => {
|
|
39
|
+
//log('[watch event]', path, event)
|
|
40
|
+
filename = await slash(filename);
|
|
41
|
+
await debug('[watch event]', filename, event);
|
|
42
|
+
await onEvent(filename, event === 'unlink' ? 'remove' : event);
|
|
43
|
+
});
|
|
44
|
+
return new Promise(async (resolve, reject) => {
|
|
45
|
+
await watcher.on('ready', async () => {
|
|
46
|
+
//log('chokidar watcher ready')
|
|
47
|
+
await debug('chokidar watcher ready');
|
|
48
|
+
await resolve(watcher);
|
|
49
|
+
});
|
|
50
|
+
}); // watch(config.watch.watchFilesBase, { recursive: true, filter: watchFilter }, function(event, filename) {
|
|
51
|
+
// filename = slash(filename)
|
|
52
|
+
// debug('watch event')
|
|
53
|
+
// onEvent(filename, event)
|
|
54
|
+
// })
|
|
55
|
+
}); //return new Promise( () => {} ) // infinite promise to let us watch
|
|
56
|
+
}
|