Code Monkey home page Code Monkey logo

libecheck's Introduction

libecheck consists of two main parts:

* E(asy)Embed: make writing and testing embedded libraries easier.

* E(asy)Check: Boiler-plate to make simple testing easier, when one
  does not want a whole testing framework.

ECheck
------
The basic idea is that you get this kind of interface:

  check_[TYPE](actual, expected);


The function returns 0 on success, and error code (typically 1) on failure.
for instance:

  error_code = check_int(val, 42);
  assert(error_code == 0);


To send error messages to a log, first construct a log object, which is
defined in eembed.h (see below).  Then use the "lcheck" form:

 error_code = lcheck_char(log, val, 'a');

In hosted systems which have stdio.h, to send error messages to a FILE*,
set the context on the default logger:

 FILE *logfile = fopen(filename, "w");
 echeck_default_log->context = logfile;
 error_code = check_char(val, 'a');


To include a message for additional context, use the "_m" form:

 error_code = check_size_t_m(size_of(foo), foo_len, "BAD MOJO!");


And the forms can be combined:

 error_code = lcheck_str_m(log, actual, "expected str", "BAD MOJO!")


To check a byte[] buffer, additional length parameters are required:

 error_code = check_byte_array(actual, actual_len, expected, expected_len);


To check a double, an epsilon is used to approximate equality.
For example, the would be equal:

 actual =  1.0000003;
 expect =  1.0000004;
 epsilon = 0.0001;
 error_code = check_double(actual, expected, epsilon);

The following would not be equal:

 actual =  1.0000003;
 expect =  1.0000004;
 epsilon = 0.000000001;
 error_code = check_double(actual, expected, epsilon);

For ease of use, a default scaled epsilon is available:

 check_double_scaled_epsilon(actual, expected);

is the same as, and #defined to be:

 check_double(actual, expected, ((expected) * DBL_EPSILON));

Thus, if "expected" is a function with side-effects, that the call to
"check_double_scaled_epsilon" will call the "expected" function twice.


If you wish to return from main() with a count of errors, this can be
error-prone, especially if (failures % 256 == 0). This will not be a problem
if you use "check_status", because this function caps values at 127 (and -128).

 return check_status(failures);


EEmbed
------

Embedded environments may not support very much more than what is
required of "freestanding" implementations, thus it might be as
little as:

	<float.h>
	<iso646.h>
	<limits.h>
	<stdarg.h>
	<stdbool.h>
	<stddef.h>
	<stdint.h>



That said however, often there is at least some support for serial
I/O even when if it is lacking the lovely full-features of fprintf
and sprintf.  Additionally, development may be done in a "hosted"
environment where the developer has access to the full standard
library.

The goal of this library is to make it straight-forward to develop
libraries which are useful in both freestanding and hosted
applications.

Since "assert.h" is not in the list of freestanding headers,
"eembed-assert.h" can be used.  In the standard hosted case,
"eembed_assert(expression)" is defined to be "assert(expression)",
in the freestanding case, it is defined to make use of
"eembed_system_print" and then call the function:

	void (*eembed_assert_crash)(void);

By default, in the hosted case, this calls exit(EXIT_FAILURE) and in
the freestanding case, it is a null function pointer which it can be
redefined if other action is needed to reset the firmware. Then it
can be used just like "assert" and also honors NDEBUG:

	eembed_assert(a == b);

In a hosted environment, the global eembed_err_log is a wrapper for
fprintf with stderr, the eembed_out_log is a wrapper for fprintf (stdout),
and the eembed_null_log ignores the input. In a freestanding environment,
the eembed_out_log and eembed_err_log point to the same eembed_null_log
instance, and thus they ignores input.

This is the structure of the eembed_log:

	struct eembed_log {
		void *context;
		void (*append_c)(struct eembed_log *log, char c);
		void (*append_s)(struct eembed_log *log, const char *str);
		void (*append_ul)(struct eembed_log *log, uint64_t ul);
		void (*append_l)(struct eembed_log *log, int64_t l);
		void (*append_f)(struct eembed_log *log, long double f);
		void (*append_vp)(struct eembed_log *log, const void *ptr);
		void (*append_eol)(struct eembed_log *log);
	}

	extern struct eembed_log *eembed_out_log;
	extern struct eembed_log *eembed_err_log;
	extern struct eembed_log *eembed_null_log;

Exmple useage:

	struct eembed_log *out = eembed_out_log;

	out->append_s(out, "hello, world.");

The "eembed-arduino.cpp" gives an example of how the eembed_err_log and the
eembed_system_print functions can be set to use serial printing.

Because it can be handy to print numbers, simple number-to-string functions
are provided. In the hosted case, these simply wrap sprintf; in the
freestanding case, straight-forward, if inefficient, implementations are
provided:

	char *eembed_long_to_str(char *buf, size_t len, long l);
	char *eembed_ulong_to_str(char *buf, size_t len, unsigned long ul);
	char *eembed_ulong_to_hex(char *buf, size_t len, unsigned long z);
	char *eembed_float_to_str(char *buf, size_t len, double f);

Additionally, there are a handful of functions which are requiried by
echeck or are needed by nearly any program.  This library provides
function pointers to a few of these functions which in the hosted
case simply point to the standard library version. In the
freestanding case, very basic implementations of a few of these
functions are provided:

	int (*eembed_memcmp)(const void *s1, const void *s2, size_t n);

	void *(*eembed_memcpy)(void *dest, const void *src, size_t n);
	void *(*eembed_memmove)(void *dest, const void *src, size_t n);

	void *(*eembed_memset)(void *dest, int val, size_t n);

	char *(*eembed_strcat)(char *dest, const char *src);
	char *(*eembed_strncat)(char *dest, const char *src, size_t n);

	int (*eembed_strcmp)(const char *s1, const char *s2);
	int (*eembed_strncmp)(const char *s1, const char *s2, size_t n);

	char *(*eembed_strcpy)(char *dest, const char *src);
	char *(*eembed_strncpy)(char *dest, const char *src, size_t n);

	size_t (*eembed_strlen)(const char *s);
	size_t (*eembed_strnlen)(const char *s, size_t maxlen);

	char *(*eembed_strstr)(const char *haystack, const char *needle);

The default malloc/free functions are wrapped in the HOSTED case. In the
freestanding case, eembed_malloc and eembed_calloc default to functions
which return a NULL pointer:

	void *eembed_malloc(size_t size);
	void *eembed_calloc(size_t nmemb, size_t size);
	void *eembed_realloc(void *ptr, size_t size);
	void *eembed_reallocarray(void *ptr, size_t nmemb, size_t size);
	void eembed_free(void *ptr);

Especially important for testing and for the embedded case, the allocator
is plug-able with this structure:

	struct eembed_allocator {
		void *context;
		void *(*malloc)(struct eembed_allocator *ea, size_t size);
		void *(*calloc)(struct eembed_allocator *ea, size_t nmemb,
				size_t size);
		void *(*realloc)(struct eembed_allocator *ea, void *ptr,
				size_t size);
		void *(*reallocarray)(struct eembed_allocator *ea, void *ptr,
				size_t nmemb, size_t size);
		void (*free)(struct eembed_allocator *ea, void *ptr);
	};

	extern struct eembed_allocator *eembed_global_allocator;

The eembed_malloc/free functions make use of the eembed_global_allocator.
Especially for the embedded case, eembed.h includes a simple allocator
which can be constructed from a byte array which can be provided during
firmware initialization:

	/* globals */
	unsigned char bytes[1024];

	void setup(void)
	{
		eembed_global_allocator = eembed_bytes_allocator(bytes, 1024);
	}

If programs are written using eembed_malloc/free functions, they can be
tested for robustness in the face of memory allocation failures using
the error injection facilities of EasyCheck. In echeck.h is a structure
for tracking memory allocation/frees:

	struct echeck_err_injecting_context {
		unsigned long allocs;
		unsigned long alloc_bytes;
		unsigned long frees;
		unsigned long free_bytes;
		unsigned long fails;
		unsigned long max_used;
		unsigned long attempts;
		unsigned long attempts_to_fail_bitmask;

		struct eembed_allocator *real;
		struct eembed_log *log;
	};

Tests can then make assertions such as the number of frees matches the
number of allocs, and the alloc_bytes equals the free_bytes. It may be
useful to assert that the max_used never exceeded some threshold.

The "attempts_to_fail_bitmask" is used to intercept allocation attempts
and return NULL instead of attempting to call the allocation function.
The obvious shortcoming of this approach is that it does not currently
support failures for more than 32-ish (bits of unsigned long) attempts
to allocate. Experience so far has shown that this is plenty, but this
may need to be extended in the future.

To use EasyCheck's error injection, create a structure for the allocator,
and a structure for the context, and pass in references to the logger
and the real allocator, e.g.:

	struct eembed_allocator our_allocator;
	struct echeck_err_injecting_context our_context;


	echeck_err_injecting_allocator_init(&our_allocator,
					    eembed_global_allocator,
					    &our_context,
					    eembed_err_log);

Firmware Testing
----------------
Firmware testing can be tedious. To enable easier early testing of
firmware, EasyCheck and EasyEmbed can be "faked" into a building
almost as if in a freestanding environment. Try this:

	make faux-fs-check

Or if hand-rolling, you can set the #defines yourself, a bit like:

	gcc -DEEMBED_HOSTED=0 -DFAUX_FREESTANDING=1 \
		-I$(ECHECK_SRC) $(ECHECK_SRC)/*.c \
		src/the_firmware.c \
		src/the_firmware_test.c \
		-o the_firmware_test

The tests in the tests/ directory use echeck.h (and eembed) themselves,
thus these can serve as examples for how to make use of the library.

The very popular Arduino embedded platform has a notoriously restrictive
build. As such, a little extra work has gone into supporting it. Try the
'eembed_arduino_demo' to see how to use the 'eembed_bytes_allocator' and
the 'eembed_strlen' functions.

The tests for echeck and eembed also can be run on the Arduino with the
'echeck_tests_arduino' sketch.

Additionally, a bit of documentation of echeck is provided in the form
of the 'echeck_arduino_demo' sketch.


Contributing
------------
Patches welcome! Bug reports are also appreciated.

The scope of functions to include in core eembed is limited to those
which are nearly essential for testing and reporting. That said, if
experience shows that another function would be useful, perhaps it
will get added. Compilers tend to be good about not including code
which is not used.

Code should have tests demonstrating the use and have test coverage
via:
	make coverage


Including
---------
Typically, including the git repo as a submodule is the way to go:

    git submodule add https://github.com/ericherman/libecheck \
	submodules/libecheck
    git submodule update --init --recursive

Alternatively, one can build locally and copy the .h files and .so
files from the ./build/ or ./debug/ directory to /usr/local or
similar.

For embedded work, you may wish to copy or symlink the files directly
in to your project's source directory, as can be seen in the sample
echeck_arduino_demo directory.

License
-------
GNU Lesser General Public License (LGPL), version 3 or later.
See COPYING, COPYING.LESSER for details.

libecheck's People

Contributors

ericherman avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.