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