469 lines · 19.0 KB · TypeScript
RawDownload
import { DesktopHostTelemetryMessage } from "@t3tools/contracts";import { assert, describe, it } from "@effect/vitest";import * as Deferred from "effect/Deferred";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Fiber from "effect/Fiber";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Stream from "effect/Stream";import * as TestClock from "effect/testing/TestClock"; import type * as Electron from "electron"; import * as ElectronApp from "../electron/ElectronApp.ts";import * as ElectronPowerMonitor from "../electron/ElectronPowerMonitor.ts";import * as DesktopTelemetryPublisher from "./DesktopTelemetryPublisher.ts"; function makeElectronAppLayer(  metrics: ReadonlyArray<Electron.ProcessMetric>,  onMetricsRead: () => void = () => undefined,) {  return Layer.succeed(ElectronApp.ElectronApp, {    metadata: Effect.die("unexpected metadata read"),    name: Effect.succeed("T3 Code"),    systemLocale: Effect.succeed("en-US"),    whenReady: Effect.void,    quit: Effect.void,    exit: () => Effect.void,    relaunch: () => Effect.void,    setPath: () => Effect.void,    setName: () => Effect.void,    setAboutPanelOptions: () => Effect.void,    setAppUserModelId: () => Effect.void,    getAppMetrics: Effect.sync(() => {      onMetricsRead();      return metrics;    }),    isDefaultProtocolClient: () => Effect.succeed(false),    setAsDefaultProtocolClient: () => Effect.succeed(true),    setDesktopName: () => Effect.void,    setDockIcon: () => Effect.void,    appendCommandLineSwitch: () => Effect.void,    removeCommandLineSwitch: () => Effect.void,    onBeforeQuitForUpdate: () => Effect.void,    on: () => Effect.void,  } satisfies ElectronApp.ElectronApp["Service"]);} describe("DesktopTelemetryPublisher", () => {  it.effect("stops when its scope closes during an Electron telemetry sample", () =>    Effect.gen(function* () {      const pollStarted = yield* Deferred.make<void>();      const blockPoll = yield* Deferred.make<void>();      const powerLayer = Layer.succeed(        ElectronPowerMonitor.ElectronPowerMonitor,        ElectronPowerMonitor.ElectronPowerMonitor.of({          isOnBatteryPower: Effect.succeed(false),          getSystemIdleTime: Deferred.succeed(pollStarted, undefined).pipe(            Effect.andThen(Deferred.await(blockPoll)),            Effect.as(0),          ),          getSystemIdleState: () => Effect.succeed("active"),          getCurrentThermalState: Effect.succeed("nominal"),          onSimpleEvent: () => Effect.void,          onThermalStateChange: () => Effect.void,          onSpeedLimitChange: () => Effect.void,        }),      );      const layer = DesktopTelemetryPublisher.layer.pipe(        Layer.provide(Layer.mergeAll(makeElectronAppLayer([]), powerLayer)),      );      const scope = yield* Scope.make();       yield* Layer.buildWithScope(layer, scope);      yield* Deferred.await(pollStarted);       const closeFiber = yield* Scope.close(scope, Exit.void).pipe(Effect.forkDetach);      yield* Effect.yieldNow;      yield* Effect.yieldNow;       assert.isDefined(closeFiber.pollUnsafe());    }),  );   it.effect("publishes Electron metrics and event-driven power state over NDJSON", () =>    Effect.gen(function* () {      const onBattery = yield* Ref.make(false);      const systemIdleState = yield* Ref.make<ElectronPowerMonitor.ElectronIdleState>("active");      let beforeSystemIdleState: Effect.Effect<void> = Effect.void;      let metricsReadCount = 0;      const simpleListeners = new Map<string, () => void>();      let thermalListener: ((state: ElectronPowerMonitor.ElectronThermalState) => void) | null =        null;      let speedLimitListener: ((limit: number) => void) | null = null;      const metrics = [        {          pid: 4_242,          type: "Browser",          creationTime: 1_000.75,          name: "electron",          cpu: {            percentCPUUsage: 12.5,            cumulativeCPUUsage: 3.25,            idleWakeupsPerSecond: 7,          },          memory: {            workingSetSize: 2_048,            peakWorkingSetSize: 4_096,          },        } as Electron.ProcessMetric,      ];      const powerLayer = Layer.succeed(        ElectronPowerMonitor.ElectronPowerMonitor,        ElectronPowerMonitor.ElectronPowerMonitor.of({          isOnBatteryPower: Ref.get(onBattery),          getSystemIdleTime: Effect.succeed(5),          getSystemIdleState: () =>            beforeSystemIdleState.pipe(Effect.andThen(Ref.get(systemIdleState))),          getCurrentThermalState: Effect.succeed("nominal"),          onSimpleEvent: (eventName, listener) =>            Effect.sync(() => {              simpleListeners.set(eventName, listener);            }),          onThermalStateChange: (listener) =>            Effect.sync(() => {              thermalListener = listener;            }),          onSpeedLimitChange: (listener) =>            Effect.sync(() => {              speedLimitListener = listener;            }),        }),      );      const layer = DesktopTelemetryPublisher.layer.pipe(        Layer.provide(          Layer.mergeAll(            makeElectronAppLayer(metrics, () => {              metricsReadCount += 1;            }),            powerLayer,          ),        ),      );       yield* Effect.gen(function* () {        const publisher = yield* DesktopTelemetryPublisher.DesktopTelemetryPublisher;        const encoded = yield* publisher.encoded.pipe(Stream.take(2), Stream.runCollect);        const decoder = new TextDecoder();        const decodeMessage = Schema.decodeUnknownEffect(          Schema.fromJsonString(DesktopHostTelemetryMessage),        );        const messages = yield* Effect.forEach(encoded, (bytes) =>          decodeMessage(decoder.decode(bytes).trim()),        );         const hello = messages[0];        if (hello?.type !== "desktopTelemetryHello") {          return assert.fail("Expected the first telemetry message to be the hello.");        }        assert.equal(hello.electronPid, process.pid);        const initialSnapshot = messages[1];        if (initialSnapshot?.type !== "desktopTelemetry") {          return assert.fail("Expected the second telemetry message to be a snapshot.");        }        assert.deepEqual(initialSnapshot.electronProcesses, []);        assert.equal(initialSnapshot.electronPid, process.pid);        assert.equal(metricsReadCount, 0);         const nextSnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(Effect.forkChild);        yield* Effect.yieldNow;        yield* publisher.handleControl({          version: 1,          type: "setDiagnosticsDemand",          enabled: true,        });        const demandedSnapshot = Option.getOrThrow(yield* Fiber.join(nextSnapshotFiber));        assert.equal(demandedSnapshot.electronProcesses[0]?.pid, 4_242);        assert.equal(demandedSnapshot.electronProcesses[0]?.creationTimeMs, 1_001);        assert.equal(demandedSnapshot.electronProcesses[0]?.cpuPercent, 12.5);        assert.equal(demandedSnapshot.electronProcesses[0]?.workingSetBytes, 2_048 * 1_024);        assert.equal(metricsReadCount, 1);        yield* publisher.handleControlForSource("secondary-backend", {          version: 1,          type: "setDiagnosticsDemand",          enabled: true,        });        yield* publisher.handleControl({          version: 1,          type: "setDiagnosticsDemand",          enabled: false,        });        yield* publisher.handleControlForSource("old-backend", {          version: 1,          type: "setDiagnosticsDemand",          enabled: true,        });        yield* publisher.handleControlForSource("secondary-backend", {          version: 1,          type: "setDiagnosticsDemand",          enabled: false,        });        yield* Effect.all(          [            publisher.removeControlSource("old-backend"),            publisher.handleControlForSource("replacement-backend", {              version: 1,              type: "setDiagnosticsDemand",              enabled: true,            }),          ],          { concurrency: "unbounded", discard: true },        );         const batterySnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(          Effect.forkChild,        );        yield* Effect.yieldNow;        simpleListeners.get("on-battery")?.();        const batterySnapshot = Option.getOrThrow(yield* Fiber.join(batterySnapshotFiber));        assert.equal(batterySnapshot.power.onBattery, "true");        yield* Ref.set(onBattery, true);         const metricsAfterBatteryEvent = metricsReadCount;        yield* TestClock.adjust(Duration.millis(4_999));        assert.equal(metricsReadCount, metricsAfterBatteryEvent);        yield* TestClock.adjust(Duration.millis(1));        assert.equal(metricsReadCount, metricsAfterBatteryEvent + 1);        assert.equal((yield* publisher.latest).pipe(Option.getOrThrow).power.onBattery, "true");         yield* Ref.set(onBattery, false);        const metricsBeforePolledAc = metricsReadCount;        yield* TestClock.adjust(Duration.seconds(5));        assert.equal(metricsReadCount, metricsBeforePolledAc + 1);        assert.equal((yield* publisher.latest).pipe(Option.getOrThrow).power.onBattery, "false");        yield* TestClock.adjust(Duration.seconds(1));        assert.equal(metricsReadCount, metricsBeforePolledAc + 2);         const suspendedSnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(          Effect.forkChild,        );        yield* Effect.yieldNow;        simpleListeners.get("suspend")?.();        const suspendedSnapshot = Option.getOrThrow(yield* Fiber.join(suspendedSnapshotFiber));        assert.isTrue(suspendedSnapshot.power.suspended);         const constrainedSnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(          Effect.forkChild,        );        yield* Effect.yieldNow;        thermalListener?.("serious");        const constrainedSnapshot = Option.getOrThrow(yield* Fiber.join(constrainedSnapshotFiber));        assert.equal(constrainedSnapshot.power.thermalState, "serious");        assert.isTrue(constrainedSnapshot.power.suspended);         const metricsAfterThermalEvent = metricsReadCount;        const recoveredSnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(          Effect.forkChild,        );        yield* TestClock.adjust(Duration.millis(14_999));        assert.equal(metricsReadCount, metricsAfterThermalEvent);        yield* TestClock.adjust(Duration.millis(1));        assert.equal(metricsReadCount, metricsAfterThermalEvent + 1);        const recoveredSnapshot = Option.getOrThrow(yield* Fiber.join(recoveredSnapshotFiber));        assert.isFalse(recoveredSnapshot.power.suspended);         const speedLimitSnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(          Effect.forkChild,        );        yield* Effect.yieldNow;        speedLimitListener?.(65);        const speedLimitSnapshot = Option.getOrThrow(yield* Fiber.join(speedLimitSnapshotFiber));        assert.equal(Option.getOrNull(speedLimitSnapshot.speedLimitPercent), 65);         const encodedSpeedLimit = yield* publisher.encoded.pipe(Stream.take(2), Stream.runCollect);        const decodedSpeedLimit = yield* decodeMessage(decoder.decode(encodedSpeedLimit[1]).trim());        if (decodedSpeedLimit.type !== "desktopTelemetry") {          return assert.fail("Expected the encoded telemetry message to be a snapshot.");        }        assert.equal(Option.getOrNull(decodedSpeedLimit.speedLimitPercent), 65);        assert.equal(decodedSpeedLimit.electronProcesses[0]?.pid, 4_242);        assert.equal(decodedSpeedLimit.electronProcesses[0]?.creationTimeMs, 1_001);         const metricsBeforeSecondaryOnlySample = metricsReadCount;        yield* TestClock.adjust(Duration.seconds(15));        assert.equal(metricsReadCount, metricsBeforeSecondaryOnlySample + 1);        assert.equal(          (yield* publisher.latest).pipe(Option.getOrThrow).electronProcesses[0]?.pid,          4_242,        );         const stoppedSnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(          Effect.forkChild,        );        yield* Effect.yieldNow;        yield* publisher.handleControlForSource("replacement-backend", {          version: 1,          type: "setDiagnosticsDemand",          enabled: false,        });        const stoppedSnapshot = Option.getOrThrow(yield* Fiber.join(stoppedSnapshotFiber));        assert.deepEqual(stoppedSnapshot.electronProcesses, []);        const metricsAfterStopping = metricsReadCount;        const configuredSnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(          Effect.forkChild,        );        yield* Effect.yieldNow;        yield* publisher.handleControl({          version: 1,          type: "setHostPowerIntervals",          activeIntervalMs: 7_000,          idleIntervalMs: 11_000,        });        const configuredSequence = Option.getOrThrow(          yield* Fiber.join(configuredSnapshotFiber),        ).sequence;         yield* TestClock.adjust(Duration.millis(6_999));        assert.equal(          (yield* publisher.latest).pipe(Option.getOrThrow).sequence,          configuredSequence,        );        assert.equal(metricsReadCount, metricsAfterStopping);        yield* TestClock.adjust(Duration.millis(1));        assert.equal(          (yield* publisher.latest).pipe(Option.getOrThrow).sequence,          configuredSequence + 1,        );        assert.equal(metricsReadCount, metricsAfterStopping);         yield* Ref.set(systemIdleState, "locked");        yield* TestClock.adjust(Duration.seconds(7));        const lockedSequence = (yield* publisher.latest).pipe(Option.getOrThrow).sequence;        assert.equal((yield* publisher.latest).pipe(Option.getOrThrow).power.locked, "true");        yield* TestClock.adjust(Duration.millis(10_999));        assert.equal((yield* publisher.latest).pipe(Option.getOrThrow).sequence, lockedSequence);        yield* TestClock.adjust(Duration.millis(1));        assert.equal(          (yield* publisher.latest).pipe(Option.getOrThrow).sequence,          lockedSequence + 1,        );         yield* Ref.set(systemIdleState, "active");        yield* TestClock.adjust(Duration.seconds(11));        const unlockedSnapshot = (yield* publisher.latest).pipe(Option.getOrThrow);        assert.equal(unlockedSnapshot.power.locked, "false");        assert.equal(unlockedSnapshot.power.idle, "false");         yield* TestClock.adjust(Duration.millis(6_999));        assert.equal(          (yield* publisher.latest).pipe(Option.getOrThrow).sequence,          unlockedSnapshot.sequence,        );        yield* TestClock.adjust(Duration.millis(1));        assert.equal(          (yield* publisher.latest).pipe(Option.getOrThrow).sequence,          unlockedSnapshot.sequence + 1,        );         const pollStarted = yield* Deferred.make<void>();        const releasePoll = yield* Deferred.make<void>();        beforeSystemIdleState = Deferred.succeed(pollStarted, undefined).pipe(          Effect.andThen(Deferred.await(releasePoll)),        );        const concurrentEventSnapshotFiber = yield* Stream.runHead(publisher.changes).pipe(          Effect.forkChild,        );        yield* Effect.yieldNow;        yield* publisher.handleControl({          version: 1,          type: "setDiagnosticsDemand",          enabled: true,        });        yield* Deferred.await(pollStarted);        simpleListeners.get("lock-screen")?.();        thermalListener?.("critical");        yield* Effect.yieldNow;        yield* Effect.yieldNow;        yield* Deferred.succeed(releasePoll, undefined);         const concurrentEventSnapshot = Option.getOrThrow(          yield* Fiber.join(concurrentEventSnapshotFiber),        );        assert.equal(concurrentEventSnapshot.power.locked, "true");        assert.equal(concurrentEventSnapshot.power.thermalState, "critical");      }).pipe(Effect.provide(layer));    }),  );   it.effect("routes requestDesktopUpdate control messages and replays update reports", () =>    Effect.gen(function* () {      const powerLayer = Layer.succeed(        ElectronPowerMonitor.ElectronPowerMonitor,        ElectronPowerMonitor.ElectronPowerMonitor.of({          isOnBatteryPower: Effect.succeed(false),          getSystemIdleTime: Effect.succeed(0),          getSystemIdleState: () => Effect.succeed("active"),          getCurrentThermalState: Effect.succeed("nominal"),          onSimpleEvent: () => Effect.void,          onThermalStateChange: () => Effect.void,          onSpeedLimitChange: () => Effect.void,        }),      );      const layer = DesktopTelemetryPublisher.layer.pipe(        Layer.provide(Layer.mergeAll(makeElectronAppLayer([]), powerLayer)),      );       yield* Effect.gen(function* () {        const publisher = yield* DesktopTelemetryPublisher.DesktopTelemetryPublisher;         const requestFiber = yield* Stream.runHead(publisher.updateRequests).pipe(Effect.forkChild);        yield* Effect.yieldNow;        yield* publisher.handleControlForSource("test", {          version: 1,          type: "requestDesktopUpdate",          requestId: "req-9",        });        const received = yield* Fiber.join(requestFiber);        assert.equal(Option.getOrThrow(received).requestId, "req-9");         const report = {          version: 1,          type: "desktopUpdateStatus",          outcome: "up-to-date",          state: {            enabled: true,            status: "up-to-date",            channel: "latest",            currentVersion: "1.2.3",            hostArch: "arm64",            appArch: "arm64",            runningUnderArm64Translation: false,            availableVersion: null,            downloadedVersion: null,            releaseNotes: [],            downloadPercent: null,            checkedAt: null,            message: null,            errorContext: null,            canRetry: false,            omittedReleaseCount: 0,          },        } as const;        yield* publisher.publishUpdateReport(report);         // A subscriber that attaches after the publish (the backend spawned        // by a relaunch) still sees the latest report replayed.        const decoder = new TextDecoder();        const decodeMessage = Schema.decodeUnknownEffect(          Schema.fromJsonString(DesktopHostTelemetryMessage),        );        const replayed = yield* publisher.encoded.pipe(          Stream.mapEffect((bytes) => decodeMessage(decoder.decode(bytes).trim())),          Stream.filter((message) => message.type === "desktopUpdateStatus"),          Stream.runHead,        );        const replayedReport = Option.getOrThrow(replayed);        if (replayedReport.type !== "desktopUpdateStatus") {          return assert.fail("Expected a desktop update status report.");        }        assert.equal(replayedReport.outcome, "up-to-date");        assert.equal(replayedReport.state.currentVersion, "1.2.3");      }).pipe(Effect.provide(layer));    }),  );});