1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
|
// Copyright 2024 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/buildtool/storage/large_object_cas.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "catch2/catch_test_macros.hpp"
#include "src/buildtool/common/bazel_types.hpp"
#include "src/buildtool/compatibility/compatibility.hpp"
#include "src/buildtool/execution_api/bazel_msg/bazel_msg_factory.hpp"
#include "src/buildtool/file_system/file_system_manager.hpp"
#include "src/buildtool/file_system/object_type.hpp"
#include "src/buildtool/storage/config.hpp"
#include "src/buildtool/storage/garbage_collector.hpp"
#include "src/buildtool/storage/storage.hpp"
#include "src/utils/cpp/tmp_dir.hpp"
#include "test/utils/hermeticity/test_storage_config.hpp"
#include "test/utils/large_objects/large_object_utils.hpp"
namespace {
namespace LargeTestUtils {
template <bool IsExecutable>
class Blob final {
public:
static constexpr auto kLargeId = std::string_view("bl_8Mb");
static constexpr auto kLargeSize = std::uintmax_t(8 * 1024 * 1024);
static constexpr auto kSmallId = std::string_view("bl_1kB");
static constexpr auto kSmallSize = std::uintmax_t(1024);
static constexpr auto kEmptyId = std::string_view("bl_0");
static constexpr auto kEmptySize = std::uintmax_t(0);
[[nodiscard]] static auto Create(
LocalCAS<kDefaultDoGlobalUplink> const& cas,
std::string const& id,
std::uintmax_t size) noexcept
-> std::optional<std::pair<ArtifactDigest, std::filesystem::path>>;
[[nodiscard]] static auto Generate(std::string const& id,
std::uintmax_t size) noexcept
-> std::optional<std::filesystem::path>;
};
using File = Blob<false>;
class Tree final {
public:
static constexpr auto kLargeId = std::string_view("tree_4096");
static constexpr auto kLargeSize = std::uintmax_t(4096);
static constexpr auto kSmallId = std::string_view("tree_1");
static constexpr auto kSmallSize = std::uintmax_t(1);
static constexpr auto kEmptyId = std::string_view("tree_0");
static constexpr auto kEmptySize = std::uintmax_t(0);
[[nodiscard]] static auto Create(
LocalCAS<kDefaultDoGlobalUplink> const& cas,
std::string const& id,
std::uintmax_t entries_count) noexcept
-> std::optional<std::pair<ArtifactDigest, std::filesystem::path>>;
[[nodiscard]] static auto Generate(std::string const& id,
std::uintmax_t entries_count) noexcept
-> std::optional<std::filesystem::path>;
[[nodiscard]] static auto StoreRaw(
LocalCAS<kDefaultDoGlobalUplink> const& cas,
std::filesystem::path const& directory) noexcept
-> std::optional<ArtifactDigest>;
};
} // namespace LargeTestUtils
} // namespace
// Test splitting of a small tree.
TEST_CASE("LargeObjectCAS: split a small tree", "[storage]") {
auto const storage_config = TestStorageConfig::Create();
auto const storage = Storage::Create(&storage_config.Get());
auto const& cas = storage.CAS();
// Create a small tree:
using LargeTestUtils::Tree;
auto small =
Tree::Create(cas, std::string(Tree::kSmallId), Tree::kSmallSize);
REQUIRE(small);
auto const& [digest, path] = *small;
// Split must be successful:
auto split_pack = cas.SplitTree(digest);
REQUIRE(split_pack);
// The result must contain one blob digest:
CHECK(split_pack->size() == 1);
CHECK_FALSE(split_pack->front().IsTree());
}
// Test splitting of a large object. The split must be successful and the entry
// must be placed to the LargeCAS. The second split of the same object must load
// the result from the LargeCAS, no actual split must occur.
// The object can be implicitly reconstructed from the LargeCAS.
template <ObjectType kType>
static void TestLarge(StorageConfig const& storage_config,
Storage const& storage) noexcept {
SECTION("Large") {
static constexpr bool kIsTree = IsTreeObject(kType);
static constexpr bool kIsExec = IsExecutableObject(kType);
using TestType = std::conditional_t<kIsTree,
LargeTestUtils::Tree,
LargeTestUtils::Blob<kIsExec>>;
auto const& cas = storage.CAS();
// Create a large object:
auto object = TestType::Create(
cas, std::string(TestType::kLargeId), TestType::kLargeSize);
CHECK(object);
auto const& [digest, path] = *object;
// Split the large object:
auto pack_1 = kIsTree ? cas.SplitTree(digest) : cas.SplitBlob(digest);
CHECK(pack_1);
CHECK(pack_1->size() > 1);
CHECK(FileSystemManager::RemoveFile(path));
CHECK_FALSE(FileSystemManager::IsFile(path));
SECTION("Split short-circuiting") {
// Check the second call loads the entry from the large CAS:
auto pack_2 =
kIsTree ? cas.SplitTree(digest) : cas.SplitBlob(digest);
CHECK(pack_2);
CHECK(pack_2->size() == pack_1->size());
// There must be no spliced file:
CHECK_FALSE(FileSystemManager::IsFile(path));
}
SECTION("Splice") {
// Check implicit splice:
auto spliced_path =
kIsTree ? cas.TreePath(digest) : cas.BlobPath(digest, kIsExec);
REQUIRE(spliced_path);
// The result must be in the same location:
CHECK(*spliced_path == path);
}
SECTION("Uplinking") {
// Increment generation:
CHECK(GarbageCollector::TriggerGarbageCollection(storage_config));
// Check implicit splice:
auto spliced_path =
kIsTree ? cas.TreePath(digest) : cas.BlobPath(digest, kIsExec);
REQUIRE(spliced_path);
// The result must be spliced to the same location:
CHECK(*spliced_path == path);
// Check the large entry was uplinked too:
// Remove the spliced result:
CHECK(FileSystemManager::RemoveFile(path));
CHECK_FALSE(FileSystemManager::IsFile(path));
// Call split with disabled uplinking:
auto const youngest_storage = ::Generation::Create(&storage_config);
auto pack_3 = kIsTree ? youngest_storage.CAS().SplitTree(digest)
: youngest_storage.CAS().SplitBlob(digest);
REQUIRE(pack_3);
CHECK(pack_3->size() == pack_1->size());
// Check there are no spliced results in all generations:
for (std::size_t i = 0; i < storage_config.num_generations; ++i) {
auto const storage = ::Generation::Create(&storage_config);
auto generation_path =
kIsTree ? storage.CAS().TreePath(digest)
: storage.CAS().BlobPath(digest, kIsExec);
REQUIRE_FALSE(generation_path);
}
}
}
}
// Test splitting of a small object. The split must be successful, but the entry
// must not be placed to the LargeCAS. The result of spliting must contain one
// blob.
// The object cannot be implicitly reconstructed.
template <ObjectType kType>
static void TestSmall(Storage const& storage) noexcept {
SECTION("Small") {
static constexpr bool kIsTree = IsTreeObject(kType);
static constexpr bool kIsExec = IsExecutableObject(kType);
using TestType = std::conditional_t<kIsTree,
LargeTestUtils::Tree,
LargeTestUtils::Blob<kIsExec>>;
auto const& cas = storage.CAS();
// Create a small object:
auto object = TestType::Create(
cas, std::string(TestType::kSmallId), TestType::kSmallSize);
CHECK(object);
auto const& [digest, path] = *object;
// Split the small object:
auto pack_1 = kIsTree ? cas.SplitTree(digest) : cas.SplitBlob(digest);
CHECK(pack_1);
CHECK(pack_1->size() == 1);
CHECK_FALSE(pack_1->front().IsTree());
// Test that there is no large entry in the storage:
// To ensure there is no split of the initial object, it is removed:
CHECK(FileSystemManager::RemoveFile(path));
CHECK_FALSE(FileSystemManager::IsFile(path));
// The part of a small executable is the same file but without the
// execution permission. It must be deleted too.
if constexpr (kIsExec) {
auto part_path = cas.BlobPath(pack_1->front(), false);
CHECK(part_path);
CHECK(FileSystemManager::RemoveFile(*part_path));
}
// Split must not find the large entry:
auto pack_2 = kIsTree ? cas.SplitTree(digest) : cas.SplitBlob(digest);
CHECK_FALSE(pack_2);
CHECK(pack_2.error().Code() == LargeObjectErrorCode::FileNotFound);
// There must be no spliced file:
CHECK_FALSE(FileSystemManager::IsFile(path));
// Check implicit splice fails:
auto spliced_path =
kIsTree ? cas.TreePath(digest) : cas.BlobPath(digest, kIsExec);
CHECK_FALSE(spliced_path);
}
}
// Test splitting of an empty object. The split must be successful, but the
// entry must not be placed to the LargeCAS. The result of splitting must be
// empty.
// The object cannot be implicitly reconstructed.
template <ObjectType kType>
static void TestEmpty(Storage const& storage) noexcept {
SECTION("Empty") {
static constexpr bool kIsTree = IsTreeObject(kType);
static constexpr bool kIsExec = IsExecutableObject(kType);
using TestType = std::conditional_t<kIsTree,
LargeTestUtils::Tree,
LargeTestUtils::Blob<kIsExec>>;
// Create an empty file:
auto temp_path = LargeTestUtils::Blob</*kIsExec=*/false>::Generate(
std::string(TestType::kEmptyId), TestType::kEmptySize);
REQUIRE(temp_path);
auto const& cas = storage.CAS();
auto digest = kIsTree ? cas.StoreTree(*temp_path)
: cas.StoreBlob(*temp_path, kIsExec);
REQUIRE(digest);
auto path =
kIsTree ? cas.TreePath(*digest) : cas.BlobPath(*digest, kIsExec);
REQUIRE(path);
// Split the empty object:
auto pack_1 = kIsTree ? cas.SplitTree(*digest) : cas.SplitBlob(*digest);
CHECK(pack_1);
CHECK(pack_1->empty());
// Test that there is no large entry in the storage:
// To ensure there is no split of the initial object, it is removed:
CHECK(FileSystemManager::RemoveFile(*path));
CHECK_FALSE(FileSystemManager::IsFile(*path));
// Split must not find the large entry:
auto pack_2 = kIsTree ? cas.SplitTree(*digest) : cas.SplitBlob(*digest);
CHECK_FALSE(pack_2);
CHECK(pack_2.error().Code() == LargeObjectErrorCode::FileNotFound);
// There must be no spliced file:
CHECK_FALSE(FileSystemManager::IsFile(*path));
// Check implicit splice fails:
auto spliced_path =
kIsTree ? cas.TreePath(*digest) : cas.BlobPath(*digest, kIsExec);
CHECK_FALSE(spliced_path);
}
}
// Test splicing from an external source.
// 1. The object can be explicitly spliced, if the parts are presented in the
// storage.
// 2. Explicit splice fails, it the result of splicing is different from
// what was expected.
// 3. Explicit splice fails, if some parts of the tree are missing.
template <ObjectType kType>
static void TestExternal(StorageConfig const& storage_config,
Storage const& storage) noexcept {
SECTION("External") {
static constexpr bool kIsTree = IsTreeObject(kType);
static constexpr bool kIsExec = IsExecutableObject(kType);
using TestType = std::conditional_t<kIsTree,
LargeTestUtils::Tree,
LargeTestUtils::Blob<kIsExec>>;
auto const& cas = storage.CAS();
// Create a large object:
auto object = TestType::Create(
cas, std::string(TestType::kLargeId), TestType::kLargeSize);
CHECK(object);
auto const& [digest, path] = *object;
// Split the object:
auto pack_1 = kIsTree ? cas.SplitTree(digest) : cas.SplitBlob(digest);
CHECK(pack_1);
CHECK(pack_1->size() > 1);
// External source is emulated by moving the large entry to an older
// generation and promoting the parts of the entry to the youngest
// generation:
REQUIRE(GarbageCollector::TriggerGarbageCollection(storage_config));
for (auto const& part : *pack_1) {
static constexpr bool is_executable = false;
REQUIRE(cas.BlobPath(part, is_executable));
}
auto const youngest = ::Generation::Create(&storage_config);
SECTION("Proper request") {
if constexpr (kIsTree) {
// Promote the parts of the tree:
auto splice = cas.TreePath(digest);
REQUIRE(splice);
REQUIRE(FileSystemManager::RemoveFile(*splice));
}
REQUIRE_FALSE(FileSystemManager::IsFile(path));
// Reconstruct the result from parts:
std::ignore =
kIsTree ? youngest.CAS().SpliceTree(digest, *pack_1)
: youngest.CAS().SpliceBlob(digest, *pack_1, kIsExec);
CHECK(FileSystemManager::IsFile(path));
}
// Simulate a situation when parts result to an existing file, but it is
// not the expected result:
SECTION("Digest consistency fail") {
// Splice the result to check it will not be affected:
auto implicit_splice =
kIsTree ? cas.TreePath(digest) : cas.BlobPath(digest, kIsExec);
REQUIRE(implicit_splice);
REQUIRE(*implicit_splice == path);
// Randomize one more object to simulate invalidation:
auto small = TestType::Create(
cas, std::string(TestType::kSmallId), TestType::kSmallSize);
REQUIRE(small);
auto const& [small_digest, small_path] = *small;
// The entry itself is not important, only it's digest is needed:
REQUIRE(FileSystemManager::RemoveFile(small_path));
REQUIRE_FALSE(FileSystemManager::IsFile(small_path));
// Invalidation is simulated by reconstructing the small_digest
// object from the parts of the initial object:
auto splice =
kIsTree
? youngest.CAS().SpliceTree(small_digest, *pack_1)
: youngest.CAS().SpliceBlob(small_digest, *pack_1, kIsExec);
REQUIRE_FALSE(splice);
CHECK(splice.error().Code() == LargeObjectErrorCode::InvalidResult);
// The initial entry must not be affected:
REQUIRE(FileSystemManager::IsFile(path));
}
if (kIsTree and not Compatibility::IsCompatible()) {
// Tree invariants check is omitted in compatible mode.
SECTION("Tree invariants check fails") {
// Check splice fails due to the tree invariants check.
auto splice = youngest.CAS().SpliceTree(digest, *pack_1);
REQUIRE_FALSE(splice);
CHECK(splice.error().Code() ==
LargeObjectErrorCode::InvalidTree);
}
}
}
}
// Test compactification of a storage generation.
// If there are objects in the storage that have an entry in
// the large CAS, they must be deleted during compactification.
// All splitable objects in the generation must be split.
template <ObjectType kType>
static void TestCompactification(StorageConfig const& storage_config,
Storage const& storage) {
SECTION("Compactify") {
static constexpr bool kIsTree = IsTreeObject(kType);
static constexpr bool kIsExec = IsExecutableObject(kType);
using TestType = std::conditional_t<kIsTree,
LargeTestUtils::Tree,
LargeTestUtils::Blob<kIsExec>>;
auto const& cas = storage.CAS();
// Create a large object and split it:
auto object = TestType::Create(
cas, std::string(TestType::kLargeId), TestType::kLargeSize);
REQUIRE(object);
auto& [digest, path] = *object;
auto result = kIsTree ? cas.SplitTree(digest) : cas.SplitBlob(digest);
REQUIRE(result);
// For trees the size must be increased to exceed the internal
// compactification threshold:
static constexpr auto ExceedThresholdSize =
kIsTree ? TestType::kLargeSize * 8 : TestType::kLargeSize;
// Create a large object that is to be split during compactification:
auto object_2 = TestType::Create(
cas, std::string(TestType::kLargeId) + "_2", ExceedThresholdSize);
REQUIRE(object_2);
auto& [digest_2, path_2] = *object_2;
// After an interruption of a build process intermediate unique files
// may be present in the storage. To ensure compactification deals with
// them properly, a "unique" file is created:
auto invalid_object = TestType::Create(
cas, std::string(TestType::kLargeId) + "_3", ExceedThresholdSize);
REQUIRE(invalid_object);
auto& [invalid_digest, invalid_path] = *invalid_object;
auto unique_path = CreateUniquePath(invalid_path);
REQUIRE(unique_path);
REQUIRE(FileSystemManager::Rename(invalid_path, *unique_path));
// Ensure all entries are in the storage:
auto get_path = [](auto const& cas, ArtifactDigest const& digest) {
return kIsTree ? cas.TreePath(digest)
: cas.BlobPath(digest, kIsExec);
};
auto const latest = ::Generation::Create(&storage_config);
REQUIRE(get_path(latest.CAS(), digest).has_value());
REQUIRE(get_path(latest.CAS(), digest_2).has_value());
REQUIRE(FileSystemManager::IsFile(*unique_path));
// Compactify the youngest generation:
// Generation rotation is disabled to exclude uplinking.
static constexpr bool kNoRotation = true;
REQUIRE(GarbageCollector::TriggerGarbageCollection(storage_config,
kNoRotation));
// All entries must be deleted during compactification, and for blobs
// and executables there are no synchronized entries in the storage:
REQUIRE_FALSE(get_path(latest.CAS(), digest).has_value());
REQUIRE_FALSE(get_path(latest.CAS(), digest_2).has_value());
REQUIRE_FALSE(FileSystemManager::IsFile(*unique_path));
// All valid entries must be implicitly spliceable:
REQUIRE(get_path(cas, digest).has_value());
REQUIRE(get_path(cas, digest_2).has_value());
}
}
TEST_CASE("LocalCAS: Split-Splice", "[storage]") {
auto const config = TestStorageConfig::Create();
auto const storage = Storage::Create(&config.Get());
SECTION("File") {
TestLarge<ObjectType::File>(config.Get(), storage);
TestSmall<ObjectType::File>(storage);
TestEmpty<ObjectType::File>(storage);
TestExternal<ObjectType::File>(config.Get(), storage);
TestCompactification<ObjectType::File>(config.Get(), storage);
}
SECTION("Tree") {
TestLarge<ObjectType::Tree>(config.Get(), storage);
TestSmall<ObjectType::Tree>(storage);
TestEmpty<ObjectType::Tree>(storage);
TestExternal<ObjectType::Tree>(config.Get(), storage);
TestCompactification<ObjectType::Tree>(config.Get(), storage);
}
SECTION("Executable") {
TestLarge<ObjectType::Executable>(config.Get(), storage);
TestSmall<ObjectType::Executable>(storage);
TestEmpty<ObjectType::Executable>(storage);
TestExternal<ObjectType::Executable>(config.Get(), storage);
TestCompactification<ObjectType::Executable>(config.Get(), storage);
}
}
// Test uplinking of nested large objects:
// A large tree depends on a number of nested objects:
//
// large_tree
// | - nested_blob
// | - nested_tree
// | |- other nested entries
// | - other entries
//
// All large entries are preliminarily split and the spliced results are
// deleted. The youngest generation is empty. Uplinking must restore the
// object(and it's parts) and uplink them properly.
TEST_CASE("LargeObjectCAS: uplink nested large objects", "[storage]") {
auto const storage_config = TestStorageConfig::Create();
auto const storage = Storage::Create(&storage_config.Get());
auto const& cas = storage.CAS();
// Randomize a large directory:
auto tree_path = LargeTestUtils::Tree::Generate(
std::string("nested_tree"), LargeTestUtils::Tree::kLargeSize);
REQUIRE(tree_path);
// Randomize a large nested tree:
auto const nested_tree = (*tree_path) / "nested_tree";
REQUIRE(LargeObjectUtils::GenerateDirectory(
nested_tree, LargeTestUtils::Tree::kLargeSize));
// Randomize a large nested blob:
auto nested_blob = (*tree_path) / "nested_blob";
REQUIRE(LargeObjectUtils::GenerateFile(nested_blob,
LargeTestUtils::File::kLargeSize));
// Add the nested tree to the CAS:
auto nested_tree_digest = LargeTestUtils::Tree::StoreRaw(cas, nested_tree);
REQUIRE(nested_tree_digest);
auto nested_tree_path = cas.TreePath(*nested_tree_digest);
REQUIRE(nested_tree_path);
// Add the nested blob to the CAS:
auto nested_blob_digest = cas.StoreBlob(nested_blob, false);
REQUIRE(nested_blob_digest);
auto nested_blob_path = cas.BlobPath(*nested_blob_digest, false);
REQUIRE(nested_blob_path);
// Add the initial large directory to the CAS:
auto large_tree_digest = LargeTestUtils::Tree::StoreRaw(cas, *tree_path);
REQUIRE(large_tree_digest);
auto large_tree_path = cas.TreePath(*large_tree_digest);
REQUIRE(large_tree_path);
// Split large entries:
auto split_nested_tree = cas.SplitTree(*nested_tree_digest);
REQUIRE(split_nested_tree);
auto split_nested_blob = cas.SplitBlob(*nested_blob_digest);
REQUIRE(split_nested_blob);
auto split_large_tree = cas.SplitTree(*large_tree_digest);
REQUIRE(split_large_tree);
// Remove the spliced results:
REQUIRE(FileSystemManager::RemoveFile(*nested_tree_path));
REQUIRE(FileSystemManager::RemoveFile(*nested_blob_path));
REQUIRE(FileSystemManager::RemoveFile(*large_tree_path));
// Rotate generations:
REQUIRE(GarbageCollector::TriggerGarbageCollection(storage_config.Get()));
// Ask to splice the large tree:
auto result_path = cas.TreePath(*large_tree_digest);
REQUIRE(result_path);
// Only the main object must be reconstructed:
CHECK(FileSystemManager::IsFile(*large_tree_path));
// It's parts must not be reconstructed by default:
CHECK_FALSE(FileSystemManager::IsFile(*nested_tree_path));
CHECK_FALSE(FileSystemManager::IsFile(*nested_blob_path));
auto const latest = ::Generation::Create(&storage_config.Get());
// However, in native mode they might be reconstructed on request because
// their entries are in the latest generation:
if (not Compatibility::IsCompatible()) {
auto split_nested_tree_2 = latest.CAS().SplitTree(*nested_tree_digest);
REQUIRE(split_nested_tree_2);
auto split_nested_blob_2 = latest.CAS().SplitBlob(*nested_blob_digest);
REQUIRE(split_nested_blob_2);
}
// Check there are no spliced results in old generations:
for (std::size_t i = 1; i < storage_config.Get().num_generations; ++i) {
auto const storage =
::Generation::Create(&storage_config.Get(), /*generation=*/i);
auto const& generation_cas = storage.CAS();
REQUIRE_FALSE(generation_cas.TreePath(*nested_tree_digest));
REQUIRE_FALSE(generation_cas.TreePath(*large_tree_digest));
REQUIRE_FALSE(generation_cas.BlobPath(*nested_blob_digest,
/*is_executable=*/false));
}
}
namespace {
/// \brief Extends the lifetime of large files for the whole set of tests.
class TestFilesDirectory final {
public:
[[nodiscard]] static auto Instance() noexcept -> TestFilesDirectory const& {
static TestFilesDirectory directory;
return directory;
}
[[nodiscard]] auto GetPath() const noexcept -> std::filesystem::path {
return temp_directory_->GetPath();
}
private:
TmpDirPtr temp_directory_;
explicit TestFilesDirectory() noexcept {
auto test_dir = FileSystemManager::GetCurrentDirectory() / "tmp";
temp_directory_ = TmpDir::Create(test_dir / "tmp_space");
}
};
namespace LargeTestUtils {
template <bool IsExecutable>
auto Blob<IsExecutable>::Create(LocalCAS<kDefaultDoGlobalUplink> const& cas,
std::string const& id,
std::uintmax_t size) noexcept
-> std::optional<std::pair<ArtifactDigest, std::filesystem::path>> {
auto path = Generate(id, size);
auto digest = path ? cas.StoreBlob(*path, IsExecutable) : std::nullopt;
auto blob_path =
digest ? cas.BlobPath(*digest, IsExecutable) : std::nullopt;
if (digest and blob_path) {
return std::make_pair(std::move(*digest), std::move(*blob_path));
}
return std::nullopt;
}
template <bool IsExecutable>
auto Blob<IsExecutable>::Generate(std::string const& id,
std::uintmax_t size) noexcept
-> std::optional<std::filesystem::path> {
std::string const path_id = "blob" + id;
auto path = TestFilesDirectory::Instance().GetPath() / path_id;
if (FileSystemManager::IsFile(path) or
LargeObjectUtils::GenerateFile(path, size)) {
return path;
}
return std::nullopt;
}
auto Tree::Create(LocalCAS<kDefaultDoGlobalUplink> const& cas,
std::string const& id,
std::uintmax_t entries_count) noexcept
-> std::optional<std::pair<ArtifactDigest, std::filesystem::path>> {
auto path = Generate(id, entries_count);
auto digest = path ? StoreRaw(cas, *path) : std::nullopt;
auto cas_path = digest ? cas.TreePath(*digest) : std::nullopt;
if (digest and cas_path) {
return std::make_pair(std::move(*digest), std::move(*cas_path));
}
return std::nullopt;
}
auto Tree::Generate(std::string const& id,
std::uintmax_t entries_count) noexcept
-> std::optional<std::filesystem::path> {
std::string const path_id = "tree" + id;
auto path = TestFilesDirectory::Instance().GetPath() / path_id;
if (FileSystemManager::IsDirectory(path) or
LargeObjectUtils::GenerateDirectory(path, entries_count)) {
return path;
}
return std::nullopt;
}
auto Tree::StoreRaw(LocalCAS<kDefaultDoGlobalUplink> const& cas,
std::filesystem::path const& directory) noexcept
-> std::optional<ArtifactDigest> {
if (not FileSystemManager::IsDirectory(directory)) {
return std::nullopt;
}
auto store_blob = [&cas](std::filesystem::path const& path,
auto is_exec) -> std::optional<bazel_re::Digest> {
return cas.StoreBlob</*kOwner=*/true>(path, is_exec);
};
auto store_tree =
[&cas](std::string const& content) -> std::optional<bazel_re::Digest> {
return cas.StoreTree(content);
};
auto store_symlink =
[&cas](std::string const& content) -> std::optional<bazel_re::Digest> {
return cas.StoreBlob(content);
};
auto bazel_digest =
Compatibility::IsCompatible()
? BazelMsgFactory::CreateDirectoryDigestFromLocalTree(
directory, store_blob, store_tree, store_symlink)
: BazelMsgFactory::CreateGitTreeDigestFromLocalTree(
directory, store_blob, store_tree, store_symlink);
if (bazel_digest.has_value()) {
return static_cast<ArtifactDigest>(*bazel_digest);
}
return std::nullopt;
}
} // namespace LargeTestUtils
} // namespace
|