Merge pull request #1141 from bbassie/test/plugin-integration

Finish the plugin system integration tests
This commit is contained in:
Cooldude2606 authored and GitHub committed 2026-10-28 23:35:32 +00:00
commit ebdbb1fe56
11 files changed
+120 -4

No files matched your search

+2 -1
View File
@@ -1,7 +1,7 @@
// Plugin exercising the plugin system in the integration tests, see #342.
// Written as plain JavaScript so that it does not need to be built.
import { ControllerEcho, HostEcho, HostEchoReceived } from "./messages.js";
import { ControllerEcho, HostEcho, HostEchoReceived, InstanceEcho } from "./messages.js";
export const plugin = {
name: "test_plugin",
@@ -17,5 +17,6 @@ export const plugin = {
ControllerEcho,
HostEcho,
HostEchoReceived,
InstanceEcho,
],
};
+11
View File
@@ -1,3 +1,7 @@
import * as lib from "@clusterio/lib";
import { InstanceEcho } from "./messages.js";
export default async function(context) {
const { instance, plugin, logger } = context;
logger.info("test_plugin instance loaded");
@@ -5,4 +9,11 @@ export default async function(context) {
instance.hooks.start.attach(plugin.name, async () => {
logger.info("test_plugin instance started");
});
instance.handle(InstanceEcho, async (request) => {
logger.info(`test_plugin instance echo ${request.text}`);
const text = lib.escapeString(request.text);
const response = await instance.sendRcon(`/sc rcon.print(remote.call("test_plugin", "echo", "${text}"))`);
return response.replace(/\n$/, "");
});
}
+19
View File
@@ -54,3 +54,22 @@ export class HostEchoReceived {
toJSON() { return this.text; }
static fromJSON(json) { return new this(json); }
}
// Sent from ctl to an instance, which answers with the text echoed back by
// the remote interface of the plugin's Lua module.
export class InstanceEcho {
static type = "request";
static src = "control";
static dst = "instance";
static plugin = "test_plugin";
static permission = null;
static Response = JsonString;
constructor(text) {
this.text = text;
}
static jsonSchema = { type: "string" };
toJSON() { return this.text; }
static fromJSON(json) { return new this(json); }
}
+4
View File
@@ -0,0 +1,4 @@
{
"name": "test_plugin",
"require": ["test_plugin.lua"]
}
@@ -0,0 +1,5 @@
remote.add_interface("test_plugin", {
echo = function(text)
return text
end,
})
+8
View File
@@ -0,0 +1,8 @@
// Instance plugin without a module/ folder, for tests that need one.
export const plugin = {
name: "test_plugin_no_module",
title: "Test Plugin Without Module",
description: "Plugin used to test handling of plugins without a module.",
instanceEntrypoint: "instance.js",
};
@@ -0,0 +1 @@
export default async function() { }
@@ -0,0 +1,11 @@
{
"name": "test_plugin_no_module",
"version": "0.0.1",
"description": "Plugin without a Lua module used by the host tests",
"private": true,
"type": "module",
"main": "index.js",
"keywords": [
"clusterio-plugin"
]
}
+1 -1
View File
@@ -154,7 +154,7 @@ describe("host/create_scenario", function() {
await fs.cp(scenario, freeplay, { recursive: true });
pluginInfos = await lib.loadPluginInfos(new Map([
["research_sync", path.resolve("plugins/research_sync")],
["test_plugin", path.resolve("test/file/test_plugin")],
["test_plugin_no_module", path.resolve("test/file/test_plugin_no_module")],
]));
lib.addPluginConfigFields(pluginInfos);
hostConfig = new lib.HostConfig("host", { "host.factorio_directory": factorioDir });
+1 -1
View File
@@ -241,7 +241,7 @@ describe("host/patch", function() {
});
it("should skip plugins without a module", async function() {
const pluginInfos = await lib.loadPluginInfos(
new Map([["test_plugin", path.resolve("test/file/test_plugin")]])
new Map([["test_plugin_no_module", path.resolve("test/file/test_plugin_no_module")]])
);
const modules = await patch.loadModules(pluginInfos);
assert.deepEqual([...modules.keys()], ["clusterio"]);
+57 -1
View File
@@ -17,7 +17,7 @@ import {
} from "./index.js";
import { externalTest, slowTest } from "../common.js";
import { getTestRconPort } from "./rcon_port.js";
import { ControllerEcho, HostEchoReceived } from "../file/test_plugin/messages.js";
import { ControllerEcho, HostEchoReceived, InstanceEcho } from "../file/test_plugin/messages.js";
/** @returns {Promise<Map<number, lib.InstanceDetails>>} */
@@ -1002,6 +1002,62 @@ describe("Integration of Clusterio", function() {
});
});
describe("test_plugin on a running instance", function() {
requiresFactorio(this);
before(async function() {
this.timeout(20000);
if ((await getInstances()).get(44).status !== "running") {
await execCtl("instance start test");
}
});
async function findLog(filter, message) {
// Host and instance logs are forwarded to the controller asynchronously.
for (let attempt = 0; attempt < 100; attempt++) {
const { log } = await getControl().send(
new lib.LogQueryRequest(false, filter.controller ?? false, filter.hostIds ?? [],
filter.instanceIds ?? [], undefined, 100, "desc")
);
const entry = log.find(info => info.message === message);
if (entry) {
return entry;
}
await wait(20);
}
throw new Error(`log entry "${message}" not found`);
}
it("should route a plugin request from control to an instance", async function() {
const response = await getControl().sendTo({ instanceId: 44 }, new InstanceEcho("to-instance"));
assert.equal(response, "to-instance");
});
it("should patch the plugin's Lua module into the save", async function() {
const result = await sendRcon(44, "/sc rcon.print(remote.interfaces.test_plugin.echo)");
assert.equal(result, "true\n");
});
it("should tag plugin log entries with the plugin and where they came from", async function() {
await getControl().sendTo("controller", new ControllerEcho("log-tagging"));
await getControl().sendTo({ instanceId: 44 }, new InstanceEcho("log-tagging"));
const controllerEntry = await findLog({ controller: true }, "test_plugin controller echo log-tagging");
assert.equal(controllerEntry.plugin, "test_plugin");
assert.equal(controllerEntry.host_id, undefined);
assert.equal(controllerEntry.instance_id, undefined);
const hostEntry = await findLog({ hostIds: [4] }, "test_plugin host echo log-tagging");
assert.equal(hostEntry.plugin, "test_plugin");
assert.equal(hostEntry.host_id, 4);
assert.equal(hostEntry.instance_id, undefined);
const instanceEntry = await findLog({ instanceIds: [44] }, "test_plugin instance echo log-tagging");
assert.equal(instanceEntry.plugin, "test_plugin");
assert.equal(instanceEntry.host_id, 4);
assert.equal(instanceEntry.instance_id, 44);
});
});
describe("instance config set-prop", function() {
requiresFactorio(this);
it("applies factorio settings while running", async function() {