@@ -226,9 +226,9 @@ async fn wait_for_no_open_files() -> Result<(), Error> {
226226 Ok ( ( ) )
227227}
228228
229- /// Helper function to ensure there are no temporary files left.
230- async fn check_temp_empty ( temp_path : & str ) -> Result < ( ) , Error > {
231- let ( _permit, temp_dir_handle) = fs:: read_dir ( format ! ( "{temp_path }/{DIGEST_FOLDER}" ) )
229+ /// Helper function to ensure there are no temporary or content files left.
230+ async fn check_storage_dir_empty ( storage_path : & str ) -> Result < ( ) , Error > {
231+ let ( _permit, temp_dir_handle) = fs:: read_dir ( format ! ( "{storage_path }/{DIGEST_FOLDER}" ) )
232232 . await
233233 . err_tip ( || "Failed opening temp directory" ) ?
234234 . into_inner ( ) ;
@@ -243,7 +243,7 @@ async fn check_temp_empty(temp_path: &str) -> Result<(), Error> {
243243 ) ;
244244 }
245245
246- let ( _permit, temp_dir_handle) = fs:: read_dir ( format ! ( "{temp_path }/{STR_FOLDER}" ) )
246+ let ( _permit, temp_dir_handle) = fs:: read_dir ( format ! ( "{storage_path }/{STR_FOLDER}" ) )
247247 . await
248248 . err_tip ( || "Failed opening temp directory" ) ?
249249 . into_inner ( ) ;
@@ -380,7 +380,7 @@ async fn temp_files_get_deleted_on_replace_test() -> Result<(), Error> {
380380 "Dropped a filesystem_delete_file current_active_drop_spawns=0"
381381 ) ) ;
382382
383- check_temp_empty ( & temp_path) . await
383+ check_storage_dir_empty ( & temp_path) . await
384384}
385385
386386// This test ensures that if a file is overridden and an open stream to the file already
@@ -487,7 +487,7 @@ async fn file_continues_to_stream_on_content_replace_test() -> Result<(), Error>
487487 }
488488
489489 // Now ensure our temp file was cleaned up.
490- check_temp_empty ( & temp_path) . await
490+ check_storage_dir_empty ( & temp_path) . await
491491}
492492
493493// Eviction has a different code path than a file replacement, so we check that if a
@@ -583,7 +583,7 @@ async fn file_gets_cleans_up_on_cache_eviction() -> Result<(), Error> {
583583 }
584584
585585 // Now ensure our temp file was cleaned up.
586- check_temp_empty ( & temp_path) . await
586+ check_storage_dir_empty ( & temp_path) . await
587587}
588588
589589// Test to ensure that if we are holding a reference to `FileEntry` and the contents are
@@ -805,7 +805,7 @@ async fn rename_on_insert_fails_due_to_filesystem_error_proper_cleanup_happens()
805805
806806 // Now it should have cleaned up its temp files.
807807 {
808- check_temp_empty ( & temp_path) . await ?;
808+ check_storage_dir_empty ( & temp_path) . await ?;
809809 }
810810
811811 // Finally ensure that our entry is not in the store.
@@ -907,32 +907,6 @@ async fn get_part_is_zero_digest() -> Result<(), Error> {
907907
908908#[ nativelink_test]
909909async fn has_with_results_on_zero_digests ( ) -> Result < ( ) , Error > {
910- async fn wait_for_empty_content_file <
911- Fut : Future < Output = Result < ( ) , Error > > ,
912- F : Fn ( ) -> Fut ,
913- > (
914- content_path : & str ,
915- digest : DigestInfo ,
916- yield_fn : F ,
917- ) -> Result < ( ) , Error > {
918- loop {
919- yield_fn ( ) . await ?;
920-
921- let empty_digest_file_name =
922- OsString :: from ( format ! ( "{content_path}/{DIGEST_FOLDER}/{digest}" ) ) ;
923-
924- let file_metadata = fs:: metadata ( empty_digest_file_name)
925- . await
926- . err_tip ( || "Failed to open content file" ) ?;
927-
928- // Test that the empty digest file is created and contains an empty length.
929- if file_metadata. is_file ( ) && file_metadata. len ( ) == 0 {
930- return Ok ( ( ) ) ;
931- }
932- }
933- // Unreachable.
934- }
935-
936910 let digest = DigestInfo :: new ( Sha256 :: new ( ) . finalize ( ) . into ( ) , 0 ) ;
937911 let content_path = make_temp_path ( "content_path" ) ;
938912 let temp_path = make_temp_path ( "temp_path" ) ;
@@ -960,12 +934,93 @@ async fn has_with_results_on_zero_digests() -> Result<(), Error> {
960934 ) ;
961935 assert_eq ! ( results, vec![ Some ( 0 ) ] ) ;
962936
963- wait_for_empty_content_file ( & content_path, digest, || async move {
964- tokio:: task:: yield_now ( ) . await ;
937+ check_storage_dir_empty ( & content_path) . await ?;
938+
939+ Ok ( ( ) )
940+ }
941+
942+ async fn wrap_update_zero_digest < F > ( updater : F ) -> Result < ( ) , Error >
943+ where
944+ F : AsyncFnOnce ( DigestInfo , Arc < FilesystemStore > ) -> Result < ( ) , Error > ,
945+ {
946+ let digest = DigestInfo :: new ( Sha256 :: new ( ) . finalize ( ) . into ( ) , 0 ) ;
947+ let content_path = make_temp_path ( "content_path" ) ;
948+ let temp_path = make_temp_path ( "temp_path" ) ;
949+
950+ let store = FilesystemStore :: < FileEntryImpl > :: new_with_timeout_and_rename_fn (
951+ & FilesystemSpec {
952+ content_path : content_path. clone ( ) ,
953+ temp_path : temp_path. clone ( ) ,
954+ read_buffer_size : 1 ,
955+ ..Default :: default ( )
956+ } ,
957+ |from, to| std:: fs:: rename ( from, to) ,
958+ )
959+ . await ?;
960+ updater ( digest, store) . await ?;
961+ check_storage_dir_empty ( & content_path) . await ?;
962+ check_storage_dir_empty ( & temp_path) . await ?;
963+ Ok ( ( ) )
964+ }
965+
966+ #[ nativelink_test]
967+ async fn update_whole_file_with_zero_digest ( ) -> Result < ( ) , Error > {
968+ wrap_update_zero_digest ( async |digest, store| {
969+ let temp_file_dir = make_temp_path ( "update_with_zero_digest" ) ;
970+ std:: fs:: create_dir_all ( & temp_file_dir) ?;
971+ let temp_file_path = Path :: new ( & temp_file_dir) . join ( "zero-length-file" ) ;
972+ std:: fs:: write ( & temp_file_path, b"" )
973+ . err_tip ( || format ! ( "Writing to {temp_file_path:?}" ) ) ?;
974+ let file_slot = fs:: open_file ( & temp_file_path, 0 , 0 ) . await ?. into_inner ( ) ;
975+ store
976+ . update_with_whole_file (
977+ digest,
978+ temp_file_path. into ( ) ,
979+ file_slot,
980+ UploadSizeInfo :: ExactSize ( 0 ) ,
981+ )
982+ . await ?;
965983 Ok ( ( ) )
966984 } )
985+ . await
986+ }
987+
988+ #[ nativelink_test]
989+ async fn update_oneshot_with_zero_digest ( ) -> Result < ( ) , Error > {
990+ wrap_update_zero_digest ( async |digest, store| store. update_oneshot ( digest, Bytes :: new ( ) ) . await )
991+ . await
992+ }
993+
994+ #[ nativelink_test]
995+ async fn update_with_zero_digest ( ) -> Result < ( ) , Error > {
996+ wrap_update_zero_digest ( async |digest, store| {
997+ let ( _writer, reader) = make_buf_channel_pair ( ) ;
998+ store
999+ . update ( digest, reader, UploadSizeInfo :: ExactSize ( 0 ) )
1000+ . await
1001+ } )
1002+ . await
1003+ }
1004+
1005+ #[ nativelink_test]
1006+ async fn get_file_entry_for_zero_digest ( ) -> Result < ( ) , Error > {
1007+ let digest = DigestInfo :: new ( Sha256 :: new ( ) . finalize ( ) . into ( ) , 0 ) ;
1008+ let content_path = make_temp_path ( "content_path" ) ;
1009+ let temp_path = make_temp_path ( "temp_path" ) ;
1010+
1011+ let store = FilesystemStore :: < FileEntryImpl > :: new_with_timeout_and_rename_fn (
1012+ & FilesystemSpec {
1013+ content_path : content_path. clone ( ) ,
1014+ temp_path : temp_path. clone ( ) ,
1015+ read_buffer_size : 1 ,
1016+ ..Default :: default ( )
1017+ } ,
1018+ |from, to| std:: fs:: rename ( from, to) ,
1019+ )
9671020 . await ?;
9681021
1022+ let file_entry = store. get_file_entry_for_digest ( & digest) . await ?;
1023+ assert ! ( file_entry. is_empty( ) ) ;
9691024 Ok ( ( ) )
9701025}
9711026
0 commit comments