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