• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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