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