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_unit_test/framework.h"
19
20 namespace {
21
22 using ::pw::sync::BinarySemaphore;
23 using ::pw::thread::ThreadCore;
24 using ::pw::thread::test::TestOptionsThread0;
25 using ::pw::thread::test::TestOptionsThread1;
26 using ::pw::thread::test::WaitUntilDetachedThreadsCleanedUp;
27
TEST(Thread,DefaultIds)28 TEST(Thread, DefaultIds) {
29 pw::Thread not_executing_thread;
30 EXPECT_EQ(not_executing_thread.get_id(), pw::Thread::id());
31 }
32
33 #if PW_THREAD_JOINING_ENABLED
TEST(Thread,DefaultConstructedThreadIsNotJoinable)34 TEST(Thread, DefaultConstructedThreadIsNotJoinable) {
35 pw::Thread thread;
36 EXPECT_FALSE(thread.joinable());
37 }
38
TEST(Thread,JoinWaitsForLambdaCompletion)39 TEST(Thread, JoinWaitsForLambdaCompletion) {
40 BinarySemaphore thread_ran;
41 pw::Thread thread(TestOptionsThread0(),
42 [&thread_ran] { thread_ran.release(); });
43 EXPECT_TRUE(thread.joinable());
44 thread.join();
45 EXPECT_EQ(thread.get_id(), pw::Thread::id());
46 EXPECT_TRUE(thread_ran.try_acquire());
47 }
48
49 #endif // PW_THREAD_JOINING_ENABLED
50
TEST(Thread,DetachAllowsThreadToRunAfterExitingScope)51 TEST(Thread, DetachAllowsThreadToRunAfterExitingScope) {
52 struct {
53 BinarySemaphore thread_blocker;
54 BinarySemaphore thread_finished;
55 } semaphores;
56 {
57 pw::Thread thread(TestOptionsThread0(), [&semaphores] {
58 semaphores.thread_blocker.acquire();
59 semaphores.thread_finished.release();
60 });
61 EXPECT_NE(thread.get_id(), pw::Thread::id());
62 EXPECT_TRUE(thread.joinable());
63 thread.detach();
64 EXPECT_EQ(thread.get_id(), pw::Thread::id());
65 EXPECT_FALSE(thread.joinable());
66 }
67 EXPECT_FALSE(semaphores.thread_finished.try_acquire());
68 semaphores.thread_blocker.release();
69 semaphores.thread_finished.acquire();
70
71 WaitUntilDetachedThreadsCleanedUp();
72 }
73
TEST(Thread,SwapWithoutExecution)74 TEST(Thread, SwapWithoutExecution) {
75 pw::Thread thread_0;
76 pw::Thread thread_1;
77
78 // Make sure we can swap threads which are not associated with any execution.
79 thread_0.swap(thread_1);
80 }
81
TEST(Thread,SwapWithOneExecuting)82 TEST(Thread, SwapWithOneExecuting) {
83 pw::Thread thread_0;
84 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
85
86 BinarySemaphore thread_ran_sem;
87 pw::Thread thread_1(TestOptionsThread1(),
88 [&thread_ran_sem] { thread_ran_sem.release(); });
89
90 EXPECT_NE(thread_1.get_id(), pw::Thread::id());
91
92 thread_0.swap(thread_1);
93 EXPECT_NE(thread_0.get_id(), pw::Thread::id());
94 EXPECT_EQ(thread_1.get_id(), pw::Thread::id());
95
96 thread_0.detach();
97 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
98
99 thread_ran_sem.acquire();
100 WaitUntilDetachedThreadsCleanedUp();
101 }
102
TEST(Thread,SwapWithTwoExecuting)103 TEST(Thread, SwapWithTwoExecuting) {
104 BinarySemaphore thread_a_ran_sem;
105 pw::Thread thread_0(TestOptionsThread0(),
106 [&thread_a_ran_sem] { thread_a_ran_sem.release(); });
107 BinarySemaphore thread_b_ran_sem;
108 pw::Thread thread_1(TestOptionsThread1(),
109 [&thread_b_ran_sem] { thread_b_ran_sem.release(); });
110 const pw::Thread::id thread_a_id = thread_0.get_id();
111 EXPECT_NE(thread_a_id, pw::Thread::id());
112 const pw::Thread::id thread_b_id = thread_1.get_id();
113 EXPECT_NE(thread_b_id, pw::Thread::id());
114 EXPECT_NE(thread_a_id, thread_b_id);
115
116 thread_0.swap(thread_1);
117 EXPECT_EQ(thread_1.get_id(), thread_a_id);
118 EXPECT_EQ(thread_0.get_id(), thread_b_id);
119
120 thread_0.detach();
121 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
122 thread_1.detach();
123 EXPECT_EQ(thread_1.get_id(), pw::Thread::id());
124
125 thread_a_ran_sem.acquire();
126 thread_b_ran_sem.acquire();
127 WaitUntilDetachedThreadsCleanedUp();
128 }
129
TEST(Thread,MoveOperator)130 TEST(Thread, MoveOperator) {
131 pw::Thread thread_0;
132 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
133
134 BinarySemaphore thread_ran_sem;
135 pw::Thread thread_1(TestOptionsThread1(),
136 [&thread_ran_sem] { thread_ran_sem.release(); });
137 EXPECT_NE(thread_1.get_id(), pw::Thread::id());
138
139 thread_0 = std::move(thread_1);
140 EXPECT_NE(thread_0.get_id(), pw::Thread::id());
141 #ifndef __clang_analyzer__
142 EXPECT_EQ(thread_1.get_id(), pw::Thread::id());
143 #endif // ignore use-after-move
144
145 thread_0.detach();
146 EXPECT_EQ(thread_0.get_id(), pw::Thread::id());
147
148 thread_ran_sem.acquire();
149 WaitUntilDetachedThreadsCleanedUp();
150 }
151
152 class SemaphoreReleaser : public ThreadCore {
153 public:
semaphore()154 BinarySemaphore& semaphore() { return semaphore_; }
155
156 private:
Run()157 void Run() override { semaphore_.release(); }
158
159 BinarySemaphore semaphore_;
160 };
161
TEST(Thread,ThreadCore)162 TEST(Thread, ThreadCore) {
163 SemaphoreReleaser semaphore_releaser;
164 pw::Thread thread(TestOptionsThread0(), semaphore_releaser);
165 EXPECT_NE(thread.get_id(), pw::Thread::id());
166 EXPECT_TRUE(thread.joinable());
167 thread.detach();
168 EXPECT_EQ(thread.get_id(), pw::Thread::id());
169 EXPECT_FALSE(thread.joinable());
170 semaphore_releaser.semaphore().acquire();
171
172 WaitUntilDetachedThreadsCleanedUp();
173 }
174
175 } // namespace
176