Merge pull request #5538 from geky/littlefs-staging

Integrate littlefs into mbed OS
pull/5536/merge
Martin Kojtal 2017-12-01 08:15:26 +00:00 committed by GitHub
commit 2e1c2a1cdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 15359 additions and 41 deletions

View File

@ -1,44 +1,163 @@
python:
- "2.7"
script:
- mkdir BUILD
# Assert that the Doxygen build produced no warnings.
# The strange command below asserts that the Doxygen command had an
# output of zero length
- |
doxygen doxyfile_options 2>&1 | tee BUILD/doxygen.out && [ ! -s BUILD/doxygen.out ]
# Assert that all binary libraries are named correctly
# The strange command below asserts that there are exactly 0 libraries that do
# not start with lib
- |
find "(" -name "*.a" -or -name "*.ar" ")" -and -not -name "lib*" | tee BUILD/badlibs | sed -e "s/^/Bad library name found: /" && [ ! -s BUILD/badlibs ]
# Assert that all assebler files are named correctly
# The strange command below asserts that there are exactly 0 libraries that do
# end with .s
- |
find -name "*.s" | tee BUILD/badasm | sed -e "s/^/Bad Assembler file name found: /" && [ ! -s BUILD/badasm ]
- make -C events/equeue test clean
- PYTHONPATH=. coverage run -a -m pytest tools/test
- python2 tools/test/pylint.py
- coverage run -a tools/project.py -S
- python2 tools/build_travis.py
- coverage html
after_success:
- coveralls
env:
global:
- >
STATUS=$'curl -so/dev/null --user $MBED_BOT --request POST
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT}
--data @- << DATA\n{
"state": "$0",
"description": "$1",
"context": "travis-ci/$NAME",
"target_url": "https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$TRAVIS_JOB_ID"
}\nDATA'
before_install:
- bash -c "$STATUS" pending "Local $NAME testing is in progress"
# Make sure pipefail
- set -o pipefail
# Setup ppa to make sure arm-none-eabi-gcc is correct version
- sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa
- sudo add-apt-repository -y ppa:libreoffice/libreoffice-4-2
- sudo apt-get update -qq
- sudo apt-get install -qq gcc-arm-embedded doxygen --force-yes
# Print versions we use
- arm-none-eabi-gcc --version
- python --version
- doxygen --version
install:
- pip install --user -r requirements.txt
- pip install --user pytest
- pip install --user pylint
- pip install --user hypothesis
- pip install --user mock
- pip install --user coverage
- pip install --user coveralls
after_success:
- bash -c "$STATUS" success "Local $NAME testing has passed"
after_failure:
- bash -c "$STATUS" failure "Local $NAME testing has failed"
matrix:
include:
- python: '2.7'
env:
- NAME=tools
install:
# Install dependencies
- sudo apt-get install gcc-arm-embedded doxygen
- pip install --user -r requirements.txt
- pip install --user pytest
- pip install --user pylint
- pip install --user hypothesis
- pip install --user mock
- pip install --user coverage
- pip install --user coveralls
# Print versions we use
- arm-none-eabi-gcc --version
- python --version
- doxygen --version
before_script:
# Create BUILD directory for tests
- mkdir BUILD
script:
# Assert that the Doxygen build produced no warnings.
# The strange command below asserts that the Doxygen command had an
# output of zero length
- |
doxygen doxyfile_options 2>&1 | tee BUILD/doxygen.out && [ ! -s BUILD/doxygen.out ]
# Assert that all binary libraries are named correctly
# The strange command below asserts that there are exactly 0 libraries that do
# not start with lib
- |
find "(" -name "*.a" -or -name "*.ar" ")" -and -not -name "lib*" | tee BUILD/badlibs | sed -e "s/^/Bad library name found: /" && [ ! -s BUILD/badlibs ]
# Assert that all assebler files are named correctly
# The strange command below asserts that there are exactly 0 libraries that do
# end with .s
- |
find -name "*.s" | tee BUILD/badasm | sed -e "s/^/Bad Assembler file name found: /" && [ ! -s BUILD/badasm ]
# Run local testing on tools
# Note: These take ~40 minutes to run
- PYTHONPATH=. coverage run -a -m pytest tools/test
- python2 tools/test/pylint.py
- coverage run -a tools/project.py -S | sed -n '/^Total/p'
# - python2 -u tools/build_travis.py | sed -n '/^Executing/p'
- coverage html
after_success:
# Coverage for tools
- coveralls
# Report success since we have overridden default behaviour
- bash -c "$STATUS" success "Local $NAME testing has passed"
- python: '2.7'
env:
- NAME=events
- EVENTS=events
install:
# Install dependencies
- sudo apt-get install gcc-arm-embedded
- pip install --user -r requirements.txt
# Print versions we use
- arm-none-eabi-gcc --version
- gcc --version
- python --version
script:
# Check that example compiles
- sed -n '/``` cpp/,/```/{/```$/Q;/```/d;p;}' $EVENTS/README.md > main.cpp
- python tools/make.py -t GCC_ARM -m K64F --source=. --build=BUILD/K64F/GCC_ARM -j0
# Run local equeue tests
- make -C $EVENTS/equeue test
- python: '2.7'
env:
- NAME=littlefs
- LITTLEFS=features/filesystem/littlefs
install:
# Install dependencies
- sudo apt-get install gcc-arm-embedded fuse libfuse-dev
- pip install --user -r requirements.txt
- git clone https://github.com/armmbed/spiflash-driver.git
# Print versions
- arm-none-eabi-gcc --version
- gcc --version
- python --version
- fusermount --version
before_script:
# Setup and patch littlefs-fuse
- git clone https://github.com/geky/littlefs-fuse littlefs_fuse
- echo '*' > littlefs_fuse/.mbedignore
- rm -rf littlefs_fuse/littlefs/*
- cp -r $(git ls-tree --name-only HEAD $LITTLEFS/littlefs/) littlefs_fuse/littlefs
# Create file-backed disk
- mkdir MOUNT
- sudo chmod a+rw /dev/loop0
- dd if=/dev/zero bs=512 count=2048 of=DISK
- losetup /dev/loop0 DISK
script:
# Check that example compiles
- sed -n '/``` c++/,/```/{/```/d;p;}' $LITTLEFS/README.md > main.cpp
- python tools/make.py -t GCC_ARM -m K82F --source=. --build=BUILD/K82F/GCC_ARM -j0
# Run local littlefs tests
- CFLAGS="-Wno-format" make -C$LITTLEFS/littlefs test QUIET=1
# Run local littlefs tests with set of variations
- CFLAGS="-Wno-format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64" make -C$LITTLEFS/littlefs test QUIET=1
- CFLAGS="-Wno-format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make -C$LITTLEFS/littlefs test QUIET=1
- CFLAGS="-Wno-format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make -C$LITTLEFS/littlefs test QUIET=1
- CFLAGS="-Wno-format -DLFS_BLOCK_COUNT=1023" make -C$LITTLEFS/littlefs test QUIET=1
- CFLAGS="-Wno-format -DLFS_LOOKAHEAD=2048" make -C$LITTLEFS/littlefs test QUIET=1
# Self-hosting littlefs fuzz test with littlefs-fuse
- make -Clittlefs_fuse
- littlefs_fuse/lfs --format /dev/loop0
- littlefs_fuse/lfs /dev/loop0 MOUNT
- ls MOUNT
- mkdir MOUNT/littlefs
- cp -r $(git ls-tree --name-only HEAD $LITTLEFS/littlefs/) MOUNT/littlefs
- ls MOUNT/littlefs
- CFLAGS="-Wno-format" make -CMOUNT/littlefs -B test_dirs QUIET=1
- python: '2.7'
env:
- NAME=mbed2
install:
# Install dependencies
- sudo apt-get install gcc-arm-embedded
- pip install --user -r requirements.txt
# Print versions we use
- arm-none-eabi-gcc --version
- python --version
before_script:
# Create BUILD directory for tests
- mkdir BUILD
script:
# Run local mbed 2 testing
# Note: These take ~40 minutes to run
- python2 -u tools/build_travis.py | sed -n '/^Executing/p'

View File

@ -0,0 +1,108 @@
/* 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"
#include "mbed.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();
}

View File

@ -0,0 +1,134 @@
/* 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"
/** 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 programmable block
*
* @return Size of a programmable block in bytes
*/
virtual bd_size_t get_program_size() const;
/** Get the size of a erasable block
*
* @return Size of a erasable 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

View File

@ -0,0 +1,97 @@
/* 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"
#include "mbed.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();
}

View File

@ -0,0 +1,120 @@
/* 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 "BlockDevice.h"
#include "PlatformMutex.h"
#include "Callback.h"
class ObservingBlockDevice : public BlockDevice
{
public:
/** Lifetime of the block device
*
* @param bd Block device to observe
*/
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(mbed::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 programmable block
*
* @return Size of a programmable block in bytes
*/
virtual bd_size_t get_program_size() const;
/** Get the size of a erasable block
*
* @return Size of a erasable 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;
mbed::Callback<void(BlockDevice *)> _change;
};
#endif

View File

@ -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();
}

View File

@ -0,0 +1,112 @@
/* 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 "BlockDevice.h"
#include "PlatformMutex.h"
class ReadOnlyBlockDevice : public BlockDevice
{
public:
/** Lifetime of the block device
*
* @param bd Block device to wrap as read only
*/
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 programmable block
*
* @return Size of a programmable block in bytes
*/
virtual bd_size_t get_program_size() const;
/** Get the size of a erasable block
*
* @return Size of a erasable 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

View File

@ -0,0 +1,3 @@
littlefs/emubd/
littlefs/tests/
TESTS/util

View File

@ -0,0 +1,70 @@
script:
# Check that example compiles
- sed -n '/``` c++/,/```/{/```/d; p;}' README.md > main.cpp
- PYTHONPATH=mbed-os python mbed-os/tools/make.py -t GCC_ARM -m K82F
--source=. --build=BUILD/K82F/GCC_ARM -j0
# Check that tests compile
- rm -rf main.cpp BUILD
- PYTHONPATH=mbed-os python mbed-os/tools/test.py -t GCC_ARM -m K82F
--source=. --build=BUILD/TESTS/K82F/GCC_ARM -j0
-n 'tests*'
# Run littlefs functional tests
- CFLAGS="-Wno-format" make -Clittlefs test QUIET=1
# Run littlefs functional tests with different configurations
# Note: r/w size of 64 is default in mbed
- CFLAGS="-Wno-format -DLFS_READ_SIZE=64 -DLFS_PROG_SIZE=64"
make -Clittlefs test QUIET=1
- CFLAGS="-Wno-format -DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1"
make -Clittlefs test QUIET=1
- CFLAGS="-Wno-format -DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512"
make -Clittlefs test QUIET=1
- CFLAGS="-Wno-format -DLFS_BLOCK_COUNT=1023"
make -Clittlefs test QUIET=1
- CFLAGS="-Wno-format -DLFS_LOOKAHEAD=2048"
make -Clittlefs test QUIET=1
# Self-host with littlefs-fuse for fuzz test
- CFLAGS="-Wno-format" make -C littlefs-fuse
- littlefs-fuse/lfs --format /dev/loop0
- littlefs-fuse/lfs /dev/loop0 mount
- ls mount
- mkdir mount/littlefs
- cp -r $(git ls-tree --name-only HEAD littlefs/) mount/littlefs
- cd mount/littlefs
- ls
- CFLAGS="-Wno-format" make -B test_dirs QUIET=1
install:
# Get arm-none-eabi-gcc
- sudo add-apt-repository -y ppa:terry.guo/gcc-arm-embedded
- sudo apt-get update -qq
- sudo apt-get install -qq gcc-arm-none-eabi --force-yes
# Get dependencies
- git clone https://github.com/armmbed/mbed-os.git
- git clone https://github.com/armmbed/spiflash-driver.git
# Install python dependencies
- pip install --user -r mbed-os/requirements.txt
# Install littlefs-fuse and dependencies
- sudo apt-get install libfuse-dev
- git clone https://github.com/geky/littlefs-fuse
# Check versions
- fusermount -V
- arm-none-eabi-gcc --version
- python --version
- gcc --version
before_script:
# Patch littlefs-fuse
- rm -rf littlefs-fuse/littlefs/*
- cp -r $(git ls-tree --name-only HEAD littlefs/) littlefs-fuse/littlefs
- echo '*' > littlefs-fuse/.mbedignore
# Create file-backed disk
- mkdir mount
- sudo chmod a+rw /dev/loop0
- dd if=/dev/zero bs=512 count=2048 of=disk
- losetup /dev/loop0 disk

View File

@ -0,0 +1,165 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.

View File

@ -0,0 +1,511 @@
/* 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 "mbed.h"
#include "LittleFileSystem.h"
#include "errno.h"
extern "C" {
#include "lfs.h"
#include "lfs_util.h"
}
////// Conversion functions //////
static int lfs_toerror(int err)
{
switch (err) {
case LFS_ERR_OK: return 0;
case LFS_ERR_IO: return -EIO;
case LFS_ERR_NOENT: return -ENOENT;
case LFS_ERR_EXIST: return -EEXIST;
case LFS_ERR_NOTDIR: return -ENOTDIR;
case LFS_ERR_ISDIR: return -EISDIR;
case LFS_ERR_INVAL: return -EINVAL;
case LFS_ERR_NOSPC: return -ENOSPC;
case LFS_ERR_NOMEM: return -ENOMEM;
default: return err;
}
}
static int lfs_fromflags(int flags)
{
return (
(((flags & 3) == O_RDONLY) ? LFS_O_RDONLY : 0) |
(((flags & 3) == O_WRONLY) ? LFS_O_WRONLY : 0) |
(((flags & 3) == O_RDWR) ? LFS_O_RDWR : 0) |
((flags & O_CREAT) ? LFS_O_CREAT : 0) |
((flags & O_EXCL) ? LFS_O_EXCL : 0) |
((flags & O_TRUNC) ? LFS_O_TRUNC : 0) |
((flags & O_APPEND) ? LFS_O_APPEND : 0));
}
static int lfs_fromwhence(int whence)
{
switch (whence) {
case SEEK_SET: return LFS_SEEK_SET;
case SEEK_CUR: return LFS_SEEK_CUR;
case SEEK_END: return LFS_SEEK_END;
default: return whence;
}
}
static int lfs_tomode(int type)
{
int mode = S_IRWXU | S_IRWXG | S_IRWXO;
switch (type) {
case LFS_TYPE_DIR: return mode | S_IFDIR;
case LFS_TYPE_REG: return mode | S_IFREG;
default: return 0;
}
}
static int lfs_totype(int type)
{
switch (type) {
case LFS_TYPE_DIR: return DT_DIR;
case LFS_TYPE_REG: return DT_REG;
default: return DT_UNKNOWN;
}
}
////// Block device operations //////
static int lfs_bd_read(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) {
BlockDevice *bd = (BlockDevice *)c->context;
return bd->read(buffer, block*c->block_size + off, size);
}
static int lfs_bd_prog(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) {
BlockDevice *bd = (BlockDevice *)c->context;
return bd->program(buffer, block*c->block_size + off, size);
}
static int lfs_bd_erase(const struct lfs_config *c, lfs_block_t block)
{
BlockDevice *bd = (BlockDevice *)c->context;
return bd->erase(block*c->block_size, c->block_size);
}
static int lfs_bd_sync(const struct lfs_config *c)
{
return 0;
}
////// Generic filesystem operations //////
// Filesystem implementation (See LittleFileSystem.h)
LittleFileSystem::LittleFileSystem(const char *name, BlockDevice *bd,
lfs_size_t read_size, lfs_size_t prog_size,
lfs_size_t block_size, lfs_size_t lookahead)
: FileSystem(name)
, _read_size(read_size)
, _prog_size(prog_size)
, _block_size(block_size)
, _lookahead(lookahead) {
if (bd) {
mount(bd);
}
}
LittleFileSystem::~LittleFileSystem() {
// nop if unmounted
unmount();
}
int LittleFileSystem::mount(BlockDevice *bd)
{
_mutex.lock();
LFS_INFO("mount(%p)", bd);
_bd = bd;
int err = _bd->init();
if (err) {
LFS_INFO("mount -> %d", err);
_mutex.unlock();
return err;
}
memset(&_config, 0, sizeof(_config));
_config.context = bd;
_config.read = lfs_bd_read;
_config.prog = lfs_bd_prog;
_config.erase = lfs_bd_erase;
_config.sync = lfs_bd_sync;
_config.read_size = bd->get_read_size();
if (_config.read_size < _read_size) {
_config.read_size = _read_size;
}
_config.prog_size = bd->get_program_size();
if (_config.prog_size < _prog_size) {
_config.prog_size = _prog_size;
}
_config.block_size = bd->get_erase_size();
if (_config.block_size < _block_size) {
_config.block_size = _block_size;
}
_config.block_count = bd->size() / _config.block_size;
_config.lookahead = 32 * ((_config.block_count+31)/32);
if (_config.lookahead > _lookahead) {
_config.lookahead = _lookahead;
}
err = lfs_mount(&_lfs, &_config);
LFS_INFO("mount -> %d", lfs_toerror(err));
_mutex.unlock();
return lfs_toerror(err);
}
int LittleFileSystem::unmount()
{
_mutex.lock();
LFS_INFO("unmount(%s)", "");
if (_bd) {
int err = lfs_unmount(&_lfs);
if (err) {
LFS_INFO("unmount -> %d", lfs_toerror(err));
_mutex.unlock();
return lfs_toerror(err);
}
err = _bd->deinit();
if (err) {
LFS_INFO("unmount -> %d", err);
_mutex.unlock();
return err;
}
_bd = NULL;
}
LFS_INFO("unmount -> %d", 0);
_mutex.unlock();
return 0;
}
int LittleFileSystem::format(BlockDevice *bd,
lfs_size_t read_size, lfs_size_t prog_size,
lfs_size_t block_size, lfs_size_t lookahead) {
LFS_INFO("format(%p, %ld, %ld, %ld, %ld)",
bd, read_size, prog_size, block_size, lookahead);
int err = bd->init();
if (err) {
LFS_INFO("format -> %d", err);
return err;
}
lfs_t _lfs;
struct lfs_config _config;
memset(&_config, 0, sizeof(_config));
_config.context = bd;
_config.read = lfs_bd_read;
_config.prog = lfs_bd_prog;
_config.erase = lfs_bd_erase;
_config.sync = lfs_bd_sync;
_config.read_size = bd->get_read_size();
if (_config.read_size < read_size) {
_config.read_size = read_size;
}
_config.prog_size = bd->get_program_size();
if (_config.prog_size < prog_size) {
_config.prog_size = prog_size;
}
_config.block_size = bd->get_erase_size();
if (_config.block_size < block_size) {
_config.block_size = block_size;
}
_config.block_count = bd->size() / _config.block_size;
_config.lookahead = 32 * ((_config.block_count+31)/32);
if (_config.lookahead > lookahead) {
_config.lookahead = lookahead;
}
err = lfs_format(&_lfs, &_config);
if (err) {
LFS_INFO("format -> %d", lfs_toerror(err));
return lfs_toerror(err);
}
err = bd->deinit();
if (err) {
LFS_INFO("format -> %d", err);
return err;
}
LFS_INFO("format -> %d", 0);
return 0;
}
int LittleFileSystem::reformat(BlockDevice *bd)
{
_mutex.lock();
LFS_INFO("reformat(%p)", bd);
if (_bd) {
if (!bd) {
bd = _bd;
}
int err = unmount();
if (err) {
LFS_INFO("reformat -> %d", err);
_mutex.unlock();
return err;
}
}
if (!bd) {
LFS_INFO("reformat -> %d", -ENODEV);
_mutex.unlock();
return -ENODEV;
}
int err = LittleFileSystem::format(bd,
_read_size, _prog_size, _block_size, _lookahead);
if (err) {
LFS_INFO("reformat -> %d", err);
_mutex.unlock();
return err;
}
err = mount(bd);
if (err) {
LFS_INFO("reformat -> %d", err);
_mutex.unlock();
return err;
}
LFS_INFO("reformat -> %d", 0);
_mutex.unlock();
return 0;
}
int LittleFileSystem::remove(const char *filename)
{
_mutex.lock();
LFS_INFO("remove(\"%s\")", filename);
int err = lfs_remove(&_lfs, filename);
LFS_INFO("remove -> %d", lfs_toerror(err));
_mutex.unlock();
return lfs_toerror(err);
}
int LittleFileSystem::rename(const char *oldname, const char *newname)
{
_mutex.lock();
LFS_INFO("rename(\"%s\", \"%s\")", oldname, newname);
int err = lfs_rename(&_lfs, oldname, newname);
LFS_INFO("rename -> %d", lfs_toerror(err));
_mutex.unlock();
return lfs_toerror(err);
}
int LittleFileSystem::mkdir(const char *name, mode_t mode)
{
_mutex.lock();
LFS_INFO("mkdir(\"%s\", 0x%lx)", name, mode);
int err = lfs_mkdir(&_lfs, name);
LFS_INFO("mkdir -> %d", lfs_toerror(err));
_mutex.unlock();
return lfs_toerror(err);
}
int LittleFileSystem::stat(const char *name, struct stat *st)
{
struct lfs_info info;
_mutex.lock();
LFS_INFO("stat(\"%s\", %p)", name, st);
int err = lfs_stat(&_lfs, name, &info);
LFS_INFO("stat -> %d", lfs_toerror(err));
_mutex.unlock();
st->st_size = info.size;
st->st_mode = lfs_tomode(info.type);
return lfs_toerror(err);
}
////// File operations //////
int LittleFileSystem::file_open(fs_file_t *file, const char *path, int flags)
{
lfs_file_t *f = new lfs_file_t;
_mutex.lock();
LFS_INFO("file_open(%p, \"%s\", 0x%x)", *file, path, flags);
int err = lfs_file_open(&_lfs, f, path, lfs_fromflags(flags));
LFS_INFO("file_open -> %d", lfs_toerror(err));
_mutex.unlock();
if (!err) {
*file = f;
} else {
delete f;
}
return lfs_toerror(err);
}
int LittleFileSystem::file_close(fs_file_t file)
{
lfs_file_t *f = (lfs_file_t *)file;
_mutex.lock();
LFS_INFO("file_close(%p)", file);
int err = lfs_file_close(&_lfs, f);
LFS_INFO("file_close -> %d", lfs_toerror(err));
_mutex.unlock();
delete f;
return lfs_toerror(err);
}
ssize_t LittleFileSystem::file_read(fs_file_t file, void *buffer, size_t len)
{
lfs_file_t *f = (lfs_file_t *)file;
_mutex.lock();
LFS_INFO("file_read(%p, %p, %d)", file, buffer, len);
lfs_ssize_t res = lfs_file_read(&_lfs, f, buffer, len);
LFS_INFO("file_read -> %d", lfs_toerror(res));
_mutex.unlock();
return lfs_toerror(res);
}
ssize_t LittleFileSystem::file_write(fs_file_t file, const void *buffer, size_t len)
{
lfs_file_t *f = (lfs_file_t *)file;
_mutex.lock();
LFS_INFO("file_write(%p, %p, %d)", file, buffer, len);
lfs_ssize_t res = lfs_file_write(&_lfs, f, buffer, len);
LFS_INFO("file_write -> %d", lfs_toerror(res));
_mutex.unlock();
return lfs_toerror(res);
}
int LittleFileSystem::file_sync(fs_file_t file)
{
lfs_file_t *f = (lfs_file_t *)file;
_mutex.lock();
LFS_INFO("file_sync(%p)", file);
int err = lfs_file_sync(&_lfs, f);
LFS_INFO("file_sync -> %d", lfs_toerror(err));
_mutex.unlock();
return lfs_toerror(err);
}
off_t LittleFileSystem::file_seek(fs_file_t file, off_t offset, int whence)
{
lfs_file_t *f = (lfs_file_t *)file;
_mutex.lock();
LFS_INFO("file_seek(%p, %ld, %d)", file, offset, whence);
off_t res = lfs_file_seek(&_lfs, f, offset, lfs_fromwhence(whence));
LFS_INFO("file_seek -> %d", lfs_toerror(res));
_mutex.unlock();
return lfs_toerror(res);
}
off_t LittleFileSystem::file_tell(fs_file_t file)
{
lfs_file_t *f = (lfs_file_t *)file;
_mutex.lock();
LFS_INFO("file_tell(%p)", file);
off_t res = lfs_file_tell(&_lfs, f);
LFS_INFO("file_tell -> %d", lfs_toerror(res));
_mutex.unlock();
return lfs_toerror(res);
}
off_t LittleFileSystem::file_size(fs_file_t file)
{
lfs_file_t *f = (lfs_file_t *)file;
_mutex.lock();
LFS_INFO("file_size(%p)", file);
off_t res = lfs_file_size(&_lfs, f);
LFS_INFO("file_size -> %d", lfs_toerror(res));
_mutex.unlock();
return lfs_toerror(res);
}
////// Dir operations //////
int LittleFileSystem::dir_open(fs_dir_t *dir, const char *path)
{
lfs_dir_t *d = new lfs_dir_t;
_mutex.lock();
LFS_INFO("dir_open(%p, \"%s\")", *dir, path);
int err = lfs_dir_open(&_lfs, d, path);
LFS_INFO("dir_open -> %d", lfs_toerror(err));
_mutex.unlock();
if (!err) {
*dir = d;
} else {
delete d;
}
return lfs_toerror(err);
}
int LittleFileSystem::dir_close(fs_dir_t dir)
{
lfs_dir_t *d = (lfs_dir_t *)dir;
_mutex.lock();
LFS_INFO("dir_close(%p)", dir);
int err = lfs_dir_close(&_lfs, d);
LFS_INFO("dir_close -> %d", lfs_toerror(err));
_mutex.unlock();
delete d;
return lfs_toerror(err);
}
ssize_t LittleFileSystem::dir_read(fs_dir_t dir, struct dirent *ent)
{
lfs_dir_t *d = (lfs_dir_t *)dir;
struct lfs_info info;
_mutex.lock();
LFS_INFO("dir_read(%p, %p)", dir, ent);
int res = lfs_dir_read(&_lfs, d, &info);
LFS_INFO("dir_read -> %d", lfs_toerror(res));
_mutex.unlock();
if (res == 1) {
ent->d_type = lfs_totype(info.type);
strcpy(ent->d_name, info.name);
}
return lfs_toerror(res);
}
void LittleFileSystem::dir_seek(fs_dir_t dir, off_t offset)
{
lfs_dir_t *d = (lfs_dir_t *)dir;
_mutex.lock();
LFS_INFO("dir_seek(%p, %ld)", dir, offset);
lfs_dir_seek(&_lfs, d, offset);
LFS_INFO("dir_seek -> %s", "void");
_mutex.unlock();
}
off_t LittleFileSystem::dir_tell(fs_dir_t dir)
{
lfs_dir_t *d = (lfs_dir_t *)dir;
_mutex.lock();
LFS_INFO("dir_tell(%p)", dir);
lfs_soff_t res = lfs_dir_tell(&_lfs, d);
LFS_INFO("dir_tell -> %d", lfs_toerror(res));
_mutex.unlock();
return lfs_toerror(res);
}
void LittleFileSystem::dir_rewind(fs_dir_t dir)
{
lfs_dir_t *d = (lfs_dir_t *)dir;
_mutex.lock();
LFS_INFO("dir_rewind(%p)", dir);
lfs_dir_rewind(&_lfs, d);
LFS_INFO("dir_rewind -> %s", "void");
_mutex.unlock();
}

View File

@ -0,0 +1,275 @@
/* 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.
*/
#ifndef MBED_LFSFILESYSTEM_H
#define MBED_LFSFILESYSTEM_H
#include "FileSystem.h"
#include "BlockDevice.h"
#include "PlatformMutex.h"
extern "C" {
#include "lfs.h"
}
/**
* LittleFileSystem, a little filesystem
*/
class LittleFileSystem : public mbed::FileSystem {
public:
/** Lifetime of the LittleFileSystem
*
* @param name Name to add filesystem to tree as
* @param bd BlockDevice to mount, may be passed instead to mount call
* @param read_size
* Minimum size of a block read. This determines the size of read buffers.
* This may be larger than the physical read size to improve performance
* by caching more of the block device.
* @param prog_size
* Minimum size of a block program. This determines the size of program
* buffers. This may be larger than the physical program size to improve
* performance by caching more of the block device.
* @param block_size
* Size of an erasable block. This does not impact ram consumption and
* may be larger than the physical erase size. However, this should be
* kept small as each file currently takes up an entire block.
* @param lookahead
* Number of blocks to lookahead during block allocation. A larger
* lookahead reduces the number of passes required to allocate a block.
* The lookahead buffer requires only 1 bit per block so it can be quite
* large with little ram impact. Should be a multiple of 32.
*/
LittleFileSystem(const char *name=NULL, BlockDevice *bd=NULL,
lfs_size_t read_size=MBED_LFS_READ_SIZE,
lfs_size_t prog_size=MBED_LFS_PROG_SIZE,
lfs_size_t block_size=MBED_LFS_BLOCK_SIZE,
lfs_size_t lookahead=MBED_LFS_LOOKAHEAD);
virtual ~LittleFileSystem();
/** Formats a block device with the LittleFileSystem
*
* The block device to format should be mounted when this function is called.
*
* @param bd This is the block device that will be formated.
* @param read_size
* Minimum size of a block read. This determines the size of read buffers.
* This may be larger than the physical read size to improve performance
* by caching more of the block device.
* @param prog_size
* Minimum size of a block program. This determines the size of program
* buffers. This may be larger than the physical program size to improve
* performance by caching more of the block device.
* @param block_size
* Size of an erasable block. This does not impact ram consumption and
* may be larger than the physical erase size. However, this should be
* kept small as each file currently takes up an entire block.
* @param lookahead
* Number of blocks to lookahead during block allocation. A larger
* lookahead reduces the number of passes required to allocate a block.
* The lookahead buffer requires only 1 bit per block so it can be quite
* large with little ram impact. Should be a multiple of 32.
*/
static int format(BlockDevice *bd,
lfs_size_t read_size=MBED_LFS_READ_SIZE,
lfs_size_t prog_size=MBED_LFS_PROG_SIZE,
lfs_size_t block_size=MBED_LFS_BLOCK_SIZE,
lfs_size_t lookahead=MBED_LFS_LOOKAHEAD);
/** Mounts a filesystem to a block device
*
* @param bd BlockDevice to mount to
* @return 0 on success, negative error code on failure
*/
virtual int mount(BlockDevice *bd);
/** Unmounts a filesystem from the underlying block device
*
* @return 0 on success, negative error code on failure
*/
virtual int unmount();
/** Reformats a filesystem, results in an empty and mounted filesystem
*
* @param bd
* BlockDevice to reformat and mount. If NULL, the mounted
* block device will be used.
* Note: if mount fails, bd must be provided.
* Default: NULL
*
* @return 0 on success, negative error code on failure
*/
virtual int reformat(BlockDevice *bd);
/** Remove a file from the filesystem.
*
* @param path The name of the file to remove.
* @return 0 on success, negative error code on failure
*/
virtual int remove(const char *path);
/** Rename a file in the filesystem.
*
* @param path The name of the file to rename.
* @param newpath The name to rename it to
* @return 0 on success, negative error code on failure
*/
virtual int rename(const char *path, const char *newpath);
/** Store information about the file in a stat structure
*
* @param path The name of the file to find information about
* @param st The stat buffer to write to
* @return 0 on success, negative error code on failure
*/
virtual int stat(const char *path, struct stat *st);
/** Create a directory in the filesystem.
*
* @param path The name of the directory to create.
* @param mode The permissions with which to create the directory
* @return 0 on success, negative error code on failure
*/
virtual int mkdir(const char *path, mode_t mode);
protected:
/** Open a file on the filesystem
*
* @param file Destination for the handle to a newly created file
* @param path The name of the file to open
* @param flags The flags to open the file in, one of O_RDONLY, O_WRONLY, O_RDWR,
* bitwise or'd with one of O_CREAT, O_TRUNC, O_APPEND
* @return 0 on success, negative error code on failure
*/
virtual int file_open(mbed::fs_file_t *file, const char *path, int flags);
/** Close a file
*
* @param file File handle
* return 0 on success, negative error code on failure
*/
virtual int file_close(mbed::fs_file_t file);
/** Read the contents of a file into a buffer
*
* @param file File handle
* @param buffer The buffer to read in to
* @param size The number of bytes to read
* @return The number of bytes read, 0 at end of file, negative error on failure
*/
virtual ssize_t file_read(mbed::fs_file_t file, void *buffer, size_t size);
/** Write the contents of a buffer to a file
*
* @param file File handle
* @param buffer The buffer to write from
* @param size The number of bytes to write
* @return The number of bytes written, negative error on failure
*/
virtual ssize_t file_write(mbed::fs_file_t file, const void *buffer, size_t size);
/** Flush any buffers associated with the file
*
* @param file File handle
* @return 0 on success, negative error code on failure
*/
virtual int file_sync(mbed::fs_file_t file);
/** Move the file position to a given offset from from a given location
*
* @param file File handle
* @param offset The offset from whence to move to
* @param whence The start of where to seek
* SEEK_SET to start from beginning of file,
* SEEK_CUR to start from current position in file,
* SEEK_END to start from end of file
* @return The new offset of the file
*/
virtual off_t file_seek(mbed::fs_file_t file, off_t offset, int whence);
/** Get the file position of the file
*
* @param file File handle
* @return The current offset in the file
*/
virtual off_t file_tell(mbed::fs_file_t file);
/** Get the size of the file
*
* @param file File handle
* @return Size of the file in bytes
*/
virtual off_t file_size(mbed::fs_file_t file);
/** Open a directory on the filesystem
*
* @param dir Destination for the handle to the directory
* @param path Name of the directory to open
* @return 0 on success, negative error code on failure
*/
virtual int dir_open(mbed::fs_dir_t *dir, const char *path);
/** Close a directory
*
* @param dir Dir handle
* return 0 on success, negative error code on failure
*/
virtual int dir_close(mbed::fs_dir_t dir);
/** Read the next directory entry
*
* @param dir Dir handle
* @param ent The directory entry to fill out
* @return 1 on reading a filename, 0 at end of directory, negative error on failure
*/
virtual ssize_t dir_read(mbed::fs_dir_t dir, struct dirent *ent);
/** Set the current position of the directory
*
* @param dir Dir handle
* @param offset Offset of the location to seek to,
* must be a value returned from dir_tell
*/
virtual void dir_seek(mbed::fs_dir_t dir, off_t offset);
/** Get the current position of the directory
*
* @param dir Dir handle
* @return Position of the directory that can be passed to dir_rewind
*/
virtual off_t dir_tell(mbed::fs_dir_t dir);
/** Rewind the current position to the beginning of the directory
*
* @param dir Dir handle
*/
virtual void dir_rewind(mbed::fs_dir_t dir);
private:
lfs_t _lfs; // _the actual filesystem
struct lfs_config _config;
BlockDevice *_bd; // the block device
// default parameters
const lfs_size_t _read_size;
const lfs_size_t _prog_size;
const lfs_size_t _block_size;
const lfs_size_t _lookahead;
// thread-safe locking
PlatformMutex _mutex;
};
#endif

View File

@ -0,0 +1,104 @@
## Mbed OS API for the little filesystem
This is the Mbed OS API for littlefs, a little fail-safe filesystem
designed for embedded systems.
```
| | | .---._____
.-----. | |
--|o |---| littlefs |
--| |---| |
'-----' '----------'
| | |
```
**Bounded RAM/ROM** - The littlefs is designed to work with a limited amount
of memory. Recursion is avoided, and dynamic memory is limited to configurable
buffers that can be provided statically.
**Power-loss resilient** - The littlefs is designed for systems that may have
random power failures. The littlefs has strong copy-on-write guarantees, and
storage on disk is always kept in a valid state.
**Wear leveling** - Because the most common form of embedded storage is erodible
flash memories, littlefs provides a form of dynamic wear leveling for systems
that cannot fit a full flash translation layer.
## Usage
If you are already using a filesystem in Mbed, adopting the littlefs should
just require a name change to use the [LittleFileSystem](LittleFileSystem.h)
class.
Here is a simple example that updates a file named "boot_count" every time
the application runs:
``` c++
#include "LittleFileSystem.h"
#include "SPIFBlockDevice.h"
// Physical block device, can be any device that supports the BlockDevice API
SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5);
// Storage for the littlefs
LittleFileSystem fs("fs");
// Entry point
int main() {
// Mount the filesystem
int err = fs.mount(&bd);
if (err) {
// Reformat if we can't mount the filesystem,
// this should only happen on the first boot
LittleFileSystem::format(&bd);
fs.mount(&bd);
}
// Read the boot count
uint32_t boot_count = 0;
FILE *f = fopen("/fs/boot_count", "r+");
if (!f) {
// Create the file if it doesn't exist
f = fopen("/fs/boot_count", "w+");
}
fread(&boot_count, sizeof(boot_count), 1, f);
// Update the boot count
boot_count += 1;
rewind(f);
fwrite(&boot_count, sizeof(boot_count), 1, f);
// Remember that storage may not be updated until the file
// is closed successfully
fclose(f);
// Release any resources we were using
fs.unmount();
// Print the boot count
printf("boot_count: %ld\n", boot_count);
}
```
## Reference material
[DESIGN.md](littlefs/DESIGN.md) - DESIGN.md contains a fully detailed dive into
how littlefs actually works. We encourage you to read it because the
solutions and tradeoffs at work here are quite interesting.
[SPEC.md](littlefs/SPEC.md) - SPEC.md contains the on-disk specification of
littlefs with all the nitty-gritty details. This can be useful for developing
tooling.
## Related projects
[littlefs](https://github.com/geky/littlefs) - Where the core of littlefs
currently lives.
[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse)
wrapper for littlefs. The project allows you to mount littlefs directly in a
Linux machine. This can be useful for debugging littlefs if you have an SD card
handy.
[littlefs-js](https://github.com/geky/littlefs-js) - A JavaScript wrapper for
littlefs. I'm not sure why you would want this, but it is handy for demos.
You can see it in action [here](http://littlefs.geky.net/demo.html).

View File

@ -0,0 +1,684 @@
/* 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
void test_directory_tests()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = MBED_TEST_FILESYSTEM::format(&bd);
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_root_directory()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "/");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_creation()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("potato", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_file_creation()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "burito", O_CREAT | O_WRONLY);
TEST_ASSERT_EQUAL(0, res);
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_iteration()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "/");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "potato");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "burito");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_failures()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("potato", 0777);
TEST_ASSERT_EQUAL(-EEXIST, res);
res = dir[0].open(&fs, "tomato");
TEST_ASSERT_EQUAL(-ENOENT, res);
res = dir[0].open(&fs, "burito");
TEST_ASSERT_EQUAL(-ENOTDIR, res);
res = file[0].open(&fs, "tomato", O_RDONLY);
TEST_ASSERT_EQUAL(-ENOENT, res);
res = file[0].open(&fs, "potato", O_RDONLY);
TEST_ASSERT_EQUAL(-EISDIR, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_nested_directories()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("potato/baked", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("potato/sweet", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("potato/fried", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "potato");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "baked");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "sweet");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "fried");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_multi_block_directory()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("cactus", 0777);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 128; i++) {
sprintf((char*)buffer, "cactus/test%d", i);
res = fs.mkdir((char*)buffer, 0777);
TEST_ASSERT_EQUAL(0, res);
}
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "cactus");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
for (int i = 0; i < 128; i++) {
sprintf((char*)buffer, "test%d", i);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
}
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_remove()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.remove("potato");
TEST_ASSERT_EQUAL(-EINVAL, res);
res = fs.remove("potato/sweet");
TEST_ASSERT_EQUAL(0, res);
res = fs.remove("potato/baked");
TEST_ASSERT_EQUAL(0, res);
res = fs.remove("potato/fried");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "potato");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.remove("potato");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "/");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "burito");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "cactus");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "/");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "burito");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "cactus");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_rename()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("coldpotato", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("coldpotato/baked", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("coldpotato/sweet", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("coldpotato/fried", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.rename("coldpotato", "hotpotato");
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "hotpotato");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "baked");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "sweet");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "fried");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("warmpotato", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("warmpotato/mushy", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.rename("hotpotato", "warmpotato");
TEST_ASSERT_EQUAL(-EINVAL, res);
res = fs.remove("warmpotato/mushy");
TEST_ASSERT_EQUAL(0, res);
res = fs.rename("hotpotato", "warmpotato");
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "warmpotato");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "baked");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "sweet");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "fried");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("coldpotato", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.rename("warmpotato/baked", "coldpotato/baked");
TEST_ASSERT_EQUAL(0, res);
res = fs.rename("warmpotato/sweet", "coldpotato/sweet");
TEST_ASSERT_EQUAL(0, res);
res = fs.rename("warmpotato/fried", "coldpotato/fried");
TEST_ASSERT_EQUAL(0, res);
res = fs.remove("coldpotato");
TEST_ASSERT_EQUAL(-EINVAL, res);
res = fs.remove("warmpotato");
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "coldpotato");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "baked");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "sweet");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "fried");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}
Case cases[] = {
Case("Directory tests", test_directory_tests),
Case("Root directory", test_root_directory),
Case("Directory creation", test_directory_creation),
Case("File creation", test_file_creation),
Case("Directory iteration", test_directory_iteration),
Case("Directory failures", test_directory_failures),
Case("Nested directories", test_nested_directories),
Case("Multi-block directory", test_multi_block_directory),
Case("Directory remove", test_directory_remove),
Case("Directory rename", test_directory_rename),
};
Specification specification(test_setup, cases);
int main()
{
return !Harness::run(specification);
}

View File

@ -0,0 +1,449 @@
/* 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
void test_file_tests()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = MBED_TEST_FILESYSTEM::format(&bd);
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_simple_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "hello", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
size = strlen("Hello World!\n");
memcpy(wbuffer, "Hello World!\n", size);
res = file[0].write(wbuffer, size);
TEST_ASSERT_EQUAL(size, res);
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "hello", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
size = strlen("Hello World!\n");
res = file[0].read(rbuffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(rbuffer, wbuffer, size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_small_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
size_t size = 32;
size_t chunk = 31;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "smallavacado", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
for (size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
res = file[0].write(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 32;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "smallavacado", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = file[0].read(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_medium_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
size_t size = 8192;
size_t chunk = 31;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "mediumavacado", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
for (size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
res = file[0].write(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 8192;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "mediumavacado", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = file[0].read(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_large_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
size_t size = 262144;
size_t chunk = 31;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "largeavacado", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
for (size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
res = file[0].write(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 262144;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "largeavacado", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = file[0].read(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_non_overlap_check()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
size_t size = 32;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "smallavacado", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = file[0].read(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 8192;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "mediumavacado", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = file[0].read(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 262144;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "largeavacado", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = file[0].read(buffer, chunk);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_dir_check()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "/");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "hello");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "smallavacado");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "mediumavacado");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "largeavacado");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}
Case cases[] = {
Case("File tests", test_file_tests),
Case("Simple file test", test_simple_file_test),
Case("Small file test", test_small_file_test),
Case("Medium file test", test_medium_file_test),
Case("Large file test", test_large_file_test),
Case("Non-overlap check", test_non_overlap_check),
Case("Dir check", test_dir_check),
};
Specification specification(test_setup, cases);
int main()
{
return !Harness::run(specification);
}

View File

@ -0,0 +1,407 @@
/* 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
void test_parallel_tests()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = MBED_TEST_FILESYSTEM::format(&bd);
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_parallel_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "a", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
res = file[1].open(&fs, "b", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
res = file[2].open(&fs, "c", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
res = file[3].open(&fs, "d", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 10; i++) {
res = file[0].write((const void*)"a", 1);
TEST_ASSERT_EQUAL(1, res);
res = file[1].write((const void*)"b", 1);
TEST_ASSERT_EQUAL(1, res);
res = file[2].write((const void*)"c", 1);
TEST_ASSERT_EQUAL(1, res);
res = file[3].write((const void*)"d", 1);
TEST_ASSERT_EQUAL(1, res);
}
file[0].close();
file[1].close();
file[2].close();
file[3].close();
res = dir[0].open(&fs, "/");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "a");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "b");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "c");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "d");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "a", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
res = file[1].open(&fs, "b", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
res = file[2].open(&fs, "c", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
res = file[3].open(&fs, "d", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 10; i++) {
res = file[0].read(buffer, 1);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('a', res);
res = file[1].read(buffer, 1);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('b', res);
res = file[2].read(buffer, 1);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('c', res);
res = file[3].read(buffer, 1);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('d', res);
}
file[0].close();
file[1].close();
file[2].close();
file[3].close();
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_parallel_remove_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "e", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 5; i++) {
res = file[0].write((const void*)"e", 1);
TEST_ASSERT_EQUAL(1, res);
}
res = fs.remove("a");
TEST_ASSERT_EQUAL(0, res);
res = fs.remove("b");
TEST_ASSERT_EQUAL(0, res);
res = fs.remove("c");
TEST_ASSERT_EQUAL(0, res);
res = fs.remove("d");
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 5; i++) {
res = file[0].write((const void*)"e", 1);
TEST_ASSERT_EQUAL(1, res);
}
file[0].close();
res = dir[0].open(&fs, "/");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "e");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "e", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 10; i++) {
res = file[0].read(buffer, 1);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('e', res);
}
file[0].close();
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_remove_inconveniently_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "e", O_WRONLY | O_TRUNC);
TEST_ASSERT_EQUAL(0, res);
res = file[1].open(&fs, "f", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
res = file[2].open(&fs, "g", O_WRONLY | O_CREAT);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 5; i++) {
res = file[0].write((const void*)"e", 1);
TEST_ASSERT_EQUAL(1, res);
res = file[1].write((const void*)"f", 1);
TEST_ASSERT_EQUAL(1, res);
res = file[2].write((const void*)"g", 1);
TEST_ASSERT_EQUAL(1, res);
}
res = fs.remove("f");
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 5; i++) {
res = file[0].write((const void*)"e", 1);
TEST_ASSERT_EQUAL(1, res);
res = file[1].write((const void*)"f", 1);
TEST_ASSERT_EQUAL(1, res);
res = file[2].write((const void*)"g", 1);
TEST_ASSERT_EQUAL(1, res);
}
file[0].close();
file[1].close();
file[2].close();
res = dir[0].open(&fs, "/");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "e");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "g");
TEST_ASSERT_EQUAL(0, res);
res = ent.d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "e", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
res = file[1].open(&fs, "g", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 10; i++) {
res = file[0].read(buffer, 1);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('e', res);
res = file[1].read(buffer, 1);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('g', res);
}
file[0].close();
file[1].close();
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}
Case cases[] = {
Case("Parallel tests", test_parallel_tests),
Case("Parallel file test", test_parallel_file_test),
Case("Parallel remove file test", test_parallel_remove_file_test),
Case("Remove inconveniently test", test_remove_inconveniently_test),
};
Specification specification(test_setup, cases);
int main()
{
return !Harness::run(specification);
}

View File

@ -0,0 +1,643 @@
/* 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
void test_seek_tests()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = MBED_TEST_FILESYSTEM::format(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mkdir("hello", 0777);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 132; i++) {
sprintf((char*)buffer, "hello/kitty%d", i);
res = file[0].open(&fs, (char*)buffer,
O_WRONLY | O_CREAT | O_APPEND);
TEST_ASSERT_EQUAL(0, res);
size = strlen("kittycatcat");
memcpy(buffer, "kittycatcat", size);
for (int j = 0; j < 132; j++) {
file[0].write(buffer, size);
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
}
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_simple_dir_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "hello");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
off_t pos;
int i;
for (i = 0; i < 4; i++) {
sprintf((char*)buffer, "kitty%d", i);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
pos = dir[0].tell();
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
dir[0].seek(pos);
sprintf((char*)buffer, "kitty%d", i);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
dir[0].rewind();
sprintf((char*)buffer, "kitty%d", 0);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
dir[0].seek(pos);
sprintf((char*)buffer, "kitty%d", i);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_large_dir_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].open(&fs, "hello");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
off_t pos;
int i;
for (i = 0; i < 128; i++) {
sprintf((char*)buffer, "kitty%d", i);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
pos = dir[0].tell();
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
dir[0].seek(pos);
sprintf((char*)buffer, "kitty%d", i);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
dir[0].rewind();
sprintf((char*)buffer, "kitty%d", 0);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
dir[0].seek(pos);
sprintf((char*)buffer, "kitty%d", i);
res = dir[0].read(&ent);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ent.d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
res = dir[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_simple_file_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "hello/kitty42", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
off_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < 4; i++) {
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
pos = file[0].tell();
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
file[0].rewind();
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(-size, SEEK_CUR);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(-size, SEEK_END) >= 0;
TEST_ASSERT_EQUAL(1, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
size_t size = file[0].size();
res = file[0].seek(0, SEEK_CUR);
TEST_ASSERT_EQUAL(size, res);
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_large_file_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "hello/kitty42", O_RDONLY);
TEST_ASSERT_EQUAL(0, res);
off_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < 128; i++) {
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
pos = file[0].tell();
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
file[0].rewind();
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(-size, SEEK_CUR);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(-size, SEEK_END) >= 0;
TEST_ASSERT_EQUAL(1, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
size_t size = file[0].size();
res = file[0].seek(0, SEEK_CUR);
TEST_ASSERT_EQUAL(size, res);
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_simple_file_seek_and_write()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "hello/kitty42", O_RDWR);
TEST_ASSERT_EQUAL(0, res);
off_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < 4; i++) {
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
pos = file[0].tell();
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
memcpy(buffer, "doggodogdog", size);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].write(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "doggodogdog", size);
TEST_ASSERT_EQUAL(0, res);
file[0].rewind();
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "doggodogdog", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(-size, SEEK_END) >= 0;
TEST_ASSERT_EQUAL(1, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
size_t size = file[0].size();
res = file[0].seek(0, SEEK_CUR);
TEST_ASSERT_EQUAL(size, res);
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_large_file_seek_and_write()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "hello/kitty42", O_RDWR);
TEST_ASSERT_EQUAL(0, res);
off_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < 128; i++) {
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
if (i != 4) {
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
}
pos = file[0].tell();
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
memcpy(buffer, "doggodogdog", size);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].write(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "doggodogdog", size);
TEST_ASSERT_EQUAL(0, res);
file[0].rewind();
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(pos, SEEK_SET);
TEST_ASSERT_EQUAL(pos, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "doggodogdog", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(-size, SEEK_END) >= 0;
TEST_ASSERT_EQUAL(1, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
size_t size = file[0].size();
res = file[0].seek(0, SEEK_CUR);
TEST_ASSERT_EQUAL(size, res);
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_boundary_seek_and_write()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "hello/kitty42", O_RDWR);
TEST_ASSERT_EQUAL(0, res);
size = strlen("hedgehoghog");
const off_t offsets[] = {512, 1020, 513, 1021, 511, 1019};
for (int i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
off_t off = offsets[i];
memcpy(buffer, "hedgehoghog", size);
res = file[0].seek(off, SEEK_SET);
TEST_ASSERT_EQUAL(off, res);
res = file[0].write(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = file[0].seek(off, SEEK_SET);
TEST_ASSERT_EQUAL(off, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "hedgehoghog", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(0, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].sync();
TEST_ASSERT_EQUAL(0, res);
}
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_out_of_bounds_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = file[0].open(&fs, "hello/kitty42", O_RDWR);
TEST_ASSERT_EQUAL(0, res);
size = strlen("kittycatcat");
res = file[0].size();
TEST_ASSERT_EQUAL(132*size, res);
res = file[0].seek((132+4)*size,
SEEK_SET);
TEST_ASSERT_EQUAL((132+4)*size, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(0, res);
memcpy(buffer, "porcupineee", size);
res = file[0].write(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = file[0].seek((132+4)*size,
SEEK_SET);
TEST_ASSERT_EQUAL((132+4)*size, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "porcupineee", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].seek(132*size,
SEEK_SET);
TEST_ASSERT_EQUAL(132*size, res);
res = file[0].read(buffer, size);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size);
TEST_ASSERT_EQUAL(0, res);
res = file[0].close();
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}
Case cases[] = {
Case("Seek tests", test_seek_tests),
Case("Simple dir seek", test_simple_dir_seek),
Case("Large dir seek", test_large_dir_seek),
Case("Simple file seek", test_simple_file_seek),
Case("Large file seek", test_large_file_seek),
Case("Simple file seek and write", test_simple_file_seek_and_write),
Case("Large file seek and write", test_large_file_seek_and_write),
Case("Boundary seek and write", test_boundary_seek_and_write),
Case("Out-of-bounds seek", test_out_of_bounds_seek),
};
Specification specification(test_setup, cases);
int main()
{
return !Harness::run(specification);
}

View File

@ -0,0 +1,104 @@
/* 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 "mbed.h"
#include "unity.h"
#include "utest.h"
#include "test_env.h"
#include "atomic_usage.h"
#include "ObservingBlockDevice.h"
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_SIM_BLOCKDEVICE
#error [NOT_SUPPORTED] Simulation block device required for resilience tests
#endif
#ifndef MBED_TEST_SIM_BLOCKDEVICE_DECL
#define MBED_TEST_SIM_BLOCKDEVICE_DECL MBED_TEST_SIM_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512)
#endif
#ifndef MBED_TEST_BLOCK_COUNT
#define MBED_TEST_BLOCK_COUNT 64
#endif
#ifndef MBED_TEST_CYCLES
#define MBED_TEST_CYCLES 10
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE)
/**
* 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()
{
MBED_TEST_SIM_BLOCKDEVICE_DECL;
// bring up to get block size
bd.init();
bd_size_t block_size = bd.get_erase_size();
bd.deinit();
SlicingBlockDevice slice(&bd, 0, MBED_TEST_BLOCK_COUNT*block_size);
// Setup the test
setup_atomic_operations(&slice, true);
// Run check on every write operation
ObservingBlockDevice observer(&slice);
observer.attach(check_atomic_operations);
// Perform operations
printf("Performing %i operations on flash\n", MBED_TEST_CYCLES);
for (int i = 1; i <= MBED_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(MBED_TEST_TIMEOUT, "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);
}

View File

@ -0,0 +1,119 @@
/* 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 "mbed.h"
#include "unity.h"
#include "utest.h"
#include "test_env.h"
#include "atomic_usage.h"
#include "ObservingBlockDevice.h"
#include "LittleFileSystem.h"
#include <string.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required for resilience_functional tests
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_BLOCK_COUNT
#define MBED_TEST_BLOCK_COUNT 64
#endif
#ifndef MBED_TEST_CYCLES
#define MBED_TEST_CYCLES 10
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_BLOCKDEVICE_DECL;
typedef enum {
CMD_STATUS_PASS,
CMD_STATUS_FAIL,
CMD_STATUS_CONTINUE,
CMD_STATUS_ERROR
} cmd_status_t;
void use_filesystem()
{
// Perform operations
while (true) {
int64_t ret = perform_atomic_operations(&bd);
TEST_ASSERT(ret > 0);
}
}
static cmd_status_t handle_command(const char *key, const char *value)
{
if (strcmp(key, "format") == 0) {
setup_atomic_operations(&bd, true);
greentea_send_kv("format_done", 1);
return CMD_STATUS_CONTINUE;
} else if (strcmp(key, "run") == 0) {
use_filesystem();
return CMD_STATUS_CONTINUE;
} else if (strcmp(key, "exit") == 0) {
if (strcmp(value, "pass") != 0) {
return CMD_STATUS_FAIL;
}
check_atomic_operations(&bd);
return CMD_STATUS_PASS;
} else {
return CMD_STATUS_ERROR;
}
}
int main()
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "unexpected_reset");
static char _key[10 + 1] = {};
static char _value[128 + 1] = {};
greentea_send_kv("start", 1);
// Handshake with host
cmd_status_t cmd_status = CMD_STATUS_CONTINUE;
while (CMD_STATUS_CONTINUE == cmd_status) {
memset(_key, 0, sizeof(_key));
memset(_value, 0, sizeof(_value));
greentea_parse_kv(_key, _value, sizeof(_key) - 1, sizeof(_value) - 1);
cmd_status = handle_command(_key, _value);
}
GREENTEA_TESTSUITE_RESULT(CMD_STATUS_PASS == cmd_status);
}

View File

@ -0,0 +1,120 @@
/* 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 "mbed.h"
#include "unity.h"
#include "utest.h"
#include "test_env.h"
#include "atomic_usage.h"
#include "ExhaustibleBlockDevice.h"
#include "SlicingBlockDevice.h"
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_SIM_BLOCKDEVICE
#error [NOT_SUPPORTED] Simulation block device required for wear leveling tests
#endif
#ifndef MBED_TEST_SIM_BLOCKDEVICE_DECL
#define MBED_TEST_SIM_BLOCKDEVICE_DECL MBED_TEST_SIM_BLOCKDEVICE bd(MBED_TEST_BLOCK_COUNT*512, 1, 1, 512)
#endif
#ifndef MBED_TEST_BLOCK_COUNT
#define MBED_TEST_BLOCK_COUNT 64
#endif
#ifndef MBED_TEST_ERASE_CYCLES
#define MBED_TEST_ERASE_CYCLES 100
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_SIM_BLOCKDEVICE)
static uint32_t test_wear_leveling_size(uint32_t block_count)
{
MBED_TEST_SIM_BLOCKDEVICE_DECL;
// bring up to get block size
bd.init();
bd_size_t block_size = bd.get_erase_size();
bd.deinit();
SlicingBlockDevice slice(&bd, 0, block_count*block_size);
ExhaustibleBlockDevice ebd(&slice, MBED_TEST_ERASE_CYCLES);
printf("Testing size %llu bytes (%lux%llu) blocks\n",
block_count*block_size, block_count, block_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(MBED_TEST_BLOCK_COUNT / 2);
uint32_t cycles_2 = test_wear_leveling_size(MBED_TEST_BLOCK_COUNT / 1);
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(MBED_TEST_TIMEOUT, "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);
}

View File

@ -0,0 +1,684 @@
/* 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
void test_directory_tests()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = MBED_TEST_FILESYSTEM::format(&bd);
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_root_directory()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "/")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_creation()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "potato", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_file_creation()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "burito", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_iteration()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "/")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "potato");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "burito");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_failures()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "potato", 0777);
TEST_ASSERT_EQUAL(EEXIST, errno);
res = !((dd[0] = opendir("/fs/" "tomato")) != NULL);
TEST_ASSERT_EQUAL(ENOENT, errno);
res = !((dd[0] = opendir("/fs/" "burito")) != NULL);
TEST_ASSERT_EQUAL(ENOTDIR, errno);
res = !((fd[0] = fopen("/fs/" "tomato", "rb")) != NULL);
TEST_ASSERT_EQUAL(ENOENT, errno);
res = !((fd[0] = fopen("/fs/" "potato", "rb")) != NULL);
TEST_ASSERT_EQUAL(EISDIR, errno);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_nested_directories()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "potato/baked", 0777);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "potato/sweet", 0777);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "potato/fried", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "potato")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "baked");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "sweet");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "fried");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_multi_block_directory()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "cactus", 0777);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 128; i++) {
sprintf((char*)buffer, "/fs/" "cactus/test%d", i);
res = mkdir((char*)buffer, 0777);
TEST_ASSERT_EQUAL(0, res);
}
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "cactus")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
for (int i = 0; i < 128; i++) {
sprintf((char*)buffer, "test%d", i);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
}
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_remove()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = remove("/fs/" "potato");
TEST_ASSERT_EQUAL(EINVAL, errno);
res = remove("/fs/" "potato/sweet");
TEST_ASSERT_EQUAL(0, res);
res = remove("/fs/" "potato/baked");
TEST_ASSERT_EQUAL(0, res);
res = remove("/fs/" "potato/fried");
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "potato")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = remove("/fs/" "potato");
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "/")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "burito");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "cactus");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "/")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "burito");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "cactus");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_directory_rename()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "coldpotato", 0777);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "coldpotato/baked", 0777);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "coldpotato/sweet", 0777);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "coldpotato/fried", 0777);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = rename("/fs/" "coldpotato", "/fs/" "hotpotato");
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "hotpotato")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "baked");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "sweet");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "fried");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "warmpotato", 0777);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "warmpotato/mushy", 0777);
TEST_ASSERT_EQUAL(0, res);
res = rename("/fs/" "hotpotato", "/fs/" "warmpotato");
TEST_ASSERT_EQUAL(EINVAL, errno);
res = remove("/fs/" "warmpotato/mushy");
TEST_ASSERT_EQUAL(0, res);
res = rename("/fs/" "hotpotato", "/fs/" "warmpotato");
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "warmpotato")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "baked");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "sweet");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "fried");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "coldpotato", 0777);
TEST_ASSERT_EQUAL(0, res);
res = rename("/fs/" "warmpotato/baked", "/fs/" "coldpotato/baked");
TEST_ASSERT_EQUAL(0, res);
res = rename("/fs/" "warmpotato/sweet", "/fs/" "coldpotato/sweet");
TEST_ASSERT_EQUAL(0, res);
res = rename("/fs/" "warmpotato/fried", "/fs/" "coldpotato/fried");
TEST_ASSERT_EQUAL(0, res);
res = remove("/fs/" "coldpotato");
TEST_ASSERT_EQUAL(EINVAL, errno);
res = remove("/fs/" "warmpotato");
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "coldpotato")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "baked");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "sweet");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "fried");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}
Case cases[] = {
Case("Directory tests", test_directory_tests),
Case("Root directory", test_root_directory),
Case("Directory creation", test_directory_creation),
Case("File creation", test_file_creation),
Case("Directory iteration", test_directory_iteration),
Case("Directory failures", test_directory_failures),
Case("Nested directories", test_nested_directories),
Case("Multi-block directory", test_multi_block_directory),
Case("Directory remove", test_directory_remove),
Case("Directory rename", test_directory_rename),
};
Specification specification(test_setup, cases);
int main()
{
return !Harness::run(specification);
}

View File

@ -0,0 +1,449 @@
/* 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
void test_file_tests()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = MBED_TEST_FILESYSTEM::format(&bd);
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_simple_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "hello", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
size = strlen("Hello World!\n");
memcpy(wbuffer, "Hello World!\n", size);
res = fwrite(wbuffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "hello", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
size = strlen("Hello World!\n");
res = fread(rbuffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(rbuffer, wbuffer, size);
TEST_ASSERT_EQUAL(0, res);
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_small_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
size_t size = 32;
size_t chunk = 31;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "smallavacado", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
for (size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
res = fwrite(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 32;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "smallavacado", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = fread(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_medium_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
size_t size = 8192;
size_t chunk = 31;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "mediumavacado", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
for (size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
res = fwrite(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 8192;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "mediumavacado", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = fread(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_large_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
size_t size = 262144;
size_t chunk = 31;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "largeavacado", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
for (size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
res = fwrite(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 262144;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "largeavacado", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = fread(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_non_overlap_check()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
size_t size = 32;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "smallavacado", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = fread(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 8192;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "mediumavacado", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = fread(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
{
size_t size = 262144;
size_t chunk = 29;
srand(0);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "largeavacado", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
res = fread(buffer, 1, chunk, fd[0]);
TEST_ASSERT_EQUAL(chunk, res);
for (size_t b = 0; b < chunk && i+b < size; b++) {
res = buffer[b];
TEST_ASSERT_EQUAL(rand() & 0xff, res);
}
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_dir_check()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "/")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "hello");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "smallavacado");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "mediumavacado");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "largeavacado");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}
Case cases[] = {
Case("File tests", test_file_tests),
Case("Simple file test", test_simple_file_test),
Case("Small file test", test_small_file_test),
Case("Medium file test", test_medium_file_test),
Case("Large file test", test_large_file_test),
Case("Non-overlap check", test_non_overlap_check),
Case("Dir check", test_dir_check),
};
Specification specification(test_setup, cases);
int main()
{
return !Harness::run(specification);
}

View File

@ -0,0 +1,407 @@
/* 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
void test_parallel_tests()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = MBED_TEST_FILESYSTEM::format(&bd);
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_parallel_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "a", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[1] = fopen("/fs/" "b", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[2] = fopen("/fs/" "c", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[3] = fopen("/fs/" "d", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 10; i++) {
res = fwrite((const void*)"a", 1, 1, fd[0]);
TEST_ASSERT_EQUAL(1, res);
res = fwrite((const void*)"b", 1, 1, fd[1]);
TEST_ASSERT_EQUAL(1, res);
res = fwrite((const void*)"c", 1, 1, fd[2]);
TEST_ASSERT_EQUAL(1, res);
res = fwrite((const void*)"d", 1, 1, fd[3]);
TEST_ASSERT_EQUAL(1, res);
}
fclose(fd[0]);
fclose(fd[1]);
fclose(fd[2]);
fclose(fd[3]);
res = !((dd[0] = opendir("/fs/" "/")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "a");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "b");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "c");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "d");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "a", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[1] = fopen("/fs/" "b", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[2] = fopen("/fs/" "c", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[3] = fopen("/fs/" "d", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 10; i++) {
res = fread(buffer, 1, 1, fd[0]);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('a', res);
res = fread(buffer, 1, 1, fd[1]);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('b', res);
res = fread(buffer, 1, 1, fd[2]);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('c', res);
res = fread(buffer, 1, 1, fd[3]);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('d', res);
}
fclose(fd[0]);
fclose(fd[1]);
fclose(fd[2]);
fclose(fd[3]);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_parallel_remove_file_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "e", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 5; i++) {
res = fwrite((const void*)"e", 1, 1, fd[0]);
TEST_ASSERT_EQUAL(1, res);
}
res = remove("/fs/" "a");
TEST_ASSERT_EQUAL(0, res);
res = remove("/fs/" "b");
TEST_ASSERT_EQUAL(0, res);
res = remove("/fs/" "c");
TEST_ASSERT_EQUAL(0, res);
res = remove("/fs/" "d");
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 5; i++) {
res = fwrite((const void*)"e", 1, 1, fd[0]);
TEST_ASSERT_EQUAL(1, res);
}
fclose(fd[0]);
res = !((dd[0] = opendir("/fs/" "/")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "e");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "e", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 10; i++) {
res = fread(buffer, 1, 1, fd[0]);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('e', res);
}
fclose(fd[0]);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_remove_inconveniently_test()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "e", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[1] = fopen("/fs/" "f", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[2] = fopen("/fs/" "g", "wb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 5; i++) {
res = fwrite((const void*)"e", 1, 1, fd[0]);
TEST_ASSERT_EQUAL(1, res);
res = fwrite((const void*)"f", 1, 1, fd[1]);
TEST_ASSERT_EQUAL(1, res);
res = fwrite((const void*)"g", 1, 1, fd[2]);
TEST_ASSERT_EQUAL(1, res);
}
res = remove("/fs/" "f");
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 5; i++) {
res = fwrite((const void*)"e", 1, 1, fd[0]);
TEST_ASSERT_EQUAL(1, res);
res = fwrite((const void*)"f", 1, 1, fd[1]);
TEST_ASSERT_EQUAL(1, res);
res = fwrite((const void*)"g", 1, 1, fd[2]);
TEST_ASSERT_EQUAL(1, res);
}
fclose(fd[0]);
fclose(fd[1]);
fclose(fd[2]);
res = !((dd[0] = opendir("/fs/" "/")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_DIR, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "e");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "g");
TEST_ASSERT_EQUAL(0, res);
res = ed->d_type;
TEST_ASSERT_EQUAL(DT_REG, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "e", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[1] = fopen("/fs/" "g", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 10; i++) {
res = fread(buffer, 1, 1, fd[0]);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('e', res);
res = fread(buffer, 1, 1, fd[1]);
TEST_ASSERT_EQUAL(1, res);
res = buffer[0];
TEST_ASSERT_EQUAL('g', res);
}
fclose(fd[0]);
fclose(fd[1]);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}
Case cases[] = {
Case("Parallel tests", test_parallel_tests),
Case("Parallel file test", test_parallel_file_test),
Case("Parallel remove file test", test_parallel_remove_file_test),
Case("Remove inconveniently test", test_remove_inconveniently_test),
};
Specification specification(test_setup, cases);
int main()
{
return !Harness::run(specification);
}

View File

@ -0,0 +1,641 @@
/* 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 "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#error [NOT_SUPPORTED] Non-volatile block device required
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 480
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
void test_seek_tests()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = MBED_TEST_FILESYSTEM::format(&bd);
TEST_ASSERT_EQUAL(0, res);
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = mkdir("/fs/" "hello", 0777);
TEST_ASSERT_EQUAL(0, res);
for (int i = 0; i < 132; i++) {
sprintf((char*)buffer, "/fs/" "hello/kitty%d", i);
res = !((fd[0] = fopen((char*)buffer,
"ab")) != NULL);
TEST_ASSERT_EQUAL(0, res);
size = strlen("kittycatcat");
memcpy(buffer, "kittycatcat", size);
for (int j = 0; j < 132; j++) {
fwrite(buffer, 1, size, fd[0]);
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
}
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_simple_dir_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "hello")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
off_t pos;
int i;
for (i = 0; i < 4; i++) {
sprintf((char*)buffer, "kitty%d", i);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
pos = telldir(dd[0]);
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
seekdir(dd[0], pos);
sprintf((char*)buffer, "kitty%d", i);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
rewinddir(dd[0]);
sprintf((char*)buffer, "kitty%d", 0);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
seekdir(dd[0], pos);
sprintf((char*)buffer, "kitty%d", i);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_large_dir_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((dd[0] = opendir("/fs/" "hello")) != NULL);
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
off_t pos;
int i;
for (i = 0; i < 128; i++) {
sprintf((char*)buffer, "kitty%d", i);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
pos = telldir(dd[0]);
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
seekdir(dd[0], pos);
sprintf((char*)buffer, "kitty%d", i);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
rewinddir(dd[0]);
sprintf((char*)buffer, "kitty%d", 0);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, ".");
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, "..");
TEST_ASSERT_EQUAL(0, res);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
seekdir(dd[0], pos);
sprintf((char*)buffer, "kitty%d", i);
res = ((ed = readdir(dd[0])) != NULL);
TEST_ASSERT_EQUAL(1, res);
res = strcmp(ed->d_name, (char*)buffer);
TEST_ASSERT_EQUAL(0, res);
res = closedir(dd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_simple_file_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "hello/kitty42", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
off_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < 4; i++) {
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
pos = ftell(fd[0]);
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
rewind(fd[0]);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], -size, SEEK_CUR);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], -size, SEEK_END);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], 0, SEEK_CUR);
TEST_ASSERT_EQUAL(0, res);
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_large_file_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "hello/kitty42", "rb")) != NULL);
TEST_ASSERT_EQUAL(0, res);
off_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < 128; i++) {
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
pos = ftell(fd[0]);
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
rewind(fd[0]);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], -size, SEEK_CUR);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], -size, SEEK_END);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], 0, SEEK_CUR);
TEST_ASSERT_EQUAL(0, res);
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_simple_file_seek_and_write()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "hello/kitty42", "r+b")) != NULL);
TEST_ASSERT_EQUAL(0, res);
off_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < 4; i++) {
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
pos = ftell(fd[0]);
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
memcpy(buffer, "doggodogdog", size);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fwrite(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "doggodogdog", size);
TEST_ASSERT_EQUAL(0, res);
rewind(fd[0]);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "doggodogdog", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], -size, SEEK_END);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], 0, SEEK_CUR);
TEST_ASSERT_EQUAL(0, res);
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_large_file_seek_and_write()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "hello/kitty42", "r+b")) != NULL);
TEST_ASSERT_EQUAL(0, res);
off_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < 128; i++) {
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
if (i != 4) {
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
}
pos = ftell(fd[0]);
}
res = pos >= 0;
TEST_ASSERT_EQUAL(1, res);
memcpy(buffer, "doggodogdog", size);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fwrite(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "doggodogdog", size);
TEST_ASSERT_EQUAL(0, res);
rewind(fd[0]);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], pos, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "doggodogdog", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], -size, SEEK_END);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], 0, SEEK_CUR);
TEST_ASSERT_EQUAL(0, res);
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_boundary_seek_and_write()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "hello/kitty42", "r+b")) != NULL);
TEST_ASSERT_EQUAL(0, res);
size = strlen("hedgehoghog");
const off_t offsets[] = {512, 1020, 513, 1021, 511, 1019};
for (int i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
off_t off = offsets[i];
memcpy(buffer, "hedgehoghog", size);
res = fseek(fd[0], off, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fwrite(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = fseek(fd[0], off, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "hedgehoghog", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], 0, SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "kittycatcat", size);
TEST_ASSERT_EQUAL(0, res);
res = fflush(fd[0]);
TEST_ASSERT_EQUAL(0, res);
}
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
void test_out_of_bounds_seek()
{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{
res = fs.mount(&bd);
TEST_ASSERT_EQUAL(0, res);
res = !((fd[0] = fopen("/fs/" "hello/kitty42", "r+b")) != NULL);
TEST_ASSERT_EQUAL(0, res);
size = strlen("kittycatcat");
res = fseek(fd[0], 0, SEEK_END);
TEST_ASSERT_EQUAL(0, res);
res = ftell(fd[0]);
TEST_ASSERT_EQUAL(132*size, res);
res = fseek(fd[0], (132+4)*size,
SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(0, res);
memcpy(buffer, "porcupineee", size);
res = fwrite(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = fseek(fd[0], (132+4)*size,
SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "porcupineee", size);
TEST_ASSERT_EQUAL(0, res);
res = fseek(fd[0], 132*size,
SEEK_SET);
TEST_ASSERT_EQUAL(0, res);
res = fread(buffer, 1, size, fd[0]);
TEST_ASSERT_EQUAL(size, res);
res = memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size);
TEST_ASSERT_EQUAL(0, res);
res = fclose(fd[0]);
TEST_ASSERT_EQUAL(0, res);
res = fs.unmount();
TEST_ASSERT_EQUAL(0, res);
}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases)
{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}
Case cases[] = {
Case("Seek tests", test_seek_tests),
Case("Simple dir seek", test_simple_dir_seek),
Case("Large dir seek", test_large_dir_seek),
Case("Simple file seek", test_simple_file_seek),
Case("Large file seek", test_large_file_seek),
Case("Simple file seek and write", test_simple_file_seek_and_write),
Case("Large file seek and write", test_large_file_seek_and_write),
Case("Boundary seek and write", test_boundary_seek_and_write),
Case("Out-of-bounds seek", test_out_of_bounds_seek),
};
Specification specification(test_setup, cases);
int main()
{
return !Harness::run(specification);
}

View File

@ -0,0 +1,103 @@
"""
mbed SDK
Copyright (c) 2017-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.
"""
from __future__ import print_function
from mbed_host_tests import BaseHostTest
from time import sleep
class UnexpectedResetTest(BaseHostTest):
"""This test checks that a device's RTC keeps count through a reset
It does this by setting the RTC's time, triggering a reset,
delaying and then reading the RTC's time again to ensure
that the RTC is still counting.
"""
"""Number of times to reset the device in this test"""
RESET_COUNT = 20
RESET_DELAY_BASE = 1.0
RESET_DELAY_INC = 0.02
VALUE_PLACEHOLDER = "0"
def setup(self):
"""Register callbacks required for the test"""
self._error = False
generator = self.unexpected_reset_test()
generator.next()
def run_gen(key, value, time):
"""Run the generator, and fail testing if the iterator stops"""
if self._error:
return
try:
generator.send((key, value, time))
except StopIteration:
self._error = True
for resp in ("start", "read", "format_done", "reset_complete"):
self.register_callback(resp, run_gen)
def teardown(self):
"""No work to do here"""
pass
def unexpected_reset_test(self):
"""Generator for running the reset test
This function calls yield to wait for the next event from
the device. If the device gives the wrong response, then the
generator terminates by returing which raises a StopIteration
exception and fails the test.
"""
# Wait for start token
key, value, time = yield
if key != "start":
return
# Format the device before starting the test
self.send_kv("format", self.VALUE_PLACEHOLDER)
key, value, time = yield
if key != "format_done":
return
for i in range(self.RESET_COUNT):
self.send_kv("run", self.VALUE_PLACEHOLDER)
sleep(self.RESET_DELAY_BASE + self.RESET_DELAY_INC * i)
self.reset()
# Wait for start token
key, value, time = yield
self.log("Key from yield: %s" % key)
if key != "reset_complete":
return
self.send_kv("__sync", "00000000-0000-000000000-000000000000")
# Wait for start token
key, value, time = yield
if key != "start":
return
self.send_kv("exit", "pass")
yield # No more events expected

View File

@ -0,0 +1,21 @@
all: test_dirs test_files test_seek test_parallel
test_%: ../../littlefs/tests/test_%.sh
cp $< $(notdir $<)
sed -i -e 's/tests\//.\//' -e 's/echo/.\/echo.py/' $(notdir $<)
./clean.sh
ln -f -s replacements_mbed.yml replacements.yml
./$(notdir $<)
mkdir -p ../filesystem/$(patsubst test_%,%,$@)
cp main.cpp ../filesystem/$(patsubst test_%,%,$@)/main.cpp
./clean.sh
ln -f -s replacements_retarget.yml replacements.yml
./$(notdir $<)
mkdir -p ../filesystem_retarget/$(patsubst test_%,%,$@)
cp main.cpp ../filesystem_retarget/$(patsubst test_%,%,$@)/main.cpp
clean:
./clean.sh

View File

@ -0,0 +1,4 @@
#!/bin/bash
rm -f main.cpp
rm -f template_all_names.txt

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
import re
import sys
import subprocess
import os
def main(*args):
desc = ' '.join(args).strip('-= ')
name = 'test_' + desc.lower().replace(' ', '_').replace('-', '_')
exists = os.path.isfile('template_all_names.txt')
with open('template_all_names.txt', 'a') as file:
file.write(name + '\n')
file.write(desc + '\n')
with open('template_unit.fmt') as file:
template = file.read()
template_header, template_footer = template.split('{test}')
if exists:
with open('main.cpp', 'a') as file:
file.write(template_footer.format(
test_name=name))
if name != 'test_results':
with open('main.cpp', 'a') as file:
file.write(template_header.format(
test_name=name))
if __name__ == "__main__":
main(*sys.argv[1:])

View File

@ -0,0 +1,33 @@
- ['lfs_format\(&lfs, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)']
- ['lfs_mount\(&lfs, &cfg\)', 'fs.mount(&bd)']
- ['lfs_unmount\(&lfs\)', 'fs.unmount()']
- ['lfs_mkdir\(&lfs, (.*)\)', 'fs.mkdir(\1, 0777)']
- ['lfs_remove\(&lfs, (.*)\)', 'fs.remove(\1)']
- ['lfs_rename\(&lfs, (.*), ?(.*)\)', 'fs.rename(\1, \2)']
- ['lfs_dir_open\(&lfs, &dir\[(.*)\], ?(.*)\)', 'dir[\1].open(&fs, \2)']
- ['lfs_dir_close\(&lfs, &dir\[(.*)\]\)', 'dir[\1].close()']
- ['lfs_dir_read\(&lfs, &dir\[(.*)\], &info\)', 'dir[\1].read(&ent)']
- ['lfs_dir_seek\(&lfs, &dir\[(.*)\], ?(.*)\).*;', 'dir[\1].seek(\2);'] # no dir errors
- ['lfs_dir_rewind\(&lfs, &dir\[(.*)\]\).*;', 'dir[\1].rewind();'] # no dir errors
- ['lfs_dir_tell\(&lfs, &dir\[(.*)\]\)', 'dir[\1].tell()']
- ['lfs_file_open\(&lfs, &file\[(.*)\], ?(.*)\)', 'file[\1].open(&fs, \2)']
- ['lfs_file_close\(&lfs, &file\[(.*)\]\)', 'file[\1].close()']
- ['lfs_file_sync\(&lfs, &file\[(.*)\]\)', 'file[\1].sync()']
- ['lfs_file_write\(&lfs, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].write(\2, \3)']
- ['lfs_file_read\(&lfs, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].read(\2, \3)']
- ['lfs_file_seek\(&lfs, &file\[(.*)\], ?(.*)\)', 'file[\1].seek(\2)']
- ['lfs_file_tell\(&lfs, &file\[(.*)\]\)', 'file[\1].tell()']
- ['lfs_file_rewind\(&lfs, &file\[(.*)\]\).*;', 'file[\1].rewind();'] # no errors
- ['lfs_file_size\(&lfs, &file\[(.*)\]\)', 'file[\1].size()']
- ['LFS_TYPE_([A-Z]+)', 'DT_\1']
- ['LFS_O_([A-Z]+)', 'O_\1']
- ['LFS_SEEK_([A-Z]+)', 'SEEK_\1']
- ['LFS_ERR_([A-Z]+)', '-E\1']
- ['lfs_(s?)size_t', '\1size_t']
- ['lfs_soff_t', 'off_t']
- ['info\.name', 'ent.d_name']
- ['info\.type', 'ent.d_type']
- ['^.*info\.size.*$', ''] # dirent sizes not supported

View File

@ -0,0 +1,37 @@
- ['lfs_format\(&lfs, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)']
- ['lfs_mount\(&lfs, &cfg\)', 'fs.mount(&bd)']
- ['lfs_unmount\(&lfs\)', 'fs.unmount()']
- ['lfs_mkdir\(&lfs, (.*)\)', 'mkdir("/fs/" \1, 0777)']
- ['lfs_remove\(&lfs, (.*)\)', 'remove("/fs/" \1)']
- ['lfs_rename\(&lfs, (.*), ?(.*)\)', 'rename("/fs/" \1, "/fs/" \2)']
- ['lfs_dir_open\(&lfs, &dir\[(.*)\], ?(.*)\)', '!((dd[\1] = opendir("/fs/" \2)) != NULL)']
- ['lfs_dir_close\(&lfs, &dir\[(.*)\]\)', 'closedir(dd[\1])']
- ['lfs_dir_read\(&lfs, &dir\[(.*)\], &info\)', '((ed = readdir(dd[\1])) != NULL)']
- ['lfs_dir_seek\(&lfs, &dir\[(.*)\], ?(.*)\).*;', 'seekdir(dd[\1], \2);'] # no dir errors
- ['lfs_dir_rewind\(&lfs, &dir\[(.*)\]\).*;', 'rewinddir(dd[\1]);'] # no dir errors
- ['lfs_dir_tell\(&lfs, &dir\[(.*)\]\)', 'telldir(dd[\1])']
- ['lfs_file_open\(&lfs, &file\[(.*)\], ?(.*)\)', '!((fd[\1] = fopen("/fs/" \2)) != NULL)']
- ['lfs_file_close\(&lfs, &file\[(.*)\]\)', 'fclose(fd[\1])']
- ['lfs_file_sync\(&lfs, &file\[(.*)\]\)', 'fflush(fd[\1])']
- ['lfs_file_write\(&lfs, &file\[(.*)\], ?(.*), (.*)\)', 'fwrite(\2, 1, \3, fd[\1])']
- ['lfs_file_read\(&lfs, &file\[(.*)\], ?(.*), (.*)\)', 'fread(\2, 1, \3, fd[\1])']
- ['lfs_file_tell\(&lfs, &file\[(.*)\]\)', 'ftell(fd[\1])']
- ['lfs_file_rewind\(&lfs, &file\[(.*)\]\).*;', 'rewind(fd[\1]);'] # no errors
- ['LFS_TYPE_([A-Z]+)', 'DT_\1']
- ['LFS_SEEK_([A-Z]+)', 'SEEK_\1']
- ['LFS_ERR_([A-Z]+)', '-E\1']
- ['lfs_(s?)size_t', '\1size_t']
- ['lfs_soff_t', 'off_t']
- ['info\.name', 'ed->d_name']
- ['info\.type', 'ed->d_type']
- ['^.*info\.size.*$', ''] # dirent sizes not supported
- ['LFS_O_WRONLY \| LFS_O_CREAT \| LFS_O_APPEND', '"ab"']
- ['LFS_O_WRONLY \| LFS_O_TRUNC', '"wb"']
- ['LFS_O_CREAT \| LFS_O_WRONLY', '"wb"']
- ['LFS_O_WRONLY \| LFS_O_CREAT', '"wb"']
- ['LFS_O_RDONLY', '"rb"']
- ['LFS_O_RDWR', '"r+b"']

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python
import re
import sys
import subprocess
import os
def main(*args):
with open('main.cpp') as file:
tests = file.read()
cases = []
with open('template_all_names.txt') as file:
while True:
name = file.readline().strip('\n')
desc = file.readline().strip('\n')
if name == 'test_results':
break
cases.append((name, desc))
with open('template_wrapper.fmt') as file:
template = file.read()
with open('main.cpp', 'w') as file:
file.write(template.format(
tests=tests,
test_cases='\n'.join(
4*' '+'Case("{desc}", {name}),'.format(
name=name, desc=desc) for name, desc in cases)))
if __name__ == "__main__":
main(*sys.argv[1:])

View File

@ -0,0 +1,4 @@
{{
{test}
}}

View File

@ -0,0 +1,8 @@
void {test_name}() {{
int res = bd.init();
TEST_ASSERT_EQUAL(0, res);
{test}
res = bd.deinit();
TEST_ASSERT_EQUAL(0, res);
}}

View File

@ -0,0 +1,86 @@
#include "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include <stdlib.h>
#include <errno.h>
using namespace utest::v1;
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
#ifndef MBED_TEST_BLOCKDEVICE
#define MBED_TEST_BLOCKDEVICE SPIFBlockDevice
#define MBED_TEST_BLOCKDEVICE_DECL SPIFBlockDevice bd(PTE2, PTE4, PTE1, PTE5)
#endif
#ifndef MBED_TEST_BLOCKDEVICE_DECL
#define MBED_TEST_BLOCKDEVICE_DECL MBED_TEST_BLOCKDEVICE bd
#endif
#ifndef MBED_TEST_FILES
#define MBED_TEST_FILES 4
#endif
#ifndef MBED_TEST_DIRS
#define MBED_TEST_DIRS 4
#endif
#ifndef MBED_TEST_BUFFER
#define MBED_TEST_BUFFER 8192
#endif
#ifndef MBED_TEST_TIMEOUT
#define MBED_TEST_TIMEOUT 120
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#include INCLUDE(MBED_TEST_BLOCKDEVICE)
MBED_TEST_FILESYSTEM_DECL;
MBED_TEST_BLOCKDEVICE_DECL;
Dir dir[MBED_TEST_DIRS];
File file[MBED_TEST_FILES];
DIR *dd[MBED_TEST_DIRS];
FILE *fd[MBED_TEST_FILES];
struct dirent ent;
struct dirent *ed;
size_t size;
uint8_t buffer[MBED_TEST_BUFFER];
uint8_t rbuffer[MBED_TEST_BUFFER];
uint8_t wbuffer[MBED_TEST_BUFFER];
// tests
{tests}
// test setup
utest::v1::status_t test_setup(const size_t number_of_cases) {{
GREENTEA_SETUP(MBED_TEST_TIMEOUT, "default_auto");
return verbose_test_setup_handler(number_of_cases);
}}
Case cases[] = {{
{test_cases}
}};
Specification specification(test_setup, cases);
int main() {{
return !Harness::run(specification);
}}

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
import re
import sys
import subprocess
import os
import yaml
def generate(test):
with open('replacements.yml') as file:
replacements = yaml.load(file)
lines = []
for line in re.split('(?<=[;{}])\n', test.read()):
for pattern, replacement in replacements:
line = re.sub(pattern, replacement, line, 0, re.DOTALL | re.MULTILINE)
match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE)
if match:
tab, test, expect = match.groups()
lines.append(tab+'res = {test};'.format(test=test.strip()))
lines.append(tab+'TEST_ASSERT_EQUAL({expect}, res);'.format(
name=re.match('\w*', test.strip()).group(),
expect=expect.strip()))
else:
lines.append(line)
lines = lines[:-1]
with open('template_subunit.fmt') as file:
template = file.read()
with open('main.cpp', 'a') as file:
file.write(template.format(
test=('\n'.join(
4*' '+line.replace('\n', '\n'+4*' ')
for line in lines))))
def main(test=None):
if test and not test.startswith('-'):
with open(test) as file:
generate(file)
else:
generate(sys.stdin)
if __name__ == "__main__":
main(*sys.argv[1:])

View File

@ -0,0 +1,698 @@
/* 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 "ExhaustibleBlockDevice.h"
#include "FileSystem.h"
#include "atomic_usage.h"
// test configuration
#ifndef MBED_TEST_FILESYSTEM
#define MBED_TEST_FILESYSTEM LittleFileSystem
#endif
#ifndef MBED_TEST_FILESYSTEM_DECL
#define MBED_TEST_FILESYSTEM_DECL MBED_TEST_FILESYSTEM fs("fs")
#endif
// declarations
#define STRINGIZE(x) STRINGIZE2(x)
#define STRINGIZE2(x) #x
#define INCLUDE(x) STRINGIZE(x.h)
#include INCLUDE(MBED_TEST_FILESYSTEM)
#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)(FileSystem *fs);
typedef bool (*test_function_bool_t)(FileSystem *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(FileSystem *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(FileSystem *fs)
{
DEBUG("perform_file_rename()\n");
struct stat st;
int res = fs->stat(FILE_RENAME_A, &st);
const char *src = (res == 0) ? FILE_RENAME_A : FILE_RENAME_B;
const char *dst = (res == 0) ? FILE_RENAME_B : FILE_RENAME_A;
DEBUG(" stat result %i\n", res);
TEST_ASSERT_OR_EXIT((res == -ENOENT) || (res == 0));
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(FileSystem *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 (file.open(fs, filenames[i], O_RDONLY) == 0) {
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(FileSystem *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(FileSystem *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(FileSystem *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(FileSystem *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(FileSystem *fs)
{
DEBUG("perform_directory_rename()\n");
struct stat st;
int res = fs->stat(DIRECTORY_RENAME_A, &st);
const char *src = (res == 0) ? DIRECTORY_RENAME_A : DIRECTORY_RENAME_B;
const char *dst = (res == 0) ? DIRECTORY_RENAME_B : DIRECTORY_RENAME_A;
DEBUG(" stat result %i\n", res);
TEST_ASSERT_OR_EXIT((res == -ENOENT) || (res == 0));
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(FileSystem *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((res == -ENOENT) || (res == 0));
if (res == 0) {
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(FileSystem *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(FileSystem *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(FileSystem *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)
{
MBED_TEST_FILESYSTEM_DECL;
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)
{
MBED_TEST_FILESYSTEM_DECL;
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(FileSystem *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)
{
MBED_TEST_FILESYSTEM_DECL;
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)
{
MBED_TEST_FILESYSTEM_DECL;
fs.mount(bd);
for (size_t i = 0; i < ARRAY_LENGTH(atomic_test_entries); i++) {
atomic_test_entries[i].check(&fs);
}
fs.unmount();
}

View File

@ -0,0 +1,71 @@
/* 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"
/**
* 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

View File

@ -0,0 +1,48 @@
script:
# make sure example can at least compile
- sed -n '/``` c/,/```/{/```/d; p;}' README.md > test.c &&
CFLAGS='
-Duser_provided_block_device_read=NULL
-Duser_provided_block_device_prog=NULL
-Duser_provided_block_device_erase=NULL
-Duser_provided_block_device_sync=NULL
-include stdio.h -Werror' make all size
# run tests
- make test QUIET=1
# run tests with a few different configurations
- CFLAGS="-DLFS_READ_SIZE=1 -DLFS_PROG_SIZE=1" make test QUIET=1
- CFLAGS="-DLFS_READ_SIZE=512 -DLFS_PROG_SIZE=512" make test QUIET=1
- CFLAGS="-DLFS_BLOCK_COUNT=1023" make test QUIET=1
- CFLAGS="-DLFS_LOOKAHEAD=2048" make test QUIET=1
# self-host with littlefs-fuse for fuzz test
- make -C littlefs-fuse
- littlefs-fuse/lfs --format /dev/loop0
- littlefs-fuse/lfs /dev/loop0 mount
- ls mount
- mkdir mount/littlefs
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs
- cd mount/littlefs
- ls
- make -B test_dirs QUIET=1
before_install:
- fusermount -V
- gcc --version
install:
- sudo apt-get install libfuse-dev
- git clone --depth 1 https://github.com/geky/littlefs-fuse
before_script:
- rm -rf littlefs-fuse/littlefs/*
- cp -r $(git ls-tree --name-only HEAD) littlefs-fuse/littlefs
- mkdir mount
- sudo chmod a+rw /dev/loop0
- dd if=/dev/zero bs=512 count=2048 of=disk
- losetup /dev/loop0 disk

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.

View File

@ -0,0 +1,63 @@
TARGET = lfs
CC = gcc
AR = ar
SIZE = size
SRC += $(wildcard *.c emubd/*.c)
OBJ := $(SRC:.c=.o)
DEP := $(SRC:.c=.d)
ASM := $(SRC:.c=.s)
TEST := $(patsubst tests/%.sh,%,$(wildcard tests/test_*))
SHELL = /bin/bash -o pipefail
ifdef DEBUG
CFLAGS += -O0 -g3
else
CFLAGS += -Os
endif
ifdef WORD
CFLAGS += -m$(WORD)
endif
CFLAGS += -I.
CFLAGS += -std=c99 -Wall -pedantic
all: $(TARGET)
asm: $(ASM)
size: $(OBJ)
$(SIZE) -t $^
.SUFFIXES:
test: test_format test_dirs test_files test_seek test_parallel \
test_alloc test_paths test_orphan test_move test_corrupt
test_%: tests/test_%.sh
ifdef QUIET
./$< | sed -n '/^[-=]/p'
else
./$<
endif
-include $(DEP)
$(TARGET): $(OBJ)
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
%.a: $(OBJ)
$(AR) rcs $@ $^
%.o: %.c
$(CC) -c -MMD $(CFLAGS) $< -o $@
%.s: %.c
$(CC) -S $(CFLAGS) $< -o $@
clean:
rm -f $(TARGET)
rm -f $(OBJ)
rm -f $(DEP)
rm -f $(ASM)

View File

@ -0,0 +1,151 @@
## The little filesystem
A little fail-safe filesystem designed for embedded systems.
```
| | | .---._____
.-----. | |
--|o |---| littlefs |
--| |---| |
'-----' '----------'
| | |
```
**Bounded RAM/ROM** - The littlefs is designed to work with a limited amount
of memory. Recursion is avoided, and dynamic memory is limited to configurable
buffers that can be provided statically.
**Power-loss resilient** - The littlefs is designed for systems that may have
random power failures. The littlefs has strong copy-on-write guaruntees, and
storage on disk is always kept in a valid state.
**Wear leveling** - Because the most common form of embedded storage is erodible
flash memories, littlefs provides a form of dynamic wear leveling for systems
that cannot fit a full flash translation layer.
## Example
Here's a simple example that updates a file named `boot_count` every time
main runs. The program can be interrupted at any time without losing track
of how many times it has been booted and without corrupting the filesystem:
``` c
#include "lfs.h"
// variables used by the filesystem
lfs_t lfs;
lfs_file_t file;
// configuration of the filesystem is provided by this struct
const struct lfs_config cfg = {
// block device operations
.read = user_provided_block_device_read,
.prog = user_provided_block_device_prog,
.erase = user_provided_block_device_erase,
.sync = user_provided_block_device_sync,
// block device configuration
.read_size = 16,
.prog_size = 16,
.block_size = 4096,
.block_count = 128,
.lookahead = 128,
};
// entry point
int main(void) {
// mount the filesystem
int err = lfs_mount(&lfs, &cfg);
// reformat if we can't mount the filesystem
// this should only happen on the first boot
if (err) {
lfs_format(&lfs, &cfg);
lfs_mount(&lfs, &cfg);
}
// read current count
uint32_t boot_count = 0;
lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));
// update boot count
boot_count += 1;
lfs_file_rewind(&lfs, &file);
lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));
// remember the storage is not updated until the file is closed successfully
lfs_file_close(&lfs, &file);
// release any resources we were using
lfs_unmount(&lfs);
// print the boot count
printf("boot_count: %d\n", boot_count);
}
```
## Usage
Detailed documentation (or at least as much detail as is currently available)
can be cound in the comments in [lfs.h](lfs.h).
As you may have noticed, littlefs takes in a configuration structure that
defines how the filesystem operates. The configuration struct provides the
filesystem with the block device operations and dimensions, tweakable
parameters that trade memory usage for performance and optional
static buffers if the user wants to avoid dynamic memory.
The state of the littlefs is stored in the `lfs_t` type, which is left up
to the user to allocate, allowing multiple filesystems to be in use
simultaneously. With the `lfs_t` and configuration struct, a user can
format a block device or mount the filesystem.
Once mounted, the littlefs provides a full set of POSIX-like file and
directory functions, with the deviation that the allocation of filesystem
structures must be provided by the user.
All POSIX operations, such as remove and rename, are atomic, even in the event
of power loss. Additionally, no file updates are actually committed to the
filesystem until sync or close is called on the file.
## Other notes
All littlefs have the potential to return a negative error code. The errors
can be either one of those found in the `enum lfs_error` in [lfs.h](lfs.h),
or an error returned by the user's block device operations.
It should also be noted that the current implementation of littlefs doesn't
really do anything to ensure that the data written to disk is machine portable.
This is fine as long as all of the involved machines share endianness
(little-endian) and don't have strange padding requirements.
## Reference material
[DESIGN.md](DESIGN.md) - DESIGN.md contains a fully detailed dive into how
littlefs actually works. We would encourage you to read it because the
solutions and tradeoffs at work here are quite interesting.
[SPEC.md](SPEC.md) - SPEC.md contains the on-disk specification of littlefs
with all the nitty-gritty details. This can be useful for developing tooling.
## Testing
The littlefs comes with a test suite designed to run on a PC using the
[emulated block device](emubd/lfs_emubd.h) found in the emubd directory.
The tests assume a Linux environment and can be started with make:
``` bash
make test
```
## Related projects
[littlefs-fuse](https://github.com/geky/littlefs-fuse) - A [FUSE](https://github.com/libfuse/libfuse)
wrapper for littlefs. The project allows you to mount littlefs directly in a
Linux machine. Can be useful for debugging littlefs if you have an SD card
handy.
[littlefs-js](https://github.com/geky/littlefs-js) - A JavaScript wrapper for
littlefs. I'm not sure why you would want this, but it is handy for demos.
You can see it in action [here](http://littlefs.geky.net/demo.html).

View File

@ -0,0 +1,370 @@
## The little filesystem technical specification
This is the technical specification of the little filesystem. This document
covers the technical details of how the littlefs is stored on disk for
introspection and tooling development. This document assumes you are
familiar with the design of the littlefs. For more information on how littlefs
works, check out [DESIGN.md](DESIGN.md).
```
| | | .---._____
.-----. | |
--|o |---| littlefs |
--| |---| |
'-----' '----------'
| | |
```
## Some important details
- The littlefs is a block-based filesystem. The disk is divided into
an array of evenly sized blocks that are used as the logical unit of storage
in littlefs. Block pointers are stored in 32 bits.
- There is no explicit free-list stored on disk. The littlefs only knows what
is in use in the filesystem.
- The littlefs uses the value of 0xffffffff to represent a null block-pointer.
- All values in littlefs are stored in little-endian byte order.
## Directories/Metadata pairs
Metadata pairs form the backbone of the littlefs and provide a system for
atomic updates. Even the superblock is stored in a metadata pair.
As its name suggests, a metadata pair is stored in two blocks, with one block
acting as a redundant backup in case the other is corrupted. These two blocks
could be anywhere in the disk and may not be next to each other, so any
pointers to directory pairs need to be stored as two block pointers.
Here's the layout of metadata blocks on disk:
| offset | size | description |
|--------|---------------|----------------|
| 0x00 | 32 bits | revision count |
| 0x04 | 32 bits | dir size |
| 0x08 | 64 bits | tail pointer |
| 0x10 | size-16 bytes | dir entries |
| 0x00+s | 32 bits | crc |
**Revision count** - Incremented every update, only the uncorrupted
metadata-block with the most recent revision count contains the valid metadata.
Comparison between revision counts must use sequence comparison because the
revision counts may overflow.
**Dir size** - Size in bytes of the contents in the current metadata block,
including the metadata-pair metadata. Additionally, the highest bit of the
dir size may be set to indicate that the directory's contents continue on the
next metadata-pair pointed to by the tail pointer.
**Tail pointer** - Pointer to the next metadata-pair in the filesystem.
A null pair-pointer (0xffffffff, 0xffffffff) indicates the end of the list.
If the highest bit in the dir size is set, this points to the next
metadata-pair in the current directory. Otherwise, it points to an arbitrary
metadata-pair. Starting with the superblock, the tail-pointers form a
linked-list containing all metadata-pairs in the filesystem.
**CRC** - 32 bit CRC used to detect corruption from power-lost, from block
end-of-life or just from noise on the storage bus. The CRC is appended to
the end of each metadata-block. The littlefs uses the standard CRC-32, which
uses a polynomial of 0x04c11db7, initialized with 0xffffffff.
Here's an example of a simple directory stored on disk:
```
(32 bits) revision count = 10 (0x0000000a)
(32 bits) dir size = 154 bytes, end of dir (0x0000009a)
(64 bits) tail pointer = 37, 36 (0x00000025, 0x00000024)
(32 bits) crc = 0xc86e3106
00000000: 0a 00 00 00 9a 00 00 00 25 00 00 00 24 00 00 00 ........%...$...
00000010: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 22 "...........tea"
00000020: 08 00 06 07 00 00 00 06 00 00 00 63 6f 66 66 65 ...........coffe
00000030: 65 22 08 00 04 09 00 00 00 08 00 00 00 73 6f 64 e"...........sod
00000040: 61 22 08 00 05 1d 00 00 00 1c 00 00 00 6d 69 6c a"...........mil
00000050: 6b 31 22 08 00 05 1f 00 00 00 1e 00 00 00 6d 69 k1"...........mi
00000060: 6c 6b 32 22 08 00 05 21 00 00 00 20 00 00 00 6d lk2"...!... ...m
00000070: 69 6c 6b 33 22 08 00 05 23 00 00 00 22 00 00 00 ilk3"...#..."...
00000080: 6d 69 6c 6b 34 22 08 00 05 25 00 00 00 24 00 00 milk4"...%...$..
00000090: 00 6d 69 6c 6b 35 06 31 6e c8 .milk5.1n.
```
A note about the tail pointer linked-list: Normally, this linked-list is
threaded through the entire filesystem. However, after power loss, this
linked-list may become out of sync with the rest of the filesystem.
- The linked-list may contain a directory that has actually been removed.
- The linked-list may contain a metadata pair that has not been updated after
a block in the pair has gone bad.
The threaded linked-list must be checked for these errors before it can be
used reliably. Fortunately, the threaded linked-list can simply be ignored
if littlefs is mounted read-only.
## Entries
Each metadata block contains a series of entries that follow a standard
layout. An entry contains the type of the entry, along with a section for
entry-specific data, attributes and a name.
Here's the layout of entries on disk:
| offset | size | description |
|---------|------------------------|----------------------------|
| 0x0 | 8 bits | entry type |
| 0x1 | 8 bits | entry length |
| 0x2 | 8 bits | attribute length |
| 0x3 | 8 bits | name length |
| 0x4 | entry length bytes | entry-specific data |
| 0x4+e | attribute length bytes | system-specific attributes |
| 0x4+e+a | name length bytes | entry name |
**Entry type** - Type of the entry, currently this is limited to the following:
- 0x11 - file entry.
- 0x22 - directory entry.
- 0x2e - superblock entry.
Additionally, the type is broken into two 4 bit nibbles, with the upper nibble
specifying the type's data structure used when scanning the filesystem. The
lower nibble clarifies the type further when multiple entries share the same
data structure.
The highest bit is reserved for marking the entry as "moved". If an entry
is marked as "moved", the entry may also exist somewhere else in the
filesystem. If the entry exists elsewhere, this entry must be treated as
though it does not exist.
**Entry length** - Length in bytes of the entry-specific data. This does
not include the entry type size, attributes or name. The full size in bytes
of the entry is 4 plus entry length plus attribute length plus name length.
**Attribute length** - Length of system-specific attributes in bytes. Because
attributes are system specific, there is not much guarantee on the values in
this section, and systems are expected to work even when it is empty. See the
[attributes](#entry-attributes) section for more details.
**Name length** - Length of the entry name. Entry names are stored as utf8,
though most systems will probably only support ascii. Entry names can not
contain '/' and can not be '.' or '..' because these are a part of the syntax of
filesystem paths.
Here's an example of a simple entry stored on disk:
```
(8 bits) entry type = file (0x11)
(8 bits) entry length = 8 bytes (0x08)
(8 bits) attribute length = 0 bytes (0x00)
(8 bits) name length = 12 bytes (0x0c)
(8 bytes) entry data = 05 00 00 00 20 00 00 00
(12 bytes) entry name = smallavacado
00000000: 11 08 00 0c 05 00 00 00 20 00 00 00 73 6d 61 6c ........ ...smal
00000010: 6c 61 76 61 63 61 64 6f lavacado
```
## Superblock
The superblock is the anchor for the littlefs. The superblock is stored as
a metadata pair containing a single superblock entry. It is through the
superblock that littlefs can access the rest of the filesystem.
The superblock can always be found in blocks 0 and 1; however, fetching the
superblock requires knowing the block size. The block size can be guessed by
searching the beginning of disk for the string "littlefs", though currently,
the filesystems relies on the user providing the correct block size.
The superblock is the most valuable block in the filesystem. It is updated
very rarely, only during format or when the root directory must be moved. It
is encouraged to always write out both superblock pairs even though it is not
required.
Here's the layout of the superblock entry:
| offset | size | description |
|--------|------------------------|----------------------------------------|
| 0x00 | 8 bits | entry type (0x2e for superblock entry) |
| 0x01 | 8 bits | entry length (20 bytes) |
| 0x02 | 8 bits | attribute length |
| 0x03 | 8 bits | name length (8 bytes) |
| 0x04 | 64 bits | root directory |
| 0x0c | 32 bits | block size |
| 0x10 | 32 bits | block count |
| 0x14 | 32 bits | version |
| 0x18 | attribute length bytes | system-specific attributes |
| 0x18+a | 8 bytes | magic string ("littlefs") |
**Root directory** - Pointer to the root directory's metadata pair.
**Block size** - Size of the logical block size used by the filesystem.
**Block count** - Number of blocks in the filesystem.
**Version** - The littlefs version encoded as a 32 bit value. The upper 16 bits
encodes the major version, which is incremented when a breaking-change is
introduced in the filesystem specification. The lower 16 bits encodes the
minor version, which is incremented when a backward-compatible change is
introduced. Nonstandard Attribute changes do not change the version. This
specification describes version 1.1 (0x00010001), which is the first version
of littlefs.
**Magic string** - The magic string "littlefs" takes the place of an entry
name.
Here's an example of a complete superblock:
```
(32 bits) revision count = 3 (0x00000003)
(32 bits) dir size = 52 bytes, end of dir (0x00000034)
(64 bits) tail pointer = 3, 2 (0x00000003, 0x00000002)
(8 bits) entry type = superblock (0x2e)
(8 bits) entry length = 20 bytes (0x14)
(8 bits) attribute length = 0 bytes (0x00)
(8 bits) name length = 8 bytes (0x08)
(64 bits) root directory = 3, 2 (0x00000003, 0x00000002)
(32 bits) block size = 512 bytes (0x00000200)
(32 bits) block count = 1024 blocks (0x00000400)
(32 bits) version = 1.1 (0x00010001)
(8 bytes) magic string = littlefs
(32 bits) crc = 0xc50b74fa
00000000: 03 00 00 00 34 00 00 00 03 00 00 00 02 00 00 00 ....4...........
00000010: 2e 14 00 08 03 00 00 00 02 00 00 00 00 02 00 00 ................
00000020: 00 04 00 00 01 00 01 00 6c 69 74 74 6c 65 66 73 ........littlefs
00000030: fa 74 0b c5 .t..
```
## Directory entries
Directories are stored in entries with a pointer to the first metadata pair
in the directory. Keep in mind that a directory may be composed of multiple
metadata pairs connected by the tail pointer when the highest bit in the dir
size is set.
Here's the layout of a directory entry:
| offset | size | description |
|--------|------------------------|-----------------------------------------|
| 0x0 | 8 bits | entry type (0x22 for directory entries) |
| 0x1 | 8 bits | entry length (8 bytes) |
| 0x2 | 8 bits | attribute length |
| 0x3 | 8 bits | name length |
| 0x4 | 64 bits | directory pointer |
| 0xc | attribute length bytes | system-specific attributes |
| 0xc+a | name length bytes | directory name |
**Directory pointer** - Pointer to the first metadata pair in the directory.
Here's an example of a directory entry:
```
(8 bits) entry type = directory (0x22)
(8 bits) entry length = 8 bytes (0x08)
(8 bits) attribute length = 0 bytes (0x00)
(8 bits) name length = 3 bytes (0x03)
(64 bits) directory pointer = 5, 4 (0x00000005, 0x00000004)
(3 bytes) name = tea
00000000: 22 08 00 03 05 00 00 00 04 00 00 00 74 65 61 "...........tea
```
## File entries
Files are stored in entries with a pointer to the head of the file and the
size of the file. This is enough information to determine the state of the
CTZ skip-list that is being referenced.
How files are actually stored on disk is a bit complicated. The full
explanation of CTZ skip-lists can be found in [DESIGN.md](DESIGN.md#ctz-skip-lists).
A terribly quick summary: For every nth block where n is divisible by 2^x,
the block contains a pointer to block n-2^x. These pointers are stored in
increasing order of x in each block of the file preceding the data in the
block.
The maximum number of pointers in a block is bounded by the maximum file size
divided by the block size. With 32 bits for file size, this results in a
minimum block size of 104 bytes.
Here's the layout of a file entry:
| offset | size | description |
|--------|------------------------|------------------------------------|
| 0x0 | 8 bits | entry type (0x11 for file entries) |
| 0x1 | 8 bits | entry length (8 bytes) |
| 0x2 | 8 bits | attribute length |
| 0x3 | 8 bits | name length |
| 0x4 | 32 bits | file head |
| 0x8 | 32 bits | file size |
| 0xc | attribute length bytes | system-specific attributes |
| 0xc+a | name length bytes | directory name |
**File head** - Pointer to the block that is the head of the file's CTZ
skip-list.
**File size** - Size of file in bytes.
Here's an example of a file entry:
```
(8 bits) entry type = file (0x11)
(8 bits) entry length = 8 bytes (0x08)
(8 bits) attribute length = 0 bytes (0x00)
(8 bits) name length = 12 bytes (0x03)
(32 bits) file head = 543 (0x0000021f)
(32 bits) file size = 256 KB (0x00040000)
(12 bytes) name = largeavacado
00000000: 11 08 00 0c 1f 02 00 00 00 00 04 00 6c 61 72 67 ............larg
00000010: 65 61 76 61 63 61 64 6f eavacado
```
## Entry attributes
Each dir entry can have up to 256 bytes of system-specific attributes. Because
these attributes are system-specific, they may not be portable between
different systems. For this reason, all attributes must be optional. A minimal
littlefs driver must be able to get away with supporting no attributes at all.
For some level of portability, littlefs has a simple scheme for attributes.
Each attribute is prefixed with an 8-bit type that indicates what the attribute
is. The length of attributes may also be determined from this type. Attributes
in an entry should be sorted based on portability because attribute parsing
will end when it hits the first attribute it does not understand.
Each system should choose a 4-bit value to prefix all attribute types with to
avoid conflicts with other systems. Additionally, littlefs drivers that support
attributes should provide an "ignore attributes" flag to users in case attribute
conflicts do occur.
Attribute types prefixes with 0x0 and 0xf are currently reserved for future
standard attributes. Standard attributes will be added to this document in
that case.
Here's an example of nonstandard time attribute:
```
(8 bits) attribute type = time (0xc1)
(72 bits) time in seconds = 1506286115 (0x0059c81a23)
00000000: c1 23 1a c8 59 00 .#..Y.
```
Here's an example of nonstandard permissions attribute:
```
(8 bits) attribute type = permissions (0xc2)
(16 bits) permission bits = rw-rw-r-- (0x01b4)
00000000: c2 b4 01 ...
```
Here's what a dir entry may look like with these attributes:
```
(8 bits) entry type = file (0x11)
(8 bits) entry length = 8 bytes (0x08)
(8 bits) attribute length = 9 bytes (0x09)
(8 bits) name length = 12 bytes (0x0c)
(8 bytes) entry data = 05 00 00 00 20 00 00 00
(8 bits) attribute type = time (0xc1)
(72 bits) time in seconds = 1506286115 (0x0059c81a23)
(8 bits) attribute type = permissions (0xc2)
(16 bits) permission bits = rw-rw-r-- (0x01b4)
(12 bytes) entry name = smallavacado
00000000: 11 08 09 0c 05 00 00 00 20 00 00 00 c1 23 1a c8 ........ ....#..
00000010: 59 00 c2 b4 01 73 6d 61 6c 6c 61 76 61 63 61 64 Y....smallavacad
00000020: 6f o
```

View File

@ -0,0 +1,253 @@
/*
* Block device emulated on standard files
*
* 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 "emubd/lfs_emubd.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>
#include <stdbool.h>
// Block device emulated on existing filesystem
int lfs_emubd_create(const struct lfs_config *cfg, const char *path) {
lfs_emubd_t *emu = cfg->context;
emu->cfg.read_size = cfg->read_size;
emu->cfg.prog_size = cfg->prog_size;
emu->cfg.block_size = cfg->block_size;
emu->cfg.block_count = cfg->block_count;
// Allocate buffer for creating children files
size_t pathlen = strlen(path);
emu->path = malloc(pathlen + 1 + LFS_NAME_MAX + 1);
if (!emu->path) {
return -ENOMEM;
}
strcpy(emu->path, path);
emu->path[pathlen] = '/';
emu->child = &emu->path[pathlen+1];
memset(emu->child, '\0', LFS_NAME_MAX+1);
// Create directory if it doesn't exist
int err = mkdir(path, 0777);
if (err && errno != EEXIST) {
return -errno;
}
// Load stats to continue incrementing
snprintf(emu->child, LFS_NAME_MAX, "stats");
FILE *f = fopen(emu->path, "r");
if (!f) {
return -errno;
}
size_t res = fread(&emu->stats, sizeof(emu->stats), 1, f);
if (res < 1) {
return -errno;
}
err = fclose(f);
if (err) {
return -errno;
}
return 0;
}
void lfs_emubd_destroy(const struct lfs_config *cfg) {
lfs_emubd_sync(cfg);
lfs_emubd_t *emu = cfg->context;
free(emu->path);
}
int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size) {
lfs_emubd_t *emu = cfg->context;
uint8_t *data = buffer;
// Check if read is valid
assert(off % cfg->read_size == 0);
assert(size % cfg->read_size == 0);
assert(block < cfg->block_count);
// Zero out buffer for debugging
memset(data, 0, size);
// Read data
snprintf(emu->child, LFS_NAME_MAX, "%x", block);
FILE *f = fopen(emu->path, "rb");
if (!f && errno != ENOENT) {
return -errno;
}
if (f) {
int err = fseek(f, off, SEEK_SET);
if (err) {
return -errno;
}
size_t res = fread(data, 1, size, f);
if (res < size && !feof(f)) {
return -errno;
}
err = fclose(f);
if (err) {
return -errno;
}
}
emu->stats.read_count += 1;
return 0;
}
int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size) {
lfs_emubd_t *emu = cfg->context;
const uint8_t *data = buffer;
// Check if write is valid
assert(off % cfg->prog_size == 0);
assert(size % cfg->prog_size == 0);
assert(block < cfg->block_count);
// Program data
snprintf(emu->child, LFS_NAME_MAX, "%x", block);
FILE *f = fopen(emu->path, "r+b");
if (!f) {
return (errno == EACCES) ? 0 : -errno;
}
// Check that file was erased
assert(f);
int err = fseek(f, off, SEEK_SET);
if (err) {
return -errno;
}
size_t res = fwrite(data, 1, size, f);
if (res < size) {
return -errno;
}
err = fseek(f, off, SEEK_SET);
if (err) {
return -errno;
}
uint8_t dat;
res = fread(&dat, 1, 1, f);
if (res < 1) {
return -errno;
}
err = fclose(f);
if (err) {
return -errno;
}
emu->stats.prog_count += 1;
return 0;
}
int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block) {
lfs_emubd_t *emu = cfg->context;
// Check if erase is valid
assert(block < cfg->block_count);
// Erase the block
snprintf(emu->child, LFS_NAME_MAX, "%x", block);
struct stat st;
int err = stat(emu->path, &st);
if (err && errno != ENOENT) {
return -errno;
}
if (!err && S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode)) {
int err = unlink(emu->path);
if (err) {
return -errno;
}
}
if (errno == ENOENT || (S_ISREG(st.st_mode) && (S_IWUSR & st.st_mode))) {
FILE *f = fopen(emu->path, "w");
if (!f) {
return -errno;
}
err = fclose(f);
if (err) {
return -errno;
}
}
emu->stats.erase_count += 1;
return 0;
}
int lfs_emubd_sync(const struct lfs_config *cfg) {
lfs_emubd_t *emu = cfg->context;
// Just write out info/stats for later lookup
snprintf(emu->child, LFS_NAME_MAX, "config");
FILE *f = fopen(emu->path, "w");
if (!f) {
return -errno;
}
size_t res = fwrite(&emu->cfg, sizeof(emu->cfg), 1, f);
if (res < 1) {
return -errno;
}
int err = fclose(f);
if (err) {
return -errno;
}
snprintf(emu->child, LFS_NAME_MAX, "stats");
f = fopen(emu->path, "w");
if (!f) {
return -errno;
}
res = fwrite(&emu->stats, sizeof(emu->stats), 1, f);
if (res < 1) {
return -errno;
}
err = fclose(f);
if (err) {
return -errno;
}
return 0;
}

View File

@ -0,0 +1,89 @@
/*
* Block device emulated on standard files
*
* 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.
*/
#ifndef LFS_EMUBD_H
#define LFS_EMUBD_H
#include "lfs.h"
#include "lfs_util.h"
// Config options
#ifndef LFS_EMUBD_READ_SIZE
#define LFS_EMUBD_READ_SIZE 1
#endif
#ifndef LFS_EMUBD_PROG_SIZE
#define LFS_EMUBD_PROG_SIZE 1
#endif
#ifndef LFS_EMUBD_ERASE_SIZE
#define LFS_EMUBD_ERASE_SIZE 512
#endif
#ifndef LFS_EMUBD_TOTAL_SIZE
#define LFS_EMUBD_TOTAL_SIZE 524288
#endif
// The emu bd state
typedef struct lfs_emubd {
char *path;
char *child;
struct {
uint64_t read_count;
uint64_t prog_count;
uint64_t erase_count;
} stats;
struct {
uint32_t read_size;
uint32_t prog_size;
uint32_t block_size;
uint32_t block_count;
} cfg;
} lfs_emubd_t;
// Create a block device using path for the directory to store blocks
int lfs_emubd_create(const struct lfs_config *cfg, const char *path);
// Clean up memory associated with emu block device
void lfs_emubd_destroy(const struct lfs_config *cfg);
// Read a block
int lfs_emubd_read(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size);
// Program a block
//
// The block must have previously been erased.
int lfs_emubd_prog(const struct lfs_config *cfg, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size);
// Erase a block
//
// A block must be erased before being programmed. The
// state of an erased block is undefined.
int lfs_emubd_erase(const struct lfs_config *cfg, lfs_block_t block);
// Sync the block device
int lfs_emubd_sync(const struct lfs_config *cfg);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,451 @@
/*
* The little filesystem
*
* 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.
*/
#ifndef LFS_H
#define LFS_H
#include <stdint.h>
#include <stdbool.h>
/// Definitions ///
// Type definitions
typedef uint32_t lfs_size_t;
typedef uint32_t lfs_off_t;
typedef int32_t lfs_ssize_t;
typedef int32_t lfs_soff_t;
typedef uint32_t lfs_block_t;
// Max name size in bytes
#ifndef LFS_NAME_MAX
#define LFS_NAME_MAX 255
#endif
// Possible error codes, these are negative to allow
// valid positive return values
enum lfs_error {
LFS_ERR_OK = 0, // No error
LFS_ERR_IO = -5, // Error during device operation
LFS_ERR_CORRUPT = -52, // Corrupted
LFS_ERR_NOENT = -2, // No directory entry
LFS_ERR_EXIST = -17, // Entry already exists
LFS_ERR_NOTDIR = -20, // Entry is not a dir
LFS_ERR_ISDIR = -21, // Entry is a dir
LFS_ERR_INVAL = -22, // Invalid parameter
LFS_ERR_NOSPC = -28, // No space left on device
LFS_ERR_NOMEM = -12, // No more memory available
};
// File types
enum lfs_type {
LFS_TYPE_REG = 0x11,
LFS_TYPE_DIR = 0x22,
LFS_TYPE_SUPERBLOCK = 0x2e,
};
// File open flags
enum lfs_open_flags {
// open flags
LFS_O_RDONLY = 1, // Open a file as read only
LFS_O_WRONLY = 2, // Open a file as write only
LFS_O_RDWR = 3, // Open a file as read and write
LFS_O_CREAT = 0x0100, // Create a file if it does not exist
LFS_O_EXCL = 0x0200, // Fail if a file already exists
LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
LFS_O_APPEND = 0x0800, // Move to end of file on every write
// internally used flags
LFS_F_DIRTY = 0x10000, // File does not match storage
LFS_F_WRITING = 0x20000, // File has been written since last flush
LFS_F_READING = 0x40000, // File has been read since last flush
LFS_F_ERRED = 0x80000, // An error occured during write
};
// File seek flags
enum lfs_whence_flags {
LFS_SEEK_SET = 0, // Seek relative to an absolute position
LFS_SEEK_CUR = 1, // Seek relative to the current file position
LFS_SEEK_END = 2, // Seek relative to the end of the file
};
// Configuration provided during initialization of the littlefs
struct lfs_config {
// Opaque user provided context that can be used to pass
// information to the block device operations
void *context;
// Read a region in a block. Negative error codes are propogated
// to the user.
int (*read)(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size);
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propogated to the user.
// The prog function must return LFS_ERR_CORRUPT if the block should
// be considered bad.
int (*prog)(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size);
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propogated to the user.
int (*erase)(const struct lfs_config *c, lfs_block_t block);
// Sync the state of the underlying block device. Negative error codes
// are propogated to the user.
int (*sync)(const struct lfs_config *c);
// Minimum size of a block read. This determines the size of read buffers.
// This may be larger than the physical read size to improve performance
// by caching more of the block device.
lfs_size_t read_size;
// Minimum size of a block program. This determines the size of program
// buffers. This may be larger than the physical program size to improve
// performance by caching more of the block device.
lfs_size_t prog_size;
// Size of an erasable block. This does not impact ram consumption and
// may be larger than the physical erase size. However, this should be
// kept small as each file currently takes up an entire block .
lfs_size_t block_size;
// Number of erasable blocks on the device.
lfs_size_t block_count;
// Number of blocks to lookahead during block allocation. A larger
// lookahead reduces the number of passes required to allocate a block.
// The lookahead buffer requires only 1 bit per block so it can be quite
// large with little ram impact. Should be a multiple of 32.
lfs_size_t lookahead;
// Optional, statically allocated read buffer. Must be read sized.
void *read_buffer;
// Optional, statically allocated program buffer. Must be program sized.
void *prog_buffer;
// Optional, statically allocated lookahead buffer. Must be 1 bit per
// lookahead block.
void *lookahead_buffer;
// Optional, statically allocated buffer for files. Must be program sized.
// If enabled, only one file may be opened at a time.
void *file_buffer;
};
// File info structure
struct lfs_info {
// Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
uint8_t type;
// Size of the file, only valid for REG files
lfs_size_t size;
// Name of the file stored as a null-terminated string
char name[LFS_NAME_MAX+1];
};
/// littlefs data structures ///
typedef struct lfs_entry {
lfs_off_t off;
struct lfs_disk_entry {
uint8_t type;
uint8_t elen;
uint8_t alen;
uint8_t nlen;
union {
struct {
lfs_block_t head;
lfs_size_t size;
} file;
lfs_block_t dir[2];
} u;
} d;
} lfs_entry_t;
typedef struct lfs_cache {
lfs_block_t block;
lfs_off_t off;
uint8_t *buffer;
} lfs_cache_t;
typedef struct lfs_file {
struct lfs_file *next;
lfs_block_t pair[2];
lfs_off_t poff;
lfs_block_t head;
lfs_size_t size;
uint32_t flags;
lfs_off_t pos;
lfs_block_t block;
lfs_off_t off;
lfs_cache_t cache;
} lfs_file_t;
typedef struct lfs_dir {
struct lfs_dir *next;
lfs_block_t pair[2];
lfs_off_t off;
lfs_block_t head[2];
lfs_off_t pos;
struct lfs_disk_dir {
uint32_t rev;
lfs_size_t size;
lfs_block_t tail[2];
} d;
} lfs_dir_t;
typedef struct lfs_superblock {
lfs_off_t off;
struct lfs_disk_superblock {
uint8_t type;
uint8_t elen;
uint8_t alen;
uint8_t nlen;
lfs_block_t root[2];
uint32_t block_size;
uint32_t block_count;
uint32_t version;
char magic[8];
} d;
} lfs_superblock_t;
typedef struct lfs_free {
lfs_block_t begin;
lfs_block_t end;
lfs_block_t off;
uint32_t *buffer;
} lfs_free_t;
// The littlefs type
typedef struct lfs {
const struct lfs_config *cfg;
lfs_block_t root[2];
lfs_file_t *files;
lfs_dir_t *dirs;
lfs_cache_t rcache;
lfs_cache_t pcache;
lfs_free_t free;
bool deorphaned;
} lfs_t;
/// Filesystem functions ///
// Format a block device with the littlefs
//
// Requires a littlefs object and config struct. This clobbers the littlefs
// object, and does not leave the filesystem mounted.
//
// Returns a negative error code on failure.
int lfs_format(lfs_t *lfs, const struct lfs_config *config);
// Mounts a littlefs
//
// Requires a littlefs object and config struct. Multiple filesystems
// may be mounted simultaneously with multiple littlefs objects. Both
// lfs and config must be allocated while mounted.
//
// Returns a negative error code on failure.
int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
// Unmounts a littlefs
//
// Does nothing besides releasing any allocated resources.
// Returns a negative error code on failure.
int lfs_unmount(lfs_t *lfs);
/// General operations ///
// Removes a file or directory
//
// If removing a directory, the directory must be empty.
// Returns a negative error code on failure.
int lfs_remove(lfs_t *lfs, const char *path);
// Rename or move a file or directory
//
// If the destination exists, it must match the source in type.
// If the destination is a directory, the directory must be empty.
//
// Note: If power loss occurs, it is possible that the file or directory
// will exist in both the oldpath and newpath simultaneously after the
// next mount.
//
// Returns a negative error code on failure.
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
// Find info about a file or directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a negative error code on failure.
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
/// File operations ///
// Open a file
//
// The mode that the file is opened in is determined
// by the flags, which are values from the enum lfs_open_flags
// that are bitwise-ored together.
//
// Returns a negative error code on failure.
int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags);
// Close a file
//
// Any pending writes are written out to storage as though
// sync had been called and releases any allocated resources.
//
// Returns a negative error code on failure.
int lfs_file_close(lfs_t *lfs, lfs_file_t *file);
// Synchronize a file on storage
//
// Any pending writes are written out to storage.
// Returns a negative error code on failure.
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
// Read data from file
//
// Takes a buffer and size indicating where to store the read data.
// Returns the number of bytes read, or a negative error code on failure.
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
void *buffer, lfs_size_t size);
// Write data to file
//
// Takes a buffer and size indicating the data to write. The file will not
// actually be updated on the storage until either sync or close is called.
//
// Returns the number of bytes written, or a negative error code on failure.
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
const void *buffer, lfs_size_t size);
// Change the position of the file
//
// The change in position is determined by the offset and whence flag.
// Returns the old position of the file, or a negative error code on failure.
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
lfs_soff_t off, int whence);
// Return the position of the file
//
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
// Returns the position of the file, or a negative error code on failure.
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file);
// Change the position of the file to the beginning of the file
//
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
// Returns a negative error code on failure.
int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file);
// Return the size of the file
//
// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END)
// Returns the size of the file, or a negative error code on failure.
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file);
/// Directory operations ///
// Create a directory
//
// Returns a negative error code on failure.
int lfs_mkdir(lfs_t *lfs, const char *path);
// Open a directory
//
// Once open a directory can be used with read to iterate over files.
// Returns a negative error code on failure.
int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path);
// Close a directory
//
// Releases any allocated resources.
// Returns a negative error code on failure.
int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir);
// Read an entry in the directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a negative error code on failure.
int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);
// Change the position of the directory
//
// The new off must be a value previous returned from tell and specifies
// an absolute offset in the directory seek.
//
// Returns a negative error code on failure.
int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off);
// Return the position of the directory
//
// The returned offset is only meant to be consumed by seek and may not make
// sense, but does indicate the current position in the directory iteration.
//
// Returns the position of the directory, or a negative error code on failure.
lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);
// Change the position of the directory to the beginning of the directory
//
// Returns a negative error code on failure.
int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
/// Miscellaneous littlefs specific operations ///
// Traverse through all blocks in use by the filesystem
//
// The provided callback will be called with each block address that is
// currently in use by the filesystem. This can be used to determine which
// blocks are in use or how much of the storage is available.
//
// Returns a negative error code on failure.
int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
// Prunes any recoverable errors that may have occured in the filesystem
//
// Not needed to be called by user unless an operation is interrupted
// but the filesystem is still mounted. This is already called on first
// allocation.
//
// Returns a negative error code on failure.
int lfs_deorphan(lfs_t *lfs);
#endif

View File

@ -0,0 +1,36 @@
/*
* lfs util functions
*
* 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 "lfs_util.h"
void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size) {
static const uint32_t rtable[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
};
const uint8_t *data = buffer;
for (size_t i = 0; i < size; i++) {
*crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf];
*crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf];
}
}

View File

@ -0,0 +1,134 @@
/*
* lfs utility functions
*
* 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.
*/
#ifndef LFS_UTIL_H
#define LFS_UTIL_H
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#ifdef __ICCARM__
#include <intrinsics.h>
#endif
// Builtin functions, these may be replaced by more
// efficient implementations in the system
static inline uint32_t lfs_max(uint32_t a, uint32_t b) {
return (a > b) ? a : b;
}
static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
static inline uint32_t lfs_ctz(uint32_t a) {
#if defined(__GNUC__) || defined(__CC_ARM)
return __builtin_ctz(a);
#elif defined(__ICCARM__) && defined(__CLZ)
return __CLZ(__RBIT(a));
#else
uint32_t r = 32;
a &= -a;
if (a) r -= 1;
if (a & 0x0000ffff) r -= 16;
if (a & 0x00ff00ff) r -= 8;
if (a & 0x0f0f0f0f) r -= 4;
if (a & 0x33333333) r -= 2;
if (a & 0x55555555) r -= 1;
return r;
#endif
}
static inline uint32_t lfs_npw2(uint32_t a) {
#if defined(__GNUC__) || defined(__CC_ARM)
return 32 - __builtin_clz(a-1);
#elif defined(__ICCARM__) && defined(__CLZ)
return 32 - __CLZ(a-1);
#else
uint32_t r = 0;
uint32_t s;
s = (a > 0xffff) << 4; a >>= s; r |= s;
s = (a > 0xff ) << 3; a >>= s; r |= s;
s = (a > 0xf ) << 2; a >>= s; r |= s;
s = (a > 0x3 ) << 1; a >>= s; r |= s;
return r | (a >> 1);
#endif
}
static inline uint32_t lfs_popc(uint32_t a) {
#if defined(__GNUC__) || defined(__CC_ARM)
return __builtin_popcount(a);
#else
a = a - ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
#endif
}
static inline int lfs_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
// CRC-32 with polynomial = 0x04c11db7
void lfs_crc(uint32_t *crc, const void *buffer, size_t size);
// Logging functions
#ifdef __MBED__
#include "mbed_debug.h"
#else
#define MBED_LFS_ENABLE_INFO false
#define MBED_LFS_ENABLE_DEBUG true
#define MBED_LFS_ENABLE_WARN true
#define MBED_LFS_ENABLE_ERROR true
#endif
#if MBED_LFS_ENABLE_INFO
#define LFS_INFO(fmt, ...) printf("lfs info: " fmt "\n", __VA_ARGS__)
#elif !defined(MBED_LFS_ENABLE_INFO)
#define LFS_INFO(fmt, ...) debug("lfs info: " fmt "\n", __VA_ARGS__)
#else
#define LFS_INFO(fmt, ...)
#endif
#if MBED_LFS_ENABLE_DEBUG
#define LFS_DEBUG(fmt, ...) printf("lfs debug: " fmt "\n", __VA_ARGS__)
#elif !defined(MBED_LFS_ENABLE_DEBUG)
#define LFS_DEBUG(fmt, ...) debug("lfs debug: " fmt "\n", __VA_ARGS__)
#else
#define LFS_DEBUG(fmt, ...)
#endif
#if MBED_LFS_ENABLE_WARN
#define LFS_WARN(fmt, ...) printf("lfs warn: " fmt "\n", __VA_ARGS__)
#elif !defined(MBED_LFS_ENABLE_WARN)
#define LFS_WARN(fmt, ...) debug("lfs warn: " fmt "\n", __VA_ARGS__)
#else
#define LFS_WARN(fmt, ...)
#endif
#if MBED_LFS_ENABLE_ERROR
#define LFS_ERROR(fmt, ...) printf("lfs error: " fmt "\n", __VA_ARGS__)
#elif !defined(MBED_LFS_ENABLE_ERROR)
#define LFS_ERROR(fmt, ...) debug("lfs error: " fmt "\n", __VA_ARGS__)
#else
#define LFS_ERROR(fmt, ...)
#endif
#endif

View File

@ -0,0 +1,30 @@
#!/usr/bin/env python
import struct
import sys
import time
import os
import re
def main():
with open('blocks/config') as file:
s = struct.unpack('<LLLL', file.read())
print 'read_size: %d' % s[0]
print 'prog_size: %d' % s[1]
print 'block_size: %d' % s[2]
print 'block_size: %d' % s[3]
print 'real_size: %d' % sum(
os.path.getsize(os.path.join('blocks', f))
for f in os.listdir('blocks') if re.match('\d+', f))
with open('blocks/stats') as file:
s = struct.unpack('<QQQ', file.read())
print 'read_count: %d' % s[0]
print 'prog_count: %d' % s[1]
print 'erase_count: %d' % s[2]
print 'runtime: %.3f' % (time.time() - os.stat('blocks').st_ctime)
if __name__ == "__main__":
main(*sys.argv[1:])

View File

@ -0,0 +1,105 @@
/// AUTOGENERATED TEST ///
#include "lfs.h"
#include "emubd/lfs_emubd.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// test stuff
void test_log(const char *s, uintmax_t v) {{
printf("%s: %jd\n", s, v);
}}
void test_assert(const char *file, unsigned line,
const char *s, uintmax_t v, uintmax_t e) {{
static const char *last[6] = {{0, 0}};
if (v != e || !(last[0] == s || last[1] == s ||
last[2] == s || last[3] == s ||
last[4] == s || last[5] == s)) {{
test_log(s, v);
last[0] = last[1];
last[1] = last[2];
last[2] = last[3];
last[3] = last[4];
last[4] = last[5];
last[5] = s;
}}
if (v != e) {{
fprintf(stderr, "\033[31m%s:%u: assert %s failed with %jd, "
"expected %jd\033[0m\n", file, line, s, v, e);
exit(-2);
}}
}}
#define test_assert(s, v, e) test_assert(__FILE__, __LINE__, s, v, e)
// utility functions for traversals
int test_count(void *p, lfs_block_t b) {{
unsigned *u = (unsigned*)p;
*u += 1;
return 0;
}}
// lfs declarations
lfs_t lfs;
lfs_emubd_t bd;
lfs_file_t file[4];
lfs_dir_t dir[4];
struct lfs_info info;
uint8_t buffer[1024];
uint8_t wbuffer[1024];
uint8_t rbuffer[1024];
lfs_size_t size;
lfs_size_t wsize;
lfs_size_t rsize;
uintmax_t res;
#ifndef LFS_READ_SIZE
#define LFS_READ_SIZE 16
#endif
#ifndef LFS_PROG_SIZE
#define LFS_PROG_SIZE 16
#endif
#ifndef LFS_BLOCK_SIZE
#define LFS_BLOCK_SIZE 512
#endif
#ifndef LFS_BLOCK_COUNT
#define LFS_BLOCK_COUNT 1024
#endif
#ifndef LFS_LOOKAHEAD
#define LFS_LOOKAHEAD 128
#endif
const struct lfs_config cfg = {{
.context = &bd,
.read = &lfs_emubd_read,
.prog = &lfs_emubd_prog,
.erase = &lfs_emubd_erase,
.sync = &lfs_emubd_sync,
.read_size = LFS_READ_SIZE,
.prog_size = LFS_PROG_SIZE,
.block_size = LFS_BLOCK_SIZE,
.block_count = LFS_BLOCK_COUNT,
.lookahead = LFS_LOOKAHEAD,
}};
// Entry point
int main() {{
lfs_emubd_create(&cfg, "blocks");
{tests}
lfs_emubd_destroy(&cfg);
}}

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
import re
import sys
import subprocess
import os
def generate(test):
with open("tests/template.fmt") as file:
template = file.read()
lines = []
for line in re.split('(?<=[;{}])\n', test.read()):
match = re.match('(?: *\n)*( *)(.*)=>(.*);', line, re.DOTALL | re.MULTILINE)
if match:
tab, test, expect = match.groups()
lines.append(tab+'res = {test};'.format(test=test.strip()))
lines.append(tab+'test_assert("{name}", res, {expect});'.format(
name = re.match('\w*', test.strip()).group(),
expect = expect.strip()))
else:
lines.append(line)
with open('test.c', 'w') as file:
file.write(template.format(tests='\n'.join(lines)))
def compile():
os.environ['CFLAGS'] = os.environ.get('CFLAGS', '') + ' -Werror'
subprocess.check_call(['make', '--no-print-directory', '-s'], env=os.environ)
def execute():
subprocess.check_call(["./lfs"])
def main(test=None):
if test and not test.startswith('-'):
with open(test) as file:
generate(file)
else:
generate(sys.stdin)
compile()
if test == '-s':
sys.exit(1)
execute()
if __name__ == "__main__":
main(*sys.argv[1:])

View File

@ -0,0 +1,271 @@
#!/bin/bash
set -eu
echo "=== Allocator tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
SIZE=15000
lfs_mkdir() {
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "$1") => 0;
lfs_unmount(&lfs) => 0;
TEST
}
lfs_remove() {
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "$1/eggs") => 0;
lfs_remove(&lfs, "$1/bacon") => 0;
lfs_remove(&lfs, "$1/pancakes") => 0;
lfs_remove(&lfs, "$1") => 0;
lfs_unmount(&lfs) => 0;
TEST
}
lfs_alloc_singleproc() {
tests/test.py << TEST
const char *names[] = {"bacon", "eggs", "pancakes"};
lfs_mount(&lfs, &cfg) => 0;
for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) {
sprintf((char*)buffer, "$1/%s", names[n]);
lfs_file_open(&lfs, &file[n], (char*)buffer,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
}
for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) {
size = strlen(names[n]);
for (int i = 0; i < $SIZE; i++) {
lfs_file_write(&lfs, &file[n], names[n], size) => size;
}
}
for (int n = 0; n < sizeof(names)/sizeof(names[0]); n++) {
lfs_file_close(&lfs, &file[n]) => 0;
}
lfs_unmount(&lfs) => 0;
TEST
}
lfs_alloc_multiproc() {
for name in bacon eggs pancakes
do
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "$1/$name",
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
size = strlen("$name");
memcpy(buffer, "$name", size);
for (int i = 0; i < $SIZE; i++) {
lfs_file_write(&lfs, &file[0], buffer, size) => size;
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
done
}
lfs_verify() {
for name in bacon eggs pancakes
do
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "$1/$name", LFS_O_RDONLY) => 0;
size = strlen("$name");
for (int i = 0; i < $SIZE; i++) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "$name", size) => 0;
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
done
}
echo "--- Single-process allocation test ---"
lfs_mkdir singleproc
lfs_alloc_singleproc singleproc
lfs_verify singleproc
echo "--- Multi-process allocation test ---"
lfs_mkdir multiproc
lfs_alloc_multiproc multiproc
lfs_verify multiproc
lfs_verify singleproc
echo "--- Single-process reuse test ---"
lfs_remove singleproc
lfs_mkdir singleprocreuse
lfs_alloc_singleproc singleprocreuse
lfs_verify singleprocreuse
lfs_verify multiproc
echo "--- Multi-process reuse test ---"
lfs_remove multiproc
lfs_mkdir multiprocreuse
lfs_alloc_singleproc multiprocreuse
lfs_verify multiprocreuse
lfs_verify singleprocreuse
echo "--- Cleanup ---"
lfs_remove multiprocreuse
lfs_remove singleprocreuse
echo "--- Exhaustion test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
size = strlen("exhaustion");
memcpy(buffer, "exhaustion", size);
lfs_file_write(&lfs, &file[0], buffer, size) => size;
lfs_file_sync(&lfs, &file[0]) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
lfs_ssize_t res;
while (true) {
res = lfs_file_write(&lfs, &file[0], buffer, size);
if (res < 0) {
break;
}
res => size;
}
res => LFS_ERR_NOSPC;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY);
size = strlen("exhaustion");
lfs_file_size(&lfs, &file[0]) => size;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "exhaustion", size) => 0;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Exhaustion wraparound test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "exhaustion") => 0;
lfs_file_open(&lfs, &file[0], "padding", LFS_O_WRONLY | LFS_O_CREAT);
size = strlen("buffering");
memcpy(buffer, "buffering", size);
for (int i = 0; i < $SIZE; i++) {
lfs_file_write(&lfs, &file[0], buffer, size) => size;
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_remove(&lfs, "padding") => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
size = strlen("exhaustion");
memcpy(buffer, "exhaustion", size);
lfs_file_write(&lfs, &file[0], buffer, size) => size;
lfs_file_sync(&lfs, &file[0]) => 0;
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
lfs_ssize_t res;
while (true) {
res = lfs_file_write(&lfs, &file[0], buffer, size);
if (res < 0) {
break;
}
res => size;
}
res => LFS_ERR_NOSPC;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_RDONLY);
size = strlen("exhaustion");
lfs_file_size(&lfs, &file[0]) => size;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "exhaustion", size) => 0;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Dir exhaustion test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "exhaustion") => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs_size_t i = 0;
i < (cfg.block_count-6)*(cfg.block_size-8);
i += size) {
lfs_file_write(&lfs, &file[0], buffer, size) => size;
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_mkdir(&lfs, "exhaustiondir") => 0;
lfs_remove(&lfs, "exhaustiondir") => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_APPEND);
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs_size_t i = 0;
i < (cfg.block_size-8);
i += size) {
lfs_file_write(&lfs, &file[0], buffer, size) => size;
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Chained dir exhaustion test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "exhaustion") => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs_size_t i = 0;
i < (cfg.block_count-24)*(cfg.block_size-8);
i += size) {
lfs_file_write(&lfs, &file[0], buffer, size) => size;
}
lfs_file_close(&lfs, &file[0]) => 0;
for (int i = 0; i < 9; i++) {
sprintf((char*)buffer, "dirwithanexhaustivelylongnameforpadding%d", i);
lfs_mkdir(&lfs, (char*)buffer) => 0;
}
lfs_mkdir(&lfs, "exhaustiondir") => LFS_ERR_NOSPC;
lfs_remove(&lfs, "exhaustion") => 0;
lfs_file_open(&lfs, &file[0], "exhaustion", LFS_O_WRONLY | LFS_O_CREAT);
size = strlen("blahblahblahblah");
memcpy(buffer, "blahblahblahblah", size);
for (lfs_size_t i = 0;
i < (cfg.block_count-26)*(cfg.block_size-8);
i += size) {
lfs_file_write(&lfs, &file[0], buffer, size) => size;
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_mkdir(&lfs, "exhaustiondir") => 0;
lfs_mkdir(&lfs, "exhaustiondir2") => LFS_ERR_NOSPC;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,117 @@
#!/bin/bash
set -eu
echo "=== Corrupt tests ==="
NAMEMULT=64
FILEMULT=1
lfs_mktree() {
tests/test.py ${1:-} << TEST
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
for (int i = 1; i < 10; i++) {
for (int j = 0; j < $NAMEMULT; j++) {
buffer[j] = '0'+i;
}
buffer[$NAMEMULT] = '\0';
lfs_mkdir(&lfs, (char*)buffer) => 0;
buffer[$NAMEMULT] = '/';
for (int j = 0; j < $NAMEMULT; j++) {
buffer[j+$NAMEMULT+1] = '0'+i;
}
buffer[2*$NAMEMULT+1] = '\0';
lfs_file_open(&lfs, &file[0], (char*)buffer,
LFS_O_WRONLY | LFS_O_CREAT) => 0;
size = $NAMEMULT;
for (int j = 0; j < i*$FILEMULT; j++) {
lfs_file_write(&lfs, &file[0], buffer, size) => size;
}
lfs_file_close(&lfs, &file[0]) => 0;
}
lfs_unmount(&lfs) => 0;
TEST
}
lfs_chktree() {
tests/test.py ${1:-} << TEST
lfs_mount(&lfs, &cfg) => 0;
for (int i = 1; i < 10; i++) {
for (int j = 0; j < $NAMEMULT; j++) {
buffer[j] = '0'+i;
}
buffer[$NAMEMULT] = '\0';
lfs_stat(&lfs, (char*)buffer, &info) => 0;
info.type => LFS_TYPE_DIR;
buffer[$NAMEMULT] = '/';
for (int j = 0; j < $NAMEMULT; j++) {
buffer[j+$NAMEMULT+1] = '0'+i;
}
buffer[2*$NAMEMULT+1] = '\0';
lfs_file_open(&lfs, &file[0], (char*)buffer, LFS_O_RDONLY) => 0;
size = $NAMEMULT;
for (int j = 0; j < i*$FILEMULT; j++) {
lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
memcmp(buffer, rbuffer, size) => 0;
}
lfs_file_close(&lfs, &file[0]) => 0;
}
lfs_unmount(&lfs) => 0;
TEST
}
echo "--- Sanity check ---"
rm -rf blocks
lfs_mktree
lfs_chktree
echo "--- Block corruption ---"
for i in {0..33}
do
rm -rf blocks
mkdir blocks
ln -s /dev/zero blocks/$(printf '%x' $i)
lfs_mktree
lfs_chktree
done
echo "--- Block persistance ---"
for i in {0..33}
do
rm -rf blocks
mkdir blocks
lfs_mktree
chmod a-w blocks/$(printf '%x' $i)
lfs_mktree
lfs_chktree
done
echo "--- Big region corruption ---"
rm -rf blocks
mkdir blocks
for i in {2..255}
do
ln -s /dev/zero blocks/$(printf '%x' $i)
done
lfs_mktree
lfs_chktree
echo "--- Alternating corruption ---"
rm -rf blocks
mkdir blocks
for i in {2..511..2}
do
ln -s /dev/zero blocks/$(printf '%x' $i)
done
lfs_mktree
lfs_chktree
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,359 @@
#!/bin/bash
set -eu
LARGESIZE=128
echo "=== Directory tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
echo "--- Root directory ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Directory creation ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "potato") => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- File creation ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "burito", LFS_O_CREAT | LFS_O_WRONLY) => 0;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Directory iteration ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "potato") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "burito") => 0;
info.type => LFS_TYPE_REG;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Directory failures ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "potato") => LFS_ERR_EXIST;
lfs_dir_open(&lfs, &dir[0], "tomato") => LFS_ERR_NOENT;
lfs_dir_open(&lfs, &dir[0], "burito") => LFS_ERR_NOTDIR;
lfs_file_open(&lfs, &file[0], "tomato", LFS_O_RDONLY) => LFS_ERR_NOENT;
lfs_file_open(&lfs, &file[0], "potato", LFS_O_RDONLY) => LFS_ERR_ISDIR;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Nested directories ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "potato/baked") => 0;
lfs_mkdir(&lfs, "potato/sweet") => 0;
lfs_mkdir(&lfs, "potato/fried") => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "potato") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "baked") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "sweet") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "fried") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Multi-block directory ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "cactus") => 0;
for (int i = 0; i < $LARGESIZE; i++) {
sprintf((char*)buffer, "cactus/test%d", i);
lfs_mkdir(&lfs, (char*)buffer) => 0;
}
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "cactus") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
for (int i = 0; i < $LARGESIZE; i++) {
sprintf((char*)buffer, "test%d", i);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
}
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Directory remove ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "potato") => LFS_ERR_INVAL;
lfs_remove(&lfs, "potato/sweet") => 0;
lfs_remove(&lfs, "potato/baked") => 0;
lfs_remove(&lfs, "potato/fried") => 0;
lfs_dir_open(&lfs, &dir[0], "potato") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_remove(&lfs, "potato") => 0;
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "burito") => 0;
info.type => LFS_TYPE_REG;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "cactus") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "burito") => 0;
info.type => LFS_TYPE_REG;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "cactus") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Directory rename ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "coldpotato") => 0;
lfs_mkdir(&lfs, "coldpotato/baked") => 0;
lfs_mkdir(&lfs, "coldpotato/sweet") => 0;
lfs_mkdir(&lfs, "coldpotato/fried") => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_rename(&lfs, "coldpotato", "hotpotato") => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "hotpotato") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "baked") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "sweet") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "fried") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "warmpotato") => 0;
lfs_mkdir(&lfs, "warmpotato/mushy") => 0;
lfs_rename(&lfs, "hotpotato", "warmpotato") => LFS_ERR_INVAL;
lfs_remove(&lfs, "warmpotato/mushy") => 0;
lfs_rename(&lfs, "hotpotato", "warmpotato") => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "warmpotato") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "baked") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "sweet") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "fried") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "coldpotato") => 0;
lfs_rename(&lfs, "warmpotato/baked", "coldpotato/baked") => 0;
lfs_rename(&lfs, "warmpotato/sweet", "coldpotato/sweet") => 0;
lfs_rename(&lfs, "warmpotato/fried", "coldpotato/fried") => 0;
lfs_remove(&lfs, "coldpotato") => LFS_ERR_INVAL;
lfs_remove(&lfs, "warmpotato") => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "coldpotato") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "baked") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "sweet") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "fried") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Recursive remove ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "coldpotato") => LFS_ERR_INVAL;
lfs_dir_open(&lfs, &dir[0], "coldpotato") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
while (true) {
int err = lfs_dir_read(&lfs, &dir[0], &info);
err >= 0 => 1;
if (err == 0) {
break;
}
strcpy((char*)buffer, "coldpotato/");
strcat((char*)buffer, info.name);
lfs_remove(&lfs, (char*)buffer) => 0;
}
lfs_remove(&lfs, "coldpotato") => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "burito") => 0;
info.type => LFS_TYPE_REG;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "cactus") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Multi-block remove ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_remove(&lfs, "cactus") => LFS_ERR_INVAL;
for (int i = 0; i < $LARGESIZE; i++) {
sprintf((char*)buffer, "cactus/test%d", i);
lfs_remove(&lfs, (char*)buffer) => 0;
}
lfs_remove(&lfs, "cactus") => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "burito") => 0;
info.type => LFS_TYPE_REG;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,114 @@
#!/bin/bash
set -eu
SMALLSIZE=32
MEDIUMSIZE=8192
LARGESIZE=262144
echo "=== File tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
echo "--- Simple file test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "hello", LFS_O_WRONLY | LFS_O_CREAT) => 0;
size = strlen("Hello World!\n");
memcpy(wbuffer, "Hello World!\n", size);
lfs_file_write(&lfs, &file[0], wbuffer, size) => size;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_file_open(&lfs, &file[0], "hello", LFS_O_RDONLY) => 0;
size = strlen("Hello World!\n");
lfs_file_read(&lfs, &file[0], rbuffer, size) => size;
memcmp(rbuffer, wbuffer, size) => 0;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
w_test() {
tests/test.py << TEST
lfs_size_t size = $1;
lfs_size_t chunk = 31;
srand(0);
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "$2", LFS_O_WRONLY | LFS_O_CREAT) => 0;
for (lfs_size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
for (lfs_size_t b = 0; b < chunk; b++) {
buffer[b] = rand() & 0xff;
}
lfs_file_write(&lfs, &file[0], buffer, chunk) => chunk;
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
}
r_test() {
tests/test.py << TEST
lfs_size_t size = $1;
lfs_size_t chunk = 29;
srand(0);
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "$2", LFS_O_RDONLY) => 0;
for (lfs_size_t i = 0; i < size; i += chunk) {
chunk = (chunk < size - i) ? chunk : size - i;
lfs_file_read(&lfs, &file[0], buffer, chunk) => chunk;
for (lfs_size_t b = 0; b < chunk && i+b < size; b++) {
buffer[b] => rand() & 0xff;
}
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
}
echo "--- Small file test ---"
w_test $SMALLSIZE smallavacado
r_test $SMALLSIZE smallavacado
echo "--- Medium file test ---"
w_test $MEDIUMSIZE mediumavacado
r_test $MEDIUMSIZE mediumavacado
echo "--- Large file test ---"
w_test $LARGESIZE largeavacado
r_test $LARGESIZE largeavacado
echo "--- Non-overlap check ---"
r_test $SMALLSIZE smallavacado
r_test $MEDIUMSIZE mediumavacado
r_test $LARGESIZE largeavacado
echo "--- Dir check ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hello") => 0;
info.type => LFS_TYPE_REG;
info.size => strlen("Hello World!\n");
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "smallavacado") => 0;
info.type => LFS_TYPE_REG;
info.size => $SMALLSIZE;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "mediumavacado") => 0;
info.type => LFS_TYPE_REG;
info.size => $MEDIUMSIZE;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "largeavacado") => 0;
info.type => LFS_TYPE_REG;
info.size => $LARGESIZE;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,49 @@
#!/bin/bash
set -eu
echo "=== Formatting tests ==="
rm -rf blocks
echo "--- Basic formatting ---"
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
echo "--- Invalid superblocks ---"
ln -f -s /dev/zero blocks/0
ln -f -s /dev/zero blocks/1
tests/test.py << TEST
lfs_format(&lfs, &cfg) => LFS_ERR_CORRUPT;
TEST
rm blocks/0 blocks/1
echo "--- Basic mounting ---"
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Invalid mount ---"
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
rm blocks/0 blocks/1
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => LFS_ERR_CORRUPT;
TEST
echo "--- Valid corrupt mount ---"
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
rm blocks/0
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,236 @@
#!/bin/bash
set -eu
echo "=== Move tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "a") => 0;
lfs_mkdir(&lfs, "b") => 0;
lfs_mkdir(&lfs, "c") => 0;
lfs_mkdir(&lfs, "d") => 0;
lfs_mkdir(&lfs, "a/hi") => 0;
lfs_mkdir(&lfs, "a/hi/hola") => 0;
lfs_mkdir(&lfs, "a/hi/bonjour") => 0;
lfs_mkdir(&lfs, "a/hi/ohayo") => 0;
lfs_file_open(&lfs, &file[0], "a/hello", LFS_O_CREAT | LFS_O_WRONLY) => 0;
lfs_file_write(&lfs, &file[0], "hola\n", 5) => 5;
lfs_file_write(&lfs, &file[0], "bonjour\n", 8) => 8;
lfs_file_write(&lfs, &file[0], "ohayo\n", 6) => 6;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Move file ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_rename(&lfs, "a/hello", "b/hello") => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "a") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hi") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_dir_open(&lfs, &dir[0], "b") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hello") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Move file corrupt source ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_rename(&lfs, "b/hello", "c/hello") => 0;
lfs_unmount(&lfs) => 0;
TEST
rm -v blocks/7
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "b") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_dir_open(&lfs, &dir[0], "c") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hello") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Move file corrupt source and dest ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_rename(&lfs, "c/hello", "d/hello") => 0;
lfs_unmount(&lfs) => 0;
TEST
rm -v blocks/8
rm -v blocks/a
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "c") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hello") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_dir_open(&lfs, &dir[0], "d") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Move dir ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_rename(&lfs, "a/hi", "b/hi") => 0;
lfs_unmount(&lfs) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "a") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_dir_open(&lfs, &dir[0], "b") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hi") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Move dir corrupt source ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_rename(&lfs, "b/hi", "c/hi") => 0;
lfs_unmount(&lfs) => 0;
TEST
rm -v blocks/7
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "b") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_dir_open(&lfs, &dir[0], "c") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hello") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hi") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Move dir corrupt source and dest ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_rename(&lfs, "c/hi", "d/hi") => 0;
lfs_unmount(&lfs) => 0;
TEST
rm -v blocks/9
rm -v blocks/a
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "c") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hello") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hi") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_dir_open(&lfs, &dir[0], "d") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Move check ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "a/hi") => LFS_ERR_NOENT;
lfs_dir_open(&lfs, &dir[0], "b/hi") => LFS_ERR_NOENT;
lfs_dir_open(&lfs, &dir[0], "d/hi") => LFS_ERR_NOENT;
lfs_dir_open(&lfs, &dir[0], "c/hi") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "hola") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "bonjour") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "ohayo") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_dir_open(&lfs, &dir[0], "a/hello") => LFS_ERR_NOENT;
lfs_dir_open(&lfs, &dir[0], "b/hello") => LFS_ERR_NOENT;
lfs_dir_open(&lfs, &dir[0], "d/hello") => LFS_ERR_NOENT;
lfs_file_open(&lfs, &file[0], "c/hello", LFS_O_RDONLY) => 0;
lfs_file_read(&lfs, &file[0], buffer, 5) => 5;
memcmp(buffer, "hola\n", 5) => 0;
lfs_file_read(&lfs, &file[0], buffer, 8) => 8;
memcmp(buffer, "bonjour\n", 8) => 0;
lfs_file_read(&lfs, &file[0], buffer, 6) => 6;
memcmp(buffer, "ohayo\n", 6) => 0;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,41 @@
#!/bin/bash
set -eu
echo "=== Orphan tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
echo "--- Orphan test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "parent") => 0;
lfs_mkdir(&lfs, "parent/orphan") => 0;
lfs_mkdir(&lfs, "parent/child") => 0;
lfs_remove(&lfs, "parent/orphan") => 0;
TEST
# remove most recent file, this should be the update to the previous
# linked-list entry and should orphan the child
rm -v blocks/8
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
unsigned before = 0;
lfs_traverse(&lfs, test_count, &before) => 0;
test_log("before", before);
lfs_deorphan(&lfs) => 0;
lfs_stat(&lfs, "parent/orphan", &info) => LFS_ERR_NOENT;
unsigned after = 0;
lfs_traverse(&lfs, test_count, &after) => 0;
test_log("after", after);
int diff = before - after;
diff => 2;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,186 @@
#!/bin/bash
set -eu
echo "=== Parallel tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
echo "--- Parallel file test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "a", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_open(&lfs, &file[1], "b", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_open(&lfs, &file[2], "c", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_open(&lfs, &file[3], "d", LFS_O_WRONLY | LFS_O_CREAT) => 0;
for (int i = 0; i < 10; i++) {
lfs_file_write(&lfs, &file[0], (const void*)"a", 1) => 1;
lfs_file_write(&lfs, &file[1], (const void*)"b", 1) => 1;
lfs_file_write(&lfs, &file[2], (const void*)"c", 1) => 1;
lfs_file_write(&lfs, &file[3], (const void*)"d", 1) => 1;
}
lfs_file_close(&lfs, &file[0]);
lfs_file_close(&lfs, &file[1]);
lfs_file_close(&lfs, &file[2]);
lfs_file_close(&lfs, &file[3]);
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "a") => 0;
info.type => LFS_TYPE_REG;
info.size => 10;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "b") => 0;
info.type => LFS_TYPE_REG;
info.size => 10;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "c") => 0;
info.type => LFS_TYPE_REG;
info.size => 10;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "d") => 0;
info.type => LFS_TYPE_REG;
info.size => 10;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_file_open(&lfs, &file[0], "a", LFS_O_RDONLY) => 0;
lfs_file_open(&lfs, &file[1], "b", LFS_O_RDONLY) => 0;
lfs_file_open(&lfs, &file[2], "c", LFS_O_RDONLY) => 0;
lfs_file_open(&lfs, &file[3], "d", LFS_O_RDONLY) => 0;
for (int i = 0; i < 10; i++) {
lfs_file_read(&lfs, &file[0], buffer, 1) => 1;
buffer[0] => 'a';
lfs_file_read(&lfs, &file[1], buffer, 1) => 1;
buffer[0] => 'b';
lfs_file_read(&lfs, &file[2], buffer, 1) => 1;
buffer[0] => 'c';
lfs_file_read(&lfs, &file[3], buffer, 1) => 1;
buffer[0] => 'd';
}
lfs_file_close(&lfs, &file[0]);
lfs_file_close(&lfs, &file[1]);
lfs_file_close(&lfs, &file[2]);
lfs_file_close(&lfs, &file[3]);
lfs_unmount(&lfs) => 0;
TEST
echo "--- Parallel remove file test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "e", LFS_O_WRONLY | LFS_O_CREAT) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file[0], (const void*)"e", 1) => 1;
}
lfs_remove(&lfs, "a") => 0;
lfs_remove(&lfs, "b") => 0;
lfs_remove(&lfs, "c") => 0;
lfs_remove(&lfs, "d") => 0;
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file[0], (const void*)"e", 1) => 1;
}
lfs_file_close(&lfs, &file[0]);
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "e") => 0;
info.type => LFS_TYPE_REG;
info.size => 10;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_file_open(&lfs, &file[0], "e", LFS_O_RDONLY) => 0;
for (int i = 0; i < 10; i++) {
lfs_file_read(&lfs, &file[0], buffer, 1) => 1;
buffer[0] => 'e';
}
lfs_file_close(&lfs, &file[0]);
lfs_unmount(&lfs) => 0;
TEST
echo "--- Remove inconveniently test ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "e", LFS_O_WRONLY | LFS_O_TRUNC) => 0;
lfs_file_open(&lfs, &file[1], "f", LFS_O_WRONLY | LFS_O_CREAT) => 0;
lfs_file_open(&lfs, &file[2], "g", LFS_O_WRONLY | LFS_O_CREAT) => 0;
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file[0], (const void*)"e", 1) => 1;
lfs_file_write(&lfs, &file[1], (const void*)"f", 1) => 1;
lfs_file_write(&lfs, &file[2], (const void*)"g", 1) => 1;
}
lfs_remove(&lfs, "f") => 0;
for (int i = 0; i < 5; i++) {
lfs_file_write(&lfs, &file[0], (const void*)"e", 1) => 1;
lfs_file_write(&lfs, &file[1], (const void*)"f", 1) => 1;
lfs_file_write(&lfs, &file[2], (const void*)"g", 1) => 1;
}
lfs_file_close(&lfs, &file[0]);
lfs_file_close(&lfs, &file[1]);
lfs_file_close(&lfs, &file[2]);
lfs_dir_open(&lfs, &dir[0], "/") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
info.type => LFS_TYPE_DIR;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "e") => 0;
info.type => LFS_TYPE_REG;
info.size => 10;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "g") => 0;
info.type => LFS_TYPE_REG;
info.size => 10;
lfs_dir_read(&lfs, &dir[0], &info) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_file_open(&lfs, &file[0], "e", LFS_O_RDONLY) => 0;
lfs_file_open(&lfs, &file[1], "g", LFS_O_RDONLY) => 0;
for (int i = 0; i < 10; i++) {
lfs_file_read(&lfs, &file[0], buffer, 1) => 1;
buffer[0] => 'e';
lfs_file_read(&lfs, &file[1], buffer, 1) => 1;
buffer[0] => 'g';
}
lfs_file_close(&lfs, &file[0]);
lfs_file_close(&lfs, &file[1]);
lfs_unmount(&lfs) => 0;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,123 @@
#!/bin/bash
set -eu
echo "=== Path tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
TEST
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "tea") => 0;
lfs_mkdir(&lfs, "coffee") => 0;
lfs_mkdir(&lfs, "soda") => 0;
lfs_mkdir(&lfs, "tea/hottea") => 0;
lfs_mkdir(&lfs, "tea/warmtea") => 0;
lfs_mkdir(&lfs, "tea/coldtea") => 0;
lfs_mkdir(&lfs, "coffee/hotcoffee") => 0;
lfs_mkdir(&lfs, "coffee/warmcoffee") => 0;
lfs_mkdir(&lfs, "coffee/coldcoffee") => 0;
lfs_mkdir(&lfs, "soda/hotsoda") => 0;
lfs_mkdir(&lfs, "soda/warmsoda") => 0;
lfs_mkdir(&lfs, "soda/coldsoda") => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Root path tests ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "/tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_mkdir(&lfs, "/milk1") => 0;
lfs_stat(&lfs, "/milk1", &info) => 0;
strcmp(info.name, "milk1") => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Redundant slash path tests ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "/tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "//tea//hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "///tea///hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_mkdir(&lfs, "///milk2") => 0;
lfs_stat(&lfs, "///milk2", &info) => 0;
strcmp(info.name, "milk2") => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Dot path tests ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "./tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "/./tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "/././tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "/./tea/./hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_mkdir(&lfs, "/./milk3") => 0;
lfs_stat(&lfs, "/./milk3", &info) => 0;
strcmp(info.name, "milk3") => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Dot dot path tests ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "coffee/../tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "tea/coldtea/../hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "coffee/coldcoffee/../../tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_stat(&lfs, "coffee/../soda/../tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_mkdir(&lfs, "coffee/../milk4") => 0;
lfs_stat(&lfs, "coffee/../milk4", &info) => 0;
strcmp(info.name, "milk4") => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Root dot dot path tests ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "coffee/../../../../../../tea/hottea", &info) => 0;
strcmp(info.name, "hottea") => 0;
lfs_mkdir(&lfs, "coffee/../../../../../../milk5") => 0;
lfs_stat(&lfs, "coffee/../../../../../../milk5", &info) => 0;
strcmp(info.name, "milk5") => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Root tests ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_stat(&lfs, "/", &info) => 0;
info.type => LFS_TYPE_DIR;
strcmp(info.name, "/") => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Sketchy path tests ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "dirt/ground") => LFS_ERR_NOENT;
lfs_mkdir(&lfs, "dirt/ground/earth") => LFS_ERR_NOENT;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,345 @@
#!/bin/bash
set -eu
SMALLSIZE=4
MEDIUMSIZE=128
LARGESIZE=132
echo "=== Seek tests ==="
rm -rf blocks
tests/test.py << TEST
lfs_format(&lfs, &cfg) => 0;
lfs_mount(&lfs, &cfg) => 0;
lfs_mkdir(&lfs, "hello") => 0;
for (int i = 0; i < $LARGESIZE; i++) {
sprintf((char*)buffer, "hello/kitty%d", i);
lfs_file_open(&lfs, &file[0], (char*)buffer,
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND) => 0;
size = strlen("kittycatcat");
memcpy(buffer, "kittycatcat", size);
for (int j = 0; j < $LARGESIZE; j++) {
lfs_file_write(&lfs, &file[0], buffer, size);
}
lfs_file_close(&lfs, &file[0]) => 0;
}
lfs_unmount(&lfs) => 0;
TEST
echo "--- Simple dir seek ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "hello") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_soff_t pos;
int i;
for (i = 0; i < $SMALLSIZE; i++) {
sprintf((char*)buffer, "kitty%d", i);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
pos = lfs_dir_tell(&lfs, &dir[0]);
}
pos >= 0 => 1;
lfs_dir_seek(&lfs, &dir[0], pos) => 0;
sprintf((char*)buffer, "kitty%d", i);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
lfs_dir_rewind(&lfs, &dir[0]) => 0;
sprintf((char*)buffer, "kitty%d", 0);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
lfs_dir_seek(&lfs, &dir[0], pos) => 0;
sprintf((char*)buffer, "kitty%d", i);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Large dir seek ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_dir_open(&lfs, &dir[0], "hello") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_soff_t pos;
int i;
for (i = 0; i < $MEDIUMSIZE; i++) {
sprintf((char*)buffer, "kitty%d", i);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
pos = lfs_dir_tell(&lfs, &dir[0]);
}
pos >= 0 => 1;
lfs_dir_seek(&lfs, &dir[0], pos) => 0;
sprintf((char*)buffer, "kitty%d", i);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
lfs_dir_rewind(&lfs, &dir[0]) => 0;
sprintf((char*)buffer, "kitty%d", 0);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, ".") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, "..") => 0;
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
lfs_dir_seek(&lfs, &dir[0], pos) => 0;
sprintf((char*)buffer, "kitty%d", i);
lfs_dir_read(&lfs, &dir[0], &info) => 1;
strcmp(info.name, (char*)buffer) => 0;
lfs_dir_close(&lfs, &dir[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Simple file seek ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDONLY) => 0;
lfs_soff_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < $SMALLSIZE; i++) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
pos = lfs_file_tell(&lfs, &file[0]);
}
pos >= 0 => 1;
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_rewind(&lfs, &file[0]) => 0;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_size_t size = lfs_file_size(&lfs, &file[0]);
lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Large file seek ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDONLY) => 0;
lfs_soff_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < $MEDIUMSIZE; i++) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
pos = lfs_file_tell(&lfs, &file[0]);
}
pos >= 0 => 1;
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_rewind(&lfs, &file[0]) => 0;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_CUR) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_size_t size = lfs_file_size(&lfs, &file[0]);
lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Simple file seek and write ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
lfs_soff_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < $SMALLSIZE; i++) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
pos = lfs_file_tell(&lfs, &file[0]);
}
pos >= 0 => 1;
memcpy(buffer, "doggodogdog", size);
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_write(&lfs, &file[0], buffer, size) => size;
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "doggodogdog", size) => 0;
lfs_file_rewind(&lfs, &file[0]) => 0;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "doggodogdog", size) => 0;
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_size_t size = lfs_file_size(&lfs, &file[0]);
lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Large file seek and write ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
lfs_soff_t pos;
size = strlen("kittycatcat");
for (int i = 0; i < $MEDIUMSIZE; i++) {
lfs_file_read(&lfs, &file[0], buffer, size) => size;
if (i != $SMALLSIZE) {
memcmp(buffer, "kittycatcat", size) => 0;
}
pos = lfs_file_tell(&lfs, &file[0]);
}
pos >= 0 => 1;
memcpy(buffer, "doggodogdog", size);
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_write(&lfs, &file[0], buffer, size) => size;
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "doggodogdog", size) => 0;
lfs_file_rewind(&lfs, &file[0]) => 0;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_seek(&lfs, &file[0], pos, LFS_SEEK_SET) => pos;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "doggodogdog", size) => 0;
lfs_file_seek(&lfs, &file[0], -size, LFS_SEEK_END) >= 0 => 1;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_size_t size = lfs_file_size(&lfs, &file[0]);
lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_CUR) => size;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Boundary seek and write ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
size = strlen("hedgehoghog");
const lfs_soff_t offsets[] = {512, 1020, 513, 1021, 511, 1019};
for (int i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
lfs_soff_t off = offsets[i];
memcpy(buffer, "hedgehoghog", size);
lfs_file_seek(&lfs, &file[0], off, LFS_SEEK_SET) => off;
lfs_file_write(&lfs, &file[0], buffer, size) => size;
lfs_file_seek(&lfs, &file[0], off, LFS_SEEK_SET) => off;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "hedgehoghog", size) => 0;
lfs_file_seek(&lfs, &file[0], 0, LFS_SEEK_SET) => 0;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "kittycatcat", size) => 0;
lfs_file_sync(&lfs, &file[0]) => 0;
}
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Out-of-bounds seek ---"
tests/test.py << TEST
lfs_mount(&lfs, &cfg) => 0;
lfs_file_open(&lfs, &file[0], "hello/kitty42", LFS_O_RDWR) => 0;
size = strlen("kittycatcat");
lfs_file_size(&lfs, &file[0]) => $LARGESIZE*size;
lfs_file_seek(&lfs, &file[0], ($LARGESIZE+$SMALLSIZE)*size,
LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size;
lfs_file_read(&lfs, &file[0], buffer, size) => 0;
memcpy(buffer, "porcupineee", size);
lfs_file_write(&lfs, &file[0], buffer, size) => size;
lfs_file_seek(&lfs, &file[0], ($LARGESIZE+$SMALLSIZE)*size,
LFS_SEEK_SET) => ($LARGESIZE+$SMALLSIZE)*size;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "porcupineee", size) => 0;
lfs_file_seek(&lfs, &file[0], $LARGESIZE*size,
LFS_SEEK_SET) => $LARGESIZE*size;
lfs_file_read(&lfs, &file[0], buffer, size) => size;
memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size) => 0;
lfs_file_seek(&lfs, &file[0], -(($LARGESIZE+$SMALLSIZE)*size),
LFS_SEEK_CUR) => LFS_ERR_INVAL;
lfs_file_tell(&lfs, &file[0]) => ($LARGESIZE+1)*size;
lfs_file_seek(&lfs, &file[0], -(($LARGESIZE+2*$SMALLSIZE)*size),
LFS_SEEK_END) => LFS_ERR_INVAL;
lfs_file_tell(&lfs, &file[0]) => ($LARGESIZE+1)*size;
lfs_file_close(&lfs, &file[0]) => 0;
lfs_unmount(&lfs) => 0;
TEST
echo "--- Results ---"
tests/stats.py

View File

@ -0,0 +1,45 @@
{
"name": "littlefs",
"config": {
"read_size": {
"macro_name": "MBED_LFS_READ_SIZE",
"value": 64,
"help": "Minimum size of a block read. This determines the size of read buffers. This may be larger than the physical read size to improve performance by caching more of the block device."
},
"prog_size": {
"macro_name": "MBED_LFS_PROG_SIZE",
"value": 64,
"help": "Minimum size of a block program. This determines the size of program buffers. This may be larger than the physical program size to improve performance by caching more of the block device."
},
"block_size": {
"macro_name": "MBED_LFS_BLOCK_SIZE",
"value": 512,
"help": "Size of an erasable block. This does not impact ram consumption and may be larger than the physical erase size. However, this should be kept small as each file currently takes up an entire block."
},
"lookahead": {
"macro_name": "MBED_LFS_LOOKAHEAD",
"value": 512,
"help": "Number of blocks to lookahead during block allocation. A larger lookahead reduces the number of passes required to allocate a block. The lookahead buffer requires only 1 bit per block so it can be quite large with little ram impact. Should be a multiple of 32."
},
"enable_info": {
"macro_name": "MBED_LFS_ENABLE_INFO",
"value": false,
"help": "Enables info logging, true = enabled, false = disabled, null = disabled only in release builds"
},
"enable_debug": {
"macro_name": "MBED_LFS_ENABLE_DEBUG",
"value": null,
"help": "Enables debug logging, true = enabled, false = disabled, null = disabled only in release builds"
},
"enable_warn": {
"macro_name": "MBED_LFS_ENABLE_WARN",
"value": null,
"help": "Enables warn logging, true = enabled, false = disabled, null = disabled only in release builds"
},
"enable_error": {
"macro_name": "MBED_LFS_ENABLE_ERROR",
"value": null,
"help": "Enables error logging, true = enabled, false = disabled, null = disabled only in release builds"
}
}
}

View File

@ -0,0 +1,9 @@
{
"config": {
"sim-blockdevice": {
"help": "Simulated block device, requires sufficient heap",
"macro_name": "MBED_TEST_SIM_BLOCKDEVICE",
"value": "HeapBlockDevice"
}
}
}

View File

@ -0,0 +1,32 @@
{
"config": {
"header-file": {
"help" : "String for including your driver header file",
"value" : "\"EthernetInterface.h\""
},
"object-construction" : {
"value" : "new EthernetInterface()"
},
"connect-statement" : {
"help" : "Must use 'net' variable name",
"value" : "((EthernetInterface *)net)->connect()"
},
"echo-server-addr" : {
"help" : "IP address of echo server",
"value" : "\"195.34.89.241\""
},
"echo-server-port" : {
"help" : "Port of echo server",
"value" : "7"
},
"tcp-echo-prefix" : {
"help" : "Some servers send a prefix before echoed message",
"value" : "\"u-blox AG TCP/UDP test service\\n\""
},
"sim-blockdevice": {
"help": "Simulated block device, requires sufficient heap",
"macro_name": "MBED_TEST_SIM_BLOCKDEVICE",
"value": "HeapBlockDevice"
}
}
}

View File

@ -22,6 +22,11 @@
"tcp-echo-prefix" : {
"help" : "Some servers send a prefix before echoed message",
"value" : "\"u-blox AG TCP/UDP test service\\n\""
},
"sim-blockdevice": {
"help": "Simulated block device, requires sufficient heap",
"macro_name": "MBED_TEST_SIM_BLOCKDEVICE",
"value": "HeapBlockDevice"
}
}
}

View File

@ -22,6 +22,11 @@
"tcp-echo-prefix" : {
"help" : "Some servers send a prefix before echoed message",
"value" : "\"u-blox AG TCP/UDP test service\\n\""
},
"sim-blockdevice": {
"help": "Simulated block device, requires sufficient heap",
"macro_name": "MBED_TEST_SIM_BLOCKDEVICE",
"value": "HeapBlockDevice"
}
},
"target_overrides": {

View File

@ -22,6 +22,11 @@
"tcp-echo-prefix" : {
"help" : "Some servers send a prefix before echoed message",
"value" : "\"Realtek Ameba TCP/UDP test service\\n\""
},
"sim-blockdevice": {
"help": "Simulated block device, requires sufficient heap",
"macro_name": "MBED_TEST_SIM_BLOCKDEVICE",
"value": "HeapBlockDevice"
}
}
}

View File

@ -1,6 +1,8 @@
{
"ETHERNET" : "EthernetInterface.json",
"HEAPBLOCKDEVICE": "HeapBlockDevice.json",
"HEAPBLOCKDEVICE_AND_ETHERNET": "HeapBlockDeviceAndEthernetInterface.json",
"ODIN_WIFI" : "OdinInterface.json",
"ODIN_ETHERNET" : "Odin_EthernetInterface.json",
"REALTEK_WIFI" : "RealtekInterface.json"
"REALTEK_WIFI" : "RealtekInterface.json"
}

View File

@ -6,5 +6,13 @@
"REALTEK_RTL8195AM": {
"default_test_configuration": "NONE",
"test_configurations": ["REALTEK_WIFI"]
},
"K64F": {
"default_test_configuration": "HEAPBLOCKDEVICE_AND_ETHERNET",
"test_configurations": ["HEAPBLOCKDEVICE_AND_ETHERNET"]
},
"NUCLEO_F429ZI": {
"default_test_configuration": "HEAPBLOCKDEVICE_AND_ETHERNET",
"test_configurations": ["HEAPBLOCKDEVICE_AND_ETHERNET"]
}
}