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