// Copyright 2022 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_thread/thread_iteration.h" #include #include #include "FreeRTOS.h" #include "pw_bytes/span.h" #include "pw_span/span.h" #include "pw_string/string_builder.h" #include "pw_string/util.h" #include "pw_sync/thread_notification.h" #include "pw_thread/non_portable_test_thread_options.h" #include "pw_thread/thread.h" #include "pw_thread/thread_info.h" #include "pw_thread_freertos/freertos_tsktcb.h" #include "pw_thread_freertos_private/thread_iteration.h" #include "pw_unit_test/framework.h" namespace pw::thread::freertos { namespace { // Tests thread iteration API by: // - Forking a test thread. // - Using iteration API to iterate over all running threads. // - Compares name of forked thread and current thread. // - Confirms thread exists and is iterated over. TEST(ThreadIteration, ForkOneThread) { struct { sync::ThreadNotification start; sync::ThreadNotification end; } notify; const auto& options = *static_cast( &thread::test::TestOptionsThread0()); Thread t(options, [¬ify]() { // Release start lock to allow test thread to continue execution. notify.start.release(); while (true) { // Return only when end lock released by test thread. if (notify.end.try_acquire()) { return; } } }); // Blocked until thread t releases start lock. notify.start.acquire(); struct { bool thread_exists; span name; } temp_struct; temp_struct.thread_exists = false; // Max permissible length of task name including null byte. static constexpr size_t buffer_size = configMAX_TASK_NAME_LEN; std::string_view string(string::ClampedCString(options.name(), buffer_size)); temp_struct.name = as_bytes(span(string)); // Callback that confirms forked thread is checked by the iterator. auto cb = [&temp_struct](const ThreadInfo& thread_info) { // Compare sizes accounting for null byte. if (thread_info.thread_name().has_value()) { for (size_t i = 0; i < thread_info.thread_name().value().size(); i++) { // Compare character by character of span. if ((unsigned char)thread_info.thread_name().value().data()[i] != (unsigned char)temp_struct.name.data()[i]) { return true; } } temp_struct.thread_exists = true; } // Signal to stop iteration. return false; }; ASSERT_EQ(OkStatus(), thread::ForEachThread(cb)); // Signal to forked thread that execution is complete. notify.end.release(); // Clean up the test thread context. #if PW_THREAD_JOINING_ENABLED t.join(); #else t.detach(); thread::test::WaitUntilDetachedThreadsCleanedUp(); #endif // PW_THREAD_JOINING_ENABLED EXPECT_TRUE(temp_struct.thread_exists); } #if INCLUDE_uxTaskGetStackHighWaterMark #if configRECORD_STACK_HIGH_ADDRESS TEST(ThreadIteration, StackInfoCollector_PeakStackUsage) { // This is the value FreeRTOS expects, but it's worth noting that there's no // easy way to get this value directly from FreeRTOS. constexpr uint8_t tskSTACK_FILL_BYTE = 0xa5U; std::array stack; ByteSpan stack_bytes(as_writable_bytes(span(stack))); std::memset(stack_bytes.data(), tskSTACK_FILL_BYTE, stack_bytes.size_bytes()); tskTCB fake_tcb; StringBuilder sb(fake_tcb.pcTaskName); sb.append("FakeTCB"); fake_tcb.pxStack = stack.data(); fake_tcb.pxEndOfStack = stack.data() + stack.size(); // Clobber bytes as if they were used. constexpr size_t kBytesRemaining = 96; #if portSTACK_GROWTH > 0 std::memset(stack_bytes.data(), tskSTACK_FILL_BYTE ^ 0x2b, stack_bytes.size() - kBytesRemaining); #else std::memset(&stack_bytes[kBytesRemaining], tskSTACK_FILL_BYTE ^ 0x2b, stack_bytes.size() - kBytesRemaining); #endif // portSTACK_GROWTH > 0 ThreadCallback cb = [kBytesRemaining](const ThreadInfo& info) -> bool { EXPECT_TRUE(info.stack_high_addr().has_value()); EXPECT_TRUE(info.stack_low_addr().has_value()); EXPECT_TRUE(info.stack_peak_addr().has_value()); #if portSTACK_GROWTH > 0 EXPECT_EQ(info.stack_high_addr().value() - info.stack_peak_addr().value(), kBytesRemaining); #else EXPECT_EQ(info.stack_peak_addr().value() - info.stack_low_addr().value(), kBytesRemaining); #endif // portSTACK_GROWTH > 0 return true; }; EXPECT_TRUE( StackInfoCollector(reinterpret_cast(&fake_tcb), cb)); } #endif // INCLUDE_uxTaskGetStackHighWaterMark #endif // configRECORD_STACK_HIGH_ADDRESS } // namespace } // namespace pw::thread::freertos