summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/template.common.yml4
-rw-r--r--fs/glob.ts67
-rw-r--r--fs/glob_test.ts88
-rw-r--r--fs/globrex.ts46
-rw-r--r--fs/path/constants.ts1
-rw-r--r--fs/testdata/glob/abc0
-rw-r--r--fs/testdata/glob/abcdef0
-rw-r--r--fs/testdata/glob/abcdefghi0
-rw-r--r--fs/testdata/glob/subdir/abc0
-rw-r--r--testing/mod.ts6
-rwxr-xr-xtesting/runner.ts300
-rw-r--r--testing/runner_test.ts94
-rw-r--r--testing/testdata/bar.js1
-rw-r--r--testing/testdata/bar_test.js1
-rw-r--r--testing/testdata/foo.ts1
-rw-r--r--testing/testdata/foo_test.ts1
-rw-r--r--testing/testdata/subdir/bar.js1
-rw-r--r--testing/testdata/subdir/bar_test.js1
-rw-r--r--testing/testdata/subdir/foo.ts1
-rw-r--r--testing/testdata/subdir/foo_test.ts1
-rw-r--r--testing/testdata/subdir/test.js1
-rw-r--r--testing/testdata/subdir/test.ts1
-rw-r--r--testing/testdata/test.js1
-rw-r--r--testing/testdata/test.ts1
-rw-r--r--xeval/test.ts2
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> {