Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/export-miner-prometheus-textfile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ set -eu
# aborting the whole export -- Prometheus treats an absent series as "no data", not an error, so a partial
# export is strictly better than a stale-forever or entirely-missing one.

MINER_BIN="${LOOPOVER_MINER_BIN:-gittensory-miner}"
MINER_BIN="${LOOPOVER_MINER_BIN:-loopover-miner}"
OUT_FILE="${LOOPOVER_MINER_PROMETHEUS_TEXTFILE:-/var/lib/node_exporter/textfile_collector/gittensory_miner.prom}"
TMP_FILE="${OUT_FILE}.tmp"

Expand Down
101 changes: 101 additions & 0 deletions test/unit/export-miner-prometheus-textfile-script.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { execFileSync } from "node:child_process";
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";

// export-miner-prometheus-textfile.sh (#4839): regression for #5934, where the script's default MINER_BIN
// fallback pointed at `gittensory-miner`, a binary the package never installs (its real `bin` entry is
// `loopover-miner`). With no LOOPOVER_MINER_BIN override -- the script's own documented zero-config usage --
// every `export_family` call hit "command not found" and, being fail-open per family, silently produced an
// empty .prom file instead of erroring loudly.

const tmpRoots: string[] = [];

function tmpRoot(): string {
const dir = mkdtempSync(join(tmpdir(), "miner-prometheus-textfile-"));
tmpRoots.push(dir);
return dir;
}

afterEach(() => {
for (const dir of tmpRoots.splice(0)) rmSync(dir, { force: true, recursive: true });
});

/** A fake `loopover-miner` that answers each of the script's four subcommands with distinguishable text, so
* tests can assert the export actually reached it (as opposed to falling back to a nonexistent binary). */
function fakeMinerBin(root: string, name = "loopover-miner"): string {
const bin = join(root, "bin");
execFileSync("mkdir", ["-p", bin]);
const path = join(bin, name);
writeFileSync(
path,
`#!/bin/sh
case "$1 $2" in
"metrics ") echo "fake_prediction_calibration 1" ;;
"queue metrics") echo "fake_portfolio_queue 1" ;;
"ledger metrics") echo "fake_event_ledger 1" ;;
"governor metrics") echo "fake_governor 1" ;;
*) echo "unexpected args: $*" >&2; exit 2 ;;
esac
`,
);
chmodSync(path, 0o755);
return bin;
}

function runExport(outFile: string, env: Record<string, string> = {}): { stdout: string; stderr: string } {
try {
const stdout = execFileSync("sh", ["scripts/export-miner-prometheus-textfile.sh"], {
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
LOOPOVER_MINER_PROMETHEUS_TEXTFILE: outFile,
...env,
},
stdio: ["ignore", "pipe", "pipe"],
});
return { stdout, stderr: "" };
} catch (error) {
const err = error as { stdout?: Buffer; stderr?: Buffer };
return { stdout: err.stdout?.toString() ?? "", stderr: err.stderr?.toString() ?? "" };
}
}

describe("export-miner-prometheus-textfile.sh (#5934)", () => {
it("defaults MINER_BIN to loopover-miner, not the nonexistent gittensory-miner", () => {
const script = readFileSync(join(process.cwd(), "scripts/export-miner-prometheus-textfile.sh"), "utf8");
expect(script).toMatch(/MINER_BIN="\$\{LOOPOVER_MINER_BIN:-loopover-miner\}"/);
});

it("REGRESSION: with no LOOPOVER_MINER_BIN override, invokes the real loopover-miner binary and populates every family", () => {
const root = tmpRoot();
const bin = fakeMinerBin(root);
const outFile = join(root, "out", "miner.prom");

const result = runExport(outFile, { PATH: `${bin}:${process.env.PATH ?? ""}` });

expect(result.stderr).not.toMatch(/export failed/);
const content = readFileSync(outFile, "utf8");
expect(content).toContain("fake_prediction_calibration 1");
expect(content).toContain("fake_portfolio_queue 1");
expect(content).toContain("fake_event_ledger 1");
expect(content).toContain("fake_governor 1");
});

it("still honors an explicit LOOPOVER_MINER_BIN override", () => {
const root = tmpRoot();
const bin = fakeMinerBin(root, "custom-miner-bin");
const outFile = join(root, "out", "miner.prom");

const result = runExport(outFile, {
PATH: process.env.PATH ?? "",
LOOPOVER_MINER_BIN: join(bin, "custom-miner-bin"),
});

expect(result.stderr).not.toMatch(/export failed/);
const content = readFileSync(outFile, "utf8");
expect(content).toContain("fake_prediction_calibration 1");
});
});