From ec28da1aff7a1da2cb15be84c3520b60e0870f6e Mon Sep 17 00:00:00 2001 From: Robert Hunger Date: Thu, 6 Feb 2020 23:41:26 +0100 Subject: [PATCH] import progc-base without CUnit --- Doxyfile | 319 +++++++++++++++++++++++++++++++++++++++ Makefile | 37 +++++ testlib/Makefile | 80 ++++++++++ testlib/mainpage.dox | 10 ++ testlib/src/test_utils.c | 117 ++++++++++++++ testlib/src/test_utils.h | 146 ++++++++++++++++++ testlib/tests/tests.c | 170 +++++++++++++++++++++ 7 files changed, 879 insertions(+) create mode 100644 Doxyfile create mode 100644 Makefile create mode 100644 testlib/Makefile create mode 100644 testlib/mainpage.dox create mode 100644 testlib/src/test_utils.c create mode 100644 testlib/src/test_utils.h create mode 100644 testlib/tests/tests.c diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..221b1c5 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,319 @@ +# Doxyfile 1.8.11 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "PROGC - Labs" +PROJECT_NUMBER = +PROJECT_BRIEF = +PROJECT_LOGO = +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = YES +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h *.c *.dox +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = test*/* +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = NO +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +CLANG_ASSISTED_PARSING = NO +CLANG_OPTIONS = +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = doc +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = NO +COLLABORATION_GRAPH = NO +GROUP_GRAPHS = NO +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = NO +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = NO +DIRECTORY_GRAPH = NO +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = NO +DOT_CLEANUP = NO diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2450ff7 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +NL := $(EMPTY)\\n$(EMPTY) + +LABS := $(sort $(wildcard lab??-* testlib)) + +default: + @echo "**** PROGC Labs ****" + @echo "$(subst $(SPACE),$(NL),$(LABS))" + @echo "" + @echo "**** Prerequisites ****" + @echo "1. Change into the testlib directory" + @echo " cd testlib" + @echo "2. Build and install the library, e.g." + @echo " make clean" + @echo " make default" + @echo " make test" + @echo " make install" + @echo " make doc" + @echo " Caution: make sure the tests, installation and documentation does not produce any error." + @echo "3. View the produced documentation, e.g." + @echo " firefox doc/index.html" + @echo "" + @echo "**** How to build and run a lab? ****" + @echo "1. Change into the respective directory, e.g." + @echo " cd $(firstword $(LABS))" + @echo "2. Build the lab, e.g." + @echo " make" + @echo " The resulting executable is located in the bin folder." + @echo "3. Build and run the tests, e.g." + @echo " make test" + @echo "4. Build the HTML documentation from the sources, e.g." + @echo " make doc" + @echo " The produced HTML documentation is located in the doc folder. Open the index.html file in a HTML browser." + @echo "Notes:" + @echo "- You may cleanup the builds, e.g." + @echo " make clean" diff --git a/testlib/Makefile b/testlib/Makefile new file mode 100644 index 0000000..63c6cb5 --- /dev/null +++ b/testlib/Makefile @@ -0,0 +1,80 @@ +# what to produce +TARGET := bin/libprogctest.a + +# public headers +HEADERS := src/test_utils.h + +# implementation files +SOURCES := src/test_utils.c + +# test implementations +TSTSOURCES := tests/tests.c + +# directories to create (and remove upon cleanup) +CREATEDIRS := bin doc + +# list of derived file names from the source names +OBJECTS := $(SOURCES:%.c=%.o) # list of gcc -c ... produced *.o files +DEPS := $(SOURCES:%.c=%.d) # list of gcc -MD ... produced *.d files +TSTOBJECTS := $(TSTSOURCES:%.c=%.o) # list of gcc -c ... produced *.o files +TSTDEPS := $(TSTSOURCES:%.c=%.d) # list of gcc -MD ... produced *.d files +TSTTARGET := $(CURDIR)/tests/runtest + +# libraries +CUNITINCDIR := $(CURDIR)/../CUnit/include +CUNITLIBDIR := $(CURDIR)/../CUnit/lib + +# where to install the static library and the associated headers +INSTALLLIBDIR := $(CURDIR)/../lib +INSTALLINCDIR := $(CURDIR)/../include + +# full path to the target +FULLTARGET := $(CURDIR)/$(TARGET) + +# commands and flags +CC = gcc +CFLAGS = -std=c99 -Wall -g +CPPFLAGS = -MD -Isrc -Itests -I$(INSTALLINCDIR) -I$(CUNITINCDIR) -DTARGET=$(FULLTARGET) +LDFLAGS = -static -z muldefs +ARFLAGS = rc + +# targets which get always visited (without checking any up-to-date state) +.PHONY: default clean test doc install mkdir + +# targets +default: $(FULLTARGET) + @echo "#### $< built ####" + +$(FULLTARGET): mkdir $(OBJECTS) Makefile + $(AR) $(ARFLAGS) $@ $(OBJECTS) + +clean: + $(RM) $(TARGET) $(OBJECTS) $(DEPS) $(TSTTARGET) $(TSTOBJECTS) $(TSTDEPS) $(wildcard */*~ *~ tests/*.txt) + $(RM) -r $(CREATEDIRS) + @echo "#### $@ done ####" + +install: $(FULLTARGET) + mkdir -p $(INSTALLLIBDIR) $(INSTALLINCDIR) + cp -f $(FULLTARGET) $(INSTALLLIBDIR)/ + cp -f $(HEADERS) $(INSTALLINCDIR)/ + @echo "#### $< installed ####" + +doc: + doxygen ../Doxyfile > /dev/null + @echo "#### $@ done ####" + +test: $(TSTTARGET) + (cd tests; $(TSTTARGET)) + @echo "#### $< executed ####" + +$(TSTTARGET): $(FULLTARGET) $(TSTOBJECTS) + $(LINK.c) -o $(TSTTARGET) $(TSTOBJECTS) $(FULLTARGET) -L$(CUNITLIBDIR) -lcunit + @echo "#### $@ built ####" + + +# create needed directories (ignoring any error) +mkdir: + -mkdir -p $(CREATEDIRS) + +# read in the gcc -MD ... produced dependencies (ignoring any error) +-include $(DEPS) $(TSTDEPS) diff --git a/testlib/mainpage.dox b/testlib/mainpage.dox new file mode 100644 index 0000000..b77c7e5 --- /dev/null +++ b/testlib/mainpage.dox @@ -0,0 +1,10 @@ +/** + * @mainpage PROGC - Labs + * + * @section Purpose + * + * This is a supporting test library for PROGC tests. + * + * This project needs to be built before the labs. + * It provides the needed header files in the include folder and the libraries in the lib folder. + */ diff --git a/testlib/src/test_utils.c b/testlib/src/test_utils.c new file mode 100644 index 0000000..fe6fa85 --- /dev/null +++ b/testlib/src/test_utils.c @@ -0,0 +1,117 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Implementation of the test_utils. + */ +#include +#include +#include +#include +#include +#include +#include "CUnit/Basic.h" +#include "test_utils.h" + +int file_exists(const char file_path[]) +{ + // preconditions + assert(file_path); + int errno_safe = errno; + errno = 0; + // try and forgive... + FILE *file = fopen(file_path, "r"); + assert(file || (!file && (errno == ENOENT))); // either it exists or "No such file or directory" error code (see man errno). + errno = errno_safe; // fopen will set errno if the file does not exist + if (file) { + assert(0 == fclose(file)); + return 1; // existed + } + return 0; // did not exist +} + +void remove_file_if_exists(const char file_path[]) +{ + // we take the risk that between checking and removing, some undesired file access may happen and jeopardize the control logic... + if (file_exists(file_path)) { + assert(0 == unlink(file_path)); + } +} + +void assert_lines(const char file[], const char *lines[], size_t n_lines) +{ + // preconditions + CU_ASSERT_PTR_NOT_NULL_FATAL(file); + CU_ASSERT_PTR_NOT_NULL_FATAL(lines); + + // file access may always fail + FILE *input = fopen(file, "r"); + if (!input) perror(file); + CU_ASSERT_PTR_NOT_NULL_FATAL(input); + + // process all lines and compare to the file content + size_t i = 0; + size_t n = 0; + for(i = 0; i < n_lines && n == 0; i++) { + const char *line = lines[i]; + CU_ASSERT_PTR_NOT_NULL(line); + if (line) { + size_t len = n = strlen(line); + CU_ASSERT(n > 0); + while (n > 0) { + int c = fgetc(input); + CU_ASSERT_FALSE(feof(input)); + CU_ASSERT_EQUAL(c, *line); + if (c != *line) { + printf("\nfile %s: line %zu, pos %zu = %d = '%c', expected = %d = '%c'\n", + file, i+1, len-n+1, c, isprint(c) ? c : '.', *line, isprint(*line) ? *line : '.'); + break; + } + line++; + n--; + } + } + } + CU_ASSERT_FALSE(feof(input)); + (void)fgetc(input); + CU_ASSERT_TRUE(feof(input)); + CU_ASSERT_EQUAL(i, n_lines); + CU_ASSERT_EQUAL(n, 0); + + // successfully reached the end... + int fclose_result = fclose(input); + CU_ASSERT(0 == fclose_result); + + // print actual versus expected in case of error + if (n != 0 || i != n_lines) { + printf("---- EXPECTED ----\n"); + for(int i = 0; i < n_lines; i++) { + const char *p = lines[i]; + while(p && *p) { + putchar(*p); + p++; + } + } + printf("---- ACTUAL (%s) ----\n", file); + FILE* fd = fopen(file, "r"); + int last = 0; + while(fd && !feof(fd)) { + int c = fgetc(fd); + if (c != EOF) { + last = c; + putchar(c); + } + } + if (fd) fclose(fd); + if (last != '\n') putchar('\n'); + printf("---- END ----\n"); + } + +} diff --git a/testlib/src/test_utils.h b/testlib/src/test_utils.h new file mode 100644 index 0000000..525f578 --- /dev/null +++ b/testlib/src/test_utils.h @@ -0,0 +1,146 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Common test utilities for writing PROGC tests. + */ +#ifndef _TEST_UTILS_H_ +#define _TEST_UTILS_H_ + +#include +#include +#include "CUnit/Basic.h" + +/// Stringize macro (evaluates the passed macro and makes a string out of it). Non-macros are stringized as-is. +#define XSTR(x) STR(x) +/// Stringize macro (does *not* evaluate the passed macro - makes the macro *name* a string). Non-macros are stringized as-is. +#define STR(x) #x + +/** + * @brief Gracefully checks by means of fopen(..., "r") if a file exists. + * @param[in] file_path The file to check for existence. + * @returns Returns 0 if the file does not exist, 1 otherwise. + * @remark If the file does not exist, errno was affected. This function takes care of this, i.e. it safes and restores the original errno state. + * @remark In case of an error situation, the function fails with a hard assert.h assertion violation. + * This allows to use the function outside of a tests, e.g. in setup and teardown functions. + */ +int file_exists(const char file_path[]); + +/** + * @brief Removes the given file if it exists. + * @param[in] file_path: The path to the file which gets removed if it exists. + * @remark Silently ignores any error. If you are interrested in the errors, set first *errno* to zero, and check it after the call. + * @remark In case of an error situation, the function fails with a hard assert.h assertion violation. + * This allows to use the function outside of a tests, e.g. in setup and teardown functions. + */ +void remove_file_if_exists(const char file_path[]); + +/** + * @brief Checks if the file content matches exactly the given lines. + * The lines must contain the new-line character. This is especially important if a file terminates without a new line character. + * @param[in] file: The path to the file to check against the lines. + * @param[in] lines: The array of lines which must match the file content exactly. Newlines must be part of the lines too. + * @param[in] n_lines: The number of lines in the line array. + */ +void assert_lines(const char file[], const char *lines[], size_t n_lines); + +/** + * @brief The test function's callback type. + */ +typedef void (*test_function_t)(void); + +/** + * @brief Boiler-plate code as a macro to define the complete body of the main function of the tests suite. + * @param[in] suite: The name of the test suite (const char *). + * @param[in] setup: The setup callback function which is executed before the first test is executed (*int setup(void)*). + * @param[in] cleanup: The teardown callback function which is executed after the last test is executed (*int teardown(void)*). + * @param[in] ...: The variable list of test_function callback (*void test(void)*) which constitue the test cases. They are executed in the given sequence. + * @remark Example code (see also CUnit Framework Documentation): + * @code + * // setup and teardown + * int setup(void) + * { + * // do some initialization if needed + * // ... + * return 0; // success + * } + * int teardown(void) + * { + * // do some cleanup if needed + * // ... + * return 0; // success + * } + * // tests + * void test_main_no_args(void) + * { + * // arrange + * // ... + + * // act + * // ... + + * // assert (use the CUnit CU_ASSERT_... macros) + * // ... + * } + * void test_main_one_arg(void) + * { + * //... + * } + * void test_main_two_args(void) + * { + * //... + * } + + * // execute the tests + * int main(void) + * { + * TestMainBasic("Hello World", setup, teardown + * , test_main_no_args + * , test_main_one_arg + * , test_main_two_args + * ); + * } + * @endcode + */ +#define TestMainBasic(suite, setup, cleanup, ...) \ + do { \ + CU_pSuite pSuite = NULL; \ + \ + /* initialize the CUnit test registry */ \ + if (CUE_SUCCESS != CU_initialize_registry()) \ + return CU_get_error(); \ + \ + /* functions and their names */ \ + test_function_t tests[] = { __VA_ARGS__ }; \ + char all_names[] = #__VA_ARGS__; \ + const size_t n = sizeof(tests)/sizeof(*tests); \ + const char *names[sizeof(tests)/sizeof(*tests)] = { strtok(all_names, ", ") } ; \ + for(size_t i = 1; i < n; i++) { \ + names[i] = strtok(NULL, ", "); \ + } \ + /* init suite and tests */ \ + pSuite = CU_add_suite(suite, setup, cleanup); \ + if (pSuite) { \ + size_t i; \ + for(i = 0; i < n; i++) { \ + if (!CU_add_test(pSuite, names[i], tests[i])) break; \ + } \ + /* Run all tests using the CUnit Basic interface */ \ + if (i == n) { \ + CU_basic_set_mode(CU_BRM_VERBOSE); \ + CU_basic_run_tests(); \ + } \ + } \ + CU_cleanup_registry(); \ + return CU_get_error(); \ + } while(0) \ + + +#endif diff --git a/testlib/tests/tests.c b/testlib/tests/tests.c new file mode 100644 index 0000000..54b03fd --- /dev/null +++ b/testlib/tests/tests.c @@ -0,0 +1,170 @@ +/* ---------------------------------------------------------------------------- + * -- _____ ______ _____ - + * -- |_ _| | ____|/ ____| - + * -- | | _ __ | |__ | (___ Institute of Embedded Systems - + * -- | | | '_ \| __| \___ \ Zuercher Hochschule Winterthur - + * -- _| |_| | | | |____ ____) | (University of Applied Sciences) - + * -- |_____|_| |_|______|_____/ 8401 Winterthur, Switzerland - + * ---------------------------------------------------------------------------- + */ +/** + * @file + * @brief Test suite for the given package. + */ +#include +#include +#include "CUnit/Basic.h" +#include "test_utils.h" + +#ifndef TARGET // must be given by the make file --> see test target +#error missing TARGET define +#endif + +/// @brief The name of the STDOUT text file. +#define OUTFILE "stdout.txt" +/// @brief The name of the STDERR text file. +#define ERRFILE "stderr.txt" +/// @brief Some test output file +#define EMPTYFILE "EmptyFile.txt" +/// @brief Some test output file +#define NONEMPTYFILE "NonEmptyFile.txt" +/// @brief Some test output file +#define NONEMPTYFILE_NONLEND "NonEmptyFileNoNlEnd.txt" + +// setup & teardown +static int setup(void) +{ + remove_file_if_exists(OUTFILE); + remove_file_if_exists(ERRFILE); + remove_file_if_exists(EMPTYFILE); + remove_file_if_exists(NONEMPTYFILE); + remove_file_if_exists(NONEMPTYFILE_NONLEND); + // do nothing + return 0; // success +} +static int teardown(void) +{ + // do nothing + return 0; // success +} + + +// tests +static void test_remove_file_that_exists(void) +{ + // ** arrange ** + + // create a file + FILE *file = fopen(OUTFILE, "w"); + CU_ASSERT_PTR_NOT_NULL(file); + CU_ASSERT_EQUAL(fclose(file), 0); + errno = 0; + + // ** act ** + remove_file_if_exists(OUTFILE); + + // ** assert ** + + // make sure the file is removed + CU_ASSERT_EQUAL(errno, 0); + file = fopen(OUTFILE, "r"); + CU_ASSERT_TRUE(!file && errno == ENOENT); + // cleanup gracefully + if (file) CU_ASSERT_EQUAL(fclose(file), 0); + errno = 0; +} +static void test_remove_file_that_does_not_exist(void) +{ + // ** arrange ** + remove_file_if_exists(OUTFILE); + // the file is now supposed to not exist any more --> see test_remove_file_that_exists test result + + // ** act ** + + // call with a not existing file + remove_file_if_exists(OUTFILE); + // must not fail in any internal assertion + + // ** assert ** + + // make sure the file is removed + CU_ASSERT_EQUAL(errno, 0); + FILE *file = fopen(OUTFILE, "r"); + CU_ASSERT_TRUE(!file && errno == ENOENT); + // cleanup gracefully + if (file) CU_ASSERT_EQUAL(fclose(file), 0); + errno = 0; +} + +static void test_assert_lines_empty_file(void) +{ + // ** arrange ** + + // empty file + remove_file_if_exists(EMPTYFILE); + FILE *file = fopen(EMPTYFILE, "w"); + CU_ASSERT_PTR_NOT_NULL_FATAL(file); + CU_ASSERT_EQUAL(fclose(file), 0); + const char *lines[] = {}; + + // ** act ** + assert_lines(EMPTYFILE, lines, sizeof(lines)/sizeof(*lines)); + + // ** assert ** + + // no assertions should have happened within assert_lines(...) +} +static void test_assert_lines_non_empty_file(void) +{ + // ** arrange ** + + // reference file + remove_file_if_exists(NONEMPTYFILE); + FILE *file = fopen(NONEMPTYFILE, "w"); + CU_ASSERT_PTR_NOT_NULL_FATAL(file); + CU_ASSERT_EQUAL(fprintf(file, "LINE1\n"), 6); + CU_ASSERT_EQUAL(fprintf(file, "LINE2\n"), 6); + CU_ASSERT_EQUAL(fclose(file), 0); + const char *lines[] = { "LINE1\n", "LINE2\n"}; + + // ** act ** + assert_lines(NONEMPTYFILE, lines, sizeof(lines)/sizeof(*lines)); + + // ** assert ** + + // no assertions should have happened within assert_lines(...) +} +static void test_assert_lines_no_newline_at_the_end(void) +{ + // ** arrange ** + + // reference file + remove_file_if_exists(NONEMPTYFILE_NONLEND); + FILE *file = fopen(NONEMPTYFILE_NONLEND, "w"); + CU_ASSERT_PTR_NOT_NULL_FATAL(file); + CU_ASSERT_EQUAL(fprintf(file, "LINE1\n"), 6); + CU_ASSERT_EQUAL(fprintf(file, "LINE2"), 5); + CU_ASSERT_EQUAL(fclose(file), 0); + const char *lines[] = { "LINE1\n", "LINE2"}; + + // ** act ** + assert_lines(NONEMPTYFILE_NONLEND, lines, sizeof(lines)/sizeof(*lines)); + + // ** assert ** + + // no assertions should have happened within assert_lines(...) +} + +/** + * @brief Registers and runs the tests. + */ +int main(void) +{ + TestMainBasic("PROGC Test Lib", setup, teardown + , test_remove_file_that_exists + , test_remove_file_that_does_not_exist + , test_assert_lines_empty_file + , test_assert_lines_non_empty_file + , test_assert_lines_no_newline_at_the_end + ); +}