@@ -709,6 +709,130 @@ def mock_func():
709709 core ._save_cache ({"key" : "value" }, separate_file_key = "test_key" )
710710
711711
712+ @pytest .mark .pickle
713+ def test_save_cache_removes_temp_file_when_fsync_fails (tmp_path ):
714+ """Test _save_cache removes the temp file when fsync fails."""
715+ core = _PickleCore (
716+ hash_func = None ,
717+ cache_dir = tmp_path ,
718+ pickle_reload = False ,
719+ wait_for_calc_timeout = 10 ,
720+ separate_files = False ,
721+ )
722+
723+ def mock_func ():
724+ pass
725+
726+ core .set_func (mock_func )
727+
728+ with patch ("cachier.cores.pickle.os.fsync" , side_effect = OSError ("fsync failed" )), pytest .raises (
729+ OSError , match = "fsync failed"
730+ ):
731+ core ._save_cache (
732+ {"key" : CacheEntry (value = "value" , time = datetime .now (), stale = False , _processing = False )}
733+ )
734+
735+ assert list (tmp_path .iterdir ()) == []
736+
737+
738+ @pytest .mark .pickle
739+ def test_save_cache_propagates_tempfile_creation_failure_without_cleanup_error (tmp_path ):
740+ """Test _save_cache handles temp-file creation failures before temp_path exists."""
741+ core = _PickleCore (
742+ hash_func = None ,
743+ cache_dir = tmp_path ,
744+ pickle_reload = False ,
745+ wait_for_calc_timeout = 10 ,
746+ separate_files = False ,
747+ )
748+
749+ def mock_func ():
750+ pass
751+
752+ core .set_func (mock_func )
753+
754+ with patch ("cachier.cores.pickle.tempfile.NamedTemporaryFile" , side_effect = OSError ("tempfile failed" )), patch (
755+ "cachier.cores.pickle.os.replace"
756+ ) as mock_replace , patch ("cachier.cores.pickle.os.remove" ) as mock_remove , pytest .raises (
757+ OSError , match = "tempfile failed"
758+ ):
759+ core ._save_cache (
760+ {"key" : CacheEntry (value = "value" , time = datetime .now (), stale = False , _processing = False )}
761+ )
762+
763+ mock_replace .assert_not_called ()
764+ mock_remove .assert_not_called ()
765+ assert list (tmp_path .iterdir ()) == []
766+
767+
768+ @pytest .mark .pickle
769+ def test_shared_lock_fpath_falls_back_to_cache_dir_when_temp_dir_unwritable (tmp_path ):
770+ """Test _shared_lock_fpath falls back when the system temp dir is not writable."""
771+ core = _PickleCore (
772+ hash_func = None ,
773+ cache_dir = tmp_path ,
774+ pickle_reload = False ,
775+ wait_for_calc_timeout = 10 ,
776+ separate_files = False ,
777+ )
778+
779+ def mock_func ():
780+ pass
781+
782+ core .set_func (mock_func )
783+
784+ temp_lock_dir = os .path .join ("/non-writable-temp" , "cachier-locks" )
785+ fallback_lock_dir = os .path .join (core .cache_dir , ".cachier-locks" )
786+
787+ def mock_makedirs (path , exist_ok = False ):
788+ if path in (core .cache_dir , fallback_lock_dir ):
789+ return None
790+ if path == temp_lock_dir :
791+ raise PermissionError ("temp dir not writable" )
792+ raise AssertionError (f"Unexpected os.makedirs path: { path } " )
793+
794+ with patch ("cachier.cores.pickle.tempfile.gettempdir" , return_value = "/non-writable-temp" ), patch (
795+ "cachier.cores.pickle.os.makedirs" , side_effect = mock_makedirs
796+ ):
797+ assert core ._shared_lock_fpath == os .path .join (
798+ fallback_lock_dir ,
799+ f"{ hashlib .sha256 (core .cache_fpath .encode ('utf-8' )).hexdigest ()} { core ._SHARED_LOCK_SUFFIX } " ,
800+ )
801+
802+
803+ @pytest .mark .pickle
804+ def test_shared_lock_fpath_uses_cache_dir_file_when_lock_dirs_unwritable (tmp_path ):
805+ """Test _shared_lock_fpath falls back to a lockfile in the cache dir."""
806+ core = _PickleCore (
807+ hash_func = None ,
808+ cache_dir = tmp_path ,
809+ pickle_reload = False ,
810+ wait_for_calc_timeout = 10 ,
811+ separate_files = False ,
812+ )
813+
814+ def mock_func ():
815+ pass
816+
817+ core .set_func (mock_func )
818+
819+ temp_lock_dir = os .path .join ("/non-writable-temp" , "cachier-locks" )
820+ fallback_lock_dir = os .path .join (core .cache_dir , ".cachier-locks" )
821+ cache_hash = hashlib .sha256 (core .cache_fpath .encode ("utf-8" )).hexdigest ()
822+
823+ def mock_makedirs (path , exist_ok = False ):
824+ if path == core .cache_dir :
825+ return None
826+ if path in (temp_lock_dir , fallback_lock_dir ):
827+ raise PermissionError ("lock dir not writable" )
828+ raise AssertionError (f"Unexpected os.makedirs path: { path } " )
829+
830+ with patch ("cachier.cores.pickle.tempfile.gettempdir" , return_value = "/non-writable-temp" ), patch (
831+ "cachier.cores.pickle.os.makedirs" , side_effect = mock_makedirs
832+ ):
833+ assert core ._shared_lock_fpath == os .path .join (core .cache_dir , f".{ cache_hash } { core ._SHARED_LOCK_SUFFIX } " )
834+
835+
712836@pytest .mark .pickle
713837def test_set_entry_should_not_store (tmp_path ):
714838 """Test set_entry when value should not be stored."""
0 commit comments