|
17 | 17 | #include "TSystem.h" |
18 | 18 | #include "TEnv.h" // gEnv |
19 | 19 |
|
| 20 | +#include <cstdio> |
| 21 | + |
20 | 22 | TEST(TFile, WriteObjectTObject) |
21 | 23 | { |
22 | 24 | auto filename{"tfile_writeobject_tobject.root"}; |
@@ -332,3 +334,134 @@ TEST(TFile, UUID) |
332 | 334 | TMemFile f("uuidtest.root", "RECREATE"); |
333 | 335 | EXPECT_EQ('4', f.GetUUID().AsString()[14]); |
334 | 336 | } |
| 337 | + |
| 338 | +TEST(TFile, DeleteKey) |
| 339 | +{ |
| 340 | + struct FileRaii { |
| 341 | + std::string fFilename; |
| 342 | + FileRaii(std::string_view fname) : fFilename(fname) {} |
| 343 | + ~FileRaii() { gSystem->Unlink(fFilename.c_str()); } |
| 344 | + } fileGuard("tfile_test_delete_keys.root"); |
| 345 | + |
| 346 | + auto fnCountGaps = [](const std::string &fileName) { |
| 347 | + auto f = std::unique_ptr<TFile>(TFile::Open(fileName.c_str())); |
| 348 | + std::uint64_t nGaps = 0; |
| 349 | + for (const auto &k : f->WalkTKeys()) { |
| 350 | + if (k.fType == ROOT::Detail::TKeyMapNode::kGap) |
| 351 | + nGaps++; |
| 352 | + } |
| 353 | + return nGaps; |
| 354 | + }; |
| 355 | + |
| 356 | + auto f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "RECREATE")); |
| 357 | + f->SetCompressionSettings(0); |
| 358 | + f->Write(); |
| 359 | + f->Close(); |
| 360 | + |
| 361 | + // The empty file should have no gaps. Note that gaps are created temporarily when certain keys are overwritten. |
| 362 | + EXPECT_EQ(0, fnCountGaps(fileGuard.fFilename)); |
| 363 | + |
| 364 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 365 | + std::vector<char> v; |
| 366 | + f->WriteObject(&v, "va0"); |
| 367 | + f->WriteObject(&v, "va1"); |
| 368 | + f->WriteObject(&v, "va2"); |
| 369 | + f->Write(); |
| 370 | + f->Close(); |
| 371 | + // 2 gaps: new (larger) keys list and free list are written |
| 372 | + EXPECT_EQ(2, fnCountGaps(fileGuard.fFilename)); |
| 373 | + |
| 374 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 375 | + f->Delete("va1;*"); // should create small gap that cannot be merged, trapped between v0 and v2 |
| 376 | + f->Write(); |
| 377 | + f->Close(); |
| 378 | + |
| 379 | + EXPECT_EQ(3, fnCountGaps(fileGuard.fFilename)); |
| 380 | + |
| 381 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 382 | + f->Delete("va2;*"); // gaps at the tail should merge |
| 383 | + f->Write(); |
| 384 | + f->Close(); |
| 385 | + |
| 386 | + EXPECT_EQ(2, fnCountGaps(fileGuard.fFilename)); |
| 387 | + |
| 388 | + // The following tests run out of memory on 32bit platforms |
| 389 | + if (sizeof(std::size_t) == 4) { |
| 390 | + printf("Skipping test partially on 32bit platform.\n"); |
| 391 | + return; |
| 392 | + } |
| 393 | + |
| 394 | + v.resize(1000 * 1000 * 1000 - 100, 'x'); // almost 1GB |
| 395 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 396 | + f->WriteObject(&v, "vb0"); |
| 397 | + f->WriteObject(&v, "vb1"); |
| 398 | + v.resize(1000 * 1000); // truncate next objects to 1MB |
| 399 | + f->WriteObject(&v, "vc0"); |
| 400 | + f->WriteObject(&v, "vc1"); |
| 401 | + f->WriteObject(&v, "vc2"); |
| 402 | + f->WriteObject(&v, "vc3"); |
| 403 | + f->Write(); |
| 404 | + EXPECT_GT(f->GetEND(), TFile::kStartBigFile); |
| 405 | + f->Close(); |
| 406 | + |
| 407 | + // New keys list, hence 3 gaps |
| 408 | + EXPECT_EQ(3, fnCountGaps(fileGuard.fFilename)); |
| 409 | + |
| 410 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 411 | + f->Delete("vb0;*"); // | |
| 412 | + f->Delete("vb1;*"); // |---> First merged gap |
| 413 | + f->Delete("vc0;*"); // | |
| 414 | + f->Delete("vc1;*"); // |---> Second merged gap |
| 415 | + f->Write(); |
| 416 | + f->Close(); |
| 417 | + |
| 418 | + // Two merged gaps, the new keys list fits into either of them |
| 419 | + EXPECT_EQ(4, fnCountGaps(fileGuard.fFilename)); |
| 420 | + |
| 421 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 422 | + // Delete the remaining data at the tail of the file in reverse order |
| 423 | + f->Delete("vc2;*"); |
| 424 | + f->Delete("vc3;*"); |
| 425 | + f->Write(); |
| 426 | + // Back to small file |
| 427 | + EXPECT_LT(f->GetEND(), TFile::kStartBigFile); |
| 428 | + f->Close(); |
| 429 | + |
| 430 | + // Only the original 2 gaps from the first keys list and free list overwrite |
| 431 | + EXPECT_EQ(2, fnCountGaps(fileGuard.fFilename)); |
| 432 | + |
| 433 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 434 | + v.resize(700 * 1000 * 1000); // construct objects such that 3 consecutive gaps surpass 2GB (but not 2) |
| 435 | + f->WriteObject(&v, "vd0"); |
| 436 | + f->WriteObject(&v, "vd1"); |
| 437 | + f->WriteObject(&v, "vd2"); |
| 438 | + f->WriteObject(&v, "vd3"); |
| 439 | + f->WriteObject(&v, "vd4"); |
| 440 | + f->Write(); |
| 441 | + f->Close(); |
| 442 | + |
| 443 | + // New keys list --> 3 gaps |
| 444 | + EXPECT_EQ(3, fnCountGaps(fileGuard.fFilename)); |
| 445 | + |
| 446 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 447 | + f->Delete("vd1;*"); |
| 448 | + f->Delete("vd3;*"); |
| 449 | + f->Write(); |
| 450 | + f->Close(); |
| 451 | + |
| 452 | + // Nothing mergable, 2 more gaps |
| 453 | + EXPECT_EQ(5, fnCountGaps(fileGuard.fFilename)); |
| 454 | + |
| 455 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str(), "UPDATE")); |
| 456 | + auto theEnd = f->GetEND(); |
| 457 | + f->Delete("vd2;*"); |
| 458 | + f->Write(); |
| 459 | + f->Close(); |
| 460 | + |
| 461 | + // We can only merge the gaps of v1 and v2, not all three (vd1, vd2, vd3) due to the gap size |
| 462 | + EXPECT_EQ(5, fnCountGaps(fileGuard.fFilename)); |
| 463 | + |
| 464 | + // Ensure that the file's tail is still intact |
| 465 | + f = std::unique_ptr<TFile>(TFile::Open(fileGuard.fFilename.c_str())); |
| 466 | + EXPECT_EQ(f->GetEND(), theEnd); |
| 467 | +} |
0 commit comments