Ten recipes: lamp, colour lamp, thermostat, blind, lock (deferred), contact sensor (ChangeReport), scene, doorbell (raise), typed errors, and a plug as an ES module. Each reads ALEX2MQTT_USERNAME, _PASSWORD and _ROOT_TOPIC and names the ones that are missing. The nine examples of 1.5.2 move to examples/legacy unchanged. test/examples.test.js starts every file with node, its broker connection sent to a broker on 127.0.0.1 (test/helpers/loopback.js), discovers the device, sends directives and checks the answers. A legacy file is started and its discovery compared with the answer the 1.5.2 build gave, recorded in test/fixtures/legacy-examples. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
53 lines
2.4 KiB
JavaScript
53 lines
2.4 KiB
JavaScript
"use strict";
|
|
// Runs a file of examples/ as its reader does, with node, in a process of its own. The process gets the environment
|
|
// the test names and nothing else, a directory without a .env file, and test/helpers/loopback.js, which sends its
|
|
// broker connection to the broker of the test.
|
|
const { spawn } = require("node:child_process");
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
const { cleanup, until } = require("./harness.js");
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
const LOOPBACK = path.join(__dirname, "loopback.js");
|
|
|
|
/**
|
|
* Start examples/<file>. broker is one of harness.broker(); without it the example can connect nowhere. Resolves
|
|
* with { child, output, write, exited, stop } once the example has subscribed to <root>/discover or, without a
|
|
* root, at once. output() is what it wrote to stdout and stderr so far.
|
|
*/
|
|
async function run(file, { broker, root, env = {}, directory = path.join(ROOT, "examples") } = {}) {
|
|
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "alex2node-example-"));
|
|
let subscribed = false;
|
|
const onSubscribe = (subscriptions) => {
|
|
if (subscriptions.some(({ topic }) => topic === `${root}/discover` || topic === `${root}/#`)) subscribed = true;
|
|
};
|
|
if (broker && root) broker.aedes.on("subscribe", onSubscribe);
|
|
|
|
const child = spawn(process.execPath, ["--require", LOOPBACK, path.join(directory, file)], {
|
|
cwd,
|
|
env: { PATH: process.env.PATH, LOOPBACK_PORT: String(broker ? broker.port : 1), ...env },
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
});
|
|
let text = "";
|
|
child.stdout.on("data", (chunk) => { text += chunk; });
|
|
child.stderr.on("data", (chunk) => { text += chunk; });
|
|
const exited = new Promise((resolve) => child.once("exit", (code) => resolve(code)));
|
|
const stop = async () => {
|
|
if (broker && root) broker.aedes.off("subscribe", onSubscribe);
|
|
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
|
|
await exited;
|
|
fs.rmdirSync(cwd);
|
|
};
|
|
cleanup(stop);
|
|
|
|
const started = { child, output: () => text, write: (line) => child.stdin.write(line), exited, stop };
|
|
if (broker && root) {
|
|
await until(() => subscribed || child.exitCode !== null, 10000, `${file} to subscribe`);
|
|
broker.aedes.off("subscribe", onSubscribe);
|
|
if (!subscribed) throw new Error(`${file} ended before it subscribed:\n${text}`);
|
|
}
|
|
return started;
|
|
}
|
|
|
|
module.exports = { run, ROOT };
|