mirror of https://github.com/ARMmbed/mbed-os.git
Add filesystem recovery tests
Add tests for filesystem resilience and wear leveling. These tests take shared filesystem code and simulate different scenarios while this code is running. Information on the new tests can be found below. mbed-littlefs-tests-filesystem_recovery-resilience: Tests that after every block device operation the filesystem is in a well defined state. mbed-littlefs-tests-filesystem_recovery-wear_leveling: Tests that the littlefs correctly handles when flash is exhausted by using a simulated block device until there are no free good blocks. Note - This patch also adds several new block devices for testing. These will eventually be moved into mbed-os.pull/5538/head
parent
7eaf61c047
commit
bb155adc16
|
@ -0,0 +1,80 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017-2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mbed.h"
|
||||||
|
#include "unity.h"
|
||||||
|
#include "utest.h"
|
||||||
|
#include "test_env.h"
|
||||||
|
|
||||||
|
#include "atomic_usage.h"
|
||||||
|
#include "ObservingBlockDevice.h"
|
||||||
|
#include "LittleFileSystem.h"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace utest::v1;
|
||||||
|
|
||||||
|
#define TEST_CYCLES 10
|
||||||
|
#define TEST_BD_SIZE (16 * 1024)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the filesystem is valid after every change
|
||||||
|
*
|
||||||
|
* This test is to ensure that littlefs contains a valid filesystem at
|
||||||
|
* all times. This property is required for handling unexpected power
|
||||||
|
* loss.
|
||||||
|
*/
|
||||||
|
void test_resilience()
|
||||||
|
{
|
||||||
|
HeapBlockDevice bd(TEST_BD_SIZE);
|
||||||
|
|
||||||
|
// Setup the test
|
||||||
|
setup_atomic_operations(&bd, true);
|
||||||
|
|
||||||
|
// Run check on every write operation
|
||||||
|
ObservingBlockDevice observer(&bd);
|
||||||
|
observer.attach(check_atomic_operations);
|
||||||
|
|
||||||
|
// Perform operations
|
||||||
|
printf("Performing %i operations on flash\n", TEST_CYCLES);
|
||||||
|
for (int i = 1; i <= TEST_CYCLES; i++) {
|
||||||
|
int64_t ret = perform_atomic_operations(&observer);
|
||||||
|
TEST_ASSERT_EQUAL(i, ret);
|
||||||
|
}
|
||||||
|
printf("No errors detected\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Case cases[] = {
|
||||||
|
Case("test resilience", test_resilience),
|
||||||
|
};
|
||||||
|
|
||||||
|
utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
|
||||||
|
{
|
||||||
|
GREENTEA_SETUP(20, "default_auto");
|
||||||
|
return greentea_test_setup_handler(number_of_cases);
|
||||||
|
}
|
||||||
|
|
||||||
|
Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Harness::run(specification);
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017-2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mbed.h"
|
||||||
|
#include "unity.h"
|
||||||
|
#include "utest.h"
|
||||||
|
#include "test_env.h"
|
||||||
|
|
||||||
|
#include "atomic_usage.h"
|
||||||
|
#include "ExhaustibleBlockDevice.h"
|
||||||
|
#include "LittleFileSystem.h"
|
||||||
|
|
||||||
|
using namespace utest::v1;
|
||||||
|
|
||||||
|
#define ERASE_CYCLES 20
|
||||||
|
#define TEST_BD_SIZE (8 * 1024)
|
||||||
|
|
||||||
|
static uint32_t test_wear_leveling_size(uint32_t bd_size)
|
||||||
|
{
|
||||||
|
HeapBlockDevice hbd(bd_size, 1, 1, 512);
|
||||||
|
ExhaustibleBlockDevice ebd(&hbd, ERASE_CYCLES);
|
||||||
|
|
||||||
|
printf("Testing size %lu\n", bd_size);
|
||||||
|
setup_atomic_operations(&ebd, true);
|
||||||
|
|
||||||
|
int64_t cycles = 0;
|
||||||
|
while (true) {
|
||||||
|
int64_t ret = perform_atomic_operations(&ebd);
|
||||||
|
check_atomic_operations(&ebd);
|
||||||
|
if (-1 == ret) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cycles++;
|
||||||
|
TEST_ASSERT_EQUAL(cycles, ret);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" Simulated flash lasted %lli cylces\n", cycles);
|
||||||
|
return cycles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that storage life is proportional to storage size
|
||||||
|
*
|
||||||
|
* This test is to ensure that littlefs is properly handling wear
|
||||||
|
* leveling. It does this by creating a simulated flash block device
|
||||||
|
* which can be worn out and then using it until it is exhausted.
|
||||||
|
* It then doubles the size of the block device and runs the same
|
||||||
|
* test. If the block device with twice the size lasts at least
|
||||||
|
* twice as long then the test passes.
|
||||||
|
*/
|
||||||
|
void test_wear_leveling()
|
||||||
|
{
|
||||||
|
uint32_t cycles_1 = test_wear_leveling_size(TEST_BD_SIZE * 1);
|
||||||
|
uint32_t cycles_2 = test_wear_leveling_size(TEST_BD_SIZE * 2);
|
||||||
|
TEST_ASSERT(cycles_2 > cycles_1 * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Case cases[] = {
|
||||||
|
Case("test wear leveling", test_wear_leveling),
|
||||||
|
};
|
||||||
|
|
||||||
|
utest::v1::status_t greentea_test_setup(const size_t number_of_cases)
|
||||||
|
{
|
||||||
|
GREENTEA_SETUP(60, "default_auto");
|
||||||
|
return greentea_test_setup_handler(number_of_cases);
|
||||||
|
}
|
||||||
|
|
||||||
|
Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Harness::run(specification);
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017 ARM Limited
|
||||||
|
*
|
||||||
|
* 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 "ExhaustibleBlockDevice.h"
|
||||||
|
|
||||||
|
|
||||||
|
ExhaustibleBlockDevice::ExhaustibleBlockDevice(BlockDevice *bd, uint32_t erase_cycles)
|
||||||
|
: _bd(bd), _erase_array(NULL), _erase_cycles(erase_cycles)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ExhaustibleBlockDevice::~ExhaustibleBlockDevice()
|
||||||
|
{
|
||||||
|
delete[] _erase_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExhaustibleBlockDevice::init()
|
||||||
|
{
|
||||||
|
int err = _bd->init();
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_erase_array) {
|
||||||
|
// can only be allocated after initialization
|
||||||
|
_erase_array = new uint32_t[_bd->size() / _bd->get_erase_size()];
|
||||||
|
for (size_t i = 0; i < _bd->size() / _bd->get_erase_size(); i++) {
|
||||||
|
_erase_array[i] = _erase_cycles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExhaustibleBlockDevice::deinit()
|
||||||
|
{
|
||||||
|
// _erase_array is lazily cleaned up in destructor to allow
|
||||||
|
// data to live across de/reinitialization
|
||||||
|
return _bd->deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExhaustibleBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
return _bd->read(buffer, addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExhaustibleBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
MBED_ASSERT(is_valid_program(addr, size));
|
||||||
|
|
||||||
|
if (_erase_array[addr / get_erase_size()] == 0) {
|
||||||
|
// TODO possibly something more destructive here
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _bd->program(buffer, addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExhaustibleBlockDevice::erase(bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
MBED_ASSERT(is_valid_erase(addr, size));
|
||||||
|
|
||||||
|
// use an erase cycle
|
||||||
|
if (_erase_array[addr / get_erase_size()] > 0) {
|
||||||
|
_erase_array[addr / get_erase_size()] -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_erase_array[addr / get_erase_size()] == 0) {
|
||||||
|
// TODO possibly something more destructive here
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _bd->erase(addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ExhaustibleBlockDevice::get_read_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_read_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ExhaustibleBlockDevice::get_program_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_program_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ExhaustibleBlockDevice::get_erase_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_erase_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ExhaustibleBlockDevice::size() const
|
||||||
|
{
|
||||||
|
return _bd->size();
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef MBED_EXHAUSTIBLE_BLOCK_DEVICE_H
|
||||||
|
#define MBED_EXHAUSTIBLE_BLOCK_DEVICE_H
|
||||||
|
|
||||||
|
#include "BlockDevice.h"
|
||||||
|
#include "mbed.h"
|
||||||
|
|
||||||
|
|
||||||
|
/** Heap backed block device which simulates failures
|
||||||
|
*
|
||||||
|
* Similar to heap block device but sectors wear out and are no longer programmable
|
||||||
|
* after a configurable number of cycles.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ExhaustibleBlockDevice : public BlockDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Lifetime of the block device
|
||||||
|
*
|
||||||
|
* @param bd Block device to back the ExhaustibleBlockDevice
|
||||||
|
* @param erase_cycles Number of erase cycles before failure
|
||||||
|
*/
|
||||||
|
ExhaustibleBlockDevice(BlockDevice *bd, uint32_t erase_cycles);
|
||||||
|
virtual ~ExhaustibleBlockDevice();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of erase cycles remaining on a block
|
||||||
|
*
|
||||||
|
* @param addr Any address in the block being queried for erase cycles
|
||||||
|
* @return Number of erase cycles remaining
|
||||||
|
*/
|
||||||
|
uint32_t get_erase_cycles(bd_addr_t addr) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the number of erase cycles before failure
|
||||||
|
*
|
||||||
|
* @param addr Any address in the block being queried for erase cycles
|
||||||
|
* @param cycles Erase cycles before the block malfunctions
|
||||||
|
*/
|
||||||
|
void set_erase_cycles(bd_addr_t addr, uint32_t cycles);
|
||||||
|
|
||||||
|
/** Initialize a block device
|
||||||
|
*
|
||||||
|
* @return 0 on success or a negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int init();
|
||||||
|
|
||||||
|
/** Deinitialize a block device
|
||||||
|
*
|
||||||
|
* @return 0 on success or a negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int deinit();
|
||||||
|
|
||||||
|
/** Read blocks from a block device
|
||||||
|
*
|
||||||
|
* @param buffer Buffer to read blocks into
|
||||||
|
* @param addr Address of block to begin reading from
|
||||||
|
* @param size Size to read in bytes, must be a multiple of read block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int read(void *buffer, bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Program blocks to a block device
|
||||||
|
*
|
||||||
|
* The blocks must have been erased prior to being programmed
|
||||||
|
*
|
||||||
|
* @param buffer Buffer of data to write to blocks
|
||||||
|
* @param addr Address of block to begin writing to
|
||||||
|
* @param size Size to write in bytes, must be a multiple of program block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Erase blocks on a block device
|
||||||
|
*
|
||||||
|
* The state of an erased block is undefined until it has been programmed
|
||||||
|
*
|
||||||
|
* @param addr Address of block to begin erasing
|
||||||
|
* @param size Size to erase in bytes, must be a multiple of erase block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int erase(bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Get the size of a readable block
|
||||||
|
*
|
||||||
|
* @return Size of a readable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_read_size() const;
|
||||||
|
|
||||||
|
/** Get the size of a programable block
|
||||||
|
*
|
||||||
|
* @return Size of a programable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_program_size() const;
|
||||||
|
|
||||||
|
/** Get the size of a eraseable block
|
||||||
|
*
|
||||||
|
* @return Size of a eraseable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_erase_size() const;
|
||||||
|
|
||||||
|
/** Get the total size of the underlying device
|
||||||
|
*
|
||||||
|
* @return Size of the underlying device in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t size() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BlockDevice *_bd;
|
||||||
|
uint32_t *_erase_array;
|
||||||
|
uint32_t _erase_cycles;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,96 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017-2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ObservingBlockDevice.h"
|
||||||
|
#include "ReadOnlyBlockDevice.h"
|
||||||
|
|
||||||
|
|
||||||
|
ObservingBlockDevice::ObservingBlockDevice(BlockDevice *bd)
|
||||||
|
: _bd(bd)
|
||||||
|
{
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
ObservingBlockDevice::~ObservingBlockDevice()
|
||||||
|
{
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
void ObservingBlockDevice::attach(Callback<void(BlockDevice *)> cb)
|
||||||
|
{
|
||||||
|
_change = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ObservingBlockDevice::init()
|
||||||
|
{
|
||||||
|
return _bd->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ObservingBlockDevice::deinit()
|
||||||
|
{
|
||||||
|
return _bd->deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ObservingBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
return _bd->read(buffer, addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ObservingBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
int res = _bd->program(buffer, addr, size);
|
||||||
|
if (_change) {
|
||||||
|
ReadOnlyBlockDevice dev(_bd);
|
||||||
|
_change(&dev);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ObservingBlockDevice::erase(bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
int res = _bd->erase(addr, size);
|
||||||
|
if (_change) {
|
||||||
|
ReadOnlyBlockDevice dev(_bd);
|
||||||
|
_change(&dev);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ObservingBlockDevice::get_read_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_read_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ObservingBlockDevice::get_program_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_program_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ObservingBlockDevice::get_erase_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_erase_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ObservingBlockDevice::size() const
|
||||||
|
{
|
||||||
|
return _bd->size();
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017-2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef MBED_OBSERVING_BLOCK_DEVICE_H
|
||||||
|
#define MBED_OBSERVING_BLOCK_DEVICE_H
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "BlockDevice.h"
|
||||||
|
#include "PlatformMutex.h"
|
||||||
|
|
||||||
|
using namespace mbed;
|
||||||
|
|
||||||
|
|
||||||
|
class ObservingBlockDevice : public BlockDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Lifetime of the block device
|
||||||
|
*
|
||||||
|
* @param size Size of the Block Device in bytes
|
||||||
|
* @param block Block size in bytes
|
||||||
|
*/
|
||||||
|
ObservingBlockDevice(BlockDevice *bd);
|
||||||
|
virtual ~ObservingBlockDevice();
|
||||||
|
|
||||||
|
/** Attach a callback which is called on change
|
||||||
|
*
|
||||||
|
* @param cb Function to call on filesystem change (erase or program)
|
||||||
|
*/
|
||||||
|
void attach(Callback<void(BlockDevice *)> cb);
|
||||||
|
|
||||||
|
/** Initialize a block device
|
||||||
|
*
|
||||||
|
* @return 0 on success or a negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int init();
|
||||||
|
|
||||||
|
/** Deinitialize a block device
|
||||||
|
*
|
||||||
|
* @return 0 on success or a negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int deinit();
|
||||||
|
|
||||||
|
/** Read blocks from a block device
|
||||||
|
*
|
||||||
|
* @param buffer Buffer to read blocks into
|
||||||
|
* @param addr Address of block to begin reading from
|
||||||
|
* @param size Size to read in bytes, must be a multiple of read block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int read(void *buffer, bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Program blocks to a block device
|
||||||
|
*
|
||||||
|
* The blocks must have been erased prior to being programmed
|
||||||
|
*
|
||||||
|
* @param buffer Buffer of data to write to blocks
|
||||||
|
* @param addr Address of block to begin writing to
|
||||||
|
* @param size Size to write in bytes, must be a multiple of program block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Erase blocks on a block device
|
||||||
|
*
|
||||||
|
* The state of an erased block is undefined until it has been programmed
|
||||||
|
*
|
||||||
|
* @param addr Address of block to begin erasing
|
||||||
|
* @param size Size to erase in bytes, must be a multiple of erase block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int erase(bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Get the size of a readable block
|
||||||
|
*
|
||||||
|
* @return Size of a readable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_read_size() const;
|
||||||
|
|
||||||
|
/** Get the size of a programable block
|
||||||
|
*
|
||||||
|
* @return Size of a programable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_program_size() const;
|
||||||
|
|
||||||
|
/** Get the size of a eraseable block
|
||||||
|
*
|
||||||
|
* @return Size of a eraseable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_erase_size() const;
|
||||||
|
|
||||||
|
/** Get the total size of the underlying device
|
||||||
|
*
|
||||||
|
* @return Size of the underlying device in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t size() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BlockDevice *_bd;
|
||||||
|
Callback<void(BlockDevice *)> _change;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017-2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ReadOnlyBlockDevice.h"
|
||||||
|
#include "mbed_error.h"
|
||||||
|
|
||||||
|
|
||||||
|
ReadOnlyBlockDevice::ReadOnlyBlockDevice(BlockDevice *bd)
|
||||||
|
: _bd(bd)
|
||||||
|
{
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlyBlockDevice::~ReadOnlyBlockDevice()
|
||||||
|
{
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadOnlyBlockDevice::init()
|
||||||
|
{
|
||||||
|
return _bd->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadOnlyBlockDevice::deinit()
|
||||||
|
{
|
||||||
|
return _bd->deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadOnlyBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
return _bd->read(buffer, addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadOnlyBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
error("ReadOnlyBlockDevice::program() not allowed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadOnlyBlockDevice::erase(bd_addr_t addr, bd_size_t size)
|
||||||
|
{
|
||||||
|
error("ReadOnlyBlockDevice::erase() not allowed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ReadOnlyBlockDevice::get_read_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_read_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ReadOnlyBlockDevice::get_program_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_program_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ReadOnlyBlockDevice::get_erase_size() const
|
||||||
|
{
|
||||||
|
return _bd->get_erase_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bd_size_t ReadOnlyBlockDevice::size() const
|
||||||
|
{
|
||||||
|
return _bd->size();
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017-2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef MBED_READ_ONLY_BLOCK_DEVICE_H
|
||||||
|
#define MBED_READ_ONLY_BLOCK_DEVICE_H
|
||||||
|
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "BlockDevice.h"
|
||||||
|
#include "PlatformMutex.h"
|
||||||
|
|
||||||
|
using namespace mbed;
|
||||||
|
|
||||||
|
|
||||||
|
class ReadOnlyBlockDevice : public BlockDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Lifetime of the block device
|
||||||
|
*
|
||||||
|
* @param size Size of the Block Device in bytes
|
||||||
|
* @param block Block size in bytes
|
||||||
|
*/
|
||||||
|
ReadOnlyBlockDevice(BlockDevice *bd);
|
||||||
|
virtual ~ReadOnlyBlockDevice();
|
||||||
|
|
||||||
|
/** Initialize a block device
|
||||||
|
*
|
||||||
|
* @return 0 on success or a negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int init();
|
||||||
|
|
||||||
|
/** Deinitialize a block device
|
||||||
|
*
|
||||||
|
* @return 0 on success or a negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int deinit();
|
||||||
|
|
||||||
|
/** Read blocks from a block device
|
||||||
|
*
|
||||||
|
* @param buffer Buffer to read blocks into
|
||||||
|
* @param addr Address of block to begin reading from
|
||||||
|
* @param size Size to read in bytes, must be a multiple of read block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int read(void *buffer, bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Program blocks to a block device
|
||||||
|
*
|
||||||
|
* The blocks must have been erased prior to being programmed
|
||||||
|
*
|
||||||
|
* @param buffer Buffer of data to write to blocks
|
||||||
|
* @param addr Address of block to begin writing to
|
||||||
|
* @param size Size to write in bytes, must be a multiple of program block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int program(const void *buffer, bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Erase blocks on a block device
|
||||||
|
*
|
||||||
|
* The state of an erased block is undefined until it has been programmed
|
||||||
|
*
|
||||||
|
* @param addr Address of block to begin erasing
|
||||||
|
* @param size Size to erase in bytes, must be a multiple of erase block size
|
||||||
|
* @return 0 on success, negative error code on failure
|
||||||
|
*/
|
||||||
|
virtual int erase(bd_addr_t addr, bd_size_t size);
|
||||||
|
|
||||||
|
/** Get the size of a readable block
|
||||||
|
*
|
||||||
|
* @return Size of a readable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_read_size() const;
|
||||||
|
|
||||||
|
/** Get the size of a programable block
|
||||||
|
*
|
||||||
|
* @return Size of a programable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_program_size() const;
|
||||||
|
|
||||||
|
/** Get the size of a eraseable block
|
||||||
|
*
|
||||||
|
* @return Size of a eraseable block in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t get_erase_size() const;
|
||||||
|
|
||||||
|
/** Get the total size of the underlying device
|
||||||
|
*
|
||||||
|
* @return Size of the underlying device in bytes
|
||||||
|
*/
|
||||||
|
virtual bd_size_t size() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BlockDevice *_bd;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,680 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017-2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains code which performs various atomic operations using
|
||||||
|
* littlefs. It is intended for use in tests and test applications to
|
||||||
|
* validate that the defined behavior below is being met.
|
||||||
|
*
|
||||||
|
* # Defined behavior
|
||||||
|
* - A file rename is atomic (Note - rename can be used to replace a file)
|
||||||
|
* - Atomic file rename tested by setup/perform/check_file_rename
|
||||||
|
* - Atomic file replace tested by setup/perform/check_file_rename_replace
|
||||||
|
* - A directory rename is atomic (Note - rename can be used to replace an empty directory)
|
||||||
|
* - Tested by setup/perform/check_directory_rename
|
||||||
|
* - Directory create is atomic
|
||||||
|
* - Directory delete is atomic
|
||||||
|
* - File create is atomic
|
||||||
|
* - File delete is atomic
|
||||||
|
* - File contents are atomically written on close
|
||||||
|
* - Tested by setup/perform/check_file_change_contents
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "mbed.h"
|
||||||
|
#include "greentea-client/test_env.h"
|
||||||
|
#include "unity.h"
|
||||||
|
#include "utest.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "ObservingBlockDevice.h"
|
||||||
|
#include "LittleFileSystem.h"
|
||||||
|
#include "ExhaustibleBlockDevice.h"
|
||||||
|
|
||||||
|
#include "atomic_usage.h"
|
||||||
|
|
||||||
|
#define DEBUG(...)
|
||||||
|
#define DEBUG_CHECK(...)
|
||||||
|
#define BUFFER_SIZE 64
|
||||||
|
// Version is written to a file and is used
|
||||||
|
// to determine if a reformat is required
|
||||||
|
#define ATOMIC_USAGE_VERSION 1
|
||||||
|
|
||||||
|
#define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0]))
|
||||||
|
|
||||||
|
#define TEST_ASSERT_OR_EXIT(condition) \
|
||||||
|
TEST_ASSERT(condition); if (!(condition)) {error("Assert failed");}
|
||||||
|
|
||||||
|
#define TEST_ASSERT_EQUAL_OR_EXIT(expected, actual) \
|
||||||
|
TEST_ASSERT_EQUAL(expected, actual); if ((int64_t)(expected) != (int64_t)(actual)) {error("Assert failed");}
|
||||||
|
|
||||||
|
using namespace utest::v1;
|
||||||
|
|
||||||
|
typedef void (*test_function_t)(LittleFileSystem *fs);
|
||||||
|
typedef bool (*test_function_bool_t)(LittleFileSystem *fs);
|
||||||
|
|
||||||
|
struct TestEntry {
|
||||||
|
const char *name;
|
||||||
|
test_function_t setup;
|
||||||
|
test_function_bool_t perform;
|
||||||
|
test_function_t check;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to the file while checking for error conditions
|
||||||
|
*
|
||||||
|
* @param file File to write to
|
||||||
|
* @param data Data to write
|
||||||
|
* @param size Size of data to write
|
||||||
|
* @return true if flash has been exhausted, false otherwise
|
||||||
|
*/
|
||||||
|
static bool file_write(File *file, uint8_t *data, uint32_t size)
|
||||||
|
{
|
||||||
|
int res = file->write(data, size);
|
||||||
|
if (-ENOSPC == res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(size, res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write padding data of the given size
|
||||||
|
*
|
||||||
|
* @param file Pointer to the file to write to
|
||||||
|
* @param padding Value to pad
|
||||||
|
* @param size Size to pad
|
||||||
|
* @return true if flash has been exhausted, false otherwise
|
||||||
|
*/
|
||||||
|
static bool file_pad(File *file, char padding, uint32_t size)
|
||||||
|
{
|
||||||
|
uint8_t buf[BUFFER_SIZE];
|
||||||
|
memset(buf, padding, sizeof(buf));
|
||||||
|
|
||||||
|
while (size > 0) {
|
||||||
|
uint32_t write_size = sizeof(buf) <= size ? sizeof(buf) : size;
|
||||||
|
if (file_write(file, buf, write_size)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
size -= write_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Similar to fscanf but uses and mbed file
|
||||||
|
*
|
||||||
|
* @param file File to scan from
|
||||||
|
* @param format Format string of values to read
|
||||||
|
* @return the number of arguments read
|
||||||
|
*/
|
||||||
|
static int file_scanf(File *file, const char *format, ...)
|
||||||
|
{
|
||||||
|
uint8_t buf[BUFFER_SIZE];
|
||||||
|
va_list args;
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
|
||||||
|
int res = file->read(buf, sizeof(buf) - 1);
|
||||||
|
TEST_ASSERT_OR_EXIT(res >= 0);
|
||||||
|
|
||||||
|
va_start (args, format);
|
||||||
|
int count = vsscanf((char*)buf, format, args);
|
||||||
|
va_end (args);
|
||||||
|
TEST_ASSERT_OR_EXIT(count >= 0);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Similar to fprintf but uses and mbed file
|
||||||
|
*
|
||||||
|
* @param file File to print to
|
||||||
|
* @param format Format string of values to write
|
||||||
|
* @return size written to file or -1 on out of space
|
||||||
|
*/
|
||||||
|
static int file_printf(File *file, const char *format, ...)
|
||||||
|
{
|
||||||
|
uint8_t buf[BUFFER_SIZE];
|
||||||
|
va_list args;
|
||||||
|
va_start (args, format);
|
||||||
|
int size = vsprintf((char*)buf, format, args);
|
||||||
|
va_end (args);
|
||||||
|
TEST_ASSERT_OR_EXIT((size >= 0) && (size <= (int)sizeof(buf)));
|
||||||
|
|
||||||
|
if (file_write(file, buf, size)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const char FILE_RENAME_A[] = "file_to_rename_a.txt";
|
||||||
|
static const char FILE_RENAME_B[] = "file_to_rename_b.txt";
|
||||||
|
static const char FILE_RENAME_CONTENTS[] = "Test contents for the file to be renamed";
|
||||||
|
static const int FILE_RENAME_LEN = strlen(FILE_RENAME_CONTENTS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup for the file rename test
|
||||||
|
*
|
||||||
|
* Create file FILE_RENAME_A with contents FILE_RENAME_CONTENTS.
|
||||||
|
*/
|
||||||
|
static void setup_file_rename(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG("setup_file_rename()\n");
|
||||||
|
|
||||||
|
File file;
|
||||||
|
|
||||||
|
int res = file.open(fs, FILE_RENAME_A, O_WRONLY | O_CREAT);
|
||||||
|
DEBUG(" open result %i\n", res);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
res = file.write(FILE_RENAME_CONTENTS, FILE_RENAME_LEN);
|
||||||
|
DEBUG(" write result %i\n", res);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(FILE_RENAME_LEN, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the file name to either FILE_RENAME_A or FILE_RENAME_B
|
||||||
|
*/
|
||||||
|
static bool perform_file_rename(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG("perform_file_rename()\n");
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
int res = fs->stat(FILE_RENAME_A, &st);
|
||||||
|
const char *src = (0 == res) ? FILE_RENAME_A : FILE_RENAME_B;
|
||||||
|
const char *dst = (0 == res) ? FILE_RENAME_B : FILE_RENAME_A;
|
||||||
|
|
||||||
|
DEBUG(" stat result %i\n", res);
|
||||||
|
TEST_ASSERT_OR_EXIT((res == -ENOENT) || (0 == res));
|
||||||
|
|
||||||
|
DEBUG(" Renaming %s to %s\n", src, dst);
|
||||||
|
res = fs->rename(src, dst);
|
||||||
|
if (-ENOSPC == res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the file rename is in a good state
|
||||||
|
*
|
||||||
|
* Check that there is only one file and that file contains the correct
|
||||||
|
* contents.
|
||||||
|
*
|
||||||
|
* Allowed states:
|
||||||
|
* - File FILE_RENAME_A exists with contents and FILE_RENAME_B does not
|
||||||
|
* - File FILE_RENAME_B exists with contents and FILE_RENAME_A does not
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void check_file_rename(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
|
||||||
|
int files = 0;
|
||||||
|
int valids = 0;
|
||||||
|
const char * const filenames[] = {FILE_RENAME_A, FILE_RENAME_B};
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
File file;
|
||||||
|
if (0 == file.open(fs, filenames[i], O_RDONLY)) {
|
||||||
|
uint8_t buf[BUFFER_SIZE];
|
||||||
|
files++;
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
int res = file.read(buf, FILE_RENAME_LEN);
|
||||||
|
if (res != FILE_RENAME_LEN) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (memcmp(buf, FILE_RENAME_CONTENTS, FILE_RENAME_LEN) != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
valids++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(1, files);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(1, valids);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const char FILE_RENAME_REPLACE[] = "rename_replace_file.txt";
|
||||||
|
static const char FILE_RENAME_REPLACE_NEW[] = "new_rename_replace_file.txt";
|
||||||
|
static const char FILE_RENAME_REPLACE_FMT[] = "file replace count: %lu\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the file FILE_RENAME_REPLACE with initial contents
|
||||||
|
*
|
||||||
|
* Create an write an initial count of 0 to the file.
|
||||||
|
*/
|
||||||
|
static void setup_file_rename_replace(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG("setup_file_rename_replace()\n");
|
||||||
|
File file;
|
||||||
|
|
||||||
|
// Write out initial count
|
||||||
|
|
||||||
|
int res = file.open(fs, FILE_RENAME_REPLACE, O_WRONLY | O_CREAT);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
uint32_t count = 0;
|
||||||
|
uint8_t buf[BUFFER_SIZE];
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
const int length = sprintf((char*)buf, FILE_RENAME_REPLACE_FMT, count);
|
||||||
|
TEST_ASSERT_OR_EXIT(length > 0);
|
||||||
|
|
||||||
|
res = file.write(buf, length);
|
||||||
|
DEBUG(" write result %i\n", res);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(length, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomically increment the count in FILE_RENAME_REPLACE using a rename
|
||||||
|
*/
|
||||||
|
bool perform_file_rename_replace(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG("perform_file_rename_replace()\n");
|
||||||
|
File file;
|
||||||
|
|
||||||
|
// Read in previous count
|
||||||
|
|
||||||
|
int res = file.open(fs, FILE_RENAME_REPLACE, O_RDONLY);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
uint64_t count;
|
||||||
|
int args_read = file_scanf(&file, FILE_RENAME_REPLACE_FMT, &count);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(1, args_read);
|
||||||
|
|
||||||
|
res = file.close();
|
||||||
|
if (-ENOSPC == res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL(0, res);
|
||||||
|
|
||||||
|
// Write out new count
|
||||||
|
|
||||||
|
count++;
|
||||||
|
|
||||||
|
res = file.open(fs, FILE_RENAME_REPLACE_NEW, O_WRONLY | O_CREAT);
|
||||||
|
if (-ENOSPC == res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
if (file_printf(&file, FILE_RENAME_REPLACE_FMT, count) <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = file.close();
|
||||||
|
if (-ENOSPC == res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL(0, res);
|
||||||
|
|
||||||
|
// Rename file
|
||||||
|
|
||||||
|
res = fs->rename(FILE_RENAME_REPLACE_NEW, FILE_RENAME_REPLACE);
|
||||||
|
if (-ENOSPC == res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
DEBUG(" count %llu -> %llu\n", count - 1, count);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that FILE_RENAME_REPLACE always has a valid count
|
||||||
|
*
|
||||||
|
* Allowed states:
|
||||||
|
* - FILE_RENAME_REPLACE exists with valid contents
|
||||||
|
*/
|
||||||
|
static void check_file_rename_replace(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG_CHECK("check_file_rename_replace()\n");
|
||||||
|
File file;
|
||||||
|
|
||||||
|
// Read in previous count
|
||||||
|
|
||||||
|
int res = file.open(fs, FILE_RENAME_REPLACE, O_RDONLY);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
uint64_t count;
|
||||||
|
int args_read = file_scanf(&file, FILE_RENAME_REPLACE_FMT, &count);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(1, args_read);
|
||||||
|
DEBUG_CHECK(" count %llu\n", count);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const char DIRECTORY_RENAME_A[] = "dir_a";
|
||||||
|
static const char DIRECTORY_RENAME_B[] = "dir_b";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create DIRECTORY_RENAME_A with initial contents
|
||||||
|
*/
|
||||||
|
static void setup_directory_rename(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG("setup_directory_rename()\n");
|
||||||
|
|
||||||
|
int res = fs->mkdir(DIRECTORY_RENAME_A, 0777);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the directory name from either DIRECTORY_RENAME_A or DIRECTORY_RENAME_B to the other
|
||||||
|
*/
|
||||||
|
static bool perform_directory_rename(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG("perform_directory_rename()\n");
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
int res = fs->stat(DIRECTORY_RENAME_A, &st);
|
||||||
|
const char *src = (0 == res) ? DIRECTORY_RENAME_A : DIRECTORY_RENAME_B;
|
||||||
|
const char *dst = (0 == res) ? DIRECTORY_RENAME_B : DIRECTORY_RENAME_A;
|
||||||
|
|
||||||
|
DEBUG(" stat result %i\n", res);
|
||||||
|
TEST_ASSERT_OR_EXIT((res == -ENOENT) || (0 == res));
|
||||||
|
|
||||||
|
DEBUG(" Renaming %s to %s\n", src, dst);
|
||||||
|
res = fs->rename(src, dst);
|
||||||
|
if (-ENOSPC == res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the directory name from either DIRECTORY_RENAME_A or DIRECTORY_RENAME_B to the other
|
||||||
|
*
|
||||||
|
* Allowed states:
|
||||||
|
* - DIRECTORY_RENAME_A exists with valid contents and DIRECTORY_RENAME_B does not exist
|
||||||
|
* - DIRECTORY_RENAME_B exists with valid contents and DIRECTORY_RENAME_A does not exist
|
||||||
|
*/
|
||||||
|
static void check_directory_rename(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG_CHECK("check_directory_rename()\n");
|
||||||
|
|
||||||
|
static const char *directory_names[] = {
|
||||||
|
DIRECTORY_RENAME_A,
|
||||||
|
DIRECTORY_RENAME_B
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t directories = 0;
|
||||||
|
for (size_t i = 0; i < ARRAY_LENGTH(directory_names); i++) {
|
||||||
|
Dir dir;
|
||||||
|
int res = dir.open(fs, directory_names[i]);
|
||||||
|
TEST_ASSERT_OR_EXIT((-ENOENT == res) || (0 == res));
|
||||||
|
if (0 == res) {
|
||||||
|
directories++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(1, directories);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const char CHANGE_CONTENTS_NAME[] = "file_changing_contents.txt";
|
||||||
|
static const char CHANGE_CONTENTS_FILL = ' ';
|
||||||
|
static const uint32_t BLOCK_SIZE = 512;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create file CHANGE_CONTENTS_NAME with initial contents
|
||||||
|
*
|
||||||
|
* File contains three blocks of data each which start
|
||||||
|
* with a count.
|
||||||
|
*/
|
||||||
|
static void setup_file_change_contents(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG("setup_file_change_contents()\n");
|
||||||
|
|
||||||
|
File file;
|
||||||
|
int res = file.open(fs, CHANGE_CONTENTS_NAME, O_WRONLY | O_CREAT);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
for (int count = 1; count <= 3; count++) {
|
||||||
|
int size = file_printf(&file, "%lu\n", count);
|
||||||
|
TEST_ASSERT_OR_EXIT(size >= 0);
|
||||||
|
|
||||||
|
bool dead = file_pad(&file, CHANGE_CONTENTS_FILL, BLOCK_SIZE - size);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(false, dead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomically increment the counts in the file CHANGE_CONTENTS_NAME
|
||||||
|
*
|
||||||
|
* Read in the current counts, increment them and then write them
|
||||||
|
* back in non-sequential order.
|
||||||
|
*/
|
||||||
|
static bool perform_file_change_contents(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG("perform_file_change_contents()\n");
|
||||||
|
File file;
|
||||||
|
|
||||||
|
int res = file.open(fs, CHANGE_CONTENTS_NAME, O_RDWR);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
// Read in values
|
||||||
|
uint32_t values[3];
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
file.seek(i * BLOCK_SIZE);
|
||||||
|
int args_read = file_scanf(&file, "%lu\n", &values[i]);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(1, args_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment values
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
values[i]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write values out of order
|
||||||
|
int i;
|
||||||
|
i = 0;
|
||||||
|
file.seek(i * BLOCK_SIZE);
|
||||||
|
if (file_printf(&file, "%lu\n", values[i]) <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
DEBUG(" value[%i]: %lu -> %lu\n", i, values[i] - 1, values[i]);
|
||||||
|
|
||||||
|
i = 2;
|
||||||
|
file.seek(i * BLOCK_SIZE);
|
||||||
|
if (file_printf(&file, "%lu\n", values[i]) <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
DEBUG(" value[%i]: %lu -> %lu\n", i, values[i] - 1, values[i]);
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
file.seek(i * BLOCK_SIZE);
|
||||||
|
if (file_printf(&file, "%lu\n", values[i]) <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
DEBUG(" value[%i]: %lu -> %lu\n", i, values[i] - 1, values[i]);
|
||||||
|
|
||||||
|
res = file.close();
|
||||||
|
if (-ENOSPC == res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL(0, res);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the directory name from either DIRECTORY_RENAME_A or DIRECTORY_RENAME_B to the other
|
||||||
|
*
|
||||||
|
* Allowed states:
|
||||||
|
* - CHANGE_CONTENTS_NAME exists and contains 3 counts which are in order
|
||||||
|
*/
|
||||||
|
static void check_file_change_contents(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
DEBUG_CHECK("check_file_change_contents()\n");
|
||||||
|
File file;
|
||||||
|
|
||||||
|
int res = file.open(fs, CHANGE_CONTENTS_NAME, O_RDONLY);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
// Read in values
|
||||||
|
uint32_t values[3];
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
file.seek(i * BLOCK_SIZE);
|
||||||
|
int args_read = file_scanf(&file, "%lu\n", &values[i]);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(1, args_read);
|
||||||
|
DEBUG_CHECK(" value[%i]: %lu\n", i, values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(values[0] + 1, values[1]);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(values[1] + 1, values[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const TestEntry atomic_test_entries[] = {
|
||||||
|
{"File rename", setup_file_rename, perform_file_rename, check_file_rename},
|
||||||
|
{"File rename replace", setup_file_rename_replace, perform_file_rename_replace, check_file_rename_replace},
|
||||||
|
{"Directory rename", setup_directory_rename, perform_directory_rename, check_directory_rename},
|
||||||
|
{"File change contents", setup_file_change_contents, perform_file_change_contents, check_file_change_contents},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char FILE_SETUP_COMPLETE[] = "setup_complete.txt";
|
||||||
|
static const char FILE_SETUP_COMPLETE_FMT[] = "Test version: %lu\n";
|
||||||
|
|
||||||
|
static bool format_required(BlockDevice *bd)
|
||||||
|
{
|
||||||
|
LittleFileSystem fs("fs");
|
||||||
|
|
||||||
|
if (fs.mount(bd) != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if setup complete file exists
|
||||||
|
File file;
|
||||||
|
int res = file.open(&fs, FILE_SETUP_COMPLETE, O_RDONLY);
|
||||||
|
if (res != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read contents of setup complete file
|
||||||
|
uint8_t buf[BUFFER_SIZE];
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
int size_read = file.read(buf, sizeof(buf) - 1);
|
||||||
|
if (size_read <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the test version
|
||||||
|
uint32_t version = 0;
|
||||||
|
res = sscanf((char*)buf, FILE_SETUP_COMPLETE_FMT, &version);
|
||||||
|
if (res != 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ATOMIC_USAGE_VERSION != version) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup file exists and is the correct version
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void format(BlockDevice *bd)
|
||||||
|
{
|
||||||
|
LittleFileSystem fs("fs");
|
||||||
|
|
||||||
|
int res = fs.format(bd);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
res = fs.mount(bd);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_LENGTH(atomic_test_entries); i++) {
|
||||||
|
atomic_test_entries[i].setup(&fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
File file;
|
||||||
|
res = file.open(&fs, FILE_SETUP_COMPLETE, O_CREAT | O_WRONLY);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
int size = file_printf(&file, FILE_SETUP_COMPLETE_FMT, (uint32_t)ATOMIC_USAGE_VERSION);
|
||||||
|
TEST_ASSERT_OR_EXIT(size >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t get_cycle_count(LittleFileSystem *fs)
|
||||||
|
{
|
||||||
|
File file;
|
||||||
|
|
||||||
|
int res = file.open(fs, FILE_RENAME_REPLACE, O_RDONLY);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(0, res);
|
||||||
|
|
||||||
|
uint64_t count = 0;
|
||||||
|
int args_read = file_scanf(&file, FILE_RENAME_REPLACE_FMT, &count);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(1, args_read);
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
return (int64_t)count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setup_atomic_operations(BlockDevice *bd, bool force_rebuild)
|
||||||
|
{
|
||||||
|
if (force_rebuild || format_required(bd)) {
|
||||||
|
format(bd);
|
||||||
|
TEST_ASSERT_EQUAL_OR_EXIT(false, format_required(bd));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t perform_atomic_operations(BlockDevice *bd)
|
||||||
|
{
|
||||||
|
LittleFileSystem fs("fs");
|
||||||
|
bool out_of_space = false;
|
||||||
|
|
||||||
|
fs.mount(bd);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_LENGTH(atomic_test_entries); i++) {
|
||||||
|
out_of_space |= atomic_test_entries[i].perform(&fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t cycle_count = get_cycle_count(&fs);
|
||||||
|
|
||||||
|
fs.unmount();
|
||||||
|
|
||||||
|
if (out_of_space) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return cycle_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_atomic_operations(BlockDevice *bd)
|
||||||
|
{
|
||||||
|
LittleFileSystem fs("fs");
|
||||||
|
fs.mount(bd);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_LENGTH(atomic_test_entries); i++) {
|
||||||
|
atomic_test_entries[i].check(&fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.unmount();
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* mbed Microcontroller Library
|
||||||
|
* Copyright (c) 2017 ARM Limited
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef MBED_ATOMIC_USAGE_H
|
||||||
|
#define MBED_ATOMIC_USAGE_H
|
||||||
|
|
||||||
|
#include "BlockDevice.h"
|
||||||
|
#include "mbed.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the given block device to test littlefs atomic operations
|
||||||
|
*
|
||||||
|
* Format the blockdevice with a littlefs filesystem and create
|
||||||
|
* the files and directories required to test atomic operations.
|
||||||
|
*
|
||||||
|
* @param bd Block device format and setup
|
||||||
|
* @param force_rebuild Force a reformat even if the device is already setup
|
||||||
|
* @return true if the block device was formatted, false otherwise
|
||||||
|
* @note utest asserts are used to detect fatal errors so utest must be
|
||||||
|
* initialized before calling this function.
|
||||||
|
*/
|
||||||
|
bool setup_atomic_operations(BlockDevice *bd, bool force_rebuild);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a set of atomic littlefs operations on the block device
|
||||||
|
*
|
||||||
|
* Mount the block device as a littlefs filesystem and a series of
|
||||||
|
* atomic operations on it. Since the operations performed are atomic
|
||||||
|
* the file system will always be in a well defined state. The block
|
||||||
|
* device must have been setup by calling ::setup_atomic_operations.
|
||||||
|
*
|
||||||
|
* @param bd Block device to perform the operations on
|
||||||
|
* @return -1 if flash is exhausted, otherwise the cycle count on the fs
|
||||||
|
* @note utest asserts are used to detect fatal errors so utest must be
|
||||||
|
* initialized before calling this function.
|
||||||
|
*/
|
||||||
|
int64_t perform_atomic_operations(BlockDevice *bd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the littlefs image on the block device is in a good state
|
||||||
|
*
|
||||||
|
* Mount the block device as a littlefs filesystem and check the files
|
||||||
|
* and directories to ensure they are valid. Since all the operations
|
||||||
|
* performed are atomic the filesystem should always be in a good
|
||||||
|
* state.
|
||||||
|
*
|
||||||
|
* @param bd Block device to check
|
||||||
|
* @note This function does not change the contents of the block device
|
||||||
|
* @note utest asserts are used to detect fatal errors so utest must be
|
||||||
|
* initialized before calling this function.
|
||||||
|
*/
|
||||||
|
void check_atomic_operations(BlockDevice *bd);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue