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