1 /**
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <securec.h>
17
18 #include <cstdlib>
19 #include <memory>
20 #include <vector>
21
22 #include "gtest/gtest.h"
23
24 #include "libpandabase/macros.h"
25 #include "libpandabase/utils/utils.h"
26 #include "runtime/fibers/fiber_context.h"
27
28 namespace ark::fibers::test {
29
30 /// The fixture
31 class FibersTest : public testing::Test {
32 public:
GetEntryExecCounter()33 size_t GetEntryExecCounter()
34 {
35 return entryExecCounter_;
36 }
37
IncEntryExecCounter()38 void IncEntryExecCounter()
39 {
40 ++entryExecCounter_;
41 }
42
43 private:
44 size_t entryExecCounter_ = 0;
45 };
46
47 /// A fiber instance: provides stack, registers the EP
48 class Fiber final {
49 public:
50 NO_COPY_SEMANTIC(Fiber);
51 NO_MOVE_SEMANTIC(Fiber);
52
53 static constexpr size_t STACK_SIZE = 4096 * 32;
54 static constexpr uint32_t MAGIC = 0xC001BEAF;
55
56 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
Fiber(FibersTest & fixture,Fiber * parent=nullptr,fibers::FiberEntry entry=nullptr)57 explicit Fiber(FibersTest &fixture, Fiber *parent = nullptr, fibers::FiberEntry entry = nullptr)
58 : fixture_(fixture), parent_(parent), magic_(MAGIC)
59 {
60 stack_ = new uint8_t[STACK_SIZE];
61 fibers::GetCurrentContext(&ctx_);
62 if (entry != nullptr) {
63 fibers::UpdateContext(&ctx_, entry, this, stack_, STACK_SIZE);
64 }
65 }
66
~Fiber()67 ~Fiber()
68 {
69 delete[] stack_;
70 }
71
GetContextPtr()72 fibers::FiberContext *GetContextPtr()
73 {
74 return &ctx_;
75 }
76
GetParent()77 Fiber *GetParent()
78 {
79 return parent_;
80 }
81
GetMagic()82 uint32_t GetMagic()
83 {
84 return magic_;
85 }
86
RegisterEntryExecution()87 void RegisterEntryExecution()
88 {
89 fixture_.IncEntryExecCounter();
90 }
91
92 private:
93 FibersTest &fixture_;
94 fibers::FiberContext ctx_;
95 uint8_t *stack_ = nullptr;
96 Fiber *parent_ = nullptr;
97
98 volatile uint32_t magic_;
99 };
100
101 /// Regular fiber EP: switches to parent fiber on return
Entry(void * currentFiber)102 extern "C" void Entry(void *currentFiber)
103 {
104 ASSERT_TRUE(currentFiber != nullptr);
105 auto *fCur = reinterpret_cast<Fiber *>(currentFiber);
106 ASSERT_EQ(fCur->GetMagic(), Fiber::MAGIC);
107 // increment the EP execution counter
108 fCur->RegisterEntryExecution();
109 // EOT: switch to parent (otherwise fibers lib will call abort())
110 ASSERT_TRUE(fCur->GetParent() != nullptr);
111 fibers::SwitchContext(fCur->GetContextPtr(), fCur->GetParent()->GetContextPtr());
112 }
113
114 /// Empty fiber EP: checks what happens on a simple return from a fiber
EmptyEntry(void * currentFiber)115 extern "C" void EmptyEntry([[maybe_unused]] void *currentFiber)
116 {
117 // NOLINTNEXTLINE(readability-redundant-control-flow)
118 return;
119 }
120
121 /// The EP that switches back to parent in a loop
LoopedSwitchEntry(void * currentFiber)122 extern "C" void LoopedSwitchEntry(void *currentFiber)
123 {
124 ASSERT_TRUE(currentFiber != nullptr);
125 auto *fCur = reinterpret_cast<Fiber *>(currentFiber);
126 ASSERT_EQ(fCur->GetMagic(), Fiber::MAGIC);
127
128 // some non-optimized counters...
129 volatile size_t counterInt = 0;
130 volatile double counterDbl = 0;
131 while (true) {
132 // ...and their modification...
133 ++counterInt;
134 counterDbl = static_cast<double>(counterInt);
135
136 fCur->RegisterEntryExecution();
137 ASSERT_TRUE(fCur->GetParent() != nullptr);
138 fibers::SwitchContext(fCur->GetContextPtr(), fCur->GetParent()->GetContextPtr());
139
140 // ...and the check for the counters to stay correct after the switch
141 ASSERT_DOUBLE_EQ(counterDbl, static_cast<double>(counterInt));
142 }
143 }
144
145 /* Tests*/
146
147 /// Create fiber, switch to it, execute till its end, switch back
TEST_F(FibersTest,SwitchExecuteSwitchBack)148 TEST_F(FibersTest, SwitchExecuteSwitchBack)
149 {
150 Fiber fInit(*this);
151 Fiber f1(*this, &fInit, Entry);
152 fibers::SwitchContext(fInit.GetContextPtr(), f1.GetContextPtr());
153
154 ASSERT_EQ(GetEntryExecCounter(), 1);
155 }
156
157 /**
158 * Create several fibers, organizing them in a chain using the "parent" field.
159 * Switch to the last one, wait till the whole chain is executed
160 */
TEST_F(FibersTest,ChainSwitch)161 TEST_F(FibersTest, ChainSwitch)
162 {
163 Fiber fInit(*this);
164 Fiber f1(*this, &fInit, Entry);
165 Fiber f2(*this, &f1, Entry);
166 Fiber f3(*this, &f2, Entry);
167 fibers::SwitchContext(fInit.GetContextPtr(), f3.GetContextPtr());
168
169 ASSERT_EQ(GetEntryExecCounter(), 3U);
170 }
171
172 /// Create the child fiber, then switch context back and forth several times in a loop
TEST_F(FibersTest,LoopedSwitch)173 TEST_F(FibersTest, LoopedSwitch)
174 {
175 constexpr size_t SWITCHES = 10;
176
177 Fiber fInit(*this);
178 Fiber fTarget(*this, &fInit, LoopedSwitchEntry);
179
180 // some unoptimized counters
181 volatile size_t counterInt = 0;
182 volatile double counterDbl = 0;
183 for (size_t i = 0; i < SWITCHES; ++i) {
184 counterInt = i;
185 counterDbl = static_cast<double>(i);
186
187 // do something with the context before the next switch
188 double n1 = 0;
189 double n2 = 0;
190 // NOLINTNEXTLINE(cert-err34-c, cppcoreguidelines-pro-type-vararg)
191 [[maybe_unused]] auto res = sscanf_s("1.23 4.56", "%lf %lf", &n1, &n2);
192 ASSERT(res != -1);
193
194 fibers::SwitchContext(fInit.GetContextPtr(), fTarget.GetContextPtr());
195
196 // check that no corruption occurred
197 ASSERT_DOUBLE_EQ(n1, 1.23_D);
198 ASSERT_DOUBLE_EQ(n2, 4.56_D);
199
200 // counters should not be corrupted after a switch
201 ASSERT_EQ(counterInt, i);
202 ASSERT_DOUBLE_EQ(counterDbl, static_cast<double>(i));
203 }
204
205 ASSERT_EQ(GetEntryExecCounter(), SWITCHES);
206 }
207
208 using FibersDeathTest = FibersTest;
209 /**
210 * Death test. Creates an orphaned fiber that will silently return from its entry function.
211 * Should cause the program to be abort()-ed
212 */
TEST_F(FibersDeathTest,DISABLED_AbortOnFiberReturn)213 TEST_F(FibersDeathTest, DISABLED_AbortOnFiberReturn)
214 {
215 // Death test under qemu_arm32 is not compatible with 'threadsafe' death test style flag
216 #if defined(PANDA_TARGET_ARM32) && defined(PANDA_QEMU_BUILD)
217 testing::FLAGS_gtest_death_test_style = "fast";
218 #endif
219 Fiber fInit(*this);
220 Fiber fAborts(*this, nullptr, EmptyEntry);
221 EXPECT_EXIT(fibers::SwitchContext(fInit.GetContextPtr(), fAborts.GetContextPtr()), testing::KilledBySignal(SIGABRT),
222 ".*");
223 }
224
225 } // namespace ark::fibers::test
226