Преглед изворни кода

Merge branch 'feature/linux_target' into 'master'

build system: Add Linux target

Closes IDF-2145

See merge request espressif/esp-idf!10076
Ivan Grokhotkov пре 5 година
родитељ
комит
792dc6ebb8
39 измењених фајлова са 2367 додато и 180 уклоњено
  1. 35 4
      components/nvs_flash/CMakeLists.txt
  2. 429 0
      components/nvs_flash/host_test/fixtures/test_fixtures.hpp
  3. 27 0
      components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt
  4. 23 0
      components/nvs_flash/host_test/nvs_page_test/README.rst
  5. 10 0
      components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt
  6. 934 0
      components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp
  7. 3 0
      components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults
  8. 1 1
      components/nvs_flash/mock/int/crc.cpp
  9. 5 1
      components/nvs_flash/mock/int/crc.h
  10. 2 2
      components/nvs_flash/src/compressed_enum_table.hpp
  11. 10 10
      components/nvs_flash/src/nvs_api.cpp
  12. 9 9
      components/nvs_flash/src/nvs_page.cpp
  13. 3 0
      components/nvs_flash/src/nvs_page.hpp
  14. 2 2
      components/nvs_flash/src/nvs_partition.cpp
  15. 16 19
      components/nvs_flash/src/nvs_platform.hpp
  16. 14 8
      components/nvs_flash/src/nvs_storage.cpp
  17. 10 10
      components/nvs_flash/src/nvs_types.cpp
  18. 4 4
      components/nvs_flash/test_nvs_host/Makefile
  19. 123 55
      components/spi_flash/CMakeLists.txt
  20. 1 37
      components/spi_flash/include/esp_spi_flash.h
  21. 65 0
      components/spi_flash/include/esp_spi_flash_counters.h
  22. 7 0
      components/spi_flash/mock/mock_config.yaml
  23. 149 0
      components/spi_flash/sim/stubs/esp_common/esp_err.h
  24. 148 0
      components/spi_flash/sim/stubs/soc/include/hal/spi_flash_types.h
  25. 95 0
      components/spi_flash/sim/stubs/spi_flash/esp_partition.h
  26. 149 0
      components/spi_flash/sim/stubs/xtensa/esp_attr.h
  27. 20 6
      components/unity/CMakeLists.txt
  28. 5 0
      examples/build_system/cmake/linux_host_app/CMakeLists.txt
  29. 8 0
      examples/build_system/cmake/linux_host_app/README.md
  30. 1 0
      examples/build_system/cmake/linux_host_app/main/CMakeLists.txt
  31. 20 0
      examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp
  32. 2 0
      examples/build_system/cmake/linux_host_app/sdkconfig.defaults
  33. 17 7
      tools/cmake/build.cmake
  34. 4 2
      tools/cmake/component.cmake
  35. 2 0
      tools/cmake/dfu.cmake
  36. 8 0
      tools/cmake/toolchain-linux.cmake
  37. 2 1
      tools/find_build_apps/common.py
  38. 3 1
      tools/gen_esp_err_to_name.py
  39. 1 1
      tools/idf_py_actions/constants.py

+ 35 - 4
components/nvs_flash/CMakeLists.txt

@@ -1,3 +1,5 @@
+idf_build_get_property(target IDF_TARGET)
+
 set(srcs "src/nvs_api.cpp"
          "src/nvs_cxx_api.cpp"
          "src/nvs_item_hash_list.cpp"
@@ -11,10 +13,39 @@ set(srcs "src/nvs_api.cpp"
          "src/nvs_partition_manager.cpp"
          "src/nvs_types.cpp")
 
+set(public_req spi_flash)
+
+set(include_dirs "include")
+
+idf_component_register(SRCS "${srcs}"
+                    REQUIRES "${public_req}"
+                    INCLUDE_DIRS "${include_dirs}")
+
+# If we use the linux target, we need to redirect the crc functions to the linux
+if(${target} STREQUAL "linux")
+    if(CONFIG_NVS_ENCRYPTION)
+        # mbedtls isn't configured for building with linux or as mock target. It will draw in all kind of dependencies
+        message(FATAL_ERROR "NVS currently doesn't support encryption if built for Linux.")
+    endif()
+    idf_component_get_property(spi_flash_dir spi_flash COMPONENT_DIR)
+    target_include_directories(${COMPONENT_LIB} PUBLIC
+            "${CMAKE_CURRENT_SOURCE_DIR}/mock/int"
+            "${spi_flash_dir}/sim/stubs/freertos/include")
+    target_sources(${COMPONENT_LIB} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/mock/int/crc.cpp")
+    target_compile_options(${COMPONENT_LIB} PUBLIC "-DLINUX_TARGET")
+else()
+    # TODO: this is a workaround until IDF-2085 is fixed
+    idf_component_get_property(mbedtls_lib mbedtls COMPONENT_LIB)
+    target_link_libraries(${COMPONENT_LIB} PUBLIC ${mbedtls_lib})
+endif()
+
 if(CONFIG_NVS_ENCRYPTION)
-    list(APPEND srcs "src/nvs_encrypted_partition.cpp")
+    target_sources(${COMPONENT_LIB} PRIVATE "src/nvs_encrypted_partition.cpp")
+    idf_component_get_property(mbedtls_lib mbedtls COMPONENT_LIB)
+    target_link_libraries(${COMPONENT_LIB} PUBLIC ${mbedtls_lib})
 endif()
 
-idf_component_register(SRCS "${srcs}"
-                    REQUIRES spi_flash mbedtls
-                    INCLUDE_DIRS include)
+if(${target} STREQUAL "linux")
+    target_compile_options(${COMPONENT_LIB} PUBLIC --coverage)
+    target_link_libraries(${COMPONENT_LIB} PUBLIC --coverage)
+endif()

+ 429 - 0
components/nvs_flash/host_test/fixtures/test_fixtures.hpp

@@ -0,0 +1,429 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// 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
+
+//     http://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 "nvs_partition.hpp"
+#include "nvs.h"
+#include "nvs_page.hpp"
+#include "nvs_storage.hpp"
+#include <exception>
+#include <string>
+
+#ifdef CONFIG_NVS_ENCRYPTION
+#include "nvs_encrypted_partition.hpp"
+#endif
+
+extern "C" {
+#include "Mockesp_partition.h"
+}
+
+struct FixtureException : std::exception {
+    FixtureException(const std::string& msg) : msg(msg) { }
+
+    const char *what() {
+        return msg.c_str();
+    }
+
+    std::string msg;
+};
+
+class PartitionMock : public nvs::Partition {
+public:
+    PartitionMock(uint32_t address, uint32_t size)
+        : partition(), address(address), size(size)
+    {
+        assert(size);
+    }
+
+    const char *get_partition_name() override
+    {
+        return "";
+    }
+
+    esp_err_t read_raw(size_t src_offset, void* dst, size_t size) override
+    {
+        return esp_partition_read_raw(&partition, src_offset, dst, size);
+    }
+
+    esp_err_t read(size_t src_offset, void* dst, size_t size) override
+    {
+        return esp_partition_read(&partition, src_offset, dst, size);
+    }
+
+    esp_err_t write_raw(size_t dst_offset, const void* src, size_t size) override
+    {
+        return esp_partition_write_raw(&partition, dst_offset, src, size);
+    }
+
+    esp_err_t write(size_t dst_offset, const void* src, size_t size) override
+    {
+        return esp_partition_write(&partition, dst_offset, src, size);
+    }
+
+    esp_err_t erase_range(size_t dst_offset, size_t size) override
+    {
+        return esp_partition_erase_range(&partition, dst_offset, size);
+    }
+
+    uint32_t get_address() override
+    {
+        return address;
+    }
+
+    uint32_t get_size() override
+    {
+        return size;
+    }
+
+    const esp_partition_t partition;
+
+private:
+    uint32_t address;
+
+    uint32_t size;
+};
+
+#ifdef CONFIG_NVS_ENCRYPTION
+struct EncryptedPartitionFixture {
+    EncryptedPartitionFixture(nvs_sec_cfg_t *cfg,
+            uint32_t start_sector = 0,
+            uint32_t sector_size = 1,
+            const char *partition_name = NVS_DEFAULT_PART_NAME)
+        : esp_partition(), emu(start_sector + sector_size),
+          part(partition_name, &esp_partition) {
+        esp_partition.address = start_sector * SPI_FLASH_SEC_SIZE;
+        esp_partition.size = sector_size * SPI_FLASH_SEC_SIZE;
+        assert(part.init(cfg) == ESP_OK);
+    }
+
+    ~EncryptedPartitionFixture() { }
+
+    esp_partition_t esp_partition;
+
+    SpiFlashEmulator emu;
+
+    nvs::NVSEncryptedPartition part;
+};
+#endif
+
+struct PartitionMockFixture {
+    PartitionMockFixture(uint32_t start_sector = 0,
+            uint32_t sector_size = 1,
+            const char *partition_name = NVS_DEFAULT_PART_NAME)
+        : part_mock(start_sector * SPI_FLASH_SEC_SIZE, sector_size * SPI_FLASH_SEC_SIZE) {
+        std::fill_n(raw_header, sizeof(raw_header)/sizeof(raw_header[0]), UINT8_MAX);
+    }
+
+    ~PartitionMockFixture() { }
+
+    uint8_t raw_header[512];
+
+    PartitionMock part_mock;
+};
+
+struct NVSPageFixture : public PartitionMockFixture {
+    NVSPageFixture(uint32_t start_sector = 0,
+            uint32_t sector_size = 1,
+            const char *partition_name = NVS_DEFAULT_PART_NAME)
+        : PartitionMockFixture(start_sector, sector_size, partition_name), page()
+    {
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 32);
+
+        for (int i = 0; i < 8; i++) {
+            esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+            esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512);
+        }
+
+        if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page");
+    }
+
+    nvs::Page page;
+};
+
+struct NVSValidPageFixture : public PartitionMockFixture {
+    const static uint8_t NS_INDEX = 1;
+
+    // valid header
+    uint8_t raw_header_valid [32];
+
+    // entry table with one entry
+    uint8_t raw_entry_table [32];
+
+    uint8_t ns_entry [32];
+
+    uint8_t value_entry [32];
+
+    NVSValidPageFixture(uint32_t start_sector = 0,
+            uint32_t sector_size = 1,
+            const char *partition_name = NVS_DEFAULT_PART_NAME)
+        : PartitionMockFixture(start_sector, sector_size, partition_name),
+        raw_header_valid {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc},
+        ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0',
+                '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+        value_entry {0x01, 0x01, 0x01, 0xff, 0x3d, 0xf3, 0x99, 0xe5, 't', 'e', 's', 't', '_', 'v', 'a', 'l',
+                'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+        page()
+    {
+        std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0);
+        raw_entry_table[0] = 0xfa;
+
+        // read page header
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32);
+
+        // read entry table
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32);
+
+        // read next free entry's header
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 4);
+
+        // read namespace entry
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32);
+
+        // read normal entry
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32);
+
+        // read normal entry second time during duplicated entry check
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32);
+
+        if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page");
+    }
+
+    nvs::Page page;
+};
+
+struct NVSValidStorageFixture : public PartitionMockFixture {
+    const static uint8_t NS_INDEX = 1;
+
+    uint8_t ns_entry [32];
+
+    uint8_t empty_entry [32];
+
+    NVSValidStorageFixture(uint32_t start_sector = 0,
+            uint32_t sector_size = 3,
+            const char *partition_name = NVS_DEFAULT_PART_NAME)
+        : PartitionMockFixture(start_sector, sector_size, partition_name),
+        ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0',
+                '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+        empty_entry(),
+        storage(&part_mock)
+    {
+        std::fill_n(empty_entry, sizeof(empty_entry)/sizeof(empty_entry[0]), 0xFF);
+
+        // entry table with one entry
+        uint8_t raw_entry_table [32];
+
+        uint8_t header_full_page [] = {
+                0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc};
+
+        uint8_t header_second_page [] = {
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+        uint8_t header_third_page [] = {
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+        // entry_table with all elements deleted except the namespace entry written and the last entry free
+        std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0);
+        raw_entry_table[0] = 0x02;
+        raw_entry_table[31] = 0xFC;
+
+        // read full page header
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(header_full_page, 32);
+
+        // read entry table
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32);
+
+        // reading entry table checks empty entry
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(empty_entry, 32);
+
+        // read namespace entry
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32);
+
+        // read last two pages' headers, which trigger an automatic full read each because each page is empty
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(header_second_page, 32);
+        for (int i = 0; i < 8; i++) {
+            esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+            esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512);
+        }
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(header_third_page, 32);
+        for (int i = 0; i < 8; i++) {
+            esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+            esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 512);
+        }
+
+        // read namespace entry in duplicated header item check of pagemanager::load
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32);
+
+        // storage finally actually reads namespace
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32);
+
+        // storage looks for blob index entries
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32);
+
+        // Storage::eraseOrphanDataBlobs() also wants to take it's turn...
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32);
+
+        if (storage.init(start_sector, sector_size) != ESP_OK) throw FixtureException("couldn't setup page");
+    }
+
+    nvs::Storage storage;
+};
+
+struct NVSValidBlobPageFixture : public PartitionMockFixture {
+    const static uint8_t NS_INDEX = 1;
+    const static size_t BLOB_DATA_SIZE = 32;
+
+    // valid header
+    uint8_t raw_header_valid [32];
+
+    // entry table with one entry
+    uint8_t raw_entry_table [32];
+
+    uint8_t ns_entry [32];
+
+    uint8_t blob_entry [32];
+    uint8_t blob_data [BLOB_DATA_SIZE];
+    uint8_t blob_index [32];
+
+    NVSValidBlobPageFixture(uint32_t start_sector = 0,
+            uint32_t sector_size = 1,
+            const char *partition_name = NVS_DEFAULT_PART_NAME)
+        : PartitionMockFixture(start_sector, sector_size, partition_name),
+        raw_header_valid {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x16, 0xdd, 0xdc},
+        ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0',
+                '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+        blob_entry {0x01, 0x42, 0x02, 0x00, 0xaa, 0xf3, 0x23, 0x87, 't', 'e', 's', 't', '_', 'b', 'l', 'o',
+                'b', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 0x20, 0x00, 0xff, 0xff, 0xc6, 0x96, 0x86, 0xd9},
+        blob_data {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+                0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef},
+        blob_index {0x01, 0x48, 0x01, 0xff, 0x42, 0x6b, 0xdf, 0x66, 't', 'e', 's', 't', '_', 'b', 'l', 'o',
+                'b', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0xff},
+        page()
+    {
+        std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0xFF);
+        raw_entry_table[0] = 0xaa;
+
+        // read page header
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32);
+
+        // read entry table
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32);
+
+        // read next free entry's header
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header, 4);
+
+        // read namespace entry
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32);
+
+        // read normal blob entry + index, not the data
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(blob_entry, 32);
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(blob_index, 32);
+
+        // read normal entry second time during duplicated entry check
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(blob_entry, 32);
+
+        if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page");
+    }
+
+    nvs::Page page;
+};
+
+struct NVSFullPageFixture : public PartitionMockFixture {
+    const static uint8_t NS_INDEX = 1;
+
+    // valid header
+    uint8_t raw_header_valid [32];
+
+    // entry table with one entry
+    uint8_t raw_entry_table [32];
+
+    uint8_t ns_entry [32];
+
+    uint8_t value_entry [32];
+
+    NVSFullPageFixture(uint32_t start_sector = 0,
+            uint32_t sector_size = 1,
+            const char *partition_name = NVS_DEFAULT_PART_NAME,
+            bool load = true)
+        : PartitionMockFixture(start_sector, sector_size, partition_name),
+        raw_header_valid {0xfc, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa3, 0x48, 0x9f, 0x38},
+        ns_entry {0x00, 0x01, 0x01, 0xff, 0x68, 0xc5, 0x3f, 0x0b, 't', 'e', 's', 't', '_', 'n', 's', '\0',
+                '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+        value_entry {0x01, 0x01, 0x01, 0xff, 0x3d, 0xf3, 0x99, 0xe5, 't', 'e', 's', 't', '_', 'v', 'a', 'l',
+                'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+        page()
+    {
+        std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0);
+        raw_entry_table[0] = 0xfa;
+
+        // entry_table with all elements deleted except the namespace entry written and the last entry free
+        std::fill_n(raw_entry_table, sizeof(raw_entry_table)/sizeof(raw_entry_table[0]), 0);
+        raw_entry_table[0] = 0x0a;
+        raw_entry_table[31] = 0xFC;
+
+        // read page header
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_valid, 32);
+
+        // read entry table
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_entry_table, 32);
+
+        // no next free entry check, only one entry written
+
+        // read namespace entry
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(ns_entry, 32);
+
+        // read normal entry
+        esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_ReturnArrayThruPtr_dst(value_entry, 32);
+
+        // no duplicated entry check
+
+        if (load) {
+            if (page.load(&part_mock, start_sector) != ESP_OK) throw FixtureException("couldn't setup page");
+        }
+    }
+
+    nvs::Page page;
+};

+ 27 - 0
components/nvs_flash/host_test/nvs_page_test/CMakeLists.txt

@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+set(COMPONENTS main)
+idf_build_set_property(CONFIG_SPI_FLASH_MOCK 1)
+idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND)
+project(host_nvs_page_test)
+
+add_custom_command(
+    OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
+    COMMAND lcov --capture --directory . --output-file coverage.info
+    COMMENT "Create coverage report"
+    )
+
+add_custom_command(
+    OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage_report/"
+    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info"
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
+    COMMAND genhtml coverage.info --output-directory coverage_report/
+    COMMENT "Turn coverage report into html-based visualization"
+    )
+
+add_custom_target(coverage
+    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
+    DEPENDS "coverage_report/"
+    )

+ 23 - 0
components/nvs_flash/host_test/nvs_page_test/README.rst

@@ -0,0 +1,23 @@
+NVS Page Test for Host
+======================
+
+Build
+-----
+
+First, make sure that the target is set to linux.
+Run ``idf.py --preview set-target linux`` to be sure.
+Then do a normal IDF build: ``idf.py build``.
+
+Run
+---
+
+IDF monitor doesn't work yet for Linux.
+You have to run the app manually: ``./build/host_nvs_page_test.elf``.
+
+Coverage
+---
+
+To generate the coverage, run: ``idf.py coverage``.
+Afterwards, you can view the coverage by opening ``build/coverage_report/index.html`` with your browser.
+Note that you need to run the application at least once before generating the coverage information.
+If you run it multiple times, the coverage information adds up.

+ 10 - 0
components/nvs_flash/host_test/nvs_page_test/main/CMakeLists.txt

@@ -0,0 +1,10 @@
+idf_component_register(SRCS "nvs_page_test.cpp"
+                    INCLUDE_DIRS
+                    "."
+                    "${CMAKE_CURRENT_SOURCE_DIR}/../../fixtures"
+                    "${CMAKE_CURRENT_SOURCE_DIR}/../../../test_nvs_host"
+                    "${CMAKE_CURRENT_SOURCE_DIR}/../../../src"
+                    REQUIRES cmock nvs_flash spi_flash)
+
+target_compile_options(${COMPONENT_LIB} PUBLIC --coverage)
+target_link_libraries(${COMPONENT_LIB} --coverage)

+ 934 - 0
components/nvs_flash/host_test/nvs_page_test/main/nvs_page_test.cpp

@@ -0,0 +1,934 @@
+/* Hello World Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include <stdio.h>
+#include "unity.h"
+#include "test_fixtures.hpp"
+
+extern "C" {
+#include "Mockesp_partition.h"
+}
+
+using namespace std;
+using namespace nvs;
+
+void test_Page_load_reading_header_fails()
+{
+    PartitionMock mock(0, 4096);
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_ARG);
+    Page page;
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state());
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, page.load(&mock, 0));
+}
+
+void test_Page_load_reading_data_fails()
+{
+    uint8_t header[64];
+    std::fill_n(header, sizeof(header)/sizeof(header[0]), UINT8_MAX);
+    PartitionMock mock(0, 4096);
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_raw_ReturnArrayThruPtr_dst(header, 32);
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_FAIL);
+    Page page;
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state());
+    TEST_ASSERT_EQUAL(ESP_FAIL, page.load(&mock, 0));
+}
+
+void test_Page_load__uninitialized_page_has_0xfe()
+{
+    PartitionMockFixture fix;
+    Page page;
+
+    fix.raw_header[511] = 0xfe;
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32);
+
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512);
+
+    // Page::load() should return ESP_OK, but state has to be corrupt
+    TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0));
+
+    TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state());
+}
+
+void test_Page_load__initialized_corrupt_header()
+{
+    PartitionMockFixture fix;
+    Page page;
+
+    uint8_t raw_header_corrupt [] = {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x16, 0xdd, 0xdc};
+
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_corrupt, 32);
+
+    // Page::load() should return ESP_OK, but state has to be corrupt
+    TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0));
+
+    TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state());
+}
+
+void test_Page_load_success()
+{
+    PartitionMockFixture fix;
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32);
+    for (int i = 0; i < 8; i++) {
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512);
+    }
+    Page page;
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state());
+    TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0));
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, page.state());
+}
+
+void test_Page_load_full_page()
+{
+    NVSFullPageFixture fix(0, 1, NVS_DEFAULT_PART_NAME, false);
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state());
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.load(&fix.part_mock, 0));
+    TEST_ASSERT_EQUAL(Page::PageState::FULL, fix.page.state());
+}
+void test_Page_load__seq_number_0()
+{
+    NVSValidPageFixture fix;
+
+    uint32_t seq_num;
+    fix.page.getSeqNumber(seq_num);
+    TEST_ASSERT_EQUAL(0, seq_num);
+}
+
+void test_Page_erase__write_fail()
+{
+    NVSValidPageFixture fix;
+
+    esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 0, 4096, ESP_FAIL);
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.erase());
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state());
+}
+
+void test_Page_erase__success()
+{
+    NVSValidPageFixture fix;
+
+    esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 0, 4096, ESP_OK);
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.erase());
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());
+}
+
+void test_Page_write__initialize_write_failure()
+{
+    PartitionMockFixture fix;
+    uint8_t write_data = 47;
+
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 32);
+    for (int i = 0; i < 8; i++) {
+        esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+        esp_partition_read_raw_ReturnArrayThruPtr_dst(fix.raw_header, 512);
+    }
+    esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_FAIL);
+
+    Page page;
+
+    TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0));
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, page.state());
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, page.writeItem(1, nvs::ItemType::U8, "test", &write_data, sizeof(write_data)));
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state());
+}
+
+void test_Page_write__write_data_fails()
+{
+    NVSPageFixture fix;
+    uint8_t write_data = 47;
+    esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_write_ExpectAnyArgsAndReturn(ESP_FAIL);
+
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.writeItem(1, nvs::ItemType::U8, "test", &write_data, sizeof(write_data)));
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state());
+}
+
+void test_page_write__write_correct_entry_state()
+{
+    NVSPageFixture fix;
+    uint8_t write_data = 47;
+    uint8_t raw_result [4];
+    std::fill_n(raw_result, sizeof(raw_result)/sizeof(raw_result[0]), UINT8_MAX);
+    // mark first entry as written
+    raw_result[0] = 0xfe;
+
+    // initialize page
+    esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK);
+
+    // write entry
+    esp_partition_write_ExpectAnyArgsAndReturn(ESP_OK);
+
+    // write entry state
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK);
+
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.writeItem(1, nvs::ItemType::U8, "test_key", &write_data, sizeof(write_data)));
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_write__write_correct_data()
+{
+    NVSPageFixture fix;
+    uint8_t write_data = 47;
+    uint8_t raw_result [32] = {0x01, 0x01, 0x01, 0xff, 0x98, 0x6f, 0x21, 0xfd, 't', 'e', 's', 't', '_', 'k', 'e', 'y',
+            '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 47, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+    // initialize page
+    esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK);
+
+    // write entry
+    esp_partition_write_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 64, raw_result, 32, 32, ESP_OK);
+
+    // write entry state
+    esp_partition_write_raw_ExpectAnyArgsAndReturn(ESP_OK);
+
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.writeItem(1, nvs::ItemType::U8, "test_key", &write_data, sizeof(write_data)));
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_readItem__read_entry_fails()
+{
+    NVSValidPageFixture fix;
+
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    uint8_t read_value = 0;
+
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL);
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value));
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state());
+
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+}
+
+void test_Page_readItem__read_corrupted_entry()
+{
+    NVSValidPageFixture fix;
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    uint8_t read_value = 0;
+
+    // corrupting entry
+    fix.value_entry[0] = 0x0;
+
+    // first read the entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // Page::eraseEntryAndSpan() reads entry again
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // erasing entry by setting bit in entry table (0xfa -> 0xf2)
+    uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00};
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK);
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value));
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount());
+}
+
+void test_Page_readItem__read_corrupted_second_read_fail()
+{
+    NVSValidPageFixture fix;
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    uint8_t read_value = 0;
+
+    // corrupting entry
+    fix.value_entry[0] = 0x0;
+
+    // first read the entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // Page::eraseEntryAndSpan() reads entry again
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL);
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value));
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state());
+}
+
+void test_Page_readItem__read_corrupted_erase_fail()
+{
+    NVSValidPageFixture fix;
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    uint8_t read_value = 0;
+
+    // corrupting entry
+    fix.value_entry[0] = 0x0;
+
+    // first read the entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // Page::eraseEntryAndSpan() reads entry again
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // erasing entry by setting bit in entry table (0xfa -> 0xf2)
+    uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00};
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_FAIL);
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value));
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state());
+}
+
+void test_Page_readItem__read_entry_suceeds()
+{
+    NVSValidPageFixture fix;
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    uint8_t read_value = 0;
+
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.readItem(NVSValidPageFixture::NS_INDEX, "test_value", read_value));
+
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(47, read_value);
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_readItem__blob_read_data_fails()
+{
+    NVSValidBlobPageFixture fix;
+    TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    uint8_t chunk_start = 0;
+    uint8_t read_data [32];
+
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32);
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL);
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX,
+            ItemType::BLOB_DATA,
+            "test_blob",
+            read_data,
+            32,
+            chunk_start));
+
+    TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_readItem__corrupt_data_erase_failure()
+{
+    NVSValidBlobPageFixture fix;
+    TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    uint8_t chunk_start = 0;
+    uint8_t read_data [32];
+
+    fix.blob_data[16] = 0xdf;
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32);
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE);
+
+    // Page::eraseEntryAndSpan() reads entry again
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_FAIL);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE);
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.readItem(NVSValidPageFixture::NS_INDEX,
+            ItemType::BLOB_DATA,
+            "test_blob",
+            read_data,
+            32,
+            chunk_start));
+
+    TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_readItem__blob_corrupt_data()
+{
+    NVSValidBlobPageFixture fix;
+    TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    uint8_t chunk_start = 0;
+    uint8_t read_data [32];
+
+    fix.blob_data[16] = 0xdf;
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32);
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE);
+
+    // Page::eraseEntryAndSpan() reads entry again
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE);
+
+    // erasing entry by setting bit in entry table (0xfa -> 0xf2)
+    uint8_t raw_result [4] = {0xa2, 0xff, 0xff, 0xff};
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK);
+
+    esp_partition_erase_range_ExpectAndReturn(&fix.part_mock.partition, 96, 64, ESP_OK);
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.readItem(NVSValidPageFixture::NS_INDEX,
+            ItemType::BLOB_DATA,
+            "test_blob",
+            read_data,
+            32,
+            chunk_start));
+
+    TEST_ASSERT_EQUAL(3, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(1, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_readItem__blob_read_entry_suceeds()
+{
+    NVSValidBlobPageFixture fix;
+    TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    uint8_t chunk_start = 0;
+    uint8_t read_data [32];
+
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32);
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE);
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.readItem(NVSValidPageFixture::NS_INDEX,
+            ItemType::BLOB_DATA,
+            "test_blob",
+            read_data,
+            32,
+            chunk_start));
+
+    TEST_ASSERT_EQUAL_MEMORY(fix.blob_data, read_data, fix.BLOB_DATA_SIZE);
+
+    // make sure nothing was erased, i.e. checksums matched
+    TEST_ASSERT_EQUAL(4, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(0, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_cmp__uninitialized()
+{
+    Page page;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, page.cmpItem(uint8_t(1) , "test", 47));
+}
+
+void test_Page_cmp__item_not_found()
+{
+    NVSValidPageFixture fix;
+
+    // no expectations here since comparison uses the item hash list
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.cmpItem(uint8_t(1), "different", 47));
+}
+
+void test_Page_cmp__item_type_mismatch()
+{
+    NVSValidPageFixture fix;
+
+    // read normal entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_TYPE_MISMATCH, fix.page.cmpItem(uint8_t(1), "test_value", int(47)));
+}
+
+void test_Page_cmp__item_content_mismatch()
+{
+    NVSValidPageFixture fix;
+
+    // read normal entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_CONTENT_DIFFERS, fix.page.cmpItem(uint8_t(1), "test_value", uint8_t(46)));
+}
+
+void test_Page_cmp__item_content_match()
+{
+    NVSValidPageFixture fix;
+
+    // read normal entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.cmpItem(NVSValidPageFixture::NS_INDEX, "test_value", uint8_t(47)));
+}
+
+void test_Page_cmpItem__blob_data_mismatch()
+{
+    NVSValidBlobPageFixture fix;
+
+    // read blob entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32);
+
+    // read blob data
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE);
+
+
+    uint8_t blob_data_different [] =
+           {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xee};
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_CONTENT_DIFFERS,
+            fix.page.cmpItem(uint8_t(1),
+            ItemType::BLOB_DATA,
+            "test_blob",
+            blob_data_different,
+            32));
+}
+
+void test_Page_cmpItem__blob_data_match()
+{
+    NVSValidBlobPageFixture fix;
+
+    // read blob entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_entry, 32);
+
+    // read blob data
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.blob_data, fix.BLOB_DATA_SIZE);
+
+
+    uint8_t blob_data_same [] =
+           {0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+            0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef};
+
+    TEST_ASSERT_EQUAL(ESP_OK,
+            fix.page.cmpItem(NVSValidPageFixture::NS_INDEX,
+            ItemType::BLOB_DATA,
+            "test_blob",
+            blob_data_same,
+            32));
+}
+
+void test_Page_eraseItem__uninitialized()
+{
+    Page page;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
+}
+
+void test_Page_eraseItem__key_not_found()
+{
+    NVSValidPageFixture fix;
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "different"));
+
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_eraseItem__write_fail()
+{
+    NVSValidPageFixture fix;
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    // first read the entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // Page::eraseEntryAndSpan() reads entry again
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // erasing entry by setting bit in entry table (0xfa -> 0xf2)
+    uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00};
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_FAIL);
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
+
+    TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state());
+}
+
+void test_Page_eraseItem__write_succeed()
+{
+    NVSValidPageFixture fix;
+    TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+
+    // first read the entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // Page::eraseEntryAndSpan() reads entry again
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    // erasing entry by setting bit in entry table (0xfa -> 0xf2)
+    uint8_t raw_result [4] = {0xf2, 0x00, 0x00, 0x00};
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, 1, 32, raw_result, 4, 4, ESP_OK);
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
+
+    TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount());
+    TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount());
+
+    TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
+}
+
+void test_Page_findItem__uninitialized()
+{
+    Page page;
+
+    size_t index = 0;
+    Item item;
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND,
+            page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "test_value", index, item));
+}
+
+void test_Page_find__wrong_ns()
+{
+    NVSValidPageFixture fix;
+    size_t index = 0;
+    Item item;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND,
+            fix.page.findItem(NVSValidPageFixture::NS_INDEX + 1, nvs::ItemType::U8, "test_value", index, item));
+}
+
+void test_Page_find__wrong_type()
+{
+    NVSValidPageFixture fix;
+    size_t index = 0;
+    Item item;
+
+    // read normal entry
+    esp_partition_read_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_ReturnArrayThruPtr_dst(fix.value_entry, 32);
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_TYPE_MISMATCH,
+            fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::I8, "test_value", index, item));
+}
+
+void test_Page_find__key_empty()
+{
+    NVSValidPageFixture fix;
+    size_t index = 0;
+    Item item;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND,
+            fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "", index, item));
+}
+
+void test_Page_find__wrong_key()
+{
+    NVSValidPageFixture fix;
+    size_t index = 0;
+    Item item;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND,
+            fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "different", index, item));
+}
+
+void test_Page_find__too_large_index()
+{
+    NVSValidPageFixture fix;
+    size_t index = 2;
+    Item item;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND,
+            fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "test_value", index, item));
+}
+
+void test_Page_findItem__without_read()
+{
+    NVSValidPageFixture fix;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND,
+            fix.page.findItem(NVSValidPageFixture::NS_INDEX, nvs::ItemType::U8, "different"));
+}
+
+void test_Page_markFull__wrong_state()
+{
+    NVSPageFixture fix;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, fix.page.markFull());
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());
+}
+
+void test_Page_markFull__success()
+{
+    NVSValidPageFixture fix;
+    Page::PageState expected_state = Page::PageState::FULL;
+
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_OK);
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.markFull());
+    TEST_ASSERT_EQUAL(Page::PageState::FULL, fix.page.state());
+}
+
+void test_Page_markFull__write_fail()
+{
+    NVSValidPageFixture fix;
+    Page::PageState expected_state = Page::PageState::FREEING;
+
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_FAIL);
+
+    TEST_ASSERT_EQUAL(ESP_FAIL, fix.page.markFreeing());
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, fix.page.state());
+}
+
+void test_Page_markFreeing__wrong_state()
+{
+    NVSPageFixture fix;
+
+    TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_STATE, fix.page.markFreeing());
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());
+}
+
+void test_Page_markFreeing__success()
+{
+    NVSValidPageFixture fix;
+    Page::PageState expected_state = Page::PageState::FREEING;
+
+    esp_partition_write_raw_ExpectWithArrayAndReturn(&fix.part_mock.partition, sizeof(fix.part_mock.partition), 0, &expected_state, sizeof(expected_state), 4, ESP_OK);
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.markFreeing());
+    TEST_ASSERT_EQUAL(Page::PageState::FREEING, fix.page.state());
+}
+
+void test_Page_getVarDataTailroom__uninitialized_page()
+{
+    NVSPageFixture fix;
+
+    TEST_ASSERT_EQUAL(Page::CHUNK_MAX_SIZE, fix.page.getVarDataTailroom());
+}
+
+void test_Page_getVarDataTailroom__success()
+{
+    NVSValidPageFixture fix;
+
+    // blob data item, written namespace item, written normal item: 3 items
+    TEST_ASSERT_EQUAL((Page::ENTRY_COUNT - 3) * Page::ENTRY_SIZE, fix.page.getVarDataTailroom());
+}
+
+void test_Page_calcEntries__uninit()
+{
+    NVSPageFixture fix;
+    TEST_ASSERT_EQUAL(Page::PageState::UNINITIALIZED, fix.page.state());
+
+    nvs_stats_t nvsStats = {0, 0, 0, 0};
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats));
+    TEST_ASSERT_EQUAL(0, nvsStats.used_entries);
+    TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.free_entries);
+    TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries);
+    TEST_ASSERT_EQUAL(0, nvsStats.namespace_count);
+}
+
+void test_Page_calcEntries__corrupt()
+{
+    PartitionMockFixture fix;
+    Page page;
+
+    uint8_t raw_header_corrupt [] = {0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x16, 0xdd, 0xdc};
+
+    esp_partition_read_raw_ExpectAnyArgsAndReturn(ESP_OK);
+    esp_partition_read_raw_ReturnArrayThruPtr_dst(raw_header_corrupt, 32);
+
+    // Page::load() should return ESP_OK, but state has to be corrupt
+    TEST_ASSERT_EQUAL(ESP_OK, page.load(&fix.part_mock, 0));
+
+    TEST_ASSERT_EQUAL(Page::PageState::CORRUPT, page.state());
+
+    nvs_stats_t nvsStats = {0, 0, 0, 0};
+
+    TEST_ASSERT_EQUAL(ESP_OK, page.calcEntries(nvsStats));
+    TEST_ASSERT_EQUAL(0, nvsStats.used_entries);
+    TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.free_entries);
+    TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries);
+    TEST_ASSERT_EQUAL(0, nvsStats.namespace_count);
+}
+
+void test_Page_calcEntries__active_wo_blob()
+{
+    NVSValidPageFixture fix;
+
+    nvs_stats_t nvsStats = {0, 0, 0, 0};
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats));
+    TEST_ASSERT_EQUAL(2, nvsStats.used_entries);
+    TEST_ASSERT_EQUAL(124, nvsStats.free_entries);
+    TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries);
+    TEST_ASSERT_EQUAL(0, nvsStats.namespace_count);
+}
+
+void test_Page_calcEntries__active_with_blob()
+{
+    NVSValidBlobPageFixture fix;
+
+    nvs_stats_t nvsStats = {0, 0, 0, 0};
+
+    TEST_ASSERT_EQUAL(ESP_OK, fix.page.calcEntries(nvsStats));
+    TEST_ASSERT_EQUAL(4, nvsStats.used_entries);
+    TEST_ASSERT_EQUAL(122, nvsStats.free_entries);
+    TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries);
+    TEST_ASSERT_EQUAL(0, nvsStats.namespace_count);
+}
+
+void test_Page_calcEntries__invalid()
+{
+    Page page;
+
+    nvs_stats_t nvsStats = {0, 0, 0, 0};
+
+    TEST_ASSERT_EQUAL(Page::PageState::INVALID, page.state());
+
+    // total entries always get updated
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, page.calcEntries(nvsStats));
+    TEST_ASSERT_EQUAL(0, nvsStats.used_entries);
+    TEST_ASSERT_EQUAL(0, nvsStats.free_entries);
+    TEST_ASSERT_EQUAL(Page::ENTRY_COUNT, nvsStats.total_entries);
+    TEST_ASSERT_EQUAL(0, nvsStats.namespace_count);
+}
+
+int main(int argc, char **argv)
+{
+    UNITY_BEGIN();
+    RUN_TEST(test_Page_load_reading_header_fails);
+    RUN_TEST(test_Page_load_reading_data_fails);
+    RUN_TEST(test_Page_load__uninitialized_page_has_0xfe);
+    RUN_TEST(test_Page_load__initialized_corrupt_header);
+    RUN_TEST(test_Page_load_success);
+    RUN_TEST(test_Page_load_full_page);
+    RUN_TEST(test_Page_load__seq_number_0);
+    RUN_TEST(test_Page_erase__write_fail);
+    RUN_TEST(test_Page_erase__success);
+    RUN_TEST(test_Page_write__initialize_write_failure);
+    RUN_TEST(test_Page_write__write_data_fails);
+    RUN_TEST(test_page_write__write_correct_entry_state);
+    RUN_TEST(test_Page_write__write_correct_data);
+    RUN_TEST(test_Page_readItem__read_entry_fails);
+    RUN_TEST(test_Page_readItem__read_corrupted_entry);
+    RUN_TEST(test_Page_readItem__read_corrupted_second_read_fail);
+    RUN_TEST(test_Page_readItem__read_corrupted_erase_fail);
+    RUN_TEST(test_Page_readItem__read_entry_suceeds);
+    RUN_TEST(test_Page_readItem__blob_read_data_fails);
+    RUN_TEST(test_Page_readItem__blob_corrupt_data);
+    RUN_TEST(test_Page_readItem__blob_read_entry_suceeds);
+    RUN_TEST(test_Page_cmp__uninitialized);
+    RUN_TEST(test_Page_cmp__item_not_found);
+    RUN_TEST(test_Page_cmp__item_type_mismatch);
+    RUN_TEST(test_Page_cmp__item_content_mismatch);
+    RUN_TEST(test_Page_cmp__item_content_match);
+    RUN_TEST(test_Page_cmpItem__blob_data_mismatch);
+    RUN_TEST(test_Page_cmpItem__blob_data_match);
+    RUN_TEST(test_Page_eraseItem__uninitialized);
+    RUN_TEST(test_Page_eraseItem__key_not_found);
+    RUN_TEST(test_Page_eraseItem__write_fail);
+    RUN_TEST(test_Page_readItem__corrupt_data_erase_failure);
+    RUN_TEST(test_Page_eraseItem__write_succeed);
+    RUN_TEST(test_Page_findItem__uninitialized);
+    RUN_TEST(test_Page_find__wrong_ns);
+    RUN_TEST(test_Page_find__wrong_type);
+    RUN_TEST(test_Page_find__key_empty);
+    RUN_TEST(test_Page_find__wrong_key);
+    RUN_TEST(test_Page_find__too_large_index);
+    RUN_TEST(test_Page_findItem__without_read);
+    RUN_TEST(test_Page_markFull__wrong_state);
+    RUN_TEST(test_Page_markFreeing__wrong_state);
+    RUN_TEST(test_Page_markFull__success);
+    RUN_TEST(test_Page_markFreeing__success);
+    RUN_TEST(test_Page_markFull__write_fail);
+    RUN_TEST(test_Page_getVarDataTailroom__uninitialized_page);
+    RUN_TEST(test_Page_getVarDataTailroom__success);
+    RUN_TEST(test_Page_calcEntries__uninit);
+    RUN_TEST(test_Page_calcEntries__corrupt);
+    RUN_TEST(test_Page_calcEntries__active_wo_blob);
+    RUN_TEST(test_Page_calcEntries__active_with_blob);
+    RUN_TEST(test_Page_calcEntries__invalid);
+    UNITY_END();
+    return 0;
+}

+ 3 - 0
components/nvs_flash/host_test/nvs_page_test/sdkconfig.defaults

@@ -0,0 +1,3 @@
+CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
+CONFIG_IDF_TARGET="linux"
+CONFIG_CXX_EXCEPTIONS=y

+ 1 - 1
components/nvs_flash/test_nvs_host/crc.cpp → components/nvs_flash/mock/int/crc.cpp

@@ -52,7 +52,7 @@ static const unsigned int crc32_le_table[256] = {
 
 
 
-extern "C" unsigned int crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len)
+extern "C" uint32_t esp_rom_crc32_le(unsigned int crc, unsigned char const * buf,unsigned int len)
 {
     unsigned int i;
     crc = ~crc;

+ 5 - 1
components/nvs_flash/test_nvs_host/crc.h → components/nvs_flash/mock/int/crc.h

@@ -20,7 +20,11 @@
 extern "C" {
 #endif
 
-uint32_t crc32_le(uint32_t crc, const uint8_t* buf, size_t len);
+/**
+ * Mock function to replace ESP ROM function used in IDF with a Linux implementation.
+ * Note: the name MUST have the prefix esp_rom_* since tools/ci/check_rom_apis.sh checks and complains otherwise.
+ */
+uint32_t esp_rom_crc32_le(uint32_t crc, const uint8_t* buf, size_t len);
 
 #ifdef __cplusplus
 }

+ 2 - 2
components/nvs_flash/src/compressed_enum_table.hpp

@@ -35,7 +35,7 @@ public:
 
     Tenum get(size_t index) const
     {
-        assert(index >= 0 && index < Nitems);
+        assert(index < Nitems);
         size_t wordIndex = index / ITEMS_PER_WORD;
         size_t offset = (index % ITEMS_PER_WORD) * Nbits;
 
@@ -44,7 +44,7 @@ public:
 
     void set(size_t index, Tenum val)
     {
-        assert(index >= 0 && index < Nitems);
+        assert(index < Nitems);
         size_t wordIndex = index / ITEMS_PER_WORD;
         size_t offset = (index % ITEMS_PER_WORD) * Nbits;
 

+ 10 - 10
components/nvs_flash/src/nvs_api.cpp

@@ -22,17 +22,17 @@
 #include <functional>
 #include "nvs_handle_simple.hpp"
 
-#ifdef ESP_PLATFORM
+#ifdef LINUX_TARGET
+#include "crc.h"
+#define ESP_LOGD(...)
+#else // LINUX_TARGET
 #include <esp32/rom/crc.h>
 
 // Uncomment this line to force output from this module
 // #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
 #include "esp_log.h"
 static const char* TAG = "nvs";
-#else
-#include "crc.h"
-#define ESP_LOGD(...)
-#endif
+#endif // ! LINUX_TARGET
 
 class NVSHandleEntry : public intrusive_list_node<NVSHandleEntry> {
 public:
@@ -56,9 +56,9 @@ uint32_t NVSHandleEntry::s_nvs_next_handle;
 
 extern "C" void nvs_dump(const char *partName);
 
-#ifdef ESP_PLATFORM
+#ifndef LINUX_TARGET
 SemaphoreHandle_t nvs::Lock::mSemaphore = nullptr;
-#endif
+#endif // ! LINUX_TARGET
 
 using namespace std;
 using namespace nvs;
@@ -125,7 +125,7 @@ extern "C" esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partiti
     return init_res;
 }
 
-#ifdef ESP_PLATFORM
+#ifndef LINUX_TARGET
 extern "C" esp_err_t nvs_flash_init_partition(const char *part_name)
 {
     Lock::init();
@@ -204,7 +204,7 @@ extern "C" esp_err_t nvs_flash_erase(void)
 {
     return nvs_flash_erase_partition(NVS_DEFAULT_PART_NAME);
 }
-#endif // ESP_PLATFORM
+#endif // ! LINUX_TARGET
 
 extern "C" esp_err_t nvs_flash_deinit_partition(const char* partition_name)
 {
@@ -528,7 +528,7 @@ extern "C" esp_err_t nvs_get_used_entry_count(nvs_handle_t c_handle, size_t* use
     return err;
 }
 
-#if (defined CONFIG_NVS_ENCRYPTION) && (defined ESP_PLATFORM)
+#if (defined CONFIG_NVS_ENCRYPTION) && (!defined LINUX_TARGET)
 
 extern "C" esp_err_t nvs_flash_generate_keys(const esp_partition_t* partition, nvs_sec_cfg_t* cfg)
 {

+ 9 - 9
components/nvs_flash/src/nvs_page.cpp

@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 #include "nvs_page.hpp"
-#if defined(ESP_PLATFORM)
-#include <esp32/rom/crc.h>
-#else
+#if defined(LINUX_TARGET)
 #include "crc.h"
+#else
+#include <esp_rom_crc.h>
 #endif
 #include <cstdio>
 #include <cstring>
@@ -27,7 +27,7 @@ Page::Page() : mPartition(nullptr) { }
 
 uint32_t Page::Header::calculateCrc32()
 {
-    return crc32_le(0xffffffff,
+    return esp_rom_crc32_le(0xffffffff,
                     reinterpret_cast<uint8_t*>(this) + offsetof(Header, mSeqNumber),
                     offsetof(Header, mCrc32) - offsetof(Header, mSeqNumber));
 }
@@ -137,7 +137,7 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size)
 
     const uint8_t* buf = data;
 
-#ifdef ESP_PLATFORM
+#if !defined LINUX_TARGET
     // TODO: check whether still necessary with esp_partition* API
     /* On the ESP32, data can come from DROM, which is not accessible by spi_flash_write
      * function. To work around this, we copy the data to heap if it came from DROM.
@@ -153,15 +153,15 @@ esp_err_t Page::writeEntryData(const uint8_t* data, size_t size)
         }
         memcpy((void*)buf, data, size);
     }
-#endif //ESP_PLATFORM
+#endif // ! LINUX_TARGET
 
     auto rc = mPartition->write(getEntryAddress(mNextFreeEntry), buf, size);
 
-#ifdef ESP_PLATFORM
+#if !defined LINUX_TARGET
     if (buf != data) {
         free((void*)buf);
     }
-#endif //ESP_PLATFORM
+#endif // ! LINUX_TARGET
     if (rc != ESP_OK) {
         mState = PageState::INVALID;
         return rc;
@@ -968,7 +968,7 @@ size_t Page::getVarDataTailroom() const
     } else if (mState == PageState::FULL) {
         return 0;
     }
-    /* Skip one entry for header*/
+    /* Skip one entry for blob data item precessing the data */
     return ((mNextFreeEntry < (ENTRY_COUNT-1)) ? ((ENTRY_COUNT - mNextFreeEntry - 1) * ENTRY_SIZE): 0);
 }
 

+ 3 - 0
components/nvs_flash/src/nvs_page.hpp

@@ -224,6 +224,9 @@ protected:
     uint16_t mUsedEntryCount = 0;
     uint16_t mErasedEntryCount = 0;
 
+    /**
+     * This hash list stores hashes of namespace index, key, and ChunkIndex for quick lookup when searching items.
+     */
     HashList mHashList;
 
     Partition *mPartition;

+ 2 - 2
components/nvs_flash/src/nvs_partition.cpp

@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "string.h"
+#include <cstdlib>
 #include "nvs_partition.hpp"
 
 namespace nvs {
@@ -22,7 +22,7 @@ NVSPartition::NVSPartition(const esp_partition_t* partition)
 {
     // ensure the class is in a valid state
     if (partition == nullptr) {
-        abort();
+        std::abort();
     }
 }
 

+ 16 - 19
components/nvs_flash/src/nvs_platform.hpp

@@ -11,11 +11,23 @@
 // 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.
-#ifndef nvs_platform_h
-#define nvs_platform_h
+#pragma once
 
+#ifdef LINUX_TARGET
+namespace nvs
+{
+class Lock
+{
+public:
+    Lock() { }
+    ~Lock() { }
+    static void init() {}
+    static void uninit() {}
+};
+} // namespace nvs
+
+#else // LINUX_TARGET
 
-#ifdef ESP_PLATFORM
 #include "freertos/FreeRTOS.h"
 #include "freertos/semphr.h"
 
@@ -63,19 +75,4 @@ public:
 };
 } // namespace nvs
 
-#else // ESP_PLATFORM
-namespace nvs
-{
-class Lock
-{
-public:
-    Lock() { }
-    ~Lock() { }
-    static void init() {}
-    static void uninit() {}
-};
-} // namespace nvs
-#endif // ESP_PLATFORM
-
-
-#endif /* nvs_platform_h */
+#endif // LINUX_TARGET

+ 14 - 8
components/nvs_flash/src/nvs_storage.cpp

@@ -14,9 +14,15 @@
 #include "nvs_storage.hpp"
 
 #ifndef ESP_PLATFORM
+// We need NO_DEBUG_STORAGE here since the integration tests on the host add some debug code.
+// The unit tests, however, don't want debug code since they check the behavior via data in/output and disturb
+// the order of calling mocked functions.
+#ifndef NO_DEBUG_STORAGE
 #include <map>
 #include <sstream>
+#define DEBUG_STORAGE
 #endif
+#endif // !ESP_PLATFORM
 
 namespace nvs
 {
@@ -136,7 +142,7 @@ esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount)
     // Purge the blob index list
     blobIdxList.clearAndFreeNodes();
 
-#ifndef ESP_PLATFORM
+#ifdef DEBUG_STORAGE
     debugCheck();
 #endif
     return ESP_OK;
@@ -165,7 +171,7 @@ esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const vo
     uint8_t chunkCount = 0;
     TUsedPageList usedPages;
     size_t remainingSize = dataSize;
-    size_t offset=0;
+    size_t offset = 0;
     esp_err_t err = ESP_OK;
 
     /* Check how much maximum data can be accommodated**/
@@ -182,8 +188,8 @@ esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const vo
     do {
         Page& page = getCurrentPage();
         size_t tailroom = page.getVarDataTailroom();
-        size_t chunkSize =0;
-        if (!chunkCount && tailroom < dataSize && tailroom < Page::CHUNK_MAX_SIZE/10) {
+        size_t chunkSize = 0;
+        if (chunkCount == 0U && ((tailroom < dataSize) || (tailroom == 0 && dataSize == 0)) && tailroom < Page::CHUNK_MAX_SIZE/10) {
             /** This is the first chunk and tailroom is too small ***/
             if (page.state() != Page::PageState::FULL) {
                 err = page.markFull();
@@ -206,7 +212,7 @@ esp_err_t Storage::writeMultiPageBlob(uint8_t nsIndex, const char* key, const vo
         }
 
         /* Split the blob into two and store the chunk of available size onto the current page */
-        assert(tailroom!=0);
+        assert(tailroom != 0);
         chunkSize = (remainingSize > tailroom)? tailroom : remainingSize;
         remainingSize -= chunkSize;
 
@@ -383,7 +389,7 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
             return err;
         }
     }
-#ifndef ESP_PLATFORM
+#ifdef DEBUG_STORAGE
     debugCheck();
 #endif
     return ESP_OK;
@@ -666,7 +672,7 @@ void Storage::debugDump()
     }
 }
 
-#ifndef ESP_PLATFORM
+#ifdef DEBUG_STORAGE
 void Storage::debugCheck()
 {
     std::map<std::string, Page*> keys;
@@ -691,7 +697,7 @@ void Storage::debugCheck()
         assert(usedCount == p->getUsedEntryCount());
     }
 }
-#endif //ESP_PLATFORM
+#endif //DEBUG_STORAGE
 
 esp_err_t Storage::fillStats(nvs_stats_t& nvsStats)
 {

+ 10 - 10
components/nvs_flash/src/nvs_types.cpp

@@ -13,10 +13,10 @@
 // limitations under the License.
 #include "nvs_types.hpp"
 
-#if defined(ESP_PLATFORM)
-#include <esp32/rom/crc.h>
-#else
+#if defined(LINUX_TARGET)
 #include "crc.h"
+#else
+#include <esp_rom_crc.h>
 #endif
 
 namespace nvs
@@ -25,10 +25,10 @@ uint32_t Item::calculateCrc32() const
 {
     uint32_t result = 0xffffffff;
     const uint8_t* p = reinterpret_cast<const uint8_t*>(this);
-    result = crc32_le(result, p + offsetof(Item, nsIndex),
+    result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex),
                       offsetof(Item, crc32) - offsetof(Item, nsIndex));
-    result = crc32_le(result, p + offsetof(Item, key), sizeof(key));
-    result = crc32_le(result, p + offsetof(Item, data), sizeof(data));
+    result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key));
+    result = esp_rom_crc32_le(result, p + offsetof(Item, data), sizeof(data));
     return result;
 }
 
@@ -36,17 +36,17 @@ uint32_t Item::calculateCrc32WithoutValue() const
 {
     uint32_t result = 0xffffffff;
     const uint8_t* p = reinterpret_cast<const uint8_t*>(this);
-    result = crc32_le(result, p + offsetof(Item, nsIndex),
+    result = esp_rom_crc32_le(result, p + offsetof(Item, nsIndex),
                       offsetof(Item, datatype) - offsetof(Item, nsIndex));
-    result = crc32_le(result, p + offsetof(Item, key), sizeof(key));
-    result = crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex));
+    result = esp_rom_crc32_le(result, p + offsetof(Item, key), sizeof(key));
+    result = esp_rom_crc32_le(result, p + offsetof(Item, chunkIndex), sizeof(chunkIndex));
     return result;
 }
 
 uint32_t Item::calculateCrc32(const uint8_t* data, size_t size)
 {
     uint32_t result = 0xffffffff;
-    result = crc32_le(result, data, size);
+    result = esp_rom_crc32_le(result, data, size);
     return result;
 }
 

+ 4 - 4
components/nvs_flash/test_nvs_host/Makefile

@@ -3,6 +3,7 @@ all: $(TEST_PROGRAM)
 
 SOURCE_FILES = \
 	esp_error_check_stub.cpp \
+	../mock/int/crc.cpp \
 	$(addprefix ../src/, \
 		nvs_types.cpp \
 		nvs_api.cpp \
@@ -28,7 +29,6 @@ SOURCE_FILES = \
 	test_nvs_partition.cpp \
 	test_nvs_cxx_api.cpp \
 	test_nvs_initialization.cpp \
-	crc.cpp \
 	main.cpp
 
 ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1)
@@ -37,9 +37,9 @@ else
 COMPILER := gcc
 endif
 
-CPPFLAGS += -I../include -I../src -I./ -I../../esp_common/include -I../../esp32/include -I ../../mbedtls/mbedtls/include -I ../../spi_flash/include -I ../../hal/include -I ../../xtensa/include -I ../../../tools/catch -fprofile-arcs -ftest-coverage -g2 -ggdb
-CFLAGS += -fprofile-arcs -ftest-coverage
-CXXFLAGS += -std=c++11 -Wall -Werror
+CPPFLAGS += -I../include -I../src -I../mock/int -I./ -I../../esp_common/include -I../../esp32/include -I ../../mbedtls/mbedtls/include -I ../../spi_flash/include -I ../../hal/include -I ../../xtensa/include -I ../../../tools/catch -fprofile-arcs -ftest-coverage -g2 -ggdb
+CFLAGS += -fprofile-arcs -ftest-coverage -DLINUX_TARGET
+CXXFLAGS += -std=c++11 -Wall -Werror -DLINUX_TARGET
 LDFLAGS += -lstdc++ -Wall -fprofile-arcs -ftest-coverage
 
 ifeq ($(COMPILER),clang)

+ 123 - 55
components/spi_flash/CMakeLists.txt

@@ -1,63 +1,131 @@
-if(BOOTLOADER_BUILD)
-    if(CONFIG_IDF_TARGET_ESP32)
-        # ESP32 Bootloader needs SPIUnlock from this file, but doesn't
-        # need other parts of this component
-        set(srcs "esp32/spi_flash_rom_patch.c")
-    elseif(CONFIG_IDF_TARGET_ESP32S2)
-        set(srcs "esp32s2/spi_flash_rom_patch.c")
-    elseif(CONFIG_IDF_TARGET_ESP32S3)
-        set(srcs "esp32s3/spi_flash_rom_patch.c")
+idf_build_get_property(spi_flash_mock CONFIG_SPI_FLASH_MOCK)
+idf_build_get_property(target IDF_TARGET)
+if(${spi_flash_mock})
+    message(STATUS "building SPI FLASH MOCKS")
+
+    set(IDF_PATH $ENV{IDF_PATH})
+    set(CMOCK_DIR "${IDF_PATH}/components/cmock/CMock")
+    set(MOCK_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/mocks")
+
+    file(MAKE_DIRECTORY ${MOCK_GEN_DIR})
+
+    set(MOCK_OUTPUT
+        "${MOCK_GEN_DIR}/Mockesp_partition.c" "${MOCK_GEN_DIR}/Mockesp_partition.h"
+        "${MOCK_GEN_DIR}/Mockesp_flash.c" "${MOCK_GEN_DIR}/Mockesp_flash.h"
+        "${MOCK_GEN_DIR}/Mockesp_spi_flash.c" "${MOCK_GEN_DIR}/Mockesp_spi_flash.h")
+
+    set(MOCK_HEADERS
+        ${CMAKE_CURRENT_SOURCE_DIR}/include/esp_partition.h
+        ${CMAKE_CURRENT_SOURCE_DIR}/include/esp_flash.h
+        ${CMAKE_CURRENT_SOURCE_DIR}/include/esp_spi_flash.h
+        )
+
+    set(ENV{UNITY_DIR} "$ENV{IDF_PATH}/components/cmock/CMock")
+
+    set(include_dirs
+        "${CMAKE_CURRENT_SOURCE_DIR}/include"
+        "${MOCK_GEN_DIR}")
+
+    set(srcs "${MOCK_GEN_DIR}/Mockesp_partition.c"
+             "${MOCK_GEN_DIR}/Mockesp_spi_flash.c"
+             "${MOCK_GEN_DIR}/Mockesp_flash.c")
+
+    if(${target} STREQUAL "linux")
+        list(APPEND include_dirs
+            "${CMAKE_CURRENT_SOURCE_DIR}/../spi_flash/sim/stubs/soc/include"
+            "${CMAKE_CURRENT_SOURCE_DIR}/../spi_flash/sim/stubs/xtensa")
     endif()
-    set(cache_srcs "")
-    set(priv_requires bootloader_support soc)
-else()
-    set(cache_srcs
-        "cache_utils.c"
-        "flash_mmap.c"
-        "flash_ops.c"
-        "${IDF_TARGET}/flash_ops_${IDF_TARGET}.c"
+
+    idf_component_register(SRCS "${srcs}"
+                        INCLUDE_DIRS ${include_dirs}
+                        REQUIRES cmock)
+
+    add_custom_command(
+        OUTPUT ruby_found SYMBOLIC
+        COMMAND "ruby" "-v"
+        COMMENT "Try to find ruby. If this fails, you need to install ruby"
     )
-    set(srcs
-        "partition.c")
 
-    if(CONFIG_IDF_TARGET_ESP32)
-        list(APPEND srcs
-            "esp32/spi_flash_rom_patch.c")
-    elseif(CONFIG_IDF_TARGET_ESP32S2)
-        list(APPEND srcs
-            "esp32s2/spi_flash_rom_patch.c")
-    elseif(CONFIG_IDF_TARGET_ESP32S3)
+    # This command builds the mocks.
+    # First, environment variable UNITY_DIR is set. This is necessary to prevent unity from looking in its own submodule
+    # which doesn't work in our CI yet...
+    # The rest is a straight forward call to cmock.rb, consult cmock's documentation for more information.
+    add_custom_command(
+        OUTPUT ${MOCK_OUTPUT}
+        DEPENDS ruby_found
+        COMMAND ${CMAKE_COMMAND} -E env "UNITY_DIR=${IDF_PATH}/components/unity/unity"
+            ruby
+            ${CMOCK_DIR}/lib/cmock.rb
+            -o${CMAKE_CURRENT_SOURCE_DIR}/mock/mock_config.yaml
+            ${MOCK_HEADERS}
+      )
+
+else()
+    if(BOOTLOADER_BUILD)
+        if(CONFIG_IDF_TARGET_ESP32)
+            # ESP32 Bootloader needs SPIUnlock from this file, but doesn't
+            # need other parts of this component
+            set(srcs "esp32/spi_flash_rom_patch.c")
+        elseif(CONFIG_IDF_TARGET_ESP32S2)
+            set(srcs "esp32s2/spi_flash_rom_patch.c")
+        elseif(CONFIG_IDF_TARGET_ESP32S3)
+            set(srcs "esp32s3/spi_flash_rom_patch.c")
+        else()
+            # but on other platforms no source files are needed for bootloader
+            set(srcs)
+        endif()
+        set(cache_srcs "")
+        set(priv_requires bootloader_support soc)
+    else()
+        set(cache_srcs
+            "cache_utils.c"
+            "flash_mmap.c"
+            "flash_ops.c"
+            "${IDF_TARGET}/flash_ops_${IDF_TARGET}.c"
+        )
+        set(srcs
+            "partition.c")
+
+        if(CONFIG_IDF_TARGET_ESP32)
+            list(APPEND srcs
+                "esp32/spi_flash_rom_patch.c")
+        elseif(CONFIG_IDF_TARGET_ESP32S2)
+            list(APPEND srcs
+                "esp32s2/spi_flash_rom_patch.c")
+        elseif(CONFIG_IDF_TARGET_ESP32S3)
+            list(APPEND srcs
+                "esp32s3/spi_flash_rom_patch.c")
+        endif()
+
+        # New implementation after IDF v4.0
         list(APPEND srcs
-            "esp32s3/spi_flash_rom_patch.c")
+            "spi_flash_chip_drivers.c"
+            "spi_flash_chip_generic.c"
+            "spi_flash_chip_issi.c"
+            "spi_flash_chip_mxic.c"
+            "spi_flash_chip_gd.c"
+            "spi_flash_chip_winbond.c"
+            "memspi_host_driver.c")
+
+        list(APPEND cache_srcs
+            "esp_flash_api.c"
+            "esp_flash_spi_init.c"
+            "spi_flash_os_func_app.c"
+            "spi_flash_os_func_noos.c")
+
+        list(APPEND srcs ${cache_srcs})
+        set(priv_requires bootloader_support app_update soc esp_ipc)
     endif()
 
-    # New implementation after IDF v4.0
-    list(APPEND srcs
-        "spi_flash_chip_drivers.c"
-        "spi_flash_chip_generic.c"
-        "spi_flash_chip_issi.c"
-        "spi_flash_chip_mxic.c"
-        "spi_flash_chip_gd.c"
-        "spi_flash_chip_winbond.c"
-        "memspi_host_driver.c")
-
-    list(APPEND cache_srcs
-        "esp_flash_api.c"
-        "esp_flash_spi_init.c"
-        "spi_flash_os_func_app.c"
-        "spi_flash_os_func_noos.c")
-
-    list(APPEND srcs ${cache_srcs})
-    set(priv_requires bootloader_support app_update soc esp_ipc)
-endif()
+    idf_component_register(SRCS "${srcs}"
+                        REQUIRES hal
+                        PRIV_REQUIRES "${priv_requires}"
+                        INCLUDE_DIRS include
+                        PRIV_INCLUDE_DIRS private_include
+                        LDFRAGMENTS linker.lf)
 
-idf_component_register(SRCS "${srcs}"
-                    REQUIRES hal
-                    PRIV_REQUIRES "${priv_requires}"
-                    INCLUDE_DIRS include
-                    PRIV_INCLUDE_DIRS private_include
-                    LDFRAGMENTS linker.lf)
+    # Avoid cache miss by unexpected inlineing when built by -Os
+    set_source_files_properties(${cache_srcs} PROPERTIES COMPILE_FLAGS
+        "-fno-inline-functions -fno-inline-small-functions -fno-inline-functions-called-once")
 
-# Avoid cache miss by unexpected inlineing when built by -Os
-set_source_files_properties(${cache_srcs} PROPERTIES COMPILE_FLAGS
-    "-fno-inline-functions -fno-inline-small-functions -fno-inline-functions-called-once")
+endif()

+ 1 - 37
components/spi_flash/include/esp_spi_flash.h

@@ -20,6 +20,7 @@
 #include <stddef.h>
 #include "esp_err.h"
 #include "sdkconfig.h"
+#include "esp_spi_flash_counters.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -419,43 +420,6 @@ extern const spi_flash_guard_funcs_t g_flash_guard_default_ops;
  */
 extern const spi_flash_guard_funcs_t g_flash_guard_no_os_ops;
 
-#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
-
-/**
- * Structure holding statistics for one type of operation
- */
-typedef struct {
-    uint32_t count;     // number of times operation was executed
-    uint32_t time;      // total time taken, in microseconds
-    uint32_t bytes;     // total number of bytes
-} spi_flash_counter_t;
-
-typedef struct {
-    spi_flash_counter_t read;
-    spi_flash_counter_t write;
-    spi_flash_counter_t erase;
-} spi_flash_counters_t;
-
-/**
- * @brief  Reset SPI flash operation counters
- */
-void spi_flash_reset_counters(void);
-
-/**
- * @brief  Print SPI flash operation counters
- */
-void spi_flash_dump_counters(void);
-
-/**
- * @brief  Return current SPI flash operation counters
- *
- * @return  pointer to the spi_flash_counters_t structure holding values
- *          of the operation counters
- */
-const spi_flash_counters_t* spi_flash_get_counters(void);
-
-#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS
-
 #ifdef __cplusplus
 }
 #endif

+ 65 - 0
components/spi_flash/include/esp_spi_flash_counters.h

@@ -0,0 +1,65 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// 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
+
+//     http://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 <stdint.h>
+#include <stdbool.h>
+#include "esp_err.h"
+#include "sdkconfig.h"
+
+#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Structure holding statistics for one type of operation
+ */
+typedef struct {
+    uint32_t count;     // number of times operation was executed
+    uint32_t time;      // total time taken, in microseconds
+    uint32_t bytes;     // total number of bytes
+} spi_flash_counter_t;
+
+typedef struct {
+    spi_flash_counter_t read;
+    spi_flash_counter_t write;
+    spi_flash_counter_t erase;
+} spi_flash_counters_t;
+
+/**
+ * @brief  Reset SPI flash operation counters
+ */
+void spi_flash_reset_counters(void);
+
+/**
+ * @brief  Print SPI flash operation counters
+ */
+void spi_flash_dump_counters(void);
+
+/**
+ * @brief  Return current SPI flash operation counters
+ *
+ * @return  pointer to the spi_flash_counters_t structure holding values
+ *          of the operation counters
+ */
+const spi_flash_counters_t* spi_flash_get_counters(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //CONFIG_SPI_FLASH_ENABLE_COUNTERS

+ 7 - 0
components/spi_flash/mock/mock_config.yaml

@@ -0,0 +1,7 @@
+        :cmock:
+          :plugins:
+            - expect
+            - expect_any_args
+            - return_thru_ptr
+            - array
+            - callback

+ 149 - 0
components/spi_flash/sim/stubs/esp_common/esp_err.h

@@ -0,0 +1,149 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// 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
+
+//     http://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 <stdint.h>
+#include <stdio.h>
+#include <assert.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int32_t esp_err_t;
+
+/* Definitions for error constants. */
+#define ESP_OK          0       /*!< esp_err_t value indicating success (no error) */
+#define ESP_FAIL        -1      /*!< Generic esp_err_t code indicating failure */
+
+#define ESP_ERR_NO_MEM              0x101   /*!< Out of memory */
+#define ESP_ERR_INVALID_ARG         0x102   /*!< Invalid argument */
+#define ESP_ERR_INVALID_STATE       0x103   /*!< Invalid state */
+#define ESP_ERR_INVALID_SIZE        0x104   /*!< Invalid size */
+#define ESP_ERR_NOT_FOUND           0x105   /*!< Requested resource not found */
+#define ESP_ERR_NOT_SUPPORTED       0x106   /*!< Operation or feature not supported */
+#define ESP_ERR_TIMEOUT             0x107   /*!< Operation timed out */
+#define ESP_ERR_INVALID_RESPONSE    0x108   /*!< Received response was invalid */
+#define ESP_ERR_INVALID_CRC         0x109   /*!< CRC or checksum was invalid */
+#define ESP_ERR_INVALID_VERSION     0x10A   /*!< Version was invalid */
+#define ESP_ERR_INVALID_MAC         0x10B   /*!< MAC address was invalid */
+
+#define ESP_ERR_WIFI_BASE           0x3000  /*!< Starting number of WiFi error codes */
+#define ESP_ERR_MESH_BASE           0x4000  /*!< Starting number of MESH error codes */
+#define ESP_ERR_FLASH_BASE          0x6000  /*!< Starting number of flash error codes */
+
+/**
+  * @brief Returns string for esp_err_t error codes
+  *
+  * This function finds the error code in a pre-generated lookup-table and
+  * returns its string representation.
+  *
+  * The function is generated by the Python script
+  * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t
+  * error is modified, created or removed from the IDF project.
+  *
+  * @param code esp_err_t error code
+  * @return string error message
+  */
+const char *esp_err_to_name(esp_err_t code);
+
+/**
+  * @brief Returns string for esp_err_t and system error codes
+  *
+  * This function finds the error code in a pre-generated lookup-table of
+  * esp_err_t errors and returns its string representation. If the error code
+  * is not found then it is attempted to be found among system errors.
+  *
+  * The function is generated by the Python script
+  * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t
+  * error is modified, created or removed from the IDF project.
+  *
+  * @param code esp_err_t error code
+  * @param[out] buf buffer where the error message should be written
+  * @param buflen Size of buffer buf. At most buflen bytes are written into the buf buffer (including the terminating null byte).
+  * @return buf containing the string error message
+  */
+const char *esp_err_to_name_r(esp_err_t code, char *buf, size_t buflen);
+
+/** @cond */
+void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression) __attribute__((noreturn));
+
+/** @cond */
+void _esp_error_check_failed_without_abort(esp_err_t rc, const char *file, int line, const char *function, const char *expression);
+
+#ifndef __ASSERT_FUNC
+/* This won't happen on IDF, which defines __ASSERT_FUNC in assert.h, but it does happen when building on the host which
+   uses /usr/include/assert.h or equivalent.
+*/
+#ifdef __ASSERT_FUNCTION
+#define __ASSERT_FUNC __ASSERT_FUNCTION /* used in glibc assert.h */
+#else
+#define __ASSERT_FUNC "??"
+#endif
+#endif
+/** @endcond */
+
+/**
+ * Macro which can be used to check the error code,
+ * and terminate the program in case the code is not ESP_OK.
+ * Prints the error code, error location, and the failed statement to serial output.
+ *
+ * Disabled if assertions are disabled.
+ */
+#ifdef NDEBUG
+#define ESP_ERROR_CHECK(x) do {                                         \
+        esp_err_t __err_rc = (x);                                       \
+        (void) sizeof(__err_rc);                                        \
+    } while(0)
+#elif defined(CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT)
+#define ESP_ERROR_CHECK(x) do {                                         \
+        esp_err_t __err_rc = (x);                                       \
+        if (__err_rc != ESP_OK) {                                       \
+            abort();                                                    \
+        }                                                               \
+    } while(0)
+#else
+#define ESP_ERROR_CHECK(x) do {                                         \
+        esp_err_t __err_rc = (x);                                       \
+        if (__err_rc != ESP_OK) {                                       \
+            _esp_error_check_failed(__err_rc, __FILE__, __LINE__,       \
+                                    __ASSERT_FUNC, #x);                 \
+        }                                                               \
+    } while(0)
+#endif
+
+/**
+ * Macro which can be used to check the error code. Prints the error code, error location, and the failed statement to
+ * serial output.
+ * In comparison with ESP_ERROR_CHECK(), this prints the same error message but isn't terminating the program.
+ */
+#ifdef NDEBUG
+#define ESP_ERROR_CHECK_WITHOUT_ABORT(x) ({                                         \
+        esp_err_t __err_rc = (x);                                                   \
+        __err_rc;                                                                   \
+    })
+#else
+#define ESP_ERROR_CHECK_WITHOUT_ABORT(x) ({                                         \
+        esp_err_t __err_rc = (x);                                                   \
+        if (__err_rc != ESP_OK) {                                                   \
+            _esp_error_check_failed_without_abort(__err_rc, __FILE__, __LINE__,     \
+                                    __ASSERT_FUNC, #x);                             \
+        }                                                                           \
+        __err_rc;                                                                   \
+    })
+#endif //NDEBUG
+
+#ifdef __cplusplus
+}
+#endif

+ 148 - 0
components/spi_flash/sim/stubs/soc/include/hal/spi_flash_types.h

@@ -0,0 +1,148 @@
+// Copyright 2010-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// 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
+//
+//     http://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
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Definition of a common transaction. Also holds the return value. */
+typedef struct {
+    uint8_t command;            ///< Command to send, always 8bits
+    uint8_t mosi_len;           ///< Output data length, in bytes
+    uint8_t miso_len;           ///< Input data length, in bytes
+    uint8_t address_bitlen;     ///< Length of address in bits, set to 0 if command does not need an address
+    uint32_t address;           ///< Address to perform operation on
+    const uint8_t *mosi_data;   ///< Output data to salve
+    uint8_t *miso_data;         ///< [out] Input data from slave, little endian
+} spi_flash_trans_t;
+
+/** @brief Mode used for reading from SPI flash */
+typedef enum {
+    SPI_FLASH_SLOWRD = 0, ///< Data read using single I/O, some limits on speed
+    SPI_FLASH_FASTRD, ///< Data read using single I/O, no limit on speed
+    SPI_FLASH_DOUT,   ///< Data read using dual I/O
+    SPI_FLASH_DIO,    ///< Both address & data transferred using dual I/O
+    SPI_FLASH_QOUT,   ///< Data read using quad I/O
+    SPI_FLASH_QIO,    ///< Both address & data transferred using quad I/O
+
+    SPI_FLASH_READ_MODE_MAX,    ///< The fastest io mode supported by the host is ``ESP_FLASH_READ_MODE_MAX-1``.
+} esp_flash_io_mode_t;
+
+struct spi_flash_host_driver_s;
+typedef struct spi_flash_host_driver_s spi_flash_host_driver_t;
+
+/** SPI Flash Host driver instance */
+typedef struct {
+    const struct spi_flash_host_driver_s* driver;  ///< Pointer to the implementation function table
+    // Implementations can wrap this structure into their own ones, and append other data here
+} spi_flash_host_inst_t ;
+
+/** Host driver configuration and context structure. */
+struct spi_flash_host_driver_s {
+    /**
+     * Configure the device-related register before transactions. This saves
+     * some time to re-configure those registers when we send continuously
+     */
+    esp_err_t (*dev_config)(spi_flash_host_inst_t *host);
+    /**
+     * Send an user-defined spi transaction to the device.
+     */
+    esp_err_t (*common_command)(spi_flash_host_inst_t *host, spi_flash_trans_t *t);
+    /**
+     * Read flash ID.
+     */
+    esp_err_t (*read_id)(spi_flash_host_inst_t *host, uint32_t *id);
+    /**
+     * Erase whole flash chip.
+     */
+    void (*erase_chip)(spi_flash_host_inst_t *host);
+    /**
+     * Erase a specific sector by its start address.
+     */
+    void (*erase_sector)(spi_flash_host_inst_t *host, uint32_t start_address);
+    /**
+     * Erase a specific block by its start address.
+     */
+    void (*erase_block)(spi_flash_host_inst_t *host, uint32_t start_address);
+    /**
+     * Read the status of the flash chip.
+     */
+    esp_err_t (*read_status)(spi_flash_host_inst_t *host, uint8_t *out_sr);
+    /**
+     * Disable write protection.
+     */
+    esp_err_t (*set_write_protect)(spi_flash_host_inst_t *host, bool wp);
+    /**
+     * Program a page of the flash. Check ``max_write_bytes`` for the maximum allowed writing length.
+     */
+    void (*program_page)(spi_flash_host_inst_t *host, const void *buffer, uint32_t address, uint32_t length);
+    /** Check whether given buffer can be directly used to write */
+    bool (*supports_direct_write)(spi_flash_host_inst_t *host, const void *p);
+    /**
+     * Slicer for write data. The `program_page` should be called iteratively with the return value
+     * of this function.
+     *
+     * @param address Beginning flash address to write
+     * @param len Length request to write
+     * @param align_addr Output of the aligned address to write to
+     * @param page_size Physical page size of the flash chip
+     * @return Length that can be actually written in one `program_page` call
+     */
+    int (*write_data_slicer)(spi_flash_host_inst_t *host, uint32_t address, uint32_t len, uint32_t *align_addr,
+                             uint32_t page_size);
+    /**
+     * Read data from the flash. Check ``max_read_bytes`` for the maximum allowed reading length.
+     */
+    esp_err_t (*read)(spi_flash_host_inst_t *host, void *buffer, uint32_t address, uint32_t read_len);
+    /** Check whether given buffer can be directly used to read */
+    bool (*supports_direct_read)(spi_flash_host_inst_t *host, const void *p);
+    /**
+     * Slicer for read data. The `read` should be called iteratively with the return value
+     * of this function.
+     *
+     * @param address Beginning flash address to read
+     * @param len Length request to read
+     * @param align_addr Output of the aligned address to read
+     * @param page_size Physical page size of the flash chip
+     * @return Length that can be actually read in one `read` call
+     */
+    int (*read_data_slicer)(spi_flash_host_inst_t *host, uint32_t address, uint32_t len, uint32_t *align_addr, uint32_t page_size);
+    /**
+     * Check whether the host is idle to perform new operations.
+     */
+    bool (*host_idle)(spi_flash_host_inst_t *host);
+    /**
+     * Configure the host to work at different read mode. Responsible to compensate the timing and set IO mode.
+     */
+    esp_err_t (*configure_host_io_mode)(spi_flash_host_inst_t *host, uint32_t command,
+                                        uint32_t addr_bitlen, int dummy_bitlen_base,
+                                        esp_flash_io_mode_t io_mode);
+    /**
+     *  Internal use, poll the HW until the last operation is done.
+     */
+    void (*poll_cmd_done)(spi_flash_host_inst_t *host);
+    /**
+     * For some host (SPI1), they are shared with a cache. When the data is
+     * modified, the cache needs to be flushed. Left NULL if not supported.
+     */
+    esp_err_t (*flush_cache)(spi_flash_host_inst_t* host, uint32_t addr, uint32_t size);
+};
+///Slowest io mode supported by ESP32, currently SlowRd
+#define SPI_FLASH_READ_MODE_MIN SPI_FLASH_SLOWRD
+
+#ifdef __cplusplus
+}
+#endif

+ 95 - 0
components/spi_flash/sim/stubs/spi_flash/esp_partition.h

@@ -0,0 +1,95 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// 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
+
+//     http://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.
+
+#ifndef __ESP_PARTITION_H__
+#define __ESP_PARTITION_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "esp_err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+    ESP_PARTITION_TYPE_APP = 0x00,       //!< Application partition type
+    ESP_PARTITION_TYPE_DATA = 0x01,      //!< Data partition type
+} esp_partition_type_t;
+
+typedef enum {
+    ESP_PARTITION_SUBTYPE_APP_FACTORY = 0x00,                                 //!< Factory application partition
+    ESP_PARTITION_SUBTYPE_APP_OTA_MIN = 0x10,                                 //!< Base for OTA partition subtypes
+    ESP_PARTITION_SUBTYPE_APP_OTA_0 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 0,  //!< OTA partition 0
+    ESP_PARTITION_SUBTYPE_APP_OTA_1 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 1,  //!< OTA partition 1
+    ESP_PARTITION_SUBTYPE_APP_OTA_2 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 2,  //!< OTA partition 2
+    ESP_PARTITION_SUBTYPE_APP_OTA_3 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 3,  //!< OTA partition 3
+    ESP_PARTITION_SUBTYPE_APP_OTA_4 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 4,  //!< OTA partition 4
+    ESP_PARTITION_SUBTYPE_APP_OTA_5 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 5,  //!< OTA partition 5
+    ESP_PARTITION_SUBTYPE_APP_OTA_6 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 6,  //!< OTA partition 6
+    ESP_PARTITION_SUBTYPE_APP_OTA_7 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 7,  //!< OTA partition 7
+    ESP_PARTITION_SUBTYPE_APP_OTA_8 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 8,  //!< OTA partition 8
+    ESP_PARTITION_SUBTYPE_APP_OTA_9 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 9,  //!< OTA partition 9
+    ESP_PARTITION_SUBTYPE_APP_OTA_10 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 10,//!< OTA partition 10
+    ESP_PARTITION_SUBTYPE_APP_OTA_11 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 11,//!< OTA partition 11
+    ESP_PARTITION_SUBTYPE_APP_OTA_12 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 12,//!< OTA partition 12
+    ESP_PARTITION_SUBTYPE_APP_OTA_13 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 13,//!< OTA partition 13
+    ESP_PARTITION_SUBTYPE_APP_OTA_14 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 14,//!< OTA partition 14
+    ESP_PARTITION_SUBTYPE_APP_OTA_15 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 15,//!< OTA partition 15
+    ESP_PARTITION_SUBTYPE_APP_OTA_MAX = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 16,//!< Max subtype of OTA partition
+    ESP_PARTITION_SUBTYPE_APP_TEST = 0x20,                                    //!< Test application partition
+
+    ESP_PARTITION_SUBTYPE_DATA_OTA = 0x00,                                    //!< OTA selection partition
+    ESP_PARTITION_SUBTYPE_DATA_PHY = 0x01,                                    //!< PHY init data partition
+    ESP_PARTITION_SUBTYPE_DATA_NVS = 0x02,                                    //!< NVS partition
+    ESP_PARTITION_SUBTYPE_DATA_COREDUMP = 0x03,                               //!< COREDUMP partition
+    ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS = 0x04,                               //!< Partition for NVS keys
+    ESP_PARTITION_SUBTYPE_DATA_EFUSE_EM = 0x05,                               //!< Partition for emulate eFuse bits
+
+    ESP_PARTITION_SUBTYPE_DATA_ESPHTTPD = 0x80,                               //!< ESPHTTPD partition
+    ESP_PARTITION_SUBTYPE_DATA_FAT = 0x81,                                    //!< FAT partition
+    ESP_PARTITION_SUBTYPE_DATA_SPIFFS = 0x82,                                 //!< SPIFFS partition
+
+    ESP_PARTITION_SUBTYPE_ANY = 0xff,                                         //!< Used to search for partitions with any subtype
+} esp_partition_subtype_t;
+
+/**
+ * @brief Opaque partition iterator type
+ */
+typedef struct esp_partition_iterator_opaque_* esp_partition_iterator_t;
+
+/**
+ * @brief partition information structure
+ *
+ * This is not the format in flash, that format is esp_partition_info_t.
+ *
+ * However, this is the format used by this API.
+ */
+typedef struct {
+    void* flash_chip;            /*!< SPI flash chip on which the partition resides */
+    esp_partition_type_t type;          /*!< partition type (app/data) */
+    esp_partition_subtype_t subtype;    /*!< partition subtype */
+    uint32_t address;                   /*!< starting address of the partition in flash */
+    uint32_t size;                      /*!< size of the partition, in bytes */
+    char label[17];                     /*!< partition label, zero-terminated ASCII string */
+    bool encrypted;                     /*!< flag is set to true if partition is encrypted */
+} esp_partition_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* __ESP_PARTITION_H__ */

+ 149 - 0
components/spi_flash/sim/stubs/xtensa/esp_attr.h

@@ -0,0 +1,149 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// 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
+
+//     http://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.
+#ifndef __ESP_ATTR_H__
+#define __ESP_ATTR_H__
+
+#include "sdkconfig.h"
+
+#define ROMFN_ATTR
+
+//Normally, the linker script will put all code and rodata in flash,
+//and all variables in shared RAM. These macros can be used to redirect
+//particular functions/variables to other memory regions.
+
+// Forces code into IRAM instead of flash
+#define IRAM_ATTR _SECTION_ATTR_IMPL(".iram1", __COUNTER__)
+
+// Forces data into DRAM instead of flash
+#define DRAM_ATTR _SECTION_ATTR_IMPL(".dram1", __COUNTER__)
+
+#ifdef CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
+// Forces data into IRAM instead of DRAM
+#define IRAM_DATA_ATTR __attribute__((section(".iram.data")))
+
+// Forces data into IRAM instead of DRAM and map it to coredump
+#define COREDUMP_IRAM_DATA_ATTR _SECTION_ATTR_IMPL(".iram.data.coredump", __COUNTER__)
+
+// Forces bss into IRAM instead of DRAM
+#define IRAM_BSS_ATTR __attribute__((section(".iram.bss")))
+#else
+#define COREDUMP_IRAM_DATA_ATTR
+#define IRAM_DATA_ATTR
+
+#define IRAM_BSS_ATTR
+#endif
+
+// Forces data to be 4 bytes aligned
+#define WORD_ALIGNED_ATTR __attribute__((aligned(4)))
+
+// Forces data to be placed to DMA-capable places
+#define DMA_ATTR WORD_ALIGNED_ATTR DRAM_ATTR
+
+// Forces a function to be inlined
+#define FORCE_INLINE_ATTR static inline __attribute__((always_inline))
+
+// Forces a string into DRAM instead of flash
+// Use as esp_rom_printf(DRAM_STR("Hello world!\n"));
+#define DRAM_STR(str) (__extension__({static const DRAM_ATTR char __c[] = (str); (const char *)&__c;}))
+
+// Forces code into RTC fast memory. See "docs/deep-sleep-stub.rst"
+#define RTC_IRAM_ATTR _SECTION_ATTR_IMPL(".rtc.text", __COUNTER__)
+
+#if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
+// Forces bss variable into external memory. "
+#define EXT_RAM_ATTR _SECTION_ATTR_IMPL(".ext_ram.bss", __COUNTER__)
+#else
+#define EXT_RAM_ATTR
+#endif
+
+// Forces data into RTC slow memory. See "docs/deep-sleep-stub.rst"
+// Any variable marked with this attribute will keep its value
+// during a deep sleep / wake cycle.
+#define RTC_DATA_ATTR _SECTION_ATTR_IMPL(".rtc.data", __COUNTER__)
+
+// Forces read-only data into RTC memory. See "docs/deep-sleep-stub.rst"
+#define RTC_RODATA_ATTR _SECTION_ATTR_IMPL(".rtc.rodata", __COUNTER__)
+
+// Allows to place data into RTC_SLOW memory.
+#define RTC_SLOW_ATTR _SECTION_ATTR_IMPL(".rtc.force_slow", __COUNTER__)
+
+// Allows to place data into RTC_FAST memory.
+#define RTC_FAST_ATTR _SECTION_ATTR_IMPL(".rtc.force_fast", __COUNTER__)
+
+// Forces data into noinit section to avoid initialization after restart.
+#define __NOINIT_ATTR _SECTION_ATTR_IMPL(".noinit", __COUNTER__)
+
+// Forces data into RTC slow memory of .noinit section.
+// Any variable marked with this attribute will keep its value
+// after restart or during a deep sleep / wake cycle.
+#define RTC_NOINIT_ATTR  _SECTION_ATTR_IMPL(".rtc_noinit", __COUNTER__)
+
+// Forces code into DRAM instead of flash and map it to coredump
+#define COREDUMP_DRAM_ATTR _SECTION_ATTR_IMPL(".dram1.coredump", __COUNTER__)
+
+// Forces data into RTC memory and map it to coredump
+#define COREDUMP_RTC_DATA_ATTR _SECTION_ATTR_IMPL(".rtc.coredump", __COUNTER__)
+
+// Allows to place data into RTC_FAST memory and map it to coredump
+#define COREDUMP_RTC_FAST_ATTR _SECTION_ATTR_IMPL(".rtc.fast.coredump", __COUNTER__)
+
+// Forces to not inline function
+#define NOINLINE_ATTR __attribute__((noinline))
+
+// This allows using enum as flags in C++
+// Format: FLAG_ATTR(flag_enum_t)
+#ifdef __cplusplus
+
+// Inline is required here to avoid multiple definition error in linker
+#define FLAG_ATTR_IMPL(TYPE, INT_TYPE) \
+FORCE_INLINE_ATTR constexpr TYPE operator~ (TYPE a) { return (TYPE)~(INT_TYPE)a; } \
+FORCE_INLINE_ATTR constexpr TYPE operator| (TYPE a, TYPE b) { return (TYPE)((INT_TYPE)a | (INT_TYPE)b); } \
+FORCE_INLINE_ATTR constexpr TYPE operator& (TYPE a, TYPE b) { return (TYPE)((INT_TYPE)a & (INT_TYPE)b); } \
+FORCE_INLINE_ATTR constexpr TYPE operator^ (TYPE a, TYPE b) { return (TYPE)((INT_TYPE)a ^ (INT_TYPE)b); } \
+FORCE_INLINE_ATTR constexpr TYPE operator>> (TYPE a, int b) { return (TYPE)((INT_TYPE)a >> b); } \
+FORCE_INLINE_ATTR constexpr TYPE operator<< (TYPE a, int b) { return (TYPE)((INT_TYPE)a << b); } \
+FORCE_INLINE_ATTR TYPE& operator|=(TYPE& a, TYPE b) { a = a | b; return a; } \
+FORCE_INLINE_ATTR TYPE& operator&=(TYPE& a, TYPE b) { a = a & b; return a; } \
+FORCE_INLINE_ATTR TYPE& operator^=(TYPE& a, TYPE b) { a = a ^ b; return a; } \
+FORCE_INLINE_ATTR TYPE& operator>>=(TYPE& a, int b) { a >>= b; return a; } \
+FORCE_INLINE_ATTR TYPE& operator<<=(TYPE& a, int b) { a <<= b; return a; }
+
+#define FLAG_ATTR_U32(TYPE) FLAG_ATTR_IMPL(TYPE, uint32_t)
+#define FLAG_ATTR FLAG_ATTR_U32
+
+#else
+#define FLAG_ATTR(TYPE)
+#endif
+
+// Implementation for a unique custom section
+//
+// This prevents gcc producing "x causes a section type conflict with y"
+// errors if two variables in the same source file have different linkage (maybe const & non-const) but are placed in the same custom section
+//
+// Using unique sections also means --gc-sections can remove unused
+// data with a custom section type set
+#define _SECTION_ATTR_IMPL(SECTION, COUNTER) __attribute__((section(SECTION "." _COUNTER_STRINGIFY(COUNTER))))
+
+#define _COUNTER_STRINGIFY(COUNTER) #COUNTER
+
+/* Use IDF_DEPRECATED attribute to mark anything deprecated from use in
+   ESP-IDF's own source code, but not deprecated for external users.
+*/
+#ifdef IDF_CI_BUILD
+#define IDF_DEPRECATED(REASON) __attribute__((deprecated(REASON)))
+#else
+#define IDF_DEPRECATED(REASON)
+#endif
+
+#endif /* __ESP_ATTR_H__ */

+ 20 - 6
components/unity/CMakeLists.txt

@@ -1,6 +1,9 @@
 set(srcs
-    "unity/src/unity.c"
-    "unity_port_esp32.c")
+    "unity/src/unity.c")
+
+set(includes
+    "include"
+    "unity/src")
 
 if(CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL)
     list(APPEND COMPONENT_PRIV_INCLUDEDIRS "include/priv")
@@ -12,13 +15,24 @@ endif()
 
 if(CONFIG_UNITY_ENABLE_FIXTURE)
     list(APPEND srcs "unity/extras/fixture/src/unity_fixture.c")
+    list(APPEND includes "unity/extras/fixture/src")
+endif()
+
+if(${IDF_TARGET} STREQUAL "linux")
+    message(STATUS "adding linux stuff...")
+    idf_component_get_property(spi_flash_dir spi_flash COMPONENT_DIR)
+    list(APPEND includes "${spi_flash_dir}/sim/stubs/esp_common")
+else()
+    list(APPEND srcs "unity_port_esp32.c")
 endif()
 
 idf_component_register(SRCS "${srcs}"
-                    INCLUDE_DIRS "include" "unity/src" "unity/extras/fixture/src")
+                    INCLUDE_DIRS ${includes})
 
-target_compile_definitions(${COMPONENT_LIB} PUBLIC
-    -DUNITY_INCLUDE_CONFIG_H
-)
+if(NOT ${IDF_TARGET} STREQUAL "linux")
+    target_compile_definitions(${COMPONENT_LIB} PUBLIC
+        -DUNITY_INCLUDE_CONFIG_H
+    )
+endif()
 
 target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-const-variable)

+ 5 - 0
examples/build_system/cmake/linux_host_app/CMakeLists.txt

@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+set(COMPONENTS main)
+project(linux_host_app)

+ 8 - 0
examples/build_system/cmake/linux_host_app/README.md

@@ -0,0 +1,8 @@
+| Supported Targets | Linux |
+| ----------------- | ----- |
+
+# Build
+`idf.py build` (sdkconfig.defaults sets the linux target by default)
+
+# Run
+`build/linux_host_app.elf`

+ 1 - 0
examples/build_system/cmake/linux_host_app/main/CMakeLists.txt

@@ -0,0 +1 @@
+idf_component_register(SRCS "linux_host_app.cpp")

+ 20 - 0
examples/build_system/cmake/linux_host_app/main/linux_host_app.cpp

@@ -0,0 +1,20 @@
+/* Hello World Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include "stdio.h"
+
+void app_main() {
+    printf("Hello, Host!\n");
+}
+
+int main(int argc, char **argv)
+{
+    app_main();
+    return 0;
+}

+ 2 - 0
examples/build_system/cmake/linux_host_app/sdkconfig.defaults

@@ -0,0 +1,2 @@
+CONFIG_IDF_TARGET="linux"
+CONFIG_CXX_EXCEPTIONS=y

+ 17 - 7
tools/cmake/build.cmake

@@ -151,12 +151,16 @@ function(__build_init idf_path)
         endif()
     endforeach()
 
-    # Set components required by all other components in the build
-    #
-    # - lwip is here so that #include <sys/socket.h> works without any special provisions
-    # - esp_hw_support is here for backward compatibility
-    set(requires_common cxx newlib freertos esp_hw_support heap log lwip soc hal esp_rom esp_common esp_system)
-    idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${requires_common}")
+
+    idf_build_get_property(target IDF_TARGET)
+    if(NOT target STREQUAL "linux")
+        # Set components required by all other components in the build
+        #
+        # - lwip is here so that #include <sys/socket.h> works without any special provisions
+        # - esp_hw_support is here for backward compatibility
+        set(requires_common cxx newlib freertos esp_hw_support heap log lwip soc hal esp_rom esp_common esp_system)
+        idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${requires_common}")
+    endif()
 
     __build_get_idf_git_revision()
     __kconfig_init()
@@ -397,7 +401,13 @@ macro(idf_build_process target)
     # Check for required Python modules
     __build_check_python()
 
-    idf_build_set_property(__COMPONENT_REQUIRES_COMMON ${target} APPEND)
+    idf_build_get_property(target IDF_TARGET)
+
+    if(NOT target STREQUAL "linux")
+        idf_build_set_property(__COMPONENT_REQUIRES_COMMON ${target} APPEND)
+    else()
+        idf_build_set_property(__COMPONENT_REQUIRES_COMMON "")
+    endif()
 
     # Perform early expansion of component CMakeLists.txt in CMake scripting mode.
     # It is here we retrieve the public and private requirements of each component.

+ 4 - 2
tools/cmake/component.cmake

@@ -463,7 +463,9 @@ function(idf_component_register)
     idf_build_get_property(compile_definitions COMPILE_DEFINITIONS GENERATOR_EXPRESSION)
     add_compile_options("${compile_definitions}")
 
-    list(REMOVE_ITEM common_reqs ${component_lib})
+    if(common_reqs) # check whether common_reqs exists, this may be the case in minimalistic host unit test builds
+        list(REMOVE_ITEM common_reqs ${component_lib})
+    endif()
     link_libraries(${common_reqs})
 
     idf_build_get_property(config_dir CONFIG_DIR)
@@ -475,7 +477,7 @@ function(idf_component_register)
         __component_add_include_dirs(${component_lib} "${__INCLUDE_DIRS}" PUBLIC)
         __component_add_include_dirs(${component_lib} "${__PRIV_INCLUDE_DIRS}" PRIVATE)
         __component_add_include_dirs(${component_lib} "${config_dir}" PUBLIC)
-        set_target_properties(${component_lib} PROPERTIES OUTPUT_NAME ${COMPONENT_NAME})
+        set_target_properties(${component_lib} PROPERTIES OUTPUT_NAME ${COMPONENT_NAME} LINKER_LANGUAGE C)
         __ldgen_add_component(${component_lib})
     else()
         add_library(${component_lib} INTERFACE)

+ 2 - 0
tools/cmake/dfu.cmake

@@ -9,6 +9,8 @@ function(__add_dfu_targets)
         set(dfu_pid "2")
     elseif("${target}" STREQUAL "esp32s3")
         set(dfu_pid "4")
+    elseif("${target}" STREQUAL "linux")
+        return()
     else()
         message(FATAL_ERROR "DFU PID unknown for ${target}")
     endif()

+ 8 - 0
tools/cmake/toolchain-linux.cmake

@@ -0,0 +1,8 @@
+set(CMAKE_SYSTEM_NAME Generic)
+
+set(CMAKE_C_COMPILER gcc)
+set(CMAKE_CXX_COMPILER g++)
+set(CMAKE_ASM_COMPILER gcc)
+
+set(CMAKE_C_FLAGS "-Wno-frame-address" CACHE STRING "C Compiler Base Flags")
+set(CMAKE_CXX_FLAGS "-Wno-frame-address" CACHE STRING "C++ Compiler Base Flags")

+ 2 - 1
tools/find_build_apps/common.py

@@ -289,12 +289,13 @@ class BuildSystem:
     Objects of these classes aren't instantiated, instead the class (type object) is used.
     """
     NAME = "undefined"
-    SUPPORTED_TARGETS_REGEX = re.compile(r'Supported [Tt]argets((?:[\s|]+(?:ESP[0-9A-Z\-]+))+)')
+    SUPPORTED_TARGETS_REGEX = re.compile(r'Supported [Tt]argets((?:[ |]+(?:[0-9a-zA-Z\-]+))+)')
 
     FORMAL_TO_USUAL = {
         'ESP32': 'esp32',
         'ESP32-S2': 'esp32s2',
         'ESP32-S3': 'esp32s3',
+        'Linux': 'linux',
     }
 
     @classmethod

+ 3 - 1
tools/gen_esp_err_to_name.py

@@ -46,7 +46,9 @@ ignore_files = [os.path.join('components', 'mdns', 'test_afl_fuzz_host', 'esp32_
                 ]
 
 # add directories here which should not be parsed, this is a tuple since it will be used with *.startswith()
-ignore_dirs = (os.path.join('examples'), os.path.join('components', 'cmock', 'CMock', 'test'))
+ignore_dirs = (os.path.join('examples'),
+               os.path.join('components', 'cmock', 'CMock', 'test'),
+               os.path.join('components', 'spi_flash', 'sim'))
 
 # macros from here have higher priorities in case of collisions
 priority_headers = [os.path.join('components', 'esp_common', 'include', 'esp_err.h')]

+ 1 - 1
tools/idf_py_actions/constants.py

@@ -38,4 +38,4 @@ GENERATORS = collections.OrderedDict([
 
 SUPPORTED_TARGETS = ["esp32", "esp32s2"]
 
-PREVIEW_TARGETS = ["esp32s3"]
+PREVIEW_TARGETS = ["esp32s3", "linux"]