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