Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test runner v2 #604

Merged
merged 29 commits into from
Sep 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c08efb0
Change help message for new API
nayeemrmn Sep 18, 2019
888c954
Refactor: Separate CLI handling from export-worthy APIs
nayeemrmn Sep 19, 2019
0b90140
Respect disableLog in runTestModules()'s own output
nayeemrmn Sep 19, 2019
7d16aa8
Make getMatchingUrls()'s root optional
nayeemrmn Sep 19, 2019
86e9a2f
Fail if no test modules are found, add an --allow-none flag
nayeemrmn Sep 19, 2019
85b12ac
Remove current directory matching behaviour, inline filePathToRegExp()
nayeemrmn Sep 19, 2019
d3f425f
Add prototypes for expandGlob() and expandGlobSync()
nayeemrmn Sep 20, 2019
004724c
Implement new test module matching
nayeemrmn Sep 20, 2019
ff4fd3b
Use extended glob syntax
nayeemrmn Sep 20, 2019
1dbf119
Default to . instead of the default globs
nayeemrmn Sep 20, 2019
e9a35d3
Rename getMatchingUrls() to findTestModules()
nayeemrmn Sep 20, 2019
b48c2d2
Improve directory expansion
nayeemrmn Sep 20, 2019
b3926f0
Apply fixes from #599
lucacasonato Sep 20, 2019
da18ab8
Add a test
nayeemrmn Sep 20, 2019
d72d23d
Rename RunOptions to RunTestsOptions
nayeemrmn Sep 20, 2019
0e98214
Fix globrex's globstar on Windows
nayeemrmn Sep 20, 2019
11aede4
Make include optional
nayeemrmn Sep 20, 2019
29b3152
Favor try/catch to Promise::catch()
nayeemrmn Sep 22, 2019
b7ba067
Improve and add more tests
nayeemrmn Sep 22, 2019
ffc2a39
Fix file path to URL conversion for Windows
nayeemrmn Sep 22, 2019
31204c9
Add tests for expandGlob()
nayeemrmn Sep 26, 2019
4715ac6
Merge branch 'master' into test-runner
ry Sep 27, 2019
232de65
Review: Trim test subprocess output
nayeemrmn Sep 27, 2019
7859892
Remove logs
nayeemrmn Sep 27, 2019
8b79ee7
globrex: Match both separator types on Windows
nayeemrmn Sep 27, 2019
0e7ed7f
Review: Extract URL to file path conversion in runner_test.ts
nayeemrmn Sep 27, 2019
3b7fbbe
Extract URL to file path conversion in glob_test.ts
nayeemrmn Sep 27, 2019
b421efd
Default filepath to true in expandGlob()
nayeemrmn Sep 28, 2019
883969a
Review: Improve help message, add examples
nayeemrmn Sep 28, 2019
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
4 changes: 2 additions & 2 deletions .ci/template.common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
- 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
67 changes: 66 additions & 1 deletion fs/glob.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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(
nayeemrmn marked this conversation as resolved.
Show resolved Hide resolved
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
});
}
88 changes: 83 additions & 5 deletions fs/glob_test.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
46 changes: 23 additions & 23 deletions fs/globrex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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" });
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions fs/path/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? "\\" : "/";
Empty file added fs/testdata/glob/abc
Empty file.
Empty file added fs/testdata/glob/abcdef
Empty file.
Empty file added fs/testdata/glob/abcdefghi
Empty file.
Empty file added fs/testdata/glob/subdir/abc
Empty file.
6 changes: 3 additions & 3 deletions testing/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
);
Expand Down Expand Up @@ -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);
Expand Down
Loading