@@ -251,3 +251,128 @@ TEST(TFile, WalkTKeys)
251251 EXPECT_EQ (it->fKeyName , kLongerKey );
252252 EXPECT_EQ (it->fClassName , " string" );
253253}
254+
255+ TEST (TFile, DeleteKey)
256+ {
257+ struct FileRaii {
258+ std::string fFilename ;
259+ FileRaii (std::string_view fname) : fFilename (fname) {}
260+ ~FileRaii () { gSystem ->Unlink (fFilename .c_str ()); }
261+ } fileGuard (" tfile_test_delete_keys.root" );
262+
263+ auto fnCountGaps = [](const std::string &fileName) {
264+ auto f = std::unique_ptr<TFile>(TFile::Open (fileName.c_str ()));
265+ std::uint64_t nGaps = 0 ;
266+ for (const auto &k : f->WalkTKeys ()) {
267+ if (k.fType == ROOT::Detail::TKeyMapNode::kGap )
268+ nGaps++;
269+ }
270+ return nGaps;
271+ };
272+
273+ auto f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " RECREATE" ));
274+ f->SetCompressionSettings (0 );
275+ f->Write ();
276+ f->Close ();
277+
278+ // The empty file should have no gaps. Note that gaps are created temporarily when certain keys are overwritten.
279+ EXPECT_EQ (0 , fnCountGaps (fileGuard.fFilename ));
280+
281+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
282+ std::vector<char > v;
283+ f->WriteObject (&v, " va0" );
284+ f->WriteObject (&v, " va1" );
285+ f->WriteObject (&v, " va2" );
286+ f->Write ();
287+ f->Close ();
288+ // 2 gaps: new (larger) keys list and free list are written
289+ EXPECT_EQ (2 , fnCountGaps (fileGuard.fFilename ));
290+
291+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
292+ f->Delete (" va1;*" ); // should create small gap that cannot be merged, trapped between v0 and v2
293+ f->Write ();
294+ f->Close ();
295+
296+ EXPECT_EQ (3 , fnCountGaps (fileGuard.fFilename ));
297+
298+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
299+ f->Delete (" va2;*" ); // gaps at the tail should merge
300+ f->Write ();
301+ f->Close ();
302+
303+ EXPECT_EQ (2 , fnCountGaps (fileGuard.fFilename ));
304+
305+ v.resize (1000 * 1000 * 1000 - 100 , ' x' ); // almost 1GB
306+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
307+ f->WriteObject (&v, " vb0" );
308+ f->WriteObject (&v, " vb1" );
309+ v.resize (1000 * 1000 ); // truncate next objects to 1MB
310+ f->WriteObject (&v, " vc0" );
311+ f->WriteObject (&v, " vc1" );
312+ f->WriteObject (&v, " vc2" );
313+ f->WriteObject (&v, " vc3" );
314+ f->Write ();
315+ EXPECT_GT (f->GetEND (), TFile::kStartBigFile );
316+ f->Close ();
317+
318+ // New keys list, hence 3 gaps
319+ EXPECT_EQ (3 , fnCountGaps (fileGuard.fFilename ));
320+
321+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
322+ f->Delete (" vb0;*" ); // |
323+ f->Delete (" vb1;*" ); // |---> First merged gap
324+ f->Delete (" vc0;*" ); // |
325+ f->Delete (" vc1;*" ); // |---> Second merged gap
326+ f->Write ();
327+ f->Close ();
328+
329+ // Two merged gaps, the new keys list fits into either of them
330+ EXPECT_EQ (4 , fnCountGaps (fileGuard.fFilename ));
331+
332+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
333+ // Delete the remaining data at the tail of the file in reverse order
334+ f->Delete (" vc2;*" );
335+ f->Delete (" vc3;*" );
336+ f->Write ();
337+ // Back to small file
338+ EXPECT_LT (f->GetEND (), TFile::kStartBigFile );
339+ f->Close ();
340+
341+ // Only the original 2 gaps from the first keys list and free list overwrite
342+ EXPECT_EQ (2 , fnCountGaps (fileGuard.fFilename ));
343+
344+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
345+ v.resize (700 * 1000 * 1000 ); // construct objects such that 3 consecutive gaps surpass 2GB (but not 2)
346+ f->WriteObject (&v, " vd0" );
347+ f->WriteObject (&v, " vd1" );
348+ f->WriteObject (&v, " vd2" );
349+ f->WriteObject (&v, " vd3" );
350+ f->WriteObject (&v, " vd4" );
351+ f->Write ();
352+ f->Close ();
353+
354+ // New keys list --> 3 gaps
355+ EXPECT_EQ (3 , fnCountGaps (fileGuard.fFilename ));
356+
357+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
358+ f->Delete (" vd1;*" );
359+ f->Delete (" vd3;*" );
360+ f->Write ();
361+ f->Close ();
362+
363+ // Nothing mergable, 2 more gaps
364+ EXPECT_EQ (5 , fnCountGaps (fileGuard.fFilename ));
365+
366+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str (), " UPDATE" ));
367+ auto theEnd = f->GetEND ();
368+ f->Delete (" vd2;*" );
369+ f->Write ();
370+ f->Close ();
371+
372+ // We can only merge the gaps of v1 and v2, not all three (vd1, vd2, vd3) due to the gap size
373+ EXPECT_EQ (5 , fnCountGaps (fileGuard.fFilename ));
374+
375+ // Ensure that the file's tail is still intact
376+ f = std::unique_ptr<TFile>(TFile::Open (fileGuard.fFilename .c_str ()));
377+ EXPECT_EQ (f->GetEND (), theEnd);
378+ }
0 commit comments