forked from rtfeldman/node-test-runner
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFindTests.js
More file actions
274 lines (232 loc) · 8.12 KB
/
FindTests.js
File metadata and controls
274 lines (232 loc) · 8.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// @flow
const gracefulFs = require('graceful-fs');
const fs = require('fs');
const { globSync } = require('tinyglobby');
const path = require('path');
const Parser = require('./Parser');
const Project = require('./Project');
void Project;
// Double stars at the start and end is the correct way to ignore directories in
// the `glob` package.
// https://github.com/isaacs/node-glob/issues/270#issuecomment-273949982
// https://github.com/isaacs/node-glob/blob/f5a57d3d6e19b324522a3fa5bdd5075fd1aa79d1/common.js#L222-L231
const ignoredDirsGlobs = ['**/elm-stuff/**', '**/node_modules/**'];
function resolveGlobs(
fileGlobs /*: Array<string> */,
projectRootDir /*: string */
) /*: Array<string> */ {
return Array.from(
new Set(
fileGlobs.flatMap((fileGlob) => {
const absolutePath = path.resolve(fileGlob);
try {
const stat = fs.statSync(absolutePath);
// If the CLI arg exists…
return stat.isDirectory()
? // …and it’s a directory, find all .elm files in there…
findAllElmFilesInDir(absolutePath)
: // …otherwise use it as-is.
[absolutePath];
} catch (error) {
// If the CLI arg does not exist…
return error.code === 'ENOENT'
? // …resolve it as a glob for shells that don’t support globs.
resolveCliArgGlob(absolutePath, projectRootDir)
: // The glob package ignores other types of stat errors.
[];
}
})
),
// The `glob` package returns absolute paths with slashes always, even on
// Windows. All other paths in elm-test use the native directory separator
// so normalize here.
(filePath) => path.normalize(filePath)
);
}
function resolveCliArgGlob(
fileGlob /*: string */,
projectRootDir /*: string */
) /*: Array<string> */ {
// Globs passed as CLI arguments are relative to CWD, while elm-test
// operates from the project root dir.
const globRelativeToProjectRoot = path.relative(
projectRootDir,
path.resolve(fileGlob)
);
// glob@8 (via minimatch@5) had a breaking change where you _have_ to use
// forwards slash as path separator, regardless of platform, making it
// unambiguous which characters are separators and which are escapes. This
// restores the previous behavior, avoiding a breaking change in elm-test.
// Note: As far I can tell, escaping glob syntax has _never_ worked on
// Windows. In Elm, needing to escape glob syntax should be very rare, since
// Elm file paths must match the module name (letters only). So it’s probably
// more worth supporting `some\folder\*Test.elm` rather than escaping.
// https://github.com/isaacs/node-glob/issues/468
// https://github.com/isaacs/minimatch/commit/9104d8d175bdd8843338103be1401f80774d2a10#diff-f41746899d033115e03bebe4fbde76acf2de4bf261bfb221744808f4c8a286cf
const pattern =
process.platform === 'win32'
? globRelativeToProjectRoot.replace(/\\/g, '/')
: globRelativeToProjectRoot;
return globSync(pattern, {
cwd: projectRootDir,
caseSensitiveMatch: false,
absolute: true,
ignore: ignoredDirsGlobs,
// Match directories as well
onlyFiles: false,
}).flatMap((filePath) =>
// Directories have their path end with `/`
filePath.endsWith('/') ? findAllElmFilesInDir(filePath) : filePath
);
}
// Recursively search for *.elm files.
function findAllElmFilesInDir(dir /*: string */) /*: Array<string> */ {
return globSync('**/*.elm', {
cwd: dir,
caseSensitiveMatch: false,
absolute: true,
ignore: ignoredDirsGlobs,
onlyFiles: true,
});
}
function findTests(
testFilePaths /*: Array<string> */,
project /*: typeof Project.Project */
) /*: Promise<Array<{ moduleName: string, possiblyTests: Array<string> }>> */ {
return Promise.all(
testFilePaths.map((filePath) => {
const matchingSourceDirs = project.testsSourceDirs.filter((dir) =>
filePath.startsWith(`${dir}${path.sep}`)
);
// Tests must be in tests/ or in source-directories – otherwise they won’t
// compile. Elm won’t be able to find imports.
switch (matchingSourceDirs.length) {
case 0:
return Promise.reject(
Error(
missingSourceDirectoryError(
filePath,
project.elmJson.type === 'package'
)
)
);
case 1:
// Keep going.
break;
default:
// This shouldn’t be possible for package projects.
return Promise.reject(
new Error(
multipleSourceDirectoriesError(
filePath,
matchingSourceDirs,
project.testsDir
)
)
);
}
// By finding the module name from the file path we can import it even if
// the file is full of errors. Elm will then report what’s wrong.
const moduleNameParts = path
.relative(matchingSourceDirs[0], filePath)
.replace(/\.elm$/, '')
.split(path.sep);
const moduleName = moduleNameParts.join('.');
if (!moduleNameParts.every(Parser.isUpperName)) {
return Promise.reject(
new Error(
badModuleNameError(filePath, matchingSourceDirs[0], moduleName)
)
);
}
return Parser.extractExposedPossiblyTests(
filePath,
// We’re reading files asynchronously in a loop here, so it makes sense
// to use graceful-fs to avoid “too many open files” errors.
gracefulFs.createReadStream
).then((possiblyTests) => ({
moduleName,
possiblyTests,
}));
})
);
}
function missingSourceDirectoryError(filePath, isPackageProject) {
return `
This file:
${filePath}
…matches no source directory! Imports won't work then.
${
isPackageProject
? 'Move it to tests/ or src/ in your project root.'
: 'Move it to tests/ in your project root, or make sure it is covered by "source-directories" in your elm.json.'
}
`.trim();
}
function multipleSourceDirectoriesError(
filePath,
matchingSourceDirs,
testsDir
) {
const note = matchingSourceDirs.includes(testsDir)
? "Note: The tests/ folder counts as a source directory too (even if it isn't listed in your elm.json)!"
: '';
return `
This file:
${filePath}
…matches more than one source directory:
${matchingSourceDirs.join('\n')}
Edit "source-directories" in your elm.json and try to make it so no source directory contains another source directory!
${note}
`.trim();
}
function badModuleNameError(filePath, sourceDir, moduleName) {
return `
This file:
${filePath}
…located in this directory:
${sourceDir}
…is problematic. Trying to construct a module name from the parts after the directory gives:
${moduleName}
…but module names need to look like for example:
Main
Http.Helpers
Make sure that all parts start with an uppercase letter and don't contain any spaces or anything like that.
`.trim();
}
function noFilesFoundError(
projectRootDir /*: string */,
testFileGlobs /*: Array<string> */
) /*: string */ {
return testFileGlobs.length === 0
? `
${noFilesFoundInTestsDir(projectRootDir)}
To generate some initial tests to get things going: elm-test init
Alternatively, if your project has tests in a different directory,
try calling elm-test with a glob such as: elm-test "src/**/*Tests.elm"
`.trim()
: `
No files found matching:
${testFileGlobs.join('\n')}
Are the above patterns correct? Maybe try running elm-test with no arguments?
`.trim();
}
function noFilesFoundInTestsDir(projectRootDir) {
const testsDir = path.join(projectRootDir, 'tests');
try {
const stats = fs.statSync(testsDir);
return stats.isDirectory()
? 'No .elm files found in the tests/ directory.'
: `Expected a directory but found something else at: ${testsDir}\nCheck it out! Could you remove it?`;
} catch (error) {
return error.code === 'ENOENT'
? 'The tests/ directory does not exist.'
: `Failed to read the tests/ directory: ${error.message}`;
}
}
module.exports = {
findTests,
ignoredDirsGlobs,
noFilesFoundError,
resolveGlobs,
};