Skip to content

Commit 8d9fbcb

Browse files
committed
Refactor progress file parsing and improve test validation
1 parent 6a07745 commit 8d9fbcb

File tree

4 files changed

+110
-37
lines changed

4 files changed

+110
-37
lines changed

Sources/Swiftly/JsonFileProgressReporter.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ struct JsonFileProgressReporter: ProgressAnimationProtocol {
3131
return
3232
}
3333

34-
self.fileHandle.seekToEndOfFile()
3534
self.fileHandle.write(jsonData)
3635
self.fileHandle.write("\n".data(using: .utf8) ?? Data())
37-
self.fileHandle.synchronizeFile()
36+
try? self.fileHandle.synchronize()
3837
}
3938

4039
func update(step: Int, total: Int, text: String) {
41-
assert(step <= total)
40+
guard total > 0 && step <= total else {
41+
return
42+
}
4243
self.writeProgress(
4344
ProgressInfo.step(
4445
timestamp: Date(),
@@ -52,8 +53,7 @@ struct JsonFileProgressReporter: ProgressAnimationProtocol {
5253
}
5354

5455
func clear() {
55-
self.fileHandle.truncateFile(atOffset: 0)
56-
self.fileHandle.synchronizeFile()
56+
// not implemented for JSON file reporter
5757
}
5858

5959
func close() throws {

Sources/SwiftlyCore/FileManager+FilePath.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,6 @@ extension String {
190190
try self.write(to: URL(fileURLWithPath: path.string), atomically: atomically, encoding: enc)
191191
}
192192

193-
public func append(to path: FilePath, encoding enc: String.Encoding = .utf8) throws {
194-
if !FileManager.default.fileExists(atPath: path.string) {
195-
try self.write(to: path, atomically: true, encoding: enc)
196-
return
197-
}
198-
}
199-
200193
public init(contentsOf path: FilePath, encoding enc: String.Encoding = .utf8) throws {
201194
try self.init(contentsOf: URL(fileURLWithPath: path.string), encoding: enc)
202195
}

Tests/SwiftlyTests/InstallTests.swift

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ import Testing
267267
/// Verify that progress information is written to the progress file when specified.
268268
@Test(.testHomeMockedToolchain()) func installProgressFile() async throws {
269269
let progressFile = fs.mktemp(ext: ".json")
270+
try await fs.create(.mode(0o644), file: progressFile)
270271

271272
try await SwiftlyTests.runCommand(Install.self, [
272273
"install", "5.7.0",
@@ -276,24 +277,120 @@ import Testing
276277

277278
#expect(try await fs.exists(atPath: progressFile))
278279

280+
let decoder = JSONDecoder()
279281
let progressContent = try String(contentsOfFile: progressFile.string)
280-
let lines = progressContent.components(separatedBy: .newlines).filter { !$0.isEmpty }
282+
let progressInfo = try progressContent.split(separator: "\n")
283+
.filter { !$0.isEmpty }
284+
.map { line in
285+
try decoder.decode(ProgressInfo.self, from: Data(line.utf8))
286+
}
281287

282-
#expect(!lines.isEmpty, "Progress file should contain progress entries")
288+
#expect(!progressInfo.isEmpty, "Progress file should contain progress entries")
283289

284-
// Verify that at least one progress entry exists
285-
let hasProgressEntry = lines.contains { line in
286-
line.contains("\"step\"") && line.contains("\"percent\"") && line.contains("\"timestamp\"")
290+
// Verify that at least one step progress entry exists
291+
let hasStepEntry = progressInfo.contains { info in
292+
if case .step = info { return true }
293+
return false
287294
}
288-
#expect(hasProgressEntry, "Progress file should contain step progress entries")
295+
#expect(hasStepEntry, "Progress file should contain step progress entries")
289296

290297
// Verify that a completion entry exists
291-
let hasCompletionEntry = lines.contains { line in
292-
line.contains("\"complete\"") && line.contains("\"success\"")
298+
let hasCompletionEntry = progressInfo.contains { info in
299+
if case .complete = info { return true }
300+
return false
293301
}
294302
#expect(hasCompletionEntry, "Progress file should contain completion entry")
295303

296304
// Clean up
297305
try FileManager.default.removeItem(atPath: progressFile.string)
298306
}
307+
308+
#if os(Linux) || os(macOS)
309+
@Test(.testHomeMockedToolchain())
310+
func installProgressFileNamedPipe() async throws {
311+
#if os(Linux) || os(macOS)
312+
let tempDir = NSTemporaryDirectory()
313+
let pipePath = tempDir + "swiftly_install_progress_pipe"
314+
315+
let result = mkfifo(pipePath, 0o644)
316+
guard result == 0 else {
317+
return // Skip test if mkfifo syscall failed
318+
}
319+
320+
defer {
321+
try? FileManager.default.removeItem(atPath: pipePath)
322+
}
323+
324+
var receivedMessages: [ProgressInfo] = []
325+
let decoder = JSONDecoder()
326+
var installCompleted = false
327+
328+
let readerTask = Task {
329+
guard let fileHandle = FileHandle(forReadingAtPath: pipePath) else { return }
330+
defer { fileHandle.closeFile() }
331+
332+
var buffer = Data()
333+
334+
while !installCompleted {
335+
let data = fileHandle.availableData
336+
if data.isEmpty {
337+
try await Task.sleep(nanoseconds: 100_000_000)
338+
continue
339+
}
340+
341+
buffer.append(data)
342+
343+
while let newlineRange = buffer.range(of: "\n".data(using: .utf8)!) {
344+
let lineData = buffer.subdata(in: 0..<newlineRange.lowerBound)
345+
buffer.removeSubrange(0..<newlineRange.upperBound)
346+
347+
if !lineData.isEmpty {
348+
if let progress = try? decoder.decode(ProgressInfo.self, from: lineData) {
349+
receivedMessages.append(progress)
350+
if case .complete = progress {
351+
installCompleted = true
352+
return
353+
}
354+
}
355+
}
356+
}
357+
}
358+
}
359+
360+
let installTask = Task {
361+
try await SwiftlyTests.runCommand(Install.self, [
362+
"install", "5.7.0",
363+
"--post-install-file=\(fs.mktemp())",
364+
"--progress-file=\(pipePath)",
365+
])
366+
}
367+
368+
await withTaskGroup(of: Void.self) { group in
369+
group.addTask { try? await readerTask.value }
370+
group.addTask { try? await installTask.value }
371+
}
372+
373+
try await Task.sleep(nanoseconds: 100_000_000) // 100ms
374+
375+
#expect(!receivedMessages.isEmpty, "Named pipe should receive progress entries")
376+
377+
let hasCompletionEntry = receivedMessages.contains { info in
378+
if case .complete = info { return true }
379+
return false
380+
}
381+
#expect(hasCompletionEntry, "Named pipe should receive completion entry")
382+
383+
for message in receivedMessages {
384+
switch message {
385+
case let .step(timestamp, percent, text):
386+
#expect(timestamp.timeIntervalSince1970 > 0)
387+
#expect(percent >= 0 && percent <= 100)
388+
#expect(!text.isEmpty)
389+
case let .complete(success):
390+
#expect(success == true)
391+
}
392+
}
393+
#endif
394+
}
395+
#endif
299396
}

Tests/SwiftlyTests/JsonFileProgressReporterTests.swift

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,6 @@ import Testing
100100
try FileManager.default.removeItem(atPath: tempFile.string)
101101
}
102102

103-
@Test("Test clear method truncates the file")
104-
func testClearTruncatesFile() async throws {
105-
let tempFile = fs.mktemp(ext: ".json")
106-
try await fs.create(.mode(Int(0o644)), file: tempFile)
107-
defer { try? FileManager.default.removeItem(atPath: tempFile.string) }
108-
let reporter = try JsonFileProgressReporter(SwiftlyTests.ctx, filePath: tempFile)
109-
defer { try? reporter.close() }
110-
111-
reporter.update(step: 1, total: 2, text: "Test")
112-
113-
#expect(try String(contentsOf: tempFile).lengthOfBytes(using: String.Encoding.utf8) > 0)
114-
115-
reporter.clear()
116-
117-
#expect(try String(contentsOf: tempFile).lengthOfBytes(using: String.Encoding.utf8) == 0)
118-
}
119-
120103
@Test("Test multiple progress updates create multiple lines")
121104
func testMultipleUpdatesCreateMultipleLines() async throws {
122105
let tempFile = fs.mktemp(ext: ".json")

0 commit comments

Comments
 (0)