run integration tests in worker process

This commit is contained in:
Florian Dold 2021-01-13 13:17:38 +01:00
parent 94e35ca70a
commit 9139a08c4d
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
2 changed files with 116 additions and 12 deletions

View File

@ -1086,13 +1086,13 @@ export class ExchangeService implements ExchangeServiceInterface {
this.helperCryptoEddsaProc = this.globalState.spawnService( this.helperCryptoEddsaProc = this.globalState.spawnService(
"taler-helper-crypto-eddsa", "taler-helper-crypto-eddsa",
["-c", this.configFilename, ...this.timetravelArgArr], ["-c", this.configFilename, "-LDEBUG", ...this.timetravelArgArr],
`exchange-crypto-eddsa-${this.name}`, `exchange-crypto-eddsa-${this.name}`,
); );
this.helperCryptoRsaProc = this.globalState.spawnService( this.helperCryptoRsaProc = this.globalState.spawnService(
"taler-helper-crypto-rsa", "taler-helper-crypto-rsa",
["-c", this.configFilename, ...this.timetravelArgArr], ["-c", this.configFilename, "-LDEBUG", ...this.timetravelArgArr],
`exchange-crypto-rsa-${this.name}`, `exchange-crypto-rsa-${this.name}`,
); );
@ -1458,10 +1458,9 @@ export async function runTestWithState(
const handleSignal = (s: string) => { const handleSignal = (s: string) => {
gc.shutdownSync(); gc.shutdownSync();
console.warn( console.warn(
"**** received fatal proces event, shutting down test harness", `**** received fatal proces event, terminating test ${testName}`,
); );
status = "fail"; process.exit(1);
p.reject(Error("caught signal"));
}; };
process.on("SIGINT", handleSignal); process.on("SIGINT", handleSignal);

View File

@ -19,6 +19,7 @@ import { runPaymentTest } from "./test-payment";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import * as os from "os"; import * as os from "os";
import * as child_process from "child_process";
import { runBankApiTest } from "./test-bank-api"; import { runBankApiTest } from "./test-bank-api";
import { runClaimLoopTest } from "./test-claim-loop"; import { runClaimLoopTest } from "./test-claim-loop";
import { runExchangeManagementTest } from "./test-exchange-management"; import { runExchangeManagementTest } from "./test-exchange-management";
@ -123,6 +124,11 @@ export function getTestName(tf: TestMainFunction): string {
.toLowerCase(); .toLowerCase();
} }
interface RunTestChildInstruction {
testName: string;
testRootDir: string;
}
export async function runTests(spec: TestRunSpec) { export async function runTests(spec: TestRunSpec) {
const testRootDir = fs.mkdtempSync( const testRootDir = fs.mkdtempSync(
path.join(os.tmpdir(), "taler-integrationtests-"), path.join(os.tmpdir(), "taler-integrationtests-"),
@ -137,20 +143,70 @@ export async function runTests(spec: TestRunSpec) {
const testResults: TestRunResult[] = []; const testResults: TestRunResult[] = [];
let currentChild: child_process.ChildProcess | undefined;
const handleSignal = () => {
if (currentChild) {
currentChild.kill("SIGTERM");
}
process.exit(3);
};
process.on("SIGINT", () => handleSignal);
process.on("SIGTERM", () => handleSignal);
process.on("unhandledRejection", handleSignal);
process.on("uncaughtException", handleSignal);
for (const [n, testCase] of allTests.entries()) { for (const [n, testCase] of allTests.entries()) {
const testName = getTestName(testCase); const testName = getTestName(testCase);
if (spec.include_pattern && !M(testName, spec.include_pattern)) { if (spec.include_pattern && !M(testName, spec.include_pattern)) {
continue; continue;
} }
const testDir = path.join(testRootDir, testName);
fs.mkdirSync(testDir); const testInstr: RunTestChildInstruction = {
console.log(`running test ${testName}`); testName,
const gc = new GlobalTestState({ testRootDir,
testDir, };
currentChild = child_process.fork(__filename, {
env: {
TWCLI_RUN_TEST_INSTRUCTION: JSON.stringify(testInstr),
...process.env,
},
stdio: ["pipe", "pipe", "pipe", "ipc"],
}); });
const result = await runTestWithState(gc, testCase, testName);
currentChild.stderr?.pipe(process.stderr);
currentChild.stdout?.pipe(process.stdout);
const result: TestRunResult = await new Promise((resolve, reject) => {
let msg: TestRunResult | undefined;
currentChild!.on("message", (m) => {
msg = m as TestRunResult;
});
currentChild!.on("exit", (code, signal) => {
if (signal) {
reject(new Error(`test worker exited with signal ${signal}`));
} else if (code != 0) {
reject(new Error(`test worker exited with code ${code}`));
} else if (!msg) {
reject(
new Error(
`test worker exited without giving back the test results`,
),
);
} else {
resolve(msg);
}
});
currentChild!.on("error", (err) => {
reject(err);
});
});
console.log(`parent: got result ${JSON.stringify(result)}`);
testResults.push(result); testResults.push(result);
console.log(result);
numTotal++; numTotal++;
if (result.status === "fail") { if (result.status === "fail") {
numFail++; numFail++;
@ -160,6 +216,7 @@ export async function runTests(spec: TestRunSpec) {
numPass++; numPass++;
} }
} }
const resultsFile = path.join(testRootDir, "results.json"); const resultsFile = path.join(testRootDir, "results.json");
fs.writeFileSync( fs.writeFileSync(
path.join(testRootDir, "results.json"), path.join(testRootDir, "results.json"),
@ -179,3 +236,51 @@ export function getTestInfo(): TestInfo[] {
name: getTestName(x), name: getTestName(x),
})); }));
} }
const runTestInstrStr = process.env["TWCLI_RUN_TEST_INSTRUCTION"];
if (runTestInstrStr) {
// Test will call taler-wallet-cli, so we must not propagate this variable.
delete process.env["TWCLI_RUN_TEST_NAME"];
const { testRootDir, testName } = JSON.parse(
runTestInstrStr,
) as RunTestChildInstruction;
console.log(`running test ${testName} in worker process`);
process.on("disconnect", () => {
process.exit(3);
});
const runTest = async () => {
let testMain: TestMainFunction | undefined;
for (const t of allTests) {
if (getTestName(t) === testName) {
testMain = t;
break;
}
}
if (!process.send) {
console.error("can't communicate with parent");
process.exit(2);
}
if (!testMain) {
console.log(`test ${testName} not found`);
process.exit(2);
}
const testDir = path.join(testRootDir, testName);
fs.mkdirSync(testDir);
console.log(`running test ${testName}`);
const gc = new GlobalTestState({
testDir,
});
const testResult = await runTestWithState(gc, testMain, testName);
process.send(testResult);
};
runTest().catch((e) => {
console.log(e);
process.exit(1);
});
}