mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 06:46:09 +00:00
104 lines
3.5 KiB
TypeScript
104 lines
3.5 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
|
|
// Exercise the production service and stdin reader in a real Swift process.
|
|
// Only the device backend is simulated: CI must not request screen/mic access.
|
|
describe.skipIf(process.platform !== "darwin")("recorder process lifecycle", () => {
|
|
let directory: string;
|
|
let executable: string;
|
|
beforeAll(() => {
|
|
directory = mkdtempSync(join(tmpdir(), "recordly-lifecycle-"));
|
|
executable = join(directory, "helper");
|
|
const source = readFileSync(
|
|
new URL("./ScreenCaptureKitRecorder.swift", import.meta.url),
|
|
"utf8",
|
|
);
|
|
const service = source.slice(
|
|
source.indexOf("final class RecorderService"),
|
|
source.indexOf("guard CommandLine.arguments.count"),
|
|
);
|
|
const commands = source
|
|
.slice(
|
|
source.indexOf("let service = RecorderService()"),
|
|
source.indexOf("if !service.waitUntilFinished()"),
|
|
)
|
|
.replace(
|
|
"service.stop()",
|
|
"service.stop()\n\tservice.drainCommandsForTest()\n\tcommandsFinished.signal()",
|
|
);
|
|
writeFileSync(
|
|
join(directory, "main.swift"),
|
|
`
|
|
import Foundation
|
|
final class ScreenCaptureRecorder {
|
|
var output = ""
|
|
func startCapture(configJSON: String) async throws {
|
|
try await Task.sleep(nanoseconds: 50_000_000)
|
|
if configJSON == "fail" { throw NSError(domain: "startup", code: 1) }
|
|
output = configJSON
|
|
try "recording".write(toFile: output, atomically: true, encoding: .utf8)
|
|
}
|
|
func stopCapture() async throws -> String {
|
|
print("BACKEND_STOP")
|
|
fflush(stdout)
|
|
if output.isEmpty { throw NSError(domain: "inactive", code: 2) }
|
|
try "finalized".write(toFile: output, atomically: true, encoding: .utf8)
|
|
return output
|
|
}
|
|
func pauseCapture() async -> Bool { true }
|
|
func resumeCapture() async -> Bool { true }
|
|
}
|
|
${service}
|
|
extension RecorderService {
|
|
func drainCommandsForTest() { queue.sync {} }
|
|
}
|
|
let commandsFinished = DispatchSemaphore(value: 0)
|
|
${commands}
|
|
let success = service.waitUntilFinished()
|
|
// Wait for the reader to enqueue stop AND for the serialized operations to finish.
|
|
commandsFinished.wait()
|
|
exit(success ? 0 : 1)
|
|
`,
|
|
);
|
|
const build = spawnSync(
|
|
"swiftc",
|
|
[
|
|
"-module-cache-path",
|
|
join(directory, "cache"),
|
|
join(directory, "main.swift"),
|
|
"-o",
|
|
executable,
|
|
],
|
|
{ encoding: "utf8", timeout: 60_000 },
|
|
);
|
|
expect(build.status, build.stderr).toBe(0);
|
|
}, 60_000);
|
|
afterAll(() => {
|
|
if (directory) rmSync(directory, { recursive: true, force: true });
|
|
});
|
|
|
|
it.each([
|
|
"",
|
|
"stop\n",
|
|
"pause\nresume\n",
|
|
"stop\nstop\n",
|
|
])("finalizes once with stdin %j", (input) => {
|
|
const output = join(directory, "recording");
|
|
const result = spawnSync(executable, [output], { input, encoding: "utf8", timeout: 5_000 });
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(result.stdout.match(/BACKEND_STOP/g)).toHaveLength(1);
|
|
expect(readFileSync(output, "utf8")).toBe("finalized");
|
|
});
|
|
|
|
it.each(["", "stop\n"])("exits cleanly after failed startup with stdin %j", (input) => {
|
|
const result = spawnSync(executable, ["fail"], { input, encoding: "utf8", timeout: 5_000 });
|
|
expect(result.signal, result.stderr).toBeNull();
|
|
expect(result.status, result.stderr).toBe(1);
|
|
expect(result.stderr).toContain("Error starting capture");
|
|
expect(result.stdout).not.toContain("BACKEND_STOP");
|
|
});
|
|
});
|