Improve large-scale test result reporting
This commit is contained in:
		
							parent
							
								
									2a915e2654
								
							
						
					
					
						commit
						510cc1e0c0
					
				| 
						 | 
					@ -3,3 +3,4 @@
 | 
				
			||||||
.lsp-repl-history
 | 
					.lsp-repl-history
 | 
				
			||||||
node_modules
 | 
					node_modules
 | 
				
			||||||
out
 | 
					out
 | 
				
			||||||
 | 
					tests
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,16 @@
 | 
				
			||||||
import * as path from "path";
 | 
					import * as fs from "fs";
 | 
				
			||||||
import * as process from "process";
 | 
					import * as process from "process";
 | 
				
			||||||
 | 
					import { promisify } from "util";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import PQueue from "p-queue";
 | 
				
			||||||
 | 
					import * as rimraf from "rimraf";
 | 
				
			||||||
import { v4 as getUUID } from "uuid";
 | 
					import { v4 as getUUID } from "uuid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as api from "./api";
 | 
					import * as api from "./api";
 | 
				
			||||||
import { LangConfig, langs } from "./langs";
 | 
					import { LangConfig, langs } from "./langs";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TIMEOUT_MS = 3000;
 | 
					const TIMEOUT_MS = 3000;
 | 
				
			||||||
 | 
					const CONCURRENCY = 16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function findPosition(str: string, idx: number) {
 | 
					function findPosition(str: string, idx: number) {
 | 
				
			||||||
  const lines = str.substring(0, idx).split("\n");
 | 
					  const lines = str.substring(0, idx).split("\n");
 | 
				
			||||||
| 
						 | 
					@ -40,8 +44,11 @@ class Test {
 | 
				
			||||||
    this.type = type;
 | 
					    this.type = type;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getLog = () => {
 | 
					  getLog = (opts?: any) => {
 | 
				
			||||||
    return this.messages.map((msg: any) => JSON.stringify(msg)).join("\n");
 | 
					    opts = opts || {};
 | 
				
			||||||
 | 
					    return this.messages
 | 
				
			||||||
 | 
					      .map((msg: any) => JSON.stringify(msg, null, opts.pretty && 2))
 | 
				
			||||||
 | 
					      .join("\n");
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  run = async () => {
 | 
					  run = async () => {
 | 
				
			||||||
| 
						 | 
					@ -508,6 +515,14 @@ function lint(lang: string) {
 | 
				
			||||||
  if (!config.template.endsWith("\n")) {
 | 
					  if (!config.template.endsWith("\n")) {
 | 
				
			||||||
    throw new Error("template is missing a trailing newline");
 | 
					    throw new Error("template is missing a trailing newline");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  // These can be removed when the types are adjusted to make these
 | 
				
			||||||
 | 
					  // situations impossible.
 | 
				
			||||||
 | 
					  if (config.format && !config.format.input) {
 | 
				
			||||||
 | 
					    throw new Error("formatter is missing test");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (config.lsp && !(config.lsp.code && config.lsp.item)) {
 | 
				
			||||||
 | 
					    throw new Error("LSP is missing test");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const testTypes: {
 | 
					const testTypes: {
 | 
				
			||||||
| 
						 | 
					@ -546,6 +561,11 @@ function getTestList() {
 | 
				
			||||||
  return tests;
 | 
					  return tests;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function writeLog(lang: string, type: string, log: string) {
 | 
				
			||||||
 | 
					  await promisify(fs.mkdir)(`tests/${lang}`, { recursive: true });
 | 
				
			||||||
 | 
					  await promisify(fs.writeFile)(`tests/${lang}/${type}.log`, log);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function main() {
 | 
					async function main() {
 | 
				
			||||||
  let tests = getTestList();
 | 
					  let tests = getTestList();
 | 
				
			||||||
  const args = process.argv.slice(2);
 | 
					  const args = process.argv.slice(2);
 | 
				
			||||||
| 
						 | 
					@ -564,42 +584,68 @@ async function main() {
 | 
				
			||||||
    process.exit(1);
 | 
					    process.exit(1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  const lintSeen = new Set();
 | 
					  const lintSeen = new Set();
 | 
				
			||||||
  let lintPassed = 0;
 | 
					  let lintPassed = new Set();
 | 
				
			||||||
  let lintFailed = 0;
 | 
					  let lintFailed = new Map();
 | 
				
			||||||
  for (const { lang } of tests) {
 | 
					  for (const { lang } of tests) {
 | 
				
			||||||
    if (!lintSeen.has(lang)) {
 | 
					    if (!lintSeen.has(lang)) {
 | 
				
			||||||
      lintSeen.add(lang);
 | 
					      lintSeen.add(lang);
 | 
				
			||||||
      console.error(`===== LANGUAGE ${lang}, LINT`);
 | 
					 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        lint(lang);
 | 
					        lint(lang);
 | 
				
			||||||
        console.error("passed");
 | 
					        lintPassed.add(lang);
 | 
				
			||||||
        lintPassed += 1;
 | 
					 | 
				
			||||||
      } catch (err) {
 | 
					      } catch (err) {
 | 
				
			||||||
        console.error("failed");
 | 
					        lintFailed.set(lang, err);
 | 
				
			||||||
        console.error(err);
 | 
					 | 
				
			||||||
        lintFailed += 1;
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (lintFailed) {
 | 
					  if (lintFailed.size > 0) {
 | 
				
			||||||
 | 
					    console.error(
 | 
				
			||||||
 | 
					      `Language${lintFailed.size !== 1 ? "s" : ""} failed linting:`
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    console.error(
 | 
				
			||||||
 | 
					      Array.from(lintFailed)
 | 
				
			||||||
 | 
					        .map(([lang, err]) => `  - ${lang} (${err})`)
 | 
				
			||||||
 | 
					        .join("\n")
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    process.exit(1);
 | 
					    process.exit(1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  await promisify(rimraf)("tests");
 | 
				
			||||||
 | 
					  const queue = new PQueue({ concurrency: CONCURRENCY });
 | 
				
			||||||
  let passed = 0;
 | 
					  let passed = 0;
 | 
				
			||||||
  let failed = 0;
 | 
					  let failed = 0;
 | 
				
			||||||
  for (const { lang, test: type } of tests) {
 | 
					  for (const { lang, test: type } of tests) {
 | 
				
			||||||
    console.error(`===== LANGUAGE ${lang}, TEST ${type}`);
 | 
					    let test: Test;
 | 
				
			||||||
    const test = new Test(lang, type);
 | 
					    queue
 | 
				
			||||||
    try {
 | 
					      .add(() => {
 | 
				
			||||||
      await test.run();
 | 
					        test = new Test(lang, type);
 | 
				
			||||||
      console.error("passed");
 | 
					        return test.run();
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .then(async () => {
 | 
				
			||||||
        passed += 1;
 | 
					        passed += 1;
 | 
				
			||||||
    } catch (err) {
 | 
					        console.error(`PASSED: ${lang}/${type}`);
 | 
				
			||||||
      console.error("failed");
 | 
					        await writeLog(
 | 
				
			||||||
 | 
					          lang,
 | 
				
			||||||
 | 
					          type,
 | 
				
			||||||
 | 
					          `PASSED: ${lang}/${type}\n` + test.getLog({ pretty: true }) + "\n"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(async (err) => {
 | 
				
			||||||
 | 
					        failed += 1;
 | 
				
			||||||
 | 
					        console.error(`FAILED: ${lang}/${type}`);
 | 
				
			||||||
        console.error(test.getLog());
 | 
					        console.error(test.getLog());
 | 
				
			||||||
        console.error(err);
 | 
					        console.error(err);
 | 
				
			||||||
      failed += 1;
 | 
					        await writeLog(
 | 
				
			||||||
    }
 | 
					          lang,
 | 
				
			||||||
 | 
					          type,
 | 
				
			||||||
 | 
					          `FAILED: ${lang}/${type}\n` +
 | 
				
			||||||
 | 
					            test.getLog({ pretty: true }) +
 | 
				
			||||||
 | 
					            "\n" +
 | 
				
			||||||
 | 
					            err.stack +
 | 
				
			||||||
 | 
					            "\n"
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .catch(console.error);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  await queue.onIdle();
 | 
				
			||||||
  process.exit(failed ? 1 : 0);
 | 
					  process.exit(failed ? 1 : 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,7 @@
 | 
				
			||||||
    "npm-run-all": "^4.1.5",
 | 
					    "npm-run-all": "^4.1.5",
 | 
				
			||||||
    "p-queue": "^6.6.0",
 | 
					    "p-queue": "^6.6.0",
 | 
				
			||||||
    "parse-passwd": "^1.0.0",
 | 
					    "parse-passwd": "^1.0.0",
 | 
				
			||||||
 | 
					    "rimraf": "^3.0.2",
 | 
				
			||||||
    "shell-quote": "^1.7.2",
 | 
					    "shell-quote": "^1.7.2",
 | 
				
			||||||
    "style-loader": "^1.2.1",
 | 
					    "style-loader": "^1.2.1",
 | 
				
			||||||
    "ts-loader": "^7.0.5",
 | 
					    "ts-loader": "^7.0.5",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4189,6 +4189,13 @@ rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    glob "^7.1.3"
 | 
					    glob "^7.1.3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rimraf@^3.0.2:
 | 
				
			||||||
 | 
					  version "3.0.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
 | 
				
			||||||
 | 
					  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    glob "^7.1.3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ripemd160@^2.0.0, ripemd160@^2.0.1:
 | 
					ripemd160@^2.0.0, ripemd160@^2.0.1:
 | 
				
			||||||
  version "2.0.2"
 | 
					  version "2.0.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
 | 
					  resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue