140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
import fs from "fs";
|
|
import waitOn from "wait-on";
|
|
import axios from "axios";
|
|
import { spawn, execSync, ChildProcess } from "child_process";
|
|
import os from "os";
|
|
import path from "path";
|
|
import { Transform } from "stream";
|
|
|
|
const BINARY = "./evcc";
|
|
|
|
function workerPort() {
|
|
const index = Number(process.env["TEST_WORKER_INDEX"] ?? 0);
|
|
return 11000 + index;
|
|
}
|
|
|
|
function logPrefix() {
|
|
return `[worker:${process.env["TEST_WORKER_INDEX"]}]`;
|
|
}
|
|
|
|
function createSteamLog() {
|
|
return new Transform({
|
|
transform(chunk: Buffer, _, callback) {
|
|
const lines = chunk.toString().split("\n");
|
|
lines.forEach((line: string) => {
|
|
if (line.trim()) log(line);
|
|
});
|
|
callback();
|
|
},
|
|
});
|
|
}
|
|
|
|
function log(...args: any[]) {
|
|
console.log(logPrefix(), ...args);
|
|
}
|
|
|
|
export function baseUrl() {
|
|
return `http://localhost:${workerPort()}`;
|
|
}
|
|
|
|
function dbPath() {
|
|
const file = `evcc-${workerPort()}.db`;
|
|
return path.join(os.tmpdir(), file);
|
|
}
|
|
|
|
export async function start(
|
|
config: string,
|
|
sqlDumps?: string | null,
|
|
flags: string | string[] = "--disable-auth"
|
|
) {
|
|
await _clean();
|
|
if (sqlDumps) {
|
|
await _restoreDatabase(sqlDumps);
|
|
}
|
|
return await _start(config, flags);
|
|
}
|
|
|
|
export async function stop(instance?: ChildProcess) {
|
|
await _stop(instance);
|
|
await _clean();
|
|
}
|
|
|
|
export async function restart(config: string, flags = "--disable-auth") {
|
|
await _stop();
|
|
await _start(config, flags);
|
|
}
|
|
|
|
export async function cleanRestart(config: string, sqlDumps: string) {
|
|
await _stop();
|
|
await _clean();
|
|
if (sqlDumps) {
|
|
await _restoreDatabase(sqlDumps);
|
|
}
|
|
await _start(config);
|
|
}
|
|
|
|
async function _restoreDatabase(sqlDumps: string) {
|
|
const dumps = Array.isArray(sqlDumps) ? sqlDumps : [sqlDumps];
|
|
for (const dump of dumps) {
|
|
log("loading database", dbPath(), dump);
|
|
execSync(`sqlite3 ${dbPath()} < tests/${dump}`);
|
|
}
|
|
}
|
|
|
|
async function _start(config: string, flags: string | string[] = []) {
|
|
const configFile = config.includes("/") ? config : `tests/${config}`;
|
|
const port = workerPort();
|
|
log(`wait until port ${port} is available`);
|
|
// wait for port to be available
|
|
await waitOn({ resources: [`tcp:${port}`], reverse: true, log: true });
|
|
const additionalFlags = typeof flags === "string" ? [flags] : flags;
|
|
additionalFlags.push("--log", "debug,httpd:trace");
|
|
log("starting evcc", { config, port, additionalFlags });
|
|
const instance = spawn(BINARY, ["--config", configFile, ...additionalFlags], {
|
|
env: { EVCC_NETWORK_PORT: port.toString(), EVCC_DATABASE_DSN: dbPath() },
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
});
|
|
const steamLog = createSteamLog();
|
|
instance.stdout.pipe(steamLog);
|
|
instance.stderr.pipe(steamLog);
|
|
instance.on("exit", (code) => {
|
|
log("evcc terminated", { code, port, config });
|
|
steamLog.end();
|
|
});
|
|
await waitOn({ resources: [baseUrl()], log: true });
|
|
return instance;
|
|
}
|
|
|
|
async function _stop(instance?: ChildProcess) {
|
|
const port = workerPort();
|
|
if (instance) {
|
|
log("shutting down evcc hard", { port });
|
|
instance.kill("SIGKILL");
|
|
await waitOn({ resources: [`tcp:${port}`], reverse: true, log: true });
|
|
log("evcc is down", { port });
|
|
return;
|
|
}
|
|
// check if auth is required
|
|
const res = await axios.get(`${baseUrl()}/api/auth/status`);
|
|
log("auth status", res.status, res.statusText, res.data);
|
|
let cookie;
|
|
// login required
|
|
if (!res.data) {
|
|
const res = await axios.post(`${baseUrl()}/api/auth/login`, { password: "secret" });
|
|
log("login", res.status, res.statusText);
|
|
cookie = res.headers["set-cookie"];
|
|
}
|
|
log("shutting down evcc", { port });
|
|
await axios.post(`${baseUrl()}/api/system/shutdown`, {}, { headers: { cookie } });
|
|
log(`wait until port ${port} is closed`);
|
|
await waitOn({ resources: [`tcp:${port}`], reverse: true, log: true });
|
|
log("evcc is down", { port });
|
|
}
|
|
|
|
async function _clean() {
|
|
const db = dbPath();
|
|
if (fs.existsSync(db)) {
|
|
log("delete database", db);
|
|
fs.unlinkSync(db);
|
|
}
|
|
}
|