taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit c3fffe82bdf240a15b4576f3392cc27ca2794466
parent ad824ad9162076c6b1894bee4ecba05debd78d6b
Author: Florian Dold <dold@taler.net>
Date:   Sun, 13 Sep 2026 20:52:03 +0200

taler-harness: allow suites to be disabled by default

Require explicit --suites selection for suites with additional setup.
Keep this independent of test name patterns and --experimental, and
show the disabled state when listing integration tests.

Diffstat:
Mpackages/taler-harness/README.md | 11+++++++++++
Mpackages/taler-harness/src/index.ts | 5++++-
Apackages/taler-harness/src/integrationtests/test-selection.test.ts | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/taler-harness/src/integrationtests/test-selection.ts | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 65++++++++++++++++++-----------------------------------------------
Mpackages/taler-harness/src/integrationtests/todo.test.ts | 1+
6 files changed, 221 insertions(+), 48 deletions(-)

diff --git a/packages/taler-harness/README.md b/packages/taler-harness/README.md @@ -4,6 +4,17 @@ This package implements the `taler-harness` CLI tool. It contains integration tests for GNU Taler and GNU anastasis, as well as various helpers for managing deployments of GNU Taler. +## Integration test selection + +`run-integrationtests` runs enabled suites by default. Suites that require +additional setup can be disabled by default in +`src/integrationtests/test-selection.ts`; currently this applies to +`wallet-android`. Enable such a suite with `--suites wallet-android`. +A test-name pattern only narrows the selection and does not enable a disabled +suite. `--experimental` controls experimental tests independently and also does +not enable disabled suites. `list-integrationtests` marks tests belonging to a +disabled suite, and `run-integrationtests --dry` shows the actual selection. + ## Stagefright merchant browser scenarios Run the full merchant scenario using this checkout's Web UI against staging: diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -1984,6 +1984,9 @@ talerHarnessCli if (t.experimental) { s += ` [experimental]`; } + if (t.disabledByDefault) { + s += ` [suite disabled by default]`; + } if (t.todo) { s += ` [todo: ${t.todoBugUrl}]`; } @@ -1997,7 +2000,7 @@ talerHarnessCli help: "Glob pattern to select which tests to run", }) .maybeOption("suites", ["--suites"], clk.STRING, { - help: "Only run selected suites (comma-separated list)", + help: "Select suites, including suites disabled by default (comma-separated list)", }) .maybeOption("testDir", ["--test-dir"], clk.STRING, { help: "When to run the tests", diff --git a/packages/taler-harness/src/integrationtests/test-selection.test.ts b/packages/taler-harness/src/integrationtests/test-selection.test.ts @@ -0,0 +1,110 @@ +/* + This file is part of GNU Taler + (C) 2026 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/> + */ + +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { selectTests, TestSelectionSpec } from "./test-selection.js"; + +const tests = [ + { name: "unclassified" }, + { name: "wallet-basic", suites: ["wallet"] }, + { name: "wallet-experimental", suites: ["wallet"], experimental: true }, + { name: "wallet-android-basic", suites: ["wallet-android"] }, + { name: "wallet-android-shared", suites: ["wallet", "wallet-android"] }, + { + name: "wallet-android-experimental", + suites: ["wallet-android"], + experimental: true, + }, +]; + +const cases: { + name: string; + spec: Partial<TestSelectionSpec>; + expected: string[]; +}[] = [ + { + name: "default selection excludes disabled suites and experimental tests", + spec: {}, + expected: ["unclassified", "wallet-basic"], + }, + { + name: "experimental flag does not enable disabled suites", + spec: { includeExperimental: true }, + expected: ["unclassified", "wallet-basic", "wallet-experimental"], + }, + { + name: "matching patterns do not enable disabled suites", + spec: { includePattern: "wallet-android-*", includeExperimental: true }, + expected: [], + }, + { + name: "explicit suite selection enables its ordinary tests", + spec: { suiteSpec: "wallet-android" }, + expected: ["wallet-android-basic", "wallet-android-shared"], + }, + { + name: "experimental tests in disabled suites still need both options", + spec: { suiteSpec: "wallet-android", includeExperimental: true }, + expected: [ + "wallet-android-basic", + "wallet-android-shared", + "wallet-android-experimental", + ], + }, + { + name: "another suite cannot bypass the required opt-in", + spec: { suiteSpec: "wallet" }, + expected: ["wallet-basic"], + }, + { + name: "comma-separated suites support mixed enabled and disabled suites", + spec: { suiteSpec: "wallet, wallet-android" }, + expected: ["wallet-basic", "wallet-android-basic", "wallet-android-shared"], + }, + { + name: "patterns narrow an explicitly enabled suite", + spec: { + suiteSpec: "wallet-android", + includePattern: "wallet-android-basic", + }, + expected: ["wallet-android-basic"], + }, + { + name: "comma-separated patterns intersect suite selection", + spec: { + suiteSpec: "wallet-android", + includePattern: "wallet-basic,wallet-android-shared", + }, + expected: ["wallet-android-shared"], + }, + { + name: "unknown suites do not select any tests", + spec: { suiteSpec: "unknown" }, + expected: [], + }, +]; + +for (const { name, spec, expected } of cases) { + test(name, () => { + assert.deepEqual( + selectTests(tests, { includeExperimental: false, ...spec }).map( + (t) => t.name, + ), + expected, + ); + }); +} diff --git a/packages/taler-harness/src/integrationtests/test-selection.ts b/packages/taler-harness/src/integrationtests/test-selection.ts @@ -0,0 +1,77 @@ +/* + This file is part of GNU Taler + (C) 2026 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/> + */ + +import { minimatch } from "@gnu-taler/taler-util"; + +export interface TestSelectionSpec { + includePattern?: string; + suiteSpec?: string; + includeExperimental: boolean; +} + +interface SelectableTest { + name: string; + suites?: readonly string[]; + experimental?: boolean; +} + +// These suites require an explicit --suites entry, even when a test pattern +// matches or --experimental is given. Other suites remain enabled by default. +const disabledByDefaultSuites: ReadonlySet<string> = new Set([ + "wallet-android", +]); + +export function isTestDisabledByDefault( + test: Pick<SelectableTest, "suites">, +): boolean { + return ( + test.suites?.some((suite) => disabledByDefaultSuites.has(suite)) ?? false + ); +} + +export function selectTests<T extends SelectableTest>( + tests: readonly T[], + spec: TestSelectionSpec, +): T[] { + const suites = spec.suiteSpec + ? new Set(spec.suiteSpec.split(",").map((suite) => suite.trim())) + : undefined; + const patterns = spec.includePattern?.split(","); + + return tests.filter((test) => { + if ( + patterns && + !patterns.some((pattern) => minimatch(test.name, pattern)) + ) { + return false; + } + if (test.experimental && !spec.includeExperimental) { + return false; + } + // Membership in an ordinary suite must not bypass the required opt-in. + if ( + isTestDisabledByDefault(test) && + !test.suites?.some( + (suite) => disabledByDefaultSuites.has(suite) && suites?.has(suite), + ) + ) { + return false; + } + return ( + !suites || (test.suites?.some((suite) => suites.has(suite)) ?? false) + ); + }); +} diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -15,11 +15,7 @@ */ import { runTransactionFinalAmountsTest } from "./test-transaction-final-amounts.js"; -import { - Logger, - minimatch, - setGlobalLogLevelFromString, -} from "@gnu-taler/taler-util"; +import { Logger, setGlobalLogLevelFromString } from "@gnu-taler/taler-util"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; @@ -309,6 +305,11 @@ import { runWithdrawalManualTest } from "./test-withdrawal-manual.js"; import { runWithdrawalShortenTest } from "./test-withdrawal-shorten.js"; import { TodoBugUrl, validateTodoBugUrl } from "./todo.js"; import { withWalletDbBackend } from "./wallet-db-backends.js"; +import { + isTestDisabledByDefault, + selectTests, + TestSelectionSpec, +} from "./test-selection.js"; /** * Test runner. @@ -590,16 +591,13 @@ const allTests: TestMainFunction[] = [ runMerchantOrderListingTest, ]; -export interface TestRunSpec { +export interface TestRunSpec extends TestSelectionSpec { /** Service log audit policy, defaulting to warn. */ logAudit?: LogAuditMode; - includePattern?: string; - suiteSpec?: string; testDir?: string; dryRun?: boolean; failFast?: boolean; waitOnFail?: boolean; - includeExperimental: boolean; /** * Treat failures of todo tests as real failures. */ @@ -618,6 +616,7 @@ export interface TestInfo { name: string; suites: string[]; experimental: boolean; + disabledByDefault: boolean; todo: boolean; todoBugUrl?: string; } @@ -696,44 +695,15 @@ export async function runTests(spec: TestRunSpec) { process.on("SIGINT", handleSignal); process.on("SIGTERM", handleSignal); - let suites: Set<string> | undefined; - - if (spec.suiteSpec) { - suites = new Set(spec.suiteSpec.split(",").map((x) => x.trim())); - } - - const filteredTests: TestMainFunction[] = []; - - const patterns = spec.includePattern?.split(","); - - for (const [, testCase] of allTests.entries()) { - const testName = getTestName(testCase); - if (patterns) { - let matched = false; - for (const pat of patterns) { - if (minimatch(testName, pat)) { - matched = true; - break; - } - } - if (!matched) { - continue; - } - } - - if (testCase.experimental && !spec.includeExperimental) { - continue; - } - - if (suites) { - const ts = new Set(testCase.suites ?? []); - const intersection = new Set([...suites].filter((x) => ts.has(x))); - if (intersection.size === 0) { - continue; - } - } - filteredTests.push(testCase); - } + const filteredTests = selectTests( + allTests.map((test) => ({ + name: getTestName(test), + suites: test.suites, + experimental: test.experimental, + test, + })), + spec, + ).map((entry) => entry.test); console.log(`selected ${filteredTests.length} tests`); @@ -1029,6 +999,7 @@ export function getTestInfo(): TestInfo[] { name: getTestName(x), suites: x.suites ?? [], experimental: x.experimental ?? false, + disabledByDefault: isTestDisabledByDefault(x), todo: x.todo !== undefined, todoBugUrl: x.todo, })); diff --git a/packages/taler-harness/src/integrationtests/todo.test.ts b/packages/taler-harness/src/integrationtests/todo.test.ts @@ -58,6 +58,7 @@ test("exposes the bug URL in todo test information", () => { name: "revocation", suites: ["wallet"], experimental: true, + disabledByDefault: false, todo: true, todoBugUrl: "https://bugs.taler.net/n/9828", });