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