331 lines · 13.8 KB · TypeScript
RawDownload
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, describe, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Schema from "effect/Schema"; import * as DesktopConfig from "./DesktopConfig.ts";import * as DesktopEnvironment from "./DesktopEnvironment.ts";import * as DesktopObservability from "./DesktopObservability.ts"; const DesktopBackendChildLogRecord = Schema.Struct({  message: Schema.String,  level: Schema.Literals(["INFO", "ERROR"]),  timestamp: Schema.String,  annotations: Schema.Record(Schema.String, Schema.Unknown),  spans: Schema.Record(Schema.String, Schema.Unknown),  fiberId: Schema.String,}); const decodeDesktopBackendChildLogRecord = Schema.decodeEffect(  Schema.fromJsonString(DesktopBackendChildLogRecord),); const TraceRecordLine = Schema.Struct({  name: Schema.String,  attributes: Schema.Record(Schema.String, Schema.Unknown),  events: Schema.Array(    Schema.Struct({      name: Schema.String,      attributes: Schema.Record(Schema.String, Schema.Unknown),    }),  ),}); const decodeTraceRecordLine = Schema.decodeUnknownSync(Schema.fromJsonString(TraceRecordLine)); const environmentInput = (baseDir: string) =>  ({    dirname: "/repo/apps/desktop/dist-electron",    homeDirectory: baseDir,    platform: "darwin",    processArch: "arm64",    appVersion: "1.2.3",    appPath: "/repo",    isPackaged: false,    resourcesPath: "/repo/resources",    runningUnderArm64Translation: false,  }) satisfies DesktopEnvironment.MakeDesktopEnvironmentInput; const makeEnvironmentLayer = (baseDir: string, isDevelopment = true) =>  DesktopEnvironment.layer(environmentInput(baseDir)).pipe(    Layer.provide(      Layer.mergeAll(        NodeServices.layer,        DesktopConfig.layerTest({          T3CODE_HOME: baseDir,          VITE_DEV_SERVER_URL: isDevelopment ? "http://127.0.0.1:5733" : undefined,        }),      ),    ),  ); describe("DesktopObservability", () => {  it("advances a retained output offset instead of repeatedly copying a full head chunk", () => {    const maxBufferedBytes = 1024 * 1024;    const initial = DesktopObservability.appendBoundedOutputChunk(      {        runId: "test-run",        startDetails: "pid=123",        chunks: [],        byteLength: 0,      },      "stderr",      new Uint8Array(maxBufferedBytes),    );    const initialBackingBuffer = initial.chunks[0]?.chunk.buffer;     const next = DesktopObservability.appendBoundedOutputChunk(initial, "stderr", Uint8Array.of(1));     assert.equal(next.chunks[0]?.chunk.buffer, initialBackingBuffer);    assert.equal(next.chunks[0]?.offset, 1);    assert.equal(next.byteLength, maxBufferedBytes);  });   it.effect("persists desktop Effect logs as span events in desktop.trace.ndjson", () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const baseDir = yield* fileSystem.makeTempDirectoryScoped({        prefix: "t3-desktop-observability-test-",      });      const environmentLayer = makeEnvironmentLayer(baseDir);      const tracePath = yield* Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        return environment.path.join(environment.logDir, "desktop.trace.ndjson");      }).pipe(Effect.provide(environmentLayer));      const logPath = yield* Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        return environment.path.join(environment.logDir, "desktop-main.log");      }).pipe(Effect.provide(environmentLayer));       yield* Effect.scoped(        Effect.gen(function* () {          yield* Effect.annotateCurrentSpan({ "desktop.test": true });          yield* Effect.logInfo("desktop trace event");        }).pipe(          Effect.withSpan("desktop-observability-test"),          Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),        ),      );       const records = (yield* fileSystem.readFileString(tracePath))        .trim()        .split("\n")        .filter((line) => line.length > 0)        .map((line) => decodeTraceRecordLine(line));      const record = records.find((entry) => entry.name === "desktop-observability-test");       assert.notEqual(record, undefined);      if (!record) {        return;      }      assert.equal(record.attributes["desktop.test"], true);      assert.equal(        record.events.some((event) => event.name === "desktop trace event"),        true,      );      assert.isFalse(yield* fileSystem.exists(logPath));    }).pipe(      Effect.scoped,      Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),    ),  );   it.effect("buffers backend child output and persists it only when a failure is reported", () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const baseDir = yield* fileSystem.makeTempDirectoryScoped({        prefix: "t3-desktop-backend-output-log-test-",      });      const environmentLayer = makeEnvironmentLayer(baseDir, false);      const logPath = yield* Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        return environment.path.join(environment.logDir, "server-child.log");      }).pipe(Effect.provide(environmentLayer));      const tracePath = yield* Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        return environment.path.join(environment.logDir, "desktop.trace.ndjson");      }).pipe(Effect.provide(environmentLayer));       yield* Effect.gen(function* () {        const factory = yield* DesktopObservability.DesktopBackendOutputLogFactory;        const outputLog = yield* factory.forInstance("primary");        yield* outputLog.beginSession({          details: "pid=123 port=3773 cwd=/repo",        });        yield* outputLog.writeOutputChunk("stdout", new TextEncoder().encode("hello server\n"));        assert.isFalse(yield* fileSystem.exists(logPath));        yield* outputLog.persistFailure({ details: "code=1" });        yield* outputLog.beginSession({ details: "pid=456" });        yield* outputLog.writeOutputChunk("stderr", new TextEncoder().encode("normal shutdown\n"));        yield* outputLog.discardSession;      }).pipe(        Effect.annotateLogs({ runId: "test-run" }),        Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),      );       const log = yield* fileSystem.readFileString(logPath);      const lines = log.trimEnd().split("\n");      const start = yield* decodeDesktopBackendChildLogRecord(lines[0] ?? "");      const output = yield* decodeDesktopBackendChildLogRecord(lines[1] ?? "");      const end = yield* decodeDesktopBackendChildLogRecord(lines[2] ?? "");       assert.equal(lines.length, 3);      assert.equal(start.message, "backend child process failure output start");      assert.equal(start.level, "ERROR");      assert.equal(start.annotations.component, "desktop-backend-child");      assert.equal(start.annotations.runId, "test-run");      assert.equal(start.annotations.instanceId, "primary");      assert.equal(start.annotations.phase, "START");      assert.equal(start.annotations.details, "pid=123 port=3773 cwd=/repo");       assert.equal(output.message, "backend child process output");      assert.equal(output.level, "INFO");      assert.equal(output.annotations.component, "desktop-backend-child");      assert.equal(output.annotations.runId, "test-run");      assert.equal(output.annotations.instanceId, "primary");      assert.equal(output.annotations.stream, "stdout");      assert.equal(output.annotations.text, "hello server\n");       assert.equal(end.message, "backend child process failure output end");      assert.equal(end.level, "ERROR");      assert.equal(end.annotations.instanceId, "primary");      assert.equal(end.annotations.phase, "END");      assert.equal(end.annotations.details, "code=1");       const traceRecords = (yield* fileSystem.readFileString(tracePath))        .trim()        .split("\n")        .filter((line) => line.length > 0)        .map((line) => decodeTraceRecordLine(line));      assert.isFalse(        traceRecords.some(          (record) => record.name === "desktop.observability.backendOutput.writeOutputChunk",        ),      );    }).pipe(      Effect.scoped,      Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),    ),  );   it.effect("keeps buffering output after a non-terminal failure snapshot", () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const baseDir = yield* fileSystem.makeTempDirectoryScoped({        prefix: "t3-desktop-backend-output-snapshot-test-",      });      const environmentLayer = makeEnvironmentLayer(baseDir, false);      const logPath = yield* Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        return environment.path.join(environment.logDir, "server-child.log");      }).pipe(Effect.provide(environmentLayer));       yield* Effect.gen(function* () {        const factory = yield* DesktopObservability.DesktopBackendOutputLogFactory;        const outputLog = yield* factory.forInstance("primary");        yield* outputLog.beginSession({ details: "pid=123" });        yield* outputLog.writeOutputChunk("stdout", new TextEncoder().encode("before timeout\n"));        yield* outputLog.persistFailureSnapshot({ details: "readiness timeout" });        yield* outputLog.writeOutputChunk("stderr", new TextEncoder().encode("after timeout\n"));        yield* outputLog.persistFailure({ details: "code=1" });      }).pipe(        Effect.annotateLogs({ runId: "test-run" }),        Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),      );       const records = yield* Effect.forEach(        (yield* fileSystem.readFileString(logPath)).trimEnd().split("\n"),        (line) => decodeDesktopBackendChildLogRecord(line),      );      assert.equal(        records.some((record) => record.annotations.text === "after timeout\n"),        true,      );      assert.equal(records.at(-1)?.annotations.details, "code=1");    }).pipe(      Effect.scoped,      Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),    ),  );   it.effect("retains only the last mebibyte of backend child output", () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const baseDir = yield* fileSystem.makeTempDirectoryScoped({        prefix: "t3-desktop-backend-output-bound-test-",      });      const environmentLayer = makeEnvironmentLayer(baseDir, false);      const logPath = yield* Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        return environment.path.join(environment.logDir, "server-child.log");      }).pipe(Effect.provide(environmentLayer));      const maxBufferedBytes = 1024 * 1024;      const discardedPrefixBytes = 128;      const output = new Uint8Array(maxBufferedBytes + discardedPrefixBytes);      output.fill("x".charCodeAt(0));      output.fill("y".charCodeAt(0), 0, discardedPrefixBytes);       yield* Effect.scoped(        Effect.gen(function* () {          const factory = yield* DesktopObservability.DesktopBackendOutputLogFactory;          const outputLog = yield* factory.forInstance("primary");          yield* outputLog.beginSession({ details: "pid=123" });          yield* outputLog.writeOutputChunk("stderr", output);          yield* outputLog.persistFailure({ details: "code=1" });        }).pipe(          Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),        ),      );       const lines = (yield* fileSystem.readFileString(logPath)).trimEnd().split("\n");      const record = yield* decodeDesktopBackendChildLogRecord(lines[1] ?? "");      const text = record.annotations.text;      assert.equal(typeof text, "string");      if (typeof text !== "string") {        return;      }      assert.equal(new TextEncoder().encode(text).byteLength, maxBufferedBytes);      assert.isFalse(text.includes("y"));    }).pipe(      Effect.scoped,      Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),    ),  );   it.effect("bounds the number of retained backend child output chunks", () =>    Effect.gen(function* () {      const fileSystem = yield* FileSystem.FileSystem;      const baseDir = yield* fileSystem.makeTempDirectoryScoped({        prefix: "t3-desktop-backend-output-chunks-test-",      });      const environmentLayer = makeEnvironmentLayer(baseDir, false);      const logPath = yield* Effect.gen(function* () {        const environment = yield* DesktopEnvironment.DesktopEnvironment;        return environment.path.join(environment.logDir, "server-child.log");      }).pipe(Effect.provide(environmentLayer));       yield* Effect.scoped(        Effect.gen(function* () {          const factory = yield* DesktopObservability.DesktopBackendOutputLogFactory;          const outputLog = yield* factory.forInstance("primary");          yield* outputLog.beginSession({ details: "pid=123" });          for (let index = 0; index < 300; index += 1) {            yield* outputLog.writeOutputChunk("stderr", Uint8Array.of(index % 128));          }          yield* outputLog.persistFailure({ details: "code=1" });        }).pipe(          Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),        ),      );       const lines = (yield* fileSystem.readFileString(logPath)).trimEnd().split("\n");      assert.equal(lines.length, 258);    }).pipe(      Effect.scoped,      Effect.provide(Layer.mergeAll(NodeServices.layer, NodeHttpClient.layerUndici)),    ),  );});