harness: reusable test env
This commit is contained in:
parent
ef5962cd3c
commit
7fbe28e640
@ -489,6 +489,7 @@ export interface BankConfig {
|
|||||||
database: string;
|
database: string;
|
||||||
allowRegistrations: boolean;
|
allowRegistrations: boolean;
|
||||||
maxDebt?: string;
|
maxDebt?: string;
|
||||||
|
overrideTestDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FakeBankConfig {
|
export interface FakeBankConfig {
|
||||||
@ -534,6 +535,14 @@ function setCoin(config: Configuration, c: CoinConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function backoffStart(): number {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
function backoffIncrement(n: number): number {
|
||||||
|
return Math.max(n * 2, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an HTTP request until it succeeds or the process dies.
|
* Send an HTTP request until it succeeds or the process dies.
|
||||||
*/
|
*/
|
||||||
@ -545,6 +554,7 @@ export async function pingProc(
|
|||||||
if (!proc || proc.proc.exitCode !== null) {
|
if (!proc || proc.proc.exitCode !== null) {
|
||||||
throw Error(`service process ${serviceName} not started, can't ping`);
|
throw Error(`service process ${serviceName} not started, can't ping`);
|
||||||
}
|
}
|
||||||
|
let nextDelay = backoffStart();
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
logger.trace(`pinging ${serviceName} at ${url}`);
|
logger.trace(`pinging ${serviceName} at ${url}`);
|
||||||
@ -554,7 +564,8 @@ export async function pingProc(
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.warn(`service ${serviceName} not ready:`, e.toString());
|
logger.warn(`service ${serviceName} not ready:`, e.toString());
|
||||||
//console.log(e);
|
//console.log(e);
|
||||||
await delayMs(1000);
|
await delayMs(nextDelay);
|
||||||
|
nextDelay = backoffIncrement(nextDelay);
|
||||||
}
|
}
|
||||||
if (!proc || proc.proc.exitCode != null || proc.proc.signalCode != null) {
|
if (!proc || proc.proc.exitCode != null || proc.proc.signalCode != null) {
|
||||||
throw Error(`service process ${serviceName} stopped unexpectedly`);
|
throw Error(`service process ${serviceName} stopped unexpectedly`);
|
||||||
@ -885,19 +896,38 @@ export class FakebankService
|
|||||||
bc: BankConfig,
|
bc: BankConfig,
|
||||||
): Promise<FakebankService> {
|
): Promise<FakebankService> {
|
||||||
const config = new Configuration();
|
const config = new Configuration();
|
||||||
setTalerPaths(config, gc.testDir + "/talerhome");
|
const testDir = bc.overrideTestDir ?? gc.testDir;
|
||||||
|
setTalerPaths(config, testDir + "/talerhome");
|
||||||
config.setString("taler", "currency", bc.currency);
|
config.setString("taler", "currency", bc.currency);
|
||||||
config.setString("bank", "http_port", `${bc.httpPort}`);
|
config.setString("bank", "http_port", `${bc.httpPort}`);
|
||||||
config.setString("bank", "serve", "http");
|
config.setString("bank", "serve", "http");
|
||||||
config.setString("bank", "max_debt_bank", `${bc.currency}:999999`);
|
config.setString("bank", "max_debt_bank", `${bc.currency}:999999`);
|
||||||
config.setString("bank", "max_debt", bc.maxDebt ?? `${bc.currency}:100`);
|
config.setString("bank", "max_debt", bc.maxDebt ?? `${bc.currency}:100`);
|
||||||
config.setString("bank", "ram_limit", `${1024}`);
|
config.setString("bank", "ram_limit", `${1024}`);
|
||||||
const cfgFilename = gc.testDir + "/bank.conf";
|
const cfgFilename = testDir + "/bank.conf";
|
||||||
config.write(cfgFilename);
|
config.write(cfgFilename);
|
||||||
|
|
||||||
return new FakebankService(gc, bc, cfgFilename);
|
return new FakebankService(gc, bc, cfgFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromExistingConfig(
|
||||||
|
gc: GlobalTestState,
|
||||||
|
opts: { overridePath?: string },
|
||||||
|
): FakebankService {
|
||||||
|
const testDir = opts.overridePath ?? gc.testDir;
|
||||||
|
const cfgFilename = testDir + `/bank.conf`;
|
||||||
|
const config = Configuration.load(cfgFilename);
|
||||||
|
const bc: BankConfig = {
|
||||||
|
allowRegistrations:
|
||||||
|
config.getYesNo("bank", "allow_registrations").orUndefined() ?? true,
|
||||||
|
currency: config.getString("taler", "currency").required(),
|
||||||
|
database: "none",
|
||||||
|
httpPort: config.getNumber("bank", "http_port").required(),
|
||||||
|
maxDebt: config.getString("bank", "max_debt").required(),
|
||||||
|
};
|
||||||
|
return new FakebankService(gc, bc, cfgFilename);
|
||||||
|
}
|
||||||
|
|
||||||
setSuggestedExchange(e: ExchangeServiceInterface, exchangePayto: string) {
|
setSuggestedExchange(e: ExchangeServiceInterface, exchangePayto: string) {
|
||||||
if (!!this.proc) {
|
if (!!this.proc) {
|
||||||
throw Error("Can't set suggested exchange while bank is running.");
|
throw Error("Can't set suggested exchange while bank is running.");
|
||||||
@ -981,6 +1011,7 @@ export interface ExchangeConfig {
|
|||||||
roundUnit?: string;
|
roundUnit?: string;
|
||||||
httpPort: number;
|
httpPort: number;
|
||||||
database: string;
|
database: string;
|
||||||
|
overrideTestDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeServiceInterface {
|
export interface ExchangeServiceInterface {
|
||||||
@ -991,8 +1022,13 @@ export interface ExchangeServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExchangeService implements ExchangeServiceInterface {
|
export class ExchangeService implements ExchangeServiceInterface {
|
||||||
static fromExistingConfig(gc: GlobalTestState, exchangeName: string) {
|
static fromExistingConfig(
|
||||||
const cfgFilename = gc.testDir + `/exchange-${exchangeName}.conf`;
|
gc: GlobalTestState,
|
||||||
|
exchangeName: string,
|
||||||
|
opts: { overridePath?: string },
|
||||||
|
) {
|
||||||
|
const testDir = opts.overridePath ?? gc.testDir;
|
||||||
|
const cfgFilename = testDir + `/exchange-${exchangeName}.conf`;
|
||||||
const config = Configuration.load(cfgFilename);
|
const config = Configuration.load(cfgFilename);
|
||||||
const ec: ExchangeConfig = {
|
const ec: ExchangeConfig = {
|
||||||
currency: config.getString("taler", "currency").required(),
|
currency: config.getString("taler", "currency").required(),
|
||||||
@ -1103,7 +1139,9 @@ export class ExchangeService implements ExchangeServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static create(gc: GlobalTestState, e: ExchangeConfig) {
|
static create(gc: GlobalTestState, e: ExchangeConfig) {
|
||||||
|
const testDir = e.overrideTestDir ?? gc.testDir;
|
||||||
const config = new Configuration();
|
const config = new Configuration();
|
||||||
|
setTalerPaths(config, testDir + "/talerhome");
|
||||||
config.setString("taler", "currency", e.currency);
|
config.setString("taler", "currency", e.currency);
|
||||||
// Required by the exchange but not really used yet.
|
// Required by the exchange but not really used yet.
|
||||||
config.setString("exchange", "aml_threshold", `${e.currency}:1000000`);
|
config.setString("exchange", "aml_threshold", `${e.currency}:1000000`);
|
||||||
@ -1112,7 +1150,6 @@ export class ExchangeService implements ExchangeServiceInterface {
|
|||||||
"currency_round_unit",
|
"currency_round_unit",
|
||||||
e.roundUnit ?? `${e.currency}:0.01`,
|
e.roundUnit ?? `${e.currency}:0.01`,
|
||||||
);
|
);
|
||||||
setTalerPaths(config, gc.testDir + "/talerhome");
|
|
||||||
config.setString(
|
config.setString(
|
||||||
"exchange",
|
"exchange",
|
||||||
"revocation_dir",
|
"revocation_dir",
|
||||||
@ -1149,7 +1186,7 @@ export class ExchangeService implements ExchangeServiceInterface {
|
|||||||
|
|
||||||
fs.writeFileSync(masterPrivFile, Buffer.from(exchangeMasterKey.eddsaPriv));
|
fs.writeFileSync(masterPrivFile, Buffer.from(exchangeMasterKey.eddsaPriv));
|
||||||
|
|
||||||
const cfgFilename = gc.testDir + `/exchange-${e.name}.conf`;
|
const cfgFilename = testDir + `/exchange-${e.name}.conf`;
|
||||||
config.write(cfgFilename);
|
config.write(cfgFilename);
|
||||||
return new ExchangeService(gc, e, cfgFilename, exchangeMasterKey);
|
return new ExchangeService(gc, e, cfgFilename, exchangeMasterKey);
|
||||||
}
|
}
|
||||||
@ -1553,6 +1590,7 @@ export interface MerchantConfig {
|
|||||||
currency: string;
|
currency: string;
|
||||||
httpPort: number;
|
httpPort: number;
|
||||||
database: string;
|
database: string;
|
||||||
|
overrideTestDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PrivateOrderStatusQuery {
|
export interface PrivateOrderStatusQuery {
|
||||||
@ -1798,8 +1836,13 @@ export interface CreateMerchantTippingReserveRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MerchantService implements MerchantServiceInterface {
|
export class MerchantService implements MerchantServiceInterface {
|
||||||
static fromExistingConfig(gc: GlobalTestState, name: string) {
|
static fromExistingConfig(
|
||||||
const cfgFilename = gc.testDir + `/merchant-${name}.conf`;
|
gc: GlobalTestState,
|
||||||
|
name: string,
|
||||||
|
opts: { overridePath?: string },
|
||||||
|
) {
|
||||||
|
const testDir = opts.overridePath ?? gc.testDir;
|
||||||
|
const cfgFilename = testDir + `/merchant-${name}.conf`;
|
||||||
const config = Configuration.load(cfgFilename);
|
const config = Configuration.load(cfgFilename);
|
||||||
const mc: MerchantConfig = {
|
const mc: MerchantConfig = {
|
||||||
currency: config.getString("taler", "currency").required(),
|
currency: config.getString("taler", "currency").required(),
|
||||||
@ -1894,11 +1937,12 @@ export class MerchantService implements MerchantServiceInterface {
|
|||||||
gc: GlobalTestState,
|
gc: GlobalTestState,
|
||||||
mc: MerchantConfig,
|
mc: MerchantConfig,
|
||||||
): Promise<MerchantService> {
|
): Promise<MerchantService> {
|
||||||
|
const testDir = mc.overrideTestDir ?? gc.testDir;
|
||||||
const config = new Configuration();
|
const config = new Configuration();
|
||||||
config.setString("taler", "currency", mc.currency);
|
config.setString("taler", "currency", mc.currency);
|
||||||
|
|
||||||
const cfgFilename = gc.testDir + `/merchant-${mc.name}.conf`;
|
const cfgFilename = testDir + `/merchant-${mc.name}.conf`;
|
||||||
setTalerPaths(config, gc.testDir + "/talerhome");
|
setTalerPaths(config, testDir + "/talerhome");
|
||||||
config.setString("merchant", "serve", "tcp");
|
config.setString("merchant", "serve", "tcp");
|
||||||
config.setString("merchant", "port", `${mc.httpPort}`);
|
config.setString("merchant", "port", `${mc.httpPort}`);
|
||||||
config.setString(
|
config.setString(
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
NotificationType,
|
NotificationType,
|
||||||
WalletNotification,
|
WalletNotification,
|
||||||
TransactionMajorState,
|
TransactionMajorState,
|
||||||
|
Logger,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
BankAccessApi,
|
BankAccessApi,
|
||||||
@ -49,6 +50,7 @@ import {
|
|||||||
DbInfo,
|
DbInfo,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
ExchangeServiceInterface,
|
ExchangeServiceInterface,
|
||||||
|
FakebankService,
|
||||||
getPayto,
|
getPayto,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantPrivateApi,
|
MerchantPrivateApi,
|
||||||
@ -62,6 +64,10 @@ import {
|
|||||||
WithAuthorization,
|
WithAuthorization,
|
||||||
} from "./harness.js";
|
} from "./harness.js";
|
||||||
|
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
const logger = new Logger("helpers.ts");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
@ -212,48 +218,103 @@ export async function createSimpleTestkudosEnvironment(
|
|||||||
export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
||||||
const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS"));
|
const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS"));
|
||||||
|
|
||||||
|
// FIXME: We should probably have some file to indicate that
|
||||||
|
// the previous env setup finished successfully.
|
||||||
|
|
||||||
|
const sharedDir = `/tmp/taler-harness@${process.env.USER}`;
|
||||||
|
|
||||||
|
fs.mkdirSync(sharedDir, { recursive: true });
|
||||||
|
|
||||||
const db = await setupSharedDb(t);
|
const db = await setupSharedDb(t);
|
||||||
|
|
||||||
const bank = await BankService.create(t, {
|
let bank: FakebankService;
|
||||||
allowRegistrations: true,
|
|
||||||
currency: "TESTKUDOS",
|
|
||||||
database: db.connStr,
|
|
||||||
httpPort: 8082,
|
|
||||||
});
|
|
||||||
|
|
||||||
const exchange = ExchangeService.create(t, {
|
const prevSetupDone = fs.existsSync(sharedDir + "/setup-done");
|
||||||
name: "testexchange-1",
|
|
||||||
currency: "TESTKUDOS",
|
|
||||||
httpPort: 8081,
|
|
||||||
database: db.connStr,
|
|
||||||
});
|
|
||||||
|
|
||||||
const merchant = await MerchantService.create(t, {
|
logger.info(`previous setup done: ${prevSetupDone}`);
|
||||||
name: "testmerchant-1",
|
|
||||||
currency: "TESTKUDOS",
|
if (fs.existsSync(sharedDir + "/bank.conf")) {
|
||||||
httpPort: 8083,
|
logger.info("reusing existing bank");
|
||||||
database: db.connStr,
|
bank = BankService.fromExistingConfig(t, {
|
||||||
});
|
overridePath: sharedDir,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.info("creating new bank config");
|
||||||
|
bank = await BankService.create(t, {
|
||||||
|
allowRegistrations: true,
|
||||||
|
currency: "TESTKUDOS",
|
||||||
|
database: db.connStr,
|
||||||
|
httpPort: 8082,
|
||||||
|
overrideTestDir: sharedDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("setting up exchange");
|
||||||
|
|
||||||
|
const exchangeName = "testexchange-1";
|
||||||
|
const exchangeConfigFilename = sharedDir + `/exchange-${exchangeName}}`;
|
||||||
|
|
||||||
|
let exchange: ExchangeService;
|
||||||
|
|
||||||
|
if (fs.existsSync(exchangeConfigFilename)) {
|
||||||
|
exchange = ExchangeService.fromExistingConfig(t, exchangeName, {
|
||||||
|
overridePath: sharedDir,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
exchange = ExchangeService.create(t, {
|
||||||
|
name: "testexchange-1",
|
||||||
|
currency: "TESTKUDOS",
|
||||||
|
httpPort: 8081,
|
||||||
|
database: db.connStr,
|
||||||
|
overrideTestDir: sharedDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("setting up exchange");
|
||||||
|
|
||||||
|
let merchant: MerchantService;
|
||||||
|
const merchantName = "testmerchant-1";
|
||||||
|
const merchantConfigFilename = sharedDir + `/merchant-${merchantName}}`;
|
||||||
|
|
||||||
|
if (fs.existsSync(merchantConfigFilename)) {
|
||||||
|
merchant = MerchantService.fromExistingConfig(t, merchantName, {
|
||||||
|
overridePath: sharedDir,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
merchant = await MerchantService.create(t, {
|
||||||
|
name: "testmerchant-1",
|
||||||
|
currency: "TESTKUDOS",
|
||||||
|
httpPort: 8083,
|
||||||
|
database: db.connStr,
|
||||||
|
overrideTestDir: sharedDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("creating bank account for exchange");
|
||||||
|
|
||||||
const exchangeBankAccount = await bank.createExchangeAccount(
|
const exchangeBankAccount = await bank.createExchangeAccount(
|
||||||
"myexchange",
|
"myexchange",
|
||||||
"x",
|
"x",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
logger.info("creating exchange bank account");
|
||||||
await exchange.addBankAccount("1", exchangeBankAccount);
|
await exchange.addBankAccount("1", exchangeBankAccount);
|
||||||
|
|
||||||
bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri);
|
bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri);
|
||||||
|
|
||||||
|
exchange.addCoinConfigList(coinConfig);
|
||||||
|
|
||||||
|
merchant.addExchange(exchange);
|
||||||
|
|
||||||
|
logger.info("basic setup done, starting services");
|
||||||
|
|
||||||
await bank.start();
|
await bank.start();
|
||||||
|
|
||||||
await bank.pingUntilAvailable();
|
await bank.pingUntilAvailable();
|
||||||
|
|
||||||
exchange.addCoinConfigList(coinConfig);
|
|
||||||
|
|
||||||
await exchange.start();
|
await exchange.start();
|
||||||
await exchange.pingUntilAvailable();
|
await exchange.pingUntilAvailable();
|
||||||
|
|
||||||
merchant.addExchange(exchange);
|
|
||||||
|
|
||||||
await merchant.start();
|
await merchant.start();
|
||||||
await merchant.pingUntilAvailable();
|
await merchant.pingUntilAvailable();
|
||||||
|
|
||||||
@ -282,6 +343,8 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
|||||||
|
|
||||||
console.log("setup done!");
|
console.log("setup done!");
|
||||||
|
|
||||||
|
fs.writeFileSync(sharedDir + "/setup-done", "OK");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
commonDb: db,
|
commonDb: db,
|
||||||
exchange,
|
exchange,
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CancellationToken, Logger, minimatch } from "@gnu-taler/taler-util";
|
import { CancellationToken, Logger, minimatch, setGlobalLogLevelFromString } from "@gnu-taler/taler-util";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
@ -494,6 +494,8 @@ if (runTestInstrStr && process.argv.includes("__TWCLI_TESTWORKER")) {
|
|||||||
runTestInstrStr,
|
runTestInstrStr,
|
||||||
) as RunTestChildInstruction;
|
) as RunTestChildInstruction;
|
||||||
|
|
||||||
|
setGlobalLogLevelFromString("TRACE");
|
||||||
|
|
||||||
process.on("disconnect", () => {
|
process.on("disconnect", () => {
|
||||||
logger.trace("got disconnect from parent");
|
logger.trace("got disconnect from parent");
|
||||||
process.exit(3);
|
process.exit(3);
|
||||||
|
@ -32,13 +32,13 @@ export enum LogLevel {
|
|||||||
None = "none",
|
None = "none",
|
||||||
}
|
}
|
||||||
|
|
||||||
export let globalLogLevel = LogLevel.Info;
|
let globalLogLevel = LogLevel.Info;
|
||||||
|
const byTagLogLevel: Record<string, LogLevel> = {};
|
||||||
|
|
||||||
export function setGlobalLogLevelFromString(logLevelStr: string): void {
|
export function setGlobalLogLevelFromString(logLevelStr: string): void {
|
||||||
globalLogLevel = getLevelForString(logLevelStr);
|
globalLogLevel = getLevelForString(logLevelStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const byTagLogLevel: Record<string, LogLevel> = {};
|
|
||||||
export function setLogLevelFromString(tag: string, logLevelStr: string): void {
|
export function setLogLevelFromString(tag: string, logLevelStr: string): void {
|
||||||
byTagLogLevel[tag] = getLevelForString(logLevelStr);
|
byTagLogLevel[tag] = getLevelForString(logLevelStr);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user