xref: /aosp_15_r20/external/pigweed/pw_thread_freertos/thread_iteration_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2022 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_thread/thread_iteration.h"
16 
17 #include <cstddef>
18 #include <string_view>
19 
20 #include "FreeRTOS.h"
21 #include "pw_bytes/span.h"
22 #include "pw_span/span.h"
23 #include "pw_string/string_builder.h"
24 #include "pw_string/util.h"
25 #include "pw_sync/thread_notification.h"
26 #include "pw_thread/non_portable_test_thread_options.h"
27 #include "pw_thread/thread.h"
28 #include "pw_thread/thread_info.h"
29 #include "pw_thread_freertos/freertos_tsktcb.h"
30 #include "pw_thread_freertos_private/thread_iteration.h"
31 #include "pw_unit_test/framework.h"
32 
33 namespace pw::thread::freertos {
34 namespace {
35 
36 // Tests thread iteration API by:
37 //  - Forking a test thread.
38 //  - Using iteration API to iterate over all running threads.
39 //  - Compares name of forked thread and current thread.
40 //  - Confirms thread exists and is iterated over.
TEST(ThreadIteration,ForkOneThread)41 TEST(ThreadIteration, ForkOneThread) {
42   struct {
43     sync::ThreadNotification start;
44     sync::ThreadNotification end;
45   } notify;
46 
47   const auto& options = *static_cast<const pw::thread::freertos::Options*>(
48       &thread::test::TestOptionsThread0());
49 
50   Thread t(options, [&notify]() {
51     // Release start lock to allow test thread to continue execution.
52     notify.start.release();
53     while (true) {
54       // Return only when end lock released by test thread.
55       if (notify.end.try_acquire()) {
56         return;
57       }
58     }
59   });
60 
61   // Blocked until thread t releases start lock.
62   notify.start.acquire();
63 
64   struct {
65     bool thread_exists;
66     span<const std::byte> name;
67   } temp_struct;
68 
69   temp_struct.thread_exists = false;
70   // Max permissible length of task name including null byte.
71   static constexpr size_t buffer_size = configMAX_TASK_NAME_LEN;
72 
73   std::string_view string(string::ClampedCString(options.name(), buffer_size));
74   temp_struct.name = as_bytes(span(string));
75 
76   // Callback that confirms forked thread is checked by the iterator.
77   auto cb = [&temp_struct](const ThreadInfo& thread_info) {
78     // Compare sizes accounting for null byte.
79     if (thread_info.thread_name().has_value()) {
80       for (size_t i = 0; i < thread_info.thread_name().value().size(); i++) {
81         // Compare character by character of span.
82         if ((unsigned char)thread_info.thread_name().value().data()[i] !=
83             (unsigned char)temp_struct.name.data()[i]) {
84           return true;
85         }
86       }
87       temp_struct.thread_exists = true;
88     }
89     // Signal to stop iteration.
90     return false;
91   };
92 
93   ASSERT_EQ(OkStatus(), thread::ForEachThread(cb));
94 
95   // Signal to forked thread that execution is complete.
96   notify.end.release();
97 
98   // Clean up the test thread context.
99 #if PW_THREAD_JOINING_ENABLED
100   t.join();
101 #else
102   t.detach();
103   thread::test::WaitUntilDetachedThreadsCleanedUp();
104 #endif  // PW_THREAD_JOINING_ENABLED
105 
106   EXPECT_TRUE(temp_struct.thread_exists);
107 }
108 
109 #if INCLUDE_uxTaskGetStackHighWaterMark
110 #if configRECORD_STACK_HIGH_ADDRESS
111 
TEST(ThreadIteration,StackInfoCollector_PeakStackUsage)112 TEST(ThreadIteration, StackInfoCollector_PeakStackUsage) {
113   // This is the value FreeRTOS expects, but it's worth noting that there's no
114   // easy way to get this value directly from FreeRTOS.
115   constexpr uint8_t tskSTACK_FILL_BYTE = 0xa5U;
116   std::array<StackType_t, 128> stack;
117   ByteSpan stack_bytes(as_writable_bytes(span(stack)));
118   std::memset(stack_bytes.data(), tskSTACK_FILL_BYTE, stack_bytes.size_bytes());
119 
120   tskTCB fake_tcb;
121   StringBuilder sb(fake_tcb.pcTaskName);
122   sb.append("FakeTCB");
123   fake_tcb.pxStack = stack.data();
124   fake_tcb.pxEndOfStack = stack.data() + stack.size();
125 
126   // Clobber bytes as if they were used.
127   constexpr size_t kBytesRemaining = 96;
128 #if portSTACK_GROWTH > 0
129   std::memset(stack_bytes.data(),
130               tskSTACK_FILL_BYTE ^ 0x2b,
131               stack_bytes.size() - kBytesRemaining);
132 #else
133   std::memset(&stack_bytes[kBytesRemaining],
134               tskSTACK_FILL_BYTE ^ 0x2b,
135               stack_bytes.size() - kBytesRemaining);
136 #endif  // portSTACK_GROWTH > 0
137 
138   ThreadCallback cb = [kBytesRemaining](const ThreadInfo& info) -> bool {
139     EXPECT_TRUE(info.stack_high_addr().has_value());
140     EXPECT_TRUE(info.stack_low_addr().has_value());
141     EXPECT_TRUE(info.stack_peak_addr().has_value());
142 
143 #if portSTACK_GROWTH > 0
144     EXPECT_EQ(info.stack_high_addr().value() - info.stack_peak_addr().value(),
145               kBytesRemaining);
146 #else
147     EXPECT_EQ(info.stack_peak_addr().value() - info.stack_low_addr().value(),
148               kBytesRemaining);
149 #endif  // portSTACK_GROWTH > 0
150     return true;
151   };
152 
153   EXPECT_TRUE(
154       StackInfoCollector(reinterpret_cast<TaskHandle_t>(&fake_tcb), cb));
155 }
156 
157 #endif  // INCLUDE_uxTaskGetStackHighWaterMark
158 #endif  // configRECORD_STACK_HIGH_ADDRESS
159 
160 }  // namespace
161 }  // namespace pw::thread::freertos
162