356d7065be
These have been battle-tested in external benchmarks, and have proven useful for finding a runtime estimate on stack+heap usage. Of course, to be realistic they need to be cross-compiled and run under QEMU (which does work!), but even on x86_64 they provide a nice insight into RAM usage. In practice the only real difference is pointer width anyways. --- Enabled by default for the bench runner, these are available if TEST/BENCH_YES_HEAP and/or TEST/BENCH_YES_STACK are defined. (This default is provided by the Makefile. At least heap measurements rely on linker flags, so it probably doesn't make sense to default enable in the bench runner itself.) Stack vs heap rely on slightly different mechanisms: - Stack: Uses GCC's __builtin_frame_address(0) to measure the current stack usage on entry to every bd operation. - Heap: Relies on GCC's -Wl,--wrap flags to intercept every malloc/free call, to track the current heap usage. These are available via bench/test macros: - BENCH_STACK() - Maximum stack usage of the current run - BENCH_STACK_CURRENT() - Current stack usage - BENCH_HEAP() - Maximum heap usage of the current run - BENCH_HEAP_CURRENT() - Current heap usage Note BENCH_STACK_CURRENT() can be useful for separating out the bench's ctx from total stack usage, similarly to our static analysis. --- One surprising outcome is that these heap hooks trivially implement a memory leak detector. Maybe that could be useful in the test_runner as a cheaper alternative to Valgrind?
155 lines
3.5 KiB
C
155 lines
3.5 KiB
C
/*
|
|
* Runner for littlefs tests
|
|
*
|
|
* Copyright (c) 2022, The littlefs authors.
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
#ifndef TEST_RUNNER_H
|
|
#define TEST_RUNNER_H
|
|
|
|
|
|
// default to using emubd for tests
|
|
#if !defined(TEST_EMUBD) && !defined(TEST_KIWIBD)
|
|
#define TEST_EMUBD
|
|
#endif
|
|
|
|
// ifdef macros for emubd vs kiwibd
|
|
#ifdef TEST_EMUBD
|
|
#define TEST_IFDEF_EMUBD(a, b) (a)
|
|
#else
|
|
#define TEST_IFDEF_EMUBD(a, b) (b)
|
|
#endif
|
|
#ifdef TEST_KIWIBD
|
|
#define TEST_IFDEF_KIWIBD(a, b) (a)
|
|
#else
|
|
#define TEST_IFDEF_KIWIBD(a, b) (b)
|
|
#endif
|
|
|
|
// override LFS3_TRACE
|
|
void test_trace(const char *fmt, ...);
|
|
|
|
#define LFS3_TRACE_(fmt, ...) \
|
|
test_trace("%s:%d:trace: " fmt "%s\n", \
|
|
__FILE__, \
|
|
__LINE__, \
|
|
__VA_ARGS__)
|
|
#define LFS3_TRACE(...) LFS3_TRACE_(__VA_ARGS__, "")
|
|
#define LFS3_EMUBD_TRACE(...) LFS3_TRACE_(__VA_ARGS__, "")
|
|
#define LFS3_KIWIBD_TRACE(...) LFS3_TRACE_(__VA_ARGS__, "")
|
|
|
|
|
|
// note these are indirectly included in any generated files
|
|
#ifndef TEST_KIWIBD
|
|
#include "bd/lfs3_emubd.h"
|
|
#else
|
|
#include "bd/lfs3_kiwibd.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
|
|
// give source a chance to define feature macros
|
|
#undef _FEATURES_H
|
|
#undef _STDIO_H
|
|
|
|
|
|
// generated test configurations
|
|
struct lfs3_cfg;
|
|
|
|
enum test_flags {
|
|
TEST_INTERNAL = 0x1,
|
|
TEST_REENTRANT = 0x2,
|
|
TEST_FUZZ = 0x4,
|
|
};
|
|
typedef uint8_t test_flags_t;
|
|
|
|
typedef struct test_define {
|
|
const char *name;
|
|
intmax_t *define;
|
|
intmax_t (*cb)(void *data, size_t i);
|
|
void *data;
|
|
size_t permutations;
|
|
} test_define_t;
|
|
|
|
struct test_case {
|
|
const char *name;
|
|
const char *path;
|
|
test_flags_t flags;
|
|
|
|
const test_define_t *defines;
|
|
size_t permutations;
|
|
|
|
bool (*if_)(void);
|
|
void (*run)(struct lfs3_cfg *cfg);
|
|
};
|
|
|
|
struct test_suite {
|
|
const char *name;
|
|
const char *path;
|
|
test_flags_t flags;
|
|
|
|
const test_define_t *defines;
|
|
size_t define_count;
|
|
|
|
const struct test_case *cases;
|
|
size_t case_count;
|
|
};
|
|
|
|
extern const struct test_suite *const test_suites[];
|
|
extern const size_t test_suite_count;
|
|
|
|
|
|
// this variable tracks the number of powerlosses triggered during the
|
|
// current test permutation, this is useful for both tests and debugging
|
|
extern volatile size_t TEST_PLS;
|
|
|
|
// deterministic prng for pseudo-randomness in tests
|
|
uint32_t test_prng(uint32_t *state);
|
|
|
|
#define TEST_PRNG(state) test_prng(state)
|
|
|
|
// generation of specific permutations of an array for exhaustive testing
|
|
size_t test_factorial(size_t x);
|
|
void test_permutation(size_t i, uint32_t *buffer, size_t size);
|
|
|
|
#define TEST_FACTORIAL(x) test_factorial(x)
|
|
#define TEST_PERMUTATION(i, buffer, size) test_permutation(i, buffer, size)
|
|
|
|
#ifdef TEST_YES_STACK
|
|
// get the maximum/current stack usage for this run
|
|
size_t test_stack(void);
|
|
__attribute__((noinline))
|
|
size_t test_stack_current(void);
|
|
__attribute__((noinline))
|
|
void test_stack_pause(void);
|
|
void test_stack_resume(void);
|
|
|
|
#define TEST_STACK() test_stack()
|
|
#define TEST_STACK_CURRENT() test_stack_current()
|
|
#define TEST_STACK_PAUSE() test_stack_pause()
|
|
#define TEST_STACK_RESUME() test_stack_resume()
|
|
#endif
|
|
|
|
#ifdef TEST_YES_HEAP
|
|
// get the maximum/current heap usage for this run
|
|
size_t test_heap(void);
|
|
size_t test_heap_current(void);
|
|
void test_heap_pause(void);
|
|
void test_heap_resume(void);
|
|
|
|
#define TEST_HEAP() test_heap()
|
|
#define TEST_HEAP_CURRENT() test_heap_current()
|
|
#define TEST_HEAP_PAUSE() test_heap_pause()
|
|
#define TEST_HEAP_RESUME() test_heap_resume()
|
|
#endif
|
|
|
|
|
|
// declare implicit defines as global intmax_ts
|
|
#define TEST_DEFINE(k, v) \
|
|
extern intmax_t k;
|
|
#include "test_defines.h"
|
|
#undef TEST_DEFINE
|
|
|
|
|
|
#endif
|