1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://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, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_sync/binary_semaphore.h"
16 #include "pw_thread/non_portable_test_thread_options.h"
17 #include "pw_thread/thread.h"
18 #include "pw_thread/thread_core.h"
19 #include "pw_unit_test/framework.h"
20
21 namespace {
22
23 using ::pw::sync::BinarySemaphore;
24 using ::pw::thread::ThreadCore;
25 using ::pw::thread::test::TestOptionsThread0;
26 using ::pw::thread::test::TestOptionsThread1;
27 using ::pw::thread::test::WaitUntilDetachedThreadsCleanedUp;
28
TEST(Thread,DefaultIds)29 TEST(Thread, DefaultIds) {
30 pw::Thread not_executing_thread;
31 EXPECT_EQ(not_executing_thread.get_id(), pw::Thread::id());
32 }
33
34 #if PW_THREAD_JOINING_ENABLED
TEST(Thread,DefaultConstructedThreadIsNotJoinable)35 TEST(Thread, DefaultConstructedThreadIsNotJoinable) {
36 pw::Thread thread;
37 EXPECT_FALSE(thread.joinable());
38 }
39
TEST(Thread,JoinWaitsForLambdaCompletion)40 TEST(Thread, JoinWaitsForLambdaCompletion) {
41 BinarySemaphore thread_ran;
42 pw::Thread thread(TestOptionsThread0(),
43 [&thread_ran] { thread_ran.release(); });
44 EXPECT_TRUE(thread.joinable());
45 thread.join();
46 EXPECT_EQ(thread.get_id(), pw::Thread::id());
47 EXPECT_TRUE(thread_ran.try_acquire());
48 }
49
50 #endif // PW_THREAD_JOINING_ENABLED
51
TEST(Thread,DetachAllowsThreadToRunAfterExitingScope)52 TEST(Thread, DetachAllowsThreadToRunAfterExitingScope) {
53 struct {
54 BinarySemaphore thread_blocker;
55 BinarySemaphore thread_finished;
56 } semaphores;
57 {
58 pw::Thread thread(TestOptionsThread0(), [&semaphores] {
59 semaphores.thread_blocker.acquire();
60 semaphores.thread_finished.release();
61 });
62 EXPECT_NE(thread.get_id(), pw::Thread::id());
63 EXPECT_TRUE(thread.joinable());
64 thread.detach();
65 EXPECT_EQ(thread.get_id(), pw::Thread::id());
66 EXPECT_FALSE(thread.joinable());
67 }
68 EXPECT_FALSE(semaphores.thread_finished.try_acquire());
69 semaphores.thread_blocker.release();
70 semaphores.thread_finished.acquire();
71
72 WaitUntilDetachedThreadsCleanedUp();
73 }
74
TEST(Thread,SwapWithoutExecution)75 TEST(Thread, SwapWithoutExecution) {
76 pw::Thread thread_0;
77 pw::Thread thread_1;
78
79 // Make sure we can swap threads which are not associated with any execution.
80 thread_0.swap(thread_1);
81 }
82
TEST(Thread,SwapWithOneExecuting)83 TEST(Thread, SwapWithOneExecuting) {
84 pw::Thread thread_0;
85 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
86
87 BinarySemaphore thread_ran_sem;
88 pw::Thread thread_1(TestOptionsThread1(),
89 [&thread_ran_sem] { thread_ran_sem.release(); });
90
91 EXPECT_NE(thread_1.get_id(), pw::Thread::id());
92
93 thread_0.swap(thread_1);
94 EXPECT_NE(thread_0.get_id(), pw::Thread::id());
95 EXPECT_EQ(thread_1.get_id(), pw::Thread::id());
96
97 thread_0.detach();
98 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
99
100 thread_ran_sem.acquire();
101 WaitUntilDetachedThreadsCleanedUp();
102 }
103
TEST(Thread,SwapWithTwoExecuting)104 TEST(Thread, SwapWithTwoExecuting) {
105 BinarySemaphore thread_a_ran_sem;
106 pw::Thread thread_0(TestOptionsThread0(),
107 [&thread_a_ran_sem] { thread_a_ran_sem.release(); });
108 BinarySemaphore thread_b_ran_sem;
109 pw::Thread thread_1(TestOptionsThread1(),
110 [&thread_b_ran_sem] { thread_b_ran_sem.release(); });
111 const pw::Thread::id thread_a_id = thread_0.get_id();
112 EXPECT_NE(thread_a_id, pw::Thread::id());
113 const pw::Thread::id thread_b_id = thread_1.get_id();
114 EXPECT_NE(thread_b_id, pw::Thread::id());
115 EXPECT_NE(thread_a_id, thread_b_id);
116
117 thread_0.swap(thread_1);
118 EXPECT_EQ(thread_1.get_id(), thread_a_id);
119 EXPECT_EQ(thread_0.get_id(), thread_b_id);
120
121 thread_0.detach();
122 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
123 thread_1.detach();
124 EXPECT_EQ(thread_1.get_id(), pw::Thread::id());
125
126 thread_a_ran_sem.acquire();
127 thread_b_ran_sem.acquire();
128 WaitUntilDetachedThreadsCleanedUp();
129 }
130
TEST(Thread,MoveOperator)131 TEST(Thread, MoveOperator) {
132 pw::Thread thread_0;
133 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
134
135 BinarySemaphore thread_ran_sem;
136 pw::Thread thread_1(TestOptionsThread1(),
137 [&thread_ran_sem] { thread_ran_sem.release(); });
138 EXPECT_NE(thread_1.get_id(), pw::Thread::id());
139
140 thread_0 = std::move(thread_1);
141 EXPECT_NE(thread_0.get_id(), pw::Thread::id());
142 #ifndef __clang_analyzer__
143 EXPECT_EQ(thread_1.get_id(), pw::Thread::id());
144 #endif // ignore use-after-move
145
146 thread_0.detach();
147 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
148
149 thread_ran_sem.acquire();
150 WaitUntilDetachedThreadsCleanedUp();
151 }
152
153 class SemaphoreReleaser : public ThreadCore {
154 public:
semaphore()155 BinarySemaphore& semaphore() { return semaphore_; }
156
157 private:
Run()158 void Run() override { semaphore_.release(); }
159
160 BinarySemaphore semaphore_;
161 };
162
TEST(Thread,ThreadCore)163 TEST(Thread, ThreadCore) {
164 SemaphoreReleaser semaphore_releaser;
165 pw::Thread thread(TestOptionsThread0(), semaphore_releaser);
166 EXPECT_NE(thread.get_id(), pw::Thread::id());
167 EXPECT_TRUE(thread.joinable());
168 thread.detach();
169 EXPECT_EQ(thread.get_id(), pw::Thread::id());
170 EXPECT_FALSE(thread.joinable());
171 semaphore_releaser.semaphore().acquire();
172
173 WaitUntilDetachedThreadsCleanedUp();
174 }
175
176 } // namespace
177