GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 0 / 75 / 75
Functions: -% 0 / 18 / 18
Branches: -% 0 / 338 / 338

test/unittest/UnitBlockAllocator.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2 // LCOV_EXCL_START
3
4 #include <gmock/gmock.h>
5 #include <gtest/gtest.h>
6
7 #include <ast/ASTNodes.h>
8 #include <util/BlockAllocator.h>
9 #include <util/CodeLoc.h>
10 #include <util/Memory.h>
11
12 namespace spice::testing {
13
14 using namespace spice::compiler;
15
16 static size_t destructedDummyNodes = 0;
17
18 class DummyNode final : public ASTNode {
19 // Constructors
20 using ASTNode::ASTNode;
21
22 // Destructors
23 ~DummyNode() override { destructedDummyNodes++; }
24
25 // Visitor methods
26 std::any accept(AbstractASTVisitor *visitor) override { return {}; } // LCOV_EXCL_LINE
27 std::any accept(ParallelizableASTVisitor *visitor) const override { return {}; } // LCOV_EXCL_LINE
28
29 // Other methods
30 GET_CHILDREN();
31 };
32 static constexpr size_t DUMMY_NODE_SIZE = sizeof(DummyNode);
33 static_assert(DUMMY_NODE_SIZE == 48, "DummyNode size has changed. Update test accordingly.");
34
35 class MockMemoryManager final : public MemoryManager {
36 public:
37 MOCK_METHOD(byte *, allocate, (size_t size), (const override));
38 MOCK_METHOD(void, deallocate, (byte * ptr), (const override));
39 };
40
41 TEST(BlockAllocatorTest, BlockAllocatorLarge) {
42 destructedDummyNodes = 0; // Reset destruction counter
43 static constexpr size_t NODE_COUNT = 100'000; // 100.000 * 48 bytes = 4.8 MB
44
45 {
46 // Create allocator, that can hold 5 nodes per block
47 constexpr DefaultMemoryManager memoryManager;
48 BlockAllocator<ASTNode> alloc(memoryManager, DUMMY_NODE_SIZE * 5);
49
50 // Allocate nodes
51 std::vector<ASTNode *> nodes;
52 for (size_t i = 0; i < NODE_COUNT; i++) {
53 auto node = alloc.allocate<DummyNode>(CodeLoc(i, 1));
54 ASSERT_NE(nullptr, node);
55 nodes.push_back(node);
56 ASSERT_EQ(i, nodes.at(i)->codeLoc.line);
57 ASSERT_EQ(1, nodes.at(i)->codeLoc.col);
58 }
59
60 // Check if stats are correct
61 ASSERT_EQ(NODE_COUNT, alloc.getAllocationCount());
62 ASSERT_EQ(6'000'000, alloc.getTotalAllocatedSize());
63
64 // Block Allocator gets destructed here and with that, all allocated nodes should be destructed
65 }
66
67 ASSERT_EQ(NODE_COUNT, destructedDummyNodes);
68 }
69
70 TEST(BlockAllocatorTest, BlockAllocatorUnevenBlockSize) {
71 destructedDummyNodes = 0; // Reset destruction counter
72 static constexpr size_t NODE_COUNT = 1'000; // 1.000 * 48 bytes = 48 KB
73
74 {
75 // Create allocator, that can hold 4.5 nodes per block
76 constexpr DefaultMemoryManager memoryManager;
77 BlockAllocator<ASTNode> alloc(memoryManager, DUMMY_NODE_SIZE * 4.5);
78
79 // Allocate nodes
80 std::vector<ASTNode *> nodes;
81 for (size_t i = 0; i < NODE_COUNT; i++) {
82 auto node = alloc.allocate<DummyNode>(CodeLoc(i, 1));
83 ASSERT_NE(nullptr, node);
84 nodes.push_back(node);
85 ASSERT_EQ(i, nodes.at(i)->codeLoc.line);
86 ASSERT_EQ(1, nodes.at(i)->codeLoc.col);
87 }
88
89 // Check if stats are correct
90 ASSERT_EQ(NODE_COUNT, alloc.getAllocationCount());
91 ASSERT_EQ(54'000, alloc.getTotalAllocatedSize());
92
93 // Block Allocator gets destructed here and with that, all allocated nodes should be destructed
94 }
95
96 ASSERT_EQ(NODE_COUNT, destructedDummyNodes);
97 }
98
99 TEST(BlockAllocatorTest, BlockAllocatorOOM) {
100 destructedDummyNodes = 0; // Reset destruction counter
101 static constexpr size_t NODE_COUNT = 10; // 10 * 48 bytes = 0.48 KB
102
103 // Prepare mock methods
104 MockMemoryManager mockMemoryManager;
105
106 // Make sure, that the memory manager returns nullptr when trying to allocate the fifth block
107 ::testing::InSequence s;
108 auto mallocCallback = [](size_t size) { return static_cast<byte *>(malloc(size)); };
109 EXPECT_CALL(mockMemoryManager, allocate(::testing::_)).Times(4).WillRepeatedly(mallocCallback);
110 EXPECT_CALL(mockMemoryManager, allocate(::testing::_)).Times(1).WillOnce(::testing::Return(nullptr));
111 EXPECT_CALL(mockMemoryManager, deallocate(::testing::_)).Times(4).WillRepeatedly(::testing::Invoke(free));
112
113 {
114 // Create allocator, that can hold 2 nodes per block
115 BlockAllocator<ASTNode> alloc(mockMemoryManager, DUMMY_NODE_SIZE * 2.25);
116
117 try {
118 // Allocate nodes
119 std::vector<ASTNode *> nodes;
120 for (size_t i = 0; i < NODE_COUNT; i++) {
121 auto node = alloc.allocate<DummyNode>(CodeLoc(i, 1));
122 ASSERT_NE(nullptr, node);
123 nodes.push_back(node);
124 ASSERT_EQ(i, nodes.at(i)->codeLoc.line);
125 ASSERT_EQ(1, nodes.at(i)->codeLoc.col);
126 }
127 FAIL(); // LCOV_EXCL_LINE - Should not reach this point
128 } catch (CompilerError &ce) {
129 std::stringstream ss;
130 ss << "[Error|Compiler]:\n";
131 ss << "An out of memory error occurred: Could not allocate memory for BlockAllocator. Already allocated 4 blocks.";
132 ASSERT_EQ(ss.str(), ce.what());
133 }
134
135 // Block Allocator gets destructed here and with that, all allocated nodes should be destructed
136 }
137
138 ASSERT_EQ(8, destructedDummyNodes); // Only 8 blocks were constructed until the OOM error occurred
139 ::testing::Mock::VerifyAndClearExpectations(&mockMemoryManager);
140 }
141
142 } // namespace spice::testing
143
144 // LCOV_EXCL_STOP
145