@@ -60,10 +60,8 @@ public async Task File_OpenWrite_InternalBufferWriteError_DoesNotCreateFile()
6060 var underlying = ( FileStream ) stream . GetType ( ) . GetField ( "_stream" , BindingFlags . NonPublic | BindingFlags . Instance ) ! . GetValue ( stream ) ! ;
6161 Assert . That ( underlying , Is . Not . Null ) ;
6262
63- await stream . WriteAsync ( new ReadOnlyMemory < byte > ( new byte [ 1024 ] ) ) ;
64-
65- // Forces to upload buffer.
66- await stream . FlushAsync ( ) ;
63+ // Write enough data to trigger automatic part upload (>= 5 MiB).
64+ await stream . WriteAsync ( new ReadOnlyMemory < byte > ( new byte [ 6 * 1024 * 1024 ] ) ) ;
6765
6866 // Simulates an internal buffer write error.
6967 await underlying . DisposeAsync ( ) ;
@@ -195,6 +193,93 @@ await reader.ReadToEndAsync(),
195193 await destination . DeleteAsync ( ) ;
196194 }
197195
196+ [ Test ]
197+ public async Task File_OpenWrite_FlushDoesNotCauseUndersizedParts ( )
198+ {
199+ using var fs = GetFileSystem ( ) ;
200+
201+ const string Content = "Hello, World!" ;
202+
203+ {
204+ await using var stream = await fs . OpenWriteAsync ( "/flush-test.txt" ) ;
205+ await using var writer = new StreamWriter ( stream ) ;
206+
207+ // Write small data and flush multiple times.
208+ // Flush should be a no-op and not upload undersized parts.
209+ foreach ( var ch in Content )
210+ {
211+ await writer . WriteAsync ( ch ) ;
212+ await writer . FlushAsync ( ) ;
213+ }
214+ }
215+ {
216+ // ReSharper disable once UseAwaitUsing
217+ using var stream = await fs . OpenReadAsync ( "/flush-test.txt" ) ;
218+ using var reader = new StreamReader ( stream ) ;
219+
220+ Assert . That ( await reader . ReadToEndAsync ( ) , Is . EqualTo ( Content ) ) ;
221+ }
222+
223+ await fs . DeleteFileAsync ( "/flush-test.txt" ) ;
224+ }
225+
226+ [ Test ]
227+ public async Task File_OpenWrite_FlushWithMultipartUpload ( )
228+ {
229+ using var fs = GetFileSystem ( ) ;
230+
231+ const int Count = 5 ;
232+ const string FileName = "/flush-multipart-test.bin" ;
233+
234+ var chunk = new byte [ 3 * 1024 * 1024 ] ;
235+ Random . Shared . NextBytes ( chunk ) ;
236+
237+ {
238+ await using var stream = await fs . OpenWriteAsync ( FileName ) ;
239+ for ( var i = 0 ; i < Count ; i ++ )
240+ await stream . WriteAsync ( chunk ) ;
241+ }
242+
243+ {
244+ var file = fs . GetFile ( FileName ) ;
245+
246+ Assert . That ( await file . ExistsAsync ( ) , Is . True ) ;
247+ Assert . That ( await file . GetLengthAsync ( ) , Is . EqualTo ( chunk . Length * Count ) ) ;
248+
249+ // ReSharper disable once UseAwaitUsing
250+ using var stream = await file . OpenReadAsync ( ) ;
251+
252+ var bytes = new byte [ chunk . Length ] ;
253+
254+ for ( var i = 0 ; i < Count ; i ++ )
255+ {
256+ var n = await ReadBlockAsync ( stream , bytes ) ;
257+ Assert . That ( n , Is . EqualTo ( bytes . Length ) ) ;
258+
259+ Assert . That (
260+ bytes . AsSpan ( ) . SequenceEqual ( chunk ) ,
261+ Is . True ) ;
262+ }
263+ }
264+
265+ await fs . DeleteFileAsync ( FileName ) ;
266+
267+ static async Task < int > ReadBlockAsync ( Stream stream , Memory < byte > memory )
268+ {
269+ var count = memory . Length ;
270+
271+ while ( ! memory . IsEmpty )
272+ {
273+ var n = await stream . ReadAsync ( memory ) ;
274+ if ( n == 0 )
275+ return 0 ;
276+
277+ memory = memory [ n ..] ;
278+ }
279+
280+ return count ;
281+ }
282+ }
198283
199284 [ Test ]
200285 public async Task Directory_BatchDeleting ( )
0 commit comments