@@ -346,43 +346,44 @@ async fn get_part_with_range() -> Result<(), Error> {
346
346
Ok ( ( ) )
347
347
}
348
348
349
- #[ nativelink_test]
350
- async fn get_part_zero_digest ( ) -> Result < ( ) , Error > {
351
- // Create mock GCS operations
352
- let mock_ops = Arc :: new ( MockGcsOperations :: new ( ) ) ;
353
- let ops_as_trait: Arc < dyn GcsOperations > = mock_ops. clone ( ) ;
354
- let store = create_test_store ( ops_as_trait) . await ?;
355
-
356
- // Create a zero digest
357
- let digest = DigestInfo :: new ( Sha256 :: new ( ) . finalize ( ) . into ( ) , 0 ) ;
358
- let store_key: StoreKey = to_store_key ( digest) ;
359
- let ( mut tx, mut rx) = make_buf_channel_pair ( ) ;
360
-
361
- // Start get_part operation
362
- let store_clone = store. clone ( ) ;
363
- let get_fut = nativelink_util:: spawn!( "get_part_full_task" , async move {
364
- store_clone. get_part( store_key, & mut tx, 0 , None ) . await
365
- } ) ;
366
-
367
- // Receive the data - should be empty
368
- let received_data = rx. consume ( Some ( 100 ) ) . await ?;
369
- assert_eq ! (
370
- received_data. as_ref( ) ,
371
- b"" ,
372
- "Received data should be empty for zero digest"
373
- ) ;
374
- get_fut. await ??;
375
-
376
- // Verify no mock operations were called (zero digest is special-cased)
377
- let call_counts = mock_ops. get_call_counts ( ) ;
378
- assert_eq ! (
379
- call_counts. read_calls. load( Ordering :: Relaxed ) ,
380
- 0 ,
381
- "No read operations should have been called for zero digest"
382
- ) ;
383
-
384
- Ok ( ( ) )
385
- }
349
+ // I'll delete this but it did not actually test and prevent the issue we were addressing.
350
+ // #[nativelink_test]
351
+ // async fn get_part_zero_digest() -> Result<(), Error> {
352
+ // // Create mock GCS operations
353
+ // let mock_ops = Arc::new(MockGcsOperations::new());
354
+ // let ops_as_trait: Arc<dyn GcsOperations> = mock_ops.clone();
355
+ // let store = create_test_store(ops_as_trait).await?;
356
+
357
+ // // Create a zero digest
358
+ // let digest = DigestInfo::new(Sha256::new().finalize().into(), 0);
359
+ // let store_key: StoreKey = to_store_key(digest);
360
+ // let (mut tx, mut rx) = make_buf_channel_pair();
361
+
362
+ // // Start get_part operation
363
+ // let store_clone = store.clone();
364
+ // let get_fut = nativelink_util::spawn!("get_part_full_task", async move {
365
+ // store_clone.get_part(store_key, &mut tx, 0, None).await
366
+ // });
367
+
368
+ // // Receive the data - should be empty
369
+ // let received_data = rx.consume(Some(100)).await?;
370
+ // assert_eq!(
371
+ // received_data.as_ref(),
372
+ // b"",
373
+ // "Received data should be empty for zero digest"
374
+ // );
375
+ // get_fut.await??;
376
+
377
+ // // Verify no mock operations were called (zero digest is special-cased)
378
+ // let call_counts = mock_ops.get_call_counts();
379
+ // assert_eq!(
380
+ // call_counts.read_calls.load(Ordering::Relaxed),
381
+ // 0,
382
+ // "No read operations should have been called for zero digest"
383
+ // );
384
+
385
+ // Ok(())
386
+ // }
386
387
387
388
#[ nativelink_test]
388
389
async fn test_expired_object ( ) -> Result < ( ) , Error > {
@@ -609,3 +610,63 @@ fn create_object_path(key: &StoreKey) -> ObjectPath {
609
610
& format ! ( "{}{}" , KEY_PREFIX , key. as_str( ) ) ,
610
611
)
611
612
}
613
+
614
+ #[ nativelink_test]
615
+ async fn test_zero_byte_upload_prevention ( ) -> Result < ( ) , Error > {
616
+ // Create mock GCS operations
617
+ let mock_ops = Arc :: new ( MockGcsOperations :: new ( ) ) ;
618
+ let ops_as_trait: Arc < dyn GcsOperations > = mock_ops. clone ( ) ;
619
+ let store = create_test_store ( ops_as_trait) . await ?;
620
+
621
+ // Create a zero-byte digest
622
+ let zero_digest = DigestInfo :: new ( Sha256 :: new ( ) . finalize ( ) . into ( ) , 0 ) ;
623
+ let store_key: StoreKey = to_store_key ( zero_digest) ;
624
+ let ( mut tx, rx) = make_buf_channel_pair ( ) ;
625
+
626
+ // Start update operation for zero-byte file
627
+ let store_clone = store. clone ( ) ;
628
+ let update_fut = nativelink_util:: spawn!( "update_zero_byte_task" , async move {
629
+ store_clone
630
+ . update( store_key, rx, UploadSizeInfo :: ExactSize ( 0 ) )
631
+ . await
632
+ } ) ;
633
+
634
+ // Send EOF immediately (zero bytes)
635
+ tx. send_eof ( ) ?;
636
+
637
+ // The update should complete but no upload should occur
638
+ let result = update_fut. await ?;
639
+ assert ! ( result. is_ok( ) , "Update should complete without error" ) ;
640
+
641
+ // Verify that no write operations were called
642
+ let requests = mock_ops. get_requests ( ) . await ;
643
+ let write_request_count = requests
644
+ . iter ( )
645
+ . filter ( |req| {
646
+ matches ! (
647
+ req,
648
+ MockRequest :: Write { .. } | MockRequest :: StartResumable { .. }
649
+ )
650
+ } )
651
+ . count ( ) ;
652
+
653
+ assert_eq ! (
654
+ write_request_count, 0 ,
655
+ "No write requests should be made for zero-byte files"
656
+ ) ;
657
+
658
+ // Verify call counts
659
+ let call_counts = mock_ops. get_call_counts ( ) ;
660
+ assert_eq ! (
661
+ call_counts. write_calls. load( Ordering :: Relaxed ) ,
662
+ 0 ,
663
+ "No write operations should have been called"
664
+ ) ;
665
+ assert_eq ! (
666
+ call_counts. start_resumable_calls. load( Ordering :: Relaxed ) ,
667
+ 0 ,
668
+ "No resumable upload operations should have been called"
669
+ ) ;
670
+
671
+ Ok ( ( ) )
672
+ }
0 commit comments