aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/dashboard/check.js
blob: 95f805a5c02ac280d0be69acd02b080c653f5549 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Browser checks of the dashboard page (`cargo test --features dashboard
// dashboard_in_a_browser -- --ignored`, which serves this the page as the node does): the
// page loads without errors; the peers table has a row per peer, and an update changes
// cells without making the rows anew (`morphChildren`); the map draws a node per member;
// and with 300 members, laid out by round trip, the first drawing doesn't hold the page up
// for long (`LAYOUT_FOR_MS`).
//
// Needs Node.js, Playwright's library (`playwright-core`) and its Chromium; `check.nix`
// gives all three from nixpkgs.
//
// Usage: node check.js <page.html>

const { chromium } = require("playwright-core");

const page = process.argv[2];
const failures = [];
const check = (ok, what) => {
  if (!ok) failures.push(what);
  console.log((ok ? "ok   " : "FAIL ") + what);
};

(async () => {
  const browser = await chromium.launch();
  const tab = await browser.newPage({ viewport: { width: 1400, height: 900 } });
  const errors = [];
  tab.on("pageerror", error => errors.push(String(error)));
  await tab.goto("file://" + page);
  await tab.waitForTimeout(300);

  // The peers table, and an update to it.
  const peers = await tab.evaluate(() => {
    const peer = (alias, rtt) => ({ alias, status: "active", lastSeenSecsAgo: 1,
      destination: { address: "sim:" + alias, paths: [{ rttMs: rtt }] }, clock: null,
      neighbour: true, sameEpoch: true, epoch: { generation: 1, id: "ab" },
      reaches: ["a"], hopDelay: null });
    const members = { a: { self: true, alias: "a" }, b: { alias: "b" }, c: { alias: "c" },
      d: { alias: "d" } };
    state = { node: { id: "a", alias: "a" }, stats: {},
      peers: { localReaches: ["b", "c", "d"],
        peers: { b: peer("b", 10), c: peer("c", 20), d: peer("d", 30) } },
      graph: { members, intended: {}, measured: {} } };
    renderPeers();
    const before = [...document.querySelectorAll("#peers tr")];
    state.peers.peers.c.destination.paths[0].rttMs = 25;
    renderPeers();
    const after = [...document.querySelectorAll("#peers tr")];
    return { rows: after.length, same: before.every((row, i) => row === after[i]),
      rtt: after[2].textContent.includes("25.0 ms") };
  });
  check(peers.rows === 4, "the peers table has a header and a row per peer");
  check(peers.same, "an update keeps the table's rows");
  check(peers.rtt, "an update changes what changed");

  // The map, small and large.
  for (const [n, budget] of [[13, 500], [300, 1500]]) {
    const map = await tab.evaluate(n => {
      const ids = [...Array(n).keys()].map(i => "n" + String(i).padStart(3, "0"));
      const members = {};
      for (const id of ids) members[id] = { alias: id, self: id === "n000" };
      const intended = {}, measured = {};
      for (let i = 0; i < n; i++) {
        const neighbours = [1, 2, 7, 13].map(k => ids[(i + k) % n]).filter(id => id !== ids[i]);
        intended[ids[i]] = neighbours;
        measured[ids[i]] = { secsAgo: 1, links: neighbours.map(peer => ({ peer,
          rttMs: 20 + (i * 37) % 200, dormant: false, down: false, others: [] })) };
      }
      state = { node: { id: "n000", alias: "n000" }, stats: {},
        peers: { localReaches: [], peers: {} }, graph: { members, intended, measured } };
      byRtt = true; relayout = true; lastShape = null;
      const started = performance.now();
      renderGraph();
      const ms = performance.now() - started;
      const nodes = [...drawn.keys()].filter(key => key.startsWith("node ")).length;
      return { ms, nodes };
    }, n);
    check(map.nodes === n, `the map draws a node per member (${map.nodes} of ${n})`);
    check(map.ms < budget, `${n} members drawn in ${map.ms.toFixed(0)} ms (under ${budget})`);
  }

  await tab.waitForTimeout(500);
  check(errors.length === 0, "no errors on the page" + (errors.length ? ": " + errors : ""));
  await browser.close();
  process.exit(failures.length ? 1 : 0);
})().catch(error => {
  console.error(error);
  process.exit(2);
});