diff options
Diffstat (limited to 'packages/taler-harness/src/index.ts')
-rw-r--r-- | packages/taler-harness/src/index.ts | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts new file mode 100644 index 000000000..632bd5877 --- /dev/null +++ b/packages/taler-harness/src/index.ts @@ -0,0 +1,338 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { deepStrictEqual } from "assert"; +import fs from "fs"; +import os from "os"; +import path from "path"; +import { + Amounts, + clk, + Configuration, + decodeCrock, + Logger, + rsaBlind, + setGlobalLogLevelFromString, +} from "@gnu-taler/taler-util"; +import { runBench1 } from "./bench1.js"; +import { runBench2 } from "./bench2.js"; +import { runBench3 } from "./bench3.js"; +import { runEnv1 } from "./env1.js"; +import { GlobalTestState, runTestWithState } from "./harness/harness.js"; +import { getTestInfo, runTests } from "./integrationtests/testrunner.js"; +import { lintExchangeDeployment } from "./lint.js"; +import { runEnvFull } from "./env-full.js"; + +const logger = new Logger("taler-harness:index.ts"); + +process.on("unhandledRejection", (error: any) => { + logger.error("unhandledRejection", error.message); + logger.error("stack", error.stack); + process.exit(2); +}); + +declare const __VERSION__: string; +function printVersion(): void { + console.log(__VERSION__); + process.exit(0); +} + +export const testingCli = clk + .program("testing", { + help: "Command line interface for the GNU Taler test/deployment harness.", + }) + .maybeOption("log", ["-L", "--log"], clk.STRING, { + help: "configure log level (NONE, ..., TRACE)", + onPresentHandler: (x) => { + setGlobalLogLevelFromString(x); + }, + }) + .flag("version", ["-v", "--version"], { + onPresentHandler: printVersion, + }) + .flag("verbose", ["-V", "--verbose"], { + help: "Enable verbose output.", + }); + +const advancedCli = testingCli.subcommand("advancedArgs", "advanced", { + help: "Subcommands for advanced operations (only use if you know what you're doing!).", +}); + +advancedCli + .subcommand("bench1", "bench1", { + help: "Run the 'bench1' benchmark", + }) + .requiredOption("configJson", ["--config-json"], clk.STRING) + .action(async (args) => { + let config: any; + try { + config = JSON.parse(args.bench1.configJson); + } catch (e) { + console.log("Could not parse config JSON"); + } + await runBench1(config); + }); + +advancedCli + .subcommand("bench2", "bench2", { + help: "Run the 'bench2' benchmark", + }) + .requiredOption("configJson", ["--config-json"], clk.STRING) + .action(async (args) => { + let config: any; + try { + config = JSON.parse(args.bench2.configJson); + } catch (e) { + console.log("Could not parse config JSON"); + } + await runBench2(config); + }); + +advancedCli + .subcommand("bench3", "bench3", { + help: "Run the 'bench3' benchmark", + }) + .requiredOption("configJson", ["--config-json"], clk.STRING) + .action(async (args) => { + let config: any; + try { + config = JSON.parse(args.bench3.configJson); + } catch (e) { + console.log("Could not parse config JSON"); + } + await runBench3(config); + }); + +advancedCli + .subcommand("envFull", "env-full", { + help: "Run a test environment for bench1", + }) + .action(async (args) => { + const testDir = fs.mkdtempSync(path.join(os.tmpdir(), "taler-env-full-")); + const testState = new GlobalTestState({ + testDir, + }); + await runTestWithState(testState, runEnvFull, "env-full", true); + }); + +advancedCli + .subcommand("env1", "env1", { + help: "Run a test environment for bench1", + }) + .action(async (args) => { + const testDir = fs.mkdtempSync(path.join(os.tmpdir(), "taler-env1-")); + const testState = new GlobalTestState({ + testDir, + }); + await runTestWithState(testState, runEnv1, "env1", true); + }); + +const deploymentCli = testingCli.subcommand("deploymentArgs", "deployment", { + help: "Subcommands for handling GNU Taler deployments.", +}); + +deploymentCli + .subcommand("lintExchange", "lint-exchange", { + help: "Run checks on the exchange deployment.", + }) + .flag("cont", ["--continue"], { + help: "Continue after errors if possible", + }) + .flag("debug", ["--debug"], { + help: "Output extra debug info", + }) + .action(async (args) => { + await lintExchangeDeployment( + args.lintExchange.debug, + args.lintExchange.cont, + ); + }); + +deploymentCli + .subcommand("coincfg", "gen-coin-config", { + help: "Generate a coin/denomination configuration for the exchange.", + }) + .requiredOption("minAmount", ["--min-amount"], clk.STRING, { + help: "Smallest denomination", + }) + .requiredOption("maxAmount", ["--max-amount"], clk.STRING, { + help: "Largest denomination", + }) + .action(async (args) => { + let out = ""; + + const stamp = Math.floor(new Date().getTime() / 1000); + + const min = Amounts.parseOrThrow(args.coincfg.minAmount); + const max = Amounts.parseOrThrow(args.coincfg.maxAmount); + if (min.currency != max.currency) { + console.error("currency mismatch"); + process.exit(1); + } + const currency = min.currency; + let x = min; + let n = 1; + + out += "# Coin configuration for the exchange.\n"; + out += '# Should be placed in "/etc/taler/conf.d/exchange-coins.conf".\n'; + out += "\n"; + + while (Amounts.cmp(x, max) < 0) { + out += `[COIN-${currency}-n${n}-t${stamp}]\n`; + out += `VALUE = ${Amounts.stringify(x)}\n`; + out += `DURATION_WITHDRAW = 7 days\n`; + out += `DURATION_SPEND = 2 years\n`; + out += `DURATION_LEGAL = 6 years\n`; + out += `FEE_WITHDRAW = ${currency}:0\n`; + out += `FEE_DEPOSIT = ${Amounts.stringify(min)}\n`; + out += `FEE_REFRESH = ${currency}:0\n`; + out += `FEE_REFUND = ${currency}:0\n`; + out += `RSA_KEYSIZE = 2048\n`; + out += "\n"; + x = Amounts.add(x, x).amount; + n++; + } + + console.log(out); + }); + +const deploymentConfigCli = deploymentCli.subcommand("configArgs", "config", { + help: "Subcommands the Taler configuration.", +}); + +deploymentConfigCli + .subcommand("show", "show") + .flag("diagnostics", ["-d", "--diagnostics"]) + .maybeArgument("cfgfile", clk.STRING, {}) + .action(async (args) => { + const cfg = Configuration.load(args.show.cfgfile); + console.log( + cfg.stringify({ + diagnostics: args.show.diagnostics, + }), + ); + }); + + +testingCli.subcommand("logtest", "logtest").action(async (args) => { + logger.trace("This is a trace message."); + logger.info("This is an info message."); + logger.warn("This is an warning message."); + logger.error("This is an error message."); +}); + +testingCli + .subcommand("listIntegrationtests", "list-integrationtests") + .action(async (args) => { + for (const t of getTestInfo()) { + let s = t.name; + if (t.suites.length > 0) { + s += ` (suites: ${t.suites.join(",")})`; + } + if (t.excludeByDefault) { + s += ` [excluded by default]`; + } + console.log(s); + } + }); + +testingCli + .subcommand("runIntegrationtests", "run-integrationtests") + .maybeArgument("pattern", clk.STRING, { + help: "Glob pattern to select which tests to run", + }) + .maybeOption("suites", ["--suites"], clk.STRING, { + help: "Only run selected suites (comma-separated list)", + }) + .flag("dryRun", ["--dry"], { + help: "Only print tests that will be selected to run.", + }) + .flag("quiet", ["--quiet"], { + help: "Produce less output.", + }) + .action(async (args) => { + await runTests({ + includePattern: args.runIntegrationtests.pattern, + suiteSpec: args.runIntegrationtests.suites, + dryRun: args.runIntegrationtests.dryRun, + verbosity: args.runIntegrationtests.quiet ? 0 : 1, + }); + }); + +async function read(stream: NodeJS.ReadStream) { + const chunks = []; + for await (const chunk of stream) chunks.push(chunk); + return Buffer.concat(chunks).toString("utf8"); +} + +testingCli.subcommand("tvgcheck", "tvgcheck").action(async (args) => { + const data = await read(process.stdin); + + const lines = data.match(/[^\r\n]+/g); + + if (!lines) { + throw Error("can't split lines"); + } + + const vals: Record<string, string> = {}; + + let inBlindSigningSection = false; + + for (const line of lines) { + if (line === "blind signing:") { + inBlindSigningSection = true; + continue; + } + if (line[0] !== " ") { + inBlindSigningSection = false; + continue; + } + if (inBlindSigningSection) { + const m = line.match(/ (\w+) (\w+)/); + if (!m) { + console.log("bad format"); + process.exit(2); + } + vals[m[1]] = m[2]; + } + } + + console.log(vals); + + const req = (k: string) => { + if (!vals[k]) { + throw Error(`no value for ${k}`); + } + return decodeCrock(vals[k]); + }; + + const myBm = rsaBlind( + req("message_hash"), + req("blinding_key_secret"), + req("rsa_public_key"), + ); + + deepStrictEqual(req("blinded_message"), myBm); + + console.log("check passed!"); +}); + +export function main() { + testingCli.run(); +} |