Skip to content
Source
Sign inSign up

ProviderLatestVersions.test.ts

136 lines4.9 KB

1import { describe, expect, it } from "@effect/vitest";
2import * as Deferred from "effect/Deferred";
3import * as Effect from "effect/Effect";
4import * as Exit from "effect/Exit";
5import * as Fiber from "effect/Fiber";
6​
7import * as ProviderLatestVersions from "./ProviderLatestVersions.ts";
8​
9describe("ProviderLatestVersions", () => {
10 it.effect("concurrent misses for one key share a single lookup", () =>
11 Effect.gen(function* () {
12 const latestVersions = yield* ProviderLatestVersions.make();
13 const release = yield* Deferred.make<void>();
14 let lookups = 0;
15 const lookup = Effect.suspend(() => {
16 lookups += 1;
17 return Deferred.await(release).pipe(Effect.as("2.0.0"));
18 });
19​
20 const callers = yield* Effect.forEach([1, 2, 3], () =>
21 Effect.forkChild(latestVersions.cached("@example/cli", lookup), {
22 startImmediately: true,
23 }),
24 );
25 yield* Deferred.succeed(release, undefined);
26 expect(yield* Effect.forEach(callers, Fiber.join)).toEqual(["2.0.0", "2.0.0", "2.0.0"]);
27 expect(lookups).toBe(1);
28 }),
29 );
30​
31 it.effect("a waiting caller looks up again when the shared lookup is interrupted", () =>
32 Effect.gen(function* () {
33 const latestVersions = yield* ProviderLatestVersions.make();
34 let lookups = 0;
35 const lookup = Effect.suspend(() => {
36 lookups += 1;
37 return lookups === 1 ? Effect.never : Effect.succeed("2.0.0");
38 });
39​
40 const owner = yield* Effect.forkChild(latestVersions.cached("@example/cli", lookup), {
41 startImmediately: true,
42 });
43 const waiter = yield* Effect.forkChild(latestVersions.cached("@example/cli", lookup), {
44 startImmediately: true,
45 });
46 expect(lookups).toBe(1);
47 yield* Fiber.interrupt(owner);
48 expect(yield* Fiber.join(waiter)).toBe("2.0.0");
49 expect(yield* latestVersions.cached("@example/cli", lookup)).toBe("2.0.0");
50 expect(lookups).toBe(2);
51 }),
52 );
53​
54 it.effect("a caller interrupted while it claims the lookup frees the key", () =>
55 Effect.gen(function* () {
56 const latestVersions = yield* ProviderLatestVersions.make();
57 let lookups = 0;
58 const lookup = Effect.sync(() => {
59 lookups += 1;
60 return "2.0.0";
61 });
62 // `cached` reads `fresh` in the same step that claims the key, so the
63 // interrupt arrives after the claim and before the lookup starts.
64 const interruptOnClaim = {
65 get fresh() {
66 Fiber.getCurrent()?.interruptUnsafe();
67 return false;
68 },
69 };
70​
71 const owner = yield* Effect.forkChild(
72 latestVersions.cached("@example/cli", lookup, interruptOnClaim),
73 { startImmediately: true },
74 );
75 expect(Exit.hasInterrupts(yield* Fiber.await(owner))).toBe(true);
76 expect(lookups).toBe(0);
77 const next = yield* Effect.forkChild(latestVersions.cached("@example/cli", lookup), {
78 startImmediately: true,
79 });
80 expect(lookups).toBe(1);
81 expect(yield* Fiber.join(next)).toBe("2.0.0");
82 }),
83 );
84 it.effect("a waiting caller retries with its own lookup after the owner fails", () =>
85 Effect.gen(function* () {
86 const latestVersions = yield* ProviderLatestVersions.make();
87 const release = yield* Deferred.make<void>();
88 const owner = yield* Effect.forkChild(
89 latestVersions.cached(
90 "cli",
91 Deferred.await(release).pipe(Effect.andThen(Effect.fail("offline"))),
92 ),
93 { startImmediately: true },
94 );
95 let retries = 0;
96 const waiter = yield* Effect.forkChild(
97 latestVersions.cached(
98 "cli",
99 Effect.sync(() => {
100 retries += 1;
101 return "2.0.0";
102 }),
103 ),
104 { startImmediately: true },
105 );
106 expect(retries).toBe(0);
107 yield* Deferred.succeed(release, undefined);
108 expect(yield* Effect.flip(Fiber.join(owner))).toBe("offline");
109 expect(yield* Fiber.join(waiter)).toBe("2.0.0");
110 expect(retries).toBe(1);
111 }),
112 );
113​
114 it.effect("interrupting a waiter leaves the shared lookup running", () =>
115 Effect.gen(function* () {
116 const latestVersions = yield* ProviderLatestVersions.make();
117 const release = yield* Deferred.make<void>();
118 let lookups = 0;
119 const lookup = Effect.suspend(() => {
120 lookups += 1;
121 return Deferred.await(release).pipe(Effect.as("2.0.0"));
122 });
123 const owner = yield* Effect.forkChild(latestVersions.cached("cli", lookup), {
124 startImmediately: true,
125 });
126 const waiter = yield* Effect.forkChild(latestVersions.cached("cli", lookup), {
127 startImmediately: true,
128 });
129 yield* Fiber.interrupt(waiter);
130 yield* Deferred.succeed(release, undefined);
131 expect(yield* Fiber.join(owner)).toBe("2.0.0");
132 expect(yield* latestVersions.cached("cli", lookup)).toBe("2.0.0");
133 expect(lookups).toBe(1);
134 }),
135 );
136});