233 lines
7.8 KiB
C++
233 lines
7.8 KiB
C++
// Copyright 2021 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_file/flat_file_system.h"
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <span>
|
|
#include <string_view>
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "pw_bytes/span.h"
|
|
#include "pw_file/file.pwpb.h"
|
|
#include "pw_protobuf/decoder.h"
|
|
#include "pw_rpc/raw/test_method_context.h"
|
|
#include "pw_status/status.h"
|
|
#include "pw_status/status_with_size.h"
|
|
|
|
namespace pw::file {
|
|
namespace {
|
|
|
|
class FakeFile : public FlatFileSystemService::Entry {
|
|
public:
|
|
constexpr FakeFile(std::string_view file_name, size_t size, uint32_t file_id)
|
|
: name_(file_name), size_(size), file_id_(file_id) {}
|
|
|
|
StatusWithSize Name(std::span<char> dest) override {
|
|
if (name_.empty()) {
|
|
return StatusWithSize(Status::NotFound(), 0);
|
|
}
|
|
|
|
size_t bytes_to_copy = std::min(dest.size_bytes(), name_.size());
|
|
memcpy(dest.data(), name_.data(), bytes_to_copy);
|
|
if (bytes_to_copy != name_.size()) {
|
|
return StatusWithSize(Status::ResourceExhausted(), bytes_to_copy);
|
|
}
|
|
|
|
return StatusWithSize(OkStatus(), bytes_to_copy);
|
|
}
|
|
|
|
size_t SizeBytes() override { return size_; }
|
|
|
|
FlatFileSystemService::Entry::FilePermissions Permissions() const override {
|
|
return FlatFileSystemService::Entry::FilePermissions::NONE;
|
|
}
|
|
|
|
Status Delete() override { return Status::Unimplemented(); }
|
|
|
|
FlatFileSystemService::Entry::Id FileId() const override { return file_id_; }
|
|
|
|
private:
|
|
std::string_view name_;
|
|
size_t size_;
|
|
uint32_t file_id_;
|
|
};
|
|
|
|
bool EntryHasName(FlatFileSystemService::Entry* entry) {
|
|
std::array<char, 4> expected_name;
|
|
StatusWithSize file_name_sws = entry->Name(expected_name);
|
|
return file_name_sws.size() != 0;
|
|
}
|
|
|
|
// Compares a serialized Path message to a flat file system entry.
|
|
void ComparePathToEntry(ConstByteSpan serialized_path,
|
|
FlatFileSystemService::Entry* entry) {
|
|
std::array<char, 64> expected_name;
|
|
StatusWithSize file_name_sws = entry->Name(expected_name);
|
|
|
|
// A partial name read shouldn't happen.
|
|
ASSERT_EQ(OkStatus(), file_name_sws.status());
|
|
|
|
protobuf::Decoder decoder(serialized_path);
|
|
while (decoder.Next().ok()) {
|
|
switch (decoder.FieldNumber()) {
|
|
case static_cast<uint32_t>(pw::file::Path::Fields::PATH): {
|
|
std::string_view serialized_name;
|
|
EXPECT_EQ(OkStatus(), decoder.ReadString(&serialized_name));
|
|
size_t name_bytes_to_read =
|
|
std::min(serialized_name.size(), file_name_sws.size());
|
|
EXPECT_EQ(0,
|
|
memcmp(expected_name.data(),
|
|
serialized_name.data(),
|
|
name_bytes_to_read));
|
|
break;
|
|
}
|
|
|
|
case static_cast<uint32_t>(pw::file::Path::Fields::PERMISSIONS): {
|
|
uint32_t seralized_permissions;
|
|
EXPECT_EQ(OkStatus(), decoder.ReadUint32(&seralized_permissions));
|
|
EXPECT_EQ(static_cast<uint32_t>(entry->Permissions()),
|
|
seralized_permissions);
|
|
break;
|
|
}
|
|
|
|
case static_cast<uint32_t>(pw::file::Path::Fields::SIZE_BYTES): {
|
|
uint32_t serialized_file_size;
|
|
EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_size));
|
|
EXPECT_EQ(static_cast<uint32_t>(entry->SizeBytes()),
|
|
serialized_file_size);
|
|
break;
|
|
}
|
|
|
|
case static_cast<uint32_t>(pw::file::Path::Fields::FILE_ID): {
|
|
uint32_t serialized_file_id;
|
|
EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_id));
|
|
EXPECT_EQ(static_cast<uint32_t>(entry->FileId()), serialized_file_id);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// unexpected result.
|
|
// TODO something here.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t ValidateExpectedPaths(
|
|
std::span<FlatFileSystemService::Entry*> flat_file_system,
|
|
const rpc::PayloadsView& results) {
|
|
size_t serialized_path_entry_count = 0;
|
|
size_t file_system_index = 0;
|
|
for (ConstByteSpan response : results) {
|
|
protobuf::Decoder decoder(response);
|
|
while (decoder.Next().ok()) {
|
|
constexpr uint32_t kListResponsePathsFieldNumber =
|
|
static_cast<uint32_t>(pw::file::ListResponse::Fields::PATHS);
|
|
EXPECT_EQ(decoder.FieldNumber(), kListResponsePathsFieldNumber);
|
|
if (decoder.FieldNumber() != kListResponsePathsFieldNumber) {
|
|
return 0;
|
|
}
|
|
|
|
serialized_path_entry_count++;
|
|
|
|
// Skip any file system entries without names.
|
|
while (!EntryHasName(flat_file_system[file_system_index])) {
|
|
file_system_index++;
|
|
EXPECT_GT(flat_file_system.size(), file_system_index);
|
|
}
|
|
|
|
// There's a 1:1 mapping in the same order for all files that have a name.
|
|
ConstByteSpan serialized_path;
|
|
EXPECT_EQ(OkStatus(), decoder.ReadBytes(&serialized_path));
|
|
ComparePathToEntry(serialized_path,
|
|
flat_file_system[file_system_index++]);
|
|
}
|
|
}
|
|
return serialized_path_entry_count;
|
|
}
|
|
|
|
TEST(FlatFileSystem, EncodingBufferSizeBytes) {
|
|
EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(10),
|
|
2u /* path nested message key and size */ + 12 /* path */ +
|
|
2 /* permissions */ + 6 /* size_bytes */ + 6 /* file_id */);
|
|
EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(10, 2),
|
|
2 * (1u + 1 + 12 + 2 + 6 + 6));
|
|
EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(100, 3),
|
|
3 * (1u + 1 + 102 + 2 + 6 + 6));
|
|
}
|
|
|
|
TEST(FlatFileSystem, List_NoFiles) {
|
|
PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<1>, List)
|
|
ctx{std::span<FlatFileSystemService::Entry*>()};
|
|
ctx.call(ConstByteSpan());
|
|
|
|
EXPECT_TRUE(ctx.done());
|
|
EXPECT_EQ(OkStatus(), ctx.status());
|
|
EXPECT_EQ(0u, ctx.responses().size());
|
|
}
|
|
|
|
TEST(FlatFileSystem, List_OneFile) {
|
|
FakeFile file{"compressed.zip.gz", 2, 1231};
|
|
std::array<FlatFileSystemService::Entry*, 1> static_file_system{&file};
|
|
|
|
PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<20>, List)
|
|
ctx(static_file_system);
|
|
ctx.call(ConstByteSpan());
|
|
|
|
EXPECT_EQ(1u, ValidateExpectedPaths(static_file_system, ctx.responses()));
|
|
}
|
|
|
|
TEST(FlatFileSystem, List_ThreeFiles) {
|
|
std::array<FakeFile, 3> files{
|
|
{{"SNAP_001", 372, 9}, {"tokens.csv", 808, 15038202}, {"a.txt", 0, 2}}};
|
|
std::array<FlatFileSystemService::Entry*, 3> static_file_system{
|
|
&files[0], &files[1], &files[2]};
|
|
|
|
PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
|
|
ctx(static_file_system);
|
|
ctx.call(ConstByteSpan());
|
|
|
|
EXPECT_EQ(3u, ValidateExpectedPaths(static_file_system, ctx.responses()));
|
|
}
|
|
|
|
TEST(FlatFileSystem, List_UnnamedFile) {
|
|
FakeFile file{"", 0, 0};
|
|
std::array<FlatFileSystemService::Entry*, 1> static_file_system{&file};
|
|
|
|
PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
|
|
ctx(static_file_system);
|
|
ctx.call(ConstByteSpan());
|
|
|
|
EXPECT_EQ(0u, ValidateExpectedPaths(static_file_system, ctx.responses()));
|
|
}
|
|
|
|
TEST(FlatFileSystem, List_FileMissingName) {
|
|
std::array<FakeFile, 3> files{
|
|
{{"SNAP_001", 372, 9}, {"", 808, 15038202}, {"a.txt", 0, 2}}};
|
|
std::array<FlatFileSystemService::Entry*, 3> static_file_system{
|
|
&files[0], &files[1], &files[2]};
|
|
|
|
PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
|
|
ctx(static_file_system);
|
|
ctx.call(ConstByteSpan());
|
|
|
|
EXPECT_EQ(2u, ValidateExpectedPaths(static_file_system, ctx.responses()));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace pw::file
|