From ea1cb6750292f5352b9f60b9b4b76cfa4925cd48 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 14 Sep 2026 19:03:50 +0200 Subject: [PATCH 1/4] Setup logging transport in tests Without a transport Winston will print annoying "Attempt to write logs with no transports" warnings all over the place. Setup a transport in tests that cause logging to suppress this. --- test/controller/Controller.js | 2 ++ test/controller/routes.js | 2 ++ test/host.js | 1 + test/host/Instance.js | 2 ++ test/host/server.js | 1 + test/integration/index.js | 11 +++-------- test/lib/ModStore.test.js | 1 + test/lib/bootstrap.test.js | 2 ++ test/lib/datastore.js | 1 + test/lib/link/connector.js | 3 ++- test/lib/link/link.js | 1 + test/lib/logging_utils.js | 1 + test/lib/rce_ops.js | 1 + test/lib/shared_commands.js | 1 + test/lib/subscriptions.test.js | 1 + test/messages/controller.test.js | 1 + test/messages/host.test.js | 1 + test/messages/mod.test.js | 1 + test/messages/plugin.test.js | 2 ++ test/setup_logging.js | 15 +++++++++++++++ 20 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 test/setup_logging.js diff --git a/test/controller/Controller.js b/test/controller/Controller.js index d2f014ca..c0fc29e6 100644 --- a/test/controller/Controller.js +++ b/test/controller/Controller.js @@ -12,6 +12,8 @@ import { addPluginConfigFields, } from "@clusterio/lib"; +import "../setup_logging.js"; + class MockEvent {} class MockInstanceConfig extends EventEmitter { diff --git a/test/controller/routes.js b/test/controller/routes.js index a15eccda..e86811e8 100644 --- a/test/controller/routes.js +++ b/test/controller/routes.js @@ -7,6 +7,8 @@ import { wait, expositionContentType } from "@clusterio/lib"; import * as routes from "@clusterio/controller/dist/node/src/routes.js"; import * as mock from "../mock.js"; +import "../setup_logging.js"; + describe("controller/src/routes", function() { let controller; let server; diff --git a/test/host.js b/test/host.js index 4fafc52e..d16fb265 100644 --- a/test/host.js +++ b/test/host.js @@ -6,6 +6,7 @@ import * as lib from "@clusterio/lib"; import Host, { _discoverInstances } from "@clusterio/host/dist/node/src/Host.js"; import Instance from "@clusterio/host/dist/node/src/Instance.js"; import { HostConnector } from "@clusterio/host/dist/node/host.js"; +import "./setup_logging.js"; describe("Host testing", function() { describe("discoverInstances()", function() { diff --git a/test/host/Instance.js b/test/host/Instance.js index afefe99b..81511911 100644 --- a/test/host/Instance.js +++ b/test/host/Instance.js @@ -6,6 +6,8 @@ import { PlayerStats, wait } from "@clusterio/lib"; import Instance from "@clusterio/host/dist/node/src/Instance.js"; import { MockConnector, MockLogger, MockServer } from "../mock.js"; +import "../setup_logging.js"; + const addr = lib.Address.fromShorthand; diff --git a/test/host/server.js b/test/host/server.js index 60a0e681..b3469fce 100644 --- a/test/host/server.js +++ b/test/host/server.js @@ -7,6 +7,7 @@ import * as hostServer from "@clusterio/host/dist/node/src/server.js"; import { Link, wait } from "@clusterio/lib"; import { testLines } from "../lib/factorio/lines.js"; import { slowTest, externalTest } from "../integration/index.js"; +import "../setup_logging.js"; describe("host/server", function() { diff --git a/test/integration/index.js b/test/integration/index.js index 305d6769..11db0d8e 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -8,11 +8,13 @@ import util from "node:util"; import events from "node:events"; import * as lib from "@clusterio/lib"; -import { LineSplitter, ConsoleTransport, logger } from "@clusterio/lib"; +import { LineSplitter, logger } from "@clusterio/lib"; import { selectTargetCommand, initialize as initializeCtl } from "@clusterio/ctl"; import { _listFactorioVersions } from "@clusterio/host/dist/node/src/server.js"; +import "../setup_logging.js"; + // Make sure permissions from plugins are loaded import "../../plugins/global_chat/dist/node/index.js"; import "../../plugins/player_auth/dist/node/index.js"; @@ -316,13 +318,6 @@ before(async function() { console.log("SILENT_TEST is present in env, loggers after bootstrap will be muted."); } - // Some integration tests may cause log events - logger.add(new ConsoleTransport({ - level: "info", - format: new lib.TerminalFormat(), - filter: () => !silent, - })); - // If fast test is enabled then output that it is if (process.env.FAST_TEST) { diff --git a/test/lib/ModStore.test.js b/test/lib/ModStore.test.js index 32e88e5e..cce56b59 100644 --- a/test/lib/ModStore.test.js +++ b/test/lib/ModStore.test.js @@ -9,6 +9,7 @@ const nativeFetch = global.fetch; import { ModStore, ModInfo, ModVersionEquality } from "@clusterio/lib"; // Adjust path based on compiled output import { externalTest } from "../integration/index.js"; +import "../setup_logging.js"; const MODS_DIR = path.join("temp", "test", "mod_store", "mods"); const CACHE_FILE = path.join(MODS_DIR, "mod-info-cache.json"); diff --git a/test/lib/bootstrap.test.js b/test/lib/bootstrap.test.js index 065aecc4..a5d00b88 100644 --- a/test/lib/bootstrap.test.js +++ b/test/lib/bootstrap.test.js @@ -4,6 +4,8 @@ import path from "node:path"; import * as lib from "@clusterio/lib"; +import "../setup_logging.js"; + describe("lib/bootstrap", function() { describe("loadConfigFromArgs()", function() { const folder = path.join("temp", "test", "bootstrap"); diff --git a/test/lib/datastore.js b/test/lib/datastore.js index b4dc5cee..322450aa 100644 --- a/test/lib/datastore.js +++ b/test/lib/datastore.js @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import * as lib from "@clusterio/lib"; import fs from "node:fs/promises"; import path from "node:path"; +import "../setup_logging.js"; class MockDatastoreProvider extends lib.DatastoreProvider { constructor() { diff --git a/test/lib/link/connector.js b/test/lib/link/connector.js index 851b9e78..7423cc70 100644 --- a/test/lib/link/connector.js +++ b/test/lib/link/connector.js @@ -1,8 +1,9 @@ import assert from "node:assert/strict"; import events from "node:events"; -import * as mock from "../../mock.js"; import * as lib from "@clusterio/lib"; +import * as mock from "../../mock.js"; +import "../../setup_logging.js"; describe("lib/link/connectors", function() { diff --git a/test/lib/link/link.js b/test/lib/link/link.js index 6519a08e..5a02dc10 100644 --- a/test/lib/link/link.js +++ b/test/lib/link/link.js @@ -3,6 +3,7 @@ import events from "node:events"; import * as lib from "@clusterio/lib"; import * as mock from "../../mock.js"; +import "../../setup_logging.js"; const addr = lib.Address.fromShorthand; diff --git a/test/lib/logging_utils.js b/test/lib/logging_utils.js index ba159555..054e49e4 100644 --- a/test/lib/logging_utils.js +++ b/test/lib/logging_utils.js @@ -4,6 +4,7 @@ import path from "node:path"; import { testLines } from "./factorio/lines.js"; import * as lib from "@clusterio/lib"; +import "../setup_logging.js"; describe("lib/logging_utils.js", function() { diff --git a/test/lib/rce_ops.js b/test/lib/rce_ops.js index 8ba32661..ae9942fb 100644 --- a/test/lib/rce_ops.js +++ b/test/lib/rce_ops.js @@ -1,5 +1,6 @@ import assert from "node:assert/strict"; import * as lib from "@clusterio/lib"; +import "../setup_logging.js"; describe("rce_ops", function() { describe("updatePackage", function() { diff --git a/test/lib/shared_commands.js b/test/lib/shared_commands.js index d40c27a0..d8c7dcc8 100644 --- a/test/lib/shared_commands.js +++ b/test/lib/shared_commands.js @@ -4,6 +4,7 @@ import path from "node:path"; import * as lib from "@clusterio/lib"; import { slowTest } from "../integration/index.js"; +import "../setup_logging.js"; describe("lib/shared_commands", function() { diff --git a/test/lib/subscriptions.test.js b/test/lib/subscriptions.test.js index 97e7f943..780d58b8 100644 --- a/test/lib/subscriptions.test.js +++ b/test/lib/subscriptions.test.js @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import * as lib from "@clusterio/lib"; import { MockController, MockConnector, MockControl } from "../mock.js"; import { testMatrix, testRoundTripJsonSerialisable } from "../common.js"; +import "../setup_logging.js"; const addr = lib.Address.fromShorthand; diff --git a/test/messages/controller.test.js b/test/messages/controller.test.js index d876a60c..b7bd90cc 100644 --- a/test/messages/controller.test.js +++ b/test/messages/controller.test.js @@ -6,6 +6,7 @@ import * as lib from "@clusterio/lib"; import { Controller, ControlConnection } from "@clusterio/controller"; import { testMatrix, testRoundTripJsonSerialisable } from "../common.js"; +import "../setup_logging.js"; describe("messages/controller", function() { /** @type {Controller} */ diff --git a/test/messages/host.test.js b/test/messages/host.test.js index fe40256a..25310748 100644 --- a/test/messages/host.test.js +++ b/test/messages/host.test.js @@ -3,6 +3,7 @@ import * as lib from "@clusterio/lib"; import { Host } from "@clusterio/host"; import { testMatrix, testRoundTripJsonSerialisable } from "../common.js"; +import "../setup_logging.js"; describe("messages/host", function() { /** @type {Host} */ diff --git a/test/messages/mod.test.js b/test/messages/mod.test.js index 7a80b2eb..7371665b 100644 --- a/test/messages/mod.test.js +++ b/test/messages/mod.test.js @@ -7,6 +7,7 @@ import { testMatrix, testRoundTripJsonSerialisable } from "../common.js"; import { Controller, ControlConnection } from "@clusterio/controller"; import { externalTest } from "../integration/index.js"; +import "../setup_logging.js"; describe("messages/mod", function() { /** @type {Controller} */ diff --git a/test/messages/plugin.test.js b/test/messages/plugin.test.js index 45e55048..38d03b9a 100644 --- a/test/messages/plugin.test.js +++ b/test/messages/plugin.test.js @@ -4,6 +4,8 @@ import * as lib from "@clusterio/lib"; import { Controller, ControlConnection } from "@clusterio/controller"; import { Host } from "@clusterio/host"; +import "../setup_logging.js"; + describe("messages/plugin", function() { /** @type {Host} */ let host; diff --git a/test/setup_logging.js b/test/setup_logging.js new file mode 100644 index 00000000..f5d8cec4 --- /dev/null +++ b/test/setup_logging.js @@ -0,0 +1,15 @@ +import { ConsoleTransport, TerminalFormat, logger } from "@clusterio/lib"; + +// Some integration tests may cause log events, import this file for such tests +// to avoid getting the "Attempt to write logs with no transports" warning when +// running tests individually. + +before(function() { + const silent = process.env.SILENT_TEST; + + logger.add(new ConsoleTransport({ + level: "info", + format: new TerminalFormat(), + filter: () => !silent, + })); +}); From 92d26620787ec03588899bdcfdcd350b2115247f Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 14 Sep 2026 19:12:17 +0200 Subject: [PATCH 2/4] Move slowTest and externalTest to test/common.js These are used outside of the integration tests, but including the integration index file causes the very slow setup of the contorller and host. Avoid the integration setup costs when running commands such as `pnpm mocha test/lib` by moving these utility functions to the common test functions. --- test/common.js | 22 ++++++++++++++++++++++ test/host/server.js | 2 +- test/integration/clusterio.js | 3 ++- test/integration/index.js | 19 ------------------- test/integration/instance.js | 4 ++-- test/integration/link.js | 3 ++- test/integration/patch.js | 2 +- test/integration/server.js | 3 ++- test/lib/ModStore.test.js | 2 +- test/lib/external/FactorioVersions.test.js | 2 +- test/lib/external/LatestReleases.test.js | 2 +- test/lib/shared_commands.js | 2 +- test/messages/mod.test.js | 6 ++---- 13 files changed, 38 insertions(+), 34 deletions(-) diff --git a/test/common.js b/test/common.js index b35116d9..12bf426e 100644 --- a/test/common.js +++ b/test/common.js @@ -33,3 +33,25 @@ export function testRoundTripJsonSerialisable(Class, tests) { } } } + + +/** + * Mark that this test takes a lot of time, or depends on a test that takes a + * lot of time. + */ +export function slowTest(test) { + if (process.env.FAST_TEST) { + test.skip(); + } + test.timeout(30000); +} + +/** + * Mark that this test depends on an external API and may be slow or flaky. + */ +export function externalTest(test) { + if (process.env.NO_EXTERNAL_TEST) { + test.skip(); + } + test.timeout(60000); +} diff --git a/test/host/server.js b/test/host/server.js index b3469fce..dd4789dc 100644 --- a/test/host/server.js +++ b/test/host/server.js @@ -6,7 +6,7 @@ import path from "node:path"; import * as hostServer from "@clusterio/host/dist/node/src/server.js"; import { Link, wait } from "@clusterio/lib"; import { testLines } from "../lib/factorio/lines.js"; -import { slowTest, externalTest } from "../integration/index.js"; +import { slowTest, externalTest } from "../common.js"; import "../setup_logging.js"; diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index b68f3809..01cd0681 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -10,11 +10,12 @@ import * as libBuildMod from "@clusterio/lib/build_mod.js"; import * as testStrings from "../lib/factorio/test_strings.js"; import { - TestControl, TestControlConnector, url, controlToken, slowTest, + TestControl, TestControlConnector, url, controlToken, execCtl, execCtlProcess, execController, execHost, sendRcon, getControl, spawnNode, instancesDir, factorioDir, databaseDir, controllerConfigPath, requiresFactorio, hasFactorio, } from "./index.js"; +import { slowTest } from "../common.js"; import { ControllerEcho, HostEchoReceived } from "../file/test_plugin/messages.js"; diff --git a/test/integration/index.js b/test/integration/index.js index 11db0d8e..48b018df 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -119,25 +119,6 @@ export class TestHostConnector extends lib.WebSocketClientConnector { } } -// Mark that this test takes a lot of time, or depends on a test that takes a lot of time. -export function slowTest(test) { - - if (process.env.FAST_TEST) { - test.skip(); - } - - test.timeout(30000); -} - -// Mark that this test depends on an external API and may be slow or flaky. -export function externalTest(test) { - if (process.env.NO_EXTERNAL_TEST) { - test.skip(); - } - - test.timeout(60000); -} - // Mark that this test or suite of tests requires a factorio install to run. export function requiresFactorio(testOrSuite) { if (testOrSuite.skip) { diff --git a/test/integration/instance.js b/test/integration/instance.js index 8bcdec7d..e82d3bfb 100644 --- a/test/integration/instance.js +++ b/test/integration/instance.js @@ -3,9 +3,9 @@ import assert from "node:assert/strict"; import * as lib from "@clusterio/lib"; import fs from "node:fs/promises"; -import { testMatrix } from "../common.js"; +import { slowTest, testMatrix } from "../common.js"; import { - slowTest, exec, execCtl, execCtlProcess, sendRcon, getControl, + execCtl, execCtlProcess, sendRcon, getControl, requiresFactorio, hasFactorio, instancesDir, } from "./index.js"; diff --git a/test/integration/link.js b/test/integration/link.js index 53b3c76b..8bfdd211 100644 --- a/test/integration/link.js +++ b/test/integration/link.js @@ -3,9 +3,10 @@ import events from "node:events"; import jwt from "jsonwebtoken"; import { - TestControlConnector, TestHostConnector, TestControl, get, exec, url, controlToken, slowTest, + TestControlConnector, TestHostConnector, TestControl, url, controlToken, } from "./index.js"; import { ConnectionClosed, ProtocolError, PolicyViolation, AuthenticationFailed } from "@clusterio/lib"; +import { slowTest } from "../common.js"; let tokenHost = jwt.sign({ aud: "host", host: 0 }, Buffer.from("TestSecretDoNotUse", "base64")); diff --git a/test/integration/patch.js b/test/integration/patch.js index d222ff6d..8f04302a 100644 --- a/test/integration/patch.js +++ b/test/integration/patch.js @@ -6,7 +6,7 @@ import path from "node:path"; import { patch, SaveModule } from "@clusterio/host/dist/node/src/patch.js"; import * as lib from "@clusterio/lib"; -import { slowTest } from "./index.js"; +import { slowTest } from "../common.js"; // The server integration test is required to run before this one diff --git a/test/integration/server.js b/test/integration/server.js index f0e08c30..fe3d129b 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -8,7 +8,8 @@ import { InstanceManager } from "@clusterio/controller"; import { FactorioServer, _getFactorioVersion } from "@clusterio/host/dist/node/src/server.js"; import { logger } from "@clusterio/lib"; -import { slowTest, factorioDir, requiresFactorio } from "./index.js"; +import { factorioDir, requiresFactorio } from "./index.js"; +import { slowTest } from "../common.js"; describe("Integration of host/src/server", function() { diff --git a/test/lib/ModStore.test.js b/test/lib/ModStore.test.js index cce56b59..4c7d1b0b 100644 --- a/test/lib/ModStore.test.js +++ b/test/lib/ModStore.test.js @@ -8,7 +8,7 @@ import JSZip from "jszip"; // Added for creating mock zips const nativeFetch = global.fetch; import { ModStore, ModInfo, ModVersionEquality } from "@clusterio/lib"; // Adjust path based on compiled output -import { externalTest } from "../integration/index.js"; +import { externalTest } from "../common.js"; import "../setup_logging.js"; const MODS_DIR = path.join("temp", "test", "mod_store", "mods"); diff --git a/test/lib/external/FactorioVersions.test.js b/test/lib/external/FactorioVersions.test.js index f63554d9..b87d9016 100644 --- a/test/lib/external/FactorioVersions.test.js +++ b/test/lib/external/FactorioVersions.test.js @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { fetchFactorioVersions } from "@clusterio/lib"; -import { slowTest, externalTest } from "../../integration/index.js"; +import { slowTest, externalTest } from "../../common.js"; describe("FactorioVersions", function() { describe("fetchFactorioVersions", function() { diff --git a/test/lib/external/LatestReleases.test.js b/test/lib/external/LatestReleases.test.js index 686b6b52..951e9376 100644 --- a/test/lib/external/LatestReleases.test.js +++ b/test/lib/external/LatestReleases.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { fetchLatestReleases, resolveReleaseChannel } from "@clusterio/lib"; -import { slowTest, externalTest } from "../../integration/index.js"; +import { slowTest, externalTest } from "../../common.js"; describe("LatestReleases", function() { describe("fetchLatestReleases", function() { diff --git a/test/lib/shared_commands.js b/test/lib/shared_commands.js index d8c7dcc8..740550ad 100644 --- a/test/lib/shared_commands.js +++ b/test/lib/shared_commands.js @@ -3,7 +3,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import * as lib from "@clusterio/lib"; -import { slowTest } from "../integration/index.js"; +import { slowTest } from "../common.js"; import "../setup_logging.js"; diff --git a/test/messages/mod.test.js b/test/messages/mod.test.js index 7371665b..486a6a2a 100644 --- a/test/messages/mod.test.js +++ b/test/messages/mod.test.js @@ -2,11 +2,9 @@ import assert from "node:assert/strict"; import * as lib from "@clusterio/lib"; import { ModDependencyResolveRequest, ModDependency, ModInfo } from "@clusterio/lib"; - -import { testMatrix, testRoundTripJsonSerialisable } from "../common.js"; - import { Controller, ControlConnection } from "@clusterio/controller"; -import { externalTest } from "../integration/index.js"; + +import { externalTest, testMatrix, testRoundTripJsonSerialisable } from "../common.js"; import "../setup_logging.js"; describe("messages/mod", function() { From 0c4fac37b017cf356182773b7425beaad6ef5500 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 14 Sep 2026 19:18:45 +0200 Subject: [PATCH 3/4] Move test/host.js into the test/host folder This is clearly part of the host tests. It should be inside of the host test folder. --- test/{host.js => host/Host.test.js} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename test/{host.js => host/Host.test.js} (99%) diff --git a/test/host.js b/test/host/Host.test.js similarity index 99% rename from test/host.js rename to test/host/Host.test.js index d16fb265..8332469a 100644 --- a/test/host.js +++ b/test/host/Host.test.js @@ -6,9 +6,9 @@ import * as lib from "@clusterio/lib"; import Host, { _discoverInstances } from "@clusterio/host/dist/node/src/Host.js"; import Instance from "@clusterio/host/dist/node/src/Instance.js"; import { HostConnector } from "@clusterio/host/dist/node/host.js"; -import "./setup_logging.js"; +import "../setup_logging.js"; -describe("Host testing", function() { +describe("host/src/Host", function() { describe("discoverInstances()", function() { it("should discover test instance", async function() { const instancePath = path.join("test", "file", "instances"); From 9fbff1e8e5db61e23e9863d3ea3f1a48323299c9 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 14 Sep 2026 19:19:23 +0200 Subject: [PATCH 4/4] Ignore errors in concurrent safeOutput test On Windows this test throws "EPERM: operation not permitted, rename " presumably because Windows prevents concurrent renames. This does not affect the test outcome, so ignore the error(s). --- test/lib/file_ops.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/lib/file_ops.js b/test/lib/file_ops.js index b338d927..dfaf905f 100644 --- a/test/lib/file_ops.js +++ b/test/lib/file_ops.js @@ -122,7 +122,8 @@ describe("lib/file_ops", function() { for (let i = 0; i < 20; i++) { writes.push(lib.safeOutputFile(target, `write ${i}`, "utf8")); } - await Promise.all(writes); + // Ignore errors from concurrently renaming onto the same file. + await Promise.allSettled(writes); await assertNoTemporary(target); assert.match(await fs.readFile(target, "utf8"), /^write \d+$/); });