@@ -52,28 +52,74 @@ struct WebPageImageStoreImplTests {
5252 #expect( directorySize == 0 )
5353 }
5454
55- @Test ( " 동시 저장 요청은 요청 순서대로 같은 파일을 갱신한다 " )
56- func 동시_저장_요청은_요청_순서대로_같은_파일을_갱신한다 ( ) async throws {
55+ @Test ( " 같은 store를 공유하는 객체의 저장은 트랜잭션 단위로 반영된다 " )
56+ func 같은_store를_공유하는_객체의_저장은_트랜잭션_단위로_반영된다 ( ) async throws {
5757 let store = WebPageImageStoreImpl ( )
58+ let firstClient = WebPageImageStoreClient ( store: store)
59+ let secondClient = WebPageImageStoreClient ( store: store)
5860 try await store. clearDirectory ( )
5961 let url = try #require( URL ( string: " https://example.com/ \( UUID ( ) . uuidString) " ) )
60- let firstData = Data ( repeating: 1 , count: 64 * 1024 * 1024 )
61- let secondData = Data ( " latest " . utf8)
62+ let largeData = Data ( repeating: 1 , count: 64 * 1024 * 1024 )
63+ let smallData = Data ( " latest " . utf8)
64+ let startedAt = ContinuousClock . now
6265
63- let firstSaveTask = Task {
64- try await store. saveImage ( firstData, for: url)
66+ // 동일한 Impl 인스턴스를 공유하는 두 객체가 접근하는 형태의 결과 기반 테스트다.
67+ // 첫 번째 큰 저장 작업이 먼저 큐에 들어갈 시간을 준 뒤 두 번째 작은 저장을 요청하고,
68+ // 최종 파일이 작은 데이터라면 앞 작업 전체가 끝난 뒤 뒤 작업이 반영된 것으로 본다.
69+ // 각 작업의 완료 시점은 호출자 관점에서 saveImage await가 반환된 시점으로 기록한다.
70+ let largeSaveTask = Task {
71+ try await firstClient. saveImage ( largeData, for: url, name: " large " , since: startedAt)
6572 }
6673 try await Task . sleep ( nanoseconds: 10_000_000 )
67- let secondSaveTask = Task {
68- try await store. saveImage ( secondData, for: url)
69- }
7074
71- _ = try await firstSaveTask. value
72- let fileURL = try await secondSaveTask. value
73- let savedData = try Data ( contentsOf: fileURL)
75+ let smallSaveMeasurement = try await secondClient. saveImage ( smallData, for: url, name: " small " , since: startedAt)
76+ let largeSaveMeasurement = try await largeSaveTask. value
77+ let savedData = try Data ( contentsOf: smallSaveMeasurement. fileURL)
78+
79+ print ( saveSummary ( largeSaveMeasurement) )
80+ print ( saveSummary ( smallSaveMeasurement) )
7481
75- #expect( savedData == secondData )
82+ #expect( savedData == smallData )
7683
7784 try await store. clearDirectory ( )
7885 }
7986}
87+
88+ private struct WebPageImageStoreClient {
89+ let store : WebPageImageStoreImpl
90+
91+ func saveImage(
92+ _ data: Data ,
93+ for url: URL ,
94+ name: String ,
95+ since startedAt: ContinuousClock . Instant
96+ ) async throws -> WebPageImageStoreSaveMeasurement {
97+ let requestedAt = startedAt. duration ( to: . now)
98+ let fileURL = try await store. saveImage ( data, for: url)
99+ let finishedAt = startedAt. duration ( to: . now)
100+
101+ return WebPageImageStoreSaveMeasurement (
102+ name: name,
103+ fileURL: fileURL,
104+ requestedAt: requestedAt,
105+ finishedAt: finishedAt
106+ )
107+ }
108+ }
109+
110+ private struct WebPageImageStoreSaveMeasurement {
111+ let name : String
112+ let fileURL : URL
113+ let requestedAt : Duration
114+ let finishedAt : Duration
115+ }
116+
117+ private func saveSummary( _ measurement: WebPageImageStoreSaveMeasurement ) -> String {
118+ " \( measurement. name) save requested: \( millisecondsString ( measurement. requestedAt) ) ms, finished: \( millisecondsString ( measurement. finishedAt) ) ms "
119+ }
120+
121+ private func millisecondsString( _ duration: Duration ) -> String {
122+ let components = duration. components
123+ let milliseconds = Double ( components. seconds) * 1_000 + Double( components. attoseconds) / 1_000_000_000_000_000
124+ return String ( format: " %.3f " , milliseconds)
125+ }
0 commit comments