aboutsummaryrefslogtreecommitdiff
path: root/pw_thread_freertos/thread_iteration_test.cc
blob: 9e0e5c14e5d2687e412b5fad6b3332f9ccbe9689 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 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 <cstddef>
#include <string_view>

#include "FreeRTOS.h"
#include "gtest/gtest.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"

namespace pw::thread::freertos {
namespace {

sync::ThreadNotification lock_start;
sync::ThreadNotification lock_end;

void ForkedThreadEntry(void*) {
  // Release start lock to allow test thread to continue execution.
  lock_start.release();
  while (true) {
    // Return only when end lock released by test thread.
    if (lock_end.try_acquire()) {
      return;
    }
  }
}

// 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) {
  const auto& options = *static_cast<const pw::thread::freertos::Options*>(
      &thread::test::TestOptionsThread0());
  thread::Thread t(options, ForkedThreadEntry);

  // Blocked until thread t releases start lock.
  lock_start.acquire();

  struct {
    bool thread_exists;
    span<const std::byte> 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;
  };

  thread::ForEachThread(cb);

  // Signal to forked thread that execution is complete.
  lock_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<StackType_t, 128> 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<TaskHandle_t>(&fake_tcb), cb));
}

#endif  // INCLUDE_uxTaskGetStackHighWaterMark
#endif  // configRECORD_STACK_HIGH_ADDRESS

}  // namespace
}  // namespace pw::thread::freertos