xref: /aosp_15_r20/external/pigweed/pw_thread/thread_snapshot_service_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_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, RequiredServiceBufferSize(1)> encode_buffer;
81 
82   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
83 
84   ThreadInfo thread_info = CreateThreadInfoObject(
85       as_bytes(span("MyThread")), /* thread name */
86       static_cast<uintptr_t>(12345678u) /* stack low address */,
87       static_cast<uintptr_t>(0u) /* stack high address */,
88       static_cast<uintptr_t>(987654321u) /* stack peak address */);
89 
90   EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
91 
92   ConstByteSpan response_span(encoder);
93   EXPECT_TRUE(
94       EncodedThreadExists(response_span, thread_info.thread_name().value()));
95 }
96 
TEST(ThreadSnapshotService,DecodeMultipleThreadInfoObjects)97 TEST(ThreadSnapshotService, DecodeMultipleThreadInfoObjects) {
98   std::array<std::byte, RequiredServiceBufferSize(3)> encode_buffer;
99 
100   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
101 
102   ThreadInfo thread_info_1 =
103       CreateThreadInfoObject(as_bytes(span("MyThread1")),
104                              static_cast<uintptr_t>(123u),
105                              static_cast<uintptr_t>(1023u),
106                              static_cast<uintptr_t>(321u));
107 
108   ThreadInfo thread_info_2 =
109       CreateThreadInfoObject(as_bytes(span("MyThread2")),
110                              static_cast<uintptr_t>(1000u),
111                              static_cast<uintptr_t>(999999u),
112                              static_cast<uintptr_t>(0u));
113 
114   ThreadInfo thread_info_3 =
115       CreateThreadInfoObject(as_bytes(span("MyThread3")),
116                              static_cast<uintptr_t>(123u),
117                              static_cast<uintptr_t>(1023u),
118                              static_cast<uintptr_t>(321u));
119 
120   // Encode out of order.
121   EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_3));
122   EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_1));
123   EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info_2));
124 
125   ConstByteSpan response_span(encoder);
126   EXPECT_TRUE(
127       EncodedThreadExists(response_span, thread_info_1.thread_name().value()));
128   EXPECT_TRUE(
129       EncodedThreadExists(response_span, thread_info_2.thread_name().value()));
130   EXPECT_TRUE(
131       EncodedThreadExists(response_span, thread_info_3.thread_name().value()));
132 }
133 
TEST(ThreadSnapshotService,DefaultBufferSize)134 TEST(ThreadSnapshotService, DefaultBufferSize) {
135   static std::array<std::byte, RequiredServiceBufferSize()> encode_buffer;
136 
137   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
138 
139   std::optional<uintptr_t> example_addr = std::numeric_limits<uintptr_t>::max();
140 
141   ThreadInfo thread_info = CreateThreadInfoObject(
142       as_bytes(span("MyThread")), example_addr, example_addr, example_addr);
143 
144   for (int i = 0; i < PW_THREAD_MAXIMUM_THREADS; i++) {
145     EXPECT_EQ(OkStatus(), ProtoEncodeThreadInfo(encoder, thread_info));
146   }
147 
148   ConstByteSpan response_span(encoder);
149   EXPECT_TRUE(
150       EncodedThreadExists(response_span, thread_info.thread_name().value()));
151 }
152 
TEST(ThreadSnapshotService,FailedPrecondition)153 TEST(ThreadSnapshotService, FailedPrecondition) {
154   static std::array<std::byte, RequiredServiceBufferSize(1)> encode_buffer;
155 
156   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
157 
158   ThreadInfo thread_info_no_name =
159       CreateThreadInfoObject(std::nullopt,
160                              static_cast<uintptr_t>(1111111111u),
161                              static_cast<uintptr_t>(2222222222u),
162                              static_cast<uintptr_t>(3333333333u));
163   Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_name);
164   EXPECT_EQ(status, Status::FailedPrecondition());
165   // Expected log: "Thread missing information needed by service."
166   ErrorLog(status);
167 
168   // Same error log as above.
169   ThreadInfo thread_info_no_high_addr =
170       CreateThreadInfoObject(as_bytes(span("MyThread")),
171                              static_cast<uintptr_t>(1111111111u),
172                              std::nullopt,
173                              static_cast<uintptr_t>(3333333333u));
174   EXPECT_EQ(ProtoEncodeThreadInfo(encoder, thread_info_no_high_addr),
175             Status::FailedPrecondition());
176 }
177 
TEST(ThreadSnapshotService,Unimplemented)178 TEST(ThreadSnapshotService, Unimplemented) {
179   static std::array<std::byte, RequiredServiceBufferSize(1)> encode_buffer;
180 
181   proto::pwpb::SnapshotThreadInfo::MemoryEncoder encoder(encode_buffer);
182 
183   ThreadInfo thread_info_no_peak_addr =
184       CreateThreadInfoObject(as_bytes(span("MyThread")),
185                              static_cast<uintptr_t>(0u),
186                              static_cast<uintptr_t>(0u),
187                              std::nullopt);
188 
189   Status status = ProtoEncodeThreadInfo(encoder, thread_info_no_peak_addr);
190   EXPECT_EQ(status, Status::Unimplemented());
191   // Expected log: "Peak stack usage reporting not supported by your current OS
192   // or configuration."
193   ErrorLog(status);
194 }
195 
196 }  // namespace
197 }  // namespace pw::thread::proto
198