Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
file_b_tree_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
36#include <gtest/gtest.h>
37
38#include <array>
39#include <atomic>
40#include <bit>
41#include <chrono>
42#include <cstdint>
43#include <filesystem>
44#include <fstream>
45#include <string>
46#include <vector>
47
48#include <tpl_file_bplus_tree.H>
49#include <tpl_file_b_tree.H>
50
51#if defined(__unix__) || defined(__APPLE__)
52# include <sys/wait.h>
53# include <unistd.h>
54#endif
55
56using namespace Aleph;
57
58namespace
59{
60 namespace fs = std::filesystem;
61
62 fs::path make_temp_path()
63 {
64 // Cada test debe usar un path único. Bajo `ctest --parallel N`,
65 // `gtest_discover_tests` invoca el mismo binario varias veces en
66 // paralelo (una por TEST), y dos invocaciones que arranquen muy
67 // próximas pueden obtener el mismo `steady_clock::now()` con la
68 // resolución limitada de macOS. Incluir el PID garantiza unicidad
69 // entre procesos; el counter atómico garantiza unicidad dentro del
70 // mismo proceso. Sin esta combinación, dos tests podían pelearse
71 // por el mismo `.idx.lock` y uno fallaba al adquirirlo.
72 static std::atomic<unsigned long long> counter{0};
73 const auto now = std::chrono::steady_clock::now().time_since_epoch().count();
74#if defined(__unix__) || defined(__APPLE__)
75 const auto pid = static_cast<long long>(::getpid());
76#else
77 const auto pid = 0LL;
78#endif
79 const auto id = std::to_string(pid) + "_" +
80 std::to_string(now) + "_" +
81 std::to_string(counter++);
82 const fs::path dir = fs::temp_directory_path() / "aleph_file_btree_tests";
83 fs::create_directories(dir);
84 return dir / (id + ".idx");
85 }
86
87 struct TempFile
88 {
89 fs::path path;
90
91 explicit TempFile(fs::path p) : path(std::move(p)) {}
92
93 ~TempFile()
94 {
95 std::error_code ec;
96 fs::remove(path, ec);
97 fs::remove(path.string() + ".wal", ec);
98 fs::remove(path.string() + ".wal.tmp", ec);
99 fs::remove(path.string() + ".journal", ec);
100 fs::remove(path.string() + ".journal.tmp", ec);
101 fs::remove(path.string() + ".lock", ec);
102 fs::remove(path.string() + ".old", ec);
103 fs::remove(path.string() + ".new", ec);
104 }
105 };
106
107 template <typename T>
108 std::vector<T> to_vector(const Array<T> & arr)
109 {
110 std::vector<T> ret;
111 ret.reserve(arr.size());
112 for (size_t i = 0; i < arr.size(); ++i)
113 ret.push_back(arr[i]);
114 return ret;
115 }
116
117#if defined(__unix__) || defined(__APPLE__)
118 constexpr bool running_under_tsan() noexcept
119 {
120# if defined(__SANITIZE_THREAD__)
121 return true;
122# elif defined(__has_feature)
123# if __has_feature(thread_sanitizer)
124 return true;
125# else
126 return false;
127# endif
128# else
129 return false;
130# endif
131 }
132
133 template <typename F>
134 int run_in_child(F && fn)
135 {
136 const pid_t pid = ::fork();
137 if (pid < 0)
138 return -1;
139
140 if (pid == 0)
141 {
142 int code = 100;
143 try
144 {
145 code = fn();
146 }
147 catch (...)
148 {
149 code = 101;
150 }
151 ::_exit(code);
152 }
153
154 int status = 0;
155 if (::waitpid(pid, &status, 0) < 0)
156 return -1;
157 return status;
158 }
159#endif
160
161 constexpr std::uint32_t file_b_tree_test_file_version = 5;
162 constexpr std::uint32_t file_b_tree_test_wal_version = 4;
163 constexpr std::uint32_t file_b_tree_test_key_size =
165 constexpr std::uint64_t file_b_tree_test_min_degree = 3;
166 constexpr std::uint8_t file_b_tree_test_encoding = 3;
167
168 std::array<char, 7> file_b_tree_reserved_bytes()
169 {
170 std::array<char, 7> reserved = {};
172 reserved[0] = static_cast<char>(codec_id & 0xFFu);
173 reserved[1] = static_cast<char>((codec_id >> 8) & 0xFFu);
174 reserved[2] = static_cast<char>((codec_id >> 16) & 0xFFu);
175 reserved[3] = static_cast<char>((codec_id >> 24) & 0xFFu);
176 return reserved;
177 }
178
179 std::uint32_t btree_wal_checksum(const std::uint32_t wal_version,
180 const std::uint64_t size,
181 const std::uint64_t root_page,
182 const std::uint64_t page_count,
183 const std::uint64_t free_page_head,
184 const std::uint64_t checkpoint_sequence,
185 const std::uint64_t dirty_count)
186 {
187 std::uint32_t crc = Aleph::detail::crc32_begin();
189
196 crc = Aleph::detail::crc32_add(crc, root_page);
197 crc = Aleph::detail::crc32_add(crc, page_count);
198 crc = Aleph::detail::crc32_add(crc, free_page_head);
199 crc = Aleph::detail::crc32_add(crc, checkpoint_sequence);
202 }
203
204 std::uint32_t btree_wal_payload_checksum(const std::uint64_t page_count,
205 const size_t page_bytes,
206 const std::vector<char> & page_blob)
207 {
208 std::uint32_t crc = Aleph::detail::crc32_begin();
209 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
210 {
211 crc = Aleph::detail::crc32_add(crc, page_id);
213 crc, page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
214 }
216 }
217
218 void write_btree_wal_from_file(const fs::path & data_path,
219 const fs::path & wal_path)
220 {
221 constexpr std::uint32_t file_version = file_b_tree_test_file_version;
222 constexpr std::uint32_t wal_version = file_b_tree_test_wal_version;
223 constexpr std::uint32_t key_size = file_b_tree_test_key_size;
224 constexpr std::uint64_t min_degree = file_b_tree_test_min_degree;
225 constexpr std::uint8_t encoding = file_b_tree_test_encoding;
226 constexpr size_t max_keys = 5;
227 constexpr size_t max_children = 6;
228 constexpr size_t page_bytes =
229 sizeof(std::uint8_t) + sizeof(std::uint16_t) * 2 + 3
230 + sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(int) * max_keys
231 + sizeof(std::uint64_t) * max_children + sizeof(std::uint32_t);
232
233 std::ifstream in(data_path, std::ios::binary);
234 ASSERT_TRUE(in.good());
235
236 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
237 in.read(magic.data(), magic.size());
238 ASSERT_TRUE(in.good());
239
240 auto read_u32 = [&](std::uint32_t & value)
241 {
242 in.read(reinterpret_cast<char *>(&value), sizeof(value));
243 ASSERT_TRUE(in.good());
244 };
245 auto read_u64 = [&](std::uint64_t & value)
246 {
247 in.read(reinterpret_cast<char *>(&value), sizeof(value));
248 ASSERT_TRUE(in.good());
249 };
250 auto read_u8 = [&](std::uint8_t & value)
251 {
252 in.read(reinterpret_cast<char *>(&value), sizeof(value));
253 ASSERT_TRUE(in.good());
254 };
255
256 std::uint32_t version = 0;
257 std::uint32_t file_key_size = 0;
258 std::uint64_t file_min_degree = 0;
259 std::uint8_t file_encoding = 0;
260 read_u32(version);
264 ASSERT_EQ(version, file_version);
265 ASSERT_EQ(file_key_size, key_size);
266 ASSERT_EQ(file_min_degree, min_degree);
268
270 std::array<char, 7> stored_reserved = {};
271 in.read(stored_reserved.data(), stored_reserved.size());
272 ASSERT_TRUE(in.good());
274
275 std::uint64_t size = 0;
276 std::uint64_t root_page = 0;
277 std::uint64_t page_count = 0;
278 std::uint64_t free_page_head = 0;
279 std::uint64_t checkpoint_sequence = 0;
280 std::uint32_t checksum = 0;
281 read_u64(size);
282 read_u64(root_page);
283 read_u64(page_count);
284 read_u64(free_page_head);
285 read_u64(checkpoint_sequence);
287
288 std::vector<char> page_blob(page_bytes * page_count);
289 in.read(page_blob.data(), page_blob.size());
290 ASSERT_TRUE(in.good());
291
292 std::ofstream out(wal_path, std::ios::binary | std::ios::trunc);
293 ASSERT_TRUE(out.good());
294
295 const auto wal_magic =
297 const auto trailer_magic =
299 out.write(wal_magic.data(), wal_magic.size());
300 out.write(reinterpret_cast<const char *>(&wal_version), sizeof(wal_version));
301 out.write(reinterpret_cast<const char *>(&key_size), sizeof(key_size));
302 out.write(reinterpret_cast<const char *>(&min_degree), sizeof(min_degree));
303 out.write(reinterpret_cast<const char *>(&encoding), sizeof(encoding));
304 out.write(reserved.data(), reserved.size());
305 out.write(reinterpret_cast<const char *>(&size), sizeof(size));
306 out.write(reinterpret_cast<const char *>(&root_page), sizeof(root_page));
307 out.write(reinterpret_cast<const char *>(&page_count), sizeof(page_count));
308 out.write(reinterpret_cast<const char *>(&free_page_head), sizeof(free_page_head));
309 out.write(reinterpret_cast<const char *>(&checkpoint_sequence), sizeof(checkpoint_sequence));
310 out.write(reinterpret_cast<const char *>(&page_count), sizeof(page_count));
311 const auto wal_checksum =
312 btree_wal_checksum(wal_version, size, root_page, page_count, free_page_head,
313 checkpoint_sequence, page_count);
314 out.write(reinterpret_cast<const char *>(&wal_checksum), sizeof(wal_checksum));
315
316 for (std::uint64_t page_id = 1; page_id <= page_count; ++page_id)
317 {
318 out.write(reinterpret_cast<const char *>(&page_id), sizeof(page_id));
319 out.write(page_blob.data() + (page_id - 1) * page_bytes, page_bytes);
320 }
321
322 const auto payload_checksum =
323 btree_wal_payload_checksum(page_count, page_bytes, page_blob);
324 out.write(trailer_magic.data(), trailer_magic.size());
325 out.write(reinterpret_cast<const char *>(&checkpoint_sequence),
326 sizeof(checkpoint_sequence));
327 out.write(reinterpret_cast<const char *>(&page_count), sizeof(page_count));
328 out.write(reinterpret_cast<const char *>(&payload_checksum),
329 sizeof(payload_checksum));
330
331 ASSERT_TRUE(out.good());
332 }
333
334 std::uint64_t read_btree_page_count(const fs::path & data_path)
335 {
336 std::ifstream in(data_path, std::ios::binary);
337 EXPECT_TRUE(in.good());
338 if (not in.good())
339 return 0;
340
342 + sizeof(std::uint32_t) * 2 + sizeof(std::uint64_t)
343 + sizeof(std::uint8_t) + 7 + sizeof(std::uint64_t) * 2);
344 EXPECT_TRUE(in.good());
345 std::uint64_t page_count = 0;
346 in.read(reinterpret_cast<char *>(&page_count), sizeof(page_count));
347 EXPECT_TRUE(in.good());
348 return page_count;
349 }
350
351 int read_btree_root_first_key(const fs::path & data_path)
352 {
353 constexpr size_t header_bytes =
354 Aleph::detail::Ordered_Tree_Snapshot_Magic_Size + sizeof(std::uint32_t) * 3
355 + sizeof(std::uint64_t) * 5 + sizeof(std::uint8_t) + 7;
356 constexpr size_t page_bytes =
357 sizeof(std::uint8_t) + sizeof(std::uint16_t) * 2 + 3
358 + sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(int) * 5
359 + sizeof(std::uint64_t) * 6 + sizeof(std::uint32_t);
360
361 std::ifstream in(data_path, std::ios::binary);
362 EXPECT_TRUE(in.good());
363 if (not in.good())
364 return 0;
365
367 + sizeof(std::uint32_t) * 2 + sizeof(std::uint64_t)
368 + sizeof(std::uint8_t) + 7 + sizeof(std::uint64_t));
369 EXPECT_TRUE(in.good());
370
371 std::uint64_t root_page = 0;
372 in.read(reinterpret_cast<char *>(&root_page), sizeof(root_page));
373 EXPECT_TRUE(in.good());
374
375 const auto offset = static_cast<std::streamoff>(
376 header_bytes + (root_page - 1) * page_bytes + sizeof(std::uint8_t)
377 + sizeof(std::uint16_t) * 2 + 3 + sizeof(std::uint64_t) * 2);
378 in.seekg(offset);
379 EXPECT_TRUE(in.good());
380
381 std::array<unsigned char, Aleph::detail::Paged_Value_Codec<int>::encoded_size>
382 key_bytes = {};
383 in.read(reinterpret_cast<char *>(key_bytes.data()), key_bytes.size());
384 EXPECT_TRUE(in.good());
386 }
387
388 std::vector<std::uint64_t> read_btree_wal_page_ids(const fs::path & wal_path)
389 {
390 constexpr size_t page_bytes =
391 sizeof(std::uint8_t) + sizeof(std::uint16_t) * 2 + 3
392 + sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(int) * 5
393 + sizeof(std::uint64_t) * 6 + sizeof(std::uint32_t);
394
395 std::ifstream in(wal_path, std::ios::binary);
396 EXPECT_TRUE(in.good());
397 std::vector<std::uint64_t> page_ids;
398 if (not in.good())
399 return page_ids;
400
401 auto read_u32 = [&](std::uint32_t & value)
402 {
403 in.read(reinterpret_cast<char *>(&value), sizeof(value));
404 EXPECT_TRUE(in.good());
405 };
406 auto read_u64 = [&](std::uint64_t & value)
407 {
408 in.read(reinterpret_cast<char *>(&value), sizeof(value));
409 EXPECT_TRUE(in.good());
410 };
411 auto read_u8 = [&](std::uint8_t & value)
412 {
413 in.read(reinterpret_cast<char *>(&value), sizeof(value));
414 EXPECT_TRUE(in.good());
415 };
416
417 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size> magic = {};
418 std::array<char, 7> reserved = {};
419 std::array<char, Aleph::detail::Ordered_Tree_Snapshot_Magic_Size>
420 trailer_magic = {};
421 std::uint32_t wal_version = 0;
422 std::uint32_t key_size = 0;
423 std::uint64_t min_degree = 0;
424 std::uint8_t encoding = 0;
425 std::uint64_t ignored64 = 0;
426 std::uint32_t ignored32 = 0;
427 std::uint64_t dirty_count = 0;
428
429 in.read(magic.data(), magic.size());
430 EXPECT_TRUE(in.good());
432 read_u32(key_size);
433 read_u64(min_degree);
435 in.read(reserved.data(), reserved.size());
436 EXPECT_TRUE(in.good());
437 read_u64(ignored64); // size
438 read_u64(ignored64); // root page
439 read_u64(ignored64); // page count
440 read_u64(ignored64); // free page head
441 read_u64(ignored64); // checkpoint sequence
443 read_u32(ignored32); // header checksum
444
445 page_ids.reserve(dirty_count);
446 for (std::uint64_t i = 0; i < dirty_count; ++i)
447 {
448 std::uint64_t page_id = 0;
449 read_u64(page_id);
450 page_ids.push_back(page_id);
451 in.seekg(static_cast<std::streamoff>(page_bytes), std::ios::cur);
452 EXPECT_TRUE(in.good());
453 }
454
455 in.read(trailer_magic.data(), trailer_magic.size());
456 EXPECT_TRUE(in.good());
457 return page_ids;
458 }
459
460 void copy_region(const fs::path & src, const fs::path & dst,
461 const std::streamoff offset, const size_t size)
462 {
463 std::ifstream in(src, std::ios::binary);
464 ASSERT_TRUE(in.good());
465 std::fstream out(dst, std::ios::binary | std::ios::in | std::ios::out);
466 ASSERT_TRUE(out.good());
467
468 std::vector<char> buffer(size);
469 in.seekg(offset);
470 ASSERT_TRUE(in.good());
471 in.read(buffer.data(), buffer.size());
472 ASSERT_TRUE(in.good());
473
474 out.seekp(offset);
475 ASSERT_TRUE(out.good());
476 out.write(buffer.data(), buffer.size());
477 ASSERT_TRUE(out.good());
478 }
479}
480
482{
483 TempFile tmp(make_temp_path());
484
485 {
486 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
487 for (int value : {40, 10, 90, 20, 70, 60, 30})
488 EXPECT_TRUE(tree.insert(value));
489
490 EXPECT_TRUE(tree.verify());
491 EXPECT_EQ(to_vector(tree.keys()),
492 (std::vector<int>{10, 20, 30, 40, 60, 70, 90}));
493 }
494
496 EXPECT_TRUE(reopened.verify());
498 (std::vector<int>{10, 20, 30, 40, 60, 70, 90}));
499 EXPECT_EQ(reopened.lower_bound(55), std::optional<int>(60));
500 EXPECT_EQ(reopened.upper_bound(60), std::optional<int>(70));
501}
502
504{
505 TempFile tmp(make_temp_path());
506
507 {
508 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
510 EXPECT_TRUE(tree.insert(10));
511 EXPECT_TRUE(tree.insert(20));
512 }
513
514 {
516 EXPECT_TRUE(reopened.is_empty());
517 }
518
519 {
520 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
521 tree.set_auto_sync(false);
522 EXPECT_TRUE(tree.insert(10));
523 EXPECT_TRUE(tree.insert(20));
524 tree.sync();
525 }
526
528 EXPECT_EQ(to_vector(reopened.keys()), (std::vector<int>{10, 20}));
529}
530
532{
533 TempFile tmp(make_temp_path());
534
535 std::uint64_t first = 0;
536 std::uint64_t second = 0;
537
538 {
539 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
540 EXPECT_EQ(tree.checkpoint_sequence(), 1u);
541 EXPECT_TRUE(tree.insert(10));
542 tree.checkpoint();
543 first = tree.checkpoint_sequence();
544 EXPECT_GT(first, 1u);
545
546 EXPECT_TRUE(tree.insert(20));
547 tree.sync();
548 second = tree.checkpoint_sequence();
549 EXPECT_GT(second, first);
550 }
551
553 EXPECT_EQ(reopened.checkpoint_sequence(), second);
554}
555
557{
558 TempFile tmp(make_temp_path());
560 const std::vector<std::string> expected = {
561 "alfa", "beta", "delta", "epsilon", "gama"
562 };
563
564 {
566 tree(tmp.path.string(), false);
567 EXPECT_TRUE(tree.insert("delta"));
568 EXPECT_TRUE(tree.insert("alfa"));
569 EXPECT_TRUE(tree.insert("gama"));
570 EXPECT_TRUE(tree.insert("beta"));
571 EXPECT_TRUE(tree.insert("epsilon"));
572 EXPECT_TRUE(tree.verify());
573 tree.sync();
574 }
575
577 reopened(tmp.path.string(),
580 EXPECT_TRUE(reopened.verify());
582 EXPECT_EQ(reopened.lower_bound(std::string("carrot")),
583 std::optional<std::string>("delta"));
584}
585
587{
588 TempFile tmp(make_temp_path());
589
590 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
591 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string(), false)),
592 std::runtime_error);
593}
594
596{
597 TempFile tmp(make_temp_path());
598
599 {
600 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
601 EXPECT_TRUE(tree.insert(10));
602 EXPECT_TRUE(tree.insert(20));
603 tree.sync();
604 }
605
607 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
609 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
611
612 EXPECT_TRUE(reader1.is_read_only());
613 EXPECT_EQ(reader1.open_mode(), read_only_mode);
614 EXPECT_EQ(to_vector(reader2.keys()), (std::vector<int>{10, 20}));
616 tmp.path.string(),
618 std::runtime_error);
619}
620
622{
623 TempFile tmp(make_temp_path());
624
625 {
626 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
627 EXPECT_TRUE(tree.insert(10));
628 tree.sync();
629 }
630
632 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
633
634 EXPECT_THROW(reader.insert(20), std::runtime_error);
635 EXPECT_THROW(reader.remove(10), std::runtime_error);
636 EXPECT_THROW(reader.clear(), std::runtime_error);
637 EXPECT_THROW(reader.sync(), std::runtime_error);
638 EXPECT_EQ(to_vector(reader.keys()), (std::vector<int>{10}));
639}
640
642{
643 TempFile tmp(make_temp_path());
644
645 {
646 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
647 EXPECT_TRUE(tree.insert(10));
648 tree.sync();
649 }
650
651 {
652 std::ofstream out(tmp.path.string() + ".wal",
653 std::ios::binary | std::ios::trunc);
654 ASSERT_TRUE(out.good());
655 out << "pending recovery";
656 }
657
659 tmp.path.string(),
661 std::runtime_error);
662}
663
665{
666#if defined(__unix__) || defined(__APPLE__)
667 if (running_under_tsan())
668 GTEST_SKIP() << "TSAN does not support these fork-based lock checks";
669
670 TempFile tmp(make_temp_path());
671
672 {
673 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
674 EXPECT_TRUE(tree.insert(10));
675 tree.sync();
676 }
677
679 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
680
681 const int shared_status = run_in_child([&]() -> int
682 {
683 try
684 {
686 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Only);
687 return child.contains(10) ? 0 : 2;
688 }
689 catch (...)
690 {
691 return 1;
692 }
693 });
697
698 const int writer_status = run_in_child([&]() -> int
699 {
700 try
701 {
703 tmp.path.string(), File_B_Tree<int, Aleph::less<int>, 3>::Read_Write);
704 return 1;
705 }
706 catch (const std::runtime_error &)
707 {
708 return 0;
709 }
710 catch (...)
711 {
712 return 2;
713 }
714 });
718#else
719 GTEST_SKIP() << "fork-based lock validation is only available on Unix-like systems";
720#endif
721}
722
724{
725 TempFile tmp(make_temp_path());
726
727 {
728 std::ofstream out(tmp.path.string() + ".lock",
729 std::ios::binary | std::ios::trunc);
730 ASSERT_TRUE(out.good());
731 out << "stale\n";
732 }
733
734 {
735 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
736 EXPECT_TRUE(tree.insert(5));
737 tree.sync();
738 }
739
740 File_B_Tree<int, Aleph::less<int>, 3> reopened(tmp.path.string(), false);
741 EXPECT_EQ(to_vector(reopened.keys()), (std::vector<int>{5}));
742}
743
745{
746 TempFile tmp(make_temp_path());
747
748 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
749 EXPECT_TRUE(tree.insert(5));
750 EXPECT_TRUE(tree.insert(15));
751 tree.sync();
752
753 EXPECT_TRUE(tree.insert(25));
754 EXPECT_TRUE(tree.remove(5));
755 EXPECT_EQ(to_vector(tree.keys()), (std::vector<int>{15, 25}));
756
757 tree.reload();
758 EXPECT_EQ(to_vector(tree.keys()), (std::vector<int>{5, 15}));
759 EXPECT_TRUE(tree.verify());
760}
761
763{
764 TempFile tmp(make_temp_path());
765
766 {
767 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
768 out << "not a valid Aleph snapshot";
769 }
770
771 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
772 std::runtime_error);
773}
774
776{
777 TempFile tmp(make_temp_path());
778
779 {
780 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
781 for (int value : {10, 20, 30, 40, 50})
782 EXPECT_TRUE(tree.insert(value));
783 }
784
785 std::fstream io(tmp.path, std::ios::binary | std::ios::in | std::ios::out);
786 ASSERT_TRUE(io.good());
787 io.seekg(0, std::ios::end);
788 const auto end = io.tellg();
789 ASSERT_GT(end, 0);
790 io.seekg(end - std::streamoff(1));
791
792 char byte = 0;
793 io.read(&byte, 1);
794 ASSERT_TRUE(io.good());
795 byte ^= static_cast<char>(0x5A);
796 io.seekp(end - std::streamoff(1));
797 io.write(&byte, 1);
798 ASSERT_TRUE(io.good());
799 io.close();
800
801 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
802 std::runtime_error);
803}
804
806{
807 TempFile tmp(make_temp_path());
808 const auto journal = tmp.path.string() + ".journal";
809
810 {
811 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
812 for (int value : {12, 6, 18, 3, 9, 15, 21})
813 EXPECT_TRUE(tree.insert(value));
814 tree.sync();
815 }
816
817 std::error_code ec;
818 fs::copy_file(tmp.path, journal, fs::copy_options::overwrite_existing, ec);
819 ASSERT_FALSE(ec) << ec.message();
820
821 {
822 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
823 ASSERT_TRUE(out.good());
824 out << "corrupt";
825 }
826
828 EXPECT_TRUE(reopened.verify());
830 (std::vector<int>{3, 6, 9, 12, 15, 18, 21}));
831 EXPECT_FALSE(fs::exists(journal));
832}
833
835{
836 TempFile tmp(make_temp_path());
837 const auto wal = tmp.path.string() + ".wal";
838
839 {
840 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
841 for (int value : {12, 6, 18, 3, 9, 15, 21})
842 EXPECT_TRUE(tree.insert(value));
843 }
844
846
847 {
848 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
849 ASSERT_TRUE(out.good());
850 out << "corrupt";
851 }
852
854 EXPECT_TRUE(reopened.verify());
856 (std::vector<int>{3, 6, 9, 12, 15, 18, 21}));
857 EXPECT_FALSE(fs::exists(wal));
858}
859
861{
862 TempFile tmp(make_temp_path());
863 const auto wal = tmp.path.string() + ".wal";
864
865 {
866 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
867 for (int value : {12, 6, 18, 3, 9, 15, 21})
868 EXPECT_TRUE(tree.insert(value));
869 }
870
872
873 std::fstream wal_io(wal, std::ios::binary | std::ios::in | std::ios::out);
874 ASSERT_TRUE(wal_io.good());
875 wal_io.seekg(0, std::ios::end);
876 const auto wal_end = wal_io.tellg();
877 ASSERT_GT(wal_end, 0);
878 wal_io.seekg(wal_end - std::streamoff(1));
879
880 char byte = 0;
881 wal_io.read(&byte, 1);
882 ASSERT_TRUE(wal_io.good());
883 byte ^= static_cast<char>(0x11);
884 wal_io.seekp(wal_end - std::streamoff(1));
885 wal_io.write(&byte, 1);
886 ASSERT_TRUE(wal_io.good());
887 wal_io.close();
888
889 {
890 std::ofstream out(tmp.path, std::ios::binary | std::ios::trunc);
891 ASSERT_TRUE(out.good());
892 out << "corrupt";
893 }
894
895 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
896 std::runtime_error);
897}
898
900{
901 constexpr size_t header_bytes =
902 Aleph::detail::Ordered_Tree_Snapshot_Magic_Size + sizeof(std::uint32_t) * 3
903 + sizeof(std::uint64_t) * 5 + sizeof(std::uint8_t) + 7;
904 constexpr size_t page_bytes =
905 sizeof(std::uint8_t) + sizeof(std::uint16_t) * 2 + 3
906 + sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(int) * 5
907 + sizeof(std::uint64_t) * 6 + sizeof(std::uint32_t);
908
909 TempFile tmp(make_temp_path());
910 const auto wal = tmp.path.string() + ".wal";
911 const auto old_snapshot = tmp.path.string() + ".old";
912 const auto new_snapshot = tmp.path.string() + ".new";
913
914 {
915 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
916 for (int value = 1; value <= 40; ++value)
917 EXPECT_TRUE(tree.insert(value));
918 tree.sync();
919 }
920
921 std::error_code ec;
922 fs::copy_file(tmp.path, old_snapshot, fs::copy_options::overwrite_existing, ec);
923 ASSERT_FALSE(ec) << ec.message();
924
925 const int root_key = read_btree_root_first_key(tmp.path);
926
927 {
928 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
930 tree.sync();
931 }
932
933 fs::copy_file(tmp.path, new_snapshot, fs::copy_options::overwrite_existing, ec);
934 ASSERT_FALSE(ec) << ec.message();
936
939 ASSERT_GE(dirty_pages.size(), 2u);
940
941 fs::copy_file(old_snapshot, tmp.path, fs::copy_options::overwrite_existing, ec);
942 ASSERT_FALSE(ec) << ec.message();
943 copy_region(new_snapshot, tmp.path, 0, header_bytes);
945 static_cast<std::streamoff>(header_bytes
946 + (dirty_pages.front() - 1) * page_bytes),
947 page_bytes);
948
951 EXPECT_TRUE(reopened.verify());
953 EXPECT_FALSE(fs::exists(wal));
954}
955
957{
958 TempFile tmp(make_temp_path());
959 const auto wal = tmp.path.string() + ".wal";
960 const auto old_snapshot = tmp.path.string() + ".old";
961
962 {
963 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
964 for (int value : {12, 6, 18, 3, 9, 15, 21})
965 EXPECT_TRUE(tree.insert(value));
966 tree.sync();
967 }
968
969 std::error_code ec;
970 fs::copy_file(tmp.path, old_snapshot, fs::copy_options::overwrite_existing, ec);
971 ASSERT_FALSE(ec) << ec.message();
972
973 {
974 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
975 EXPECT_TRUE(tree.insert(24));
976 EXPECT_TRUE(tree.insert(27));
977 tree.sync();
978 }
979
981 fs::remove(old_snapshot, ec);
982
984 EXPECT_TRUE(reopened.verify());
986 (std::vector<int>{3, 6, 9, 12, 15, 18, 21, 24, 27}));
987 EXPECT_FALSE(fs::exists(wal));
988}
989
991{
992 TempFile tmp(make_temp_path());
993
994 {
995 File_BPlus_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string());
996 EXPECT_TRUE(tree.insert(1));
997 EXPECT_TRUE(tree.insert(2));
998 }
999
1000 EXPECT_THROW((File_B_Tree<int, Aleph::less<int>, 3>(tmp.path.string())),
1001 std::runtime_error);
1002}
1003
1005{
1006 TempFile tmp(make_temp_path());
1007
1008 {
1009 File_B_Tree<int, Aleph::less<int>, 3> tree(tmp.path.string(), false);
1010 for (int value = 1; value <= 80; ++value)
1011 EXPECT_TRUE(tree.insert(value));
1012
1013 for (int value : {1, 2, 3, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 80})
1014 EXPECT_TRUE(tree.remove(value));
1015
1016 EXPECT_TRUE(tree.verify());
1017 tree.sync();
1018 }
1019
1021 EXPECT_TRUE(reopened.verify());
1022 EXPECT_EQ(reopened.min_key(), std::optional<int>(4));
1023 EXPECT_EQ(reopened.max_key(), std::optional<int>(79));
1025 (std::vector<int>{
1026 4, 5, 6, 10, 11, 12, 13, 14, 18, 19, 20, 21, 22, 23, 24, 25,
1027 26, 27, 28, 29, 30, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
1028 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
1029 61, 62, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79
1030 }));
1031}
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
Persistent page-managed B+ Tree stored in a single binary file.
bool insert(const Key &key)
Insert a key if it is not already present.
Persistent page-managed B-Tree stored in a single binary file.
bool verify() const
Verify structural B-Tree invariants across cached pages.
void set_auto_sync(const bool enabled) noexcept
Enable or disable automatic flushing of dirty pages.
bool contains(const Key &key) const
Return whether a key is present.
bool auto_sync_enabled() const noexcept
Return whether automatic synchronization is enabled.
Array< Key > keys() const
Materialize the tree contents in sorted order.
void checkpoint() const
Synonym for sync().
void sync() const
Flush the current tree image using redo WAL or full-image fallback.
bool remove(const Key &key)
Remove a key if present.
void reload()
Discard unsynchronized changes and reload pages from disk.
std::uint64_t checkpoint_sequence() const noexcept
Return the durable checkpoint sequence stored in the backing file.
bool insert(const Key &key)
Insert a key if it is not already present.
Minimal std::expected-style result type for C++20.
#define TEST(name)
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
const long double offset[]
Offset values indexed by symbol string length (bounded by MAX_OFFSET_INDEX)
std::uint32_t crc32_add_bytes(std::uint32_t crc, const void *data, const size_t size) noexcept
constexpr std::array< char, Ordered_Tree_Snapshot_Magic_Size > ordered_tree_snapshot_magic(const char(&text)[N]) noexcept
std::uint32_t crc32_begin() noexcept
std::uint32_t crc32_add(std::uint32_t crc, const T &value) noexcept
constexpr size_t Ordered_Tree_Snapshot_Magic_Size
std::uint32_t crc32_finish(const std::uint32_t crc) noexcept
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
size_t size(Node *root) noexcept
std::string code(Node *root)
Compute a string with the Lukasiewicz`s word of a tree.
std::vector< typename C::Item_Type > to_vector(const C &c)
Convert a container to a std::vector.
Definition ah-convert.H:238
STL namespace.
#define LL
Definition ran_array.c:24
static long counter
Definition test-splice.C:40
Page-managed persistent B-Tree with file-backed storage.
Page-managed persistent B+ Tree with file-backed storage.