• 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_snapshot_service.h"
16 
17 #include "pw_protobuf/decoder.h"
18 #include "pw_rpc/raw/server_reader_writer.h"
19 #include "pw_span/span.h"
20 #include "pw_thread/thread_info.h"
21 #include "pw_thread/thread_iteration.h"
22 #include "pw_thread_private/thread_snapshot_service.h"
23 #include "pw_thread_protos/thread.pwpb.h"
24 #include "pw_thread_protos/thread_snapshot_service.pwpb.h"
25 #include "pw_unit_test/framework.h"
26 
27 namespace pw::thread::proto {
28 namespace {
29 
30 // Iterates through each proto encoded thread in the buffer.
EncodedThreadExists(ConstByteSpan serialized_thread_buffer,ConstByteSpan thread_name)31 bool EncodedThreadExists(ConstByteSpan serialized_thread_buffer,
32                          ConstByteSpan thread_name) {
33   protobuf::Decoder decoder(serialized_thread_buffer);
34   while (decoder.Next().ok()) {
35     switch (decoder.FieldNumber()) {
36       case static_cast<uint32_t>(
37           proto::pwpb::SnapshotThreadInfo::Fields::kThreads): {
38         ConstByteSpan thread_buffer;
39         EXPECT_EQ(OkStatus(), decoder.ReadBytes(&thread_buffer));
40         ConstByteSpan encoded_name;
41         EXPECT_EQ(OkStatus(), DecodeThreadName(thread_buffer, encoded_name));
42         if (encoded_name.size() == thread_name.size()) {
43           if (std::equal(thread_name.begin(),
44                          thread_name.end(),
45                          encoded_name.begin())) {
46             return true;
47           }
48         }
49       }
50     }
51   }
52   return false;
53 }
54 
CreateThreadInfoObject(std::optional<ConstByteSpan> name,std::optional<uintptr_t> low_addr,std::optional<uintptr_t> high_addr,std::optional<uintptr_t> peak_addr)55 ThreadInfo CreateThreadInfoObject(std::optional<ConstByteSpan> name,
56                                   std::optional<uintptr_t> low_addr,
57                                   std::optional<uintptr_t> high_addr,
58                                   std::optional<uintptr_t> peak_addr) {
59   ThreadInfo thread_info;
60 
61   if (name.has_value()) {
62     thread_info.set_thread_name(name.value());
63   }
64   if (low_addr.has_value()) {
65     thread_info.set_stack_low_addr(low_addr.value());
66   }
67   if (high_addr.has_value()) {
68     thread_info.set_stack_high_addr(high_addr.value());
69   }
70   if (peak_addr.has_value()) {
71     thread_info.set_stack_peak_addr(peak_addr.value());
72   }
73 
74   return thread_info;
75 }
76 
77 // Test creates a custom thread info object and proto encodes. Checks that the
78 // custom object is encoded properly.
TEST(ThreadSnapshotService,DecodeSingleThreadInfoObject)79 TEST(ThreadSnapshotService, DecodeSingleThreadInfoObject) {
80   std::array<std::byte, RequiredServiceBufferSizeWithoutVariableFields(1)>
81       encode_buffer;
82 
83   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
84 
85   ThreadInfo thread_info = CreateThreadInfoObject(
86       as_bytes(span("MyThread")), /* thread name */
87       static_cast<uintptr_t>(12345678u) /* stack low address */,
88       static_cast<uintptr_t>(0u) /* stack high address */,
89       static_cast<uintptr_t>(987654321u) /* stack peak address */);
90 
91   EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
92 
93   ConstByteSpan response_span(encoder);
94   EXPECT_TRUE(
95       EncodedThreadExists(response_span, thread_info.thread_name().value()));
96 }
97 
TEST(ThreadSnapshotService,DecodeMultipleThreadInfoObjects)98 TEST(ThreadSnapshotService, DecodeMultipleThreadInfoObjects) {
99   std::array<std::byte, RequiredServiceBufferSizeWithoutVariableFields(3)>
100       encode_buffer;
101 
102   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
103 
104   ThreadInfo thread_info_1 =
105       CreateThreadInfoObject(as_bytes(span("MyThread1")),
106                              static_cast<uintptr_t>(123u),
107                              static_cast<uintptr_t>(1023u),
108                              static_cast<uintptr_t>(321u));
109 
110   ThreadInfo thread_info_2 =
111       CreateThreadInfoObject(as_bytes(span("MyThread2")),
112                              static_cast<uintptr_t>(1000u),
113                              static_cast<uintptr_t>(999999u),
114                              static_cast<uintptr_t>(0u));
115 
116   ThreadInfo thread_info_3 =
117       CreateThreadInfoObject(as_bytes(span("MyThread3")),
118                              static_cast<uintptr_t>(123u),
119                              static_cast<uintptr_t>(1023u),
120                              static_cast<uintptr_t>(321u));
121 
122   // Encode out of order.
123   EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_3));
124   EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_1));
125   EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_2));
126 
127   ConstByteSpan response_span(encoder);
128   EXPECT_TRUE(
129       EncodedThreadExists(response_span, thread_info_1.thread_name().value()));
130   EXPECT_TRUE(
131       EncodedThreadExists(response_span, thread_info_2.thread_name().value()));
132   EXPECT_TRUE(
133       EncodedThreadExists(response_span, thread_info_3.thread_name().value()));
134 }
135 
TEST(ThreadSnapshotService,DefaultBufferSize)136 TEST(ThreadSnapshotService, DefaultBufferSize) {
137   static std::array<std::byte, RequiredServiceBufferSizeWithoutVariableFields()>
138       encode_buffer;
139 
140   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
141 
142   std::optional<uintptr_t> example_addr = std::numeric_limits<uintptr_t>::max();
143 
144   ThreadInfo thread_info = CreateThreadInfoObject(
145       as_bytes(span("MyThread")), example_addr, example_addr, example_addr);
146 
147   for (int i = 0; i < PW_THREAD_MAXIMUM_THREADS; i++) {
148     EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
149   }
150 
151   ConstByteSpan response_span(encoder);
152   EXPECT_TRUE(
153       EncodedThreadExists(response_span, thread_info.thread_name().value()));
154 }
155 
TEST(ThreadSnapshotService,FailedPrecondition)156 TEST(ThreadSnapshotService, FailedPrecondition) {
157   static std::array<std::byte,
158                     RequiredServiceBufferSizeWithoutVariableFields(1)>
159       encode_buffer;
160 
161   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
162 
163   ThreadInfo thread_info_no_name =
164       CreateThreadInfoObject(std::nullopt,
165                              static_cast<uintptr_t>(1111111111u),
166                              static_cast<uintptr_t>(2222222222u),
167                              static_cast<uintptr_t>(3333333333u));
168   Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_name);
169   EXPECT_EQ(status, Status::FailedPrecondition());
170   // Expected log: "Thread missing information needed by service."
171   ErrorLog(status);
172 
173   // Same error log as above.
174   ThreadInfo thread_info_no_high_addr =
175       CreateThreadInfoObject(as_bytes(span("MyThread")),
176                              static_cast<uintptr_t>(1111111111u),
177                              std::nullopt,
178                              static_cast<uintptr_t>(3333333333u));
179   EXPECT_EQ(ProtoEncodeThreadInfo(encoder, thread_info_no_high_addr),
180             Status::FailedPrecondition());
181 }
182 
TEST(ThreadSnapshotService,Unimplemented)183 TEST(ThreadSnapshotService, Unimplemented) {
184   static std::array<std::byte,
185                     RequiredServiceBufferSizeWithoutVariableFields(1)>
186       encode_buffer;
187 
188   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
189 
190   ThreadInfo thread_info_no_peak_addr =
191       CreateThreadInfoObject(as_bytes(span("MyThread")),
192                              static_cast<uintptr_t>(0u),
193                              static_cast<uintptr_t>(0u),
194                              std::nullopt);
195 
196   Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_peak_addr);
197   EXPECT_EQ(status, Status::Unimplemented());
198   // Expected log: "Peak stack usage reporting not supported by your current OS
199   // or configuration."
200   ErrorLog(status);
201 }
202 
203 }  // namespace
204 }  // namespace pw::thread::proto
205