diff options
author | Nayeem Rahman <muhammed.9939@gmail.com> | 2019-09-28 14:33:17 +0100 |
---|---|---|
committer | Ryan Dahl <ry@tinyclouds.org> | 2019-09-28 09:33:17 -0400 |
commit | a472b6732dd37636b7b31128f53d3e6bcf531a73 (patch) | |
tree | 11fa3fb56917582c0c88c871e71ff212b39eb841 | |
parent | af18093498c3fca103bd47305e447ddeda40d9a2 (diff) |
Test runner v2 (denoland/deno_std#604)
Original: https://github.com/denoland/deno_std/commit/17a214bbd5b3a058a8126e9f7210992b1b52ba11
-rw-r--r-- | .ci/template.common.yml | 4 | ||||
-rw-r--r-- | fs/glob.ts | 67 | ||||
-rw-r--r-- | fs/glob_test.ts | 88 | ||||
-rw-r--r-- | fs/globrex.ts | 46 | ||||
-rw-r--r-- | fs/path/constants.ts | 1 | ||||
-rw-r--r-- | fs/testdata/glob/abc | 0 | ||||
-rw-r--r-- | fs/testdata/glob/abcdef | 0 | ||||
-rw-r--r-- | fs/testdata/glob/abcdefghi | 0 | ||||
-rw-r--r-- | fs/testdata/glob/subdir/abc | 0 | ||||
-rw-r--r-- | testing/mod.ts | 6 | ||||
-rwxr-xr-x | testing/runner.ts | 300 | ||||
-rw-r--r-- | testing/runner_test.ts | 94 | ||||
-rw-r--r-- | testing/testdata/bar.js | 1 | ||||
-rw-r--r-- | testing/testdata/bar_test.js | 1 | ||||
-rw-r--r-- | testing/testdata/foo.ts | 1 | ||||
-rw-r--r-- | testing/testdata/foo_test.ts | 1 | ||||
-rw-r--r-- | testing/testdata/subdir/bar.js | 1 | ||||
-rw-r--r-- | testing/testdata/subdir/bar_test.js | 1 | ||||
-rw-r--r-- | testing/testdata/subdir/foo.ts | 1 | ||||
-rw-r--r-- | testing/testdata/subdir/foo_test.ts | 1 | ||||
-rw-r--r-- | testing/testdata/subdir/test.js | 1 | ||||
-rw-r--r-- | testing/testdata/subdir/test.ts | 1 | ||||
-rw-r--r-- | testing/testdata/test.js | 1 | ||||
-rw-r--r-- | testing/testdata/test.ts | 1 | ||||
-rw-r--r-- | xeval/test.ts | 2 |
25 files changed, 449 insertions, 171 deletions
diff --git a/.ci/template.common.yml b/.ci/template.common.yml index 19ac964a0..456ecc4bb 100644 --- a/.ci/template.common.yml +++ b/.ci/template.common.yml @@ -4,5 +4,5 @@ parameters: steps: - bash: deno${{ parameters.exe_suffix }} run --allow-run --allow-write --allow-read --allow-env ./format.ts --check - bash: export START_TIME=$(date +%s) - - bash: deno${{ parameters.exe_suffix }} run --allow-run --allow-net --allow-write --allow-read --allow-env --config=tsconfig.test.json ./testing/runner.ts --exclude node_modules - - bash: deno${{ parameters.exe_suffix }} run --allow-run --allow-read .ci/check_source_file_changes.ts $START_TIME
\ No newline at end of file + - bash: deno${{ parameters.exe_suffix }} run --allow-run --allow-net --allow-write --allow-read --allow-env --config=tsconfig.test.json ./testing/runner.ts --exclude node_modules,**/testdata + - bash: deno${{ parameters.exe_suffix }} run --allow-run --allow-read .ci/check_source_file_changes.ts $START_TIME diff --git a/fs/glob.ts b/fs/glob.ts index 506d4012c..b974f2b29 100644 --- a/fs/glob.ts +++ b/fs/glob.ts @@ -1,4 +1,7 @@ import { globrex } from "./globrex.ts"; +import { isAbsolute, join } from "./path/mod.ts"; +import { WalkInfo, walk, walkSync } from "./walk.ts"; +const { cwd } = Deno; export interface GlobOptions { // Allow ExtGlob features @@ -41,7 +44,11 @@ export interface GlobOptions { * @returns A RegExp for the glob pattern */ export function glob(glob: string, options: GlobOptions = {}): RegExp { - return globrex(glob, options).regex; + const result = globrex(glob, options); + if (options.filepath) { + return result.path!.regex; + } + return result.regex; } /** Test whether the given string is a glob */ @@ -76,3 +83,61 @@ export function isGlob(str: string): boolean { return false; } + +export interface ExpandGlobOptions extends GlobOptions { + root?: string; + includeDirs?: boolean; +} + +/** + * Expand the glob string from the specified `root` directory and yield each + * result as a `WalkInfo` object. + */ +// TODO: Use a proper glob expansion algorithm. +// This is a very incomplete solution. The whole directory tree from `root` is +// walked and parent paths are not supported. +export async function* expandGlob( + globString: string, + { + root = cwd(), + includeDirs = true, + extended = false, + globstar = false, + strict = false, + filepath = true, + flags = "" + }: ExpandGlobOptions = {} +): AsyncIterableIterator<WalkInfo> { + const absoluteGlob = isAbsolute(globString) + ? globString + : join(root, globString); + const globOptions = { extended, globstar, strict, filepath, flags }; + yield* walk(root, { + match: [glob(absoluteGlob, globOptions)], + includeDirs + }); +} + +/** Synchronous version of `expandGlob()`. */ +// TODO: As `expandGlob()`. +export function* expandGlobSync( + globString: string, + { + root = cwd(), + includeDirs = true, + extended = false, + globstar = false, + strict = false, + filepath = true, + flags = "" + }: ExpandGlobOptions = {} +): IterableIterator<WalkInfo> { + const absoluteGlob = isAbsolute(globString) + ? globString + : join(root, globString); + const globOptions = { extended, globstar, strict, filepath, flags }; + yield* walkSync(root, { + match: [glob(absoluteGlob, globOptions)], + includeDirs + }); +} diff --git a/fs/glob_test.ts b/fs/glob_test.ts index 9151b9e9e..0d180eafd 100644 --- a/fs/glob_test.ts +++ b/fs/glob_test.ts @@ -1,9 +1,16 @@ -const { mkdir } = Deno; -type FileInfo = Deno.FileInfo; +const { cwd, mkdir } = Deno; import { test, runIfMain } from "../testing/mod.ts"; import { assert, assertEquals } from "../testing/asserts.ts"; -import { glob, isGlob } from "./glob.ts"; -import { join } from "./path.ts"; +import { isWindows } from "./path/constants.ts"; +import { + ExpandGlobOptions, + expandGlob, + glob, + isGlob, + expandGlobSync +} from "./glob.ts"; +import { join, normalize, relative } from "./path.ts"; +import { WalkInfo } from "./walk.ts"; import { testWalk } from "./walk_test.ts"; import { touch, walkArray } from "./walk_test.ts"; @@ -131,7 +138,6 @@ testWalk( const arr = await walkArray(".", { match: [glob("x.*", { flags: "g", globstar: true })] }); - console.log(arr); assertEquals(arr.length, 2); assertEquals(arr[0], "x.js"); assertEquals(arr[1], "x.ts"); @@ -253,4 +259,76 @@ test({ } }); +async function expandGlobArray( + globString: string, + options: ExpandGlobOptions +): Promise<string[]> { + const infos: WalkInfo[] = []; + for await (const info of expandGlob(globString, options)) { + infos.push(info); + } + infos.sort(); + const infosSync = [...expandGlobSync(globString, options)]; + infosSync.sort(); + assertEquals(infos, infosSync); + const root = normalize(options.root || cwd()); + const paths = infos.map(({ filename }): string => filename); + for (const path of paths) { + assert(path.startsWith(root)); + } + const relativePaths = paths.map((path: string): string => + relative(root, path) + ); + relativePaths.sort(); + return relativePaths; +} + +function urlToFilePath(url: URL): string { + // Since `new URL('file:///C:/a').pathname` is `/C:/a`, remove leading slash. + return url.pathname.slice(url.protocol == "file:" && isWindows ? 1 : 0); +} + +const EG_OPTIONS = { + root: urlToFilePath(new URL(join("testdata", "glob"), import.meta.url)), + includeDirs: true, + extended: false, + globstar: false, + strict: false, + filepath: false, + flags: "" +}; + +test(async function expandGlobExt(): Promise<void> { + const options = { ...EG_OPTIONS, extended: true }; + assertEquals(await expandGlobArray("abc?(def|ghi)", options), [ + "abc", + "abcdef" + ]); + assertEquals(await expandGlobArray("abc*(def|ghi)", options), [ + "abc", + "abcdef", + "abcdefghi" + ]); + assertEquals(await expandGlobArray("abc+(def|ghi)", options), [ + "abcdef", + "abcdefghi" + ]); + assertEquals(await expandGlobArray("abc@(def|ghi)", options), ["abcdef"]); + assertEquals(await expandGlobArray("abc{def,ghi}", options), ["abcdef"]); + assertEquals(await expandGlobArray("abc!(def|ghi)", options), ["abc"]); +}); + +test(async function expandGlobGlobstar(): Promise<void> { + const options = { ...EG_OPTIONS, globstar: true }; + assertEquals(await expandGlobArray(join("**", "abc"), options), [ + "abc", + join("subdir", "abc") + ]); +}); + +test(async function expandGlobIncludeDirs(): Promise<void> { + const options = { ...EG_OPTIONS, includeDirs: false }; + assertEquals(await expandGlobArray("subdir", options), []); +}); + runIfMain(import.meta); diff --git a/fs/globrex.ts b/fs/globrex.ts index 32a129a67..e382dc82e 100644 --- a/fs/globrex.ts +++ b/fs/globrex.ts @@ -5,17 +5,18 @@ import { GlobOptions } from "./glob.ts"; const isWin = Deno.build.os === "win"; -const SEP = isWin ? `\\\\+` : `\\/`; +const SEP = isWin ? `(\\\\+|\\/)` : `\\/`; const SEP_ESC = isWin ? `\\\\` : `/`; -const GLOBSTAR = `((?:[^/]*(?:/|$))*)`; -const WILDCARD = `([^/]*)`; -const GLOBSTAR_SEGMENT = `((?:[^${SEP_ESC}]*(?:${SEP_ESC}|$))*)`; -const WILDCARD_SEGMENT = `([^${SEP_ESC}]*)`; +const SEP_RAW = isWin ? `\\` : `/`; +const GLOBSTAR = `((?:[^${SEP_ESC}/]*(?:${SEP_ESC}|\/|$))*)`; +const WILDCARD = `([^${SEP_ESC}/]*)`; +const GLOBSTAR_SEGMENT = `((?:[^${SEP_ESC}/]*(?:${SEP_ESC}|\/|$))*)`; +const WILDCARD_SEGMENT = `([^${SEP_ESC}/]*)`; export interface GlobrexResult { regex: RegExp; path?: { - regex: string | RegExp; + regex: RegExp; segments: RegExp[]; globstar?: RegExp; }; @@ -44,11 +45,8 @@ export function globrex( ): GlobrexResult { let regex = ""; let segment = ""; - let path: { - regex: string | RegExp; - segments: RegExp[]; - globstar?: RegExp; - } = { regex: "", segments: [] }; + let pathRegexStr = ""; + const pathSegments = []; // If we are doing extended matching, this boolean is true when we are inside // a group (eg {*.html,*.js}), and false otherwise. @@ -72,13 +70,13 @@ export function globrex( const { split, last, only } = options; if (only !== "path") regex += str; if (filepath && only !== "regex") { - path.regex += str === "\\/" ? SEP : str; + pathRegexStr += str.match(new RegExp(`^${SEP}$`)) ? SEP : str; if (split) { if (last) segment += str; if (segment !== "") { // change it 'includes' if (!flags.includes("g")) segment = `^${segment}$`; - path.segments.push(new RegExp(segment, flags)); + pathSegments.push(new RegExp(segment, flags)); } segment = ""; } else { @@ -267,9 +265,9 @@ export function globrex( let isGlobstar = starCount > 1 && // multiple "*"'s // from the start of the segment - (prevChar === "/" || prevChar === undefined) && + [SEP_RAW, "/", undefined].includes(prevChar) && // to the end of the segment - (nextChar === "/" || nextChar === undefined); + [SEP_RAW, "/", undefined].includes(nextChar); if (isGlobstar) { // it's a globstar, so match zero or more path segments add(GLOBSTAR, { only: "regex" }); @@ -292,20 +290,22 @@ export function globrex( if (!flags.includes("g")) { regex = `^${regex}$`; segment = `^${segment}$`; - if (filepath) path.regex = `^${path.regex}$`; + if (filepath) pathRegexStr = `^${pathRegexStr}$`; } const result: GlobrexResult = { regex: new RegExp(regex, flags) }; // Push the last segment if (filepath) { - path.segments.push(new RegExp(segment, flags)); - path.regex = new RegExp(path.regex.toString(), flags); - path.globstar = new RegExp( - !flags.includes("g") ? `^${GLOBSTAR_SEGMENT}$` : GLOBSTAR_SEGMENT, - flags - ); - result.path = path; + pathSegments.push(new RegExp(segment, flags)); + result.path = { + regex: new RegExp(pathRegexStr, flags), + segments: pathSegments, + globstar: new RegExp( + !flags.includes("g") ? `^${GLOBSTAR_SEGMENT}$` : GLOBSTAR_SEGMENT, + flags + ) + }; } return result; diff --git a/fs/path/constants.ts b/fs/path/constants.ts index d70bb8073..55851f8cc 100644 --- a/fs/path/constants.ts +++ b/fs/path/constants.ts @@ -50,3 +50,4 @@ export const CHAR_9 = 57; /* 9 */ export const isWindows = build.os === "win"; export const EOL = isWindows ? "\r\n" : "\n"; +export const SEP = isWindows ? "\\" : "/"; diff --git a/fs/testdata/glob/abc b/fs/testdata/glob/abc new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/fs/testdata/glob/abc diff --git a/fs/testdata/glob/abcdef b/fs/testdata/glob/abcdef new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/fs/testdata/glob/abcdef diff --git a/fs/testdata/glob/abcdefghi b/fs/testdata/glob/abcdefghi new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/fs/testdata/glob/abcdefghi diff --git a/fs/testdata/glob/subdir/abc b/fs/testdata/glob/subdir/abc new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/fs/testdata/glob/subdir/abc diff --git a/testing/mod.ts b/testing/mod.ts index c8577770b..329e02895 100644 --- a/testing/mod.ts +++ b/testing/mod.ts @@ -348,7 +348,7 @@ async function runTestsSerial( } /** Defines options for controlling execution details of a test suite. */ -export interface RunOptions { +export interface RunTestsOptions { parallel?: boolean; exitOnFail?: boolean; only?: RegExp; @@ -368,7 +368,7 @@ export async function runTests({ only = /[^\s]/, skip = /^\s*$/, disableLog = false -}: RunOptions = {}): Promise<void> { +}: RunTestsOptions = {}): Promise<void> { const tests: TestDefinition[] = candidates.filter( ({ name }): boolean => only.test(name) && !skip.test(name) ); @@ -415,7 +415,7 @@ export async function runTests({ */ export async function runIfMain( meta: ImportMeta, - opts?: RunOptions + opts?: RunTestsOptions ): Promise<void> { if (meta.main) { return runTests(opts); diff --git a/testing/runner.ts b/testing/runner.ts index 414fb1f56..b0357d8e4 100755 --- a/testing/runner.ts +++ b/testing/runner.ts @@ -1,44 +1,46 @@ #!/usr/bin/env -S deno -A // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import { parse } from "../flags/mod.ts"; -import { glob, isGlob, walk } from "../fs/mod.ts"; -import { runTests } from "./mod.ts"; -const { args, cwd } = Deno; - -const DEFAULT_GLOBS = [ - "**/*_test.ts", - "**/*_test.js", - "**/test.ts", - "**/test.js" -]; - -/* eslint-disable max-len */ +import { + WalkInfo, + expandGlobSync, + glob, + ExpandGlobOptions +} from "../fs/mod.ts"; +import { isWindows } from "../fs/path/constants.ts"; +import { isAbsolute, join } from "../fs/path/mod.ts"; +import { RunTestsOptions, runTests } from "./mod.ts"; +const { DenoError, ErrorKind, 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] [FILES...] + deno -A https://deno.land/std/testing/runner.ts [OPTIONS] [MODULES...] OPTIONS: - -q, --quiet Don't show output from test cases - -f, --failfast Stop test suite on first error - -e, --exclude <FILES...> List of file names to exclude from run. If this options is - used files to match must be specified after "--". - -ARGS: - [FILES...] List of file names to run. Defaults to: ${DEFAULT_GLOBS.join( - "," - )} -`); -} -/* eslint-enable max-len */ + -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 -function filePathToRegExp(str: string): RegExp { - if (isGlob(str)) { - return glob(str, { flags: "g" }); - } - - return RegExp(str, "g"); +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 glob +expansion. + +Examples: + deno test src/**/*_test.ts + deno test tests`); } function isRemoteUrl(url: string): boolean { @@ -58,112 +60,184 @@ function partition( ); } +function filePathToUrl(path: string): string { + return `file://${isWindows ? "/" : ""}${path.replace(/\\/g, "/")}`; +} + +function expandDirectory(dir: string, options: ExpandGlobOptions): WalkInfo[] { + return DIR_GLOBS.flatMap((s: string): WalkInfo[] => [ + ...expandGlobSync(s, { ...options, root: dir }) + ]); +} + /** - * Given list of globs or URLs to include and exclude and root directory return - * list of file URLs that should be imported for test runner. + * Given a list of globs or URLs to include and exclude and a root directory + * from which to expand relative globs, return a list of URLs + * (file: or remote) that should be imported for the test runner. */ -export async function getMatchingUrls( - matchPaths: string[], - excludePaths: string[], - root: string +export async function findTestModules( + includeModules: string[], + excludeModules: string[], + root: string = cwd() ): Promise<string[]> { - const [includeLocal, includeRemote] = partition(matchPaths, isRemoteUrl); - const [excludeLocal, excludeRemote] = partition(excludePaths, isRemoteUrl); - - const localFileIterator = walk(root, { - match: includeLocal.map((f: string): RegExp => filePathToRegExp(f)), - skip: excludeLocal.map((f: string): RegExp => filePathToRegExp(f)) - }); - - let matchingLocalUrls: string[] = []; - for await (const { filename } of localFileIterator) { - matchingLocalUrls.push(`file://${filename}`); - } - - const excludeRemotePatterns = excludeRemote.map( - (url: string): RegExp => RegExp(url) + const [includePaths, includeUrls] = partition(includeModules, isRemoteUrl); + const [excludePaths, excludeUrls] = partition(excludeModules, isRemoteUrl); + + const expandGlobOpts = { + root, + extended: true, + globstar: true, + filepath: true + }; + + // TODO: We use the `g` flag here to support path prefixes when specifying + // excludes. Replace with a solution that does this more correctly. + const excludePathPatterns = excludePaths.map( + (s: string): RegExp => + glob(isAbsolute(s) ? s : join(root, s), { ...expandGlobOpts, flags: "g" }) ); - const matchingRemoteUrls = includeRemote.filter( - (candidateUrl: string): boolean => { - return !excludeRemotePatterns.some((pattern: RegExp): boolean => { - const r = pattern.test(candidateUrl); - pattern.lastIndex = 0; - return r; - }); - } + const excludeUrlPatterns = excludeUrls.map( + (url: string): RegExp => RegExp(url) ); + const notExcludedPath = ({ filename }: WalkInfo): boolean => + !excludePathPatterns.some((p: RegExp): boolean => !!filename.match(p)); + const notExcludedUrl = (url: string): boolean => + !excludeUrlPatterns.some((p: RegExp): boolean => !!url.match(p)); + + const matchedPaths = includePaths + .flatMap((s: string): WalkInfo[] => [...expandGlobSync(s, expandGlobOpts)]) + .filter(notExcludedPath) + .flatMap(({ filename, info }): string[] => + info.isDirectory() + ? expandDirectory(filename, { ...expandGlobOpts, includeDirs: false }) + .filter(notExcludedPath) + .map(({ filename }): string => filename) + : [filename] + ); + + const matchedUrls = includeUrls.filter(notExcludedUrl); + + return [...matchedPaths.map(filePathToUrl), ...matchedUrls]; +} - return matchingLocalUrls.concat(matchingRemoteUrls); +export interface RunTestModulesOptions extends RunTestsOptions { + include?: string[]; + exclude?: string[]; + allowNone?: boolean; } + /** - * This function runs matching test files in `root` directory. + * Import the specified test modules and run their tests as a suite. + * + * Test modules are specified as an array of strings and can include local files + * or URLs. * - * File matching and excluding supports glob syntax, ie. if encountered arg is - * a glob it will be expanded using `glob` method from `fs` module. + * File matching and excluding support glob syntax - arguments recognized as + * globs will be expanded using `glob()` from the `fs` module. * - * Note that your shell may expand globs for you: - * $ deno -A ./runner.ts **\/*_test.ts **\/test.ts + * Example: * - * Expanding using `fs.glob`: - * $ deno -A ./runner.ts \*\*\/\*_test.ts \*\*\/test.ts + * runTestModules({ include: ["**\/*_test.ts", "**\/test.ts"] }); * - * `**\/*_test.ts` and `**\/test.ts"` are arguments that will be parsed and - * expanded as: [glob("**\/*_test.ts"), glob("**\/test.ts")] + * Any matched directory `<dir>` will expand to: + * <dir>/**\/?(*_)test.{js,ts} + * + * So the above example is captured naturally by: + * + * runTestModules({ include: ["."] }); + * + * Which is the default used for: + * + * runTestModules(); */ -// TODO: change return type to `Promise<void>` once, `runTests` is updated -// to return boolean instead of exiting -export async function main(root: string = cwd()): Promise<void> { - const parsedArgs = parse(args.slice(1), { - boolean: ["quiet", "failfast", "help"], - string: ["exclude"], - alias: { - help: ["h"], - quiet: ["q"], - failfast: ["f"], - exclude: ["e"] +// TODO: Change return type to `Promise<void>` once, `runTests` is updated +// to return boolean instead of exiting. +export async function runTestModules({ + include = ["."], + exclude = [], + allowNone = false, + parallel = false, + exitOnFail = false, + only = /[^\s]/, + skip = /^\s*$/, + disableLog = false +}: RunTestModulesOptions = {}): Promise<void> { + const testModuleUrls = await findTestModules(include, exclude); + + if (testModuleUrls.length == 0) { + const noneFoundMessage = "No matching test modules found."; + if (!allowNone) { + throw new DenoError(ErrorKind.NotFound, noneFoundMessage); + } else if (!disableLog) { + console.log(noneFoundMessage); } - }); - - if (parsedArgs.help) { - return showHelp(); + return; } - let includeFiles: string[]; - let excludeFiles: string[]; - - if (parsedArgs._.length) { - includeFiles = (parsedArgs._ as string[]) - .map((fileGlob: string): string[] => { - return fileGlob.split(","); - }) - .flat(); - } else { - includeFiles = DEFAULT_GLOBS; + if (!disableLog) { + console.log(`Found ${testModuleUrls.length} matching test modules.`); } - if (parsedArgs.exclude) { - excludeFiles = (parsedArgs.exclude as string).split(","); - } else { - excludeFiles = []; + for (const url of testModuleUrls) { + await import(url); } - const foundTestUrls = await getMatchingUrls(includeFiles, excludeFiles, root); + await runTests({ + parallel, + exitOnFail, + only, + skip, + disableLog + }); +} - if (foundTestUrls.length === 0) { - console.error("No matching test files found."); - return; +async function main(): Promise<void> { + const parsedArgs = parse(args.slice(1), { + 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(); } - console.log(`Found ${foundTestUrls.length} matching test files.`); - - for (const url of foundTestUrls) { - await import(url); + 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); } - - await runTests({ - exitOnFail: !!parsedArgs.failfast, - disableLog: !!parsedArgs.quiet - }); } if (import.meta.main) { diff --git a/testing/runner_test.ts b/testing/runner_test.ts index 0e2289187..9b6214918 100644 --- a/testing/runner_test.ts +++ b/testing/runner_test.ts @@ -1,30 +1,78 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import { test } from "./mod.ts"; +import { findTestModules } from "./runner.ts"; +import { isWindows } from "../fs/path/constants.ts"; import { assertEquals } from "../testing/asserts.ts"; -import { getMatchingUrls } from "./runner.ts"; -import { join } from "../fs/path/mod.ts"; - -/** - * IMPORTANT: This file assumes it is run from root of repository. - */ -const cwd = Deno.cwd(); -const TEST_ROOT_PATH = join(cwd, "fmt"); - -test(async function getMatchingUrlsRemote(): Promise<void> { - const matches = [ - "https://deno.land/std/fmt/colors_test.ts", - "http://deno.land/std/fmt/printf_test.ts" - ]; - const urls = await getMatchingUrls(matches, [], TEST_ROOT_PATH); - assertEquals(urls, matches); +function urlToFilePath(url: URL): string { + // Since `new URL('file:///C:/a').pathname` is `/C:/a`, remove leading slash. + return url.pathname.slice(url.protocol == "file:" && isWindows ? 1 : 0); +} + +const TEST_DATA_URL = new URL("testdata", import.meta.url); +const TEST_DATA_PATH = urlToFilePath(TEST_DATA_URL); + +test(async function findTestModulesDir1(): Promise<void> { + const urls = await findTestModules(["."], [], TEST_DATA_PATH); + assertEquals(urls.sort(), [ + `${TEST_DATA_URL}/bar_test.js`, + `${TEST_DATA_URL}/foo_test.ts`, + `${TEST_DATA_URL}/subdir/bar_test.js`, + `${TEST_DATA_URL}/subdir/foo_test.ts`, + `${TEST_DATA_URL}/subdir/test.js`, + `${TEST_DATA_URL}/subdir/test.ts`, + `${TEST_DATA_URL}/test.js`, + `${TEST_DATA_URL}/test.ts` + ]); +}); + +test(async function findTestModulesDir2(): Promise<void> { + const urls = await findTestModules(["subdir"], [], TEST_DATA_PATH); + assertEquals(urls.sort(), [ + `${TEST_DATA_URL}/subdir/bar_test.js`, + `${TEST_DATA_URL}/subdir/foo_test.ts`, + `${TEST_DATA_URL}/subdir/test.js`, + `${TEST_DATA_URL}/subdir/test.ts` + ]); +}); + +test(async function findTestModulesGlob(): Promise<void> { + const urls = await findTestModules(["**/*_test.{js,ts}"], [], TEST_DATA_PATH); + assertEquals(urls.sort(), [ + `${TEST_DATA_URL}/bar_test.js`, + `${TEST_DATA_URL}/foo_test.ts`, + `${TEST_DATA_URL}/subdir/bar_test.js`, + `${TEST_DATA_URL}/subdir/foo_test.ts` + ]); +}); + +test(async function findTestModulesExcludeDir(): Promise<void> { + const urls = await findTestModules(["."], ["subdir"], TEST_DATA_PATH); + assertEquals(urls.sort(), [ + `${TEST_DATA_URL}/bar_test.js`, + `${TEST_DATA_URL}/foo_test.ts`, + `${TEST_DATA_URL}/test.js`, + `${TEST_DATA_URL}/test.ts` + ]); }); -test(async function getMatchingUrlsLocal(): Promise<void> { - const urls = await getMatchingUrls( - ["fmt/*_test.ts"], - ["colors*"], - TEST_ROOT_PATH - ); - assertEquals(urls.length, 1); +test(async function findTestModulesExcludeGlob(): Promise<void> { + const urls = await findTestModules(["."], ["**/foo*"], TEST_DATA_PATH); + assertEquals(urls.sort(), [ + `${TEST_DATA_URL}/bar_test.js`, + `${TEST_DATA_URL}/subdir/bar_test.js`, + `${TEST_DATA_URL}/subdir/test.js`, + `${TEST_DATA_URL}/subdir/test.ts`, + `${TEST_DATA_URL}/test.js`, + `${TEST_DATA_URL}/test.ts` + ]); +}); + +test(async function findTestModulesRemote(): Promise<void> { + const urls = [ + "https://example.com/colors_test.ts", + "http://example.com/printf_test.ts" + ]; + const matches = await findTestModules(urls, []); + assertEquals(matches, urls); }); diff --git a/testing/testdata/bar.js b/testing/testdata/bar.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/bar.js @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/bar_test.js b/testing/testdata/bar_test.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/bar_test.js @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/foo.ts b/testing/testdata/foo.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/foo.ts @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/foo_test.ts b/testing/testdata/foo_test.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/foo_test.ts @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/subdir/bar.js b/testing/testdata/subdir/bar.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/subdir/bar.js @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/subdir/bar_test.js b/testing/testdata/subdir/bar_test.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/subdir/bar_test.js @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/subdir/foo.ts b/testing/testdata/subdir/foo.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/subdir/foo.ts @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/subdir/foo_test.ts b/testing/testdata/subdir/foo_test.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/subdir/foo_test.ts @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/subdir/test.js b/testing/testdata/subdir/test.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/subdir/test.js @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/subdir/test.ts b/testing/testdata/subdir/test.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/subdir/test.ts @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/test.js b/testing/testdata/test.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/test.js @@ -0,0 +1 @@ +export {}; diff --git a/testing/testdata/test.ts b/testing/testdata/test.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/testing/testdata/test.ts @@ -0,0 +1 @@ +export {}; diff --git a/xeval/test.ts b/xeval/test.ts index 6b3e64ff4..f6dd70696 100644 --- a/xeval/test.ts +++ b/xeval/test.ts @@ -33,7 +33,7 @@ test(async function xevalCliReplvar(): Promise<void> { await p.stdin!.write(encode("hello")); await p.stdin!.close(); assertEquals(await p.status(), { code: 0, success: true }); - assertEquals(decode(await p.output()), "hello\n"); + assertEquals(decode(await p.output()).trimEnd(), "hello"); }); test(async function xevalCliSyntaxError(): Promise<void> { |