mirror of https://github.com/ARMmbed/mbed-os.git
LittleFSv2: Bring in v2.2
parent
72d1918a6c
commit
3dfbe139f2
|
@ -0,0 +1,3 @@
|
|||
littlefs/emubd/
|
||||
littlefs/tests/
|
||||
TESTS/util
|
|
@ -0,0 +1,40 @@
|
|||
language: python
|
||||
python: 2.7
|
||||
|
||||
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
|
||||
- make -Clittlefs test QUIET=1
|
||||
|
||||
# Run littlefs functional tests with different configurations
|
||||
# Note: r/w size of 64 is default in mbed
|
||||
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=64 -DLFS2_CACHE_SIZE=64"
|
||||
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=1 -DLFS2_CACHE_SIZE=1"
|
||||
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=512 -DLFS2_CACHE_SIZE=512 -DLFS2_BLOCK_CYCLES=16"
|
||||
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_READ_SIZE=8 -DLFS2_CACHE_SIZE=16 -DLFS2_BLOCK_CYCLES=2"
|
||||
- make -Clittlefs test QUIET=1 CFLAGS+="-DLFS2_BLOCK_COUNT=1023 -DLFS2_LOOKAHEAD_SIZE=256"
|
||||
|
||||
install:
|
||||
# Get arm-none-eabi-gcc
|
||||
- sudo add-apt-repository -y ppa:team-gcc-arm-embedded/ppa
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq gcc-arm-embedded
|
||||
# Get dependencies
|
||||
- git clone https://github.com/armmbed/mbed-os.git
|
||||
# Install python dependencies
|
||||
- pip install -r mbed-os/requirements.txt
|
||||
- sudo apt-get install python3 python3-pip
|
||||
- sudo pip3 install toml
|
||||
# Check versions
|
||||
- arm-none-eabi-gcc --version
|
||||
- gcc --version
|
|
@ -0,0 +1,512 @@
|
|||
/* 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 "filesystem/mbed_filesystem.h"
|
||||
#include "LittleFileSystem2.h"
|
||||
#include "errno.h"
|
||||
#include "lfs2.h"
|
||||
#include "lfs2_util.h"
|
||||
#include "MbedCRC.h"
|
||||
|
||||
namespace mbed {
|
||||
|
||||
extern "C" uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size)
|
||||
{
|
||||
uint32_t initial_xor = lfs2_rbit(crc);
|
||||
MbedCRC<POLY_32BIT_ANSI, 32, CrcMode::TABLE> ct(initial_xor, 0x0, true, true);
|
||||
|
||||
ct.compute((void *)buffer, size, &crc);
|
||||
return crc;
|
||||
}
|
||||
|
||||
////// Conversion functions //////
|
||||
static int lfs2_toerror(int err)
|
||||
{
|
||||
switch (err) {
|
||||
case LFS2_ERR_OK:
|
||||
return 0;
|
||||
case LFS2_ERR_IO:
|
||||
return -EIO;
|
||||
case LFS2_ERR_NOENT:
|
||||
return -ENOENT;
|
||||
case LFS2_ERR_EXIST:
|
||||
return -EEXIST;
|
||||
case LFS2_ERR_NOTDIR:
|
||||
return -ENOTDIR;
|
||||
case LFS2_ERR_ISDIR:
|
||||
return -EISDIR;
|
||||
case LFS2_ERR_INVAL:
|
||||
return -EINVAL;
|
||||
case LFS2_ERR_NOSPC:
|
||||
return -ENOSPC;
|
||||
case LFS2_ERR_NOMEM:
|
||||
return -ENOMEM;
|
||||
case LFS2_ERR_CORRUPT:
|
||||
return -EILSEQ;
|
||||
default:
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
static int lfs2_fromflags(int flags)
|
||||
{
|
||||
return (
|
||||
(((flags & 3) == O_RDONLY) ? LFS2_O_RDONLY : 0) |
|
||||
(((flags & 3) == O_WRONLY) ? LFS2_O_WRONLY : 0) |
|
||||
(((flags & 3) == O_RDWR) ? LFS2_O_RDWR : 0) |
|
||||
((flags & O_CREAT) ? LFS2_O_CREAT : 0) |
|
||||
((flags & O_EXCL) ? LFS2_O_EXCL : 0) |
|
||||
((flags & O_TRUNC) ? LFS2_O_TRUNC : 0) |
|
||||
((flags & O_APPEND) ? LFS2_O_APPEND : 0));
|
||||
}
|
||||
|
||||
static int lfs2_fromwhence(int whence)
|
||||
{
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
return LFS2_SEEK_SET;
|
||||
case SEEK_CUR:
|
||||
return LFS2_SEEK_CUR;
|
||||
case SEEK_END:
|
||||
return LFS2_SEEK_END;
|
||||
default:
|
||||
return whence;
|
||||
}
|
||||
}
|
||||
|
||||
static int lfs2_tomode(int type)
|
||||
{
|
||||
int mode = S_IRWXU | S_IRWXG | S_IRWXO;
|
||||
switch (type) {
|
||||
case LFS2_TYPE_DIR:
|
||||
return mode | S_IFDIR;
|
||||
case LFS2_TYPE_REG:
|
||||
return mode | S_IFREG;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int lfs2_totype(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case LFS2_TYPE_DIR:
|
||||
return DT_DIR;
|
||||
case LFS2_TYPE_REG:
|
||||
return DT_REG;
|
||||
default:
|
||||
return DT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////// Block device operations //////
|
||||
static int lfs2_bd_read(const struct lfs2_config *c, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size)
|
||||
{
|
||||
BlockDevice *bd = (BlockDevice *)c->context;
|
||||
return bd->read(buffer, (bd_addr_t)block * c->block_size + off, size);
|
||||
}
|
||||
|
||||
static int lfs2_bd_prog(const struct lfs2_config *c, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size)
|
||||
{
|
||||
BlockDevice *bd = (BlockDevice *)c->context;
|
||||
return bd->program(buffer, (bd_addr_t)block * c->block_size + off, size);
|
||||
}
|
||||
|
||||
static int lfs2_bd_erase(const struct lfs2_config *c, lfs2_block_t block)
|
||||
{
|
||||
BlockDevice *bd = (BlockDevice *)c->context;
|
||||
return bd->erase((bd_addr_t)block * c->block_size, c->block_size);
|
||||
}
|
||||
|
||||
static int lfs2_bd_sync(const struct lfs2_config *c)
|
||||
{
|
||||
BlockDevice *bd = (BlockDevice *)c->context;
|
||||
return bd->sync();
|
||||
}
|
||||
|
||||
|
||||
////// Generic filesystem operations //////
|
||||
|
||||
// Filesystem implementation (See LittleFileSystem2.h)
|
||||
LittleFileSystem2::LittleFileSystem2(const char *name, BlockDevice *bd,
|
||||
lfs2_size_t block_size, uint32_t block_cycles,
|
||||
lfs2_size_t cache_size, lfs2_size_t lookahead_size)
|
||||
: FileSystem(name)
|
||||
{
|
||||
memset(&_config, 0, sizeof(_config));
|
||||
_config.block_size = block_size;
|
||||
_config.block_cycles = block_cycles;
|
||||
_config.cache_size = cache_size;
|
||||
_config.lookahead_size = lookahead_size;
|
||||
if (bd) {
|
||||
mount(bd);
|
||||
}
|
||||
}
|
||||
|
||||
LittleFileSystem2::~LittleFileSystem2()
|
||||
{
|
||||
// nop if unmounted
|
||||
unmount();
|
||||
}
|
||||
|
||||
int LittleFileSystem2::mount(BlockDevice *bd)
|
||||
{
|
||||
_mutex.lock();
|
||||
_bd = bd;
|
||||
int err = _bd->init();
|
||||
if (err) {
|
||||
_bd = NULL;
|
||||
_mutex.unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
_config.context = bd;
|
||||
_config.read = lfs2_bd_read;
|
||||
_config.prog = lfs2_bd_prog;
|
||||
_config.erase = lfs2_bd_erase;
|
||||
_config.sync = lfs2_bd_sync;
|
||||
_config.read_size = bd->get_read_size();
|
||||
_config.prog_size = bd->get_program_size();
|
||||
_config.block_size = lfs2_max(_config.block_size, (lfs2_size_t)bd->get_erase_size());
|
||||
_config.block_count = bd->size() / _config.block_size;
|
||||
_config.block_cycles = _config.block_cycles;
|
||||
_config.cache_size = lfs2_max(_config.cache_size, _config.prog_size);
|
||||
_config.lookahead_size = lfs2_min(_config.lookahead_size, 8 * ((_config.block_count + 63) / 64));
|
||||
|
||||
err = lfs2_mount(&_lfs, &_config);
|
||||
if (err) {
|
||||
_bd = NULL;
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
_mutex.unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LittleFileSystem2::unmount()
|
||||
{
|
||||
_mutex.lock();
|
||||
int res = 0;
|
||||
if (_bd) {
|
||||
int err = lfs2_unmount(&_lfs);
|
||||
if (err && !res) {
|
||||
res = lfs2_toerror(err);
|
||||
}
|
||||
|
||||
err = _bd->deinit();
|
||||
if (err && !res) {
|
||||
res = err;
|
||||
}
|
||||
|
||||
_bd = NULL;
|
||||
}
|
||||
|
||||
_mutex.unlock();
|
||||
return res;
|
||||
}
|
||||
|
||||
int LittleFileSystem2::format(BlockDevice *bd,
|
||||
lfs2_size_t block_size, uint32_t block_cycles,
|
||||
lfs2_size_t cache_size, lfs2_size_t lookahead_size)
|
||||
{
|
||||
int err = bd->init();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
lfs2_t _lfs;
|
||||
struct lfs2_config _config;
|
||||
|
||||
memset(&_config, 0, sizeof(_config));
|
||||
_config.context = bd;
|
||||
_config.read = lfs2_bd_read;
|
||||
_config.prog = lfs2_bd_prog;
|
||||
_config.erase = lfs2_bd_erase;
|
||||
_config.sync = lfs2_bd_sync;
|
||||
_config.read_size = bd->get_read_size();
|
||||
_config.prog_size = bd->get_program_size();
|
||||
_config.block_size = lfs2_max(block_size, (lfs2_size_t)bd->get_erase_size());
|
||||
_config.block_count = bd->size() / _config.block_size;
|
||||
_config.block_cycles = block_cycles;
|
||||
_config.cache_size = lfs2_max(cache_size, _config.prog_size);
|
||||
_config.lookahead_size = lfs2_min(lookahead_size, 8 * ((_config.block_count + 63) / 64));
|
||||
|
||||
err = lfs2_format(&_lfs, &_config);
|
||||
if (err) {
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
err = bd->deinit();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LittleFileSystem2::reformat(BlockDevice *bd)
|
||||
{
|
||||
_mutex.lock();
|
||||
if (_bd) {
|
||||
if (!bd) {
|
||||
bd = _bd;
|
||||
}
|
||||
|
||||
int err = unmount();
|
||||
if (err) {
|
||||
_mutex.unlock();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bd) {
|
||||
_mutex.unlock();
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int err = LittleFileSystem2::format(bd,
|
||||
_config.block_size,
|
||||
_config.block_cycles,
|
||||
_config.cache_size,
|
||||
_config.lookahead_size);
|
||||
if (err) {
|
||||
_mutex.unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
err = mount(bd);
|
||||
if (err) {
|
||||
_mutex.unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
_mutex.unlock();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LittleFileSystem2::remove(const char *filename)
|
||||
{
|
||||
_mutex.lock();
|
||||
int err = lfs2_remove(&_lfs, filename);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
int LittleFileSystem2::rename(const char *oldname, const char *newname)
|
||||
{
|
||||
_mutex.lock();
|
||||
int err = lfs2_rename(&_lfs, oldname, newname);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
int LittleFileSystem2::mkdir(const char *name, mode_t mode)
|
||||
{
|
||||
_mutex.lock();
|
||||
int err = lfs2_mkdir(&_lfs, name);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
int LittleFileSystem2::stat(const char *name, struct stat *st)
|
||||
{
|
||||
struct lfs2_info info;
|
||||
_mutex.lock();
|
||||
int err = lfs2_stat(&_lfs, name, &info);
|
||||
_mutex.unlock();
|
||||
st->st_size = info.size;
|
||||
st->st_mode = lfs2_tomode(info.type);
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
int LittleFileSystem2::statvfs(const char *name, struct statvfs *st)
|
||||
{
|
||||
memset(st, 0, sizeof(struct statvfs));
|
||||
|
||||
lfs2_ssize_t in_use = 0;
|
||||
_mutex.lock();
|
||||
in_use = lfs2_fs_size(&_lfs);
|
||||
_mutex.unlock();
|
||||
if (in_use < 0) {
|
||||
return in_use;
|
||||
}
|
||||
|
||||
st->f_bsize = _config.block_size;
|
||||
st->f_frsize = _config.block_size;
|
||||
st->f_blocks = _config.block_count;
|
||||
st->f_bfree = _config.block_count - in_use;
|
||||
st->f_bavail = _config.block_count - in_use;
|
||||
st->f_namemax = LFS2_NAME_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
////// File operations //////
|
||||
int LittleFileSystem2::file_open(fs_file_t *file, const char *path, int flags)
|
||||
{
|
||||
lfs2_file_t *f = new lfs2_file_t;
|
||||
_mutex.lock();
|
||||
int err = lfs2_file_open(&_lfs, f, path, lfs2_fromflags(flags));
|
||||
_mutex.unlock();
|
||||
if (!err) {
|
||||
*file = f;
|
||||
} else {
|
||||
delete f;
|
||||
}
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
int LittleFileSystem2::file_close(fs_file_t file)
|
||||
{
|
||||
lfs2_file_t *f = (lfs2_file_t *)file;
|
||||
_mutex.lock();
|
||||
int err = lfs2_file_close(&_lfs, f);
|
||||
_mutex.unlock();
|
||||
delete f;
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
ssize_t LittleFileSystem2::file_read(fs_file_t file, void *buffer, size_t len)
|
||||
{
|
||||
lfs2_file_t *f = (lfs2_file_t *)file;
|
||||
_mutex.lock();
|
||||
lfs2_ssize_t res = lfs2_file_read(&_lfs, f, buffer, len);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(res);
|
||||
}
|
||||
|
||||
ssize_t LittleFileSystem2::file_write(fs_file_t file, const void *buffer, size_t len)
|
||||
{
|
||||
lfs2_file_t *f = (lfs2_file_t *)file;
|
||||
_mutex.lock();
|
||||
lfs2_ssize_t res = lfs2_file_write(&_lfs, f, buffer, len);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(res);
|
||||
}
|
||||
|
||||
int LittleFileSystem2::file_sync(fs_file_t file)
|
||||
{
|
||||
lfs2_file_t *f = (lfs2_file_t *)file;
|
||||
_mutex.lock();
|
||||
int err = lfs2_file_sync(&_lfs, f);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
off_t LittleFileSystem2::file_seek(fs_file_t file, off_t offset, int whence)
|
||||
{
|
||||
lfs2_file_t *f = (lfs2_file_t *)file;
|
||||
_mutex.lock();
|
||||
off_t res = lfs2_file_seek(&_lfs, f, offset, lfs2_fromwhence(whence));
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(res);
|
||||
}
|
||||
|
||||
off_t LittleFileSystem2::file_tell(fs_file_t file)
|
||||
{
|
||||
lfs2_file_t *f = (lfs2_file_t *)file;
|
||||
_mutex.lock();
|
||||
off_t res = lfs2_file_tell(&_lfs, f);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(res);
|
||||
}
|
||||
|
||||
off_t LittleFileSystem2::file_size(fs_file_t file)
|
||||
{
|
||||
lfs2_file_t *f = (lfs2_file_t *)file;
|
||||
_mutex.lock();
|
||||
off_t res = lfs2_file_size(&_lfs, f);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(res);
|
||||
}
|
||||
|
||||
int LittleFileSystem2::file_truncate(fs_file_t file, off_t length)
|
||||
{
|
||||
lfs2_file_t *f = (lfs2_file_t *)file;
|
||||
_mutex.lock();
|
||||
int err = lfs2_file_truncate(&_lfs, f, length);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
|
||||
////// Dir operations //////
|
||||
int LittleFileSystem2::dir_open(fs_dir_t *dir, const char *path)
|
||||
{
|
||||
lfs2_dir_t *d = new lfs2_dir_t;
|
||||
_mutex.lock();
|
||||
int err = lfs2_dir_open(&_lfs, d, path);
|
||||
_mutex.unlock();
|
||||
if (!err) {
|
||||
*dir = d;
|
||||
} else {
|
||||
delete d;
|
||||
}
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
int LittleFileSystem2::dir_close(fs_dir_t dir)
|
||||
{
|
||||
lfs2_dir_t *d = (lfs2_dir_t *)dir;
|
||||
_mutex.lock();
|
||||
int err = lfs2_dir_close(&_lfs, d);
|
||||
_mutex.unlock();
|
||||
delete d;
|
||||
return lfs2_toerror(err);
|
||||
}
|
||||
|
||||
ssize_t LittleFileSystem2::dir_read(fs_dir_t dir, struct dirent *ent)
|
||||
{
|
||||
lfs2_dir_t *d = (lfs2_dir_t *)dir;
|
||||
struct lfs2_info info;
|
||||
_mutex.lock();
|
||||
int res = lfs2_dir_read(&_lfs, d, &info);
|
||||
_mutex.unlock();
|
||||
if (res == 1) {
|
||||
ent->d_type = lfs2_totype(info.type);
|
||||
strcpy(ent->d_name, info.name);
|
||||
}
|
||||
return lfs2_toerror(res);
|
||||
}
|
||||
|
||||
void LittleFileSystem2::dir_seek(fs_dir_t dir, off_t offset)
|
||||
{
|
||||
lfs2_dir_t *d = (lfs2_dir_t *)dir;
|
||||
_mutex.lock();
|
||||
lfs2_dir_seek(&_lfs, d, offset);
|
||||
_mutex.unlock();
|
||||
}
|
||||
|
||||
off_t LittleFileSystem2::dir_tell(fs_dir_t dir)
|
||||
{
|
||||
lfs2_dir_t *d = (lfs2_dir_t *)dir;
|
||||
_mutex.lock();
|
||||
lfs2_soff_t res = lfs2_dir_tell(&_lfs, d);
|
||||
_mutex.unlock();
|
||||
return lfs2_toerror(res);
|
||||
}
|
||||
|
||||
void LittleFileSystem2::dir_rewind(fs_dir_t dir)
|
||||
{
|
||||
lfs2_dir_t *d = (lfs2_dir_t *)dir;
|
||||
_mutex.lock();
|
||||
lfs2_dir_rewind(&_lfs, d);
|
||||
_mutex.unlock();
|
||||
}
|
||||
|
||||
} // namespace mbed
|
|
@ -0,0 +1,308 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
/** \addtogroup storage */
|
||||
/** @{*/
|
||||
|
||||
#ifndef MBED_LFS2FILESYSTEM_H
|
||||
#define MBED_LFS2FILESYSTEM_H
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "BlockDevice.h"
|
||||
#include "PlatformMutex.h"
|
||||
#include "lfs2.h"
|
||||
|
||||
namespace mbed {
|
||||
|
||||
/**
|
||||
* LittleFileSystem2, a little file system
|
||||
*
|
||||
* Synchronization level: Thread safe
|
||||
*/
|
||||
class LittleFileSystem2 : public mbed::FileSystem {
|
||||
public:
|
||||
/** Lifetime of the LittleFileSystem2
|
||||
*
|
||||
* @param name Name of the file system in the tree.
|
||||
* @param bd Block device to mount. Mounted immediately if not NULL.
|
||||
* @param block_size
|
||||
* Size of a logical block. This does not impact ram consumption and
|
||||
* may be larger than the physical erase block. If the physical erase
|
||||
* block is larger, littlefs will use that instead. Larger values will
|
||||
* be faster but waste more storage when files are not aligned to a
|
||||
* block size.
|
||||
* @param block_cycles
|
||||
* Number of erase cycles before a block is forcefully evicted. Larger
|
||||
* values are more efficient but cause less even wear distribution. 0
|
||||
* disables dynamic wear-leveling.
|
||||
* @param cache_size
|
||||
* Size of read/program caches. Each file uses 1 cache, and littlefs
|
||||
* allocates 2 caches for internal operations. Larger values should be
|
||||
* faster but uses more RAM.
|
||||
* @param lookahead_size
|
||||
* Size of the lookahead buffer. A larger lookahead reduces the
|
||||
* allocation scans and results in a faster filesystem but uses
|
||||
* more RAM.
|
||||
*/
|
||||
LittleFileSystem2(const char *name = NULL, mbed::BlockDevice *bd = NULL,
|
||||
lfs2_size_t block_size = MBED_LFS2_BLOCK_SIZE,
|
||||
uint32_t block_cycles = MBED_LFS2_BLOCK_CYCLES,
|
||||
lfs2_size_t cache_size = MBED_LFS2_CACHE_SIZE,
|
||||
lfs2_size_t lookahead = MBED_LFS2_LOOKAHEAD_SIZE);
|
||||
|
||||
virtual ~LittleFileSystem2();
|
||||
|
||||
/** Format a block device with the LittleFileSystem2.
|
||||
*
|
||||
* The block device to format should be mounted when this function is called.
|
||||
*
|
||||
* @param bd This is the block device that will be formatted.
|
||||
* @param block_size
|
||||
* Size of a logical block. This does not impact ram consumption and
|
||||
* may be larger than the physical erase block. If the physical erase
|
||||
* block is larger, littlefs will use that instead. Larger values will
|
||||
* be faster but waste more storage when files are not aligned to a
|
||||
* block size.
|
||||
* @param block_cycles
|
||||
* Number of erase cycles before a block is forcefully evicted. Larger
|
||||
* values are more efficient but cause less even wear distribution. 0
|
||||
* disables dynamic wear-leveling.
|
||||
* @param cache_size
|
||||
* Size of read/program caches. Each file uses 1 cache, and littlefs
|
||||
* allocates 2 caches for internal operations. Larger values should be
|
||||
* faster but uses more RAM.
|
||||
* @param lookahead_size
|
||||
* Size of the lookahead buffer. A larger lookahead reduces the
|
||||
* allocation scans and results in a faster filesystem but uses
|
||||
* more RAM.
|
||||
*/
|
||||
static int format(mbed::BlockDevice *bd,
|
||||
lfs2_size_t block_size = MBED_LFS2_BLOCK_SIZE,
|
||||
uint32_t block_cycles = MBED_LFS2_BLOCK_CYCLES,
|
||||
lfs2_size_t cache_size = MBED_LFS2_CACHE_SIZE,
|
||||
lfs2_size_t lookahead_size = MBED_LFS2_LOOKAHEAD_SIZE);
|
||||
|
||||
/** Mount a file system to a block device.
|
||||
*
|
||||
* @param bd Block device to mount to.
|
||||
* @return 0 on success, negative error code on failure.
|
||||
*/
|
||||
virtual int mount(mbed::BlockDevice *bd);
|
||||
|
||||
/** Unmount a file system from the underlying block device.
|
||||
*
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
virtual int unmount();
|
||||
|
||||
/** Reformat a file system. Results in an empty and mounted file system.
|
||||
*
|
||||
* @param bd
|
||||
* Block device to reformat and mount. If NULL, the mounted
|
||||
* Block device is used.
|
||||
* Note: If mount fails, bd must be provided.
|
||||
* Default: NULL
|
||||
*
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
virtual int reformat(mbed::BlockDevice *bd);
|
||||
|
||||
/** Remove a file from the file system.
|
||||
*
|
||||
* @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 file system.
|
||||
*
|
||||
* @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 file system.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/** Store information about the mounted file system in a statvfs structure.
|
||||
*
|
||||
* @param path The name of the file to find information about.
|
||||
* @param buf The stat buffer to write to.
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
virtual int statvfs(const char *path, struct statvfs *buf);
|
||||
|
||||
protected:
|
||||
#if !(DOXYGEN_ONLY)
|
||||
/** Open a file on the file system.
|
||||
*
|
||||
* @param file Destination of the newly created handle to the referenced file.
|
||||
* @param path The name of the file to open.
|
||||
* @param flags The flags that trigger opening of the file. These flags are O_RDONLY, O_WRONLY, and O_RDWR,
|
||||
* with an O_CREAT, O_TRUNC, or O_APPEND bitwise OR operator.
|
||||
* @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 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);
|
||||
|
||||
/** Truncate or extend a file.
|
||||
*
|
||||
* The file's length is set to the specified value. The seek pointer is
|
||||
* not changed. If the file is extended, the extended area appears as if
|
||||
* it were zero-filled.
|
||||
*
|
||||
* @param file File handle.
|
||||
* @param length The requested new length for the file
|
||||
*
|
||||
* @return Zero on success, negative error code on failure
|
||||
*/
|
||||
virtual int file_truncate(mbed::fs_file_t file, off_t length);
|
||||
|
||||
/** Open a directory on the file system.
|
||||
*
|
||||
* @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);
|
||||
#endif //!(DOXYGEN_ONLY)
|
||||
|
||||
private:
|
||||
lfs2_t _lfs; // The actual file system
|
||||
struct lfs2_config _config;
|
||||
mbed::BlockDevice *_bd; // The block device
|
||||
|
||||
// thread-safe locking
|
||||
PlatformMutex _mutex;
|
||||
};
|
||||
|
||||
} // namespace mbed
|
||||
|
||||
// Added "using" for backwards compatibility
|
||||
#ifndef MBED_NO_GLOBAL_USING_DIRECTIVE
|
||||
using mbed::LittleFileSystem2;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/** @}*/
|
|
@ -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 [LittleFileSystem2](LittleFileSystem2.h)
|
||||
class.
|
||||
|
||||
Here is a simple example that updates a file named "boot_count" every time
|
||||
the application runs:
|
||||
``` c++
|
||||
#include "LittleFileSystem2.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
|
||||
LittleFileSystem2 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
|
||||
LittleFileSystem2::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).
|
|
@ -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 LittleFileSystem2
|
||||
#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();
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,683 @@
|
|||
/* 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>
|
||||
|
||||
using namespace utest::v1;
|
||||
|
||||
// test configuration
|
||||
#ifndef MBED_TEST_FILESYSTEM
|
||||
#define MBED_TEST_FILESYSTEM LittleFileSystem2
|
||||
#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, "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, "potato");
|
||||
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_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, "fried");
|
||||
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(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%03d", 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%03d", 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(-ENOTEMPTY, 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, "fried");
|
||||
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(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(-ENOTEMPTY, 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, "fried");
|
||||
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(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(-ENOTEMPTY, 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, "fried");
|
||||
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(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);
|
||||
}
|
|
@ -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 LittleFileSystem2
|
||||
#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, "largeavacado");
|
||||
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, "smallavacado");
|
||||
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);
|
||||
}
|
|
@ -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 LittleFileSystem2
|
||||
#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);
|
||||
}
|
|
@ -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 LittleFileSystem2
|
||||
#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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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/kitty042", 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/kitty042", 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/kitty042", 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/kitty042", 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/kitty042", 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/kitty042", 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);
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/* 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 LittleFileSystem2
|
||||
#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 for integration level format operations
|
||||
|
||||
void test_format()
|
||||
{
|
||||
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_mount()
|
||||
{
|
||||
int res = bd.init();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
|
||||
{
|
||||
res = fs.mount(&bd);
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
res = fs.unmount();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
}
|
||||
|
||||
res = bd.deinit();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
}
|
||||
|
||||
void test_bad_mount()
|
||||
{
|
||||
int res = bd.init();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
|
||||
{
|
||||
res = bd.erase(0, 2 * bd.get_erase_size());
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
memset(buffer, 0, bd.get_program_size());
|
||||
for (int i = 0; i < 2 * bd.get_erase_size(); i += bd.get_program_size()) {
|
||||
res = bd.program(buffer, i, bd.get_program_size());
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
res = fs.mount(&bd);
|
||||
TEST_ASSERT_NOT_EQUAL(0, res);
|
||||
}
|
||||
|
||||
res = bd.deinit();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
}
|
||||
|
||||
void test_bad_mount_then_reformat()
|
||||
{
|
||||
int res = bd.init();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
|
||||
{
|
||||
res = fs.mount(&bd);
|
||||
TEST_ASSERT_NOT_EQUAL(0, res);
|
||||
|
||||
res = fs.reformat(&bd);
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
|
||||
res = fs.unmount();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
}
|
||||
|
||||
res = bd.deinit();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
}
|
||||
|
||||
void test_good_mount_then_reformat()
|
||||
{
|
||||
int res = bd.init();
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
|
||||
{
|
||||
res = fs.mount(&bd);
|
||||
TEST_ASSERT_EQUAL(0, res);
|
||||
|
||||
res = fs.reformat(&bd);
|
||||
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("Test format", test_format),
|
||||
Case("Test mount", test_mount),
|
||||
Case("Test bad mount", test_bad_mount),
|
||||
Case("Test bad mount than reformat", test_bad_mount_then_reformat),
|
||||
Case("Test good mount than reformat", test_good_mount_then_reformat),
|
||||
};
|
||||
|
||||
Specification specification(test_setup, cases);
|
||||
|
||||
int main()
|
||||
{
|
||||
return !Harness::run(specification);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 "LittleFileSystem2.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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 <errno.h>
|
||||
#include "mbed.h"
|
||||
#include "greentea-client/test_env.h"
|
||||
#include "unity.h"
|
||||
#include "utest.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace utest::v1;
|
||||
|
||||
// test configuration
|
||||
#ifndef MBED_TEST_FILESYSTEM
|
||||
#define MBED_TEST_FILESYSTEM LittleFileSystem2
|
||||
#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, "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, "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(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, "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(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(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%03d", 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%03d", 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(ENOTEMPTY, 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, "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(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(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(ENOTEMPTY, 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, "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(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(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(ENOTEMPTY, 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, "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(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(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);
|
||||
}
|
|
@ -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 LittleFileSystem2
|
||||
#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, "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(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, "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(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);
|
||||
}
|
|
@ -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 LittleFileSystem2
|
||||
#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);
|
||||
}
|
|
@ -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 LittleFileSystem2
|
||||
#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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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%03d", 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/kitty042", "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/kitty042", "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/kitty042", "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/kitty042", "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/kitty042", "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/kitty042", "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);
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
rm -f main.cpp
|
||||
rm -f template_all_names.txt
|
|
@ -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:])
|
|
@ -0,0 +1,33 @@
|
|||
- ['lfs2_format\(&lfs2, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)']
|
||||
- ['lfs2_mount\(&lfs2, &cfg\)', 'fs.mount(&bd)']
|
||||
- ['lfs2_unmount\(&lfs2\)', 'fs.unmount()']
|
||||
- ['lfs2_mkdir\(&lfs2, (.*)\)', 'fs.mkdir(\1, 0777)']
|
||||
- ['lfs2_remove\(&lfs2, (.*)\)', 'fs.remove(\1)']
|
||||
- ['lfs2_rename\(&lfs2, (.*), ?(.*)\)', 'fs.rename(\1, \2)']
|
||||
|
||||
- ['lfs2_dir_open\(&lfs2, &dir\[(.*)\], ?(.*)\)', 'dir[\1].open(&fs, \2)']
|
||||
- ['lfs2_dir_close\(&lfs2, &dir\[(.*)\]\)', 'dir[\1].close()']
|
||||
- ['lfs2_dir_read\(&lfs2, &dir\[(.*)\], &info\)', 'dir[\1].read(&ent)']
|
||||
- ['lfs2_dir_seek\(&lfs2, &dir\[(.*)\], ?(.*)\).*;', 'dir[\1].seek(\2);'] # no dir errors
|
||||
- ['lfs2_dir_rewind\(&lfs2, &dir\[(.*)\]\).*;', 'dir[\1].rewind();'] # no dir errors
|
||||
- ['lfs2_dir_tell\(&lfs2, &dir\[(.*)\]\)', 'dir[\1].tell()']
|
||||
|
||||
- ['lfs2_file_open\(&lfs2, &file\[(.*)\], ?(.*)\)', 'file[\1].open(&fs, \2)']
|
||||
- ['lfs2_file_close\(&lfs2, &file\[(.*)\]\)', 'file[\1].close()']
|
||||
- ['lfs2_file_sync\(&lfs2, &file\[(.*)\]\)', 'file[\1].sync()']
|
||||
- ['lfs2_file_write\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].write(\2, \3)']
|
||||
- ['lfs2_file_read\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'file[\1].read(\2, \3)']
|
||||
- ['lfs2_file_seek\(&lfs2, &file\[(.*)\], ?(.*)\)', 'file[\1].seek(\2)']
|
||||
- ['lfs2_file_tell\(&lfs2, &file\[(.*)\]\)', 'file[\1].tell()']
|
||||
- ['lfs2_file_rewind\(&lfs2, &file\[(.*)\]\).*;', 'file[\1].rewind();'] # no errors
|
||||
- ['lfs2_file_size\(&lfs2, &file\[(.*)\]\)', 'file[\1].size()']
|
||||
|
||||
- ['LFS2_TYPE_([A-Z]+)', 'DT_\1']
|
||||
- ['LFS2_O_([A-Z]+)', 'O_\1']
|
||||
- ['LFS2_SEEK_([A-Z]+)', 'SEEK_\1']
|
||||
- ['LFS2_ERR_([A-Z]+)', '-E\1']
|
||||
- ['lfs2_(s?)size_t', '\1size_t']
|
||||
- ['lfs2_soff_t', 'off_t']
|
||||
- ['info\.name', 'ent.d_name']
|
||||
- ['info\.type', 'ent.d_type']
|
||||
- ['^.*info\.size.*$', ''] # dirent sizes not supported
|
|
@ -0,0 +1,37 @@
|
|||
- ['lfs2_format\(&lfs2, &cfg\)', 'MBED_TEST_FILESYSTEM::format(&bd)']
|
||||
- ['lfs2_mount\(&lfs2, &cfg\)', 'fs.mount(&bd)']
|
||||
- ['lfs2_unmount\(&lfs2\)', 'fs.unmount()']
|
||||
- ['lfs2_mkdir\(&lfs2, (.*)\)', 'mkdir("/fs/" \1, 0777)']
|
||||
- ['lfs2_remove\(&lfs2, (.*)\)', 'remove("/fs/" \1)']
|
||||
- ['lfs2_rename\(&lfs2, (.*), ?(.*)\)', 'rename("/fs/" \1, "/fs/" \2)']
|
||||
|
||||
- ['lfs2_dir_open\(&lfs2, &dir\[(.*)\], ?(.*)\)', '!((dd[\1] = opendir("/fs/" \2)) != NULL)']
|
||||
- ['lfs2_dir_close\(&lfs2, &dir\[(.*)\]\)', 'closedir(dd[\1])']
|
||||
- ['lfs2_dir_read\(&lfs2, &dir\[(.*)\], &info\)', '((ed = readdir(dd[\1])) != NULL)']
|
||||
- ['lfs2_dir_seek\(&lfs2, &dir\[(.*)\], ?(.*)\).*;', 'seekdir(dd[\1], \2);'] # no dir errors
|
||||
- ['lfs2_dir_rewind\(&lfs2, &dir\[(.*)\]\).*;', 'rewinddir(dd[\1]);'] # no dir errors
|
||||
- ['lfs2_dir_tell\(&lfs2, &dir\[(.*)\]\)', 'telldir(dd[\1])']
|
||||
|
||||
- ['lfs2_file_open\(&lfs2, &file\[(.*)\], ?(.*)\)', '!((fd[\1] = fopen("/fs/" \2)) != NULL)']
|
||||
- ['lfs2_file_close\(&lfs2, &file\[(.*)\]\)', 'fclose(fd[\1])']
|
||||
- ['lfs2_file_sync\(&lfs2, &file\[(.*)\]\)', 'fflush(fd[\1])']
|
||||
- ['lfs2_file_write\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'fwrite(\2, 1, \3, fd[\1])']
|
||||
- ['lfs2_file_read\(&lfs2, &file\[(.*)\], ?(.*), (.*)\)', 'fread(\2, 1, \3, fd[\1])']
|
||||
- ['lfs2_file_tell\(&lfs2, &file\[(.*)\]\)', 'ftell(fd[\1])']
|
||||
- ['lfs2_file_rewind\(&lfs2, &file\[(.*)\]\).*;', 'rewind(fd[\1]);'] # no errors
|
||||
|
||||
- ['LFS2_TYPE_([A-Z]+)', 'DT_\1']
|
||||
- ['LFS2_SEEK_([A-Z]+)', 'SEEK_\1']
|
||||
- ['LFS2_ERR_([A-Z]+)', '-E\1']
|
||||
- ['lfs2_(s?)size_t', '\1size_t']
|
||||
- ['lfs2_soff_t', 'off_t']
|
||||
- ['info\.name', 'ed->d_name']
|
||||
- ['info\.type', 'ed->d_type']
|
||||
- ['^.*info\.size.*$', ''] # dirent sizes not supported
|
||||
|
||||
- ['LFS2_O_WRONLY \| LFS2_O_CREAT \| LFS2_O_APPEND', '"ab"']
|
||||
- ['LFS2_O_WRONLY \| LFS2_O_TRUNC', '"wb"']
|
||||
- ['LFS2_O_CREAT \| LFS2_O_WRONLY', '"wb"']
|
||||
- ['LFS2_O_WRONLY \| LFS2_O_CREAT', '"wb"']
|
||||
- ['LFS2_O_RDONLY', '"rb"']
|
||||
- ['LFS2_O_RDWR', '"r+b"']
|
|
@ -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:])
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
{{
|
||||
{test}
|
||||
}}
|
|
@ -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);
|
||||
}}
|
|
@ -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 LittleFileSystem2
|
||||
#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);
|
||||
}}
|
|
@ -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:])
|
|
@ -0,0 +1,12 @@
|
|||
# Compilation output
|
||||
*.o
|
||||
*.d
|
||||
*.a
|
||||
|
||||
# Testing things
|
||||
blocks/
|
||||
lfs2
|
||||
test.c
|
||||
tests/*.toml.*
|
||||
scripts/__pycache__
|
||||
.gdb_history
|
|
@ -0,0 +1,429 @@
|
|||
# environment variables
|
||||
env:
|
||||
global:
|
||||
- CFLAGS=-Werror
|
||||
- MAKEFLAGS=-j
|
||||
|
||||
# cache installation dirs
|
||||
cache:
|
||||
pip: true
|
||||
directories:
|
||||
- $HOME/.cache/apt
|
||||
|
||||
# common installation
|
||||
_: &install-common
|
||||
# need toml, also pip3 isn't installed by default?
|
||||
- sudo apt-get install python3 python3-pip
|
||||
- sudo pip3 install toml
|
||||
# setup a ram-backed disk to speed up reentrant tests
|
||||
- mkdir disks
|
||||
- sudo mount -t tmpfs -o size=100m tmpfs disks
|
||||
- export TFLAGS="$TFLAGS --disk=disks/disk"
|
||||
|
||||
# test cases
|
||||
_: &test-example
|
||||
# make sure example can at least compile
|
||||
- sed -n '/``` c/,/```/{/```/d; p}' README.md > test.c &&
|
||||
make all 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"
|
||||
# default tests
|
||||
_: &test-default
|
||||
# normal+reentrant tests
|
||||
- make test TFLAGS+="-nrk"
|
||||
# common real-life geometries
|
||||
_: &test-nor
|
||||
# NOR flash: read/prog = 1 block = 4KiB
|
||||
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=1 -DLFS2_BLOCK_SIZE=4096"
|
||||
_: &test-emmc
|
||||
# eMMC: read/prog = 512 block = 512
|
||||
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=512 -DLFS2_BLOCK_SIZE=512"
|
||||
_: &test-nand
|
||||
# NAND flash: read/prog = 4KiB block = 32KiB
|
||||
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=4096 -DLFS2_BLOCK_SIZE=\(32*1024\)"
|
||||
# other extreme geometries that are useful for testing various corner cases
|
||||
_: &test-no-intrinsics
|
||||
- make test TFLAGS+="-nrk -DLFS2_NO_INTRINSICS"
|
||||
_: &test-no-inline
|
||||
- make test TFLAGS+="-nrk -DLFS2_INLINE_MAX=0"
|
||||
_: &test-byte-writes
|
||||
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=1 -DLFS2_CACHE_SIZE=1"
|
||||
_: &test-block-cycles
|
||||
- make test TFLAGS+="-nrk -DLFS2_BLOCK_CYCLES=1"
|
||||
_: &test-odd-block-count
|
||||
- make test TFLAGS+="-nrk -DLFS2_BLOCK_COUNT=1023 -DLFS2_LOOKAHEAD_SIZE=256"
|
||||
_: &test-odd-block-size
|
||||
- make test TFLAGS+="-nrk -DLFS2_READ_SIZE=11 -DLFS2_BLOCK_SIZE=704"
|
||||
|
||||
# report size
|
||||
_: &report-size
|
||||
# compile and find the code size with the smallest configuration
|
||||
- make -j1 clean size
|
||||
OBJ="$(ls lfs2*.c | sed 's/\.c/\.o/' | tr '\n' ' ')"
|
||||
CFLAGS+="-DLFS2_NO_ASSERT -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR"
|
||||
| tee sizes
|
||||
# update status if we succeeded, compare with master if possible
|
||||
- |
|
||||
if [ "$TRAVIS_TEST_RESULT" -eq 0 ]
|
||||
then
|
||||
CURR=$(tail -n1 sizes | awk '{print $1}')
|
||||
PREV=$(curl -u "$GEKY_BOT_STATUSES" https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/master \
|
||||
| jq -re "select(.sha != \"$TRAVIS_COMMIT\")
|
||||
| .statuses[] | select(.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\").description
|
||||
| capture(\"code size is (?<size>[0-9]+)\").size" \
|
||||
|| echo 0)
|
||||
|
||||
STATUS="Passed, code size is ${CURR}B"
|
||||
if [ "$PREV" -ne 0 ]
|
||||
then
|
||||
STATUS="$STATUS ($(python -c "print '%+.2f' % (100*($CURR-$PREV)/$PREV.0)")%)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# stage control
|
||||
stages:
|
||||
- name: test
|
||||
- name: deploy
|
||||
if: branch = master AND type = push
|
||||
|
||||
# job control
|
||||
jobs:
|
||||
# native testing
|
||||
- &x86
|
||||
stage: test
|
||||
env:
|
||||
- NAME=littlefs-x86
|
||||
install: *install-common
|
||||
script: [*test-example, *report-size]
|
||||
- {<<: *x86, script: [*test-default, *report-size]}
|
||||
- {<<: *x86, script: [*test-nor, *report-size]}
|
||||
- {<<: *x86, script: [*test-emmc, *report-size]}
|
||||
- {<<: *x86, script: [*test-nand, *report-size]}
|
||||
- {<<: *x86, script: [*test-no-intrinsics, *report-size]}
|
||||
- {<<: *x86, script: [*test-no-inline, *report-size]}
|
||||
- {<<: *x86, script: [*test-byte-writes, *report-size]}
|
||||
- {<<: *x86, script: [*test-block-cycles, *report-size]}
|
||||
- {<<: *x86, script: [*test-odd-block-count, *report-size]}
|
||||
- {<<: *x86, script: [*test-odd-block-size, *report-size]}
|
||||
|
||||
# cross-compile with ARM (thumb mode)
|
||||
- &arm
|
||||
stage: test
|
||||
env:
|
||||
- NAME=littlefs-arm
|
||||
- CC="arm-linux-gnueabi-gcc --static -mthumb"
|
||||
- TFLAGS="$TFLAGS --exec=qemu-arm"
|
||||
install:
|
||||
- *install-common
|
||||
- sudo apt-get install
|
||||
gcc-arm-linux-gnueabi
|
||||
libc6-dev-armel-cross
|
||||
qemu-user
|
||||
- arm-linux-gnueabi-gcc --version
|
||||
- qemu-arm -version
|
||||
script: [*test-example, *report-size]
|
||||
- {<<: *arm, script: [*test-default, *report-size]}
|
||||
- {<<: *arm, script: [*test-nor, *report-size]}
|
||||
- {<<: *arm, script: [*test-emmc, *report-size]}
|
||||
- {<<: *arm, script: [*test-nand, *report-size]}
|
||||
- {<<: *arm, script: [*test-no-intrinsics, *report-size]}
|
||||
- {<<: *arm, script: [*test-no-inline, *report-size]}
|
||||
# it just takes way to long to run byte-level writes in qemu,
|
||||
# note this is still tested in the native tests
|
||||
#- {<<: *arm, script: [*test-byte-writes, *report-size]}
|
||||
- {<<: *arm, script: [*test-block-cycles, *report-size]}
|
||||
- {<<: *arm, script: [*test-odd-block-count, *report-size]}
|
||||
- {<<: *arm, script: [*test-odd-block-size, *report-size]}
|
||||
|
||||
# cross-compile with MIPS
|
||||
- &mips
|
||||
stage: test
|
||||
env:
|
||||
- NAME=littlefs-mips
|
||||
- CC="mips-linux-gnu-gcc --static"
|
||||
- TFLAGS="$TFLAGS --exec=qemu-mips"
|
||||
install:
|
||||
- *install-common
|
||||
- sudo apt-get install
|
||||
gcc-mips-linux-gnu
|
||||
libc6-dev-mips-cross
|
||||
qemu-user
|
||||
- mips-linux-gnu-gcc --version
|
||||
- qemu-mips -version
|
||||
script: [*test-example, *report-size]
|
||||
- {<<: *mips, script: [*test-default, *report-size]}
|
||||
- {<<: *mips, script: [*test-nor, *report-size]}
|
||||
- {<<: *mips, script: [*test-emmc, *report-size]}
|
||||
- {<<: *mips, script: [*test-nand, *report-size]}
|
||||
- {<<: *mips, script: [*test-no-intrinsics, *report-size]}
|
||||
- {<<: *mips, script: [*test-no-inline, *report-size]}
|
||||
# it just takes way to long to run byte-level writes in qemu,
|
||||
# note this is still tested in the native tests
|
||||
#- {<<: *mips, script: [*test-byte-writes, *report-size]}
|
||||
- {<<: *mips, script: [*test-block-cycles, *report-size]}
|
||||
- {<<: *mips, script: [*test-odd-block-count, *report-size]}
|
||||
- {<<: *mips, script: [*test-odd-block-size, *report-size]}
|
||||
|
||||
# cross-compile with PowerPC
|
||||
- &powerpc
|
||||
stage: test
|
||||
env:
|
||||
- NAME=littlefs-powerpc
|
||||
- CC="powerpc-linux-gnu-gcc --static"
|
||||
- TFLAGS="$TFLAGS --exec=qemu-ppc"
|
||||
install:
|
||||
- *install-common
|
||||
- sudo apt-get install
|
||||
gcc-powerpc-linux-gnu
|
||||
libc6-dev-powerpc-cross
|
||||
qemu-user
|
||||
- powerpc-linux-gnu-gcc --version
|
||||
- qemu-ppc -version
|
||||
script: [*test-example, *report-size]
|
||||
- {<<: *powerpc, script: [*test-default, *report-size]}
|
||||
- {<<: *powerpc, script: [*test-nor, *report-size]}
|
||||
- {<<: *powerpc, script: [*test-emmc, *report-size]}
|
||||
- {<<: *powerpc, script: [*test-nand, *report-size]}
|
||||
- {<<: *powerpc, script: [*test-no-intrinsics, *report-size]}
|
||||
- {<<: *powerpc, script: [*test-no-inline, *report-size]}
|
||||
# it just takes way to long to run byte-level writes in qemu,
|
||||
# note this is still tested in the native tests
|
||||
#- {<<: *powerpc, script: [*test-byte-writes, *report-size]}
|
||||
- {<<: *powerpc, script: [*test-block-cycles, *report-size]}
|
||||
- {<<: *powerpc, script: [*test-odd-block-count, *report-size]}
|
||||
- {<<: *powerpc, script: [*test-odd-block-size, *report-size]}
|
||||
|
||||
# test under valgrind, checking for memory errors
|
||||
- &valgrind
|
||||
stage: test
|
||||
env:
|
||||
- NAME=littlefs-valgrind
|
||||
install:
|
||||
- *install-common
|
||||
- sudo apt-get install valgrind
|
||||
- valgrind --version
|
||||
script:
|
||||
- make test TFLAGS+="-k --valgrind"
|
||||
|
||||
# self-host with littlefs-fuse for fuzz test
|
||||
- stage: test
|
||||
env:
|
||||
- NAME=littlefs-fuse
|
||||
if: branch !~ -prefix$
|
||||
install:
|
||||
- *install-common
|
||||
- sudo apt-get install libfuse-dev
|
||||
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2
|
||||
- fusermount -V
|
||||
- gcc --version
|
||||
|
||||
# setup disk for littlefs-fuse
|
||||
- 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=128K of=disk
|
||||
- losetup /dev/loop0 disk
|
||||
script:
|
||||
# self-host test
|
||||
- make -C littlefs-fuse
|
||||
|
||||
- littlefs-fuse/lfs2 --format /dev/loop0
|
||||
- littlefs-fuse/lfs2 /dev/loop0 mount
|
||||
|
||||
- ls mount
|
||||
- mkdir mount/littlefs
|
||||
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs
|
||||
- cd mount/littlefs
|
||||
- stat .
|
||||
- ls -flh
|
||||
- make -B test
|
||||
|
||||
# test migration using littlefs-fuse
|
||||
- stage: test
|
||||
env:
|
||||
- NAME=littlefs-migration
|
||||
if: branch !~ -prefix$
|
||||
install:
|
||||
- *install-common
|
||||
- sudo apt-get install libfuse-dev
|
||||
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v2 v2
|
||||
- git clone --depth 1 https://github.com/geky/littlefs-fuse -b v1 v1
|
||||
- fusermount -V
|
||||
- gcc --version
|
||||
|
||||
# setup disk for littlefs-fuse
|
||||
- rm -rf v2/littlefs/*
|
||||
- cp -r $(git ls-tree --name-only HEAD) v2/littlefs
|
||||
|
||||
- mkdir mount
|
||||
- sudo chmod a+rw /dev/loop0
|
||||
- dd if=/dev/zero bs=512 count=128K of=disk
|
||||
- losetup /dev/loop0 disk
|
||||
script:
|
||||
# compile v1 and v2
|
||||
- make -C v1
|
||||
- make -C v2
|
||||
|
||||
# run self-host test with v1
|
||||
- v1/lfs2 --format /dev/loop0
|
||||
- v1/lfs2 /dev/loop0 mount
|
||||
|
||||
- ls mount
|
||||
- mkdir mount/littlefs
|
||||
- cp -r $(git ls-tree --name-only HEAD) mount/littlefs
|
||||
- cd mount/littlefs
|
||||
- stat .
|
||||
- ls -flh
|
||||
- make -B test
|
||||
|
||||
# attempt to migrate
|
||||
- cd ../..
|
||||
- fusermount -u mount
|
||||
|
||||
- v2/lfs2 --migrate /dev/loop0
|
||||
- v2/lfs2 /dev/loop0 mount
|
||||
|
||||
# run self-host test with v2 right where we left off
|
||||
- ls mount
|
||||
- cd mount/littlefs
|
||||
- stat .
|
||||
- ls -flh
|
||||
- make -B test
|
||||
|
||||
# automatically create releases
|
||||
- stage: deploy
|
||||
env:
|
||||
- NAME=deploy
|
||||
script:
|
||||
- |
|
||||
bash << 'SCRIPT'
|
||||
set -ev
|
||||
# Find version defined in lfs2.h
|
||||
LFS2_VERSION=$(grep -ox '#define LFS2_VERSION .*' lfs2.h | cut -d ' ' -f3)
|
||||
LFS2_VERSION_MAJOR=$((0xffff & ($LFS2_VERSION >> 16)))
|
||||
LFS2_VERSION_MINOR=$((0xffff & ($LFS2_VERSION >> 0)))
|
||||
# Grab latests patch from repo tags, default to 0, needs finagling
|
||||
# to get past github's pagination api
|
||||
PREV_URL=https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/v$LFS2_VERSION_MAJOR.$LFS2_VERSION_MINOR.
|
||||
PREV_URL=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" -I \
|
||||
| sed -n '/^Link/{s/.*<\(.*\)>; rel="last"/\1/;p;q0};$q1' \
|
||||
|| echo $PREV_URL)
|
||||
LFS2_VERSION_PATCH=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" \
|
||||
| jq 'map(.ref | match("\\bv.*\\..*\\.(.*)$";"g")
|
||||
.captures[].string | tonumber) | max + 1' \
|
||||
|| echo 0)
|
||||
# We have our new version
|
||||
LFS2_VERSION="v$LFS2_VERSION_MAJOR.$LFS2_VERSION_MINOR.$LFS2_VERSION_PATCH"
|
||||
echo "VERSION $LFS2_VERSION"
|
||||
# Check that we're the most recent commit
|
||||
CURRENT_COMMIT=$(curl -f -u "$GEKY_BOT_RELEASES" \
|
||||
https://api.github.com/repos/$TRAVIS_REPO_SLUG/commits/master \
|
||||
| jq -re '.sha')
|
||||
[ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ] || exit 0
|
||||
# Create major branch
|
||||
git branch v$LFS2_VERSION_MAJOR HEAD
|
||||
# Create major prefix branch
|
||||
git config user.name "geky bot"
|
||||
git config user.email "bot@geky.net"
|
||||
git fetch https://github.com/$TRAVIS_REPO_SLUG.git \
|
||||
--depth=50 v$LFS2_VERSION_MAJOR-prefix || true
|
||||
./scripts/prefix.py lfs2$LFS2_VERSION_MAJOR
|
||||
git branch v$LFS2_VERSION_MAJOR-prefix $( \
|
||||
git commit-tree $(git write-tree) \
|
||||
$(git rev-parse --verify -q FETCH_HEAD | sed -e 's/^/-p /') \
|
||||
-p HEAD \
|
||||
-m "Generated v$LFS2_VERSION_MAJOR prefixes")
|
||||
git reset --hard
|
||||
# Update major version branches (vN and vN-prefix)
|
||||
git push --atomic https://$GEKY_BOT_RELEASES@github.com/$TRAVIS_REPO_SLUG.git \
|
||||
v$LFS2_VERSION_MAJOR \
|
||||
v$LFS2_VERSION_MAJOR-prefix
|
||||
# Build release notes
|
||||
PREV=$(git tag --sort=-v:refname -l "v*" | head -1)
|
||||
if [ ! -z "$PREV" ]
|
||||
then
|
||||
echo "PREV $PREV"
|
||||
CHANGES=$(git log --oneline $PREV.. --grep='^Merge' --invert-grep)
|
||||
printf "CHANGES\n%s\n\n" "$CHANGES"
|
||||
fi
|
||||
case ${GEKY_BOT_DRAFT:-minor} in
|
||||
true) DRAFT=true ;;
|
||||
minor) DRAFT=$(jq -R 'endswith(".0")' <<< "$LFS2_VERSION") ;;
|
||||
false) DRAFT=false ;;
|
||||
esac
|
||||
# Create the release and patch version tag (vN.N.N)
|
||||
curl -f -u "$GEKY_BOT_RELEASES" -X POST \
|
||||
https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \
|
||||
-d "{
|
||||
\"tag_name\": \"$LFS2_VERSION\",
|
||||
\"name\": \"${LFS2_VERSION%.0}\",
|
||||
\"target_commitish\": \"$TRAVIS_COMMIT\",
|
||||
\"draft\": $DRAFT,
|
||||
\"body\": $(jq -sR '.' <<< "$CHANGES")
|
||||
}" #"
|
||||
SCRIPT
|
||||
|
||||
# manage statuses
|
||||
before_install:
|
||||
- |
|
||||
# don't clobber other (not us) failures
|
||||
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
|
||||
| jq -e ".statuses[] | select(
|
||||
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
|
||||
.state == \"failure\" and
|
||||
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
|
||||
then
|
||||
curl -u "$GEKY_BOT_STATUSES" -X POST \
|
||||
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
|
||||
-d "{
|
||||
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
|
||||
\"state\": \"pending\",
|
||||
\"description\": \"${STATUS:-In progress}\",
|
||||
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
|
||||
}"
|
||||
fi
|
||||
|
||||
after_failure:
|
||||
- |
|
||||
# don't clobber other (not us) failures
|
||||
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
|
||||
| jq -e ".statuses[] | select(
|
||||
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
|
||||
.state == \"failure\" and
|
||||
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
|
||||
then
|
||||
curl -u "$GEKY_BOT_STATUSES" -X POST \
|
||||
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
|
||||
-d "{
|
||||
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
|
||||
\"state\": \"failure\",
|
||||
\"description\": \"${STATUS:-Failed}\",
|
||||
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
|
||||
}"
|
||||
fi
|
||||
|
||||
after_success:
|
||||
- |
|
||||
# don't clobber other (not us) failures
|
||||
# only update if we were last job to mark in progress,
|
||||
# this isn't perfect but is probably good enough
|
||||
if ! curl https://api.github.com/repos/$TRAVIS_REPO_SLUG/status/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
|
||||
| jq -e ".statuses[] | select(
|
||||
.context == \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\" and
|
||||
(.state == \"failure\" or .state == \"pending\") and
|
||||
(.target_url | endswith(\"$TRAVIS_JOB_NUMBER\") | not))"
|
||||
then
|
||||
curl -u "$GEKY_BOT_STATUSES" -X POST \
|
||||
https://api.github.com/repos/$TRAVIS_REPO_SLUG/statuses/${TRAVIS_PULL_REQUEST_SHA:-$TRAVIS_COMMIT} \
|
||||
-d "{
|
||||
\"context\": \"${TRAVIS_BUILD_STAGE_NAME,,}/$NAME\",
|
||||
\"state\": \"success\",
|
||||
\"description\": \"${STATUS:-Passed}\",
|
||||
\"target_url\": \"$TRAVIS_JOB_WEB_URL#$TRAVIS_JOB_NUMBER\"
|
||||
}"
|
||||
fi
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
- Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
- Neither the name of ARM nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,69 @@
|
|||
TARGET = lfs2.a
|
||||
ifneq ($(wildcard test.c main.c),)
|
||||
override TARGET = lfs2
|
||||
endif
|
||||
|
||||
CC ?= gcc
|
||||
AR ?= ar
|
||||
SIZE ?= size
|
||||
|
||||
SRC += $(wildcard *.c bd/*.c)
|
||||
OBJ := $(SRC:.c=.o)
|
||||
DEP := $(SRC:.c=.d)
|
||||
ASM := $(SRC:.c=.s)
|
||||
|
||||
ifdef DEBUG
|
||||
override CFLAGS += -O0 -g3
|
||||
else
|
||||
override CFLAGS += -Os
|
||||
endif
|
||||
ifdef WORD
|
||||
override CFLAGS += -m$(WORD)
|
||||
endif
|
||||
ifdef TRACE
|
||||
override CFLAGS += -DLFS2_YES_TRACE
|
||||
endif
|
||||
override CFLAGS += -I.
|
||||
override CFLAGS += -std=c99 -Wall -pedantic
|
||||
override CFLAGS += -Wextra -Wshadow -Wjump-misses-init -Wundef
|
||||
# Remove missing-field-initializers because of GCC bug
|
||||
override CFLAGS += -Wno-missing-field-initializers
|
||||
|
||||
ifdef VERBOSE
|
||||
override TFLAGS += -v
|
||||
endif
|
||||
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
asm: $(ASM)
|
||||
|
||||
size: $(OBJ)
|
||||
$(SIZE) -t $^
|
||||
|
||||
test:
|
||||
./scripts/test.py $(TFLAGS)
|
||||
.SECONDEXPANSION:
|
||||
test%: tests/test$$(firstword $$(subst \#, ,%)).toml
|
||||
./scripts/test.py $@ $(TFLAGS)
|
||||
|
||||
-include $(DEP)
|
||||
|
||||
lfs2: $(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)
|
||||
rm -f tests/*.toml.*
|
|
@ -0,0 +1,252 @@
|
|||
## littlefs
|
||||
|
||||
A little fail-safe filesystem designed for microcontrollers.
|
||||
|
||||
```
|
||||
| | | .---._____
|
||||
.-----. | |
|
||||
--|o |---| littlefs |
|
||||
--| |---| |
|
||||
'-----' '----------'
|
||||
| | |
|
||||
```
|
||||
|
||||
**Power-loss resilience** - littlefs is designed to handle random power
|
||||
failures. All file operations have strong copy-on-write guarantees and if
|
||||
power is lost the filesystem will fall back to the last known good state.
|
||||
|
||||
**Dynamic wear leveling** - littlefs is designed with flash in mind, and
|
||||
provides wear leveling over dynamic blocks. Additionally, littlefs can
|
||||
detect bad blocks and work around them.
|
||||
|
||||
**Bounded RAM/ROM** - littlefs is designed to work with a small amount of
|
||||
memory. RAM usage is strictly bounded, which means RAM consumption does not
|
||||
change as the filesystem grows. The filesystem contains no unbounded
|
||||
recursion and dynamic memory is limited to configurable buffers that can be
|
||||
provided statically.
|
||||
|
||||
## 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 "lfs2.h"
|
||||
|
||||
// variables used by the filesystem
|
||||
lfs2_t lfs2;
|
||||
lfs2_file_t file;
|
||||
|
||||
// configuration of the filesystem is provided by this struct
|
||||
const struct lfs2_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,
|
||||
.cache_size = 16,
|
||||
.lookahead_size = 16,
|
||||
.block_cycles = 500,
|
||||
};
|
||||
|
||||
// entry point
|
||||
int main(void) {
|
||||
// mount the filesystem
|
||||
int err = lfs2_mount(&lfs2, &cfg);
|
||||
|
||||
// reformat if we can't mount the filesystem
|
||||
// this should only happen on the first boot
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg);
|
||||
lfs2_mount(&lfs2, &cfg);
|
||||
}
|
||||
|
||||
// read current count
|
||||
uint32_t boot_count = 0;
|
||||
lfs2_file_open(&lfs2, &file, "boot_count", LFS2_O_RDWR | LFS2_O_CREAT);
|
||||
lfs2_file_read(&lfs2, &file, &boot_count, sizeof(boot_count));
|
||||
|
||||
// update boot count
|
||||
boot_count += 1;
|
||||
lfs2_file_rewind(&lfs2, &file);
|
||||
lfs2_file_write(&lfs2, &file, &boot_count, sizeof(boot_count));
|
||||
|
||||
// remember the storage is not updated until the file is closed successfully
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
|
||||
// release any resources we were using
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
// 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 found in the comments in [lfs2.h](lfs2.h).
|
||||
|
||||
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 tradeoff 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 `lfs2_t` type which is left up
|
||||
to the user to allocate, allowing multiple filesystems to be in use
|
||||
simultaneously. With the `lfs2_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 event
|
||||
of power-loss. Additionally, file updates are not actually committed to
|
||||
the filesystem until sync or close is called on the file.
|
||||
|
||||
## Other notes
|
||||
|
||||
Littlefs is written in C, and specifically should compile with any compiler
|
||||
that conforms to the `C99` standard.
|
||||
|
||||
All littlefs calls have the potential to return a negative error code. The
|
||||
errors can be either one of those found in the `enum lfs2_error` in
|
||||
[lfs2.h](lfs2.h), or an error returned by the user's block device operations.
|
||||
|
||||
In the configuration struct, the `prog` and `erase` function provided by the
|
||||
user may return a `LFS2_ERR_CORRUPT` error if the implementation already can
|
||||
detect corrupt blocks. However, the wear leveling does not depend on the return
|
||||
code of these functions, instead all data is read back and checked for
|
||||
integrity.
|
||||
|
||||
If your storage caches writes, make sure that the provided `sync` function
|
||||
flushes all the data to memory and ensures that the next read fetches the data
|
||||
from memory, otherwise data integrity can not be guaranteed. If the `write`
|
||||
function does not perform caching, and therefore each `read` or `write` call
|
||||
hits the memory, the `sync` function can simply return 0.
|
||||
|
||||
## Design
|
||||
|
||||
At a high level, littlefs is a block based filesystem that uses small logs to
|
||||
store metadata and larger copy-on-write (COW) structures to store file data.
|
||||
|
||||
In littlefs, these ingredients form a sort of two-layered cake, with the small
|
||||
logs (called metadata pairs) providing fast updates to metadata anywhere on
|
||||
storage, while the COW structures store file data compactly and without any
|
||||
wear amplification cost.
|
||||
|
||||
Both of these data structures are built out of blocks, which are fed by a
|
||||
common block allocator. By limiting the number of erases allowed on a block
|
||||
per allocation, the allocator provides dynamic wear leveling over the entire
|
||||
filesystem.
|
||||
|
||||
```
|
||||
root
|
||||
.--------.--------.
|
||||
| A'| B'| |
|
||||
| | |-> |
|
||||
| | | |
|
||||
'--------'--------'
|
||||
.----' '--------------.
|
||||
A v B v
|
||||
.--------.--------. .--------.--------.
|
||||
| C'| D'| | | E'|new| |
|
||||
| | |-> | | | E'|-> |
|
||||
| | | | | | | |
|
||||
'--------'--------' '--------'--------'
|
||||
.-' '--. | '------------------.
|
||||
v v .-' v
|
||||
.--------. .--------. v .--------.
|
||||
| C | | D | .--------. write | new E |
|
||||
| | | | | E | ==> | |
|
||||
| | | | | | | |
|
||||
'--------' '--------' | | '--------'
|
||||
'--------' .-' |
|
||||
.-' '-. .-------------|------'
|
||||
v v v v
|
||||
.--------. .--------. .--------.
|
||||
| F | | G | | new F |
|
||||
| | | | | |
|
||||
| | | | | |
|
||||
'--------' '--------' '--------'
|
||||
```
|
||||
|
||||
More details on how littlefs works can be found in [DESIGN.md](DESIGN.md) and
|
||||
[SPEC.md](SPEC.md).
|
||||
|
||||
- [DESIGN.md](DESIGN.md) - A fully detailed dive into how littlefs works.
|
||||
I would suggest reading it as the tradeoffs at work are quite interesting.
|
||||
|
||||
- [SPEC.md](SPEC.md) - The on-disk specification of littlefs with all the
|
||||
nitty-gritty details. May be useful for tooling development.
|
||||
|
||||
## Testing
|
||||
|
||||
The littlefs comes with a test suite designed to run on a PC using the
|
||||
[emulated block device](emubd/lfs2_emubd.h) found in the emubd directory.
|
||||
The tests assume a Linux environment and can be started with make:
|
||||
|
||||
``` bash
|
||||
make test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
The littlefs is provided under the [BSD-3-Clause] license. See
|
||||
[LICENSE.md](LICENSE.md) for more information. Contributions to this project
|
||||
are accepted under the same license.
|
||||
|
||||
Individual files contain the following tag instead of the full license text.
|
||||
|
||||
SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
This enables machine processing of license information based on the SPDX
|
||||
License Identifiers that are here available: http://spdx.org/licenses/
|
||||
|
||||
## Related projects
|
||||
|
||||
- [littlefs-fuse] - A [FUSE] wrapper for littlefs. The project allows you to
|
||||
mount littlefs directly on a Linux machine. Can be useful for debugging
|
||||
littlefs if you have an SD card handy.
|
||||
|
||||
- [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][littlefs-js-demo].
|
||||
|
||||
- [mklfs] - A command line tool built by the [Lua RTOS] guys for making
|
||||
littlefs images from a host PC. Supports Windows, Mac OS, and Linux.
|
||||
|
||||
- [Mbed OS] - The easiest way to get started with littlefs is to jump into Mbed
|
||||
which already has block device drivers for most forms of embedded storage.
|
||||
littlefs is available in Mbed OS as the [LittleFileSystem] class.
|
||||
|
||||
- [SPIFFS] - Another excellent embedded filesystem for NOR flash. As a more
|
||||
traditional logging filesystem with full static wear-leveling, SPIFFS will
|
||||
likely outperform littlefs on small memories such as the internal flash on
|
||||
microcontrollers.
|
||||
|
||||
- [Dhara] - An interesting NAND flash translation layer designed for small
|
||||
MCUs. It offers static wear-leveling and power-resilience with only a fixed
|
||||
_O(|address|)_ pointer structure stored on each block and in RAM.
|
||||
|
||||
|
||||
[BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html
|
||||
[littlefs-fuse]: https://github.com/geky/littlefs-fuse
|
||||
[FUSE]: https://github.com/libfuse/libfuse
|
||||
[littlefs-js]: https://github.com/geky/littlefs-js
|
||||
[littlefs-js-demo]:http://littlefs.geky.net/demo.html
|
||||
[mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src
|
||||
[Lua RTOS]: https://github.com/whitecatboard/Lua-RTOS-ESP32
|
||||
[Mbed OS]: https://github.com/armmbed/mbed-os
|
||||
[LittleFileSystem]: https://os.mbed.com/docs/mbed-os/v5.12/apis/littlefilesystem.html
|
||||
[SPIFFS]: https://github.com/pellepl/spiffs
|
||||
[Dhara]: https://github.com/dlbeer/dhara
|
|
@ -0,0 +1,787 @@
|
|||
## littlefs 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. This document assumes you are familiar with the
|
||||
design of the littlefs, for more info on how littlefs works check
|
||||
out [DESIGN.md](DESIGN.md).
|
||||
|
||||
```
|
||||
| | | .---._____
|
||||
.-----. | |
|
||||
--|o |---| littlefs |
|
||||
--| |---| |
|
||||
'-----' '----------'
|
||||
| | |
|
||||
```
|
||||
|
||||
## Some quick notes
|
||||
|
||||
- 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.
|
||||
|
||||
- Block pointers are stored in 32 bits, with the special value `0xffffffff`
|
||||
representing a null block address.
|
||||
|
||||
- In addition to the logical block size (which usually matches the erase
|
||||
block size), littlefs also uses a program block size and read block size.
|
||||
These determine the alignment of block device operations, but don't need
|
||||
to be consistent for portability.
|
||||
|
||||
- By default, all values in littlefs are stored in little-endian byte order.
|
||||
|
||||
## Directories / Metadata pairs
|
||||
|
||||
Metadata pairs form the backbone of littlefs and provide a system for
|
||||
distributed atomic updates. Even the superblock is stored in a metadata pair.
|
||||
|
||||
As their name suggests, a metadata pair is stored in two blocks, with one block
|
||||
providing a backup during erase cycles in case power is lost. These two blocks
|
||||
are not necessarily sequential and may be anywhere on disk, so a "pointer" to a
|
||||
metadata pair is stored as two block pointers.
|
||||
|
||||
On top of this, each metadata block behaves as an appendable log, containing a
|
||||
variable number of commits. Commits can be appended to the metadata log in
|
||||
order to update the metadata without requiring an erase cycles. Note that
|
||||
successive commits may supersede the metadata in previous commits. Only the
|
||||
most recent metadata should be considered valid.
|
||||
|
||||
The high-level layout of a metadata block is fairly simple:
|
||||
|
||||
```
|
||||
.---------------------------------------.
|
||||
.-| revision count | entries | \
|
||||
| |-------------------+ | |
|
||||
| | | |
|
||||
| | | +-- 1st commit
|
||||
| | | |
|
||||
| | +-------------------| |
|
||||
| | | CRC | /
|
||||
| |-------------------+-------------------|
|
||||
| | entries | \
|
||||
| | | |
|
||||
| | | +-- 2nd commit
|
||||
| | +-------------------+--------------| |
|
||||
| | | CRC | padding | /
|
||||
| |----+-------------------+--------------|
|
||||
| | entries | \
|
||||
| | | |
|
||||
| | | +-- 3rd commit
|
||||
| | +-------------------+---------| |
|
||||
| | | CRC | | /
|
||||
| |---------+-------------------+ |
|
||||
| | unwritten storage | more commits
|
||||
| | | |
|
||||
| | | v
|
||||
| | |
|
||||
| | |
|
||||
| '---------------------------------------'
|
||||
'---------------------------------------'
|
||||
```
|
||||
|
||||
Each metadata block contains a 32-bit revision count followed by a number of
|
||||
commits. Each commit contains a variable number of metadata entries followed
|
||||
by a 32-bit CRC.
|
||||
|
||||
Note also that entries aren't necessarily word-aligned. This allows us to
|
||||
store metadata more compactly, however we can only write to addresses that are
|
||||
aligned to our program block size. This means each commit may have padding for
|
||||
alignment.
|
||||
|
||||
Metadata block fields:
|
||||
|
||||
1. **Revision count (32-bits)** - Incremented every erase cycle. If both blocks
|
||||
contain valid commits, only the block with the most recent revision count
|
||||
should be used. Sequence comparison must be used to avoid issues with
|
||||
integer overflow.
|
||||
|
||||
2. **CRC (32-bits)** - Detects corruption from power-loss or other write
|
||||
issues. Uses a CRC-32 with a polynomial of `0x04c11db7` initialized
|
||||
with `0xffffffff`.
|
||||
|
||||
Entries themselves are stored as a 32-bit tag followed by a variable length
|
||||
blob of data. But exactly how these tags are stored is a little bit tricky.
|
||||
|
||||
Metadata blocks support both forward and backward iteration. In order to do
|
||||
this without duplicating the space for each tag, neighboring entries have their
|
||||
tags XORed together, starting with `0xffffffff`.
|
||||
|
||||
```
|
||||
Forward iteration Backward iteration
|
||||
|
||||
.-------------------. 0xffffffff .-------------------.
|
||||
| revision count | | | revision count |
|
||||
|-------------------| v |-------------------|
|
||||
| tag ~A |---> xor -> tag A | tag ~A |---> xor -> 0xffffffff
|
||||
|-------------------| | |-------------------| ^
|
||||
| data A | | | data A | |
|
||||
| | | | | |
|
||||
| | | | | |
|
||||
|-------------------| v |-------------------| |
|
||||
| tag AxB |---> xor -> tag B | tag AxB |---> xor -> tag A
|
||||
|-------------------| | |-------------------| ^
|
||||
| data B | | | data B | |
|
||||
| | | | | |
|
||||
| | | | | |
|
||||
|-------------------| v |-------------------| |
|
||||
| tag BxC |---> xor -> tag C | tag BxC |---> xor -> tag B
|
||||
|-------------------| |-------------------| ^
|
||||
| data C | | data C | |
|
||||
| | | | tag C
|
||||
| | | |
|
||||
| | | |
|
||||
'-------------------' '-------------------'
|
||||
```
|
||||
|
||||
One last thing to note before we get into the details around tag encoding. Each
|
||||
tag contains a valid bit used to indicate if the tag and containing commit is
|
||||
valid. This valid bit is the first bit found in the tag and the commit and can
|
||||
be used to tell if we've attempted to write to the remaining space in the
|
||||
block.
|
||||
|
||||
Here's a more complete example of metadata block containing 4 entries:
|
||||
|
||||
```
|
||||
.---------------------------------------.
|
||||
.-| revision count | tag ~A | \
|
||||
| |-------------------+-------------------| |
|
||||
| | data A | |
|
||||
| | | |
|
||||
| |-------------------+-------------------| |
|
||||
| | tag AxB | data B | <--. |
|
||||
| |-------------------+ | | |
|
||||
| | | | +-- 1st commit
|
||||
| | +-------------------+---------| | |
|
||||
| | | tag BxC | | <-.| |
|
||||
| |---------+-------------------+ | || |
|
||||
| | data C | || |
|
||||
| | | || |
|
||||
| |-------------------+-------------------| || |
|
||||
| | tag CxCRC | CRC | || /
|
||||
| |-------------------+-------------------| ||
|
||||
| | tag CRCxA' | data A' | || \
|
||||
| |-------------------+ | || |
|
||||
| | | || |
|
||||
| | +-------------------+----| || +-- 2nd commit
|
||||
| | | tag CRCxA' | | || |
|
||||
| |--------------+-------------------+----| || |
|
||||
| | CRC | padding | || /
|
||||
| |--------------+----+-------------------| ||
|
||||
| | tag CRCxA'' | data A'' | <---. \
|
||||
| |-------------------+ | ||| |
|
||||
| | | ||| |
|
||||
| | +-------------------+---------| ||| |
|
||||
| | | tag A''xD | | < ||| |
|
||||
| |---------+-------------------+ | |||| +-- 3rd commit
|
||||
| | data D | |||| |
|
||||
| | +---------| |||| |
|
||||
| | | tag Dx| |||| |
|
||||
| |---------+-------------------+---------| |||| |
|
||||
| |CRC | CRC | | |||| /
|
||||
| |---------+-------------------+ | ||||
|
||||
| | unwritten storage | |||| more commits
|
||||
| | | |||| |
|
||||
| | | |||| v
|
||||
| | | ||||
|
||||
| | | ||||
|
||||
| '---------------------------------------' ||||
|
||||
'---------------------------------------' |||'- most recent A
|
||||
||'-- most recent B
|
||||
|'--- most recent C
|
||||
'---- most recent D
|
||||
```
|
||||
|
||||
## Metadata tags
|
||||
|
||||
So in littlefs, 32-bit tags describe every type of metadata. And this means
|
||||
_every_ type of metadata, including file entries, directory fields, and
|
||||
global state. Even the CRCs used to mark the end of commits get their own tag.
|
||||
|
||||
Because of this, the tag format contains some densely packed information. Note
|
||||
that there are multiple levels of types which break down into more info:
|
||||
|
||||
```
|
||||
[---- 32 ----]
|
||||
[1|-- 11 --|-- 10 --|-- 10 --]
|
||||
^. ^ . ^ ^- length
|
||||
|. | . '------------ id
|
||||
|. '-----.------------------ type (type3)
|
||||
'.-----------.------------------ valid bit
|
||||
[-3-|-- 8 --]
|
||||
^ ^- chunk
|
||||
'------- type (type1)
|
||||
```
|
||||
|
||||
|
||||
Before we go further, there's one important thing to note. These tags are
|
||||
**not** stored in little-endian. Tags stored in commits are actually stored
|
||||
in big-endian (and is the only thing in littlefs stored in big-endian). This
|
||||
little bit of craziness comes from the fact that the valid bit must be the
|
||||
first bit in a commit, and when converted to little-endian, the valid bit finds
|
||||
itself in byte 4. We could restructure the tag to store the valid bit lower,
|
||||
but, because none of the fields are byte-aligned, this would be more
|
||||
complicated than just storing the tag in big-endian.
|
||||
|
||||
Another thing to note is that both the tags `0x00000000` and `0xffffffff` are
|
||||
invalid and can be used for null values.
|
||||
|
||||
Metadata tag fields:
|
||||
|
||||
1. **Valid bit (1-bit)** - Indicates if the tag is valid.
|
||||
|
||||
2. **Type3 (11-bits)** - Type of the tag. This field is broken down further
|
||||
into a 3-bit abstract type and an 8-bit chunk field. Note that the value
|
||||
`0x000` is invalid and not assigned a type.
|
||||
|
||||
3. **Type1 (3-bits)** - Abstract type of the tag. Groups the tags into
|
||||
8 categories that facilitate bitmasked lookups.
|
||||
|
||||
4. **Chunk (8-bits)** - Chunk field used for various purposes by the different
|
||||
abstract types. type1+chunk+id form a unique identifier for each tag in the
|
||||
metadata block.
|
||||
|
||||
5. **Id (10-bits)** - File id associated with the tag. Each file in a metadata
|
||||
block gets a unique id which is used to associate tags with that file. The
|
||||
special value `0x3ff` is used for any tags that are not associated with a
|
||||
file, such as directory and global metadata.
|
||||
|
||||
6. **Length (10-bits)** - Length of the data in bytes. The special value
|
||||
`0x3ff` indicates that this tag has been deleted.
|
||||
|
||||
## Metadata types
|
||||
|
||||
What follows is an exhaustive list of metadata in littlefs.
|
||||
|
||||
---
|
||||
#### `0x401` LFS2_TYPE_CREATE
|
||||
|
||||
Creates a new file with this id. Note that files in a metadata block
|
||||
don't necessarily need a create tag. All a create does is move over any
|
||||
files using this id. In this sense a create is similar to insertion into
|
||||
an imaginary array of files.
|
||||
|
||||
The create and delete tags allow littlefs to keep files in a directory
|
||||
ordered alphabetically by filename.
|
||||
|
||||
---
|
||||
#### `0x4ff` LFS2_TYPE_DELETE
|
||||
|
||||
Deletes the file with this id. An inverse to create, this tag moves over
|
||||
any files neighboring this id similar to a deletion from an imaginary
|
||||
array of files.
|
||||
|
||||
---
|
||||
#### `0x0xx` LFS2_TYPE_NAME
|
||||
|
||||
Associates the id with a file name and file type.
|
||||
|
||||
The data contains the file name stored as an ASCII string (may be expanded to
|
||||
UTF8 in the future).
|
||||
|
||||
The chunk field in this tag indicates an 8-bit file type which can be one of
|
||||
the following.
|
||||
|
||||
Currently, the name tag must precede any other tags associated with the id and
|
||||
can not be reassigned without deleting the file.
|
||||
|
||||
Layout of the name tag:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][--- variable length ---]
|
||||
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---]
|
||||
^ ^ ^ ^ ^- size ^- file name
|
||||
| | | '------ id
|
||||
| | '----------- file type
|
||||
| '-------------- type1 (0x0)
|
||||
'----------------- valid bit
|
||||
```
|
||||
|
||||
Name fields:
|
||||
|
||||
1. **file type (8-bits)** - Type of the file.
|
||||
|
||||
2. **file name** - File name stored as an ASCII string.
|
||||
|
||||
---
|
||||
#### `0x001` LFS2_TYPE_REG
|
||||
|
||||
Initializes the id + name as a regular file.
|
||||
|
||||
How each file is stored depends on its struct tag, which is described below.
|
||||
|
||||
---
|
||||
#### `0x002` LFS2_TYPE_DIR
|
||||
|
||||
Initializes the id + name as a directory.
|
||||
|
||||
Directories in littlefs are stored on disk as a linked-list of metadata pairs,
|
||||
each pair containing any number of files in alphabetical order. A pointer to
|
||||
the directory is stored in the struct tag, which is described below.
|
||||
|
||||
---
|
||||
#### `0x0ff` LFS2_TYPE_SUPERBLOCK
|
||||
|
||||
Initializes the id as a superblock entry.
|
||||
|
||||
The superblock entry is a special entry used to store format-time configuration
|
||||
and identify the filesystem.
|
||||
|
||||
The name is a bit of a misnomer. While the superblock entry serves the same
|
||||
purpose as a superblock found in other filesystems, in littlefs the superblock
|
||||
does not get a dedicated block. Instead, the superblock entry is duplicated
|
||||
across a linked-list of metadata pairs rooted on the blocks 0 and 1. The last
|
||||
metadata pair doubles as the root directory of the filesystem.
|
||||
|
||||
```
|
||||
.--------. .--------. .--------. .--------. .--------.
|
||||
.| super |->| super |->| super |->| super |->| file B |
|
||||
|| block | || block | || block | || block | || file C |
|
||||
|| | || | || | || file A | || file D |
|
||||
|'--------' |'--------' |'--------' |'--------' |'--------'
|
||||
'--------' '--------' '--------' '--------' '--------'
|
||||
|
||||
\----------------+----------------/ \----------+----------/
|
||||
superblock pairs root directory
|
||||
```
|
||||
|
||||
The filesystem starts with only the root directory. The superblock metadata
|
||||
pairs grow every time the root pair is compacted in order to prolong the
|
||||
life of the device exponentially.
|
||||
|
||||
The contents of the superblock entry are stored in a name tag with the
|
||||
superblock type and an inline-struct tag. The name tag contains the magic
|
||||
string "littlefs", while the inline-struct tag contains version and
|
||||
configuration information.
|
||||
|
||||
Layout of the superblock name tag and inline-struct tag:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][-- 32 --|-- 32 --]
|
||||
[1|- 11 -| 10 | 10 ][--- 64 ---]
|
||||
^ ^ ^ ^- size (8) ^- magic string ("littlefs")
|
||||
| | '------ id (0)
|
||||
| '------------ type (0x0ff)
|
||||
'----------------- valid bit
|
||||
|
||||
tag data
|
||||
[-- 32 --][-- 32 --|-- 32 --|-- 32 --]
|
||||
[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --|-- 32 --]
|
||||
^ ^ ^ ^ ^- version ^- block size ^- block count
|
||||
| | | | [-- 32 --|-- 32 --|-- 32 --]
|
||||
| | | | [-- 32 --|-- 32 --|-- 32 --]
|
||||
| | | | ^- name max ^- file max ^- attr max
|
||||
| | | '- size (24)
|
||||
| | '------ id (0)
|
||||
| '------------ type (0x201)
|
||||
'----------------- valid bit
|
||||
```
|
||||
|
||||
Superblock fields:
|
||||
|
||||
1. **Magic string (8-bytes)** - Magic string indicating the presence of
|
||||
littlefs on the device. Must be the string "littlefs".
|
||||
|
||||
2. **Version (32-bits)** - The version of littlefs at format time. The version
|
||||
is encoded in a 32-bit value with the upper 16-bits containing the major
|
||||
version, and the lower 16-bits containing the minor version.
|
||||
|
||||
This specification describes version 2.0 (`0x00020000`).
|
||||
|
||||
3. **Block size (32-bits)** - Size of the logical block size used by the
|
||||
filesystem in bytes.
|
||||
|
||||
4. **Block count (32-bits)** - Number of blocks in the filesystem.
|
||||
|
||||
5. **Name max (32-bits)** - Maximum size of file names in bytes.
|
||||
|
||||
6. **File max (32-bits)** - Maximum size of files in bytes.
|
||||
|
||||
7. **Attr max (32-bits)** - Maximum size of file attributes in bytes.
|
||||
|
||||
The superblock must always be the first entry (id 0) in a metadata pair as well
|
||||
as be the first entry written to the block. This means that the superblock
|
||||
entry can be read from a device using offsets alone.
|
||||
|
||||
---
|
||||
#### `0x2xx` LFS2_TYPE_STRUCT
|
||||
|
||||
Associates the id with an on-disk data structure.
|
||||
|
||||
The exact layout of the data depends on the data structure type stored in the
|
||||
chunk field and can be one of the following.
|
||||
|
||||
Any type of struct supersedes all other structs associated with the id. For
|
||||
example, appending a ctz-struct replaces an inline-struct on the same file.
|
||||
|
||||
---
|
||||
#### `0x200` LFS2_TYPE_DIRSTRUCT
|
||||
|
||||
Gives the id a directory data structure.
|
||||
|
||||
Directories in littlefs are stored on disk as a linked-list of metadata pairs,
|
||||
each pair containing any number of files in alphabetical order.
|
||||
|
||||
```
|
||||
|
|
||||
v
|
||||
.--------. .--------. .--------. .--------. .--------. .--------.
|
||||
.| file A |->| file D |->| file G |->| file I |->| file J |->| file M |
|
||||
|| file B | || file E | || file H | || | || file K | || file N |
|
||||
|| file C | || file F | || | || | || file L | || |
|
||||
|'--------' |'--------' |'--------' |'--------' |'--------' |'--------'
|
||||
'--------' '--------' '--------' '--------' '--------' '--------'
|
||||
```
|
||||
|
||||
The dir-struct tag contains only the pointer to the first metadata-pair in the
|
||||
directory. The directory size is not known without traversing the directory.
|
||||
|
||||
The pointer to the next metadata-pair in the directory is stored in a tail tag,
|
||||
which is described below.
|
||||
|
||||
Layout of the dir-struct tag:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][-- 32 --|-- 32 --]
|
||||
[1|- 11 -| 10 | 10 ][--- 64 ---]
|
||||
^ ^ ^ ^- size (8) ^- metadata pair
|
||||
| | '------ id
|
||||
| '------------ type (0x200)
|
||||
'----------------- valid bit
|
||||
```
|
||||
|
||||
Dir-struct fields:
|
||||
|
||||
1. **Metadata pair (8-bytes)** - Pointer to the first metadata-pair
|
||||
in the directory.
|
||||
|
||||
---
|
||||
#### `0x201` LFS2_TYPE_INLINESTRUCT
|
||||
|
||||
Gives the id an inline data structure.
|
||||
|
||||
Inline structs store small files that can fit in the metadata pair. In this
|
||||
case, the file data is stored directly in the tag's data area.
|
||||
|
||||
Layout of the inline-struct tag:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][--- variable length ---]
|
||||
[1|- 11 -| 10 | 10 ][--- (size * 8) ---]
|
||||
^ ^ ^ ^- size ^- inline data
|
||||
| | '------ id
|
||||
| '------------ type (0x201)
|
||||
'----------------- valid bit
|
||||
```
|
||||
|
||||
Inline-struct fields:
|
||||
|
||||
1. **Inline data** - File data stored directly in the metadata-pair.
|
||||
|
||||
---
|
||||
#### `0x202` LFS2_TYPE_CTZSTRUCT
|
||||
|
||||
Gives the id a CTZ skip-list data structure.
|
||||
|
||||
CTZ skip-lists store files that can not fit in the metadata pair. These files
|
||||
are stored in a skip-list in reverse, with a pointer to the head of the
|
||||
skip-list. Note that the head of the skip-list and the file size is enough
|
||||
information to read the file.
|
||||
|
||||
How exactly CTZ skip-lists work is a bit complicated. A full explanation can be
|
||||
found in the [DESIGN.md](DESIGN.md#ctz-skip-lists).
|
||||
|
||||
A quick summary: For every _n_‍th block where _n_ is divisible by
|
||||
2‍_ˣ_, that block contains a pointer to block _n_-2‍_ˣ_.
|
||||
These pointers are stored in increasing order of _x_ in each block of the file
|
||||
before the actual data.
|
||||
|
||||
```
|
||||
|
|
||||
v
|
||||
.--------. .--------. .--------. .--------. .--------. .--------.
|
||||
| A |<-| D |<-| G |<-| J |<-| M |<-| P |
|
||||
| B |<-| E |--| H |<-| K |--| N | | Q |
|
||||
| C |<-| F |--| I |--| L |--| O | | |
|
||||
'--------' '--------' '--------' '--------' '--------' '--------'
|
||||
block 0 block 1 block 2 block 3 block 4 block 5
|
||||
1 skip 2 skips 1 skip 3 skips 1 skip
|
||||
```
|
||||
|
||||
Note that 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.
|
||||
|
||||
Layout of the CTZ-struct tag:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][-- 32 --|-- 32 --]
|
||||
[1|- 11 -| 10 | 10 ][-- 32 --|-- 32 --]
|
||||
^ ^ ^ ^ ^ ^- file size
|
||||
| | | | '-------------------- file head
|
||||
| | | '- size (8)
|
||||
| | '------ id
|
||||
| '------------ type (0x202)
|
||||
'----------------- valid bit
|
||||
```
|
||||
|
||||
CTZ-struct fields:
|
||||
|
||||
1. **File head (32-bits)** - Pointer to the block that is the head of the
|
||||
file's CTZ skip-list.
|
||||
|
||||
2. **File size (32-bits)** - Size of the file in bytes.
|
||||
|
||||
---
|
||||
#### `0x3xx` LFS2_TYPE_USERATTR
|
||||
|
||||
Attaches a user attribute to an id.
|
||||
|
||||
littlefs has a concept of "user attributes". These are small user-provided
|
||||
attributes that can be used to store things like timestamps, hashes,
|
||||
permissions, etc.
|
||||
|
||||
Each user attribute is uniquely identified by an 8-bit type which is stored in
|
||||
the chunk field, and the user attribute itself can be found in the tag's data.
|
||||
|
||||
There are currently no standard user attributes and a portable littlefs
|
||||
implementation should work with any user attributes missing.
|
||||
|
||||
Layout of the user-attr tag:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][--- variable length ---]
|
||||
[1| 3| 8 | 10 | 10 ][--- (size * 8) ---]
|
||||
^ ^ ^ ^ ^- size ^- attr data
|
||||
| | | '------ id
|
||||
| | '----------- attr type
|
||||
| '-------------- type1 (0x3)
|
||||
'----------------- valid bit
|
||||
```
|
||||
|
||||
User-attr fields:
|
||||
|
||||
1. **Attr type (8-bits)** - Type of the user attributes.
|
||||
|
||||
2. **Attr data** - The data associated with the user attribute.
|
||||
|
||||
---
|
||||
#### `0x6xx` LFS2_TYPE_TAIL
|
||||
|
||||
Provides the tail pointer for the metadata pair itself.
|
||||
|
||||
The metadata pair's tail pointer is used in littlefs for a linked-list
|
||||
containing all metadata pairs. The chunk field contains the type of the tail,
|
||||
which indicates if the following metadata pair is a part of the directory
|
||||
(hard-tail) or only used to traverse the filesystem (soft-tail).
|
||||
|
||||
```
|
||||
.--------.
|
||||
.| dir A |-.
|
||||
||softtail| |
|
||||
.--------| |-'
|
||||
| |'--------'
|
||||
| '---|--|-'
|
||||
| .-' '-------------.
|
||||
| v v
|
||||
| .--------. .--------. .--------.
|
||||
'->| dir B |->| dir B |->| dir C |
|
||||
||hardtail| ||softtail| || |
|
||||
|| | || | || |
|
||||
|'--------' |'--------' |'--------'
|
||||
'--------' '--------' '--------'
|
||||
```
|
||||
|
||||
Currently any type supersedes any other preceding tails in the metadata pair,
|
||||
but this may change if additional metadata pair state is added.
|
||||
|
||||
A note about the metadata pair linked-list: Normally, this linked-list contains
|
||||
every metadata pair in the filesystem. However, there are some operations that
|
||||
can cause this linked-list to become out of sync if a power-loss were to occur.
|
||||
When this happens, littlefs sets the "sync" flag in the global state. How
|
||||
exactly this flag is stored is described below.
|
||||
|
||||
When the sync flag is set:
|
||||
|
||||
1. The linked-list may contain an orphaned directory that has been removed in
|
||||
the filesystem.
|
||||
2. The linked-list may contain a metadata pair with a bad block that has been
|
||||
replaced in the filesystem.
|
||||
|
||||
If the sync flag is set, the threaded linked-list must be checked for these
|
||||
errors before it can be used reliably. Note that the threaded linked-list can
|
||||
be ignored if littlefs is mounted read-only.
|
||||
|
||||
Layout of the tail tag:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][-- 32 --|-- 32 --]
|
||||
[1| 3| 8 | 10 | 10 ][--- 64 ---]
|
||||
^ ^ ^ ^ ^- size (8) ^- metadata pair
|
||||
| | | '------ id
|
||||
| | '---------- tail type
|
||||
| '------------- type1 (0x6)
|
||||
'---------------- valid bit
|
||||
```
|
||||
|
||||
Tail fields:
|
||||
|
||||
1. **Tail type (8-bits)** - Type of the tail pointer.
|
||||
|
||||
2. **Metadata pair (8-bytes)** - Pointer to the next metadata-pair.
|
||||
|
||||
---
|
||||
#### `0x600` LFS2_TYPE_SOFTTAIL
|
||||
|
||||
Provides a tail pointer that points to the next metadata pair in the
|
||||
filesystem.
|
||||
|
||||
In this case, the next metadata pair is not a part of our current directory
|
||||
and should only be followed when traversing the entire filesystem.
|
||||
|
||||
---
|
||||
#### `0x601` LFS2_TYPE_HARDTAIL
|
||||
|
||||
Provides a tail pointer that points to the next metadata pair in the
|
||||
directory.
|
||||
|
||||
In this case, the next metadata pair belongs to the current directory. Note
|
||||
that because directories in littlefs are sorted alphabetically, the next
|
||||
metadata pair should only contain filenames greater than any filename in the
|
||||
current pair.
|
||||
|
||||
---
|
||||
#### `0x7xx` LFS2_TYPE_GSTATE
|
||||
|
||||
Provides delta bits for global state entries.
|
||||
|
||||
littlefs has a concept of "global state". This is a small set of state that
|
||||
can be updated by a commit to _any_ metadata pair in the filesystem.
|
||||
|
||||
The way this works is that the global state is stored as a set of deltas
|
||||
distributed across the filesystem such that the global state can be found by
|
||||
the xor-sum of these deltas.
|
||||
|
||||
```
|
||||
.--------. .--------. .--------. .--------. .--------.
|
||||
.| |->| gdelta |->| |->| gdelta |->| gdelta |
|
||||
|| | || 0x23 | || | || 0xff | || 0xce |
|
||||
|| | || | || | || | || |
|
||||
|'--------' |'--------' |'--------' |'--------' |'--------'
|
||||
'--------' '----|---' '--------' '----|---' '----|---'
|
||||
v v v
|
||||
0x00 --> xor ------------------> xor ------> xor --> gstate = 0x12
|
||||
```
|
||||
|
||||
Note that storing globals this way is very expensive in terms of storage usage,
|
||||
so any global state should be kept very small.
|
||||
|
||||
The size and format of each piece of global state depends on the type, which
|
||||
is stored in the chunk field. Currently, the only global state is move state,
|
||||
which is outlined below.
|
||||
|
||||
---
|
||||
#### `0x7ff` LFS2_TYPE_MOVESTATE
|
||||
|
||||
Provides delta bits for the global move state.
|
||||
|
||||
The move state in littlefs is used to store info about operations that could
|
||||
cause to filesystem to go out of sync if the power is lost. The operations
|
||||
where this could occur is moves of files between metadata pairs and any
|
||||
operation that changes metadata pairs on the threaded linked-list.
|
||||
|
||||
In the case of moves, the move state contains a tag + metadata pair describing
|
||||
the source of the ongoing move. If this tag is non-zero, that means that power
|
||||
was lost during a move, and the file exists in two different locations. If this
|
||||
happens, the source of the move should be considered deleted, and the move
|
||||
should be completed (the source should be deleted) before any other write
|
||||
operations to the filesystem.
|
||||
|
||||
In the case of operations to the threaded linked-list, a single "sync" bit is
|
||||
used to indicate that a modification is ongoing. If this sync flag is set, the
|
||||
threaded linked-list will need to be checked for errors before it can be used
|
||||
reliably. The exact cases to check for are described above in the tail tag.
|
||||
|
||||
Layout of the move state:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][-- 32 --|-- 32 --|-- 32 --]
|
||||
[1|- 11 -| 10 | 10 ][1|- 11 -| 10 | 10 |--- 64 ---]
|
||||
^ ^ ^ ^ ^ ^ ^ ^- padding (0) ^- metadata pair
|
||||
| | | | | | '------ move id
|
||||
| | | | | '------------ move type
|
||||
| | | | '----------------- sync bit
|
||||
| | | |
|
||||
| | | '- size (12)
|
||||
| | '------ id (0x3ff)
|
||||
| '------------ type (0x7ff)
|
||||
'----------------- valid bit
|
||||
```
|
||||
|
||||
Move state fields:
|
||||
|
||||
1. **Sync bit (1-bit)** - Indicates if the metadata pair threaded linked-list
|
||||
is in-sync. If set, the threaded linked-list should be checked for errors.
|
||||
|
||||
2. **Move type (11-bits)** - Type of move being performed. Must be either
|
||||
`0x000`, indicating no move, or `0x4ff` indicating the source file should
|
||||
be deleted.
|
||||
|
||||
3. **Move id (10-bits)** - The file id being moved.
|
||||
|
||||
4. **Metadata pair (8-bytes)** - Pointer to the metadata-pair containing
|
||||
the move.
|
||||
|
||||
---
|
||||
#### `0x5xx` LFS2_TYPE_CRC
|
||||
|
||||
Last but not least, the CRC tag marks the end of a commit and provides a
|
||||
checksum for any commits to the metadata block.
|
||||
|
||||
The first 32-bits of the data contain a CRC-32 with a polynomial of
|
||||
`0x04c11db7` initialized with `0xffffffff`. This CRC provides a checksum for
|
||||
all metadata since the previous CRC tag, including the CRC tag itself. For
|
||||
the first commit, this includes the revision count for the metadata block.
|
||||
|
||||
However, the size of the data is not limited to 32-bits. The data field may
|
||||
larger to pad the commit to the next program-aligned boundary.
|
||||
|
||||
In addition, the CRC tag's chunk field contains a set of flags which can
|
||||
change the behaviour of commits. Currently the only flag in use is the lowest
|
||||
bit, which determines the expected state of the valid bit for any following
|
||||
tags. This is used to guarantee that unwritten storage in a metadata block
|
||||
will be detected as invalid.
|
||||
|
||||
Layout of the CRC tag:
|
||||
|
||||
```
|
||||
tag data
|
||||
[-- 32 --][-- 32 --|--- variable length ---]
|
||||
[1| 3| 8 | 10 | 10 ][-- 32 --|--- (size * 8 - 32) ---]
|
||||
^ ^ ^ ^ ^ ^- crc ^- padding
|
||||
| | | | '- size
|
||||
| | | '------ id (0x3ff)
|
||||
| | '----------- valid state
|
||||
| '-------------- type1 (0x5)
|
||||
'----------------- valid bit
|
||||
```
|
||||
|
||||
CRC fields:
|
||||
|
||||
1. **Valid state (1-bit)** - Indicates the expected value of the valid bit for
|
||||
any tags in the next commit.
|
||||
|
||||
2. **CRC (32-bits)** - CRC-32 with a polynomial of `0x04c11db7` initialized
|
||||
with `0xffffffff`.
|
||||
|
||||
3. **Padding** - Padding to the next program-aligned boundary. No guarantees
|
||||
are made about the contents.
|
||||
|
||||
---
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Block device emulated in a file
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "bd/lfs2_filebd.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
int lfs2_filebd_createcfg(const struct lfs2_config *cfg, const char *path,
|
||||
const struct lfs2_filebd_config *bdcfg) {
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_createcfg(%p {.context=%p, "
|
||||
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
|
||||
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
|
||||
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
|
||||
"\"%s\", "
|
||||
"%p {.erase_value=%"PRId32"})",
|
||||
(void*)cfg, cfg->context,
|
||||
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
|
||||
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
|
||||
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
|
||||
path, (void*)bdcfg, bdcfg->erase_value);
|
||||
lfs2_filebd_t *bd = cfg->context;
|
||||
bd->cfg = bdcfg;
|
||||
|
||||
// open file
|
||||
bd->fd = open(path, O_RDWR | O_CREAT, 0666);
|
||||
if (bd->fd < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_createcfg -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_createcfg -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path) {
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_create(%p {.context=%p, "
|
||||
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
|
||||
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
|
||||
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
|
||||
"\"%s\")",
|
||||
(void*)cfg, cfg->context,
|
||||
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
|
||||
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
|
||||
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
|
||||
path);
|
||||
static const struct lfs2_filebd_config defaults = {.erase_value=-1};
|
||||
int err = lfs2_filebd_createcfg(cfg, path, &defaults);
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_create -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfs2_filebd_destroy(const struct lfs2_config *cfg) {
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_destroy(%p)", (void*)cfg);
|
||||
lfs2_filebd_t *bd = cfg->context;
|
||||
int err = close(bd->fd);
|
||||
if (err < 0) {
|
||||
err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_destroy -> %d", err);
|
||||
return err;
|
||||
}
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_destroy -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_filebd_read(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size) {
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_read(%p, "
|
||||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
|
||||
(void*)cfg, block, off, buffer, size);
|
||||
lfs2_filebd_t *bd = cfg->context;
|
||||
|
||||
// check if read is valid
|
||||
LFS2_ASSERT(off % cfg->read_size == 0);
|
||||
LFS2_ASSERT(size % cfg->read_size == 0);
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// zero for reproducability (in case file is truncated)
|
||||
if (bd->cfg->erase_value != -1) {
|
||||
memset(buffer, bd->cfg->erase_value, size);
|
||||
}
|
||||
|
||||
// read
|
||||
off_t res1 = lseek(bd->fd,
|
||||
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
|
||||
if (res1 < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ssize_t res2 = read(bd->fd, buffer, size);
|
||||
if (res2 < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_read -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size) {
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
|
||||
(void*)cfg, block, off, buffer, size);
|
||||
lfs2_filebd_t *bd = cfg->context;
|
||||
|
||||
// check if write is valid
|
||||
LFS2_ASSERT(off % cfg->prog_size == 0);
|
||||
LFS2_ASSERT(size % cfg->prog_size == 0);
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// check that data was erased? only needed for testing
|
||||
if (bd->cfg->erase_value != -1) {
|
||||
off_t res1 = lseek(bd->fd,
|
||||
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
|
||||
if (res1 < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
for (lfs2_off_t i = 0; i < size; i++) {
|
||||
uint8_t c;
|
||||
ssize_t res2 = read(bd->fd, &c, 1);
|
||||
if (res2 < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
LFS2_ASSERT(c == bd->cfg->erase_value);
|
||||
}
|
||||
}
|
||||
|
||||
// program data
|
||||
off_t res1 = lseek(bd->fd,
|
||||
(off_t)block*cfg->block_size + (off_t)off, SEEK_SET);
|
||||
if (res1 < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
ssize_t res2 = write(bd->fd, buffer, size);
|
||||
if (res2 < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_prog -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_filebd_erase(const struct lfs2_config *cfg, lfs2_block_t block) {
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
|
||||
lfs2_filebd_t *bd = cfg->context;
|
||||
|
||||
// check if erase is valid
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// erase, only needed for testing
|
||||
if (bd->cfg->erase_value != -1) {
|
||||
off_t res1 = lseek(bd->fd, (off_t)block*cfg->block_size, SEEK_SET);
|
||||
if (res1 < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
for (lfs2_off_t i = 0; i < cfg->block_size; i++) {
|
||||
ssize_t res2 = write(bd->fd, &(uint8_t){bd->cfg->erase_value}, 1);
|
||||
if (res2 < 0) {
|
||||
int err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_erase -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_filebd_sync(const struct lfs2_config *cfg) {
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_sync(%p)", (void*)cfg);
|
||||
// file sync
|
||||
lfs2_filebd_t *bd = cfg->context;
|
||||
int err = fsync(bd->fd);
|
||||
if (err) {
|
||||
err = -errno;
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_sync -> %d", 0);
|
||||
return err;
|
||||
}
|
||||
|
||||
LFS2_FILEBD_TRACE("lfs2_filebd_sync -> %d", 0);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Block device emulated in a file
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS2_FILEBD_H
|
||||
#define LFS2_FILEBD_H
|
||||
|
||||
#include "lfs2.h"
|
||||
#include "lfs2_util.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
// Block device specific tracing
|
||||
#ifdef LFS2_FILEBD_YES_TRACE
|
||||
#define LFS2_FILEBD_TRACE(...) LFS2_TRACE(__VA_ARGS__)
|
||||
#else
|
||||
#define LFS2_FILEBD_TRACE(...)
|
||||
#endif
|
||||
|
||||
// filebd config (optional)
|
||||
struct lfs2_filebd_config {
|
||||
// 8-bit erase value to use for simulating erases. -1 does not simulate
|
||||
// erases, which can speed up testing by avoiding all the extra block-device
|
||||
// operations to store the erase value.
|
||||
int32_t erase_value;
|
||||
};
|
||||
|
||||
// filebd state
|
||||
typedef struct lfs2_filebd {
|
||||
int fd;
|
||||
const struct lfs2_filebd_config *cfg;
|
||||
} lfs2_filebd_t;
|
||||
|
||||
|
||||
// Create a file block device using the geometry in lfs2_config
|
||||
int lfs2_filebd_create(const struct lfs2_config *cfg, const char *path);
|
||||
int lfs2_filebd_createcfg(const struct lfs2_config *cfg, const char *path,
|
||||
const struct lfs2_filebd_config *bdcfg);
|
||||
|
||||
// Clean up memory associated with block device
|
||||
int lfs2_filebd_destroy(const struct lfs2_config *cfg);
|
||||
|
||||
// Read a block
|
||||
int lfs2_filebd_read(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size);
|
||||
|
||||
// Program a block
|
||||
//
|
||||
// The block must have previously been erased.
|
||||
int lfs2_filebd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size);
|
||||
|
||||
// Erase a block
|
||||
//
|
||||
// A block must be erased before being programmed. The
|
||||
// state of an erased block is undefined.
|
||||
int lfs2_filebd_erase(const struct lfs2_config *cfg, lfs2_block_t block);
|
||||
|
||||
// Sync the block device
|
||||
int lfs2_filebd_sync(const struct lfs2_config *cfg);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Block device emulated in RAM
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "bd/lfs2_rambd.h"
|
||||
|
||||
int lfs2_rambd_createcfg(const struct lfs2_config *cfg,
|
||||
const struct lfs2_rambd_config *bdcfg) {
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_createcfg(%p {.context=%p, "
|
||||
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
|
||||
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
|
||||
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
|
||||
"%p {.erase_value=%"PRId32", .buffer=%p})",
|
||||
(void*)cfg, cfg->context,
|
||||
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
|
||||
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
|
||||
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
|
||||
(void*)bdcfg, bdcfg->erase_value, bdcfg->buffer);
|
||||
lfs2_rambd_t *bd = cfg->context;
|
||||
bd->cfg = bdcfg;
|
||||
|
||||
// allocate buffer?
|
||||
if (bd->cfg->buffer) {
|
||||
bd->buffer = bd->cfg->buffer;
|
||||
} else {
|
||||
bd->buffer = lfs2_malloc(cfg->block_size * cfg->block_count);
|
||||
if (!bd->buffer) {
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_createcfg -> %d", LFS2_ERR_NOMEM);
|
||||
return LFS2_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
// zero for reproducability?
|
||||
if (bd->cfg->erase_value != -1) {
|
||||
memset(bd->buffer, bd->cfg->erase_value,
|
||||
cfg->block_size * cfg->block_count);
|
||||
}
|
||||
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_createcfg -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_rambd_create(const struct lfs2_config *cfg) {
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_create(%p {.context=%p, "
|
||||
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
|
||||
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
|
||||
".block_size=%"PRIu32", .block_count=%"PRIu32"})",
|
||||
(void*)cfg, cfg->context,
|
||||
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
|
||||
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
|
||||
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count);
|
||||
static const struct lfs2_rambd_config defaults = {.erase_value=-1};
|
||||
int err = lfs2_rambd_createcfg(cfg, &defaults);
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_create -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfs2_rambd_destroy(const struct lfs2_config *cfg) {
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_destroy(%p)", (void*)cfg);
|
||||
// clean up memory
|
||||
lfs2_rambd_t *bd = cfg->context;
|
||||
if (!bd->cfg->buffer) {
|
||||
lfs2_free(bd->buffer);
|
||||
}
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_destroy -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_rambd_read(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size) {
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_read(%p, "
|
||||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
|
||||
(void*)cfg, block, off, buffer, size);
|
||||
lfs2_rambd_t *bd = cfg->context;
|
||||
|
||||
// check if read is valid
|
||||
LFS2_ASSERT(off % cfg->read_size == 0);
|
||||
LFS2_ASSERT(size % cfg->read_size == 0);
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// read data
|
||||
memcpy(buffer, &bd->buffer[block*cfg->block_size + off], size);
|
||||
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_read -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_rambd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size) {
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_prog(%p, "
|
||||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
|
||||
(void*)cfg, block, off, buffer, size);
|
||||
lfs2_rambd_t *bd = cfg->context;
|
||||
|
||||
// check if write is valid
|
||||
LFS2_ASSERT(off % cfg->prog_size == 0);
|
||||
LFS2_ASSERT(size % cfg->prog_size == 0);
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// check that data was erased? only needed for testing
|
||||
if (bd->cfg->erase_value != -1) {
|
||||
for (lfs2_off_t i = 0; i < size; i++) {
|
||||
LFS2_ASSERT(bd->buffer[block*cfg->block_size + off + i] ==
|
||||
bd->cfg->erase_value);
|
||||
}
|
||||
}
|
||||
|
||||
// program data
|
||||
memcpy(&bd->buffer[block*cfg->block_size + off], buffer, size);
|
||||
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_prog -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_rambd_erase(const struct lfs2_config *cfg, lfs2_block_t block) {
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
|
||||
lfs2_rambd_t *bd = cfg->context;
|
||||
|
||||
// check if erase is valid
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// erase, only needed for testing
|
||||
if (bd->cfg->erase_value != -1) {
|
||||
memset(&bd->buffer[block*cfg->block_size],
|
||||
bd->cfg->erase_value, cfg->block_size);
|
||||
}
|
||||
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_erase -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_rambd_sync(const struct lfs2_config *cfg) {
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_sync(%p)", (void*)cfg);
|
||||
// sync does nothing because we aren't backed by anything real
|
||||
(void)cfg;
|
||||
LFS2_RAMBD_TRACE("lfs2_rambd_sync -> %d", 0);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Block device emulated in RAM
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS2_RAMBD_H
|
||||
#define LFS2_RAMBD_H
|
||||
|
||||
#include "lfs2.h"
|
||||
#include "lfs2_util.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
// Block device specific tracing
|
||||
#ifdef LFS2_RAMBD_YES_TRACE
|
||||
#define LFS2_RAMBD_TRACE(...) LFS2_TRACE(__VA_ARGS__)
|
||||
#else
|
||||
#define LFS2_RAMBD_TRACE(...)
|
||||
#endif
|
||||
|
||||
// rambd config (optional)
|
||||
struct lfs2_rambd_config {
|
||||
// 8-bit erase value to simulate erasing with. -1 indicates no erase
|
||||
// occurs, which is still a valid block device
|
||||
int32_t erase_value;
|
||||
|
||||
// Optional statically allocated buffer for the block device.
|
||||
void *buffer;
|
||||
};
|
||||
|
||||
// rambd state
|
||||
typedef struct lfs2_rambd {
|
||||
uint8_t *buffer;
|
||||
const struct lfs2_rambd_config *cfg;
|
||||
} lfs2_rambd_t;
|
||||
|
||||
|
||||
// Create a RAM block device using the geometry in lfs2_config
|
||||
int lfs2_rambd_create(const struct lfs2_config *cfg);
|
||||
int lfs2_rambd_createcfg(const struct lfs2_config *cfg,
|
||||
const struct lfs2_rambd_config *bdcfg);
|
||||
|
||||
// Clean up memory associated with block device
|
||||
int lfs2_rambd_destroy(const struct lfs2_config *cfg);
|
||||
|
||||
// Read a block
|
||||
int lfs2_rambd_read(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size);
|
||||
|
||||
// Program a block
|
||||
//
|
||||
// The block must have previously been erased.
|
||||
int lfs2_rambd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size);
|
||||
|
||||
// Erase a block
|
||||
//
|
||||
// A block must be erased before being programmed. The
|
||||
// state of an erased block is undefined.
|
||||
int lfs2_rambd_erase(const struct lfs2_config *cfg, lfs2_block_t block);
|
||||
|
||||
// Sync the block device
|
||||
int lfs2_rambd_sync(const struct lfs2_config *cfg);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* Testing block device, wraps filebd and rambd while providing a bunch
|
||||
* of hooks for testing littlefs in various conditions.
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "bd/lfs2_testbd.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
int lfs2_testbd_createcfg(const struct lfs2_config *cfg, const char *path,
|
||||
const struct lfs2_testbd_config *bdcfg) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_createcfg(%p {.context=%p, "
|
||||
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
|
||||
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
|
||||
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
|
||||
"\"%s\", "
|
||||
"%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", "
|
||||
".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", "
|
||||
".buffer=%p, .wear_buffer=%p})",
|
||||
(void*)cfg, cfg->context,
|
||||
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
|
||||
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
|
||||
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
|
||||
path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles,
|
||||
bdcfg->badblock_behavior, bdcfg->power_cycles,
|
||||
bdcfg->buffer, bdcfg->wear_buffer);
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
bd->cfg = bdcfg;
|
||||
|
||||
// setup testing things
|
||||
bd->persist = path;
|
||||
bd->power_cycles = bd->cfg->power_cycles;
|
||||
|
||||
if (bd->cfg->erase_cycles) {
|
||||
if (bd->cfg->wear_buffer) {
|
||||
bd->wear = bd->cfg->wear_buffer;
|
||||
} else {
|
||||
bd->wear = lfs2_malloc(sizeof(lfs2_testbd_wear_t)*cfg->block_count);
|
||||
if (!bd->wear) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", LFS2_ERR_NOMEM);
|
||||
return LFS2_ERR_NOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
memset(bd->wear, 0, sizeof(lfs2_testbd_wear_t) * cfg->block_count);
|
||||
}
|
||||
|
||||
// create underlying block device
|
||||
if (bd->persist) {
|
||||
bd->u.file.cfg = (struct lfs2_filebd_config){
|
||||
.erase_value = bd->cfg->erase_value,
|
||||
};
|
||||
int err = lfs2_filebd_createcfg(cfg, path, &bd->u.file.cfg);
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", err);
|
||||
return err;
|
||||
} else {
|
||||
bd->u.ram.cfg = (struct lfs2_rambd_config){
|
||||
.erase_value = bd->cfg->erase_value,
|
||||
.buffer = bd->cfg->buffer,
|
||||
};
|
||||
int err = lfs2_rambd_createcfg(cfg, &bd->u.ram.cfg);
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_createcfg -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
int lfs2_testbd_create(const struct lfs2_config *cfg, const char *path) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_create(%p {.context=%p, "
|
||||
".read=%p, .prog=%p, .erase=%p, .sync=%p, "
|
||||
".read_size=%"PRIu32", .prog_size=%"PRIu32", "
|
||||
".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
|
||||
"\"%s\")",
|
||||
(void*)cfg, cfg->context,
|
||||
(void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
|
||||
(void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
|
||||
cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
|
||||
path);
|
||||
static const struct lfs2_testbd_config defaults = {.erase_value=-1};
|
||||
int err = lfs2_testbd_createcfg(cfg, path, &defaults);
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_create -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfs2_testbd_destroy(const struct lfs2_config *cfg) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_destroy(%p)", (void*)cfg);
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) {
|
||||
lfs2_free(bd->wear);
|
||||
}
|
||||
|
||||
if (bd->persist) {
|
||||
int err = lfs2_filebd_destroy(cfg);
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_destroy -> %d", err);
|
||||
return err;
|
||||
} else {
|
||||
int err = lfs2_rambd_destroy(cfg);
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_destroy -> %d", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal mapping to block devices ///
|
||||
static int lfs2_testbd_rawread(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size) {
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
if (bd->persist) {
|
||||
return lfs2_filebd_read(cfg, block, off, buffer, size);
|
||||
} else {
|
||||
return lfs2_rambd_read(cfg, block, off, buffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
static int lfs2_testbd_rawprog(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size) {
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
if (bd->persist) {
|
||||
return lfs2_filebd_prog(cfg, block, off, buffer, size);
|
||||
} else {
|
||||
return lfs2_rambd_prog(cfg, block, off, buffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
static int lfs2_testbd_rawerase(const struct lfs2_config *cfg,
|
||||
lfs2_block_t block) {
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
if (bd->persist) {
|
||||
return lfs2_filebd_erase(cfg, block);
|
||||
} else {
|
||||
return lfs2_rambd_erase(cfg, block);
|
||||
}
|
||||
}
|
||||
|
||||
static int lfs2_testbd_rawsync(const struct lfs2_config *cfg) {
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
if (bd->persist) {
|
||||
return lfs2_filebd_sync(cfg);
|
||||
} else {
|
||||
return lfs2_rambd_sync(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/// block device API ///
|
||||
int lfs2_testbd_read(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_read(%p, "
|
||||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
|
||||
(void*)cfg, block, off, buffer, size);
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
|
||||
// check if read is valid
|
||||
LFS2_ASSERT(off % cfg->read_size == 0);
|
||||
LFS2_ASSERT(size % cfg->read_size == 0);
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// block bad?
|
||||
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles &&
|
||||
bd->cfg->badblock_behavior == LFS2_TESTBD_BADBLOCK_READERROR) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_read -> %d", LFS2_ERR_CORRUPT);
|
||||
return LFS2_ERR_CORRUPT;
|
||||
}
|
||||
|
||||
// read
|
||||
int err = lfs2_testbd_rawread(cfg, block, off, buffer, size);
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_read -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
int lfs2_testbd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_prog(%p, "
|
||||
"0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
|
||||
(void*)cfg, block, off, buffer, size);
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
|
||||
// check if write is valid
|
||||
LFS2_ASSERT(off % cfg->prog_size == 0);
|
||||
LFS2_ASSERT(size % cfg->prog_size == 0);
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// block bad?
|
||||
if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) {
|
||||
if (bd->cfg->badblock_behavior ==
|
||||
LFS2_TESTBD_BADBLOCK_PROGERROR) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", LFS2_ERR_CORRUPT);
|
||||
return LFS2_ERR_CORRUPT;
|
||||
} else if (bd->cfg->badblock_behavior ==
|
||||
LFS2_TESTBD_BADBLOCK_PROGNOOP ||
|
||||
bd->cfg->badblock_behavior ==
|
||||
LFS2_TESTBD_BADBLOCK_ERASENOOP) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// prog
|
||||
int err = lfs2_testbd_rawprog(cfg, block, off, buffer, size);
|
||||
if (err) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// lose power?
|
||||
if (bd->power_cycles > 0) {
|
||||
bd->power_cycles -= 1;
|
||||
if (bd->power_cycles == 0) {
|
||||
// sync to make sure we persist the last changes
|
||||
assert(lfs2_testbd_rawsync(cfg) == 0);
|
||||
// simulate power loss
|
||||
exit(33);
|
||||
}
|
||||
}
|
||||
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_testbd_erase(const struct lfs2_config *cfg, lfs2_block_t block) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
|
||||
// check if erase is valid
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
// block bad?
|
||||
if (bd->cfg->erase_cycles) {
|
||||
if (bd->wear[block] >= bd->cfg->erase_cycles) {
|
||||
if (bd->cfg->badblock_behavior ==
|
||||
LFS2_TESTBD_BADBLOCK_ERASEERROR) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", LFS2_ERR_CORRUPT);
|
||||
return LFS2_ERR_CORRUPT;
|
||||
} else if (bd->cfg->badblock_behavior ==
|
||||
LFS2_TESTBD_BADBLOCK_ERASENOOP) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// mark wear
|
||||
bd->wear[block] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// erase
|
||||
int err = lfs2_testbd_rawerase(cfg, block);
|
||||
if (err) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_erase -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// lose power?
|
||||
if (bd->power_cycles > 0) {
|
||||
bd->power_cycles -= 1;
|
||||
if (bd->power_cycles == 0) {
|
||||
// sync to make sure we persist the last changes
|
||||
assert(lfs2_testbd_rawsync(cfg) == 0);
|
||||
// simulate power loss
|
||||
exit(33);
|
||||
}
|
||||
}
|
||||
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_prog -> %d", 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int lfs2_testbd_sync(const struct lfs2_config *cfg) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_sync(%p)", (void*)cfg);
|
||||
int err = lfs2_testbd_rawsync(cfg);
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_sync -> %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/// simulated wear operations ///
|
||||
lfs2_testbd_swear_t lfs2_testbd_getwear(const struct lfs2_config *cfg,
|
||||
lfs2_block_t block) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block);
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
|
||||
// check if block is valid
|
||||
LFS2_ASSERT(bd->cfg->erase_cycles);
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_getwear -> %"PRIu32, bd->wear[block]);
|
||||
return bd->wear[block];
|
||||
}
|
||||
|
||||
int lfs2_testbd_setwear(const struct lfs2_config *cfg,
|
||||
lfs2_block_t block, lfs2_testbd_wear_t wear) {
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block);
|
||||
lfs2_testbd_t *bd = cfg->context;
|
||||
|
||||
// check if block is valid
|
||||
LFS2_ASSERT(bd->cfg->erase_cycles);
|
||||
LFS2_ASSERT(block < cfg->block_count);
|
||||
|
||||
bd->wear[block] = wear;
|
||||
|
||||
LFS2_TESTBD_TRACE("lfs2_testbd_setwear -> %d", 0);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Testing block device, wraps filebd and rambd while providing a bunch
|
||||
* of hooks for testing littlefs in various conditions.
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS2_TESTBD_H
|
||||
#define LFS2_TESTBD_H
|
||||
|
||||
#include "lfs2.h"
|
||||
#include "lfs2_util.h"
|
||||
#include "bd/lfs2_rambd.h"
|
||||
#include "bd/lfs2_filebd.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
// Block device specific tracing
|
||||
#ifdef LFS2_TESTBD_YES_TRACE
|
||||
#define LFS2_TESTBD_TRACE(...) LFS2_TRACE(__VA_ARGS__)
|
||||
#else
|
||||
#define LFS2_TESTBD_TRACE(...)
|
||||
#endif
|
||||
|
||||
// Mode determining how "bad blocks" behave during testing. This simulates
|
||||
// some real-world circumstances such as progs not sticking (prog-noop),
|
||||
// a readonly disk (erase-noop), and ECC failures (read-error).
|
||||
//
|
||||
// Not that read-noop is not allowed. Read _must_ return a consistent (but
|
||||
// may be arbitrary) value on every read.
|
||||
enum lfs2_testbd_badblock_behavior {
|
||||
LFS2_TESTBD_BADBLOCK_PROGERROR,
|
||||
LFS2_TESTBD_BADBLOCK_ERASEERROR,
|
||||
LFS2_TESTBD_BADBLOCK_READERROR,
|
||||
LFS2_TESTBD_BADBLOCK_PROGNOOP,
|
||||
LFS2_TESTBD_BADBLOCK_ERASENOOP,
|
||||
};
|
||||
|
||||
// Type for measuring wear
|
||||
typedef uint32_t lfs2_testbd_wear_t;
|
||||
typedef int32_t lfs2_testbd_swear_t;
|
||||
|
||||
// testbd config, this is required for testing
|
||||
struct lfs2_testbd_config {
|
||||
// 8-bit erase value to use for simulating erases. -1 does not simulate
|
||||
// erases, which can speed up testing by avoiding all the extra block-device
|
||||
// operations to store the erase value.
|
||||
int32_t erase_value;
|
||||
|
||||
// Number of erase cycles before a block becomes "bad". The exact behavior
|
||||
// of bad blocks is controlled by the badblock_mode.
|
||||
uint32_t erase_cycles;
|
||||
|
||||
// The mode determining how bad blocks fail
|
||||
uint8_t badblock_behavior;
|
||||
|
||||
// Number of write operations (erase/prog) before forcefully killing
|
||||
// the program with exit. Simulates power-loss. 0 disables.
|
||||
uint32_t power_cycles;
|
||||
|
||||
// Optional buffer for RAM block device.
|
||||
void *buffer;
|
||||
|
||||
// Optional buffer for wear
|
||||
void *wear_buffer;
|
||||
};
|
||||
|
||||
// testbd state
|
||||
typedef struct lfs2_testbd {
|
||||
union {
|
||||
struct {
|
||||
lfs2_filebd_t bd;
|
||||
struct lfs2_filebd_config cfg;
|
||||
} file;
|
||||
struct {
|
||||
lfs2_rambd_t bd;
|
||||
struct lfs2_rambd_config cfg;
|
||||
} ram;
|
||||
} u;
|
||||
|
||||
bool persist;
|
||||
uint32_t power_cycles;
|
||||
lfs2_testbd_wear_t *wear;
|
||||
|
||||
const struct lfs2_testbd_config *cfg;
|
||||
} lfs2_testbd_t;
|
||||
|
||||
|
||||
/// Block device API ///
|
||||
|
||||
// Create a test block device using the geometry in lfs2_config
|
||||
//
|
||||
// Note that filebd is used if a path is provided, if path is NULL
|
||||
// testbd will use rambd which can be much faster.
|
||||
int lfs2_testbd_create(const struct lfs2_config *cfg, const char *path);
|
||||
int lfs2_testbd_createcfg(const struct lfs2_config *cfg, const char *path,
|
||||
const struct lfs2_testbd_config *bdcfg);
|
||||
|
||||
// Clean up memory associated with block device
|
||||
int lfs2_testbd_destroy(const struct lfs2_config *cfg);
|
||||
|
||||
// Read a block
|
||||
int lfs2_testbd_read(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size);
|
||||
|
||||
// Program a block
|
||||
//
|
||||
// The block must have previously been erased.
|
||||
int lfs2_testbd_prog(const struct lfs2_config *cfg, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size);
|
||||
|
||||
// Erase a block
|
||||
//
|
||||
// A block must be erased before being programmed. The
|
||||
// state of an erased block is undefined.
|
||||
int lfs2_testbd_erase(const struct lfs2_config *cfg, lfs2_block_t block);
|
||||
|
||||
// Sync the block device
|
||||
int lfs2_testbd_sync(const struct lfs2_config *cfg);
|
||||
|
||||
|
||||
/// Additional extended API for driving test features ///
|
||||
|
||||
// Get simulated wear on a given block
|
||||
lfs2_testbd_swear_t lfs2_testbd_getwear(const struct lfs2_config *cfg,
|
||||
lfs2_block_t block);
|
||||
|
||||
// Manually set simulated wear on a given block
|
||||
int lfs2_testbd_setwear(const struct lfs2_config *cfg,
|
||||
lfs2_block_t block, lfs2_testbd_wear_t wear);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,655 @@
|
|||
/*
|
||||
* The little filesystem
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS2_H
|
||||
#define LFS2_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
/// Version info ///
|
||||
|
||||
// Software library version
|
||||
// Major (top-nibble), incremented on backwards incompatible changes
|
||||
// Minor (bottom-nibble), incremented on feature additions
|
||||
#define LFS2_VERSION 0x00020002
|
||||
#define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16))
|
||||
#define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0))
|
||||
|
||||
// Version of On-disk data structures
|
||||
// Major (top-nibble), incremented on backwards incompatible changes
|
||||
// Minor (bottom-nibble), incremented on feature additions
|
||||
#define LFS2_DISK_VERSION 0x00020000
|
||||
#define LFS2_DISK_VERSION_MAJOR (0xffff & (LFS2_DISK_VERSION >> 16))
|
||||
#define LFS2_DISK_VERSION_MINOR (0xffff & (LFS2_DISK_VERSION >> 0))
|
||||
|
||||
|
||||
/// Definitions ///
|
||||
|
||||
// Type definitions
|
||||
typedef uint32_t lfs2_size_t;
|
||||
typedef uint32_t lfs2_off_t;
|
||||
|
||||
typedef int32_t lfs2_ssize_t;
|
||||
typedef int32_t lfs2_soff_t;
|
||||
|
||||
typedef uint32_t lfs2_block_t;
|
||||
|
||||
// Maximum name size in bytes, may be redefined to reduce the size of the
|
||||
// info struct. Limited to <= 1022. Stored in superblock and must be
|
||||
// respected by other littlefs drivers.
|
||||
#ifndef LFS2_NAME_MAX
|
||||
#define LFS2_NAME_MAX 255
|
||||
#endif
|
||||
|
||||
// Maximum size of a file in bytes, may be redefined to limit to support other
|
||||
// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the
|
||||
// functions lfs2_file_seek, lfs2_file_size, and lfs2_file_tell will return
|
||||
// incorrect values due to using signed integers. Stored in superblock and
|
||||
// must be respected by other littlefs drivers.
|
||||
#ifndef LFS2_FILE_MAX
|
||||
#define LFS2_FILE_MAX 2147483647
|
||||
#endif
|
||||
|
||||
// Maximum size of custom attributes in bytes, may be redefined, but there is
|
||||
// no real benefit to using a smaller LFS2_ATTR_MAX. Limited to <= 1022.
|
||||
#ifndef LFS2_ATTR_MAX
|
||||
#define LFS2_ATTR_MAX 1022
|
||||
#endif
|
||||
|
||||
// Possible error codes, these are negative to allow
|
||||
// valid positive return values
|
||||
enum lfs2_error {
|
||||
LFS2_ERR_OK = 0, // No error
|
||||
LFS2_ERR_IO = -5, // Error during device operation
|
||||
LFS2_ERR_CORRUPT = -84, // Corrupted
|
||||
LFS2_ERR_NOENT = -2, // No directory entry
|
||||
LFS2_ERR_EXIST = -17, // Entry already exists
|
||||
LFS2_ERR_NOTDIR = -20, // Entry is not a dir
|
||||
LFS2_ERR_ISDIR = -21, // Entry is a dir
|
||||
LFS2_ERR_NOTEMPTY = -39, // Dir is not empty
|
||||
LFS2_ERR_BADF = -9, // Bad file number
|
||||
LFS2_ERR_FBIG = -27, // File too large
|
||||
LFS2_ERR_INVAL = -22, // Invalid parameter
|
||||
LFS2_ERR_NOSPC = -28, // No space left on device
|
||||
LFS2_ERR_NOMEM = -12, // No more memory available
|
||||
LFS2_ERR_NOATTR = -61, // No data/attr available
|
||||
LFS2_ERR_NAMETOOLONG = -36, // File name too long
|
||||
};
|
||||
|
||||
// File types
|
||||
enum lfs2_type {
|
||||
// file types
|
||||
LFS2_TYPE_REG = 0x001,
|
||||
LFS2_TYPE_DIR = 0x002,
|
||||
|
||||
// internally used types
|
||||
LFS2_TYPE_SPLICE = 0x400,
|
||||
LFS2_TYPE_NAME = 0x000,
|
||||
LFS2_TYPE_STRUCT = 0x200,
|
||||
LFS2_TYPE_USERATTR = 0x300,
|
||||
LFS2_TYPE_FROM = 0x100,
|
||||
LFS2_TYPE_TAIL = 0x600,
|
||||
LFS2_TYPE_GLOBALS = 0x700,
|
||||
LFS2_TYPE_CRC = 0x500,
|
||||
|
||||
// internally used type specializations
|
||||
LFS2_TYPE_CREATE = 0x401,
|
||||
LFS2_TYPE_DELETE = 0x4ff,
|
||||
LFS2_TYPE_SUPERBLOCK = 0x0ff,
|
||||
LFS2_TYPE_DIRSTRUCT = 0x200,
|
||||
LFS2_TYPE_CTZSTRUCT = 0x202,
|
||||
LFS2_TYPE_INLINESTRUCT = 0x201,
|
||||
LFS2_TYPE_SOFTTAIL = 0x600,
|
||||
LFS2_TYPE_HARDTAIL = 0x601,
|
||||
LFS2_TYPE_MOVESTATE = 0x7ff,
|
||||
|
||||
// internal chip sources
|
||||
LFS2_FROM_NOOP = 0x000,
|
||||
LFS2_FROM_MOVE = 0x101,
|
||||
LFS2_FROM_USERATTRS = 0x102,
|
||||
};
|
||||
|
||||
// File open flags
|
||||
enum lfs2_open_flags {
|
||||
// open flags
|
||||
LFS2_O_RDONLY = 1, // Open a file as read only
|
||||
LFS2_O_WRONLY = 2, // Open a file as write only
|
||||
LFS2_O_RDWR = 3, // Open a file as read and write
|
||||
LFS2_O_CREAT = 0x0100, // Create a file if it does not exist
|
||||
LFS2_O_EXCL = 0x0200, // Fail if a file already exists
|
||||
LFS2_O_TRUNC = 0x0400, // Truncate the existing file to zero size
|
||||
LFS2_O_APPEND = 0x0800, // Move to end of file on every write
|
||||
|
||||
// internally used flags
|
||||
LFS2_F_DIRTY = 0x010000, // File does not match storage
|
||||
LFS2_F_WRITING = 0x020000, // File has been written since last flush
|
||||
LFS2_F_READING = 0x040000, // File has been read since last flush
|
||||
LFS2_F_ERRED = 0x080000, // An error occured during write
|
||||
LFS2_F_INLINE = 0x100000, // Currently inlined in directory entry
|
||||
LFS2_F_OPENED = 0x200000, // File has been opened
|
||||
};
|
||||
|
||||
// File seek flags
|
||||
enum lfs2_whence_flags {
|
||||
LFS2_SEEK_SET = 0, // Seek relative to an absolute position
|
||||
LFS2_SEEK_CUR = 1, // Seek relative to the current file position
|
||||
LFS2_SEEK_END = 2, // Seek relative to the end of the file
|
||||
};
|
||||
|
||||
|
||||
// Configuration provided during initialization of the littlefs
|
||||
struct lfs2_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 lfs2_config *c, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size);
|
||||
|
||||
// Program a region in a block. The block must have previously
|
||||
// been erased. Negative error codes are propogated to the user.
|
||||
// May return LFS2_ERR_CORRUPT if the block should be considered bad.
|
||||
int (*prog)(const struct lfs2_config *c, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_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.
|
||||
// May return LFS2_ERR_CORRUPT if the block should be considered bad.
|
||||
int (*erase)(const struct lfs2_config *c, lfs2_block_t block);
|
||||
|
||||
// Sync the state of the underlying block device. Negative error codes
|
||||
// are propogated to the user.
|
||||
int (*sync)(const struct lfs2_config *c);
|
||||
|
||||
// Minimum size of a block read. All read operations will be a
|
||||
// multiple of this value.
|
||||
lfs2_size_t read_size;
|
||||
|
||||
// Minimum size of a block program. All program operations will be a
|
||||
// multiple of this value.
|
||||
lfs2_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, non-inlined files
|
||||
// take up at minimum one block. Must be a multiple of the read
|
||||
// and program sizes.
|
||||
lfs2_size_t block_size;
|
||||
|
||||
// Number of erasable blocks on the device.
|
||||
lfs2_size_t block_count;
|
||||
|
||||
// Number of erase cycles before littlefs evicts metadata logs and moves
|
||||
// the metadata to another block. Suggested values are in the
|
||||
// range 100-1000, with large values having better performance at the cost
|
||||
// of less consistent wear distribution.
|
||||
//
|
||||
// Set to -1 to disable block-level wear-leveling.
|
||||
int32_t block_cycles;
|
||||
|
||||
// Size of block caches. Each cache buffers a portion of a block in RAM.
|
||||
// The littlefs needs a read cache, a program cache, and one additional
|
||||
// cache per file. Larger caches can improve performance by storing more
|
||||
// data and reducing the number of disk accesses. Must be a multiple of
|
||||
// the read and program sizes, and a factor of the block size.
|
||||
lfs2_size_t cache_size;
|
||||
|
||||
// Size of the lookahead buffer in bytes. A larger lookahead buffer
|
||||
// increases the number of blocks found during an allocation pass. The
|
||||
// lookahead buffer is stored as a compact bitmap, so each byte of RAM
|
||||
// can track 8 blocks. Must be a multiple of 8.
|
||||
lfs2_size_t lookahead_size;
|
||||
|
||||
// Optional statically allocated read buffer. Must be cache_size.
|
||||
// By default lfs2_malloc is used to allocate this buffer.
|
||||
void *read_buffer;
|
||||
|
||||
// Optional statically allocated program buffer. Must be cache_size.
|
||||
// By default lfs2_malloc is used to allocate this buffer.
|
||||
void *prog_buffer;
|
||||
|
||||
// Optional statically allocated lookahead buffer. Must be lookahead_size
|
||||
// and aligned to a 32-bit boundary. By default lfs2_malloc is used to
|
||||
// allocate this buffer.
|
||||
void *lookahead_buffer;
|
||||
|
||||
// Optional upper limit on length of file names in bytes. No downside for
|
||||
// larger names except the size of the info struct which is controlled by
|
||||
// the LFS2_NAME_MAX define. Defaults to LFS2_NAME_MAX when zero. Stored in
|
||||
// superblock and must be respected by other littlefs drivers.
|
||||
lfs2_size_t name_max;
|
||||
|
||||
// Optional upper limit on files in bytes. No downside for larger files
|
||||
// but must be <= LFS2_FILE_MAX. Defaults to LFS2_FILE_MAX when zero. Stored
|
||||
// in superblock and must be respected by other littlefs drivers.
|
||||
lfs2_size_t file_max;
|
||||
|
||||
// Optional upper limit on custom attributes in bytes. No downside for
|
||||
// larger attributes size but must be <= LFS2_ATTR_MAX. Defaults to
|
||||
// LFS2_ATTR_MAX when zero.
|
||||
lfs2_size_t attr_max;
|
||||
};
|
||||
|
||||
// File info structure
|
||||
struct lfs2_info {
|
||||
// Type of the file, either LFS2_TYPE_REG or LFS2_TYPE_DIR
|
||||
uint8_t type;
|
||||
|
||||
// Size of the file, only valid for REG files. Limited to 32-bits.
|
||||
lfs2_size_t size;
|
||||
|
||||
// Name of the file stored as a null-terminated string. Limited to
|
||||
// LFS2_NAME_MAX+1, which can be changed by redefining LFS2_NAME_MAX to
|
||||
// reduce RAM. LFS2_NAME_MAX is stored in superblock and must be
|
||||
// respected by other littlefs drivers.
|
||||
char name[LFS2_NAME_MAX+1];
|
||||
};
|
||||
|
||||
// Custom attribute structure, used to describe custom attributes
|
||||
// committed atomically during file writes.
|
||||
struct lfs2_attr {
|
||||
// 8-bit type of attribute, provided by user and used to
|
||||
// identify the attribute
|
||||
uint8_t type;
|
||||
|
||||
// Pointer to buffer containing the attribute
|
||||
void *buffer;
|
||||
|
||||
// Size of attribute in bytes, limited to LFS2_ATTR_MAX
|
||||
lfs2_size_t size;
|
||||
};
|
||||
|
||||
// Optional configuration provided during lfs2_file_opencfg
|
||||
struct lfs2_file_config {
|
||||
// Optional statically allocated file buffer. Must be cache_size.
|
||||
// By default lfs2_malloc is used to allocate this buffer.
|
||||
void *buffer;
|
||||
|
||||
// Optional list of custom attributes related to the file. If the file
|
||||
// is opened with read access, these attributes will be read from disk
|
||||
// during the open call. If the file is opened with write access, the
|
||||
// attributes will be written to disk every file sync or close. This
|
||||
// write occurs atomically with update to the file's contents.
|
||||
//
|
||||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
||||
// to LFS2_ATTR_MAX bytes. When read, if the stored attribute is smaller
|
||||
// than the buffer, it will be padded with zeros. If the stored attribute
|
||||
// is larger, then it will be silently truncated. If the attribute is not
|
||||
// found, it will be created implicitly.
|
||||
struct lfs2_attr *attrs;
|
||||
|
||||
// Number of custom attributes in the list
|
||||
lfs2_size_t attr_count;
|
||||
};
|
||||
|
||||
|
||||
/// internal littlefs data structures ///
|
||||
typedef struct lfs2_cache {
|
||||
lfs2_block_t block;
|
||||
lfs2_off_t off;
|
||||
lfs2_size_t size;
|
||||
uint8_t *buffer;
|
||||
} lfs2_cache_t;
|
||||
|
||||
typedef struct lfs2_mdir {
|
||||
lfs2_block_t pair[2];
|
||||
uint32_t rev;
|
||||
lfs2_off_t off;
|
||||
uint32_t etag;
|
||||
uint16_t count;
|
||||
bool erased;
|
||||
bool split;
|
||||
lfs2_block_t tail[2];
|
||||
} lfs2_mdir_t;
|
||||
|
||||
// littlefs directory type
|
||||
typedef struct lfs2_dir {
|
||||
struct lfs2_dir *next;
|
||||
uint16_t id;
|
||||
uint8_t type;
|
||||
lfs2_mdir_t m;
|
||||
|
||||
lfs2_off_t pos;
|
||||
lfs2_block_t head[2];
|
||||
} lfs2_dir_t;
|
||||
|
||||
// littlefs file type
|
||||
typedef struct lfs2_file {
|
||||
struct lfs2_file *next;
|
||||
uint16_t id;
|
||||
uint8_t type;
|
||||
lfs2_mdir_t m;
|
||||
|
||||
struct lfs2_ctz {
|
||||
lfs2_block_t head;
|
||||
lfs2_size_t size;
|
||||
} ctz;
|
||||
|
||||
uint32_t flags;
|
||||
lfs2_off_t pos;
|
||||
lfs2_block_t block;
|
||||
lfs2_off_t off;
|
||||
lfs2_cache_t cache;
|
||||
|
||||
const struct lfs2_file_config *cfg;
|
||||
} lfs2_file_t;
|
||||
|
||||
typedef struct lfs2_superblock {
|
||||
uint32_t version;
|
||||
lfs2_size_t block_size;
|
||||
lfs2_size_t block_count;
|
||||
lfs2_size_t name_max;
|
||||
lfs2_size_t file_max;
|
||||
lfs2_size_t attr_max;
|
||||
} lfs2_superblock_t;
|
||||
|
||||
typedef struct lfs2_gstate {
|
||||
uint32_t tag;
|
||||
lfs2_block_t pair[2];
|
||||
} lfs2_gstate_t;
|
||||
|
||||
// The littlefs filesystem type
|
||||
typedef struct lfs2 {
|
||||
lfs2_cache_t rcache;
|
||||
lfs2_cache_t pcache;
|
||||
|
||||
lfs2_block_t root[2];
|
||||
struct lfs2_mlist {
|
||||
struct lfs2_mlist *next;
|
||||
uint16_t id;
|
||||
uint8_t type;
|
||||
lfs2_mdir_t m;
|
||||
} *mlist;
|
||||
uint32_t seed;
|
||||
|
||||
lfs2_gstate_t gstate;
|
||||
lfs2_gstate_t gdisk;
|
||||
lfs2_gstate_t gdelta;
|
||||
|
||||
struct lfs2_free {
|
||||
lfs2_block_t off;
|
||||
lfs2_block_t size;
|
||||
lfs2_block_t i;
|
||||
lfs2_block_t ack;
|
||||
uint32_t *buffer;
|
||||
} free;
|
||||
|
||||
const struct lfs2_config *cfg;
|
||||
lfs2_size_t name_max;
|
||||
lfs2_size_t file_max;
|
||||
lfs2_size_t attr_max;
|
||||
|
||||
#ifdef LFS2_MIGRATE
|
||||
struct lfs21 *lfs21;
|
||||
#endif
|
||||
} lfs2_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. The config struct must
|
||||
// be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *config);
|
||||
|
||||
// Mounts a littlefs
|
||||
//
|
||||
// Requires a littlefs object and config struct. Multiple filesystems
|
||||
// may be mounted simultaneously with multiple littlefs objects. Both
|
||||
// lfs2 and config must be allocated while mounted. The config struct must
|
||||
// be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *config);
|
||||
|
||||
// Unmounts a littlefs
|
||||
//
|
||||
// Does nothing besides releasing any allocated resources.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_unmount(lfs2_t *lfs2);
|
||||
|
||||
/// General operations ///
|
||||
|
||||
// Removes a file or directory
|
||||
//
|
||||
// If removing a directory, the directory must be empty.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_remove(lfs2_t *lfs2, 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.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_rename(lfs2_t *lfs2, 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 lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info);
|
||||
|
||||
// Get a custom attribute
|
||||
//
|
||||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
||||
// to LFS2_ATTR_MAX bytes. When read, if the stored attribute is smaller than
|
||||
// the buffer, it will be padded with zeros. If the stored attribute is larger,
|
||||
// then it will be silently truncated. If no attribute is found, the error
|
||||
// LFS2_ERR_NOATTR is returned and the buffer is filled with zeros.
|
||||
//
|
||||
// Returns the size of the attribute, or a negative error code on failure.
|
||||
// Note, the returned size is the size of the attribute on disk, irrespective
|
||||
// of the size of the buffer. This can be used to dynamically allocate a buffer
|
||||
// or check for existance.
|
||||
lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path,
|
||||
uint8_t type, void *buffer, lfs2_size_t size);
|
||||
|
||||
// Set custom attributes
|
||||
//
|
||||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
||||
// to LFS2_ATTR_MAX bytes. If an attribute is not found, it will be
|
||||
// implicitly created.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_setattr(lfs2_t *lfs2, const char *path,
|
||||
uint8_t type, const void *buffer, lfs2_size_t size);
|
||||
|
||||
// Removes a custom attribute
|
||||
//
|
||||
// If an attribute is not found, nothing happens.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type);
|
||||
|
||||
|
||||
/// File operations ///
|
||||
|
||||
// Open a file
|
||||
//
|
||||
// The mode that the file is opened in is determined by the flags, which
|
||||
// are values from the enum lfs2_open_flags that are bitwise-ored together.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
const char *path, int flags);
|
||||
|
||||
// Open a file with extra configuration
|
||||
//
|
||||
// The mode that the file is opened in is determined by the flags, which
|
||||
// are values from the enum lfs2_open_flags that are bitwise-ored together.
|
||||
//
|
||||
// The config struct provides additional config options per file as described
|
||||
// above. The config struct must be allocated while the file is open, and the
|
||||
// config struct must be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
const char *path, int flags,
|
||||
const struct lfs2_file_config *config);
|
||||
|
||||
// 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 lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
// Synchronize a file on storage
|
||||
//
|
||||
// Any pending writes are written out to storage.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_sync(lfs2_t *lfs2, lfs2_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.
|
||||
lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
void *buffer, lfs2_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.
|
||||
lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
const void *buffer, lfs2_size_t size);
|
||||
|
||||
// Change the position of the file
|
||||
//
|
||||
// The change in position is determined by the offset and whence flag.
|
||||
// Returns the new position of the file, or a negative error code on failure.
|
||||
lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
lfs2_soff_t off, int whence);
|
||||
|
||||
// Truncates the size of the file to the specified size
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size);
|
||||
|
||||
// Return the position of the file
|
||||
//
|
||||
// Equivalent to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_CUR)
|
||||
// Returns the position of the file, or a negative error code on failure.
|
||||
lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
// Change the position of the file to the beginning of the file
|
||||
//
|
||||
// Equivalent to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_SET)
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
// Return the size of the file
|
||||
//
|
||||
// Similar to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_END)
|
||||
// Returns the size of the file, or a negative error code on failure.
|
||||
lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
|
||||
/// Directory operations ///
|
||||
|
||||
// Create a directory
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_mkdir(lfs2_t *lfs2, 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 lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path);
|
||||
|
||||
// Close a directory
|
||||
//
|
||||
// Releases any allocated resources.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir);
|
||||
|
||||
// Read an entry in the directory
|
||||
//
|
||||
// Fills out the info structure, based on the specified file or directory.
|
||||
// Returns a positive value on success, 0 at the end of directory,
|
||||
// or a negative error code on failure.
|
||||
int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_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 lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_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.
|
||||
lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir);
|
||||
|
||||
// Change the position of the directory to the beginning of the directory
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir);
|
||||
|
||||
|
||||
/// Filesystem-level filesystem operations
|
||||
|
||||
// Finds the current size of the filesystem
|
||||
//
|
||||
// Note: Result is best effort. If files share COW structures, the returned
|
||||
// size may be larger than the filesystem actually is.
|
||||
//
|
||||
// Returns the number of allocated blocks, or a negative error code on failure.
|
||||
lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2);
|
||||
|
||||
// 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 lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data);
|
||||
|
||||
#ifdef LFS2_MIGRATE
|
||||
// Attempts to migrate a previous version of littlefs
|
||||
//
|
||||
// Behaves similarly to the lfs2_format function. Attempts to mount
|
||||
// the previous version of littlefs and update the filesystem so it can be
|
||||
// mounted with the current version of littlefs.
|
||||
//
|
||||
// Requires a littlefs object and config struct. This clobbers the littlefs
|
||||
// object, and does not leave the filesystem mounted. The config struct must
|
||||
// be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* lfs2 util functions
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "lfs2_util.h"
|
||||
|
||||
// Only compile if user does not provide custom config
|
||||
#ifndef LFS2_CONFIG
|
||||
#ifndef __MBED__
|
||||
|
||||
// Software CRC implementation with small lookup table
|
||||
uint32_t lfs2_crc(uint32_t 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];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* lfs2 utility functions
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS2_UTIL_H
|
||||
#define LFS2_UTIL_H
|
||||
|
||||
// Users can override lfs2_util.h with their own configuration by defining
|
||||
// LFS2_CONFIG as a header file to include (-DLFS2_CONFIG=lfs2_config.h).
|
||||
//
|
||||
// If LFS2_CONFIG is used, none of the default utils will be emitted and must be
|
||||
// provided by the config file. To start, I would suggest copying lfs2_util.h
|
||||
// and modifying as needed.
|
||||
#ifdef LFS2_CONFIG
|
||||
#define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x)
|
||||
#define LFS2_STRINGIZE2(x) #x
|
||||
#include LFS2_STRINGIZE(LFS2_CONFIG)
|
||||
#else
|
||||
|
||||
// System includes
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifndef LFS2_NO_MALLOC
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifndef LFS2_NO_ASSERT
|
||||
#include <assert.h>
|
||||
#endif
|
||||
#if !defined(LFS2_NO_DEBUG) || \
|
||||
!defined(LFS2_NO_WARN) || \
|
||||
!defined(LFS2_NO_ERROR) || \
|
||||
defined(LFS2_YES_TRACE)
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
// Macros, may be replaced by system specific wrappers. Arguments to these
|
||||
// macros must not have side-effects as the macros can be removed for a smaller
|
||||
// code footprint
|
||||
|
||||
#ifdef __MBED__
|
||||
#include "mbed_debug.h"
|
||||
#include "mbed_assert.h"
|
||||
#include "cmsis_compiler.h"
|
||||
#else
|
||||
#define MBED_LFS2_ENABLE_INFO false
|
||||
#define MBED_LFS2_ENABLE_DEBUG true
|
||||
#define MBED_LFS2_ENABLE_WARN true
|
||||
#define MBED_LFS2_ENABLE_ERROR true
|
||||
#define MBED_LFS2_ENABLE_ASSERT true
|
||||
#define MBED_LFS2_INTRINSICS true
|
||||
#endif
|
||||
|
||||
// Logging functions
|
||||
#if defined(LFS2_YES_TRACE) && MBED_LFS2_ENABLE_TRACE
|
||||
#define LFS2_TRACE_(fmt, ...) \
|
||||
printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LFS2_TRACE(...) LFS2_TRACE_(__VA_ARGS__, "")
|
||||
#elif defined(LFS2_YES_TRACE) && !defined(MBED_LFS2_ENABLE_TRACE)
|
||||
#define LFS2_TRACE_(fmt, ...) \
|
||||
debug("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LFS2_TRACE(...) LFS2_TRACE_(__VA_ARGS__, "")
|
||||
#else
|
||||
#define LFS2_TRACE(...)
|
||||
#endif
|
||||
|
||||
#if !defined(LFS2_NO_DEBUG) && MBED_LFS2_ENABLE_DEBUG
|
||||
#define LFS2_DEBUG_(fmt, ...) \
|
||||
printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LFS2_DEBUG(...) LFS2_DEBUG_(__VA_ARGS__, "")
|
||||
#elif !defined(LFS2_NO_DEBUG) && !defined(MBED_LFS2_ENABLE_DEBUG)
|
||||
#define LFS2_DEBUG_(fmt, ...) \
|
||||
debug("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LFS2_DEBUG(...) LFS2_DEBUG_(__VA_ARGS__, "")
|
||||
#else
|
||||
#define LFS2_DEBUG(...)
|
||||
#endif
|
||||
|
||||
#if !defined(LFS2_NO_WARN) && MBED_LFS2_ENABLE_WARN
|
||||
#define LFS2_WARN_(fmt, ...) \
|
||||
printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LFS2_WARN(...) LFS2_WARN_(__VA_ARGS__, "")
|
||||
#elif !defined(LFS2_NO_WARN) && !defined(MBED_LFS2_ENABLE_WARN)
|
||||
#define LFS2_WARN_(fmt, ...) \
|
||||
debug("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LFS2_WARN(...) LFS2_WARN_(__VA_ARGS__, "")
|
||||
#else
|
||||
#define LFS2_WARN(...)
|
||||
#endif
|
||||
|
||||
#if !defined(LFS2_NO_ERROR) && MBED_LFS2_ENABLE_ERROR
|
||||
#define LFS2_ERROR_(fmt, ...) \
|
||||
printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LFS2_ERROR(...) LFS2_ERROR_(__VA_ARGS__, "")
|
||||
#elif !defined(LFS2_NO_ERROR) && !defined(MBED_LFS2_ENABLE_ERROR)
|
||||
#define LFS2_ERROR_(fmt, ...) \
|
||||
debug("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
#define LFS2_ERROR(...) LFS2_ERROR_(__VA_ARGS__, "")
|
||||
#else
|
||||
#define LFS2_ERROR(...)
|
||||
#endif
|
||||
|
||||
// Runtime assertions
|
||||
#if !defined(LFS2_NO_ASSERT) && MBED_LFS2_ENABLE_ASSERT
|
||||
#define LFS2_ASSERT(test) assert(test)
|
||||
#elif !defined(LFS2_NO_ASSERT) && !defined(MBED_LFS2_ENABLE_ASSERT)
|
||||
#define LFS2_ASSERT(test) MBED_ASSERT(test)
|
||||
#else
|
||||
#define LFS2_ASSERT(test)
|
||||
#endif
|
||||
|
||||
|
||||
// Builtin functions, these may be replaced by more efficient
|
||||
// toolchain-specific implementations. LFS2_NO_INTRINSICS falls back to a more
|
||||
// expensive basic C implementation for debugging purposes
|
||||
|
||||
// Min/max functions for unsigned 32-bit numbers
|
||||
static inline uint32_t lfs2_max(uint32_t a, uint32_t b) {
|
||||
return (a > b) ? a : b;
|
||||
}
|
||||
|
||||
static inline uint32_t lfs2_min(uint32_t a, uint32_t b) {
|
||||
return (a < b) ? a : b;
|
||||
}
|
||||
|
||||
// Align to nearest multiple of a size
|
||||
static inline uint32_t lfs2_aligndown(uint32_t a, uint32_t alignment) {
|
||||
return a - (a % alignment);
|
||||
}
|
||||
|
||||
static inline uint32_t lfs2_alignup(uint32_t a, uint32_t alignment) {
|
||||
return lfs2_aligndown(a + alignment-1, alignment);
|
||||
}
|
||||
|
||||
// Find the smallest power of 2 greater than or equal to a
|
||||
static inline uint32_t lfs2_npw2(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \
|
||||
(defined(__GNUC__) || defined(__CC_ARM))
|
||||
return 32 - __builtin_clz(a-1);
|
||||
#else
|
||||
uint32_t r = 0;
|
||||
uint32_t s;
|
||||
a -= 1;
|
||||
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)) + 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of trailing binary zeros in a
|
||||
// lfs2_ctz(0) may be undefined
|
||||
static inline uint32_t lfs2_ctz(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \
|
||||
defined(__GNUC__)
|
||||
return __builtin_ctz(a);
|
||||
#else
|
||||
return lfs2_npw2((a & -a) + 1) - 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of binary ones in a
|
||||
static inline uint32_t lfs2_popc(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \
|
||||
(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
|
||||
}
|
||||
|
||||
// Find the sequence comparison of a and b, this is the distance
|
||||
// between a and b ignoring overflow
|
||||
static inline int lfs2_scmp(uint32_t a, uint32_t b) {
|
||||
return (int)(unsigned)(a - b);
|
||||
}
|
||||
|
||||
// Convert between 32-bit little-endian and native order
|
||||
static inline uint32_t lfs2_fromle32(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \
|
||||
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
|
||||
return a;
|
||||
#elif !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \
|
||||
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
|
||||
return __builtin_bswap32(a);
|
||||
#else
|
||||
return (((uint8_t*)&a)[0] << 0) |
|
||||
(((uint8_t*)&a)[1] << 8) |
|
||||
(((uint8_t*)&a)[2] << 16) |
|
||||
(((uint8_t*)&a)[3] << 24);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t lfs2_tole32(uint32_t a) {
|
||||
return lfs2_fromle32(a);
|
||||
}
|
||||
|
||||
// Reverse the bits in a
|
||||
static inline uint32_t lfs2_rbit(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && \
|
||||
defined(__MBED__)
|
||||
return __RBIT(a);
|
||||
#else
|
||||
a = ((a & 0xaaaaaaaa) >> 1) | ((a & 0x55555555) << 1);
|
||||
a = ((a & 0xcccccccc) >> 2) | ((a & 0x33333333) << 2);
|
||||
a = ((a & 0xf0f0f0f0) >> 4) | ((a & 0x0f0f0f0f) << 4);
|
||||
a = ((a & 0xff00ff00) >> 8) | ((a & 0x00ff00ff) << 8);
|
||||
a = (a >> 16) | (a << 16);
|
||||
return a;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Convert between 32-bit big-endian and native order
|
||||
static inline uint32_t lfs2_frombe32(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \
|
||||
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
|
||||
return __builtin_bswap32(a);
|
||||
#elif !defined(LFS2_NO_INTRINSICS) && MBED_LFS2_INTRINSICS && ( \
|
||||
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
|
||||
return a;
|
||||
#else
|
||||
return (((uint8_t*)&a)[0] << 24) |
|
||||
(((uint8_t*)&a)[1] << 16) |
|
||||
(((uint8_t*)&a)[2] << 8) |
|
||||
(((uint8_t*)&a)[3] << 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t lfs2_tobe32(uint32_t a) {
|
||||
return lfs2_frombe32(a);
|
||||
}
|
||||
|
||||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
||||
uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size);
|
||||
|
||||
// Allocate memory, only used if buffers are not provided to littlefs
|
||||
// Note, memory must be 64-bit aligned
|
||||
static inline void *lfs2_malloc(size_t size) {
|
||||
#ifndef LFS2_NO_MALLOC
|
||||
return malloc(size);
|
||||
#else
|
||||
(void)size;
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Deallocate memory, only used if buffers are not provided to littlefs
|
||||
static inline void lfs2_free(void *p) {
|
||||
#ifndef LFS2_NO_MALLOC
|
||||
free(p);
|
||||
#else
|
||||
(void)p;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,383 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
PATTERN = ['LFS2_ASSERT', 'assert']
|
||||
PREFIX = 'LFS2'
|
||||
MAXWIDTH = 16
|
||||
|
||||
ASSERT = "__{PREFIX}_ASSERT_{TYPE}_{COMP}"
|
||||
FAIL = """
|
||||
__attribute__((unused))
|
||||
static void __{prefix}_assert_fail_{type}(
|
||||
const char *file, int line, const char *comp,
|
||||
{ctype} lh, size_t lsize,
|
||||
{ctype} rh, size_t rsize) {{
|
||||
printf("%s:%d:assert: assert failed with ", file, line);
|
||||
__{prefix}_assert_print_{type}(lh, lsize);
|
||||
printf(", expected %s ", comp);
|
||||
__{prefix}_assert_print_{type}(rh, rsize);
|
||||
printf("\\n");
|
||||
fflush(NULL);
|
||||
raise(SIGABRT);
|
||||
}}
|
||||
"""
|
||||
|
||||
COMP = {
|
||||
'==': 'eq',
|
||||
'!=': 'ne',
|
||||
'<=': 'le',
|
||||
'>=': 'ge',
|
||||
'<': 'lt',
|
||||
'>': 'gt',
|
||||
}
|
||||
|
||||
TYPE = {
|
||||
'int': {
|
||||
'ctype': 'intmax_t',
|
||||
'fail': FAIL,
|
||||
'print': """
|
||||
__attribute__((unused))
|
||||
static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{
|
||||
(void)size;
|
||||
printf("%"PRIiMAX, v);
|
||||
}}
|
||||
""",
|
||||
'assert': """
|
||||
#define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh)
|
||||
do {{
|
||||
__typeof__(lh) _lh = lh;
|
||||
__typeof__(lh) _rh = (__typeof__(lh))rh;
|
||||
if (!(_lh {op} _rh)) {{
|
||||
__{prefix}_assert_fail_{type}(file, line, "{comp}",
|
||||
(intmax_t)_lh, 0, (intmax_t)_rh, 0);
|
||||
}}
|
||||
}} while (0)
|
||||
"""
|
||||
},
|
||||
'bool': {
|
||||
'ctype': 'bool',
|
||||
'fail': FAIL,
|
||||
'print': """
|
||||
__attribute__((unused))
|
||||
static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{
|
||||
(void)size;
|
||||
printf("%s", v ? "true" : "false");
|
||||
}}
|
||||
""",
|
||||
'assert': """
|
||||
#define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh)
|
||||
do {{
|
||||
bool _lh = !!(lh);
|
||||
bool _rh = !!(rh);
|
||||
if (!(_lh {op} _rh)) {{
|
||||
__{prefix}_assert_fail_{type}(file, line, "{comp}",
|
||||
_lh, 0, _rh, 0);
|
||||
}}
|
||||
}} while (0)
|
||||
"""
|
||||
},
|
||||
'mem': {
|
||||
'ctype': 'const void *',
|
||||
'fail': FAIL,
|
||||
'print': """
|
||||
__attribute__((unused))
|
||||
static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{
|
||||
const uint8_t *s = v;
|
||||
printf("\\\"");
|
||||
for (size_t i = 0; i < size && i < {maxwidth}; i++) {{
|
||||
if (s[i] >= ' ' && s[i] <= '~') {{
|
||||
printf("%c", s[i]);
|
||||
}} else {{
|
||||
printf("\\\\x%02x", s[i]);
|
||||
}}
|
||||
}}
|
||||
if (size > {maxwidth}) {{
|
||||
printf("...");
|
||||
}}
|
||||
printf("\\\"");
|
||||
}}
|
||||
""",
|
||||
'assert': """
|
||||
#define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh, size)
|
||||
do {{
|
||||
const void *_lh = lh;
|
||||
const void *_rh = rh;
|
||||
if (!(memcmp(_lh, _rh, size) {op} 0)) {{
|
||||
__{prefix}_assert_fail_{type}(file, line, "{comp}",
|
||||
_lh, size, _rh, size);
|
||||
}}
|
||||
}} while (0)
|
||||
"""
|
||||
},
|
||||
'str': {
|
||||
'ctype': 'const char *',
|
||||
'fail': FAIL,
|
||||
'print': """
|
||||
__attribute__((unused))
|
||||
static void __{prefix}_assert_print_{type}({ctype} v, size_t size) {{
|
||||
__{prefix}_assert_print_mem(v, size);
|
||||
}}
|
||||
""",
|
||||
'assert': """
|
||||
#define __{PREFIX}_ASSERT_{TYPE}_{COMP}(file, line, lh, rh)
|
||||
do {{
|
||||
const char *_lh = lh;
|
||||
const char *_rh = rh;
|
||||
if (!(strcmp(_lh, _rh) {op} 0)) {{
|
||||
__{prefix}_assert_fail_{type}(file, line, "{comp}",
|
||||
_lh, strlen(_lh), _rh, strlen(_rh));
|
||||
}}
|
||||
}} while (0)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
def mkdecls(outf, maxwidth=16):
|
||||
outf.write("#include <stdio.h>\n")
|
||||
outf.write("#include <stdbool.h>\n")
|
||||
outf.write("#include <stdint.h>\n")
|
||||
outf.write("#include <inttypes.h>\n")
|
||||
outf.write("#include <signal.h>\n")
|
||||
|
||||
for type, desc in sorted(TYPE.items()):
|
||||
format = {
|
||||
'type': type.lower(), 'TYPE': type.upper(),
|
||||
'ctype': desc['ctype'],
|
||||
'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(),
|
||||
'maxwidth': maxwidth,
|
||||
}
|
||||
outf.write(re.sub('\s+', ' ',
|
||||
desc['print'].strip().format(**format))+'\n')
|
||||
outf.write(re.sub('\s+', ' ',
|
||||
desc['fail'].strip().format(**format))+'\n')
|
||||
|
||||
for op, comp in sorted(COMP.items()):
|
||||
format.update({
|
||||
'comp': comp.lower(), 'COMP': comp.upper(),
|
||||
'op': op,
|
||||
})
|
||||
outf.write(re.sub('\s+', ' ',
|
||||
desc['assert'].strip().format(**format))+'\n')
|
||||
|
||||
def mkassert(type, comp, lh, rh, size=None):
|
||||
format = {
|
||||
'type': type.lower(), 'TYPE': type.upper(),
|
||||
'comp': comp.lower(), 'COMP': comp.upper(),
|
||||
'prefix': PREFIX.lower(), 'PREFIX': PREFIX.upper(),
|
||||
'lh': lh.strip(' '),
|
||||
'rh': rh.strip(' '),
|
||||
'size': size,
|
||||
}
|
||||
if size:
|
||||
return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh}, {size})')
|
||||
.format(**format))
|
||||
else:
|
||||
return ((ASSERT + '(__FILE__, __LINE__, {lh}, {rh})')
|
||||
.format(**format))
|
||||
|
||||
|
||||
# simple recursive descent parser
|
||||
LEX = {
|
||||
'ws': [r'(?:\s|\n|#.*?\n|//.*?\n|/\*.*?\*/)+'],
|
||||
'assert': PATTERN,
|
||||
'string': [r'"(?:\\.|[^"])*"', r"'(?:\\.|[^'])\'"],
|
||||
'arrow': ['=>'],
|
||||
'paren': ['\(', '\)'],
|
||||
'op': ['strcmp', 'memcmp', '->'],
|
||||
'comp': ['==', '!=', '<=', '>=', '<', '>'],
|
||||
'logic': ['\&\&', '\|\|'],
|
||||
'sep': [':', ';', '\{', '\}', ','],
|
||||
}
|
||||
|
||||
class ParseFailure(Exception):
|
||||
def __init__(self, expected, found):
|
||||
self.expected = expected
|
||||
self.found = found
|
||||
|
||||
def __str__(self):
|
||||
return "expected %r, found %s..." % (
|
||||
self.expected, repr(self.found)[:70])
|
||||
|
||||
class Parse:
|
||||
def __init__(self, inf, lexemes):
|
||||
p = '|'.join('(?P<%s>%s)' % (n, '|'.join(l))
|
||||
for n, l in lexemes.items())
|
||||
p = re.compile(p, re.DOTALL)
|
||||
data = inf.read()
|
||||
tokens = []
|
||||
while True:
|
||||
m = p.search(data)
|
||||
if m:
|
||||
if m.start() > 0:
|
||||
tokens.append((None, data[:m.start()]))
|
||||
tokens.append((m.lastgroup, m.group()))
|
||||
data = data[m.end():]
|
||||
else:
|
||||
tokens.append((None, data))
|
||||
break
|
||||
self.tokens = tokens
|
||||
self.off = 0
|
||||
|
||||
def lookahead(self, *pattern):
|
||||
if self.off < len(self.tokens):
|
||||
token = self.tokens[self.off]
|
||||
if token[0] in pattern or token[1] in pattern:
|
||||
self.m = token[1]
|
||||
return self.m
|
||||
self.m = None
|
||||
return self.m
|
||||
|
||||
def accept(self, *patterns):
|
||||
m = self.lookahead(*patterns)
|
||||
if m is not None:
|
||||
self.off += 1
|
||||
return m
|
||||
|
||||
def expect(self, *patterns):
|
||||
m = self.accept(*patterns)
|
||||
if not m:
|
||||
raise ParseFailure(patterns, self.tokens[self.off:])
|
||||
return m
|
||||
|
||||
def push(self):
|
||||
return self.off
|
||||
|
||||
def pop(self, state):
|
||||
self.off = state
|
||||
|
||||
def passert(p):
|
||||
def pastr(p):
|
||||
p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
|
||||
p.expect('strcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
|
||||
lh = pexpr(p) ; p.accept('ws')
|
||||
p.expect(',') ; p.accept('ws')
|
||||
rh = pexpr(p) ; p.accept('ws')
|
||||
p.expect(')') ; p.accept('ws')
|
||||
comp = p.expect('comp') ; p.accept('ws')
|
||||
p.expect('0') ; p.accept('ws')
|
||||
p.expect(')')
|
||||
return mkassert('str', COMP[comp], lh, rh)
|
||||
|
||||
def pamem(p):
|
||||
p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
|
||||
p.expect('memcmp') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
|
||||
lh = pexpr(p) ; p.accept('ws')
|
||||
p.expect(',') ; p.accept('ws')
|
||||
rh = pexpr(p) ; p.accept('ws')
|
||||
p.expect(',') ; p.accept('ws')
|
||||
size = pexpr(p) ; p.accept('ws')
|
||||
p.expect(')') ; p.accept('ws')
|
||||
comp = p.expect('comp') ; p.accept('ws')
|
||||
p.expect('0') ; p.accept('ws')
|
||||
p.expect(')')
|
||||
return mkassert('mem', COMP[comp], lh, rh, size)
|
||||
|
||||
def paint(p):
|
||||
p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
|
||||
lh = pexpr(p) ; p.accept('ws')
|
||||
comp = p.expect('comp') ; p.accept('ws')
|
||||
rh = pexpr(p) ; p.accept('ws')
|
||||
p.expect(')')
|
||||
return mkassert('int', COMP[comp], lh, rh)
|
||||
|
||||
def pabool(p):
|
||||
p.expect('assert') ; p.accept('ws') ; p.expect('(') ; p.accept('ws')
|
||||
lh = pexprs(p) ; p.accept('ws')
|
||||
p.expect(')')
|
||||
return mkassert('bool', 'eq', lh, 'true')
|
||||
|
||||
def pa(p):
|
||||
return p.expect('assert')
|
||||
|
||||
state = p.push()
|
||||
lastf = None
|
||||
for pa in [pastr, pamem, paint, pabool, pa]:
|
||||
try:
|
||||
return pa(p)
|
||||
except ParseFailure as f:
|
||||
p.pop(state)
|
||||
lastf = f
|
||||
else:
|
||||
raise lastf
|
||||
|
||||
def pexpr(p):
|
||||
res = []
|
||||
while True:
|
||||
if p.accept('('):
|
||||
res.append(p.m)
|
||||
while True:
|
||||
res.append(pexprs(p))
|
||||
if p.accept('sep'):
|
||||
res.append(p.m)
|
||||
else:
|
||||
break
|
||||
res.append(p.expect(')'))
|
||||
elif p.lookahead('assert'):
|
||||
res.append(passert(p))
|
||||
elif p.accept('assert', 'ws', 'string', 'op', None):
|
||||
res.append(p.m)
|
||||
else:
|
||||
return ''.join(res)
|
||||
|
||||
def pexprs(p):
|
||||
res = []
|
||||
while True:
|
||||
res.append(pexpr(p))
|
||||
if p.accept('comp', 'logic', ','):
|
||||
res.append(p.m)
|
||||
else:
|
||||
return ''.join(res)
|
||||
|
||||
def pstmt(p):
|
||||
ws = p.accept('ws') or ''
|
||||
lh = pexprs(p)
|
||||
if p.accept('=>'):
|
||||
rh = pexprs(p)
|
||||
return ws + mkassert('int', 'eq', lh, rh)
|
||||
else:
|
||||
return ws + lh
|
||||
|
||||
|
||||
def main(args):
|
||||
inf = open(args.input, 'r') if args.input else sys.stdin
|
||||
outf = open(args.output, 'w') if args.output else sys.stdout
|
||||
|
||||
lexemes = LEX.copy()
|
||||
if args.pattern:
|
||||
lexemes['assert'] = args.pattern
|
||||
p = Parse(inf, lexemes)
|
||||
|
||||
# write extra verbose asserts
|
||||
mkdecls(outf, maxwidth=args.maxwidth)
|
||||
if args.input:
|
||||
outf.write("#line %d \"%s\"\n" % (1, args.input))
|
||||
|
||||
# parse and write out stmt at a time
|
||||
try:
|
||||
while True:
|
||||
outf.write(pstmt(p))
|
||||
if p.accept('sep'):
|
||||
outf.write(p.m)
|
||||
else:
|
||||
break
|
||||
except ParseFailure as f:
|
||||
pass
|
||||
|
||||
for i in range(p.off, len(p.tokens)):
|
||||
outf.write(p.tokens[i][1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Cpp step that increases assert verbosity")
|
||||
parser.add_argument('input', nargs='?',
|
||||
help="Input C file after cpp.")
|
||||
parser.add_argument('-o', '--output', required=True,
|
||||
help="Output C file.")
|
||||
parser.add_argument('-p', '--pattern', action='append',
|
||||
help="Patterns to search for starting an assert statement.")
|
||||
parser.add_argument('--maxwidth', default=MAXWIDTH, type=int,
|
||||
help="Maximum number of characters to display for strcmp and memcmp.")
|
||||
main(parser.parse_args())
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
# This script replaces prefixes of files, and symbols in that file.
|
||||
# Useful for creating different versions of the codebase that don't
|
||||
# conflict at compile time.
|
||||
#
|
||||
# example:
|
||||
# $ ./scripts/prefix.py lfs22
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import glob
|
||||
import itertools
|
||||
import tempfile
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
DEFAULT_PREFIX = "lfs2"
|
||||
|
||||
def subn(from_prefix, to_prefix, name):
|
||||
name, count1 = re.subn('\\b'+from_prefix, to_prefix, name)
|
||||
name, count2 = re.subn('\\b'+from_prefix.upper(), to_prefix.upper(), name)
|
||||
name, count3 = re.subn('\\B-D'+from_prefix.upper(),
|
||||
'-D'+to_prefix.upper(), name)
|
||||
return name, count1+count2+count3
|
||||
|
||||
def main(from_prefix, to_prefix=None, files=None):
|
||||
if not to_prefix:
|
||||
from_prefix, to_prefix = DEFAULT_PREFIX, from_prefix
|
||||
|
||||
if not files:
|
||||
files = subprocess.check_output([
|
||||
'git', 'ls-tree', '-r', '--name-only', 'HEAD']).split()
|
||||
|
||||
for oldname in files:
|
||||
# Rename any matching file names
|
||||
newname, namecount = subn(from_prefix, to_prefix, oldname)
|
||||
if namecount:
|
||||
subprocess.check_call(['git', 'mv', oldname, newname])
|
||||
|
||||
# Rename any prefixes in file
|
||||
count = 0
|
||||
with open(newname+'~', 'w') as tempf:
|
||||
with open(newname) as newf:
|
||||
for line in newf:
|
||||
line, n = subn(from_prefix, to_prefix, line)
|
||||
count += n
|
||||
tempf.write(line)
|
||||
shutil.copystat(newname, newname+'~')
|
||||
os.rename(newname+'~', newname)
|
||||
subprocess.check_call(['git', 'add', newname])
|
||||
|
||||
# Summary
|
||||
print '%s: %d replacements' % (
|
||||
'%s -> %s' % (oldname, newname) if namecount else oldname,
|
||||
count)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.exit(main(*sys.argv[1:]))
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess as sp
|
||||
|
||||
def main(args):
|
||||
with open(args.disk, 'rb') as f:
|
||||
f.seek(args.block * args.block_size)
|
||||
block = (f.read(args.block_size)
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
|
||||
# what did you expect?
|
||||
print("%-8s %-s" % ('off', 'data'))
|
||||
return sp.run(['xxd', '-g1', '-'], input=block).returncode
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Hex dump a specific block in a disk.")
|
||||
parser.add_argument('disk',
|
||||
help="File representing the block device.")
|
||||
parser.add_argument('block_size', type=lambda x: int(x, 0),
|
||||
help="Size of a block in bytes.")
|
||||
parser.add_argument('block', type=lambda x: int(x, 0),
|
||||
help="Address of block to dump.")
|
||||
sys.exit(main(parser.parse_args()))
|
|
@ -0,0 +1,367 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import struct
|
||||
import binascii
|
||||
import sys
|
||||
import itertools as it
|
||||
|
||||
TAG_TYPES = {
|
||||
'splice': (0x700, 0x400),
|
||||
'create': (0x7ff, 0x401),
|
||||
'delete': (0x7ff, 0x4ff),
|
||||
'name': (0x700, 0x000),
|
||||
'reg': (0x7ff, 0x001),
|
||||
'dir': (0x7ff, 0x002),
|
||||
'superblock': (0x7ff, 0x0ff),
|
||||
'struct': (0x700, 0x200),
|
||||
'dirstruct': (0x7ff, 0x200),
|
||||
'ctzstruct': (0x7ff, 0x202),
|
||||
'inlinestruct': (0x7ff, 0x201),
|
||||
'userattr': (0x700, 0x300),
|
||||
'tail': (0x700, 0x600),
|
||||
'softtail': (0x7ff, 0x600),
|
||||
'hardtail': (0x7ff, 0x601),
|
||||
'gstate': (0x700, 0x700),
|
||||
'movestate': (0x7ff, 0x7ff),
|
||||
'crc': (0x700, 0x500),
|
||||
}
|
||||
|
||||
class Tag:
|
||||
def __init__(self, *args):
|
||||
if len(args) == 1:
|
||||
self.tag = args[0]
|
||||
elif len(args) == 3:
|
||||
if isinstance(args[0], str):
|
||||
type = TAG_TYPES[args[0]][1]
|
||||
else:
|
||||
type = args[0]
|
||||
|
||||
if isinstance(args[1], str):
|
||||
id = int(args[1], 0) if args[1] not in 'x.' else 0x3ff
|
||||
else:
|
||||
id = args[1]
|
||||
|
||||
if isinstance(args[2], str):
|
||||
size = int(args[2], str) if args[2] not in 'x.' else 0x3ff
|
||||
else:
|
||||
size = args[2]
|
||||
|
||||
self.tag = (type << 20) | (id << 10) | size
|
||||
else:
|
||||
assert False
|
||||
|
||||
@property
|
||||
def isvalid(self):
|
||||
return not bool(self.tag & 0x80000000)
|
||||
|
||||
@property
|
||||
def isattr(self):
|
||||
return not bool(self.tag & 0x40000000)
|
||||
|
||||
@property
|
||||
def iscompactable(self):
|
||||
return bool(self.tag & 0x20000000)
|
||||
|
||||
@property
|
||||
def isunique(self):
|
||||
return not bool(self.tag & 0x10000000)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return (self.tag & 0x7ff00000) >> 20
|
||||
|
||||
@property
|
||||
def type1(self):
|
||||
return (self.tag & 0x70000000) >> 20
|
||||
|
||||
@property
|
||||
def type3(self):
|
||||
return (self.tag & 0x7ff00000) >> 20
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return (self.tag & 0x000ffc00) >> 10
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return (self.tag & 0x000003ff) >> 0
|
||||
|
||||
@property
|
||||
def dsize(self):
|
||||
return 4 + (self.size if self.size != 0x3ff else 0)
|
||||
|
||||
@property
|
||||
def chunk(self):
|
||||
return self.type & 0xff
|
||||
|
||||
@property
|
||||
def schunk(self):
|
||||
return struct.unpack('b', struct.pack('B', self.chunk))[0]
|
||||
|
||||
def is_(self, type):
|
||||
return (self.type & TAG_TYPES[type][0]) == TAG_TYPES[type][1]
|
||||
|
||||
def mkmask(self):
|
||||
return Tag(
|
||||
0x700 if self.isunique else 0x7ff,
|
||||
0x3ff if self.isattr else 0,
|
||||
0)
|
||||
|
||||
def chid(self, nid):
|
||||
ntag = Tag(self.type, nid, self.size)
|
||||
if hasattr(self, 'off'): ntag.off = self.off
|
||||
if hasattr(self, 'data'): ntag.data = self.data
|
||||
if hasattr(self, 'crc'): ntag.crc = self.crc
|
||||
return ntag
|
||||
|
||||
def typerepr(self):
|
||||
if self.is_('crc') and getattr(self, 'crc', 0xffffffff) != 0xffffffff:
|
||||
return 'crc (bad)'
|
||||
|
||||
reverse_types = {v: k for k, v in TAG_TYPES.items()}
|
||||
for prefix in range(12):
|
||||
mask = 0x7ff & ~((1 << prefix)-1)
|
||||
if (mask, self.type & mask) in reverse_types:
|
||||
type = reverse_types[mask, self.type & mask]
|
||||
if prefix > 0:
|
||||
return '%s %#0*x' % (
|
||||
type, prefix//4, self.type & ((1 << prefix)-1))
|
||||
else:
|
||||
return type
|
||||
else:
|
||||
return '%02x' % self.type
|
||||
|
||||
def idrepr(self):
|
||||
return repr(self.id) if self.id != 0x3ff else '.'
|
||||
|
||||
def sizerepr(self):
|
||||
return repr(self.size) if self.size != 0x3ff else 'x'
|
||||
|
||||
def __repr__(self):
|
||||
return 'Tag(%r, %d, %d)' % (self.typerepr(), self.id, self.size)
|
||||
|
||||
def __lt__(self, other):
|
||||
return (self.id, self.type) < (other.id, other.type)
|
||||
|
||||
def __bool__(self):
|
||||
return self.isvalid
|
||||
|
||||
def __int__(self):
|
||||
return self.tag
|
||||
|
||||
def __index__(self):
|
||||
return self.tag
|
||||
|
||||
class MetadataPair:
|
||||
def __init__(self, blocks):
|
||||
if len(blocks) > 1:
|
||||
self.pair = [MetadataPair([block]) for block in blocks]
|
||||
self.pair = sorted(self.pair, reverse=True)
|
||||
|
||||
self.data = self.pair[0].data
|
||||
self.rev = self.pair[0].rev
|
||||
self.tags = self.pair[0].tags
|
||||
self.ids = self.pair[0].ids
|
||||
self.log = self.pair[0].log
|
||||
self.all_ = self.pair[0].all_
|
||||
return
|
||||
|
||||
self.pair = [self]
|
||||
self.data = blocks[0]
|
||||
block = self.data
|
||||
|
||||
self.rev, = struct.unpack('<I', block[0:4])
|
||||
crc = binascii.crc32(block[0:4])
|
||||
|
||||
# parse tags
|
||||
corrupt = False
|
||||
tag = Tag(0xffffffff)
|
||||
off = 4
|
||||
self.log = []
|
||||
self.all_ = []
|
||||
while len(block) - off >= 4:
|
||||
ntag, = struct.unpack('>I', block[off:off+4])
|
||||
|
||||
tag = Tag(int(tag) ^ ntag)
|
||||
tag.off = off + 4
|
||||
tag.data = block[off+4:off+tag.dsize]
|
||||
if tag.is_('crc'):
|
||||
crc = binascii.crc32(block[off:off+4+4], crc)
|
||||
else:
|
||||
crc = binascii.crc32(block[off:off+tag.dsize], crc)
|
||||
tag.crc = crc
|
||||
off += tag.dsize
|
||||
|
||||
self.all_.append(tag)
|
||||
|
||||
if tag.is_('crc'):
|
||||
# is valid commit?
|
||||
if crc != 0xffffffff:
|
||||
corrupt = True
|
||||
if not corrupt:
|
||||
self.log = self.all_.copy()
|
||||
|
||||
# reset tag parsing
|
||||
crc = 0
|
||||
tag = Tag(int(tag) ^ ((tag.type & 1) << 31))
|
||||
|
||||
# find active ids
|
||||
self.ids = list(it.takewhile(
|
||||
lambda id: Tag('name', id, 0) in self,
|
||||
it.count()))
|
||||
|
||||
# find most recent tags
|
||||
self.tags = []
|
||||
for tag in self.log:
|
||||
if tag.is_('crc') or tag.is_('splice'):
|
||||
continue
|
||||
elif tag.id == 0x3ff:
|
||||
if tag in self and self[tag] is tag:
|
||||
self.tags.append(tag)
|
||||
else:
|
||||
# id could have change, I know this is messy and slow
|
||||
# but it works
|
||||
for id in self.ids:
|
||||
ntag = tag.chid(id)
|
||||
if ntag in self and self[ntag] is tag:
|
||||
self.tags.append(ntag)
|
||||
|
||||
self.tags = sorted(self.tags)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.log)
|
||||
|
||||
def __lt__(self, other):
|
||||
# corrupt blocks don't count
|
||||
if not self or not other:
|
||||
return bool(other)
|
||||
|
||||
# use sequence arithmetic to avoid overflow
|
||||
return not ((other.rev - self.rev) & 0x80000000)
|
||||
|
||||
def __contains__(self, args):
|
||||
try:
|
||||
self[args]
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def __getitem__(self, args):
|
||||
if isinstance(args, tuple):
|
||||
gmask, gtag = args
|
||||
else:
|
||||
gmask, gtag = args.mkmask(), args
|
||||
|
||||
gdiff = 0
|
||||
for tag in reversed(self.log):
|
||||
if (gmask.id != 0 and tag.is_('splice') and
|
||||
tag.id <= gtag.id - gdiff):
|
||||
if tag.is_('create') and tag.id == gtag.id - gdiff:
|
||||
# creation point
|
||||
break
|
||||
|
||||
gdiff += tag.schunk
|
||||
|
||||
if ((int(gmask) & int(tag)) ==
|
||||
(int(gmask) & int(gtag.chid(gtag.id - gdiff)))):
|
||||
if tag.size == 0x3ff:
|
||||
# deleted
|
||||
break
|
||||
|
||||
return tag
|
||||
|
||||
raise KeyError(gmask, gtag)
|
||||
|
||||
def _dump_tags(self, tags, f=sys.stdout, truncate=True):
|
||||
f.write("%-8s %-8s %-13s %4s %4s" % (
|
||||
'off', 'tag', 'type', 'id', 'len'))
|
||||
if truncate:
|
||||
f.write(' data (truncated)')
|
||||
f.write('\n')
|
||||
|
||||
for tag in tags:
|
||||
f.write("%08x: %08x %-13s %4s %4s" % (
|
||||
tag.off, tag,
|
||||
tag.typerepr(), tag.idrepr(), tag.sizerepr()))
|
||||
if truncate:
|
||||
f.write(" %-23s %-8s\n" % (
|
||||
' '.join('%02x' % c for c in tag.data[:8]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[:8]))))
|
||||
else:
|
||||
f.write("\n")
|
||||
for i in range(0, len(tag.data), 16):
|
||||
f.write(" %08x: %-47s %-16s\n" % (
|
||||
tag.off+i,
|
||||
' '.join('%02x' % c for c in tag.data[i:i+16]),
|
||||
''.join(c if c >= ' ' and c <= '~' else '.'
|
||||
for c in map(chr, tag.data[i:i+16]))))
|
||||
|
||||
def dump_tags(self, f=sys.stdout, truncate=True):
|
||||
self._dump_tags(self.tags, f=f, truncate=truncate)
|
||||
|
||||
def dump_log(self, f=sys.stdout, truncate=True):
|
||||
self._dump_tags(self.log, f=f, truncate=truncate)
|
||||
|
||||
def dump_all(self, f=sys.stdout, truncate=True):
|
||||
self._dump_tags(self.all_, f=f, truncate=truncate)
|
||||
|
||||
def main(args):
|
||||
blocks = []
|
||||
with open(args.disk, 'rb') as f:
|
||||
for block in [args.block1, args.block2]:
|
||||
if block is None:
|
||||
continue
|
||||
f.seek(block * args.block_size)
|
||||
blocks.append(f.read(args.block_size)
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
|
||||
# find most recent pair
|
||||
mdir = MetadataPair(blocks)
|
||||
|
||||
try:
|
||||
mdir.tail = mdir[Tag('tail', 0, 0)]
|
||||
if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff':
|
||||
mdir.tail = None
|
||||
except KeyError:
|
||||
mdir.tail = None
|
||||
|
||||
print("mdir {%s} rev %d%s%s%s" % (
|
||||
', '.join('%#x' % b
|
||||
for b in [args.block1, args.block2]
|
||||
if b is not None),
|
||||
mdir.rev,
|
||||
' (was %s)' % ', '.join('%d' % m.rev for m in mdir.pair[1:])
|
||||
if len(mdir.pair) > 1 else '',
|
||||
' (corrupted!)' if not mdir else '',
|
||||
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
||||
if mdir.tail else ''))
|
||||
if args.all:
|
||||
mdir.dump_all(truncate=not args.no_truncate)
|
||||
elif args.log:
|
||||
mdir.dump_log(truncate=not args.no_truncate)
|
||||
else:
|
||||
mdir.dump_tags(truncate=not args.no_truncate)
|
||||
|
||||
return 0 if mdir else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Dump useful info about metadata pairs in littlefs.")
|
||||
parser.add_argument('disk',
|
||||
help="File representing the block device.")
|
||||
parser.add_argument('block_size', type=lambda x: int(x, 0),
|
||||
help="Size of a block in bytes.")
|
||||
parser.add_argument('block1', type=lambda x: int(x, 0),
|
||||
help="First block address for finding the metadata pair.")
|
||||
parser.add_argument('block2', nargs='?', type=lambda x: int(x, 0),
|
||||
help="Second block address for finding the metadata pair.")
|
||||
parser.add_argument('-l', '--log', action='store_true',
|
||||
help="Show tags in log.")
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
parser.add_argument('-T', '--no-truncate', action='store_true',
|
||||
help="Don't truncate large amounts of data.")
|
||||
sys.exit(main(parser.parse_args()))
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import struct
|
||||
import sys
|
||||
import json
|
||||
import io
|
||||
import itertools as it
|
||||
from readmdir import Tag, MetadataPair
|
||||
|
||||
def main(args):
|
||||
superblock = None
|
||||
gstate = b'\0\0\0\0\0\0\0\0\0\0\0\0'
|
||||
dirs = []
|
||||
mdirs = []
|
||||
corrupted = []
|
||||
cycle = False
|
||||
with open(args.disk, 'rb') as f:
|
||||
tail = (args.block1, args.block2)
|
||||
hard = False
|
||||
while True:
|
||||
for m in it.chain((m for d in dirs for m in d), mdirs):
|
||||
if set(m.blocks) == set(tail):
|
||||
# cycle detected
|
||||
cycle = m.blocks
|
||||
if cycle:
|
||||
break
|
||||
|
||||
# load mdir
|
||||
data = []
|
||||
blocks = {}
|
||||
for block in tail:
|
||||
f.seek(block * args.block_size)
|
||||
data.append(f.read(args.block_size)
|
||||
.ljust(args.block_size, b'\xff'))
|
||||
blocks[id(data[-1])] = block
|
||||
|
||||
mdir = MetadataPair(data)
|
||||
mdir.blocks = tuple(blocks[id(p.data)] for p in mdir.pair)
|
||||
|
||||
# fetch some key metadata as a we scan
|
||||
try:
|
||||
mdir.tail = mdir[Tag('tail', 0, 0)]
|
||||
if mdir.tail.size != 8 or mdir.tail.data == 8*b'\xff':
|
||||
mdir.tail = None
|
||||
except KeyError:
|
||||
mdir.tail = None
|
||||
|
||||
# have superblock?
|
||||
try:
|
||||
nsuperblock = mdir[
|
||||
Tag(0x7ff, 0x3ff, 0), Tag('superblock', 0, 0)]
|
||||
superblock = nsuperblock, mdir[Tag('inlinestruct', 0, 0)]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# have gstate?
|
||||
try:
|
||||
ngstate = mdir[Tag('movestate', 0, 0)]
|
||||
gstate = bytes((a or 0) ^ (b or 0)
|
||||
for a,b in it.zip_longest(gstate, ngstate.data))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# corrupted?
|
||||
if not mdir:
|
||||
corrupted.append(mdir)
|
||||
|
||||
# add to directories
|
||||
mdirs.append(mdir)
|
||||
if mdir.tail is None or not mdir.tail.is_('hardtail'):
|
||||
dirs.append(mdirs)
|
||||
mdirs = []
|
||||
|
||||
if mdir.tail is None:
|
||||
break
|
||||
|
||||
tail = struct.unpack('<II', mdir.tail.data)
|
||||
hard = mdir.tail.is_('hardtail')
|
||||
|
||||
# find paths
|
||||
dirtable = {}
|
||||
for dir in dirs:
|
||||
dirtable[frozenset(dir[0].blocks)] = dir
|
||||
|
||||
pending = [("/", dirs[0])]
|
||||
while pending:
|
||||
path, dir = pending.pop(0)
|
||||
for mdir in dir:
|
||||
for tag in mdir.tags:
|
||||
if tag.is_('dir'):
|
||||
try:
|
||||
npath = tag.data.decode('utf8')
|
||||
dirstruct = mdir[Tag('dirstruct', tag.id, 0)]
|
||||
nblocks = struct.unpack('<II', dirstruct.data)
|
||||
nmdir = dirtable[frozenset(nblocks)]
|
||||
pending.append(((path + '/' + npath), nmdir))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
dir[0].path = path.replace('//', '/')
|
||||
|
||||
# print littlefs + version info
|
||||
version = ('?', '?')
|
||||
if superblock:
|
||||
version = tuple(reversed(
|
||||
struct.unpack('<HH', superblock[1].data[0:4].ljust(4, b'\xff'))))
|
||||
print("%-47s%s" % ("littlefs v%s.%s" % version,
|
||||
"data (truncated, if it fits)"
|
||||
if not any([args.no_truncate, args.tags, args.log, args.all]) else ""))
|
||||
|
||||
# print gstate
|
||||
print("gstate 0x%s" % ''.join('%02x' % c for c in gstate))
|
||||
tag = Tag(struct.unpack('<I', gstate[0:4].ljust(4, b'\xff'))[0])
|
||||
blocks = struct.unpack('<II', gstate[4:4+8].ljust(8, b'\xff'))
|
||||
if tag.size or not tag.isvalid:
|
||||
print(" orphans >=%d" % max(tag.size, 1))
|
||||
if tag.type:
|
||||
print(" move dir {%#x, %#x} id %d" % (
|
||||
blocks[0], blocks[1], tag.id))
|
||||
|
||||
# print mdir info
|
||||
for i, dir in enumerate(dirs):
|
||||
print("dir %s" % (json.dumps(dir[0].path)
|
||||
if hasattr(dir[0], 'path') else '(orphan)'))
|
||||
|
||||
for j, mdir in enumerate(dir):
|
||||
print("mdir {%#x, %#x} rev %d (was %d)%s%s" % (
|
||||
mdir.blocks[0], mdir.blocks[1], mdir.rev, mdir.pair[1].rev,
|
||||
' (corrupted!)' if not mdir else '',
|
||||
' -> {%#x, %#x}' % struct.unpack('<II', mdir.tail.data)
|
||||
if mdir.tail else ''))
|
||||
|
||||
f = io.StringIO()
|
||||
if args.log:
|
||||
mdir.dump_log(f, truncate=not args.no_truncate)
|
||||
elif args.all:
|
||||
mdir.dump_all(f, truncate=not args.no_truncate)
|
||||
else:
|
||||
mdir.dump_tags(f, truncate=not args.no_truncate)
|
||||
|
||||
lines = list(filter(None, f.getvalue().split('\n')))
|
||||
for k, line in enumerate(lines):
|
||||
print("%s %s" % (
|
||||
' ' if j == len(dir)-1 else
|
||||
'v' if k == len(lines)-1 else
|
||||
'|',
|
||||
line))
|
||||
|
||||
errcode = 0
|
||||
for mdir in corrupted:
|
||||
errcode = errcode or 1
|
||||
print("*** corrupted mdir {%#x, %#x}! ***" % (
|
||||
mdir.blocks[0], mdir.blocks[1]))
|
||||
|
||||
if cycle:
|
||||
errcode = errcode or 2
|
||||
print("*** cycle detected {%#x, %#x}! ***" % (
|
||||
cycle[0], cycle[1]))
|
||||
|
||||
return errcode
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
import sys
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Dump semantic info about the metadata tree in littlefs")
|
||||
parser.add_argument('disk',
|
||||
help="File representing the block device.")
|
||||
parser.add_argument('block_size', type=lambda x: int(x, 0),
|
||||
help="Size of a block in bytes.")
|
||||
parser.add_argument('block1', nargs='?', default=0,
|
||||
type=lambda x: int(x, 0),
|
||||
help="Optional first block address for finding the superblock.")
|
||||
parser.add_argument('block2', nargs='?', default=1,
|
||||
type=lambda x: int(x, 0),
|
||||
help="Optional second block address for finding the superblock.")
|
||||
parser.add_argument('-l', '--log', action='store_true',
|
||||
help="Show tags in log.")
|
||||
parser.add_argument('-a', '--all', action='store_true',
|
||||
help="Show all tags in log, included tags in corrupted commits.")
|
||||
parser.add_argument('-T', '--no-truncate', action='store_true',
|
||||
help="Show the full contents of files/attrs/tags.")
|
||||
sys.exit(main(parser.parse_args()))
|
|
@ -0,0 +1,778 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# This script manages littlefs tests, which are configured with
|
||||
# .toml files stored in the tests directory.
|
||||
#
|
||||
|
||||
import toml
|
||||
import glob
|
||||
import re
|
||||
import os
|
||||
import io
|
||||
import itertools as it
|
||||
import collections.abc as abc
|
||||
import subprocess as sp
|
||||
import base64
|
||||
import sys
|
||||
import copy
|
||||
import shlex
|
||||
import pty
|
||||
import errno
|
||||
import signal
|
||||
|
||||
TESTDIR = 'tests'
|
||||
RULES = """
|
||||
define FLATTEN
|
||||
tests/%$(subst /,.,$(target)): $(target)
|
||||
./scripts/explode_asserts.py $$< -o $$@
|
||||
endef
|
||||
$(foreach target,$(SRC),$(eval $(FLATTEN)))
|
||||
|
||||
-include tests/*.d
|
||||
|
||||
.SECONDARY:
|
||||
%.test: %.test.o $(foreach f,$(subst /,.,$(SRC:.c=.o)),%.$f)
|
||||
$(CC) $(CFLAGS) $^ $(LFLAGS) -o $@
|
||||
"""
|
||||
GLOBALS = """
|
||||
//////////////// AUTOGENERATED TEST ////////////////
|
||||
#include "lfs2.h"
|
||||
#include "bd/lfs2_testbd.h"
|
||||
#include <stdio.h>
|
||||
extern const char *lfs2_testbd_path;
|
||||
extern uint32_t lfs2_testbd_cycles;
|
||||
"""
|
||||
DEFINES = {
|
||||
'LFS2_READ_SIZE': 16,
|
||||
'LFS2_PROG_SIZE': 'LFS2_READ_SIZE',
|
||||
'LFS2_BLOCK_SIZE': 512,
|
||||
'LFS2_BLOCK_COUNT': 1024,
|
||||
'LFS2_BLOCK_CYCLES': -1,
|
||||
'LFS2_CACHE_SIZE': '(64 % LFS2_PROG_SIZE == 0 ? 64 : LFS2_PROG_SIZE)',
|
||||
'LFS2_LOOKAHEAD_SIZE': 16,
|
||||
'LFS2_ERASE_VALUE': 0xff,
|
||||
'LFS2_ERASE_CYCLES': 0,
|
||||
'LFS2_BADBLOCK_BEHAVIOR': 'LFS2_TESTBD_BADBLOCK_PROGERROR',
|
||||
}
|
||||
PROLOGUE = """
|
||||
// prologue
|
||||
__attribute__((unused)) lfs2_t lfs2;
|
||||
__attribute__((unused)) lfs2_testbd_t bd;
|
||||
__attribute__((unused)) lfs2_file_t file;
|
||||
__attribute__((unused)) lfs2_dir_t dir;
|
||||
__attribute__((unused)) struct lfs2_info info;
|
||||
__attribute__((unused)) char path[1024];
|
||||
__attribute__((unused)) uint8_t buffer[1024];
|
||||
__attribute__((unused)) lfs2_size_t size;
|
||||
__attribute__((unused)) int err;
|
||||
|
||||
__attribute__((unused)) const struct lfs2_config cfg = {
|
||||
.context = &bd,
|
||||
.read = lfs2_testbd_read,
|
||||
.prog = lfs2_testbd_prog,
|
||||
.erase = lfs2_testbd_erase,
|
||||
.sync = lfs2_testbd_sync,
|
||||
.read_size = LFS2_READ_SIZE,
|
||||
.prog_size = LFS2_PROG_SIZE,
|
||||
.block_size = LFS2_BLOCK_SIZE,
|
||||
.block_count = LFS2_BLOCK_COUNT,
|
||||
.block_cycles = LFS2_BLOCK_CYCLES,
|
||||
.cache_size = LFS2_CACHE_SIZE,
|
||||
.lookahead_size = LFS2_LOOKAHEAD_SIZE,
|
||||
};
|
||||
|
||||
__attribute__((unused)) const struct lfs2_testbd_config bdcfg = {
|
||||
.erase_value = LFS2_ERASE_VALUE,
|
||||
.erase_cycles = LFS2_ERASE_CYCLES,
|
||||
.badblock_behavior = LFS2_BADBLOCK_BEHAVIOR,
|
||||
.power_cycles = lfs2_testbd_cycles,
|
||||
};
|
||||
|
||||
lfs2_testbd_createcfg(&cfg, lfs2_testbd_path, &bdcfg) => 0;
|
||||
"""
|
||||
EPILOGUE = """
|
||||
// epilogue
|
||||
lfs2_testbd_destroy(&cfg) => 0;
|
||||
"""
|
||||
PASS = '\033[32m✓\033[0m'
|
||||
FAIL = '\033[31m✗\033[0m'
|
||||
|
||||
class TestFailure(Exception):
|
||||
def __init__(self, case, returncode=None, stdout=None, assert_=None):
|
||||
self.case = case
|
||||
self.returncode = returncode
|
||||
self.stdout = stdout
|
||||
self.assert_ = assert_
|
||||
|
||||
class TestCase:
|
||||
def __init__(self, config, filter=filter,
|
||||
suite=None, caseno=None, lineno=None, **_):
|
||||
self.config = config
|
||||
self.filter = filter
|
||||
self.suite = suite
|
||||
self.caseno = caseno
|
||||
self.lineno = lineno
|
||||
|
||||
self.code = config['code']
|
||||
self.code_lineno = config['code_lineno']
|
||||
self.defines = config.get('define', {})
|
||||
self.if_ = config.get('if', None)
|
||||
self.in_ = config.get('in', None)
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'permno'):
|
||||
if any(k not in self.case.defines for k in self.defines):
|
||||
return '%s#%d#%d (%s)' % (
|
||||
self.suite.name, self.caseno, self.permno, ', '.join(
|
||||
'%s=%s' % (k, v) for k, v in self.defines.items()
|
||||
if k not in self.case.defines))
|
||||
else:
|
||||
return '%s#%d#%d' % (
|
||||
self.suite.name, self.caseno, self.permno)
|
||||
else:
|
||||
return '%s#%d' % (
|
||||
self.suite.name, self.caseno)
|
||||
|
||||
def permute(self, class_=None, defines={}, permno=None, **_):
|
||||
ncase = (class_ or type(self))(self.config)
|
||||
for k, v in self.__dict__.items():
|
||||
setattr(ncase, k, v)
|
||||
ncase.case = self
|
||||
ncase.perms = [ncase]
|
||||
ncase.permno = permno
|
||||
ncase.defines = defines
|
||||
return ncase
|
||||
|
||||
def build(self, f, **_):
|
||||
# prologue
|
||||
for k, v in sorted(self.defines.items()):
|
||||
if k not in self.suite.defines:
|
||||
f.write('#define %s %s\n' % (k, v))
|
||||
|
||||
f.write('void test_case%d(%s) {' % (self.caseno, ','.join(
|
||||
'\n'+8*' '+'__attribute__((unused)) intmax_t %s' % k
|
||||
for k in sorted(self.perms[0].defines)
|
||||
if k not in self.defines)))
|
||||
|
||||
f.write(PROLOGUE)
|
||||
f.write('\n')
|
||||
f.write(4*' '+'// test case %d\n' % self.caseno)
|
||||
f.write(4*' '+'#line %d "%s"\n' % (self.code_lineno, self.suite.path))
|
||||
|
||||
# test case goes here
|
||||
f.write(self.code)
|
||||
|
||||
# epilogue
|
||||
f.write(EPILOGUE)
|
||||
f.write('}\n')
|
||||
|
||||
for k, v in sorted(self.defines.items()):
|
||||
if k not in self.suite.defines:
|
||||
f.write('#undef %s\n' % k)
|
||||
|
||||
def shouldtest(self, **args):
|
||||
if (self.filter is not None and
|
||||
len(self.filter) >= 1 and
|
||||
self.filter[0] != self.caseno):
|
||||
return False
|
||||
elif (self.filter is not None and
|
||||
len(self.filter) >= 2 and
|
||||
self.filter[1] != self.permno):
|
||||
return False
|
||||
elif args.get('no_internal', False) and self.in_ is not None:
|
||||
return False
|
||||
elif self.if_ is not None:
|
||||
if_ = self.if_
|
||||
while True:
|
||||
for k, v in sorted(self.defines.items(),
|
||||
key=lambda x: len(x[0]), reverse=True):
|
||||
if k in if_:
|
||||
if_ = if_.replace(k, '(%s)' % v)
|
||||
break
|
||||
else:
|
||||
break
|
||||
if_ = (
|
||||
re.sub('(\&\&|\?)', ' and ',
|
||||
re.sub('(\|\||:)', ' or ',
|
||||
re.sub('!(?!=)', ' not ', if_))))
|
||||
return eval(if_)
|
||||
else:
|
||||
return True
|
||||
|
||||
def test(self, exec=[], persist=False, cycles=None,
|
||||
gdb=False, failure=None, disk=None, **args):
|
||||
# build command
|
||||
cmd = exec + ['./%s.test' % self.suite.path,
|
||||
repr(self.caseno), repr(self.permno)]
|
||||
|
||||
# persist disk or keep in RAM for speed?
|
||||
if persist:
|
||||
if not disk:
|
||||
disk = self.suite.path + '.disk'
|
||||
if persist != 'noerase':
|
||||
try:
|
||||
with open(disk, 'w') as f:
|
||||
f.truncate(0)
|
||||
if args.get('verbose', False):
|
||||
print('truncate --size=0', disk)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
cmd.append(disk)
|
||||
|
||||
# simulate power-loss after n cycles?
|
||||
if cycles:
|
||||
cmd.append(str(cycles))
|
||||
|
||||
# failed? drop into debugger?
|
||||
if gdb and failure:
|
||||
ncmd = ['gdb']
|
||||
if gdb == 'assert':
|
||||
ncmd.extend(['-ex', 'r'])
|
||||
if failure.assert_:
|
||||
ncmd.extend(['-ex', 'up 2'])
|
||||
elif gdb == 'main':
|
||||
ncmd.extend([
|
||||
'-ex', 'b %s:%d' % (self.suite.path, self.code_lineno),
|
||||
'-ex', 'r'])
|
||||
ncmd.extend(['--args'] + cmd)
|
||||
|
||||
if args.get('verbose', False):
|
||||
print(' '.join(shlex.quote(c) for c in ncmd))
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
sys.exit(sp.call(ncmd))
|
||||
|
||||
# run test case!
|
||||
mpty, spty = pty.openpty()
|
||||
if args.get('verbose', False):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd, stdout=spty, stderr=spty)
|
||||
os.close(spty)
|
||||
mpty = os.fdopen(mpty, 'r', 1)
|
||||
stdout = []
|
||||
assert_ = None
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
line = mpty.readline()
|
||||
except OSError as e:
|
||||
if e.errno == errno.EIO:
|
||||
break
|
||||
raise
|
||||
stdout.append(line)
|
||||
if args.get('verbose', False):
|
||||
sys.stdout.write(line)
|
||||
# intercept asserts
|
||||
m = re.match(
|
||||
'^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
|
||||
.format('(?:\033\[[\d;]*.| )*', 'assert'),
|
||||
line)
|
||||
if m and assert_ is None:
|
||||
try:
|
||||
with open(m.group(1)) as f:
|
||||
lineno = int(m.group(2))
|
||||
line = (next(it.islice(f, lineno-1, None))
|
||||
.strip('\n'))
|
||||
assert_ = {
|
||||
'path': m.group(1),
|
||||
'line': line,
|
||||
'lineno': lineno,
|
||||
'message': m.group(3)}
|
||||
except:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
raise TestFailure(self, 1, stdout, None)
|
||||
proc.wait()
|
||||
|
||||
# did we pass?
|
||||
if proc.returncode != 0:
|
||||
raise TestFailure(self, proc.returncode, stdout, assert_)
|
||||
else:
|
||||
return PASS
|
||||
|
||||
class ValgrindTestCase(TestCase):
|
||||
def __init__(self, config, **args):
|
||||
self.leaky = config.get('leaky', False)
|
||||
super().__init__(config, **args)
|
||||
|
||||
def shouldtest(self, **args):
|
||||
return not self.leaky and super().shouldtest(**args)
|
||||
|
||||
def test(self, exec=[], **args):
|
||||
verbose = args.get('verbose', False)
|
||||
uninit = (self.defines.get('LFS2_ERASE_VALUE', None) == -1)
|
||||
exec = [
|
||||
'valgrind',
|
||||
'--leak-check=full',
|
||||
] + (['--undef-value-errors=no'] if uninit else []) + [
|
||||
] + (['--track-origins=yes'] if not uninit else []) + [
|
||||
'--error-exitcode=4',
|
||||
'--error-limit=no',
|
||||
] + (['--num-callers=1'] if not verbose else []) + [
|
||||
'-q'] + exec
|
||||
return super().test(exec=exec, **args)
|
||||
|
||||
class ReentrantTestCase(TestCase):
|
||||
def __init__(self, config, **args):
|
||||
self.reentrant = config.get('reentrant', False)
|
||||
super().__init__(config, **args)
|
||||
|
||||
def shouldtest(self, **args):
|
||||
return self.reentrant and super().shouldtest(**args)
|
||||
|
||||
def test(self, persist=False, gdb=False, failure=None, **args):
|
||||
for cycles in it.count(1):
|
||||
# clear disk first?
|
||||
if cycles == 1 and persist != 'noerase':
|
||||
persist = 'erase'
|
||||
else:
|
||||
persist = 'noerase'
|
||||
|
||||
# exact cycle we should drop into debugger?
|
||||
if gdb and failure and failure.cycleno == cycles:
|
||||
return super().test(gdb=gdb, persist=persist, cycles=cycles,
|
||||
failure=failure, **args)
|
||||
|
||||
# run tests, but kill the program after prog/erase has
|
||||
# been hit n cycles. We exit with a special return code if the
|
||||
# program has not finished, since this isn't a test failure.
|
||||
try:
|
||||
return super().test(persist=persist, cycles=cycles, **args)
|
||||
except TestFailure as nfailure:
|
||||
if nfailure.returncode == 33:
|
||||
continue
|
||||
else:
|
||||
nfailure.cycleno = cycles
|
||||
raise
|
||||
|
||||
class TestSuite:
|
||||
def __init__(self, path, classes=[TestCase], defines={},
|
||||
filter=None, **args):
|
||||
self.name = os.path.basename(path)
|
||||
if self.name.endswith('.toml'):
|
||||
self.name = self.name[:-len('.toml')]
|
||||
self.path = path
|
||||
self.classes = classes
|
||||
self.defines = defines.copy()
|
||||
self.filter = filter
|
||||
|
||||
with open(path) as f:
|
||||
# load tests
|
||||
config = toml.load(f)
|
||||
|
||||
# find line numbers
|
||||
f.seek(0)
|
||||
linenos = []
|
||||
code_linenos = []
|
||||
for i, line in enumerate(f):
|
||||
if re.match(r'\[\[\s*case\s*\]\]', line):
|
||||
linenos.append(i+1)
|
||||
if re.match(r'code\s*=\s*(\'\'\'|""")', line):
|
||||
code_linenos.append(i+2)
|
||||
|
||||
code_linenos.reverse()
|
||||
|
||||
# grab global config
|
||||
for k, v in config.get('define', {}).items():
|
||||
if k not in self.defines:
|
||||
self.defines[k] = v
|
||||
self.code = config.get('code', None)
|
||||
if self.code is not None:
|
||||
self.code_lineno = code_linenos.pop()
|
||||
|
||||
# create initial test cases
|
||||
self.cases = []
|
||||
for i, (case, lineno) in enumerate(zip(config['case'], linenos)):
|
||||
# code lineno?
|
||||
if 'code' in case:
|
||||
case['code_lineno'] = code_linenos.pop()
|
||||
# merge conditions if necessary
|
||||
if 'if' in config and 'if' in case:
|
||||
case['if'] = '(%s) && (%s)' % (config['if'], case['if'])
|
||||
elif 'if' in config:
|
||||
case['if'] = config['if']
|
||||
# initialize test case
|
||||
self.cases.append(TestCase(case, filter=filter,
|
||||
suite=self, caseno=i+1, lineno=lineno, **args))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.name < other.name
|
||||
|
||||
def permute(self, **args):
|
||||
for case in self.cases:
|
||||
# lets find all parameterized definitions, in one of [args.D,
|
||||
# suite.defines, case.defines, DEFINES]. Note that each of these
|
||||
# can be either a dict of defines, or a list of dicts, expressing
|
||||
# an initial set of permutations.
|
||||
pending = [{}]
|
||||
for inits in [self.defines, case.defines, DEFINES]:
|
||||
if not isinstance(inits, list):
|
||||
inits = [inits]
|
||||
|
||||
npending = []
|
||||
for init, pinit in it.product(inits, pending):
|
||||
ninit = pinit.copy()
|
||||
for k, v in init.items():
|
||||
if k not in ninit:
|
||||
try:
|
||||
ninit[k] = eval(v)
|
||||
except:
|
||||
ninit[k] = v
|
||||
npending.append(ninit)
|
||||
|
||||
pending = npending
|
||||
|
||||
# expand permutations
|
||||
pending = list(reversed(pending))
|
||||
expanded = []
|
||||
while pending:
|
||||
perm = pending.pop()
|
||||
for k, v in sorted(perm.items()):
|
||||
if not isinstance(v, str) and isinstance(v, abc.Iterable):
|
||||
for nv in reversed(v):
|
||||
nperm = perm.copy()
|
||||
nperm[k] = nv
|
||||
pending.append(nperm)
|
||||
break
|
||||
else:
|
||||
expanded.append(perm)
|
||||
|
||||
# generate permutations
|
||||
case.perms = []
|
||||
for i, (class_, defines) in enumerate(
|
||||
it.product(self.classes, expanded)):
|
||||
case.perms.append(case.permute(
|
||||
class_, defines, permno=i+1, **args))
|
||||
|
||||
# also track non-unique defines
|
||||
case.defines = {}
|
||||
for k, v in case.perms[0].defines.items():
|
||||
if all(perm.defines[k] == v for perm in case.perms):
|
||||
case.defines[k] = v
|
||||
|
||||
# track all perms and non-unique defines
|
||||
self.perms = []
|
||||
for case in self.cases:
|
||||
self.perms.extend(case.perms)
|
||||
|
||||
self.defines = {}
|
||||
for k, v in self.perms[0].defines.items():
|
||||
if all(perm.defines.get(k, None) == v for perm in self.perms):
|
||||
self.defines[k] = v
|
||||
|
||||
return self.perms
|
||||
|
||||
def build(self, **args):
|
||||
# build test files
|
||||
tf = open(self.path + '.test.c.t', 'w')
|
||||
tf.write(GLOBALS)
|
||||
if self.code is not None:
|
||||
tf.write('#line %d "%s"\n' % (self.code_lineno, self.path))
|
||||
tf.write(self.code)
|
||||
|
||||
tfs = {None: tf}
|
||||
for case in self.cases:
|
||||
if case.in_ not in tfs:
|
||||
tfs[case.in_] = open(self.path+'.'+
|
||||
case.in_.replace('/', '.')+'.t', 'w')
|
||||
tfs[case.in_].write('#line 1 "%s"\n' % case.in_)
|
||||
with open(case.in_) as f:
|
||||
for line in f:
|
||||
tfs[case.in_].write(line)
|
||||
tfs[case.in_].write('\n')
|
||||
tfs[case.in_].write(GLOBALS)
|
||||
|
||||
tfs[case.in_].write('\n')
|
||||
case.build(tfs[case.in_], **args)
|
||||
|
||||
tf.write('\n')
|
||||
tf.write('const char *lfs2_testbd_path;\n')
|
||||
tf.write('uint32_t lfs2_testbd_cycles;\n')
|
||||
tf.write('int main(int argc, char **argv) {\n')
|
||||
tf.write(4*' '+'int case_ = (argc > 1) ? atoi(argv[1]) : 0;\n')
|
||||
tf.write(4*' '+'int perm = (argc > 2) ? atoi(argv[2]) : 0;\n')
|
||||
tf.write(4*' '+'lfs2_testbd_path = (argc > 3) ? argv[3] : NULL;\n')
|
||||
tf.write(4*' '+'lfs2_testbd_cycles = (argc > 4) ? atoi(argv[4]) : 0;\n')
|
||||
for perm in self.perms:
|
||||
# test declaration
|
||||
tf.write(4*' '+'extern void test_case%d(%s);\n' % (
|
||||
perm.caseno, ', '.join(
|
||||
'intmax_t %s' % k for k in sorted(perm.defines)
|
||||
if k not in perm.case.defines)))
|
||||
# test call
|
||||
tf.write(4*' '+
|
||||
'if (argc < 3 || (case_ == %d && perm == %d)) {'
|
||||
' test_case%d(%s); '
|
||||
'}\n' % (perm.caseno, perm.permno, perm.caseno, ', '.join(
|
||||
str(v) for k, v in sorted(perm.defines.items())
|
||||
if k not in perm.case.defines)))
|
||||
tf.write('}\n')
|
||||
|
||||
for tf in tfs.values():
|
||||
tf.close()
|
||||
|
||||
# write makefiles
|
||||
with open(self.path + '.mk', 'w') as mk:
|
||||
mk.write(RULES.replace(4*' ', '\t'))
|
||||
mk.write('\n')
|
||||
|
||||
# add truely global defines globally
|
||||
for k, v in sorted(self.defines.items()):
|
||||
mk.write('%s: override CFLAGS += -D%s=%r\n' % (
|
||||
self.path+'.test', k, v))
|
||||
|
||||
for path in tfs:
|
||||
if path is None:
|
||||
mk.write('%s: %s | %s\n' % (
|
||||
self.path+'.test.c',
|
||||
self.path,
|
||||
self.path+'.test.c.t'))
|
||||
else:
|
||||
mk.write('%s: %s %s | %s\n' % (
|
||||
self.path+'.'+path.replace('/', '.'),
|
||||
self.path, path,
|
||||
self.path+'.'+path.replace('/', '.')+'.t'))
|
||||
mk.write('\t./scripts/explode_asserts.py $| -o $@\n')
|
||||
|
||||
self.makefile = self.path + '.mk'
|
||||
self.target = self.path + '.test'
|
||||
return self.makefile, self.target
|
||||
|
||||
def test(self, **args):
|
||||
# run test suite!
|
||||
if not args.get('verbose', True):
|
||||
sys.stdout.write(self.name + ' ')
|
||||
sys.stdout.flush()
|
||||
for perm in self.perms:
|
||||
if not perm.shouldtest(**args):
|
||||
continue
|
||||
|
||||
try:
|
||||
result = perm.test(**args)
|
||||
except TestFailure as failure:
|
||||
perm.result = failure
|
||||
if not args.get('verbose', True):
|
||||
sys.stdout.write(FAIL)
|
||||
sys.stdout.flush()
|
||||
if not args.get('keep_going', False):
|
||||
if not args.get('verbose', True):
|
||||
sys.stdout.write('\n')
|
||||
raise
|
||||
else:
|
||||
perm.result = PASS
|
||||
if not args.get('verbose', True):
|
||||
sys.stdout.write(PASS)
|
||||
sys.stdout.flush()
|
||||
|
||||
if not args.get('verbose', True):
|
||||
sys.stdout.write('\n')
|
||||
|
||||
def main(**args):
|
||||
# figure out explicit defines
|
||||
defines = {}
|
||||
for define in args['D']:
|
||||
k, v, *_ = define.split('=', 2) + ['']
|
||||
defines[k] = v
|
||||
|
||||
# and what class of TestCase to run
|
||||
classes = []
|
||||
if args.get('normal', False):
|
||||
classes.append(TestCase)
|
||||
if args.get('reentrant', False):
|
||||
classes.append(ReentrantTestCase)
|
||||
if args.get('valgrind', False):
|
||||
classes.append(ValgrindTestCase)
|
||||
if not classes:
|
||||
classes = [TestCase]
|
||||
|
||||
suites = []
|
||||
for testpath in args['testpaths']:
|
||||
# optionally specified test case/perm
|
||||
testpath, *filter = testpath.split('#')
|
||||
filter = [int(f) for f in filter]
|
||||
|
||||
# figure out the suite's toml file
|
||||
if os.path.isdir(testpath):
|
||||
testpath = testpath + '/test_*.toml'
|
||||
elif os.path.isfile(testpath):
|
||||
testpath = testpath
|
||||
elif testpath.endswith('.toml'):
|
||||
testpath = TESTDIR + '/' + testpath
|
||||
else:
|
||||
testpath = TESTDIR + '/' + testpath + '.toml'
|
||||
|
||||
# find tests
|
||||
for path in glob.glob(testpath):
|
||||
suites.append(TestSuite(path, classes, defines, filter, **args))
|
||||
|
||||
# sort for reproducability
|
||||
suites = sorted(suites)
|
||||
|
||||
# generate permutations
|
||||
for suite in suites:
|
||||
suite.permute(**args)
|
||||
|
||||
# build tests in parallel
|
||||
print('====== building ======')
|
||||
makefiles = []
|
||||
targets = []
|
||||
for suite in suites:
|
||||
makefile, target = suite.build(**args)
|
||||
makefiles.append(makefile)
|
||||
targets.append(target)
|
||||
|
||||
cmd = (['make', '-f', 'Makefile'] +
|
||||
list(it.chain.from_iterable(['-f', m] for m in makefiles)) +
|
||||
[target for target in targets])
|
||||
mpty, spty = pty.openpty()
|
||||
if args.get('verbose', False):
|
||||
print(' '.join(shlex.quote(c) for c in cmd))
|
||||
proc = sp.Popen(cmd, stdout=spty, stderr=spty)
|
||||
os.close(spty)
|
||||
mpty = os.fdopen(mpty, 'r', 1)
|
||||
stdout = []
|
||||
while True:
|
||||
try:
|
||||
line = mpty.readline()
|
||||
except OSError as e:
|
||||
if e.errno == errno.EIO:
|
||||
break
|
||||
raise
|
||||
stdout.append(line)
|
||||
if args.get('verbose', False):
|
||||
sys.stdout.write(line)
|
||||
# intercept warnings
|
||||
m = re.match(
|
||||
'^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$'
|
||||
.format('(?:\033\[[\d;]*.| )*', 'warning'),
|
||||
line)
|
||||
if m and not args.get('verbose', False):
|
||||
try:
|
||||
with open(m.group(1)) as f:
|
||||
lineno = int(m.group(2))
|
||||
line = next(it.islice(f, lineno-1, None)).strip('\n')
|
||||
sys.stdout.write(
|
||||
"\033[01m{path}:{lineno}:\033[01;35mwarning:\033[m "
|
||||
"{message}\n{line}\n\n".format(
|
||||
path=m.group(1), line=line, lineno=lineno,
|
||||
message=m.group(3)))
|
||||
except:
|
||||
pass
|
||||
proc.wait()
|
||||
|
||||
if proc.returncode != 0:
|
||||
if not args.get('verbose', False):
|
||||
for line in stdout:
|
||||
sys.stdout.write(line)
|
||||
sys.exit(-3)
|
||||
|
||||
print('built %d test suites, %d test cases, %d permutations' % (
|
||||
len(suites),
|
||||
sum(len(suite.cases) for suite in suites),
|
||||
sum(len(suite.perms) for suite in suites)))
|
||||
|
||||
filtered = 0
|
||||
for suite in suites:
|
||||
for perm in suite.perms:
|
||||
filtered += perm.shouldtest(**args)
|
||||
if filtered != sum(len(suite.perms) for suite in suites):
|
||||
print('filtered down to %d permutations' % filtered)
|
||||
|
||||
# only requested to build?
|
||||
if args.get('build', False):
|
||||
return 0
|
||||
|
||||
print('====== testing ======')
|
||||
try:
|
||||
for suite in suites:
|
||||
suite.test(**args)
|
||||
except TestFailure:
|
||||
pass
|
||||
|
||||
print('====== results ======')
|
||||
passed = 0
|
||||
failed = 0
|
||||
for suite in suites:
|
||||
for perm in suite.perms:
|
||||
if not hasattr(perm, 'result'):
|
||||
continue
|
||||
|
||||
if perm.result == PASS:
|
||||
passed += 1
|
||||
else:
|
||||
sys.stdout.write(
|
||||
"\033[01m{path}:{lineno}:\033[01;31mfailure:\033[m "
|
||||
"{perm} failed with {returncode}\n".format(
|
||||
perm=perm, path=perm.suite.path, lineno=perm.lineno,
|
||||
returncode=perm.result.returncode or 0))
|
||||
if perm.result.stdout:
|
||||
if perm.result.assert_:
|
||||
stdout = perm.result.stdout[:-1]
|
||||
else:
|
||||
stdout = perm.result.stdout
|
||||
for line in stdout[-5:]:
|
||||
sys.stdout.write(line)
|
||||
if perm.result.assert_:
|
||||
sys.stdout.write(
|
||||
"\033[01m{path}:{lineno}:\033[01;31massert:\033[m "
|
||||
"{message}\n{line}\n".format(
|
||||
**perm.result.assert_))
|
||||
sys.stdout.write('\n')
|
||||
failed += 1
|
||||
|
||||
if args.get('gdb', False):
|
||||
failure = None
|
||||
for suite in suites:
|
||||
for perm in suite.perms:
|
||||
if getattr(perm, 'result', PASS) != PASS:
|
||||
failure = perm.result
|
||||
if failure is not None:
|
||||
print('======= gdb ======')
|
||||
# drop into gdb
|
||||
failure.case.test(failure=failure, **args)
|
||||
sys.exit(0)
|
||||
|
||||
print('tests passed: %d' % passed)
|
||||
print('tests failed: %d' % failed)
|
||||
return 1 if failed > 0 else 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run parameterized tests in various configurations.")
|
||||
parser.add_argument('testpaths', nargs='*', default=[TESTDIR],
|
||||
help="Description of test(s) to run. By default, this is all tests \
|
||||
found in the \"{0}\" directory. Here, you can specify a different \
|
||||
directory of tests, a specific file, a suite by name, and even a \
|
||||
specific test case by adding brackets. For example \
|
||||
\"test_dirs[0]\" or \"{0}/test_dirs.toml[0]\".".format(TESTDIR))
|
||||
parser.add_argument('-D', action='append', default=[],
|
||||
help="Overriding parameter definitions.")
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help="Output everything that is happening.")
|
||||
parser.add_argument('-k', '--keep-going', action='store_true',
|
||||
help="Run all tests instead of stopping on first error. Useful for CI.")
|
||||
parser.add_argument('-p', '--persist', choices=['erase', 'noerase'],
|
||||
nargs='?', const='erase',
|
||||
help="Store disk image in a file.")
|
||||
parser.add_argument('-b', '--build', action='store_true',
|
||||
help="Only build the tests, do not execute.")
|
||||
parser.add_argument('-g', '--gdb', choices=['init', 'main', 'assert'],
|
||||
nargs='?', const='assert',
|
||||
help="Drop into gdb on test failure.")
|
||||
parser.add_argument('--no-internal', action='store_true',
|
||||
help="Don't run tests that require internal knowledge.")
|
||||
parser.add_argument('-n', '--normal', action='store_true',
|
||||
help="Run tests normally.")
|
||||
parser.add_argument('-r', '--reentrant', action='store_true',
|
||||
help="Run reentrant tests with simulated power-loss.")
|
||||
parser.add_argument('-V', '--valgrind', action='store_true',
|
||||
help="Run non-leaky tests under valgrind to check for memory leaks.")
|
||||
parser.add_argument('-e', '--exec', default=[], type=lambda e: e.split(' '),
|
||||
help="Run tests with another executable prefixed on the command line.")
|
||||
parser.add_argument('-d', '--disk',
|
||||
help="Specify a file to use for persistent/reentrant tests.")
|
||||
sys.exit(main(**vars(parser.parse_args())))
|
|
@ -0,0 +1,653 @@
|
|||
# allocator tests
|
||||
# note for these to work there are a number constraints on the device geometry
|
||||
if = 'LFS2_BLOCK_CYCLES == -1'
|
||||
|
||||
[[case]] # parallel allocation test
|
||||
define.FILES = 3
|
||||
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-6)) / FILES)'
|
||||
code = '''
|
||||
const char *names[FILES] = {"bacon", "eggs", "pancakes"};
|
||||
lfs2_file_t files[FILES];
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "breakfast") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_file_open(&lfs2, &files[n], path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
}
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
size = strlen(names[n]);
|
||||
for (lfs2_size_t i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_write(&lfs2, &files[n], names[n], size) => size;
|
||||
}
|
||||
}
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
lfs2_file_close(&lfs2, &files[n]) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
size = strlen(names[n]);
|
||||
for (lfs2_size_t i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(memcmp(buffer, names[n], size) == 0);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # serial allocation test
|
||||
define.FILES = 3
|
||||
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-6)) / FILES)'
|
||||
code = '''
|
||||
const char *names[FILES] = {"bacon", "eggs", "pancakes"};
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "breakfast") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
size = strlen(names[n]);
|
||||
memcpy(buffer, names[n], size);
|
||||
for (int i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
}
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
size = strlen(names[n]);
|
||||
for (int i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(memcmp(buffer, names[n], size) == 0);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # parallel allocation reuse test
|
||||
define.FILES = 3
|
||||
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-6)) / FILES)'
|
||||
define.CYCLES = [1, 10]
|
||||
code = '''
|
||||
const char *names[FILES] = {"bacon", "eggs", "pancakes"};
|
||||
lfs2_file_t files[FILES];
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
for (int c = 0; c < CYCLES; c++) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "breakfast") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_file_open(&lfs2, &files[n], path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
}
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
size = strlen(names[n]);
|
||||
for (int i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_write(&lfs2, &files[n], names[n], size) => size;
|
||||
}
|
||||
}
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
lfs2_file_close(&lfs2, &files[n]) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
size = strlen(names[n]);
|
||||
for (int i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(memcmp(buffer, names[n], size) == 0);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_remove(&lfs2, "breakfast") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
[[case]] # serial allocation reuse test
|
||||
define.FILES = 3
|
||||
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-6)) / FILES)'
|
||||
define.CYCLES = [1, 10]
|
||||
code = '''
|
||||
const char *names[FILES] = {"bacon", "eggs", "pancakes"};
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
for (int c = 0; c < CYCLES; c++) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "breakfast") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
size = strlen(names[n]);
|
||||
memcpy(buffer, names[n], size);
|
||||
for (int i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
}
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
size = strlen(names[n]);
|
||||
for (int i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(memcmp(buffer, names[n], size) == 0);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int n = 0; n < FILES; n++) {
|
||||
sprintf(path, "breakfast/%s", names[n]);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_remove(&lfs2, "breakfast") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
[[case]] # exhaustion test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
size = strlen("exhaustion");
|
||||
memcpy(buffer, "exhaustion", size);
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
lfs2_ssize_t res;
|
||||
while (true) {
|
||||
res = lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
if (res < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
res => size;
|
||||
}
|
||||
res => LFS2_ERR_NOSPC;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_RDONLY);
|
||||
size = strlen("exhaustion");
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "exhaustion", size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # exhaustion wraparound test
|
||||
define.SIZE = '(((LFS2_BLOCK_SIZE-8)*(LFS2_BLOCK_COUNT-4)) / 3)'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "padding", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
size = strlen("buffering");
|
||||
memcpy(buffer, "buffering", size);
|
||||
for (int i = 0; i < SIZE; i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_remove(&lfs2, "padding") => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
size = strlen("exhaustion");
|
||||
memcpy(buffer, "exhaustion", size);
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
lfs2_ssize_t res;
|
||||
while (true) {
|
||||
res = lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
if (res < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
res => size;
|
||||
}
|
||||
res => LFS2_ERR_NOSPC;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_RDONLY);
|
||||
size = strlen("exhaustion");
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "exhaustion", size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_remove(&lfs2, "exhaustion") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # dir exhaustion test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// find out max file size
|
||||
lfs2_mkdir(&lfs2, "exhaustiondir") => 0;
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
int count = 0;
|
||||
while (true) {
|
||||
err = lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
if (err < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
err => LFS2_ERR_NOSPC;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_remove(&lfs2, "exhaustion") => 0;
|
||||
lfs2_remove(&lfs2, "exhaustiondir") => 0;
|
||||
|
||||
// see if dir fits with max file size
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
for (int i = 0; i < count; i++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_mkdir(&lfs2, "exhaustiondir") => 0;
|
||||
lfs2_remove(&lfs2, "exhaustiondir") => 0;
|
||||
lfs2_remove(&lfs2, "exhaustion") => 0;
|
||||
|
||||
// see if dir fits with > max file size
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
for (int i = 0; i < count+1; i++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_mkdir(&lfs2, "exhaustiondir") => LFS2_ERR_NOSPC;
|
||||
|
||||
lfs2_remove(&lfs2, "exhaustion") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # what if we have a bad block during an allocation scan?
|
||||
in = "lfs2.c"
|
||||
define.LFS2_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS2_BADBLOCK_BEHAVIOR = 'LFS2_TESTBD_BADBLOCK_READERROR'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
// first fill to exhaustion to find available space
|
||||
lfs2_file_open(&lfs2, &file, "pacman", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
strcpy((char*)buffer, "waka");
|
||||
size = strlen("waka");
|
||||
lfs2_size_t filesize = 0;
|
||||
while (true) {
|
||||
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
assert(res == (lfs2_ssize_t)size || res == LFS2_ERR_NOSPC);
|
||||
if (res == LFS2_ERR_NOSPC) {
|
||||
break;
|
||||
}
|
||||
filesize += size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// now fill all but a couple of blocks of the filesystem with data
|
||||
filesize -= 3*LFS2_BLOCK_SIZE;
|
||||
lfs2_file_open(&lfs2, &file, "pacman", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
strcpy((char*)buffer, "waka");
|
||||
size = strlen("waka");
|
||||
for (lfs2_size_t i = 0; i < filesize/size; i++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// also save head of file so we can error during lookahead scan
|
||||
lfs2_block_t fileblock = file.ctz.head;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// remount to force an alloc scan
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// but mark the head of our file as a "bad block", this is force our
|
||||
// scan to bail early
|
||||
lfs2_testbd_setwear(&cfg, fileblock, 0xffffffff) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "ghost", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
strcpy((char*)buffer, "chomp");
|
||||
size = strlen("chomp");
|
||||
while (true) {
|
||||
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
assert(res == (lfs2_ssize_t)size || res == LFS2_ERR_CORRUPT);
|
||||
if (res == LFS2_ERR_CORRUPT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// now reverse the "bad block" and try to write the file again until we
|
||||
// run out of space
|
||||
lfs2_testbd_setwear(&cfg, fileblock, 0) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "ghost", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
strcpy((char*)buffer, "chomp");
|
||||
size = strlen("chomp");
|
||||
while (true) {
|
||||
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
assert(res == (lfs2_ssize_t)size || res == LFS2_ERR_NOSPC);
|
||||
if (res == LFS2_ERR_NOSPC) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// check that the disk isn't hurt
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "pacman", LFS2_O_RDONLY) => 0;
|
||||
strcpy((char*)buffer, "waka");
|
||||
size = strlen("waka");
|
||||
for (lfs2_size_t i = 0; i < filesize/size; i++) {
|
||||
uint8_t rbuffer[4];
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
assert(memcmp(rbuffer, buffer, size) == 0);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
|
||||
# Below, I don't like these tests. They're fragile and depend _heavily_
|
||||
# on the geometry of the block device. But they are valuable. Eventually they
|
||||
# should be removed and replaced with generalized tests.
|
||||
|
||||
[[case]] # chained dir exhaustion test
|
||||
define.LFS2_BLOCK_SIZE = 512
|
||||
define.LFS2_BLOCK_COUNT = 1024
|
||||
if = 'LFS2_BLOCK_SIZE == 512 && LFS2_BLOCK_COUNT == 1024'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// find out max file size
|
||||
lfs2_mkdir(&lfs2, "exhaustiondir") => 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
}
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
int count = 0;
|
||||
while (true) {
|
||||
err = lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
if (err < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
err => LFS2_ERR_NOSPC;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_remove(&lfs2, "exhaustion") => 0;
|
||||
lfs2_remove(&lfs2, "exhaustiondir") => 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
|
||||
// see that chained dir fails
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
for (int i = 0; i < count+1; i++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
sprintf(path, "dirwithanexhaustivelylongnameforpadding%d", i);
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
}
|
||||
|
||||
lfs2_mkdir(&lfs2, "exhaustiondir") => LFS2_ERR_NOSPC;
|
||||
|
||||
// shorten file to try a second chained dir
|
||||
while (true) {
|
||||
err = lfs2_mkdir(&lfs2, "exhaustiondir");
|
||||
if (err != LFS2_ERR_NOSPC) {
|
||||
break;
|
||||
}
|
||||
|
||||
lfs2_ssize_t filesize = lfs2_file_size(&lfs2, &file);
|
||||
filesize > 0 => true;
|
||||
|
||||
lfs2_file_truncate(&lfs2, &file, filesize - size) => 0;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
}
|
||||
err => 0;
|
||||
|
||||
lfs2_mkdir(&lfs2, "exhaustiondir2") => LFS2_ERR_NOSPC;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # split dir test
|
||||
define.LFS2_BLOCK_SIZE = 512
|
||||
define.LFS2_BLOCK_COUNT = 1024
|
||||
if = 'LFS2_BLOCK_SIZE == 512 && LFS2_BLOCK_COUNT == 1024'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// create one block hole for half a directory
|
||||
lfs2_file_open(&lfs2, &file, "bump", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
for (lfs2_size_t i = 0; i < cfg.block_size; i += 2) {
|
||||
memcpy(&buffer[i], "hi", 2);
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, cfg.block_size) => cfg.block_size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion", LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
for (lfs2_size_t i = 0;
|
||||
i < (cfg.block_count-4)*(cfg.block_size-8);
|
||||
i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// remount to force reset of lookahead
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// open hole
|
||||
lfs2_remove(&lfs2, "bump") => 0;
|
||||
|
||||
lfs2_mkdir(&lfs2, "splitdir") => 0;
|
||||
lfs2_file_open(&lfs2, &file, "splitdir/bump",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
for (lfs2_size_t i = 0; i < cfg.block_size; i += 2) {
|
||||
memcpy(&buffer[i], "hi", 2);
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, 2*cfg.block_size) => LFS2_ERR_NOSPC;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # outdated lookahead test
|
||||
define.LFS2_BLOCK_SIZE = 512
|
||||
define.LFS2_BLOCK_COUNT = 1024
|
||||
if = 'LFS2_BLOCK_SIZE == 512 && LFS2_BLOCK_COUNT == 1024'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// fill completely with two files
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion1",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
for (lfs2_size_t i = 0;
|
||||
i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
|
||||
i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion2",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
for (lfs2_size_t i = 0;
|
||||
i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
|
||||
i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// remount to force reset of lookahead
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// rewrite one file
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion1",
|
||||
LFS2_O_WRONLY | LFS2_O_TRUNC) => 0;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
for (lfs2_size_t i = 0;
|
||||
i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
|
||||
i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// rewrite second file, this requires lookahead does not
|
||||
// use old population
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion2",
|
||||
LFS2_O_WRONLY | LFS2_O_TRUNC) => 0;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
for (lfs2_size_t i = 0;
|
||||
i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
|
||||
i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # outdated lookahead and split dir test
|
||||
define.LFS2_BLOCK_SIZE = 512
|
||||
define.LFS2_BLOCK_COUNT = 1024
|
||||
if = 'LFS2_BLOCK_SIZE == 512 && LFS2_BLOCK_COUNT == 1024'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// fill completely with two files
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion1",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
for (lfs2_size_t i = 0;
|
||||
i < ((cfg.block_count-2)/2)*(cfg.block_size-8);
|
||||
i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion2",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
for (lfs2_size_t i = 0;
|
||||
i < ((cfg.block_count-2+1)/2)*(cfg.block_size-8);
|
||||
i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// remount to force reset of lookahead
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// rewrite one file with a hole of one block
|
||||
lfs2_file_open(&lfs2, &file, "exhaustion1",
|
||||
LFS2_O_WRONLY | LFS2_O_TRUNC) => 0;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
size = strlen("blahblahblahblah");
|
||||
memcpy(buffer, "blahblahblahblah", size);
|
||||
for (lfs2_size_t i = 0;
|
||||
i < ((cfg.block_count-2)/2 - 1)*(cfg.block_size-8);
|
||||
i += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// try to allocate a directory, should fail!
|
||||
lfs2_mkdir(&lfs2, "split") => LFS2_ERR_NOSPC;
|
||||
|
||||
// file should not fail
|
||||
lfs2_file_open(&lfs2, &file, "notasplit",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_write(&lfs2, &file, "hi", 2) => 2;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
|
@ -0,0 +1,304 @@
|
|||
[[case]] # set/get attribute
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "hello") => 0;
|
||||
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_write(&lfs2, &file, "hello", strlen("hello")) => strlen("hello");
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
lfs2_setattr(&lfs2, "hello", 'A', "aaaa", 4) => 0;
|
||||
lfs2_setattr(&lfs2, "hello", 'B', "bbbbbb", 6) => 0;
|
||||
lfs2_setattr(&lfs2, "hello", 'C', "ccccc", 5) => 0;
|
||||
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 6;
|
||||
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "bbbbbb", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_setattr(&lfs2, "hello", 'B', "", 0) => 0;
|
||||
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 0;
|
||||
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_removeattr(&lfs2, "hello", 'B') => 0;
|
||||
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => LFS2_ERR_NOATTR;
|
||||
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_setattr(&lfs2, "hello", 'B', "dddddd", 6) => 0;
|
||||
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 6;
|
||||
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "dddddd", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_setattr(&lfs2, "hello", 'B', "eee", 3) => 0;
|
||||
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 3;
|
||||
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "eee\0\0\0", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_setattr(&lfs2, "hello", 'A', buffer, LFS2_ATTR_MAX+1) => LFS2_ERR_NOSPC;
|
||||
lfs2_setattr(&lfs2, "hello", 'B', "fffffffff", 9) => 0;
|
||||
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 6) => 9;
|
||||
lfs2_getattr(&lfs2, "hello", 'C', buffer+10, 5) => 5;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
lfs2_getattr(&lfs2, "hello", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "hello", 'B', buffer+4, 9) => 9;
|
||||
lfs2_getattr(&lfs2, "hello", 'C', buffer+13, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "fffffffff", 9) => 0;
|
||||
memcmp(buffer+13, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => strlen("hello");
|
||||
memcmp(buffer, "hello", strlen("hello")) => 0;
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # set/get root attribute
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "hello") => 0;
|
||||
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_write(&lfs2, &file, "hello", strlen("hello")) => strlen("hello");
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
lfs2_setattr(&lfs2, "/", 'A', "aaaa", 4) => 0;
|
||||
lfs2_setattr(&lfs2, "/", 'B', "bbbbbb", 6) => 0;
|
||||
lfs2_setattr(&lfs2, "/", 'C', "ccccc", 5) => 0;
|
||||
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 6;
|
||||
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "bbbbbb", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_setattr(&lfs2, "/", 'B', "", 0) => 0;
|
||||
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 0;
|
||||
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_removeattr(&lfs2, "/", 'B') => 0;
|
||||
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => LFS2_ERR_NOATTR;
|
||||
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_setattr(&lfs2, "/", 'B', "dddddd", 6) => 0;
|
||||
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 6;
|
||||
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "dddddd", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_setattr(&lfs2, "/", 'B', "eee", 3) => 0;
|
||||
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 3;
|
||||
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "eee\0\0\0", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_setattr(&lfs2, "/", 'A', buffer, LFS2_ATTR_MAX+1) => LFS2_ERR_NOSPC;
|
||||
lfs2_setattr(&lfs2, "/", 'B', "fffffffff", 9) => 0;
|
||||
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 6) => 9;
|
||||
lfs2_getattr(&lfs2, "/", 'C', buffer+10, 5) => 5;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
lfs2_getattr(&lfs2, "/", 'A', buffer, 4) => 4;
|
||||
lfs2_getattr(&lfs2, "/", 'B', buffer+4, 9) => 9;
|
||||
lfs2_getattr(&lfs2, "/", 'C', buffer+13, 5) => 5;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "fffffffff", 9) => 0;
|
||||
memcmp(buffer+13, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => strlen("hello");
|
||||
memcmp(buffer, "hello", strlen("hello")) => 0;
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # set/get file attribute
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "hello") => 0;
|
||||
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_write(&lfs2, &file, "hello", strlen("hello")) => strlen("hello");
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
struct lfs2_attr attrs1[] = {
|
||||
{'A', buffer, 4},
|
||||
{'B', buffer+4, 6},
|
||||
{'C', buffer+10, 5},
|
||||
};
|
||||
struct lfs2_file_config cfg1 = {.attrs=attrs1, .attr_count=3};
|
||||
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
|
||||
memcpy(buffer, "aaaa", 4);
|
||||
memcpy(buffer+4, "bbbbbb", 6);
|
||||
memcpy(buffer+10, "ccccc", 5);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memset(buffer, 0, 15);
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "bbbbbb", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
attrs1[1].size = 0;
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memset(buffer, 0, 15);
|
||||
attrs1[1].size = 6;
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "\0\0\0\0\0\0", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
attrs1[1].size = 6;
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
|
||||
memcpy(buffer+4, "dddddd", 6);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memset(buffer, 0, 15);
|
||||
attrs1[1].size = 6;
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "dddddd", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
attrs1[1].size = 3;
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
|
||||
memcpy(buffer+4, "eee", 3);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memset(buffer, 0, 15);
|
||||
attrs1[1].size = 6;
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "eee\0\0\0", 6) => 0;
|
||||
memcmp(buffer+10, "ccccc", 5) => 0;
|
||||
|
||||
attrs1[0].size = LFS2_ATTR_MAX+1;
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1)
|
||||
=> LFS2_ERR_NOSPC;
|
||||
|
||||
struct lfs2_attr attrs2[] = {
|
||||
{'A', buffer, 4},
|
||||
{'B', buffer+4, 9},
|
||||
{'C', buffer+13, 5},
|
||||
};
|
||||
struct lfs2_file_config cfg2 = {.attrs=attrs2, .attr_count=3};
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDWR, &cfg2) => 0;
|
||||
memcpy(buffer+4, "fffffffff", 9);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
attrs1[0].size = 4;
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg1) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
struct lfs2_attr attrs3[] = {
|
||||
{'A', buffer, 4},
|
||||
{'B', buffer+4, 9},
|
||||
{'C', buffer+13, 5},
|
||||
};
|
||||
struct lfs2_file_config cfg3 = {.attrs=attrs3, .attr_count=3};
|
||||
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_RDONLY, &cfg3) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
memcmp(buffer, "aaaa", 4) => 0;
|
||||
memcmp(buffer+4, "fffffffff", 9) => 0;
|
||||
memcmp(buffer+13, "ccccc", 5) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, sizeof(buffer)) => strlen("hello");
|
||||
memcmp(buffer, "hello", strlen("hello")) => 0;
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # deferred file attributes
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "hello") => 0;
|
||||
lfs2_file_open(&lfs2, &file, "hello/hello", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_write(&lfs2, &file, "hello", strlen("hello")) => strlen("hello");
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_setattr(&lfs2, "hello/hello", 'B', "fffffffff", 9) => 0;
|
||||
lfs2_setattr(&lfs2, "hello/hello", 'C', "ccccc", 5) => 0;
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
struct lfs2_attr attrs1[] = {
|
||||
{'B', "gggg", 4},
|
||||
{'C', "", 0},
|
||||
{'D', "hhhh", 4},
|
||||
};
|
||||
struct lfs2_file_config cfg1 = {.attrs=attrs1, .attr_count=3};
|
||||
|
||||
lfs2_file_opencfg(&lfs2, &file, "hello/hello", LFS2_O_WRONLY, &cfg1) => 0;
|
||||
|
||||
lfs2_getattr(&lfs2, "hello/hello", 'B', buffer, 9) => 9;
|
||||
lfs2_getattr(&lfs2, "hello/hello", 'C', buffer+9, 9) => 5;
|
||||
lfs2_getattr(&lfs2, "hello/hello", 'D', buffer+18, 9) => LFS2_ERR_NOATTR;
|
||||
memcmp(buffer, "fffffffff", 9) => 0;
|
||||
memcmp(buffer+9, "ccccc\0\0\0\0", 9) => 0;
|
||||
memcmp(buffer+18, "\0\0\0\0\0\0\0\0\0", 9) => 0;
|
||||
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
lfs2_getattr(&lfs2, "hello/hello", 'B', buffer, 9) => 4;
|
||||
lfs2_getattr(&lfs2, "hello/hello", 'C', buffer+9, 9) => 0;
|
||||
lfs2_getattr(&lfs2, "hello/hello", 'D', buffer+18, 9) => 4;
|
||||
memcmp(buffer, "gggg\0\0\0\0\0", 9) => 0;
|
||||
memcmp(buffer+9, "\0\0\0\0\0\0\0\0\0", 9) => 0;
|
||||
memcmp(buffer+18, "hhhh\0\0\0\0\0", 9) => 0;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
|
@ -0,0 +1,241 @@
|
|||
# bad blocks with block cycles should be tested in test_relocations
|
||||
if = 'LFS2_BLOCK_CYCLES == -1'
|
||||
|
||||
[[case]] # single bad blocks
|
||||
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS2_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS2_ERASE_VALUE = [0x00, 0xff, -1]
|
||||
define.LFS2_BADBLOCK_BEHAVIOR = [
|
||||
'LFS2_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.NAMEMULT = 64
|
||||
define.FILEMULT = 1
|
||||
code = '''
|
||||
for (lfs2_block_t badblock = 2; badblock < LFS2_BLOCK_COUNT; badblock++) {
|
||||
lfs2_testbd_setwear(&cfg, badblock-1, 0) => 0;
|
||||
lfs2_testbd_setwear(&cfg, badblock, 0xffffffff) => 0;
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs2_mkdir(&lfs2, (char*)buffer) => 0;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs2_file_open(&lfs2, &file, (char*)buffer,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs2_stat(&lfs2, (char*)buffer, &info) => 0;
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
uint8_t rbuffer[1024];
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(buffer, rbuffer, size) => 0;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
[[case]] # region corruption (causes cascading failures)
|
||||
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS2_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS2_ERASE_VALUE = [0x00, 0xff, -1]
|
||||
define.LFS2_BADBLOCK_BEHAVIOR = [
|
||||
'LFS2_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.NAMEMULT = 64
|
||||
define.FILEMULT = 1
|
||||
code = '''
|
||||
for (lfs2_block_t i = 0; i < (LFS2_BLOCK_COUNT-2)/2; i++) {
|
||||
lfs2_testbd_setwear(&cfg, i+2, 0xffffffff) => 0;
|
||||
}
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs2_mkdir(&lfs2, (char*)buffer) => 0;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs2_file_open(&lfs2, &file, (char*)buffer,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs2_stat(&lfs2, (char*)buffer, &info) => 0;
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
uint8_t rbuffer[1024];
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(buffer, rbuffer, size) => 0;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # alternating corruption (causes cascading failures)
|
||||
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS2_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS2_ERASE_VALUE = [0x00, 0xff, -1]
|
||||
define.LFS2_BADBLOCK_BEHAVIOR = [
|
||||
'LFS2_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.NAMEMULT = 64
|
||||
define.FILEMULT = 1
|
||||
code = '''
|
||||
for (lfs2_block_t i = 0; i < (LFS2_BLOCK_COUNT-2)/2; i++) {
|
||||
lfs2_testbd_setwear(&cfg, (2*i) + 2, 0xffffffff) => 0;
|
||||
}
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs2_mkdir(&lfs2, (char*)buffer) => 0;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs2_file_open(&lfs2, &file, (char*)buffer,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 1; i < 10; i++) {
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j] = '0'+i;
|
||||
}
|
||||
buffer[NAMEMULT] = '\0';
|
||||
lfs2_stat(&lfs2, (char*)buffer, &info) => 0;
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
|
||||
buffer[NAMEMULT] = '/';
|
||||
for (int j = 0; j < NAMEMULT; j++) {
|
||||
buffer[j+NAMEMULT+1] = '0'+i;
|
||||
}
|
||||
buffer[2*NAMEMULT+1] = '\0';
|
||||
lfs2_file_open(&lfs2, &file, (char*)buffer, LFS2_O_RDONLY) => 0;
|
||||
|
||||
size = NAMEMULT;
|
||||
for (int j = 0; j < i*FILEMULT; j++) {
|
||||
uint8_t rbuffer[1024];
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(buffer, rbuffer, size) => 0;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
# other corner cases
|
||||
[[case]] # bad superblocks (corrupt 1 or 0)
|
||||
define.LFS2_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS2_ERASE_VALUE = [0x00, 0xff, -1]
|
||||
define.LFS2_BADBLOCK_BEHAVIOR = [
|
||||
'LFS2_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
code = '''
|
||||
lfs2_testbd_setwear(&cfg, 0, 0xffffffff) => 0;
|
||||
lfs2_testbd_setwear(&cfg, 1, 0xffffffff) => 0;
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => LFS2_ERR_NOSPC;
|
||||
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
|
||||
'''
|
|
@ -0,0 +1,838 @@
|
|||
[[case]] # root
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # many directory creation
|
||||
define.N = 'range(0, 100, 3)'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "dir%03d", i);
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "dir%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # many directory removal
|
||||
define.N = 'range(3, 100, 11)'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "removeme%03d", i);
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "removeme%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "removeme%03d", i);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # many directory rename
|
||||
define.N = 'range(3, 100, 11)'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "test%03d", i);
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "test%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
char oldpath[128];
|
||||
char newpath[128];
|
||||
sprintf(oldpath, "test%03d", i);
|
||||
sprintf(newpath, "tedd%03d", i);
|
||||
lfs2_rename(&lfs2, oldpath, newpath) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "tedd%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2);
|
||||
'''
|
||||
|
||||
[[case]] # reentrant many directory creation/rename/removal
|
||||
define.N = [5, 11]
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hi%03d", i);
|
||||
err = lfs2_mkdir(&lfs2, path);
|
||||
assert(err == 0 || err == LFS2_ERR_EXIST);
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hello%03d", i);
|
||||
err = lfs2_remove(&lfs2, path);
|
||||
assert(err == 0 || err == LFS2_ERR_NOENT);
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hi%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
char oldpath[128];
|
||||
char newpath[128];
|
||||
sprintf(oldpath, "hi%03d", i);
|
||||
sprintf(newpath, "hello%03d", i);
|
||||
// YES this can overwrite an existing newpath
|
||||
lfs2_rename(&lfs2, oldpath, newpath) => 0;
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hello%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hello%03d", i);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # file creation
|
||||
define.N = 'range(3, 100, 11)'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "file%03d", i);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "file%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2);
|
||||
'''
|
||||
|
||||
[[case]] # file removal
|
||||
define.N = 'range(0, 100, 3)'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "removeme%03d", i);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "removeme%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "removeme%03d", i);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # file rename
|
||||
define.N = 'range(0, 100, 3)'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "test%03d", i);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "test%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
char oldpath[128];
|
||||
char newpath[128];
|
||||
sprintf(oldpath, "test%03d", i);
|
||||
sprintf(newpath, "tedd%03d", i);
|
||||
lfs2_rename(&lfs2, oldpath, newpath) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "tedd%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2);
|
||||
'''
|
||||
|
||||
[[case]] # reentrant file creation/rename/removal
|
||||
define.N = [5, 25]
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hi%03d", i);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hello%03d", i);
|
||||
err = lfs2_remove(&lfs2, path);
|
||||
assert(err == 0 || err == LFS2_ERR_NOENT);
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hi%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
char oldpath[128];
|
||||
char newpath[128];
|
||||
sprintf(oldpath, "hi%03d", i);
|
||||
sprintf(newpath, "hello%03d", i);
|
||||
// YES this can overwrite an existing newpath
|
||||
lfs2_rename(&lfs2, oldpath, newpath) => 0;
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hello%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "hello%03d", i);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # nested directories
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "potato") => 0;
|
||||
lfs2_file_open(&lfs2, &file, "burito",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "potato/baked") => 0;
|
||||
lfs2_mkdir(&lfs2, "potato/sweet") => 0;
|
||||
lfs2_mkdir(&lfs2, "potato/fried") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "potato") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "baked") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "fried") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "sweet") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// try removing?
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_remove(&lfs2, "potato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// try renaming?
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_rename(&lfs2, "potato", "coldpotato") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_rename(&lfs2, "coldpotato", "warmpotato") => 0;
|
||||
lfs2_rename(&lfs2, "warmpotato", "hotpotato") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_remove(&lfs2, "potato") => LFS2_ERR_NOENT;
|
||||
lfs2_remove(&lfs2, "coldpotato") => LFS2_ERR_NOENT;
|
||||
lfs2_remove(&lfs2, "warmpotato") => LFS2_ERR_NOENT;
|
||||
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// try cross-directory renaming
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "coldpotato") => 0;
|
||||
lfs2_rename(&lfs2, "hotpotato/baked", "coldpotato/baked") => 0;
|
||||
lfs2_rename(&lfs2, "coldpotato", "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_remove(&lfs2, "coldpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_rename(&lfs2, "hotpotato/fried", "coldpotato/fried") => 0;
|
||||
lfs2_rename(&lfs2, "coldpotato", "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_remove(&lfs2, "coldpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_rename(&lfs2, "hotpotato/sweet", "coldpotato/sweet") => 0;
|
||||
lfs2_rename(&lfs2, "coldpotato", "hotpotato") => 0;
|
||||
lfs2_remove(&lfs2, "coldpotato") => LFS2_ERR_NOENT;
|
||||
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "hotpotato") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "baked") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "fried") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "sweet") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// final remove
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_remove(&lfs2, "hotpotato/baked") => 0;
|
||||
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_remove(&lfs2, "hotpotato/fried") => 0;
|
||||
lfs2_remove(&lfs2, "hotpotato") => LFS2_ERR_NOTEMPTY;
|
||||
lfs2_remove(&lfs2, "hotpotato/sweet") => 0;
|
||||
lfs2_remove(&lfs2, "hotpotato") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
info.type => LFS2_TYPE_DIR;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "burito") == 0);
|
||||
info.type => LFS2_TYPE_REG;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # recursive remove
|
||||
define.N = [10, 100]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "prickly-pear") => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "prickly-pear/cactus%03d", i);
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_dir_open(&lfs2, &dir, "prickly-pear") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "cactus%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2);
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_remove(&lfs2, "prickly-pear") => LFS2_ERR_NOTEMPTY;
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "prickly-pear") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "cactus%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
sprintf(path, "prickly-pear/%s", info.name);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
lfs2_remove(&lfs2, "prickly-pear") => 0;
|
||||
lfs2_remove(&lfs2, "prickly-pear") => LFS2_ERR_NOENT;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_remove(&lfs2, "prickly-pear") => LFS2_ERR_NOENT;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # other error cases
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "potato") => 0;
|
||||
lfs2_file_open(&lfs2, &file, "burito",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mkdir(&lfs2, "potato") => LFS2_ERR_EXIST;
|
||||
lfs2_mkdir(&lfs2, "burito") => LFS2_ERR_EXIST;
|
||||
lfs2_file_open(&lfs2, &file, "burito",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => LFS2_ERR_EXIST;
|
||||
lfs2_file_open(&lfs2, &file, "potato",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => LFS2_ERR_EXIST;
|
||||
lfs2_dir_open(&lfs2, &dir, "tomato") => LFS2_ERR_NOENT;
|
||||
lfs2_dir_open(&lfs2, &dir, "burito") => LFS2_ERR_NOTDIR;
|
||||
lfs2_file_open(&lfs2, &file, "tomato", LFS2_O_RDONLY) => LFS2_ERR_NOENT;
|
||||
lfs2_file_open(&lfs2, &file, "potato", LFS2_O_RDONLY) => LFS2_ERR_ISDIR;
|
||||
lfs2_file_open(&lfs2, &file, "tomato", LFS2_O_WRONLY) => LFS2_ERR_NOENT;
|
||||
lfs2_file_open(&lfs2, &file, "potato", LFS2_O_WRONLY) => LFS2_ERR_ISDIR;
|
||||
lfs2_file_open(&lfs2, &file, "potato",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => LFS2_ERR_ISDIR;
|
||||
|
||||
lfs2_mkdir(&lfs2, "/") => LFS2_ERR_EXIST;
|
||||
lfs2_file_open(&lfs2, &file, "/",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => LFS2_ERR_EXIST;
|
||||
lfs2_file_open(&lfs2, &file, "/", LFS2_O_RDONLY) => LFS2_ERR_ISDIR;
|
||||
lfs2_file_open(&lfs2, &file, "/", LFS2_O_WRONLY) => LFS2_ERR_ISDIR;
|
||||
lfs2_file_open(&lfs2, &file, "/",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => LFS2_ERR_ISDIR;
|
||||
|
||||
// check that errors did not corrupt directory
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(strcmp(info.name, "burito") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "potato") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// or on disk
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(strcmp(info.name, "burito") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
assert(strcmp(info.name, "potato") == 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # directory seek
|
||||
define.COUNT = [4, 128, 132]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "hello") => 0;
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "hello/kitty%03d", i);
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
for (int j = 2; j < COUNT; j++) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "hello") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_soff_t pos;
|
||||
for (int i = 0; i < j; i++) {
|
||||
sprintf(path, "kitty%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
pos = lfs2_dir_tell(&lfs2, &dir);
|
||||
assert(pos >= 0);
|
||||
}
|
||||
|
||||
lfs2_dir_seek(&lfs2, &dir, pos) => 0;
|
||||
sprintf(path, "kitty%03d", j);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_dir_rewind(&lfs2, &dir) => 0;
|
||||
sprintf(path, "kitty%03d", 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_dir_seek(&lfs2, &dir, pos) => 0;
|
||||
sprintf(path, "kitty%03d", j);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
}
|
||||
'''
|
||||
|
||||
[[case]] # root seek
|
||||
define.COUNT = [4, 128, 132]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "hi%03d", i);
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
for (int j = 2; j < COUNT; j++) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_soff_t pos;
|
||||
for (int i = 0; i < j; i++) {
|
||||
sprintf(path, "hi%03d", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
pos = lfs2_dir_tell(&lfs2, &dir);
|
||||
assert(pos >= 0);
|
||||
}
|
||||
|
||||
lfs2_dir_seek(&lfs2, &dir, pos) => 0;
|
||||
sprintf(path, "hi%03d", j);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_dir_rewind(&lfs2, &dir) => 0;
|
||||
sprintf(path, "hi%03d", 0);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_dir_seek(&lfs2, &dir, pos) => 0;
|
||||
sprintf(path, "hi%03d", j);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
}
|
||||
'''
|
||||
|
|
@ -0,0 +1,611 @@
|
|||
# These tests are for some specific corner cases with neighboring inline files.
|
||||
# Note that these tests are intended for 512 byte inline sizes. They should
|
||||
# still pass with other inline sizes but wouldn't be testing anything.
|
||||
|
||||
define.LFS2_CACHE_SIZE = 512
|
||||
if = 'LFS2_CACHE_SIZE % LFS2_PROG_SIZE == 0 && LFS2_CACHE_SIZE == 512'
|
||||
|
||||
[[case]] # entry grow test
|
||||
code = '''
|
||||
uint8_t wbuffer[1024];
|
||||
uint8_t rbuffer[1024];
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// write hi0 20
|
||||
sprintf(path, "hi0"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 20
|
||||
sprintf(path, "hi1"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi2 20
|
||||
sprintf(path, "hi2"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi3 20
|
||||
sprintf(path, "hi3"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi1 20
|
||||
sprintf(path, "hi1"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi0 20
|
||||
sprintf(path, "hi0"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi2 20
|
||||
sprintf(path, "hi2"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi3 20
|
||||
sprintf(path, "hi3"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # entry shrink test
|
||||
code = '''
|
||||
uint8_t wbuffer[1024];
|
||||
uint8_t rbuffer[1024];
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// write hi0 20
|
||||
sprintf(path, "hi0"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi2 20
|
||||
sprintf(path, "hi2"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi3 20
|
||||
sprintf(path, "hi3"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 20
|
||||
sprintf(path, "hi1"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi0 20
|
||||
sprintf(path, "hi0"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi1 20
|
||||
sprintf(path, "hi1"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi2 20
|
||||
sprintf(path, "hi2"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi3 20
|
||||
sprintf(path, "hi3"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # entry spill test
|
||||
code = '''
|
||||
uint8_t wbuffer[1024];
|
||||
uint8_t rbuffer[1024];
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// write hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi2 200
|
||||
sprintf(path, "hi2"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi2 200
|
||||
sprintf(path, "hi2"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # entry push spill test
|
||||
code = '''
|
||||
uint8_t wbuffer[1024];
|
||||
uint8_t rbuffer[1024];
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// write hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 20
|
||||
sprintf(path, "hi1"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi2 200
|
||||
sprintf(path, "hi2"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi1 20
|
||||
sprintf(path, "hi1"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi2 200
|
||||
sprintf(path, "hi2"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # entry push spill two test
|
||||
code = '''
|
||||
uint8_t wbuffer[1024];
|
||||
uint8_t rbuffer[1024];
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// write hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 20
|
||||
sprintf(path, "hi1"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi2 200
|
||||
sprintf(path, "hi2"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi4 200
|
||||
sprintf(path, "hi4"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi1 20
|
||||
sprintf(path, "hi1"); size = 20;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi2 200
|
||||
sprintf(path, "hi2"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi4 200
|
||||
sprintf(path, "hi4"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # entry drop test
|
||||
code = '''
|
||||
uint8_t wbuffer[1024];
|
||||
uint8_t rbuffer[1024];
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
// write hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi1 200
|
||||
sprintf(path, "hi1"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi2 200
|
||||
sprintf(path, "hi2"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// write hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_remove(&lfs2, "hi1") => 0;
|
||||
lfs2_stat(&lfs2, "hi1", &info) => LFS2_ERR_NOENT;
|
||||
// read hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi2 200
|
||||
sprintf(path, "hi2"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_remove(&lfs2, "hi2") => 0;
|
||||
lfs2_stat(&lfs2, "hi2", &info) => LFS2_ERR_NOENT;
|
||||
// read hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// read hi3 200
|
||||
sprintf(path, "hi3"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_remove(&lfs2, "hi3") => 0;
|
||||
lfs2_stat(&lfs2, "hi3", &info) => LFS2_ERR_NOENT;
|
||||
// read hi0 200
|
||||
sprintf(path, "hi0"); size = 200;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_remove(&lfs2, "hi0") => 0;
|
||||
lfs2_stat(&lfs2, "hi0", &info) => LFS2_ERR_NOENT;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # create too big
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
memset(path, 'm', 200);
|
||||
path[200] = '\0';
|
||||
|
||||
size = 400;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
uint8_t wbuffer[1024];
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
size = 400;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
uint8_t rbuffer[1024];
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # resize too big
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
memset(path, 'm', 200);
|
||||
path[200] = '\0';
|
||||
|
||||
size = 40;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
uint8_t wbuffer[1024];
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
size = 40;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
uint8_t rbuffer[1024];
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
size = 400;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
memset(wbuffer, 'c', size);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
size = 400;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
memcmp(rbuffer, wbuffer, size) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
|
@ -0,0 +1,288 @@
|
|||
# Tests for recovering from conditions which shouldn't normally
|
||||
# happen during normal operation of littlefs
|
||||
|
||||
# invalid pointer tests (outside of block_count)
|
||||
|
||||
[[case]] # invalid tail-pointer test
|
||||
define.TAIL_TYPE = ['LFS2_TYPE_HARDTAIL', 'LFS2_TYPE_SOFTTAIL']
|
||||
define.INVALSET = [0x3, 0x1, 0x2]
|
||||
in = "lfs2.c"
|
||||
code = '''
|
||||
// create littlefs
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
// change tail-pointer to invalid pointers
|
||||
lfs2_init(&lfs2, &cfg) => 0;
|
||||
lfs2_mdir_t mdir;
|
||||
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
|
||||
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
|
||||
{LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8),
|
||||
(lfs2_block_t[2]){
|
||||
(INVALSET & 0x1) ? 0xcccccccc : 0,
|
||||
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
|
||||
lfs2_deinit(&lfs2) => 0;
|
||||
|
||||
// test that mount fails gracefully
|
||||
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
|
||||
'''
|
||||
|
||||
[[case]] # invalid dir pointer test
|
||||
define.INVALSET = [0x3, 0x1, 0x2]
|
||||
in = "lfs2.c"
|
||||
code = '''
|
||||
// create littlefs
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
// make a dir
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "dir_here") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// change the dir pointer to be invalid
|
||||
lfs2_init(&lfs2, &cfg) => 0;
|
||||
lfs2_mdir_t mdir;
|
||||
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
|
||||
// make sure id 1 == our directory
|
||||
lfs2_dir_get(&lfs2, &mdir,
|
||||
LFS2_MKTAG(0x700, 0x3ff, 0),
|
||||
LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("dir_here")), buffer)
|
||||
=> LFS2_MKTAG(LFS2_TYPE_DIR, 1, strlen("dir_here"));
|
||||
assert(memcmp((char*)buffer, "dir_here", strlen("dir_here")) == 0);
|
||||
// change dir pointer
|
||||
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
|
||||
{LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, 8),
|
||||
(lfs2_block_t[2]){
|
||||
(INVALSET & 0x1) ? 0xcccccccc : 0,
|
||||
(INVALSET & 0x2) ? 0xcccccccc : 0}})) => 0;
|
||||
lfs2_deinit(&lfs2) => 0;
|
||||
|
||||
// test that accessing our bad dir fails, note there's a number
|
||||
// of ways to access the dir, some can fail, but some don't
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "dir_here", &info) => 0;
|
||||
assert(strcmp(info.name, "dir_here") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "dir_here") => LFS2_ERR_CORRUPT;
|
||||
lfs2_stat(&lfs2, "dir_here/file_here", &info) => LFS2_ERR_CORRUPT;
|
||||
lfs2_dir_open(&lfs2, &dir, "dir_here/dir_here") => LFS2_ERR_CORRUPT;
|
||||
lfs2_file_open(&lfs2, &file, "dir_here/file_here",
|
||||
LFS2_O_RDONLY) => LFS2_ERR_CORRUPT;
|
||||
lfs2_file_open(&lfs2, &file, "dir_here/file_here",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => LFS2_ERR_CORRUPT;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # invalid file pointer test
|
||||
in = "lfs2.c"
|
||||
define.SIZE = [10, 1000, 100000] # faked file size
|
||||
code = '''
|
||||
// create littlefs
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
// make a file
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "file_here",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// change the file pointer to be invalid
|
||||
lfs2_init(&lfs2, &cfg) => 0;
|
||||
lfs2_mdir_t mdir;
|
||||
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
|
||||
// make sure id 1 == our file
|
||||
lfs2_dir_get(&lfs2, &mdir,
|
||||
LFS2_MKTAG(0x700, 0x3ff, 0),
|
||||
LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("file_here")), buffer)
|
||||
=> LFS2_MKTAG(LFS2_TYPE_REG, 1, strlen("file_here"));
|
||||
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
|
||||
// change file pointer
|
||||
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
|
||||
{LFS2_MKTAG(LFS2_TYPE_CTZSTRUCT, 1, sizeof(struct lfs2_ctz)),
|
||||
&(struct lfs2_ctz){0xcccccccc, lfs2_tole32(SIZE)}})) => 0;
|
||||
lfs2_deinit(&lfs2) => 0;
|
||||
|
||||
// test that accessing our bad file fails, note there's a number
|
||||
// of ways to access the dir, some can fail, but some don't
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "file_here", &info) => 0;
|
||||
assert(strcmp(info.name, "file_here") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "file_here", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, SIZE) => LFS2_ERR_CORRUPT;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// any allocs that traverse CTZ must unfortunately must fail
|
||||
if (SIZE > 2*LFS2_BLOCK_SIZE) {
|
||||
lfs2_mkdir(&lfs2, "dir_here") => LFS2_ERR_CORRUPT;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # invalid pointer in CTZ skip-list test
|
||||
define.SIZE = ['2*LFS2_BLOCK_SIZE', '3*LFS2_BLOCK_SIZE', '4*LFS2_BLOCK_SIZE']
|
||||
in = "lfs2.c"
|
||||
code = '''
|
||||
// create littlefs
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
// make a file
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "file_here",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
char c = 'c';
|
||||
lfs2_file_write(&lfs2, &file, &c, 1) => 1;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
// change pointer in CTZ skip-list to be invalid
|
||||
lfs2_init(&lfs2, &cfg) => 0;
|
||||
lfs2_mdir_t mdir;
|
||||
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
|
||||
// make sure id 1 == our file and get our CTZ structure
|
||||
lfs2_dir_get(&lfs2, &mdir,
|
||||
LFS2_MKTAG(0x700, 0x3ff, 0),
|
||||
LFS2_MKTAG(LFS2_TYPE_NAME, 1, strlen("file_here")), buffer)
|
||||
=> LFS2_MKTAG(LFS2_TYPE_REG, 1, strlen("file_here"));
|
||||
assert(memcmp((char*)buffer, "file_here", strlen("file_here")) == 0);
|
||||
struct lfs2_ctz ctz;
|
||||
lfs2_dir_get(&lfs2, &mdir,
|
||||
LFS2_MKTAG(0x700, 0x3ff, 0),
|
||||
LFS2_MKTAG(LFS2_TYPE_STRUCT, 1, sizeof(struct lfs2_ctz)), &ctz)
|
||||
=> LFS2_MKTAG(LFS2_TYPE_CTZSTRUCT, 1, sizeof(struct lfs2_ctz));
|
||||
lfs2_ctz_fromle32(&ctz);
|
||||
// rewrite block to contain bad pointer
|
||||
uint8_t bbuffer[LFS2_BLOCK_SIZE];
|
||||
cfg.read(&cfg, ctz.head, 0, bbuffer, LFS2_BLOCK_SIZE) => 0;
|
||||
uint32_t bad = lfs2_tole32(0xcccccccc);
|
||||
memcpy(&bbuffer[0], &bad, sizeof(bad));
|
||||
memcpy(&bbuffer[4], &bad, sizeof(bad));
|
||||
cfg.erase(&cfg, ctz.head) => 0;
|
||||
cfg.prog(&cfg, ctz.head, 0, bbuffer, LFS2_BLOCK_SIZE) => 0;
|
||||
lfs2_deinit(&lfs2) => 0;
|
||||
|
||||
// test that accessing our bad file fails, note there's a number
|
||||
// of ways to access the dir, some can fail, but some don't
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "file_here", &info) => 0;
|
||||
assert(strcmp(info.name, "file_here") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "file_here", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, SIZE) => LFS2_ERR_CORRUPT;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// any allocs that traverse CTZ must unfortunately must fail
|
||||
if (SIZE > 2*LFS2_BLOCK_SIZE) {
|
||||
lfs2_mkdir(&lfs2, "dir_here") => LFS2_ERR_CORRUPT;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
|
||||
[[case]] # invalid gstate pointer
|
||||
define.INVALSET = [0x3, 0x1, 0x2]
|
||||
in = "lfs2.c"
|
||||
code = '''
|
||||
// create littlefs
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
// create an invalid gstate
|
||||
lfs2_init(&lfs2, &cfg) => 0;
|
||||
lfs2_mdir_t mdir;
|
||||
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
|
||||
lfs2_fs_prepmove(&lfs2, 1, (lfs2_block_t [2]){
|
||||
(INVALSET & 0x1) ? 0xcccccccc : 0,
|
||||
(INVALSET & 0x2) ? 0xcccccccc : 0});
|
||||
lfs2_dir_commit(&lfs2, &mdir, NULL, 0) => 0;
|
||||
lfs2_deinit(&lfs2) => 0;
|
||||
|
||||
// test that mount fails gracefully
|
||||
// mount may not fail, but our first alloc should fail when
|
||||
// we try to fix the gstate
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "should_fail") => LFS2_ERR_CORRUPT;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
# cycle detection/recovery tests
|
||||
|
||||
[[case]] # metadata-pair threaded-list loop test
|
||||
in = "lfs2.c"
|
||||
code = '''
|
||||
// create littlefs
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
// change tail-pointer to point to ourself
|
||||
lfs2_init(&lfs2, &cfg) => 0;
|
||||
lfs2_mdir_t mdir;
|
||||
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
|
||||
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
|
||||
{LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8),
|
||||
(lfs2_block_t[2]){0, 1}})) => 0;
|
||||
lfs2_deinit(&lfs2) => 0;
|
||||
|
||||
// test that mount fails gracefully
|
||||
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
|
||||
'''
|
||||
|
||||
[[case]] # metadata-pair threaded-list 2-length loop test
|
||||
in = "lfs2.c"
|
||||
code = '''
|
||||
// create littlefs with child dir
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "child") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// find child
|
||||
lfs2_init(&lfs2, &cfg) => 0;
|
||||
lfs2_mdir_t mdir;
|
||||
lfs2_block_t pair[2];
|
||||
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
|
||||
lfs2_dir_get(&lfs2, &mdir,
|
||||
LFS2_MKTAG(0x7ff, 0x3ff, 0),
|
||||
LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
|
||||
=> LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair));
|
||||
lfs2_pair_fromle32(pair);
|
||||
// change tail-pointer to point to root
|
||||
lfs2_dir_fetch(&lfs2, &mdir, pair) => 0;
|
||||
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
|
||||
{LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8),
|
||||
(lfs2_block_t[2]){0, 1}})) => 0;
|
||||
lfs2_deinit(&lfs2) => 0;
|
||||
|
||||
// test that mount fails gracefully
|
||||
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
|
||||
'''
|
||||
|
||||
[[case]] # metadata-pair threaded-list 1-length child loop test
|
||||
in = "lfs2.c"
|
||||
code = '''
|
||||
// create littlefs with child dir
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "child") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// find child
|
||||
lfs2_init(&lfs2, &cfg) => 0;
|
||||
lfs2_mdir_t mdir;
|
||||
lfs2_block_t pair[2];
|
||||
lfs2_dir_fetch(&lfs2, &mdir, (lfs2_block_t[2]){0, 1}) => 0;
|
||||
lfs2_dir_get(&lfs2, &mdir,
|
||||
LFS2_MKTAG(0x7ff, 0x3ff, 0),
|
||||
LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair)), pair)
|
||||
=> LFS2_MKTAG(LFS2_TYPE_DIRSTRUCT, 1, sizeof(pair));
|
||||
lfs2_pair_fromle32(pair);
|
||||
// change tail-pointer to point to ourself
|
||||
lfs2_dir_fetch(&lfs2, &mdir, pair) => 0;
|
||||
lfs2_dir_commit(&lfs2, &mdir, LFS2_MKATTRS(
|
||||
{LFS2_MKTAG(LFS2_TYPE_HARDTAIL, 0x3ff, 8), pair})) => 0;
|
||||
lfs2_deinit(&lfs2) => 0;
|
||||
|
||||
// test that mount fails gracefully
|
||||
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
|
||||
'''
|
|
@ -0,0 +1,465 @@
|
|||
[[case]] # test running a filesystem to exhaustion
|
||||
define.LFS2_ERASE_CYCLES = 10
|
||||
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS2_BLOCK_CYCLES = 'LFS2_ERASE_CYCLES / 2'
|
||||
define.LFS2_BADBLOCK_BEHAVIOR = [
|
||||
'LFS2_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "roadrunner") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
uint32_t cycle = 0;
|
||||
while (true) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// chose name, roughly random seed, and random 2^n size
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << ((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
|
||||
assert(res == 1 || res == LFS2_ERR_NOSPC);
|
||||
if (res == LFS2_ERR_NOSPC) {
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
if (err == LFS2_ERR_NOSPC) {
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << ((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
char r;
|
||||
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
|
||||
assert(r == c);
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
cycle += 1;
|
||||
}
|
||||
|
||||
exhausted:
|
||||
// should still be readable
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
LFS2_WARN("completed %d cycles", cycle);
|
||||
'''
|
||||
|
||||
[[case]] # test running a filesystem to exhaustion
|
||||
# which also requires expanding superblocks
|
||||
define.LFS2_ERASE_CYCLES = 10
|
||||
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS2_BLOCK_CYCLES = 'LFS2_ERASE_CYCLES / 2'
|
||||
define.LFS2_BADBLOCK_BEHAVIOR = [
|
||||
'LFS2_TESTBD_BADBLOCK_PROGERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASEERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_READERROR',
|
||||
'LFS2_TESTBD_BADBLOCK_PROGNOOP',
|
||||
'LFS2_TESTBD_BADBLOCK_ERASENOOP',
|
||||
]
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
uint32_t cycle = 0;
|
||||
while (true) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// chose name, roughly random seed, and random 2^n size
|
||||
sprintf(path, "test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << ((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
|
||||
assert(res == 1 || res == LFS2_ERR_NOSPC);
|
||||
if (res == LFS2_ERR_NOSPC) {
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
if (err == LFS2_ERR_NOSPC) {
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << ((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
char r;
|
||||
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
|
||||
assert(r == c);
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
cycle += 1;
|
||||
}
|
||||
|
||||
exhausted:
|
||||
// should still be readable
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "test%d", i);
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
LFS2_WARN("completed %d cycles", cycle);
|
||||
'''
|
||||
|
||||
# These are a sort of high-level litmus test for wear-leveling. One definition
|
||||
# of wear-leveling is that increasing a block device's space translates directly
|
||||
# into increasing the block devices lifetime. This is something we can actually
|
||||
# check for.
|
||||
|
||||
[[case]] # wear-level test running a filesystem to exhaustion
|
||||
define.LFS2_ERASE_CYCLES = 20
|
||||
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS2_BLOCK_CYCLES = 'LFS2_ERASE_CYCLES / 2'
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
uint32_t run_cycles[2];
|
||||
const uint32_t run_block_count[2] = {LFS2_BLOCK_COUNT/2, LFS2_BLOCK_COUNT};
|
||||
|
||||
for (int run = 0; run < 2; run++) {
|
||||
for (lfs2_block_t b = 0; b < LFS2_BLOCK_COUNT; b++) {
|
||||
lfs2_testbd_setwear(&cfg, b,
|
||||
(b < run_block_count[run]) ? 0 : LFS2_ERASE_CYCLES) => 0;
|
||||
}
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "roadrunner") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
uint32_t cycle = 0;
|
||||
while (true) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// chose name, roughly random seed, and random 2^n size
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << ((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
|
||||
assert(res == 1 || res == LFS2_ERR_NOSPC);
|
||||
if (res == LFS2_ERR_NOSPC) {
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
if (err == LFS2_ERR_NOSPC) {
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << ((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
char r;
|
||||
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
|
||||
assert(r == c);
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
cycle += 1;
|
||||
}
|
||||
|
||||
exhausted:
|
||||
// should still be readable
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
run_cycles[run] = cycle;
|
||||
LFS2_WARN("completed %d blocks %d cycles",
|
||||
run_block_count[run], run_cycles[run]);
|
||||
}
|
||||
|
||||
// check we increased the lifetime by 2x with ~10% error
|
||||
LFS2_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
|
||||
'''
|
||||
|
||||
[[case]] # wear-level test + expanding superblock
|
||||
define.LFS2_ERASE_CYCLES = 20
|
||||
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS2_BLOCK_CYCLES = 'LFS2_ERASE_CYCLES / 2'
|
||||
define.FILES = 10
|
||||
code = '''
|
||||
uint32_t run_cycles[2];
|
||||
const uint32_t run_block_count[2] = {LFS2_BLOCK_COUNT/2, LFS2_BLOCK_COUNT};
|
||||
|
||||
for (int run = 0; run < 2; run++) {
|
||||
for (lfs2_block_t b = 0; b < LFS2_BLOCK_COUNT; b++) {
|
||||
lfs2_testbd_setwear(&cfg, b,
|
||||
(b < run_block_count[run]) ? 0 : LFS2_ERASE_CYCLES) => 0;
|
||||
}
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
uint32_t cycle = 0;
|
||||
while (true) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// chose name, roughly random seed, and random 2^n size
|
||||
sprintf(path, "test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << ((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
|
||||
assert(res == 1 || res == LFS2_ERR_NOSPC);
|
||||
if (res == LFS2_ERR_NOSPC) {
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
if (err == LFS2_ERR_NOSPC) {
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << ((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
char r;
|
||||
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
|
||||
assert(r == c);
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
cycle += 1;
|
||||
}
|
||||
|
||||
exhausted:
|
||||
// should still be readable
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "test%d", i);
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
run_cycles[run] = cycle;
|
||||
LFS2_WARN("completed %d blocks %d cycles",
|
||||
run_block_count[run], run_cycles[run]);
|
||||
}
|
||||
|
||||
// check we increased the lifetime by 2x with ~10% error
|
||||
LFS2_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]);
|
||||
'''
|
||||
|
||||
[[case]] # test that we wear blocks roughly evenly
|
||||
define.LFS2_ERASE_CYCLES = 0xffffffff
|
||||
define.LFS2_BLOCK_COUNT = 256 # small bd so test runs faster
|
||||
define.LFS2_BLOCK_CYCLES = [5, 4, 3, 2, 1]
|
||||
define.CYCLES = 100
|
||||
define.FILES = 10
|
||||
if = 'LFS2_BLOCK_CYCLES < CYCLES/10'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "roadrunner") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
uint32_t cycle = 0;
|
||||
while (cycle < CYCLES) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// chose name, roughly random seed, and random 2^n size
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << 4; //((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
lfs2_ssize_t res = lfs2_file_write(&lfs2, &file, &c, 1);
|
||||
assert(res == 1 || res == LFS2_ERR_NOSPC);
|
||||
if (res == LFS2_ERR_NOSPC) {
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
err = lfs2_file_close(&lfs2, &file);
|
||||
assert(err == 0 || err == LFS2_ERR_NOSPC);
|
||||
if (err == LFS2_ERR_NOSPC) {
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
goto exhausted;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
srand(cycle * i);
|
||||
size = 1 << 4; //((rand() % 10)+2);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
for (lfs2_size_t j = 0; j < size; j++) {
|
||||
char c = 'a' + (rand() % 26);
|
||||
char r;
|
||||
lfs2_file_read(&lfs2, &file, &r, 1) => 1;
|
||||
assert(r == c);
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
cycle += 1;
|
||||
}
|
||||
|
||||
exhausted:
|
||||
// should still be readable
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (uint32_t i = 0; i < FILES; i++) {
|
||||
// check for errors
|
||||
sprintf(path, "roadrunner/test%d", i);
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
LFS2_WARN("completed %d cycles", cycle);
|
||||
|
||||
// check the wear on our block device
|
||||
lfs2_testbd_wear_t minwear = -1;
|
||||
lfs2_testbd_wear_t totalwear = 0;
|
||||
lfs2_testbd_wear_t maxwear = 0;
|
||||
// skip 0 and 1 as superblock movement is intentionally avoided
|
||||
for (lfs2_block_t b = 2; b < LFS2_BLOCK_COUNT; b++) {
|
||||
lfs2_testbd_wear_t wear = lfs2_testbd_getwear(&cfg, b);
|
||||
printf("%08x: wear %d\n", b, wear);
|
||||
assert(wear >= 0);
|
||||
if (wear < minwear) {
|
||||
minwear = wear;
|
||||
}
|
||||
if (wear > maxwear) {
|
||||
maxwear = wear;
|
||||
}
|
||||
totalwear += wear;
|
||||
}
|
||||
lfs2_testbd_wear_t avgwear = totalwear / LFS2_BLOCK_COUNT;
|
||||
LFS2_WARN("max wear: %d cycles", maxwear);
|
||||
LFS2_WARN("avg wear: %d cycles", totalwear / LFS2_BLOCK_COUNT);
|
||||
LFS2_WARN("min wear: %d cycles", minwear);
|
||||
|
||||
// find standard deviation^2
|
||||
lfs2_testbd_wear_t dev2 = 0;
|
||||
for (lfs2_block_t b = 2; b < LFS2_BLOCK_COUNT; b++) {
|
||||
lfs2_testbd_wear_t wear = lfs2_testbd_getwear(&cfg, b);
|
||||
assert(wear >= 0);
|
||||
lfs2_testbd_swear_t diff = wear - avgwear;
|
||||
dev2 += diff*diff;
|
||||
}
|
||||
dev2 /= totalwear;
|
||||
LFS2_WARN("std dev^2: %d", dev2);
|
||||
assert(dev2 < 8);
|
||||
'''
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
|
||||
[[case]] # simple file test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "hello",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
size = strlen("Hello World!")+1;
|
||||
strcpy((char*)buffer, "Hello World!");
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "hello", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(strcmp((char*)buffer, "Hello World!") == 0);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # larger files
|
||||
define.SIZE = [32, 8192, 262144, 0, 7, 8193]
|
||||
define.CHUNKSIZE = [31, 16, 33, 1, 1023]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
// write
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// read
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # rewriting files
|
||||
define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
|
||||
define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
|
||||
define.CHUNKSIZE = [31, 16, 1]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
// write
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// read
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE1;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// rewrite
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_WRONLY) => 0;
|
||||
srand(2);
|
||||
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// read
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => lfs2_max(SIZE1, SIZE2);
|
||||
srand(2);
|
||||
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
if (SIZE1 > SIZE2) {
|
||||
srand(1);
|
||||
for (lfs2_size_t b = 0; b < SIZE2; b++) {
|
||||
rand();
|
||||
}
|
||||
for (lfs2_size_t i = SIZE2; i < SIZE1; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # appending files
|
||||
define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
|
||||
define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
|
||||
define.CHUNKSIZE = [31, 16, 1]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
// write
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// read
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE1;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// append
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_WRONLY | LFS2_O_APPEND) => 0;
|
||||
srand(2);
|
||||
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// read
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE1 + SIZE2;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
srand(2);
|
||||
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # truncating files
|
||||
define.SIZE1 = [32, 8192, 131072, 0, 7, 8193]
|
||||
define.SIZE2 = [32, 8192, 131072, 0, 7, 8193]
|
||||
define.CHUNKSIZE = [31, 16, 1]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
|
||||
// write
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// read
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE1;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE1; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE1-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// truncate
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_WRONLY | LFS2_O_TRUNC) => 0;
|
||||
srand(2);
|
||||
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// read
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE2;
|
||||
srand(2);
|
||||
for (lfs2_size_t i = 0; i < SIZE2; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE2-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # reentrant file writing
|
||||
define.SIZE = [32, 0, 7, 2049]
|
||||
define.CHUNKSIZE = [31, 16, 65]
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
err = lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY);
|
||||
assert(err == LFS2_ERR_NOENT || err == 0);
|
||||
if (err == 0) {
|
||||
// can only be 0 (new file) or full size
|
||||
size = lfs2_file_size(&lfs2, &file);
|
||||
assert(size == 0 || size == SIZE);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
// write
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # reentrant file writing with syncs
|
||||
define = [
|
||||
# append (O(n))
|
||||
{MODE='LFS2_O_APPEND', SIZE=[32, 0, 7, 2049], CHUNKSIZE=[31, 16, 65]},
|
||||
# truncate (O(n^2))
|
||||
{MODE='LFS2_O_TRUNC', SIZE=[32, 0, 7, 200], CHUNKSIZE=[31, 16, 65]},
|
||||
# rewrite (O(n^2))
|
||||
{MODE=0, SIZE=[32, 0, 7, 200], CHUNKSIZE=[31, 16, 65]},
|
||||
]
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
err = lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY);
|
||||
assert(err == LFS2_ERR_NOENT || err == 0);
|
||||
if (err == 0) {
|
||||
// with syncs we could be any size, but it at least must be valid data
|
||||
size = lfs2_file_size(&lfs2, &file);
|
||||
assert(size <= SIZE);
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < size; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, size-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
// write
|
||||
lfs2_file_open(&lfs2, &file, "avacado",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | MODE) => 0;
|
||||
size = lfs2_file_size(&lfs2, &file);
|
||||
assert(size <= SIZE);
|
||||
srand(1);
|
||||
lfs2_size_t skip = (MODE == LFS2_O_APPEND) ? size : 0;
|
||||
for (lfs2_size_t b = 0; b < skip; b++) {
|
||||
rand();
|
||||
}
|
||||
for (lfs2_size_t i = skip; i < SIZE; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
buffer[b] = rand() & 0xff;
|
||||
}
|
||||
lfs2_file_write(&lfs2, &file, buffer, chunk) => chunk;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
// read
|
||||
lfs2_file_open(&lfs2, &file, "avacado", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
srand(1);
|
||||
for (lfs2_size_t i = 0; i < SIZE; i += CHUNKSIZE) {
|
||||
lfs2_size_t chunk = lfs2_min(CHUNKSIZE, SIZE-i);
|
||||
lfs2_file_read(&lfs2, &file, buffer, chunk) => chunk;
|
||||
for (lfs2_size_t b = 0; b < chunk; b++) {
|
||||
assert(buffer[b] == (rand() & 0xff));
|
||||
}
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, CHUNKSIZE) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # many files
|
||||
define.N = 300
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
// create N files of 7 bytes
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "file_%03d", i);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
char wbuffer[1024];
|
||||
size = 7;
|
||||
snprintf(wbuffer, size, "Hi %03d", i);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
char rbuffer[1024];
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
assert(strcmp(rbuffer, wbuffer) == 0);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # many files with power cycle
|
||||
define.N = 300
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
// create N files of 7 bytes
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "file_%03d", i);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
char wbuffer[1024];
|
||||
size = 7;
|
||||
snprintf(wbuffer, size, "Hi %03d", i);
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
char rbuffer[1024];
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
assert(strcmp(rbuffer, wbuffer) == 0);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # many files with power loss
|
||||
define.N = 300
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
// create N files of 7 bytes
|
||||
for (int i = 0; i < N; i++) {
|
||||
sprintf(path, "file_%03d", i);
|
||||
err = lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT);
|
||||
char wbuffer[1024];
|
||||
size = 7;
|
||||
snprintf(wbuffer, size, "Hi %03d", i);
|
||||
if ((lfs2_size_t)lfs2_file_size(&lfs2, &file) != size) {
|
||||
lfs2_file_write(&lfs2, &file, wbuffer, size) => size;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
char rbuffer[1024];
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_read(&lfs2, &file, rbuffer, size) => size;
|
||||
assert(strcmp(rbuffer, wbuffer) == 0);
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
|
@ -0,0 +1,244 @@
|
|||
|
||||
[[case]] # interspersed file test
|
||||
define.SIZE = [10, 100]
|
||||
define.FILES = [4, 10, 26]
|
||||
code = '''
|
||||
lfs2_file_t files[FILES];
|
||||
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
sprintf(path, "%c", alphas[j]);
|
||||
lfs2_file_open(&lfs2, &files[j], path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
lfs2_file_write(&lfs2, &files[j], &alphas[j], 1) => 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
lfs2_file_close(&lfs2, &files[j]);
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
sprintf(path, "%c", alphas[j]);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
sprintf(path, "%c", alphas[j]);
|
||||
lfs2_file_open(&lfs2, &files[j], path, LFS2_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
lfs2_file_read(&lfs2, &files[j], buffer, 1) => 1;
|
||||
assert(buffer[0] == alphas[j]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
lfs2_file_close(&lfs2, &files[j]);
|
||||
}
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # interspersed remove file test
|
||||
define.SIZE = [10, 100]
|
||||
define.FILES = [4, 10, 26]
|
||||
code = '''
|
||||
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
sprintf(path, "%c", alphas[j]);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
lfs2_file_write(&lfs2, &file, &alphas[j], 1) => 1;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "zzz", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
lfs2_file_write(&lfs2, &file, (const void*)"~", 1) => 1;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
|
||||
sprintf(path, "%c", alphas[j]);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "zzz") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(info.size == FILES);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "zzz", LFS2_O_RDONLY) => 0;
|
||||
for (int i = 0; i < FILES; i++) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, 1) => 1;
|
||||
assert(buffer[0] == '~');
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file);
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # remove inconveniently test
|
||||
define.SIZE = [10, 100]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_t files[3];
|
||||
lfs2_file_open(&lfs2, &files[0], "e", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_open(&lfs2, &files[1], "f", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_open(&lfs2, &files[2], "g", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
|
||||
for (int i = 0; i < SIZE/2; i++) {
|
||||
lfs2_file_write(&lfs2, &files[0], (const void*)"e", 1) => 1;
|
||||
lfs2_file_write(&lfs2, &files[1], (const void*)"f", 1) => 1;
|
||||
lfs2_file_write(&lfs2, &files[2], (const void*)"g", 1) => 1;
|
||||
}
|
||||
|
||||
lfs2_remove(&lfs2, "f") => 0;
|
||||
|
||||
for (int i = 0; i < SIZE/2; i++) {
|
||||
lfs2_file_write(&lfs2, &files[0], (const void*)"e", 1) => 1;
|
||||
lfs2_file_write(&lfs2, &files[1], (const void*)"f", 1) => 1;
|
||||
lfs2_file_write(&lfs2, &files[2], (const void*)"g", 1) => 1;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &files[0]);
|
||||
lfs2_file_close(&lfs2, &files[1]);
|
||||
lfs2_file_close(&lfs2, &files[2]);
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "e") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "g") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &files[0], "e", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_open(&lfs2, &files[1], "g", LFS2_O_RDONLY) => 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
lfs2_file_read(&lfs2, &files[0], buffer, 1) => 1;
|
||||
assert(buffer[0] == 'e');
|
||||
lfs2_file_read(&lfs2, &files[1], buffer, 1) => 1;
|
||||
assert(buffer[0] == 'g');
|
||||
}
|
||||
lfs2_file_close(&lfs2, &files[0]);
|
||||
lfs2_file_close(&lfs2, &files[1]);
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # reentrant interspersed file test
|
||||
define.SIZE = [10, 100]
|
||||
define.FILES = [4, 10, 26]
|
||||
reentrant = true
|
||||
code = '''
|
||||
lfs2_file_t files[FILES];
|
||||
const char alphas[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
sprintf(path, "%c", alphas[j]);
|
||||
lfs2_file_open(&lfs2, &files[j], path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
size = lfs2_file_size(&lfs2, &files[j]);
|
||||
assert((int)size >= 0);
|
||||
if ((int)size <= i) {
|
||||
lfs2_file_write(&lfs2, &files[j], &alphas[j], 1) => 1;
|
||||
lfs2_file_sync(&lfs2, &files[j]) => 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
lfs2_file_close(&lfs2, &files[j]);
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "/") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, ".") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, "..") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
sprintf(path, "%c", alphas[j]);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
assert(strcmp(info.name, path) == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
assert(info.size == SIZE);
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
sprintf(path, "%c", alphas[j]);
|
||||
lfs2_file_open(&lfs2, &files[j], path, LFS2_O_RDONLY) => 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
lfs2_file_read(&lfs2, &files[j], buffer, 1) => 1;
|
||||
assert(buffer[0] == alphas[j]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < FILES; j++) {
|
||||
lfs2_file_close(&lfs2, &files[j]);
|
||||
}
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,120 @@
|
|||
[[case]] # orphan test
|
||||
in = "lfs2.c"
|
||||
if = 'LFS2_PROG_SIZE <= 0x3fe' # only works with one crc per commit
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "parent") => 0;
|
||||
lfs2_mkdir(&lfs2, "parent/orphan") => 0;
|
||||
lfs2_mkdir(&lfs2, "parent/child") => 0;
|
||||
lfs2_remove(&lfs2, "parent/orphan") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// corrupt the child's most recent commit, this should be the update
|
||||
// to the linked-list entry, which should orphan the orphan. Note this
|
||||
// makes a lot of assumptions about the remove operation.
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "parent/child") => 0;
|
||||
lfs2_block_t block = dir.m.pair[0];
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
uint8_t bbuffer[LFS2_BLOCK_SIZE];
|
||||
cfg.read(&cfg, block, 0, bbuffer, LFS2_BLOCK_SIZE) => 0;
|
||||
int off = LFS2_BLOCK_SIZE-1;
|
||||
while (off >= 0 && bbuffer[off] == LFS2_ERASE_VALUE) {
|
||||
off -= 1;
|
||||
}
|
||||
memset(&bbuffer[off-3], LFS2_BLOCK_SIZE, 3);
|
||||
cfg.erase(&cfg, block) => 0;
|
||||
cfg.prog(&cfg, block, 0, bbuffer, LFS2_BLOCK_SIZE) => 0;
|
||||
cfg.sync(&cfg) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT;
|
||||
lfs2_stat(&lfs2, "parent/child", &info) => 0;
|
||||
lfs2_fs_size(&lfs2) => 8;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT;
|
||||
lfs2_stat(&lfs2, "parent/child", &info) => 0;
|
||||
lfs2_fs_size(&lfs2) => 8;
|
||||
// this mkdir should both create a dir and deorphan, so size
|
||||
// should be unchanged
|
||||
lfs2_mkdir(&lfs2, "parent/otherchild") => 0;
|
||||
lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT;
|
||||
lfs2_stat(&lfs2, "parent/child", &info) => 0;
|
||||
lfs2_stat(&lfs2, "parent/otherchild", &info) => 0;
|
||||
lfs2_fs_size(&lfs2) => 8;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "parent/orphan", &info) => LFS2_ERR_NOENT;
|
||||
lfs2_stat(&lfs2, "parent/child", &info) => 0;
|
||||
lfs2_stat(&lfs2, "parent/otherchild", &info) => 0;
|
||||
lfs2_fs_size(&lfs2) => 8;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # reentrant testing for orphans, basically just spam mkdir/remove
|
||||
reentrant = true
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)'
|
||||
define = [
|
||||
{FILES=6, DEPTH=1, CYCLES=20},
|
||||
{FILES=26, DEPTH=1, CYCLES=20},
|
||||
{FILES=3, DEPTH=3, CYCLES=20},
|
||||
]
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
srand(1);
|
||||
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
for (int i = 0; i < CYCLES; i++) {
|
||||
// create random path
|
||||
char full_path[256];
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
|
||||
}
|
||||
|
||||
// if it does not exist, we create it, else we destroy
|
||||
int res = lfs2_stat(&lfs2, full_path, &info);
|
||||
if (res == LFS2_ERR_NOENT) {
|
||||
// create each directory in turn, ignore if dir already exists
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
err = lfs2_mkdir(&lfs2, path);
|
||||
assert(!err || err == LFS2_ERR_EXIST);
|
||||
}
|
||||
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
}
|
||||
} else {
|
||||
// is valid dir?
|
||||
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
// try to delete path in reverse order, ignore if dir is not empty
|
||||
for (int d = DEPTH-1; d >= 0; d--) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
err = lfs2_remove(&lfs2, path);
|
||||
assert(!err || err == LFS2_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT;
|
||||
}
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
|
||||
[[case]] # simple path test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "tea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
|
||||
|
||||
lfs2_stat(&lfs2, "tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "/tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
|
||||
lfs2_mkdir(&lfs2, "/milk") => 0;
|
||||
lfs2_stat(&lfs2, "/milk", &info) => 0;
|
||||
assert(strcmp(info.name, "milk") == 0);
|
||||
lfs2_stat(&lfs2, "milk", &info) => 0;
|
||||
assert(strcmp(info.name, "milk") == 0);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # redundant slashes
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "tea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
|
||||
|
||||
lfs2_stat(&lfs2, "/tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "//tea//hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "///tea///hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
|
||||
lfs2_mkdir(&lfs2, "////milk") => 0;
|
||||
lfs2_stat(&lfs2, "////milk", &info) => 0;
|
||||
assert(strcmp(info.name, "milk") == 0);
|
||||
lfs2_stat(&lfs2, "milk", &info) => 0;
|
||||
assert(strcmp(info.name, "milk") == 0);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # dot path test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "tea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
|
||||
|
||||
lfs2_stat(&lfs2, "./tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "/./tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "/././tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "/./tea/./hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
|
||||
lfs2_mkdir(&lfs2, "/./milk") => 0;
|
||||
lfs2_stat(&lfs2, "/./milk", &info) => 0;
|
||||
assert(strcmp(info.name, "milk") == 0);
|
||||
lfs2_stat(&lfs2, "milk", &info) => 0;
|
||||
assert(strcmp(info.name, "milk") == 0);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # dot dot path test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "tea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0;
|
||||
|
||||
lfs2_stat(&lfs2, "coffee/../tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "tea/coldtea/../hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "coffee/coldcoffee/../../tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "coffee/../coffee/../tea/hottea", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
|
||||
lfs2_mkdir(&lfs2, "coffee/../milk") => 0;
|
||||
lfs2_stat(&lfs2, "coffee/../milk", &info) => 0;
|
||||
strcmp(info.name, "milk") => 0;
|
||||
lfs2_stat(&lfs2, "milk", &info) => 0;
|
||||
strcmp(info.name, "milk") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # trailing dot path test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "tea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
|
||||
|
||||
lfs2_stat(&lfs2, "tea/hottea/", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "tea/hottea/.", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "tea/hottea/./.", &info) => 0;
|
||||
assert(strcmp(info.name, "hottea") == 0);
|
||||
lfs2_stat(&lfs2, "tea/hottea/..", &info) => 0;
|
||||
assert(strcmp(info.name, "tea") == 0);
|
||||
lfs2_stat(&lfs2, "tea/hottea/../.", &info) => 0;
|
||||
assert(strcmp(info.name, "tea") == 0);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # leading dot path test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, ".milk") => 0;
|
||||
lfs2_stat(&lfs2, ".milk", &info) => 0;
|
||||
strcmp(info.name, ".milk") => 0;
|
||||
lfs2_stat(&lfs2, "tea/.././.milk", &info) => 0;
|
||||
strcmp(info.name, ".milk") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # root dot dot path test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "tea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/hottea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/warmtea") => 0;
|
||||
lfs2_mkdir(&lfs2, "tea/coldtea") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0;
|
||||
|
||||
lfs2_stat(&lfs2, "coffee/../../../../../../tea/hottea", &info) => 0;
|
||||
strcmp(info.name, "hottea") => 0;
|
||||
|
||||
lfs2_mkdir(&lfs2, "coffee/../../../../../../milk") => 0;
|
||||
lfs2_stat(&lfs2, "coffee/../../../../../../milk", &info) => 0;
|
||||
strcmp(info.name, "milk") => 0;
|
||||
lfs2_stat(&lfs2, "milk", &info) => 0;
|
||||
strcmp(info.name, "milk") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # invalid path tests
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg);
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "dirt", &info) => LFS2_ERR_NOENT;
|
||||
lfs2_stat(&lfs2, "dirt/ground", &info) => LFS2_ERR_NOENT;
|
||||
lfs2_stat(&lfs2, "dirt/ground/earth", &info) => LFS2_ERR_NOENT;
|
||||
|
||||
lfs2_remove(&lfs2, "dirt") => LFS2_ERR_NOENT;
|
||||
lfs2_remove(&lfs2, "dirt/ground") => LFS2_ERR_NOENT;
|
||||
lfs2_remove(&lfs2, "dirt/ground/earth") => LFS2_ERR_NOENT;
|
||||
|
||||
lfs2_mkdir(&lfs2, "dirt/ground") => LFS2_ERR_NOENT;
|
||||
lfs2_file_open(&lfs2, &file, "dirt/ground", LFS2_O_WRONLY | LFS2_O_CREAT)
|
||||
=> LFS2_ERR_NOENT;
|
||||
lfs2_mkdir(&lfs2, "dirt/ground/earth") => LFS2_ERR_NOENT;
|
||||
lfs2_file_open(&lfs2, &file, "dirt/ground/earth", LFS2_O_WRONLY | LFS2_O_CREAT)
|
||||
=> LFS2_ERR_NOENT;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # root operations
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "/", &info) => 0;
|
||||
assert(strcmp(info.name, "/") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
lfs2_mkdir(&lfs2, "/") => LFS2_ERR_EXIST;
|
||||
lfs2_file_open(&lfs2, &file, "/", LFS2_O_WRONLY | LFS2_O_CREAT)
|
||||
=> LFS2_ERR_ISDIR;
|
||||
|
||||
lfs2_remove(&lfs2, "/") => LFS2_ERR_INVAL;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # root representations
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "/", &info) => 0;
|
||||
assert(strcmp(info.name, "/") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_stat(&lfs2, "", &info) => 0;
|
||||
assert(strcmp(info.name, "/") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_stat(&lfs2, ".", &info) => 0;
|
||||
assert(strcmp(info.name, "/") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_stat(&lfs2, "..", &info) => 0;
|
||||
assert(strcmp(info.name, "/") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_stat(&lfs2, "//", &info) => 0;
|
||||
assert(strcmp(info.name, "/") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_stat(&lfs2, "./", &info) => 0;
|
||||
assert(strcmp(info.name, "/") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # superblock conflict test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "littlefs", &info) => LFS2_ERR_NOENT;
|
||||
lfs2_remove(&lfs2, "littlefs") => LFS2_ERR_NOENT;
|
||||
|
||||
lfs2_mkdir(&lfs2, "littlefs") => 0;
|
||||
lfs2_stat(&lfs2, "littlefs", &info) => 0;
|
||||
assert(strcmp(info.name, "littlefs") == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
lfs2_remove(&lfs2, "littlefs") => 0;
|
||||
lfs2_stat(&lfs2, "littlefs", &info) => LFS2_ERR_NOENT;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # max path test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0;
|
||||
|
||||
memset(path, 'w', LFS2_NAME_MAX+1);
|
||||
path[LFS2_NAME_MAX+1] = '\0';
|
||||
lfs2_mkdir(&lfs2, path) => LFS2_ERR_NAMETOOLONG;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT)
|
||||
=> LFS2_ERR_NAMETOOLONG;
|
||||
|
||||
memcpy(path, "coffee/", strlen("coffee/"));
|
||||
memset(path+strlen("coffee/"), 'w', LFS2_NAME_MAX+1);
|
||||
path[strlen("coffee/")+LFS2_NAME_MAX+1] = '\0';
|
||||
lfs2_mkdir(&lfs2, path) => LFS2_ERR_NAMETOOLONG;
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY | LFS2_O_CREAT)
|
||||
=> LFS2_ERR_NAMETOOLONG;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # really big path test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/hotcoffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/warmcoffee") => 0;
|
||||
lfs2_mkdir(&lfs2, "coffee/coldcoffee") => 0;
|
||||
|
||||
memset(path, 'w', LFS2_NAME_MAX);
|
||||
path[LFS2_NAME_MAX] = '\0';
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
|
||||
memcpy(path, "coffee/", strlen("coffee/"));
|
||||
memset(path+strlen("coffee/"), 'w', LFS2_NAME_MAX);
|
||||
path[strlen("coffee/")+LFS2_NAME_MAX] = '\0';
|
||||
lfs2_mkdir(&lfs2, path) => 0;
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
# specific corner cases worth explicitly testing for
|
||||
[[case]] # dangling split dir test
|
||||
define.ITERATIONS = 20
|
||||
define.COUNT = 10
|
||||
define.LFS2_BLOCK_CYCLES = [8, 1]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
// fill up filesystem so only ~16 blocks are left
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "padding", LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
|
||||
memset(buffer, 0, 512);
|
||||
while (LFS2_BLOCK_COUNT - lfs2_fs_size(&lfs2) > 16) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, 512) => 512;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// make a child dir to use in bounded space
|
||||
lfs2_mkdir(&lfs2, "child") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int j = 0; j < ITERATIONS; j++) {
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "child") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
strcmp(info.name, path) => 0;
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
if (j == ITERATIONS-1) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_dir_open(&lfs2, &dir, "child") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
strcmp(info.name, path) => 0;
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # outdated head test
|
||||
define.ITERATIONS = 20
|
||||
define.COUNT = 10
|
||||
define.LFS2_BLOCK_CYCLES = [8, 1]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
// fill up filesystem so only ~16 blocks are left
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "padding", LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
|
||||
memset(buffer, 0, 512);
|
||||
while (LFS2_BLOCK_COUNT - lfs2_fs_size(&lfs2) > 16) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, 512) => 512;
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
// make a child dir to use in bounded space
|
||||
lfs2_mkdir(&lfs2, "child") => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int j = 0; j < ITERATIONS; j++) {
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_CREAT | LFS2_O_WRONLY) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
lfs2_dir_open(&lfs2, &dir, "child") => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
strcmp(info.name, path) => 0;
|
||||
info.size => 0;
|
||||
|
||||
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY) => 0;
|
||||
lfs2_file_write(&lfs2, &file, "hi", 2) => 2;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
|
||||
lfs2_dir_rewind(&lfs2, &dir) => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
strcmp(info.name, path) => 0;
|
||||
info.size => 2;
|
||||
|
||||
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_WRONLY) => 0;
|
||||
lfs2_file_write(&lfs2, &file, "hi", 2) => 2;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
|
||||
lfs2_dir_rewind(&lfs2, &dir) => 0;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 1;
|
||||
strcmp(info.name, path) => 0;
|
||||
info.size => 2;
|
||||
}
|
||||
lfs2_dir_read(&lfs2, &dir, &info) => 0;
|
||||
lfs2_dir_close(&lfs2, &dir) => 0;
|
||||
|
||||
for (int i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "child/test%03d_loooooooooooooooooong_name", i);
|
||||
lfs2_remove(&lfs2, path) => 0;
|
||||
}
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # reentrant testing for relocations, this is the same as the
|
||||
# orphan testing, except here we also set block_cycles so that
|
||||
# almost every tree operation needs a relocation
|
||||
reentrant = true
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)'
|
||||
define = [
|
||||
{FILES=6, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1},
|
||||
{FILES=26, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1},
|
||||
{FILES=3, DEPTH=3, CYCLES=20, LFS2_BLOCK_CYCLES=1},
|
||||
]
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
srand(1);
|
||||
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
for (int i = 0; i < CYCLES; i++) {
|
||||
// create random path
|
||||
char full_path[256];
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
|
||||
}
|
||||
|
||||
// if it does not exist, we create it, else we destroy
|
||||
int res = lfs2_stat(&lfs2, full_path, &info);
|
||||
if (res == LFS2_ERR_NOENT) {
|
||||
// create each directory in turn, ignore if dir already exists
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
err = lfs2_mkdir(&lfs2, path);
|
||||
assert(!err || err == LFS2_ERR_EXIST);
|
||||
}
|
||||
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
}
|
||||
} else {
|
||||
// is valid dir?
|
||||
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
// try to delete path in reverse order, ignore if dir is not empty
|
||||
for (int d = DEPTH-1; d >= 0; d--) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
err = lfs2_remove(&lfs2, path);
|
||||
assert(!err || err == LFS2_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT;
|
||||
}
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # reentrant testing for relocations, but now with random renames!
|
||||
reentrant = true
|
||||
# TODO fix this case, caused by non-DAG trees
|
||||
if = '!(DEPTH == 3 && LFS2_CACHE_SIZE != 64)'
|
||||
define = [
|
||||
{FILES=6, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1},
|
||||
{FILES=26, DEPTH=1, CYCLES=20, LFS2_BLOCK_CYCLES=1},
|
||||
{FILES=3, DEPTH=3, CYCLES=20, LFS2_BLOCK_CYCLES=1},
|
||||
]
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
srand(1);
|
||||
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
for (int i = 0; i < CYCLES; i++) {
|
||||
// create random path
|
||||
char full_path[256];
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
sprintf(&full_path[2*d], "/%c", alpha[rand() % FILES]);
|
||||
}
|
||||
|
||||
// if it does not exist, we create it, else we destroy
|
||||
int res = lfs2_stat(&lfs2, full_path, &info);
|
||||
assert(!res || res == LFS2_ERR_NOENT);
|
||||
if (res == LFS2_ERR_NOENT) {
|
||||
// create each directory in turn, ignore if dir already exists
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
err = lfs2_mkdir(&lfs2, path);
|
||||
assert(!err || err == LFS2_ERR_EXIST);
|
||||
}
|
||||
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
}
|
||||
} else {
|
||||
assert(strcmp(info.name, &full_path[2*(DEPTH-1)+1]) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
|
||||
// create new random path
|
||||
char new_path[256];
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
sprintf(&new_path[2*d], "/%c", alpha[rand() % FILES]);
|
||||
}
|
||||
|
||||
// if new path does not exist, rename, otherwise destroy
|
||||
res = lfs2_stat(&lfs2, new_path, &info);
|
||||
assert(!res || res == LFS2_ERR_NOENT);
|
||||
if (res == LFS2_ERR_NOENT) {
|
||||
// stop once some dir is renamed
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
strcpy(&path[2*d], &full_path[2*d]);
|
||||
path[2*d+2] = '\0';
|
||||
strcpy(&path[128+2*d], &new_path[2*d]);
|
||||
path[128+2*d+2] = '\0';
|
||||
err = lfs2_rename(&lfs2, path, path+128);
|
||||
assert(!err || err == LFS2_ERR_NOTEMPTY);
|
||||
if (!err) {
|
||||
strcpy(path, path+128);
|
||||
}
|
||||
}
|
||||
|
||||
for (int d = 0; d < DEPTH; d++) {
|
||||
strcpy(path, new_path);
|
||||
path[2*d+2] = '\0';
|
||||
lfs2_stat(&lfs2, path, &info) => 0;
|
||||
assert(strcmp(info.name, &path[2*d+1]) == 0);
|
||||
assert(info.type == LFS2_TYPE_DIR);
|
||||
}
|
||||
|
||||
lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT;
|
||||
} else {
|
||||
// try to delete path in reverse order,
|
||||
// ignore if dir is not empty
|
||||
for (int d = DEPTH-1; d >= 0; d--) {
|
||||
strcpy(path, full_path);
|
||||
path[2*d+2] = '\0';
|
||||
err = lfs2_remove(&lfs2, path);
|
||||
assert(!err || err == LFS2_ERR_NOTEMPTY);
|
||||
}
|
||||
|
||||
lfs2_stat(&lfs2, full_path, &info) => LFS2_ERR_NOENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
|
@ -0,0 +1,380 @@
|
|||
|
||||
[[case]] # simple file seek
|
||||
define = [
|
||||
{COUNT=132, SKIP=4},
|
||||
{COUNT=132, SKIP=128},
|
||||
{COUNT=200, SKIP=10},
|
||||
{COUNT=200, SKIP=100},
|
||||
{COUNT=4, SKIP=1},
|
||||
{COUNT=4, SKIP=2},
|
||||
]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "kitty",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
size = strlen("kittycatcat");
|
||||
memcpy(buffer, "kittycatcat", size);
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDONLY) => 0;
|
||||
|
||||
lfs2_soff_t pos = -1;
|
||||
size = strlen("kittycatcat");
|
||||
for (int i = 0; i < SKIP; i++) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
pos = lfs2_file_tell(&lfs2, &file);
|
||||
}
|
||||
assert(pos >= 0);
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_rewind(&lfs2, &file) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_CUR) => size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, size, LFS2_SEEK_CUR) => 3*size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, -size, LFS2_SEEK_CUR) => pos;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, -size, LFS2_SEEK_END) >= 0 => 1;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
size = lfs2_file_size(&lfs2, &file);
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_CUR) => size;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # simple file seek and write
|
||||
define = [
|
||||
{COUNT=132, SKIP=4},
|
||||
{COUNT=132, SKIP=128},
|
||||
{COUNT=200, SKIP=10},
|
||||
{COUNT=200, SKIP=100},
|
||||
{COUNT=4, SKIP=1},
|
||||
{COUNT=4, SKIP=2},
|
||||
]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "kitty",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
size = strlen("kittycatcat");
|
||||
memcpy(buffer, "kittycatcat", size);
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
|
||||
|
||||
lfs2_soff_t pos = -1;
|
||||
size = strlen("kittycatcat");
|
||||
for (int i = 0; i < SKIP; i++) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
pos = lfs2_file_tell(&lfs2, &file);
|
||||
}
|
||||
assert(pos >= 0);
|
||||
|
||||
memcpy(buffer, "doggodogdog", size);
|
||||
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "doggodogdog", size) => 0;
|
||||
|
||||
lfs2_file_rewind(&lfs2, &file) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, pos, LFS2_SEEK_SET) => pos;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "doggodogdog", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, -size, LFS2_SEEK_END) >= 0 => 1;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
size = lfs2_file_size(&lfs2, &file);
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_CUR) => size;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # boundary seek and writes
|
||||
define.COUNT = 132
|
||||
define.OFFSETS = '"{512, 1020, 513, 1021, 511, 1019, 1441}"'
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "kitty",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
size = strlen("kittycatcat");
|
||||
memcpy(buffer, "kittycatcat", size);
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
|
||||
|
||||
size = strlen("hedgehoghog");
|
||||
const lfs2_soff_t offsets[] = OFFSETS;
|
||||
|
||||
for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
|
||||
lfs2_soff_t off = offsets[i];
|
||||
memcpy(buffer, "hedgehoghog", size);
|
||||
lfs2_file_seek(&lfs2, &file, off, LFS2_SEEK_SET) => off;
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
lfs2_file_seek(&lfs2, &file, off, LFS2_SEEK_SET) => off;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hedgehoghog", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, off, LFS2_SEEK_SET) => off;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hedgehoghog", size) => 0;
|
||||
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "kittycatcat", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, off, LFS2_SEEK_SET) => off;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hedgehoghog", size) => 0;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # out of bounds seek
|
||||
define = [
|
||||
{COUNT=132, SKIP=4},
|
||||
{COUNT=132, SKIP=128},
|
||||
{COUNT=200, SKIP=10},
|
||||
{COUNT=200, SKIP=100},
|
||||
{COUNT=4, SKIP=2},
|
||||
{COUNT=4, SKIP=3},
|
||||
]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "kitty",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_APPEND) => 0;
|
||||
size = strlen("kittycatcat");
|
||||
memcpy(buffer, "kittycatcat", size);
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
|
||||
|
||||
size = strlen("kittycatcat");
|
||||
lfs2_file_size(&lfs2, &file) => COUNT*size;
|
||||
lfs2_file_seek(&lfs2, &file, (COUNT+SKIP)*size,
|
||||
LFS2_SEEK_SET) => (COUNT+SKIP)*size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
|
||||
|
||||
memcpy(buffer, "porcupineee", size);
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, (COUNT+SKIP)*size,
|
||||
LFS2_SEEK_SET) => (COUNT+SKIP)*size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "porcupineee", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, COUNT*size,
|
||||
LFS2_SEEK_SET) => COUNT*size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "\0\0\0\0\0\0\0\0\0\0\0", size) => 0;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, -((COUNT+SKIP)*size),
|
||||
LFS2_SEEK_CUR) => LFS2_ERR_INVAL;
|
||||
lfs2_file_tell(&lfs2, &file) => (COUNT+1)*size;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, -((COUNT+2*SKIP)*size),
|
||||
LFS2_SEEK_END) => LFS2_ERR_INVAL;
|
||||
lfs2_file_tell(&lfs2, &file) => (COUNT+1)*size;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # inline write and seek
|
||||
define.SIZE = [2, 4, 128, 132]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "tinykitty",
|
||||
LFS2_O_RDWR | LFS2_O_CREAT) => 0;
|
||||
int j = 0;
|
||||
int k = 0;
|
||||
|
||||
memcpy(buffer, "abcdefghijklmnopqrstuvwxyz", 26);
|
||||
for (unsigned i = 0; i < SIZE; i++) {
|
||||
lfs2_file_write(&lfs2, &file, &buffer[j++ % 26], 1) => 1;
|
||||
lfs2_file_tell(&lfs2, &file) => i+1;
|
||||
lfs2_file_size(&lfs2, &file) => i+1;
|
||||
}
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
|
||||
lfs2_file_tell(&lfs2, &file) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
for (unsigned i = 0; i < SIZE; i++) {
|
||||
uint8_t c;
|
||||
lfs2_file_read(&lfs2, &file, &c, 1) => 1;
|
||||
c => buffer[k++ % 26];
|
||||
}
|
||||
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
lfs2_file_tell(&lfs2, &file) => SIZE;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
|
||||
for (unsigned i = 0; i < SIZE; i++) {
|
||||
lfs2_file_write(&lfs2, &file, &buffer[j++ % 26], 1) => 1;
|
||||
lfs2_file_tell(&lfs2, &file) => i+1;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
lfs2_file_tell(&lfs2, &file) => i+1;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
if (i < SIZE-2) {
|
||||
uint8_t c[3];
|
||||
lfs2_file_seek(&lfs2, &file, -1, LFS2_SEEK_CUR) => i;
|
||||
lfs2_file_read(&lfs2, &file, &c, 3) => 3;
|
||||
lfs2_file_tell(&lfs2, &file) => i+3;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
lfs2_file_seek(&lfs2, &file, i+1, LFS2_SEEK_SET) => i+1;
|
||||
lfs2_file_tell(&lfs2, &file) => i+1;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
|
||||
lfs2_file_tell(&lfs2, &file) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
for (unsigned i = 0; i < SIZE; i++) {
|
||||
uint8_t c;
|
||||
lfs2_file_read(&lfs2, &file, &c, 1) => 1;
|
||||
c => buffer[k++ % 26];
|
||||
}
|
||||
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
lfs2_file_tell(&lfs2, &file) => SIZE;
|
||||
lfs2_file_size(&lfs2, &file) => SIZE;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # file seek and write with power-loss
|
||||
# must be power-of-2 for quadratic probing to be exhaustive
|
||||
define.COUNT = [4, 64, 128]
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
err = lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDONLY);
|
||||
assert(!err || err == LFS2_ERR_NOENT);
|
||||
if (!err) {
|
||||
if (lfs2_file_size(&lfs2, &file) != 0) {
|
||||
lfs2_file_size(&lfs2, &file) => 11*COUNT;
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
memset(buffer, 0, 11+1);
|
||||
lfs2_file_read(&lfs2, &file, buffer, 11) => 11;
|
||||
assert(memcmp(buffer, "kittycatcat", 11) == 0 ||
|
||||
memcmp(buffer, "doggodogdog", 11) == 0);
|
||||
}
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
if (lfs2_file_size(&lfs2, &file) == 0) {
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
strcpy((char*)buffer, "kittycatcat");
|
||||
size = strlen((char*)buffer);
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
strcpy((char*)buffer, "doggodogdog");
|
||||
size = strlen((char*)buffer);
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => COUNT*size;
|
||||
// seek and write using quadratic probing to touch all
|
||||
// 11-byte words in the file
|
||||
lfs2_off_t off = 0;
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
off = (5*off + 1) % COUNT;
|
||||
lfs2_file_seek(&lfs2, &file, off*size, LFS2_SEEK_SET) => off*size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(memcmp(buffer, "kittycatcat", size) == 0 ||
|
||||
memcmp(buffer, "doggodogdog", size) == 0);
|
||||
if (memcmp(buffer, "doggodogdog", size) != 0) {
|
||||
lfs2_file_seek(&lfs2, &file, off*size, LFS2_SEEK_SET) => off*size;
|
||||
strcpy((char*)buffer, "doggodogdog");
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
lfs2_file_seek(&lfs2, &file, off*size, LFS2_SEEK_SET) => off*size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(memcmp(buffer, "doggodogdog", size) == 0);
|
||||
lfs2_file_sync(&lfs2, &file) => 0;
|
||||
lfs2_file_seek(&lfs2, &file, off*size, LFS2_SEEK_SET) => off*size;
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(memcmp(buffer, "doggodogdog", size) == 0);
|
||||
}
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "kitty", LFS2_O_RDWR) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => COUNT*size;
|
||||
for (int j = 0; j < COUNT; j++) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
assert(memcmp(buffer, "doggodogdog", size) == 0);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
|
@ -0,0 +1,127 @@
|
|||
[[case]] # simple formatting test
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # mount/unmount
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # reentrant format
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # invalid mount
|
||||
code = '''
|
||||
lfs2_mount(&lfs2, &cfg) => LFS2_ERR_CORRUPT;
|
||||
'''
|
||||
|
||||
[[case]] # expanding superblock
|
||||
define.LFS2_BLOCK_CYCLES = [32, 33, 1]
|
||||
define.N = [10, 100, 1000]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
lfs2_file_open(&lfs2, &file, "dummy",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_stat(&lfs2, "dummy", &info) => 0;
|
||||
assert(strcmp(info.name, "dummy") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
lfs2_remove(&lfs2, "dummy") => 0;
|
||||
}
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// one last check after power-cycle
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "dummy",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_stat(&lfs2, "dummy", &info) => 0;
|
||||
assert(strcmp(info.name, "dummy") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # expanding superblock with power cycle
|
||||
define.LFS2_BLOCK_CYCLES = [32, 33, 1]
|
||||
define.N = [10, 100, 1000]
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
// remove lingering dummy?
|
||||
err = lfs2_stat(&lfs2, "dummy", &info);
|
||||
assert(err == 0 || (err == LFS2_ERR_NOENT && i == 0));
|
||||
if (!err) {
|
||||
assert(strcmp(info.name, "dummy") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
lfs2_remove(&lfs2, "dummy") => 0;
|
||||
}
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "dummy",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_stat(&lfs2, "dummy", &info) => 0;
|
||||
assert(strcmp(info.name, "dummy") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
}
|
||||
|
||||
// one last check after power-cycle
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "dummy", &info) => 0;
|
||||
assert(strcmp(info.name, "dummy") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # reentrant expanding superblock
|
||||
define.LFS2_BLOCK_CYCLES = [2, 1]
|
||||
define.N = 24
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
// remove lingering dummy?
|
||||
err = lfs2_stat(&lfs2, "dummy", &info);
|
||||
assert(err == 0 || (err == LFS2_ERR_NOENT && i == 0));
|
||||
if (!err) {
|
||||
assert(strcmp(info.name, "dummy") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
lfs2_remove(&lfs2, "dummy") => 0;
|
||||
}
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "dummy",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_EXCL) => 0;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_stat(&lfs2, "dummy", &info) => 0;
|
||||
assert(strcmp(info.name, "dummy") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
}
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
// one last check after power-cycle
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_stat(&lfs2, "dummy", &info) => 0;
|
||||
assert(strcmp(info.name, "dummy") == 0);
|
||||
assert(info.type == LFS2_TYPE_REG);
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
|
@ -0,0 +1,394 @@
|
|||
[[case]] # simple truncate
|
||||
define.MEDIUMSIZE = [32, 2048]
|
||||
define.LARGESIZE = 8192
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldynoop",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
|
||||
strcpy((char*)buffer, "hair");
|
||||
size = strlen((char*)buffer);
|
||||
for (lfs2_off_t j = 0; j < LARGESIZE; j += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_size(&lfs2, &file) => LARGESIZE;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldynoop", LFS2_O_RDWR) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => LARGESIZE;
|
||||
|
||||
lfs2_file_truncate(&lfs2, &file, MEDIUMSIZE) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldynoop", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
|
||||
size = strlen("hair");
|
||||
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hair", size) => 0;
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # truncate and read
|
||||
define.MEDIUMSIZE = [32, 2048]
|
||||
define.LARGESIZE = 8192
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldyread",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
|
||||
strcpy((char*)buffer, "hair");
|
||||
size = strlen((char*)buffer);
|
||||
for (lfs2_off_t j = 0; j < LARGESIZE; j += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_size(&lfs2, &file) => LARGESIZE;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldyread", LFS2_O_RDWR) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => LARGESIZE;
|
||||
|
||||
lfs2_file_truncate(&lfs2, &file, MEDIUMSIZE) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
|
||||
size = strlen("hair");
|
||||
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hair", size) => 0;
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldyread", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
|
||||
size = strlen("hair");
|
||||
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hair", size) => 0;
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # write, truncate, and read
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "sequence",
|
||||
LFS2_O_RDWR | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
|
||||
size = lfs2_min(lfs2.cfg->cache_size, sizeof(buffer)/2);
|
||||
lfs2_size_t qsize = size / 4;
|
||||
uint8_t *wb = buffer;
|
||||
uint8_t *rb = buffer + size;
|
||||
for (lfs2_off_t j = 0; j < size; ++j) {
|
||||
wb[j] = j;
|
||||
}
|
||||
|
||||
/* Spread sequence over size */
|
||||
lfs2_file_write(&lfs2, &file, wb, size) => size;
|
||||
lfs2_file_size(&lfs2, &file) => size;
|
||||
lfs2_file_tell(&lfs2, &file) => size;
|
||||
|
||||
lfs2_file_seek(&lfs2, &file, 0, LFS2_SEEK_SET) => 0;
|
||||
lfs2_file_tell(&lfs2, &file) => 0;
|
||||
|
||||
/* Chop off the last quarter */
|
||||
lfs2_size_t trunc = size - qsize;
|
||||
lfs2_file_truncate(&lfs2, &file, trunc) => 0;
|
||||
lfs2_file_tell(&lfs2, &file) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => trunc;
|
||||
|
||||
/* Read should produce first 3/4 */
|
||||
lfs2_file_read(&lfs2, &file, rb, size) => trunc;
|
||||
memcmp(rb, wb, trunc) => 0;
|
||||
|
||||
/* Move to 1/4 */
|
||||
lfs2_file_size(&lfs2, &file) => trunc;
|
||||
lfs2_file_seek(&lfs2, &file, qsize, LFS2_SEEK_SET) => qsize;
|
||||
lfs2_file_tell(&lfs2, &file) => qsize;
|
||||
|
||||
/* Chop to 1/2 */
|
||||
trunc -= qsize;
|
||||
lfs2_file_truncate(&lfs2, &file, trunc) => 0;
|
||||
lfs2_file_tell(&lfs2, &file) => qsize;
|
||||
lfs2_file_size(&lfs2, &file) => trunc;
|
||||
|
||||
/* Read should produce second quarter */
|
||||
lfs2_file_read(&lfs2, &file, rb, size) => trunc - qsize;
|
||||
memcmp(rb, wb + qsize, trunc - qsize) => 0;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # truncate and write
|
||||
define.MEDIUMSIZE = [32, 2048]
|
||||
define.LARGESIZE = 8192
|
||||
code = '''
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldywrite",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT) => 0;
|
||||
|
||||
strcpy((char*)buffer, "hair");
|
||||
size = strlen((char*)buffer);
|
||||
for (lfs2_off_t j = 0; j < LARGESIZE; j += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_size(&lfs2, &file) => LARGESIZE;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldywrite", LFS2_O_RDWR) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => LARGESIZE;
|
||||
|
||||
lfs2_file_truncate(&lfs2, &file, MEDIUMSIZE) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
|
||||
strcpy((char*)buffer, "bald");
|
||||
size = strlen((char*)buffer);
|
||||
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
lfs2_file_open(&lfs2, &file, "baldywrite", LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
|
||||
size = strlen("bald");
|
||||
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "bald", size) => 0;
|
||||
}
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => 0;
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # truncate write under powerloss
|
||||
define.SMALLSIZE = [4, 512]
|
||||
define.MEDIUMSIZE = [32, 1024]
|
||||
define.LARGESIZE = 2048
|
||||
reentrant = true
|
||||
code = '''
|
||||
err = lfs2_mount(&lfs2, &cfg);
|
||||
if (err) {
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
}
|
||||
err = lfs2_file_open(&lfs2, &file, "baldy", LFS2_O_RDONLY);
|
||||
assert(!err || err == LFS2_ERR_NOENT);
|
||||
if (!err) {
|
||||
size = lfs2_file_size(&lfs2, &file);
|
||||
assert(size == 0 ||
|
||||
size == LARGESIZE ||
|
||||
size == MEDIUMSIZE ||
|
||||
size == SMALLSIZE);
|
||||
for (lfs2_off_t j = 0; j < size; j += 4) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, 4) => 4;
|
||||
assert(memcmp(buffer, "hair", 4) == 0 ||
|
||||
memcmp(buffer, "bald", 4) == 0 ||
|
||||
memcmp(buffer, "comb", 4) == 0);
|
||||
}
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "baldy",
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => 0;
|
||||
strcpy((char*)buffer, "hair");
|
||||
size = strlen((char*)buffer);
|
||||
for (lfs2_off_t j = 0; j < LARGESIZE; j += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_size(&lfs2, &file) => LARGESIZE;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "baldy", LFS2_O_RDWR) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => LARGESIZE;
|
||||
lfs2_file_truncate(&lfs2, &file, MEDIUMSIZE) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
strcpy((char*)buffer, "bald");
|
||||
size = strlen((char*)buffer);
|
||||
for (lfs2_off_t j = 0; j < MEDIUMSIZE; j += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_file_open(&lfs2, &file, "baldy", LFS2_O_RDWR) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => MEDIUMSIZE;
|
||||
lfs2_file_truncate(&lfs2, &file, SMALLSIZE) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => SMALLSIZE;
|
||||
strcpy((char*)buffer, "comb");
|
||||
size = strlen((char*)buffer);
|
||||
for (lfs2_off_t j = 0; j < SMALLSIZE; j += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_size(&lfs2, &file) => SMALLSIZE;
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
||||
|
||||
[[case]] # more aggressive general truncation tests
|
||||
define.CONFIG = 'range(6)'
|
||||
define.SMALLSIZE = 32
|
||||
define.MEDIUMSIZE = 2048
|
||||
define.LARGESIZE = 8192
|
||||
code = '''
|
||||
#define COUNT 5
|
||||
const struct {
|
||||
lfs2_off_t startsizes[COUNT];
|
||||
lfs2_off_t startseeks[COUNT];
|
||||
lfs2_off_t hotsizes[COUNT];
|
||||
lfs2_off_t coldsizes[COUNT];
|
||||
} configs[] = {
|
||||
// cold shrinking
|
||||
{{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}},
|
||||
// cold expanding
|
||||
{{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE}},
|
||||
// warm shrinking truncate
|
||||
{{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE},
|
||||
{ 0, 0, 0, 0, 0}},
|
||||
// warm expanding truncate
|
||||
{{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE},
|
||||
{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}},
|
||||
// mid-file shrinking truncate
|
||||
{{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{ LARGESIZE, LARGESIZE, LARGESIZE, LARGESIZE, LARGESIZE},
|
||||
{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE},
|
||||
{ 0, 0, 0, 0, 0}},
|
||||
// mid-file expanding truncate
|
||||
{{ 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE, 2*LARGESIZE},
|
||||
{ 0, 0, SMALLSIZE, MEDIUMSIZE, LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE},
|
||||
{2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE, 2*LARGESIZE}},
|
||||
};
|
||||
|
||||
const lfs2_off_t *startsizes = configs[CONFIG].startsizes;
|
||||
const lfs2_off_t *startseeks = configs[CONFIG].startseeks;
|
||||
const lfs2_off_t *hotsizes = configs[CONFIG].hotsizes;
|
||||
const lfs2_off_t *coldsizes = configs[CONFIG].coldsizes;
|
||||
|
||||
lfs2_format(&lfs2, &cfg) => 0;
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
for (unsigned i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "hairyhead%d", i);
|
||||
lfs2_file_open(&lfs2, &file, path,
|
||||
LFS2_O_WRONLY | LFS2_O_CREAT | LFS2_O_TRUNC) => 0;
|
||||
|
||||
strcpy((char*)buffer, "hair");
|
||||
size = strlen((char*)buffer);
|
||||
for (lfs2_off_t j = 0; j < startsizes[i]; j += size) {
|
||||
lfs2_file_write(&lfs2, &file, buffer, size) => size;
|
||||
}
|
||||
lfs2_file_size(&lfs2, &file) => startsizes[i];
|
||||
|
||||
if (startseeks[i] != startsizes[i]) {
|
||||
lfs2_file_seek(&lfs2, &file,
|
||||
startseeks[i], LFS2_SEEK_SET) => startseeks[i];
|
||||
}
|
||||
|
||||
lfs2_file_truncate(&lfs2, &file, hotsizes[i]) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => hotsizes[i];
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
for (unsigned i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "hairyhead%d", i);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDWR) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => hotsizes[i];
|
||||
|
||||
size = strlen("hair");
|
||||
lfs2_off_t j = 0;
|
||||
for (; j < startsizes[i] && j < hotsizes[i]; j += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hair", size) => 0;
|
||||
}
|
||||
|
||||
for (; j < hotsizes[i]; j += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "\0\0\0\0", size) => 0;
|
||||
}
|
||||
|
||||
lfs2_file_truncate(&lfs2, &file, coldsizes[i]) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => coldsizes[i];
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
|
||||
lfs2_mount(&lfs2, &cfg) => 0;
|
||||
|
||||
for (unsigned i = 0; i < COUNT; i++) {
|
||||
sprintf(path, "hairyhead%d", i);
|
||||
lfs2_file_open(&lfs2, &file, path, LFS2_O_RDONLY) => 0;
|
||||
lfs2_file_size(&lfs2, &file) => coldsizes[i];
|
||||
|
||||
size = strlen("hair");
|
||||
lfs2_off_t j = 0;
|
||||
for (; j < startsizes[i] && j < hotsizes[i] && j < coldsizes[i];
|
||||
j += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "hair", size) => 0;
|
||||
}
|
||||
|
||||
for (; j < coldsizes[i]; j += size) {
|
||||
lfs2_file_read(&lfs2, &file, buffer, size) => size;
|
||||
memcmp(buffer, "\0\0\0\0", size) => 0;
|
||||
}
|
||||
|
||||
lfs2_file_close(&lfs2, &file) => 0;
|
||||
}
|
||||
|
||||
lfs2_unmount(&lfs2) => 0;
|
||||
'''
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "littlefs2",
|
||||
"config": {
|
||||
"block_size": {
|
||||
"macro_name": "MBED_LFS2_BLOCK_SIZE",
|
||||
"value": 512,
|
||||
"help": "Size of a logical block. This does not impact ram consumption and may be larger than the physical erase block. If the physical erase block is larger, littlefs will use that instead. Larger values will be faster but waste more storage when files are not aligned to a block size."
|
||||
},
|
||||
"block_cycles": {
|
||||
"macro_name": "MBED_LFS2_BLOCK_CYCLES",
|
||||
"value": 1024,
|
||||
"help": "Number of erase cycles before a block is forcefully evicted. Larger values are more efficient but cause less even wear distribution. 0 disables dynamic wear-leveling."
|
||||
},
|
||||
"cache_size": {
|
||||
"macro_name": "MBED_LFS2_CACHE_SIZE",
|
||||
"value": "64",
|
||||
"help": "Size of read/program caches. Each file uses 1 cache, and littlefs allocates 2 caches for internal operations. Larger values should be faster but uses more RAM."
|
||||
},
|
||||
"lookahead_size": {
|
||||
"macro_name": "MBED_LFS2_LOOKAHEAD_SIZE",
|
||||
"value": 64,
|
||||
"help": "Size of the lookahead buffer. A larger lookahead reduces the allocation scans and results in a faster filesystem but uses more RAM."
|
||||
},
|
||||
"intrinsics": {
|
||||
"macro_name": "MBED_LFS2_INTRINSICS",
|
||||
"value": true,
|
||||
"help": "Enable intrinsics for bit operations such as ctz, popc, and le32 conversion. Can be disabled to help debug toolchain issues"
|
||||
},
|
||||
"enable_info": {
|
||||
"macro_name": "MBED_LFS2_ENABLE_INFO",
|
||||
"value": false,
|
||||
"help": "Enables info logging, true = enabled, false = disabled, null = disabled only in release builds"
|
||||
},
|
||||
"enable_debug": {
|
||||
"macro_name": "MBED_LFS2_ENABLE_DEBUG",
|
||||
"value": null,
|
||||
"help": "Enables debug logging, true = enabled, false = disabled, null = disabled only in release builds"
|
||||
},
|
||||
"enable_warn": {
|
||||
"macro_name": "MBED_LFS2_ENABLE_WARN",
|
||||
"value": null,
|
||||
"help": "Enables warn logging, true = enabled, false = disabled, null = disabled only in release builds"
|
||||
},
|
||||
"enable_error": {
|
||||
"macro_name": "MBED_LFS2_ENABLE_ERROR",
|
||||
"value": null,
|
||||
"help": "Enables error logging, true = enabled, false = disabled, null = disabled only in release builds"
|
||||
},
|
||||
"enable_assert": {
|
||||
"macro_name": "MBED_LFS2_ENABLE_ASSERT",
|
||||
"value": null,
|
||||
"help": "Enables asserts, true = enabled, false = disabled, null = disabled only in release builds"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue