summaryrefslogtreecommitdiff
path: root/testing/runner.ts
blob: a959e5473fac964e7123af0a7a7d9316ab00c5f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#!/usr/bin/env 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 */
function showHelp(): void {
  console.log(`Deno test runner

USAGE:
  deno -A https://deno.land/std/testing/runner.ts [OPTIONS] [FILES...]

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 */

function filePathToRegExp(str: string): RegExp {
  if (isGlob(str)) {
    return glob(str);
  }

  return RegExp(str);
}

/**
 * This function runs matching test files in `root` directory.
 *
 * File matching and excluding supports glob syntax, ie. if encountered arg is
 * a glob it will be expanded using `glob` method from `fs` module.
 *
 * Note that your shell may expand globs for you:
 *    $ deno -A ./runner.ts **\/*_test.ts **\/test.ts
 *
 * Expanding using `fs.glob`:
 *    $ deno -A ./runner.ts \*\*\/\*_test.ts \*\*\/test.ts
 *
 *  `**\/*_test.ts` and `**\/test.ts"` are arguments that will be parsed and
 *  expanded as: [glob("**\/*_test.ts"), glob("**\/test.ts")]
 */
// 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"]
    }
  });

  if (parsedArgs.help) {
    return showHelp();
  }

  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 (parsedArgs.exclude) {
    excludeFiles = (parsedArgs.exclude as string).split(",");
  } else {
    excludeFiles = [];
  }

  const filesIterator = walk(root, {
    match: includeFiles.map((f: string): RegExp => filePathToRegExp(f)),
    skip: excludeFiles.map((f: string): RegExp => filePathToRegExp(f))
  });

  const foundTestFiles: string[] = [];
  for await (const { filename } of filesIterator) {
    foundTestFiles.push(filename);
  }

  if (foundTestFiles.length === 0) {
    console.error("No matching test files found.");
    return;
  }

  console.log(`Found ${foundTestFiles.length} matching test files.`);

  for (const filename of foundTestFiles) {
    await import(`file://${filename}`);
  }

  await runTests({
    exitOnFail: !!parsedArgs.failfast,
    disableLog: !!parsedArgs.quiet
  });
}

if (import.meta.main) {
  main();
}