xref: /aosp_15_r20/external/pigweed/pw_file/flat_file_system_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2021 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_file/flat_file_system.h"
16 
17 #include <array>
18 #include <cstddef>
19 #include <cstdint>
20 #include <string_view>
21 
22 #include "pw_bytes/span.h"
23 #include "pw_file/file.pwpb.h"
24 #include "pw_protobuf/decoder.h"
25 #include "pw_rpc/raw/test_method_context.h"
26 #include "pw_span/span.h"
27 #include "pw_status/status.h"
28 #include "pw_status/status_with_size.h"
29 #include "pw_unit_test/framework.h"
30 
31 namespace pw::file {
32 namespace {
33 
34 class FakeFile : public FlatFileSystemService::Entry {
35  public:
FakeFile(std::string_view file_name,size_t size,uint32_t file_id)36   constexpr FakeFile(std::string_view file_name, size_t size, uint32_t file_id)
37       : name_(file_name), size_(size), file_id_(file_id) {}
38 
Name(span<char> dest)39   StatusWithSize Name(span<char> dest) override {
40     if (name_.empty()) {
41       return StatusWithSize(Status::NotFound(), 0);
42     }
43 
44     size_t bytes_to_copy = std::min(dest.size_bytes(), name_.size());
45     memcpy(dest.data(), name_.data(), bytes_to_copy);
46     if (bytes_to_copy != name_.size()) {
47       return StatusWithSize(Status::ResourceExhausted(), bytes_to_copy);
48     }
49 
50     return StatusWithSize(OkStatus(), bytes_to_copy);
51   }
52 
SizeBytes()53   size_t SizeBytes() override { return size_; }
54 
Permissions() const55   FlatFileSystemService::Entry::FilePermissions Permissions() const override {
56     return FlatFileSystemService::Entry::FilePermissions::NONE;
57   }
58 
Delete()59   Status Delete() override { return Status::Unimplemented(); }
60 
FileId() const61   FlatFileSystemService::Entry::Id FileId() const override { return file_id_; }
62 
63  private:
64   std::string_view name_;
65   size_t size_;
66   uint32_t file_id_;
67 };
68 
EntryHasName(FlatFileSystemService::Entry * entry)69 bool EntryHasName(FlatFileSystemService::Entry* entry) {
70   std::array<char, 4> expected_name;
71   StatusWithSize file_name_sws = entry->Name(expected_name);
72   return file_name_sws.size() != 0;
73 }
74 
75 // Compares a serialized Path message to a flat file system entry.
ComparePathToEntry(ConstByteSpan serialized_path,FlatFileSystemService::Entry * entry)76 void ComparePathToEntry(ConstByteSpan serialized_path,
77                         FlatFileSystemService::Entry* entry) {
78   std::array<char, 64> expected_name;
79   StatusWithSize file_name_sws = entry->Name(expected_name);
80 
81   // A partial name read shouldn't happen.
82   ASSERT_EQ(OkStatus(), file_name_sws.status());
83 
84   protobuf::Decoder decoder(serialized_path);
85   while (decoder.Next().ok()) {
86     switch (decoder.FieldNumber()) {
87       case static_cast<uint32_t>(pw::file::pwpb::Path::Fields::kPath): {
88         std::string_view serialized_name;
89         EXPECT_EQ(OkStatus(), decoder.ReadString(&serialized_name));
90         size_t name_bytes_to_read =
91             std::min(serialized_name.size(), file_name_sws.size());
92         EXPECT_EQ(0,
93                   memcmp(expected_name.data(),
94                          serialized_name.data(),
95                          name_bytes_to_read));
96         break;
97       }
98 
99       case static_cast<uint32_t>(pw::file::pwpb::Path::Fields::kPermissions): {
100         uint32_t seralized_permissions;
101         EXPECT_EQ(OkStatus(), decoder.ReadUint32(&seralized_permissions));
102         EXPECT_EQ(static_cast<uint32_t>(entry->Permissions()),
103                   seralized_permissions);
104         break;
105       }
106 
107       case static_cast<uint32_t>(pw::file::pwpb::Path::Fields::kSizeBytes): {
108         uint32_t serialized_file_size;
109         EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_size));
110         EXPECT_EQ(static_cast<uint32_t>(entry->SizeBytes()),
111                   serialized_file_size);
112         break;
113       }
114 
115       case static_cast<uint32_t>(pw::file::pwpb::Path::Fields::kFileId): {
116         uint32_t serialized_file_id;
117         EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_id));
118         EXPECT_EQ(static_cast<uint32_t>(entry->FileId()), serialized_file_id);
119         break;
120       }
121 
122       default:
123         // unexpected result.
124         // TODO(amontanez) something here.
125         break;
126     }
127   }
128 }
129 
ValidateExpectedPaths(span<FlatFileSystemService::Entry * > flat_file_system,const rpc::PayloadsView & results)130 size_t ValidateExpectedPaths(
131     span<FlatFileSystemService::Entry*> flat_file_system,
132     const rpc::PayloadsView& results) {
133   size_t serialized_path_entry_count = 0;
134   size_t file_system_index = 0;
135   for (ConstByteSpan response : results) {
136     protobuf::Decoder decoder(response);
137     while (decoder.Next().ok()) {
138       constexpr uint32_t kListResponsePathsFieldNumber =
139           static_cast<uint32_t>(pw::file::pwpb::ListResponse::Fields::kPaths);
140       EXPECT_EQ(decoder.FieldNumber(), kListResponsePathsFieldNumber);
141       if (decoder.FieldNumber() != kListResponsePathsFieldNumber) {
142         return 0;
143       }
144 
145       serialized_path_entry_count++;
146 
147       // Skip any file system entries without names.
148       while (!EntryHasName(flat_file_system[file_system_index])) {
149         file_system_index++;
150         EXPECT_GT(flat_file_system.size(), file_system_index);
151       }
152 
153       // There's a 1:1 mapping in the same order for all files that have a name.
154       ConstByteSpan serialized_path;
155       EXPECT_EQ(OkStatus(), decoder.ReadBytes(&serialized_path));
156       ComparePathToEntry(serialized_path,
157                          flat_file_system[file_system_index++]);
158     }
159   }
160   return serialized_path_entry_count;
161 }
162 
TEST(FlatFileSystem,EncodingBufferSizeBytes)163 TEST(FlatFileSystem, EncodingBufferSizeBytes) {
164   EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(10),
165             2u /* path nested message key and size */ + 12 /* path */ +
166                 2 /* permissions */ + 6 /* size_bytes */ + 6 /* file_id */);
167   EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(10, 2),
168             2 * (1u + 1 + 12 + 2 + 6 + 6));
169   EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(100, 3),
170             3 * (1u + 1 + 102 + 2 + 6 + 6));
171 }
172 
TEST(FlatFileSystem,List_NoFiles)173 TEST(FlatFileSystem, List_NoFiles) {
174   PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<1>, List)
175   ctx{span<FlatFileSystemService::Entry*>()};
176   ctx.call(ConstByteSpan());
177 
178   EXPECT_TRUE(ctx.done());
179   EXPECT_EQ(OkStatus(), ctx.status());
180   EXPECT_EQ(0u, ctx.responses().size());
181 }
182 
TEST(FlatFileSystem,List_OneFile)183 TEST(FlatFileSystem, List_OneFile) {
184   FakeFile file{"compressed.zip.gz", 2, 1231};
185   std::array<FlatFileSystemService::Entry*, 1> static_file_system{&file};
186 
187   PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<20>, List)
188   ctx(static_file_system);
189   ctx.call(ConstByteSpan());
190 
191   EXPECT_EQ(1u, ValidateExpectedPaths(static_file_system, ctx.responses()));
192 }
193 
TEST(FlatFileSystem,List_ThreeFiles)194 TEST(FlatFileSystem, List_ThreeFiles) {
195   std::array<FakeFile, 3> files{
196       {{"SNAP_001", 372, 9}, {"tokens.csv", 808, 15038202}, {"a.txt", 0, 2}}};
197   std::array<FlatFileSystemService::Entry*, 3> static_file_system{
198       &files[0], &files[1], &files[2]};
199 
200   PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
201   ctx(static_file_system);
202   ctx.call(ConstByteSpan());
203 
204   EXPECT_EQ(3u, ValidateExpectedPaths(static_file_system, ctx.responses()));
205 }
206 
TEST(FlatFileSystem,List_UnnamedFile)207 TEST(FlatFileSystem, List_UnnamedFile) {
208   FakeFile file{"", 0, 0};
209   std::array<FlatFileSystemService::Entry*, 1> static_file_system{&file};
210 
211   PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
212   ctx(static_file_system);
213   ctx.call(ConstByteSpan());
214 
215   EXPECT_EQ(0u, ValidateExpectedPaths(static_file_system, ctx.responses()));
216 }
217 
TEST(FlatFileSystem,List_FileMissingName)218 TEST(FlatFileSystem, List_FileMissingName) {
219   std::array<FakeFile, 3> files{
220       {{"SNAP_001", 372, 9}, {"", 808, 15038202}, {"a.txt", 0, 2}}};
221   std::array<FlatFileSystemService::Entry*, 3> static_file_system{
222       &files[0], &files[1], &files[2]};
223 
224   PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
225   ctx(static_file_system);
226   ctx.call(ConstByteSpan());
227 
228   EXPECT_EQ(2u, ValidateExpectedPaths(static_file_system, ctx.responses()));
229 }
230 
231 }  // namespace
232 }  // namespace pw::file
233