pw_kvs: Add flash partition test
Add unit tests for testing flash partition. Put the main test
implementation in to a common file that is used by end tests that
provide the actual partition to test.
Add configuration define PW_FLASH_MAX_FLASH_ALIGNMENT that is
used to size flash write buffers.
Change-Id: Ib3dd2381037d15bd61552184f59769074dece44f
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/13220
Commit-Queue: David Rogers <davidrogers@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_kvs/BUILD b/pw_kvs/BUILD
index a1fd8b8..dcf7f66 100644
--- a/pw_kvs/BUILD
+++ b/pw_kvs/BUILD
@@ -38,6 +38,7 @@
"public/pw_kvs/internal/key_descriptor.h",
"public/pw_kvs/internal/sectors.h",
"public/pw_kvs/internal/span_traits.h",
+ "pw_kvs_private/config.h",
"pw_kvs_private/macros.h",
"sectors.cc",
],
@@ -93,6 +94,21 @@
)
pw_cc_library(
+ name = "flash_partition_test",
+ srcs = [
+ "flash_partition_test.cc",
+ ],
+ hdrs = [
+ "public/pw_kvs/flash_partition_test.h",
+ ],
+ deps = [
+ ":pw_kvs",
+ "//pw_log",
+ "//pw_span",
+ ],
+)
+
+pw_cc_library(
name = "test_utils",
hdrs = [
"pw_kvs_private/byte_utils.h",
@@ -170,6 +186,17 @@
)
pw_cc_test(
+ name = "fake_flash_partition_test",
+ srcs = ["fake_flash_partition_test.cc"],
+ deps = [
+ ":pw_kvs",
+ ":flash_partition_test",
+ "//pw_log:backend",
+ "//pw_unit_test",
+ ],
+)
+
+pw_cc_test(
name = "key_value_store_test",
srcs = ["key_value_store_test.cc"],
deps = [
diff --git a/pw_kvs/BUILD.gn b/pw_kvs/BUILD.gn
index 0b0c9f4..bb380e3 100644
--- a/pw_kvs/BUILD.gn
+++ b/pw_kvs/BUILD.gn
@@ -46,6 +46,7 @@
"public/pw_kvs/internal/key_descriptor.h",
"public/pw_kvs/internal/sectors.h",
"public/pw_kvs/internal/span_traits.h",
+ "pw_kvs_private/config.h",
"pw_kvs_private/macros.h",
"sectors.cc",
]
@@ -83,6 +84,17 @@
deps = [ dir_pw_log ]
}
+pw_source_set("flash_partition_test") {
+ public = [ "public/pw_kvs/flash_partition_test.h" ]
+ sources = [ "flash_partition_test.cc" ]
+ deps = [
+ dir_pw_kvs,
+ dir_pw_log,
+ dir_pw_span,
+ dir_pw_unit_test,
+ ]
+}
+
pw_source_set("test_utils") {
public_configs = [ ":default_config" ]
public = [ "pw_kvs_private/byte_utils.h" ]
@@ -107,6 +119,7 @@
":checksum_test",
":entry_test",
":entry_cache_test",
+ ":fake_flash_partition_test",
":key_value_store_test",
":key_value_store_binary_format_test",
":key_value_store_fuzz_test",
@@ -149,6 +162,15 @@
sources = [ "entry_cache_test.cc" ]
}
+pw_test("fake_flash_partition_test") {
+ deps = [
+ ":fake_flash",
+ ":flash_partition_test",
+ dir_pw_log,
+ ]
+ sources = [ "fake_flash_partition_test.cc" ]
+}
+
pw_test("key_value_store_test") {
deps = [
":crc16",
diff --git a/pw_kvs/entry.cc b/pw_kvs/entry.cc
index 3343548..cbc07d7 100644
--- a/pw_kvs/entry.cc
+++ b/pw_kvs/entry.cc
@@ -19,11 +19,19 @@
#include <cinttypes>
#include <cstring>
+#include "pw_kvs_private/config.h"
#include "pw_kvs_private/macros.h"
#include "pw_log/log.h"
namespace pw::kvs::internal {
+static_assert(
+ kMaxFlashAlignment >= Entry::kMinAlignmentBytes,
+ "Flash alignment is required to be at least Entry::kMinAlignmentBytes");
+
+constexpr size_t kWriteBufferSize =
+ std::max(kMaxFlashAlignment, 4 * Entry::kMinAlignmentBytes);
+
using std::byte;
using std::string_view;
@@ -93,11 +101,11 @@
StatusWithSize Entry::Write(string_view key,
std::span<const byte> value) const {
FlashPartition::Output flash(partition(), address_);
- return AlignedWrite<64>(flash,
- alignment_bytes(),
- {std::as_bytes(std::span(&header_, 1)),
- std::as_bytes(std::span(key)),
- value});
+ return AlignedWrite<kWriteBufferSize>(flash,
+ alignment_bytes(),
+ {std::as_bytes(std::span(&header_, 1)),
+ std::as_bytes(std::span(key)),
+ value});
}
Status Entry::Update(const EntryFormat& new_format,
@@ -122,7 +130,7 @@
transaction_id());
FlashPartition::Output output(partition(), new_address);
- AlignedWriterBuffer<4 * kMinAlignmentBytes> writer(alignment_bytes(), output);
+ AlignedWriterBuffer<kWriteBufferSize> writer(alignment_bytes(), output);
// Use this object's header rather than the header in flash of flash, since
// this Entry may have been updated.
diff --git a/pw_kvs/fake_flash_partition_test.cc b/pw_kvs/fake_flash_partition_test.cc
new file mode 100644
index 0000000..8b28011
--- /dev/null
+++ b/pw_kvs/fake_flash_partition_test.cc
@@ -0,0 +1,61 @@
+// Copyright 2020 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 "gtest/gtest.h"
+#include "pw_kvs/fake_flash_memory.h"
+#include "pw_kvs/flash_memory.h"
+#include "pw_kvs/flash_partition_test.h"
+#include "pw_log/log.h"
+
+namespace pw::kvs::PartitionTest {
+namespace {
+
+TEST(FakeFlashPartitionTest, FillTest16) {
+ FakeFlashMemoryBuffer<512, 8> flash(16);
+ FlashPartition test_partition(&flash);
+
+ // WriteTest(test_partition);
+}
+
+TEST(FakeFlashPartitionTest, FillTest64) {
+ FakeFlashMemoryBuffer<512, 8> flash(64);
+ FlashPartition test_partition(&flash);
+
+ // WriteTest(test_partition);
+}
+
+TEST(FakeFlashPartitionTest, FillTest256) {
+ FakeFlashMemoryBuffer<512, 8> flash(256);
+ FlashPartition test_partition(&flash);
+
+ // WriteTest(test_partition);
+}
+
+TEST(FakeFlashPartitionTest, EraseTest) {
+ FakeFlashMemoryBuffer<512, 8> flash(16);
+ FlashPartition test_partition(&flash);
+
+ // EraseTest(test_partition);
+}
+
+TEST(FakeFlashPartitionTest, ReadOnlyTest) {
+ FakeFlashMemoryBuffer<512, 8> flash(16);
+ FlashPartition test_partition(
+ &flash, 0, flash.sector_count(), 0, PartitionPermission::kReadOnly);
+
+ // ReadOnlyTest(test_partition);
+}
+
+} // namespace
+} // namespace pw::kvs::PartitionTest
diff --git a/pw_kvs/flash_memory.cc b/pw_kvs/flash_memory.cc
index 30af38f..0e8af99 100644
--- a/pw_kvs/flash_memory.cc
+++ b/pw_kvs/flash_memory.cc
@@ -20,6 +20,7 @@
#include <cinttypes>
#include <cstring>
+#include "pw_kvs_private/config.h"
#include "pw_kvs_private/macros.h"
#include "pw_log/log.h"
#include "pw_status/status_with_size.h"
@@ -66,22 +67,25 @@
Status FlashPartition::IsRegionErased(Address source_flash_address,
size_t length,
bool* is_erased) {
- // Max alignment is artificial to keep the stack usage low for this
- // function. Using 16 because it's the alignment of encrypted flash.
- constexpr size_t kMaxAlignment = 16;
-
// Relying on Read() to check address and len arguments.
if (is_erased == nullptr) {
return Status::INVALID_ARGUMENT;
}
+
+ // TODO(pwbug/214): Currently using a single flash alignment to do both the
+ // read and write. The allowable flash read length may be less than what write
+ // needs (possibly by a bunch), resulting in buffer and erased_pattern_buffer
+ // being bigger than they need to be.
const size_t alignment = alignment_bytes();
- if (alignment > kMaxAlignment || kMaxAlignment % alignment ||
+ if (alignment > kMaxFlashAlignment || kMaxFlashAlignment % alignment ||
length % alignment) {
return Status::INVALID_ARGUMENT;
}
- byte buffer[kMaxAlignment];
- byte erased_pattern_buffer[kMaxAlignment];
+ byte buffer[kMaxFlashAlignment];
+
+ // TODO(pwrev/215): Stop using erased_pattern_buffer to save stack.
+ byte erased_pattern_buffer[kMaxFlashAlignment];
size_t offset = 0;
std::memset(erased_pattern_buffer,
diff --git a/pw_kvs/flash_partition_test.cc b/pw_kvs/flash_partition_test.cc
new file mode 100644
index 0000000..6b5a9ce
--- /dev/null
+++ b/pw_kvs/flash_partition_test.cc
@@ -0,0 +1,168 @@
+// Copyright 2020 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_kvs/flash_partition_test.h"
+
+#include <span>
+
+#include "gtest/gtest.h"
+#include "pw_kvs/flash_memory.h"
+#include "pw_kvs_private/config.h"
+#include "pw_log/log.h"
+
+namespace pw::kvs::PartitionTest {
+
+constexpr size_t kTestDataSize = kMaxFlashAlignment;
+
+namespace {
+
+void WriteData(FlashPartition& partition, uint8_t fill_byte) {
+ uint8_t test_data[kTestDataSize];
+ memset(test_data, fill_byte, sizeof(test_data));
+
+ ASSERT_GE(kTestDataSize, partition.alignment_bytes());
+
+ partition.Erase(0, partition.sector_count());
+
+ const size_t chunks_per_sector =
+ partition.sector_size_bytes() / partition.alignment_bytes();
+
+ // Fill partition sector by sector. Fill the sector with an integer number
+ // of alignment-size chunks. If the sector is not evenly divisible by
+ // alignment-size, the remainder is not written.
+ for (size_t sector_index = 0; sector_index < partition.sector_count();
+ sector_index++) {
+ FlashPartition::Address address =
+ sector_index * partition.sector_size_bytes();
+
+ for (size_t chunk_index = 0; chunk_index < chunks_per_sector;
+ chunk_index++) {
+ StatusWithSize status = partition.Write(
+ address, as_bytes(std::span(test_data, partition.alignment_bytes())));
+ ASSERT_EQ(Status::OK, status.status());
+ ASSERT_EQ(partition.alignment_bytes(), status.size());
+ address += partition.alignment_bytes();
+ }
+ }
+
+ // Check the fill result. Use expect so the test doesn't bail on error.
+ // Count the errors and print if any errors are found.
+ size_t error_count = 0;
+ for (size_t sector_index = 0; sector_index < partition.sector_count();
+ sector_index++) {
+ FlashPartition::Address address =
+ sector_index * partition.sector_size_bytes();
+
+ for (size_t chunk_index = 0; chunk_index < chunks_per_sector;
+ chunk_index++) {
+ memset(test_data, 0, sizeof(test_data));
+ StatusWithSize status =
+ partition.Read(address, partition.alignment_bytes(), test_data);
+
+ EXPECT_EQ(Status::OK, status.status());
+ EXPECT_EQ(partition.alignment_bytes(), status.size());
+ if (!status.ok() || (partition.alignment_bytes() != status.size())) {
+ error_count++;
+ continue;
+ }
+
+ for (size_t i = 0; i < partition.alignment_bytes(); i++) {
+ if (test_data[i] != fill_byte) {
+ error_count++;
+ }
+ }
+
+ address += partition.alignment_bytes();
+ }
+ }
+
+ EXPECT_EQ(error_count, 0U);
+ if (error_count != 0) {
+ PW_LOG_ERROR("Partition test, fill '%c', %u errors found",
+ fill_byte,
+ unsigned(error_count));
+ }
+}
+
+} // namespace
+
+void WriteTest(FlashPartition& partition, size_t test_iterations) {
+ ASSERT_GE(kTestDataSize, partition.alignment_bytes());
+
+ for (size_t i = 0; i < test_iterations; i++) {
+ WriteData(partition, 0);
+ WriteData(partition, 0xff);
+ WriteData(partition, 0x55);
+ WriteData(partition, 0xa3);
+ }
+}
+
+void EraseTest(FlashPartition& partition) {
+ static const uint8_t fill_byte = 0x55;
+ uint8_t test_data[kTestDataSize];
+ memset(test_data, fill_byte, sizeof(test_data));
+
+ ASSERT_GE(kTestDataSize, partition.alignment_bytes());
+
+ const size_t block_size =
+ std::min(sizeof(test_data), partition.sector_size_bytes());
+ auto data_span = std::span(test_data, block_size);
+
+ ASSERT_EQ(Status::OK, partition.Erase(0, partition.sector_count()));
+
+ // Write to the first page of each sector.
+ for (size_t sector_index = 0; sector_index < partition.sector_count();
+ sector_index++) {
+ FlashPartition::Address address =
+ sector_index * partition.sector_size_bytes();
+
+ StatusWithSize status = partition.Write(address, as_bytes(data_span));
+ ASSERT_EQ(Status::OK, status.status());
+ ASSERT_EQ(block_size, status.size());
+ }
+
+ ASSERT_EQ(Status::OK, partition.Erase());
+
+ bool is_erased;
+ ASSERT_EQ(Status::OK,
+ partition.IsRegionErased(0, partition.size_bytes(), &is_erased));
+ ASSERT_EQ(true, is_erased);
+
+ // Read the first page of each sector and make sure it has been erased.
+ for (size_t sector_index = 0; sector_index < partition.sector_count();
+ sector_index++) {
+ FlashPartition::Address address =
+ sector_index * partition.sector_size_bytes();
+
+ StatusWithSize status =
+ partition.Read(address, data_span.size_bytes(), data_span.data());
+ ASSERT_EQ(Status::OK, status.status());
+ ASSERT_EQ(data_span.size_bytes(), status.size());
+
+ ASSERT_EQ(true, partition.AppearsErased(as_bytes(data_span)));
+ }
+}
+
+void ReadOnlyTest(FlashPartition& partition) {
+ uint8_t test_data[kTestDataSize];
+ auto data_span = std::span(test_data);
+
+ ASSERT_EQ(Status::PERMISSION_DENIED,
+ partition.Erase(0, partition.sector_count()));
+
+ ASSERT_EQ(Status::PERMISSION_DENIED,
+ partition.Write(0, as_bytes(data_span)).status());
+}
+
+} // namespace pw::kvs::PartitionTest
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index 35840ef..3549e22 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -157,7 +157,7 @@
// Although it might be useful to test other configurations, some tests require
// at least 3 sectors; therfore it should have this when checked in.
FakeFlashMemoryBuffer<4 * 1024, 6> test_flash(
- 16); // 4 x 4k sectors, 16 byte alignment
+ 16); // 6 x 4k sectors, 16 byte alignment
FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
FakeFlashMemoryBuffer<1024, 60> large_test_flash(8);
FlashPartition large_test_partition(&large_test_flash,
@@ -1332,9 +1332,7 @@
class LargeEmptyInitializedKvs : public ::testing::Test {
protected:
LargeEmptyInitializedKvs() : kvs_(&large_test_partition, default_format) {
- ASSERT_EQ(
- Status::OK,
- large_test_partition.Erase(0, large_test_partition.sector_count()));
+ ASSERT_EQ(Status::OK, large_test_partition.Erase());
ASSERT_EQ(Status::OK, kvs_.Init());
}
diff --git a/pw_kvs/public/pw_kvs/flash_memory.h b/pw_kvs/public/pw_kvs/flash_memory.h
index ac8f429..46be588 100644
--- a/pw_kvs/public/pw_kvs/flash_memory.h
+++ b/pw_kvs/public/pw_kvs/flash_memory.h
@@ -117,7 +117,7 @@
private:
const uint32_t sector_size_;
const uint32_t flash_sector_count_;
- const uint8_t alignment_;
+ const uint32_t alignment_;
const uint32_t start_address_;
const uint32_t start_sector_;
const std::byte erased_memory_content_;
diff --git a/pw_kvs/public/pw_kvs/flash_partition_test.h b/pw_kvs/public/pw_kvs/flash_partition_test.h
new file mode 100644
index 0000000..8c8496d
--- /dev/null
+++ b/pw_kvs/public/pw_kvs/flash_partition_test.h
@@ -0,0 +1,28 @@
+// Copyright 2020 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.
+#pragma once
+
+#include <cstddef>
+
+#include "pw_kvs/flash_memory.h"
+
+namespace pw::kvs::PartitionTest {
+
+void WriteTest(FlashPartition& partition, size_t test_iterations = 2);
+
+void EraseTest(FlashPartition& partition);
+
+void ReadOnlyTest(FlashPartition& partition);
+
+} // namespace pw::kvs::PartitionTest
diff --git a/pw_kvs/pw_kvs_private/config.h b/pw_kvs/pw_kvs_private/config.h
new file mode 100644
index 0000000..0976bc0
--- /dev/null
+++ b/pw_kvs/pw_kvs_private/config.h
@@ -0,0 +1,29 @@
+// Copyright 2020 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.
+
+// Utilities for building std::byte arrays from strings or integer values.
+#pragma once
+
+#include <cstddef>
+
+#ifndef PW_KVS_MAX_FLASH_ALIGNMENT
+#define PW_KVS_MAX_FLASH_ALIGNMENT 256UL
+#endif
+
+static_assert((PW_KVS_MAX_FLASH_ALIGNMENT >= 16UL),
+ "Max flash alignment is required to be at least 16");
+
+namespace pw::kvs {
+inline constexpr size_t kMaxFlashAlignment = PW_KVS_MAX_FLASH_ALIGNMENT;
+} // namespace pw::kvs