Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 76 additions & 62 deletions lib/FindTests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const gracefulFs = require('graceful-fs');
const fs = require('fs');
const { globSync } = require('tinyglobby');
const path = require('path');
Expand Down Expand Up @@ -102,72 +101,87 @@ function findAllElmFilesInDir(dir) {
/**
* @param { Array<string> } testFilePaths
* @param { import('./Project').Project } project
* @returns { Promise<Array<{ moduleName: string, possiblyTests: Array<string> }>> }
* @returns { Array<{ moduleName: string, possiblyTests: Array<string> }> }
*/
function findTests(testFilePaths, project) {
return Promise.all(
testFilePaths.map((filePath) => {
const matchingSourceDirs = project.testsSourceDirs.filter((dir) =>
filePath.startsWith(`${dir}${path.sep}`)
);
// According to benchmarking in elm-watch, reading 2048 bytes at a time is the fastest:
// https://github.com/lydell/elm-watch/blob/6e2c2f8cf8f8e03a4886ca68e839d38e76fb7148/src/ImportWalker.ts#L114-L116
// For elm-test’s use cases, it doesn’t seem to matter as much.
const buffer = Buffer.alloc(2048);

return 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:
throw Error(
missingSourceDirectoryError(
filePath,
project.elmJson.type === 'package'
)
);

// 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)
case 1:
// Keep going.
break;

default:
// This shouldn’t be possible for package projects.
throw new Error(
multipleSourceDirectoriesError(
filePath,
matchingSourceDirs,
project.testsDir
)
);
}

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,
}));
})
);
}

// 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)) {
throw new Error(
badModuleNameError(filePath, matchingSourceDirs[0], moduleName)
);
}

const possiblyTests = Parser.extractExposedPossiblyTests(
filePath,
readChunked(buffer, filePath)
);

return {
moduleName,
possiblyTests,
};
});
}

/**
* @param { Buffer } buffer
* @param { string } filePath
* @returns { Generator<Buffer, void> }
*/
function* readChunked(buffer, filePath) {
const handle = fs.openSync(filePath, 'r');
let bytesRead = 0;
try {
while ((bytesRead = fs.readSync(handle, buffer)) > 0) {
// The last read might contain less bytes than we asked for.
yield buffer.subarray(0, bytesRead);
}
} finally {
fs.closeSync(handle);
}
}

/**
Expand Down
Loading