Skip to content

Commit 539ef1b

Browse files
authored
Merge pull request #8 from koji-1009/fix/version
fix: improve version parsing and clean up internal APIs
2 parents ecb3447 + 3209c80 commit 539ef1b

18 files changed

Lines changed: 228 additions & 58 deletions

‎__tests__/cache.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ describe("savePubCache", () => {
275275
"file1",
276276
] as unknown as ReturnType<typeof fs.readdirSync>);
277277
mockedCache.saveCache.mockResolvedValue(1);
278-
await savePubCache(["/pub-cache"], "key", "/pub-cache");
278+
await savePubCache(["/pub-cache"], "key");
279279
expect(mockedCache.saveCache).toHaveBeenCalled();
280280
});
281281

@@ -284,7 +284,7 @@ describe("savePubCache", () => {
284284
vi.mocked(fs.readdirSync).mockReturnValue(
285285
[] as unknown as ReturnType<typeof fs.readdirSync>,
286286
);
287-
await savePubCache(["/pub-cache"], "key", "/pub-cache");
287+
await savePubCache(["/pub-cache"], "key");
288288
expect(mockedCache.saveCache).not.toHaveBeenCalled();
289289
expect(mockedCore.info).toHaveBeenCalledWith(
290290
expect.stringContaining("empty"),
@@ -293,7 +293,7 @@ describe("savePubCache", () => {
293293

294294
it("skips save when directory does not exist", async () => {
295295
vi.mocked(fs.existsSync).mockReturnValue(false);
296-
await savePubCache(["/pub-cache"], "key", "/pub-cache");
296+
await savePubCache(["/pub-cache"], "key");
297297
expect(mockedCache.saveCache).not.toHaveBeenCalled();
298298
});
299299

@@ -305,7 +305,7 @@ describe("savePubCache", () => {
305305
const error = new Error("Cache already exists");
306306
error.name = "ReserveCacheError";
307307
mockedCache.saveCache.mockRejectedValue(error);
308-
await savePubCache(["/pub-cache"], "key", "/pub-cache");
308+
await savePubCache(["/pub-cache"], "key");
309309
expect(mockedCore.info).toHaveBeenCalledWith(
310310
expect.stringContaining("already exists"),
311311
);
@@ -317,7 +317,7 @@ describe("savePubCache", () => {
317317
"file1",
318318
] as unknown as ReturnType<typeof fs.readdirSync>);
319319
mockedCache.saveCache.mockRejectedValue(new Error("network error"));
320-
await savePubCache(["/pub-cache"], "key", "/pub-cache");
320+
await savePubCache(["/pub-cache"], "key");
321321
expect(mockedCore.warning).toHaveBeenCalledWith(
322322
expect.stringContaining("network error"),
323323
);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: test
2+
environment:
3+
sdk: ">=3.5.0 <4.0.0"
4+
flutter: ">=3.29.0 <4.0.0" # inline comment
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: test
2+
environment:
3+
flutter: ""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name: test
2+
environment:
3+
sdk: ">=3.5.0 <4.0.0"
4+
flutter: 3.29.0 # inline comment
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: test
2+
environment:
3+
flutter: 3.29.0

‎__tests__/installer.test.ts‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,30 @@ describe("installFromArchive", () => {
145145
expect(progressCalls[0][0]).toMatch(/\d+\.\d+ MB\/s/);
146146
});
147147

148+
it("skips progress log when threshold is not reached", async () => {
149+
// 20 small chunks, each 5% of total — only even thresholds (10%, 20%, …) log
150+
const chunkSize = 32;
151+
const totalSize = chunkSize * 20;
152+
const chunks = Array.from({ length: 20 }, () =>
153+
Buffer.alloc(chunkSize, "a"),
154+
);
155+
const sha = crypto
156+
.createHash("sha256")
157+
.update(Buffer.concat(chunks))
158+
.digest("hex");
159+
mockHttpGetWith(200, String(totalSize), chunks);
160+
const r = { ...resolved, sha256: sha };
161+
await installFromArchive(r, "/opt/flutter", "linux");
162+
163+
const progressCalls = mockedCore.info.mock.calls.filter((c) =>
164+
String(c[0]).includes("Download progress:"),
165+
);
166+
// 10%, 20%, ..., 100% = 10 log lines (5% chunks don't trigger logs)
167+
expect(progressCalls).toHaveLength(10);
168+
expect(progressCalls[0][0]).toContain("10%");
169+
expect(progressCalls[0][0]).not.toContain("5%");
170+
});
171+
148172
it("logs MB progress when content-length is missing", async () => {
149173
// First chunk (50 MB) does NOT cross a 100 MB boundary → false branch
150174
// Second chunk (60 MB) crosses the 100 MB boundary → true branch
@@ -167,6 +191,24 @@ describe("installFromArchive", () => {
167191
expect(progressCalls[0][0]).toMatch(/\d+\.\d+ MB\/s/);
168192
});
169193

194+
it("uses fallback speed when elapsed is zero", async () => {
195+
const now = Date.now();
196+
const spy = vi.spyOn(Date, "now").mockReturnValue(now);
197+
// content-length missing, single large chunk crossing 100 MB boundary
198+
const chunk = Buffer.alloc(101 * 1024 * 1024, "a");
199+
const sha = crypto.createHash("sha256").update(chunk).digest("hex");
200+
mockHttpGetWith(200, undefined, [chunk]);
201+
const r = { ...resolved, sha256: sha };
202+
await installFromArchive(r, "/opt/flutter", "linux");
203+
204+
const progressCalls = mockedCore.info.mock.calls.filter((c) =>
205+
String(c[0]).includes("MB downloaded"),
206+
);
207+
expect(progressCalls).toHaveLength(1);
208+
expect(progressCalls[0][0]).toContain("MB/s");
209+
spy.mockRestore();
210+
});
211+
170212
it("moves flutter directory to sdkPath", async () => {
171213
await installFromArchive(resolved, "/opt/flutter", "linux");
172214
expect(mockedIo.mv).toHaveBeenCalledWith(

‎__tests__/main.test.ts‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ describe("main run()", () => {
167167
it("overrides channel when version spec is a channel and warns", async () => {
168168
const { inputs } = setupDefaultMocks();
169169
inputs["flutter-version-file"] = ".fvmrc";
170-
mockedVersionFile.readVersionFile.mockResolvedValue("beta");
170+
mockedVersionFile.readVersionFile.mockReturnValue("beta");
171171
mockedVersion.parseVersionSpec.mockReturnValue({
172172
type: "channel",
173173
channel: "beta",
@@ -306,6 +306,24 @@ describe("main run()", () => {
306306
);
307307
});
308308

309+
it("uses git mode with constraint spec falls back to channel", async () => {
310+
const { inputs } = setupDefaultMocks();
311+
inputs["git-source"] = "git";
312+
inputs.channel = "stable";
313+
mockedVersion.parseVersionSpec.mockReturnValue({
314+
type: "constraint",
315+
range: ">=3.29.0 <4.0.0",
316+
});
317+
318+
await run();
319+
320+
expect(mockedGitSource.resolveGitRef).toHaveBeenCalledWith(
321+
"https://github.com/flutter/flutter.git",
322+
"stable",
323+
expect.anything(),
324+
);
325+
});
326+
309327
it("uses git mode with cache hit skips install", async () => {
310328
const { inputs } = setupDefaultMocks();
311329
inputs["git-source"] = "git";
@@ -321,7 +339,7 @@ describe("main run()", () => {
321339
it("reads version file when flutter-version is empty", async () => {
322340
const { inputs } = setupDefaultMocks();
323341
inputs["flutter-version-file"] = "pubspec.yaml";
324-
mockedVersionFile.readVersionFile.mockResolvedValue(">=3.29.0 <4.0.0");
342+
mockedVersionFile.readVersionFile.mockReturnValue(">=3.29.0 <4.0.0");
325343
mockedVersion.parseVersionSpec.mockReturnValue({
326344
type: "constraint",
327345
range: ">=3.29.0 <4.0.0",
@@ -409,9 +427,9 @@ describe("main run()", () => {
409427
it("calls setFailed when readVersionFile throws", async () => {
410428
const { inputs } = setupDefaultMocks();
411429
inputs["flutter-version-file"] = "pubspec.yaml";
412-
mockedVersionFile.readVersionFile.mockRejectedValue(
413-
new Error("pubspec.yaml does not contain environment.flutter"),
414-
);
430+
mockedVersionFile.readVersionFile.mockImplementation(() => {
431+
throw new Error("pubspec.yaml does not contain environment.flutter");
432+
});
415433

416434
await run();
417435

‎__tests__/post.test.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ describe("post run()", () => {
8282
expect(mockedCacheModule.savePubCache).toHaveBeenCalledWith(
8383
["/home/runner/.pub-cache"],
8484
"flutter-pub-linux-abc123",
85-
"/home/runner/.pub-cache",
8685
);
8786
});
8887

‎__tests__/version-file.test.ts‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,30 @@ describe("readPubspec", () => {
2323
).toThrow("pubspec.yaml does not contain environment.flutter");
2424
});
2525

26+
it("reads constraint from pubspec with quoted inline comment", () => {
27+
expect(readPubspec(path.join(fixturesDir, "pubspec-comment.yaml"))).toBe(
28+
">=3.29.0 <4.0.0",
29+
);
30+
});
31+
32+
it("reads unquoted version from pubspec", () => {
33+
expect(readPubspec(path.join(fixturesDir, "pubspec-unquoted.yaml"))).toBe(
34+
"3.29.0",
35+
);
36+
});
37+
38+
it("reads unquoted version from pubspec with inline comment", () => {
39+
expect(
40+
readPubspec(path.join(fixturesDir, "pubspec-unquoted-comment.yaml")),
41+
).toBe("3.29.0");
42+
});
43+
44+
it("returns empty string for empty quoted flutter value", () => {
45+
expect(
46+
readPubspec(path.join(fixturesDir, "pubspec-empty-quoted.yaml")),
47+
).toBe("");
48+
});
49+
2650
it("throws for invalid YAML", () => {
2751
expect(() =>
2852
readPubspec(path.join(fixturesDir, "pubspec-invalid.yaml")),
@@ -81,21 +105,21 @@ describe("readFvmrc", () => {
81105
});
82106

83107
describe("readVersionFile", () => {
84-
it("reads pubspec.yaml", async () => {
85-
const result = await readVersionFile(
108+
it("reads pubspec.yaml", () => {
109+
const result = readVersionFile(
86110
path.join(fixturesDir, "pubspec-dir", "pubspec.yaml"),
87111
);
88112
expect(result).toBe(">=3.29.0 <4.0.0");
89113
});
90114

91-
it("reads .fvmrc", async () => {
92-
const result = await readVersionFile(path.join(fixturesDir, ".fvmrc"));
115+
it("reads .fvmrc", () => {
116+
const result = readVersionFile(path.join(fixturesDir, ".fvmrc"));
93117
expect(result).toBe("3.29.0");
94118
});
95119

96-
it("throws for unsupported file type", async () => {
97-
await expect(
120+
it("throws for unsupported file type", () => {
121+
expect(() =>
98122
readVersionFile(path.join(fixturesDir, "unknown.txt")),
99-
).rejects.toThrow("Unsupported version file");
123+
).toThrow("Unsupported version file");
100124
});
101125
});

‎__tests__/version.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,34 @@ describe("parseVersionSpec", () => {
7676
});
7777
});
7878

79+
it("returns ref for ~ (not a Dart constraint)", () => {
80+
expect(parseVersionSpec("~3.27.0")).toEqual({
81+
type: "ref",
82+
ref: "~3.27.0",
83+
});
84+
});
85+
86+
it("returns constraint for <", () => {
87+
expect(parseVersionSpec("<4.0.0")).toEqual({
88+
type: "constraint",
89+
range: "<4.0.0",
90+
});
91+
});
92+
93+
it("returns constraint for >", () => {
94+
expect(parseVersionSpec(">3.0.0")).toEqual({
95+
type: "constraint",
96+
range: ">3.0.0",
97+
});
98+
});
99+
100+
it("returns constraint for <=", () => {
101+
expect(parseVersionSpec("<=3.29.0")).toEqual({
102+
type: "constraint",
103+
range: "<=3.29.0",
104+
});
105+
});
106+
79107
it("returns range for 3.x", () => {
80108
expect(parseVersionSpec("3.x")).toEqual({ type: "range", major: 3 });
81109
});

0 commit comments

Comments
 (0)