Skip to content

Commit

Permalink
Test runner v2 (#604)
Browse files Browse the repository at this point in the history
  • Loading branch information
nayeemrmn authored and ry committed Sep 28, 2019
1 parent 5d0dd58 commit 17a214b
Show file tree
Hide file tree
Showing 25 changed files with 449 additions and 171 deletions.
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 comment has been minimized.

Copy link
@bartlomieju

bartlomieju Sep 28, 2019

Member

@nayeemrmn please open issues for these TODOs

This comment has been minimized.

Copy link
@nayeemrmn

nayeemrmn Sep 28, 2019

Author Contributor

On it... I think I can fix this and the others in one go by enhancing the expandGlob() API with an exclude option.

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

0 comments on commit 17a214b

Please sign in to comment.