@@ -191,6 +191,113 @@ def clear_store
191191 def second_file
192192 File . join ( Dir . tmpdir , "pstore.tmp2.#{ Process . pid } " )
193193 end
194+
195+ def test_truncated_read_only_store_is_not_empty
196+ @pstore . transaction { @pstore [ :foo ] = "bar" }
197+ data = File . binread ( @pstore_file )
198+ File . binwrite ( @pstore_file , data . byteslice ( 0 , data . bytesize - 1 ) )
199+
200+ assert_raise ( EOFError ) { @pstore . transaction ( true ) { @pstore [ :foo ] } }
201+ end
202+
203+ def test_store_operations_require_transaction_owner
204+ ready = Thread ::Queue . new
205+ release = Thread ::Queue . new
206+ owner = Thread . new do
207+ @pstore . transaction do
208+ @pstore [ :foo ] = "bar"
209+ ready << true
210+ release . pop
211+ end
212+ end
213+
214+ ready . pop
215+ assert_raise ( PStore ::Error ) { @pstore [ :foo ] }
216+ assert_raise ( PStore ::Error ) { @pstore [ :foo ] = "other" }
217+ ensure
218+ release << true if release
219+ owner . join if owner
220+ end
221+
222+ def test_commit_targets_the_owning_store
223+ inner = PStore . new ( second_file )
224+ @pstore . transaction do
225+ @pstore [ :outer ] = true
226+ inner . transaction do
227+ inner [ :inner ] = true
228+ @pstore . commit
229+ flunk ( "outer commit should exit its own transaction" )
230+ end
231+ flunk ( "outer commit should exit its own transaction" )
232+ end
233+
234+ assert_equal ( true , @pstore . transaction ( true ) { @pstore [ :outer ] } )
235+ assert_nil ( inner . transaction ( true ) { inner [ :inner ] } )
236+ ensure
237+ File . unlink ( second_file ) rescue nil
238+ end
239+
240+ def test_fetch_compares_missing_default_by_identity
241+ default = Object . new
242+ def default . ==( _other )
243+ true
244+ end
245+
246+ assert_same ( default , @pstore . transaction ( true ) { @pstore . fetch ( :missing , default ) } )
247+ end
248+
249+ def test_lock_retries_when_atomic_save_replaces_file
250+ fake_file = Struct . new ( :closed ) do
251+ def flock ( _mode )
252+ true
253+ end
254+
255+ def close
256+ self . closed = true
257+ end
258+ end
259+ first = fake_file . new ( false )
260+ second = fake_file . new ( false )
261+ files = [ first , second ]
262+ original_new = File . method ( :new )
263+ original_identical = File . method ( :identical? )
264+ File . define_singleton_method ( :new ) { |*_args , **_kwargs | files . shift }
265+ File . define_singleton_method ( :identical? ) { |file , _path | file . equal? ( second ) }
266+
267+ result = @pstore . send ( :open_and_lock_file , @pstore_file , false )
268+
269+ assert_same ( second , result )
270+ assert_equal ( true , first . closed )
271+ assert_equal ( false , second . closed )
272+ ensure
273+ result . close if result && !result . closed
274+ File . define_singleton_method ( :new , original_new ) if original_new
275+ File . define_singleton_method ( :identical? , original_identical ) if original_identical
276+ end
277+
278+ def test_interrupted_atomic_save_removes_temporary_file
279+ return if /mswin|mingw|bccwin|wince/ =~ RUBY_PLATFORM
280+
281+ @pstore . transaction { @pstore [ :foo ] = "old" }
282+ @pstore . ultra_safe = true
283+ original_new = File . method ( :new )
284+ store_path = @pstore_file
285+ File . define_singleton_method ( :new ) do |path , **options |
286+ file = original_new . call ( path , **options )
287+ if path . start_with? ( "#{ store_path } .tmp." )
288+ file . define_singleton_method ( :write ) { |_data | raise Interrupt }
289+ end
290+ file
291+ end
292+
293+ assert_raise ( Interrupt ) { @pstore . transaction { @pstore [ :foo ] = "new" } }
294+ assert_equal ( [ ] , Dir . glob ( "#{ @pstore_file } .tmp.*" ) )
295+ assert_equal ( "old" , @pstore . transaction ( true ) { @pstore [ :foo ] } )
296+ ensure
297+ File . define_singleton_method ( :new , original_new ) if original_new
298+ Dir . glob ( "#{ @pstore_file } .tmp.*" ) . each { |path | File . unlink ( path ) rescue nil }
299+ end
300+
194301 def test_path_like_filename_is_normalized
195302 store = PStore . new ( Pathname ( @pstore_file ) )
196303 store . ultra_safe = true
@@ -200,5 +307,4 @@ def test_path_like_filename_is_normalized
200307 assert_equal ( @pstore_file , store . path )
201308 assert_equal ( "bar" , store . transaction ( true ) { store [ :foo ] } )
202309 end
203-
204310end
0 commit comments