import { parse } from "../flags/mod.ts";import { ExpandGlobOptions, expandGlob } from "../fs/mod.ts";import { isWindows, join } from "../path/mod.ts";const { args, cwd, exit } = Deno;
const DIR_GLOBS = [join("**", "?(*_)test.{js,ts}")];
function showHelp(): void { console.log(`Deno test runner
USAGE: deno -A https://deno.land/std/testing/runner.ts [OPTIONS] [MODULES...]
OPTIONS: -q, --quiet Don't show output from test cases -f, --failfast Stop running tests on first error -e, --exclude <MODULES...> List of comma-separated modules to exclude --allow-none Exit with status 0 even when no test modules are found
ARGS: [MODULES...] List of test modules to run. A directory <dir> will expand to: ${DIR_GLOBS.map((s: string): string => `${join("<dir>", s)}`) .join(` `)} Defaults to "." when none are provided.
Note that modules can refer to file paths or URLs. File paths support globexpansion.
Examples: deno test src/**/*_test.ts deno test tests`);}
function isRemoteUrl(url: string): boolean { return /^https?:\/\//.test(url);}
function partition( arr: string[], callback: (el: string) => boolean): [string[], string[]] { return arr.reduce( (paritioned: [string[], string[]], el: string): [string[], string[]] => { paritioned[callback(el) ? 1 : 0].push(el); return paritioned; }, [[], []] );}
function filePathToUrl(path: string): string { return `file://${isWindows ? "/" : ""}${path.replace(/\\/g, "/")}`;}
export async function* findTestModules( includeModules: string[], excludeModules: string[], root: string = cwd()): AsyncIterableIterator<string> { const [includePaths, includeUrls] = partition(includeModules, isRemoteUrl); const [excludePaths, excludeUrls] = partition(excludeModules, isRemoteUrl);
const expandGlobOpts: ExpandGlobOptions = { root, exclude: excludePaths, includeDirs: true, extended: true, globstar: true };
async function* expandDirectory(d: string): AsyncIterableIterator<string> { for (const dirGlob of DIR_GLOBS) { for await (const walkInfo of expandGlob(dirGlob, { ...expandGlobOpts, root: d, includeDirs: false })) { yield filePathToUrl(walkInfo.filename); } } }
for (const globString of includePaths) { for await (const walkInfo of expandGlob(globString, expandGlobOpts)) { if (walkInfo.info.isDirectory()) { yield* expandDirectory(walkInfo.filename); } else { yield filePathToUrl(walkInfo.filename); } } }
const excludeUrlPatterns = excludeUrls.map( (url: string): RegExp => RegExp(url) ); const shouldIncludeUrl = (url: string): boolean => !excludeUrlPatterns.some((p: RegExp): boolean => !!url.match(p));
yield* includeUrls.filter(shouldIncludeUrl);}
export interface RunTestModulesOptions extends Deno.RunTestsOptions { include?: string[]; exclude?: string[]; allowNone?: boolean;}
function renderTestFile(testModules: string[]): string { let testFile = "";
for (const testModule of testModules) { testFile += 'import "' + testModule + '"\n'; }
return testFile;}
export async function runTestModules({ include = ["."], exclude = [], allowNone = false, exitOnFail = false, only = /[^\s]/, skip = /^\s*$/, disableLog = false}: RunTestModulesOptions = {}): Promise<void> { let moduleCount = 0; const testModules = []; for await (const testModule of findTestModules(include, exclude)) { testModules.push(testModule); moduleCount++; }
if (moduleCount == 0) { const noneFoundMessage = "No matching test modules found."; if (!allowNone) { throw new Deno.errors.NotFound(noneFoundMessage); } else if (!disableLog) { console.log(noneFoundMessage); } return; }
const testFile = renderTestFile(testModules); const root = Deno.env("DENO_DIR") || Deno.cwd(); const testFilePath = join(root, ".deno.test.ts"); const data = new TextEncoder().encode(testFile); await Deno.writeFile(testFilePath, data);
let err; try { await import(`file://${testFilePath}`); } catch (e) { err = e; } finally { await Deno.remove(testFilePath); }
if (err) { throw err; }
if (!disableLog) { console.log(`Found ${moduleCount} matching test modules.`); }
await Deno.runTests({ exitOnFail, only, skip, disableLog });}
async function main(): Promise<void> { const parsedArgs = parse(args, { boolean: ["allow-none", "failfast", "help", "quiet"], string: ["exclude"], alias: { exclude: ["e"], failfast: ["f"], help: ["h"], quiet: ["q"] }, default: { "allow-none": false, failfast: false, help: false, quiet: false } }); if (parsedArgs.help) { return showHelp(); }
const include = parsedArgs._.length > 0 ? (parsedArgs._ as string[]).flatMap((fileGlob: string): string[] => fileGlob.split(",") ) : ["."]; const exclude = parsedArgs.exclude != null ? (parsedArgs.exclude as string).split(",") : []; const allowNone = parsedArgs["allow-none"]; const exitOnFail = parsedArgs.failfast; const disableLog = parsedArgs.quiet;
try { await runTestModules({ include, exclude, allowNone, exitOnFail, disableLog }); } catch (error) { if (!disableLog) { console.error(error.message); } exit(1); }}
if (import.meta.main) { main();}