examples: one runnable file per recipe on the 2.0 API, the 1.x files in examples/legacy
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>
This commit is contained in:
parent
2cbcde563e
commit
8379a65d78
25 changed files with 1705 additions and 0 deletions
53
test/helpers/examples.js
Normal file
53
test/helpers/examples.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"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 };
|
||||
Loading…
Add table
Add a link
Reference in a new issue