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/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