diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c5a7241 --- /dev/null +++ b/.clang-format @@ -0,0 +1,108 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 78 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 4 +UseTab: Never +... + diff --git a/.gitignore b/.gitignore index 2f7e18f..413159d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.o a.out vgcore.* -docs/* lib/* obj/* +**/*build diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..593a9d9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,38 @@ +sudo: false +language: c + +os: + - linux + - osx +dist: xenial +osx_image: xcode10.2 + +env: + - CC=gcc + - CC=clang + +addons: + apt: + packages: + - clang-format + - cppcheck + homebrew: + packages: + - ninja + - meson + - clang-format + - cppcheck + +before_install: + - |+ + if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + sudo apt-get -q update + sudo apt-get -y install meson/xenial-backports + fi + +before_script: + - meson _build + - ninja -C _build cppcheck + +script: + - ninja -C _build all diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..85abdff --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2018-2019 Keefer Rourke + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile index a69426d..0658603 100644 --- a/Makefile +++ b/Makefile @@ -33,13 +33,14 @@ STD = -std=c99 -D_POSIX_C_SOURCE=199506L WD := $(PWD) INCLDIR = $(WD)/include SRCDIR = $(WD)/src -OBJDIR = $(WD)/obj -OUTDIR = $(WD)/lib +BUILDDIR = $(WD)/_build +OBJDIR = $(BUILDDIR)/obj +OUTDIR = $(BUILDDIR)/lib # DOCDIR and TEXDIR must match the appropriate directories specified in the # Doxyfile; TEXDIR is a subdirectory of DOCDIR DOCDIR = docs -TEXDIR = latex +BUILTDOCDIR = $(BUILDDIR)/docs # library output OUTNAME = libtdd @@ -86,6 +87,9 @@ $(OBJDIR): $(OUTDIR): @mkdir -p $(OUTDIR) +$(BUILTDOCDIR): + @mkdir -p $(BUILTDOCDIR) + # generate docs with doxygen # this is intended to be used with a Doxyfile that specified LaTeX output # modify as required for different documentation formats @@ -93,24 +97,28 @@ $(OUTDIR): # inelegant, but if doxygen fails for some reason, it is not the end of the # world .PHONY: doc doc-clean -doc: Doxyfile +doc: $(BUILTDOCDIR) $(DOCDIR)/Doxyfile.in + -@cp $(DOCDIR)/Doxyfile.in Doxyfile + -@sed -i "s/@HAS_DOT@/NO/g" Doxyfile + -@sed -i "s/@HTML_PAGES@/YES/g" Doxyfile + -@sed -i "s/@GEN_LATEX@/NO/g" Doxyfile + -@sed -i "s/@MAN_PAGES@/YES/g" Doxyfile + -@sed -i "s/@PROJECT_NAME@/$(OUTNAME)/g" Doxyfile + -@sed -i "s/@VERSION@//g" Doxyfile + -@sed -i "s|@DOCS_OUTPUT_DIR@|$(BUILTDOCDIR)|g" Doxyfile + -@sed -i "s|@INCLUDE_DIR@|$(INCLDIR)|g" Doxyfile + -@sed -i "s|@README_PATH|README.md|g" Doxyfile @doxygen 2>/dev/null 1>&2 - -@echo 'Generating application internal documentation...' -# generate PDF from LaTeX sources - -@cd $(DOCDIR)/$(TEXDIR) && $(MAKE) 2>/dev/null 1>&2 - -@mv $(DOCDIR)/$(TEXDIR)/refman.pdf $(DOCDIR) + -@rm Doxyfile -@echo 'Generated application internal documentation.' doc-clean: - -@rm -r $(DOCDIR)/$(TEXDIR) - -$(DOCDIR): - @mkdir -p $(DOCDIR) + -@rm -r $(BUILTDDOCDIR) # clean entire project directory .PHONY: clean cleaner clean: - -@rm -rf $(OBJDIR) $(DOCDIR) + -@rm -rf $(OBJDIR) cleaner: -@rm -rf $(OUTDIR) diff --git a/README.md b/README.md index b4de545..4781907 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ libtdd ====== +[![repo ci status](https://img.shields.io/travis/keeferrourke/libtdd.svg) +![dev ci status](https://img.shields.io/travis/com/keeferrourke/libtdd/development.svg?label=build%20%28dev%29)](https://travis-ci.com/keeferrourke/libtdd) +[![v0.0.2](https://img.shields.io/badge/version-0.0.2%20%28alpha%29-blue.svg)](#) + `libtdd` is a minimalist testing framework inspired by the Golang testing pkg. It is designed to provide a framework which may be used to build scaffolding during Test Driven Development (TDD). -This small C library attempts to provide a consistent API for creating, -running, and checking the results of tests. There is very little -boilerplate required in the main function. +This small C library attempts to provide a simple, featureful API for +creating, running, and checking the results of tests. Simply declare +test functions, add them to a suite in your `main()`, compile and run :) Features @@ -20,51 +24,65 @@ Features * Catch and count crashes (SIGSEGV handler) -Build and usage ---------------- +Build and Installation +---------------------- -The Makefile included with this project creates static and dynamic -libraries in the `lib/` directory. To build this, simply run: +### Meson (Ninja) - make lib # libtdd.a - make dylib # libtdd.so (or libtdd.dylib on macOS/Darwin) +To build this library, run -Colour output support can be enabled at compile time by including the -`DEFINE=-DUSE_COLOUR` directive on the `make` line. ex. + meson setup --prefix=/usr/local _build + ninja -C _build - make dylib DEFINE=-DUSE_COLOUR # enables pretty colour printing +To install the library and documentation, run -You can then include the outputted `libtdd.so` or `libtdd.a` in your -project as is required. + ninja -C _build all docs install -API documentation can be compiled with Doxygen and LaTeX via: +Builds have been tested for Fedora 29 Workstation, Ubuntu 16.04, and +macOS Mojave with both `clang` and `gcc`. - make docs +#### Build options -See the Makefile for other relevant build targets. +Build options are detailed in the `meson_options.txt` file. +You may modify them there as required. -**NOTE** macOS/Darwin support is untested at this time. -This library was built and tested on Fedora 27 Workstation with glibc -v2.26. +### Make -Example program ---------------- +This project was originally built with GNU Make before I migrated to +Meson/Ninja. The included Makefile will not be updated, but also won't +be removed in the foreseeable future. -A simple example program that demonstrates all features provided by this -library can be found in the `examples/` directory of this project. -It can be compiled and run as follows: +Usage +----- + +### Documentation + +This library is extensively documented. You can generate the +documentation with `doxygen`. + +Documentation is provided in the following formats: + + - HTML + - PDF (requires `LaTeX`) + - man pages (run `man -m _build/docs/man $page` to view) + +To build the documentation, run the following: - make lib - cc -o tdd_example -std=c99 -Iinclude examples/main.c -pthread \ - -Llib -ltdd - ./tdd_example + ninja -C _build docs +It will be output to `_build/docs`. -Conventions ------------ +### Example program -### Testing +A simple example program that demonstrates all features provided by this +library can be found in the `examples/` directory of this project. + +It is built as part of the default build target, + +### Conventions to follow + +#### Test functions Every testing function is defined as a `void* test_myfunc(void* t)` where the parameter `t` is a `test_t` that records information about @@ -73,30 +91,38 @@ of each test. The parameter and return types are `void*` because tests are run in `pthreads`. Despite this, there is no need to cast `t` when calling the provided API functions. -The start of a test is signified by `test_start(t)` and the end -of a test is signified by `test_done(t)`, but these calls are completely -optional if only testing for correctness and not speed. +A test is assumed to have succeeded if it runs to completion without +raising any failure or error flags. A test can be marked as a failure +by a call to `test_fail(t, "reason")`; you should return from the test +function shortly after this call is made. Non-critical errors that +should not end a test can be recorded by `test_error(t, "reason")`. + +The macro `test_fatal(t, "reason")` exists for convenience so you don't +need to return from a failure condition (this is handled automatically) +but should be used sparingly as you won't have an opportunity to run +clean-up code if it executes. -You can end the execution of a test early and mark it as a failure -through a call to `test_fail(t, "message")`. Non-critical errors that -should not end a test can be recorded by `test_error(t, "message")`. +The start of a test can be optionally flagged by `test_timer_start(t)` +and the end the test can be similarly flagged by `test_timer_end(t)`, +if the name of your test is prefixed by `bench_` then these functions +will be called for you automatically. -### Benchmarking +#### Benchmarked functions -If benchmarking, then for convenience, the `test_start(t)` and -`test_done(t)` function calls may be omitted within a test body if the -test name is prefixed by `bench_`. These times will be automatically -recorded by the suite. +If benchmarking a test, then the `test_timer_start(t)` and +`test_timer_end(t)` function calls will automatically be called if +the test name is prefixed by `bench_`. These times will be automatically +recorded by the suite and reported at the end. -Calls to `test_start(t)` and `test_done(t)` may still be made to -override the start and end times if the benchmarks require setup and +Calls to `test_timer_start(t)` and `test_timer_end(t)` may still be made +to override the start and end times if the benchmarks require setup and teardown code that should not be included in the recorded time. i.e. a benchmarking function may be simply defined as void* bench_func(void* t) { ... - return; + return NULL; } and can be added to a test suite as a benchmarking function by @@ -107,23 +133,29 @@ After execution of each benchmark function, a short summary of runtime is printed. -Bugs and future development ---------------------------- +Future development +------------------ -No bugs to speak of at this moment :) +There are no bugs to speak of at the moment :) + +### Possible feature work -Future development: * It might be nice to optionally run tests in parallel; currently all - tests run sequentially in the order they are added to the suite. + tests run sequentially in the order they are added to the suite + * I'll probably factor out the reporter interface so that new reporters + can be written and used to display results + * It might be good to specify how long a benchmarked function has to + run before it might be considered a failure due to poor performance License information ------------------- -Copyright (c) 2018 Keefer Rourke +Copyright (c) 2018-2019 Keefer Rourke + +This software is released under the ISC License. See [LICENSE](LICENSE) +for more details. -This software is released under the ISC License. See LICENSE for more -details. Alternatives ------------ diff --git a/Doxyfile b/docs/Doxyfile.in similarity index 99% rename from Doxyfile rename to docs/Doxyfile.in index ea3eac3..ef8d1e0 100644 --- a/Doxyfile +++ b/docs/Doxyfile.in @@ -23,7 +23,7 @@ # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "libtdd" +PROJECT_NAME = "@PROJECT_NAME@" # The INPUT tag is used to specify the files and/or directories that contain @@ -32,14 +32,14 @@ PROJECT_NAME = "libtdd" # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = include +INPUT = "@INCLUDE_DIR@" # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = docs +OUTPUT_DIRECTORY = "@DOCS_OUTPUT_DIR@" # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text @@ -54,7 +54,7 @@ DOXYFILE_ENCODING = UTF-8 # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = @VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -495,7 +495,7 @@ EXTRACT_ANON_NSPACES = NO # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. -HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_MEMBERS = YES # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set @@ -503,7 +503,7 @@ HIDE_UNDOC_MEMBERS = NO # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. -HIDE_UNDOC_CLASSES = NO +HIDE_UNDOC_CLASSES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO, these declarations will be @@ -1106,7 +1106,7 @@ IGNORE_PREFIX = # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. -GENERATE_HTML = NO +GENERATE_HTML = @HTML_PAGES@ # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of @@ -1225,7 +1225,7 @@ HTML_COLORSTYLE_GAMMA = 80 # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_TIMESTAMP = NO +HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that @@ -1661,7 +1661,7 @@ EXTRA_SEARCH_MAPPINGS = # If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. # The default value is: YES. -GENERATE_LATEX = YES +GENERATE_LATEX = @GEN_LATEX@ # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of @@ -1897,7 +1897,7 @@ RTF_SOURCE_CODE = NO # classes and files. # The default value is: NO. -GENERATE_MAN = NO +GENERATE_MAN = @MAN_PAGES@ # The MAN_OUTPUT tag is used to specify where the man pages will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of diff --git a/docs/make_docs.sh b/docs/make_docs.sh new file mode 100755 index 0000000..e35eb60 --- /dev/null +++ b/docs/make_docs.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +cd "${MESON_SOURCE_ROOT}" + +doxyfile=$1 +output_dir=$2 +pdf_doc=$3 + +doxygen $doxyfile +make -C $(dirname $pdf_doc) +mv $pdf_doc $output_dir +rm -r $(dirname $pdf_doc) diff --git a/docs/meson.build b/docs/meson.build new file mode 100644 index 0000000..3a92a9c --- /dev/null +++ b/docs/meson.build @@ -0,0 +1,14 @@ +doxyfile = configure_file(input: 'Doxyfile.in', output: 'Doxyfile', + configuration: cdata, install: false) + +docs_out = cdata.get('DOCS_OUTPUT_DIR') +latex_dir = join_paths(docs_out, 'latex') +doc_name = 'refman.pdf' +make_docs = [ + 'make_docs.sh', + doxyfile, + docs_out, + join_paths(latex_dir, doc_name) +] + +run_target('docs', command: make_docs) diff --git a/examples/main.c b/examples/main.c index 5e6866b..d82743e 100644 --- a/examples/main.c +++ b/examples/main.c @@ -10,39 +10,39 @@ #include "tdd.h" -static void* test_errfunc(void* t); -static void* test_failfunc(void* t); +static void* test_errfn(void* t); +static void* test_failfn(void* t); static void* test_timer(void* t); -static void* bench_func(void* t); -static void* test_segvfunc(void* t); +static void* bench_fn(void* t); +static void* test_segvfn(void* t); int main(int argc, char* argv[]) { - suite_t* s = suite_init(); + suite_t* s = suite_new(); // suite_add is a variadic function that can be used to add an arbitrary // number of tests at once // new tests are added with a function pointer, a name, and an optional // description; if the description is to be omitted, you may pass NULL - suite_add(s, 2, - newtest(&test_timer, "test_timer", - "Manual benchmark. Requires timespan to be printed " - "manually."), - newtest(&bench_func, "bench_func", - "Builtin benchmark. Execution timespan is printed " - "automatically below.")); - - // suite_addtest is a function that simply appends a test to the list of + suite_add( + s, 2, + runner_new(&test_timer, "test_timer", + "Manual benchmark. Requires timespan to be printed " + "manually."), + runner_new(&bench_fn, "bench_fn", + "Builtin benchmark (name prefixed by 'bench_')." + "Execution timespan is printed automatically below.")); + + // suite_add_test is a function that simply appends a test to the list of // tests in the suite; could be used to programmatically add tests - suite_addtest(s, newtest(&test_errfunc, "test_errfunc", "Raises error.")); + suite_add_test(s, runner_new(&test_errfn, "test_errfn", "Raises error.")); // suite_add can be called multiple times to add groups of tests to the // test suite suite_add(s, 2, - newtest(&test_failfunc, "test_failfunc", "Fails immediately."), - newtest(&test_segvfunc, "test_segvfunc", "Raises SIGSEGV.")); + runner_new(&test_failfn, "test_failfn", "Fails immediately."), + runner_new(&test_segvfn, "test_segvfn", "Raises SIGSEGV.")); printf("Running tests ignoring failures.\n"); - suite_run(s, false); if (s->finished) { printf("Suite ran all tests.\n"); @@ -65,46 +65,48 @@ int main(int argc, char* argv[]) { } // print summary statistics - suite_stats_t* stats = suite_stats(s); + suite_stats_t* stats = suite_get_stats(s); printf("Suite encountered: %d segmentation faults.\n", s->n_segv); - // delete the suite and all testfns associated with it + // delete the suite and all runners associated with it suite_del(s); int ret = stats->n_fail; - suite_delstats(stats); + suite_stats_del(stats); return ret; } -void* test_errfunc(void* t) { +void* test_errfn(void* t) { test_error(t, "a non-critical error ocurred."); return NULL; } -void* test_failfunc(void* t) { - test_fail(t, "a critical error occurred!"); - printf("this code should not run unless fatal failures are disabled!\n"); +void* test_failfn(void* t) { + test_fatal(t, "a critical error occurred!"); + printf("this code will not be reached!"); return NULL; } void* test_timer(void* t) { - test_start(t); + test_timer_start(t); + char* s = calloc(128, sizeof(char)); strcpy(s, "This function is being timed!"); free(s); - test_done(t); + + test_timer_end(t); return NULL; } -void* bench_func(void* t) { +void* bench_fn(void* t) { char* s = calloc(128, sizeof(char)); strcpy(s, "This function is being timed!"); free(s); return NULL; } -void* test_segvfunc(void* t) { +void* test_segvfn(void* t) { raise(SIGSEGV); return NULL; } diff --git a/examples/meson.build b/examples/meson.build new file mode 100644 index 0000000..51a1873 --- /dev/null +++ b/examples/meson.build @@ -0,0 +1,3 @@ +main_source = files(['main.c']) +example_sources += main_source + diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..ccbdbbb --- /dev/null +++ b/include/meson.build @@ -0,0 +1,3 @@ +project_api_headers += files('tdd.h') +project_headers += files(['strutil.h','timeutil.h']) +project_includes += include_directories('.') diff --git a/include/strutil.h b/include/strutil.h index c40c6cd..d23191e 100644 --- a/include/strutil.h +++ b/include/strutil.h @@ -2,7 +2,6 @@ * @private * @file strutil.h * @author Keefer Rourke - * @date 08 Apr 2018 * * @mainpage * This library provides a simple framework for defining, organizing, and @@ -15,48 +14,67 @@ * and create any resource files, etc. that might be expected by tests in the * suite. * - * @section example Example usage of this library: + * @section example Example usage of this library + * + * Below is a basic example. See the source code `examples/` directory for + * more. * *``` * #include * #include "tdd.h" * * static void* error_func(test_t* t) { - * test_error(t, "oops!"); + * test_error(t, "oops I made a mistake!"); + * printf("I should keep going though!"); + * test_error(t, "oops I made another mistake :("); * return NULL; * } * * static void* fail_func(test_t* t) { - * test_fail(t, "badness!"); + * ... + * test_fail(t, "I made a critical mistake!"); + * // clean up code + * return NULL; + * } + * + * static void* fatal_func(test_t* t) { + * test_fatal(t, "I made a critical mistake!"); + * printf("this code will not be reached"); * return NULL; * } * * int main(int argc, char* argv[]) { * // create test suite - * suite_t* s = suite_init(); + * suite_t* s = suite_new(); * * // initalize tests * // variadic func; add as many tests as needed - * suite_add(s, 2, newtest(&error_func, "error", NULL), - * newtest(&fail_func, "fail", NULL)); + * suite_add(s, 3, + * runner_new(&error_func, "error", NULL), + * runner_new(&fail_func, "fail", NULL), + * runner_new(&fatal_func, "fatal ends early", NULL)); * * // run the test suite * suite_run(s); - * stats = suite_stats(s); + * stats = suite_get_stats(s); * suite_del(s); * ... * - * return stats.n_error + * int retcode = stats.n_error; + * suite_stats_del(stats); + * return retcode; * } *``` * * @section api Test API - * Test functions must always be defined by one of the following signatures: + * Test functions should be defined by one of the following signatures: * - `void* test_func(test_t* t);` for regular tests * - `void* bench_func(test_t* t);` for benchmarking tests * * These functions are added to a test suite using either the `suite_add(...)` - * or `suite_addtest(testfn*)` API calls. + * or `suite_add_test(runner_t*)` API calls. + * + * The name of the test when added to the suite matters. * * @section benchmarking Benchmarking * Test functions may be specially marked as time-sensitive, as in where @@ -65,9 +83,14 @@ * functions A timer will be started when the test runs, and stopped when the * test finishes. A report of the runtime is printed after the test finishes. * - * For example: + * For example, the following function is added as a benchmarked function. + * ``` + * suite_add_test(runner_new(&bench_func, "bench_func", "time sensitive test")); + * ``` + * + * The following code is added as a regular test. It will not be timed. * ``` - * suite_addtest(newtest(&bench_func, "bench_func", "time sensitive test")); + * suite_add_test(runner_new(&test_func, "test_func", "basic test")); * ``` * * @section notes Notes @@ -98,15 +121,14 @@ extern volatile sig_atomic_t tdd_sigsegv_caught; **/ void tdd_sigsegv_handler(int sig); -struct _testfn; -struct test_s; -struct suite_s; - /** - * Testing structure. This structure is the only parameter in all testing - * functions. If at any point during a testing function, unexpected bevahiour - * occurs or the test downright fails, you should call `test_error(t)` and - * `test_fail(t)` respectively. + * Testing structure which records results from tests. You will never need to + * initialize or free this structure yourself. + * + * Every testing function should take a pointer to a test_t as its only + * parameter. If at any point during a testing function, unexpected bevahiour + * occurs or the test downright fails, you should call `test_error(t)`, + * `test_fail(t)`, or `test_fatal(t)` respectively. **/ typedef struct test_t { /** @@ -123,8 +145,8 @@ typedef struct test_t { **/ int err; /** - * `fail_msg` is a message that is by test_fail() indicating the reason - * for test failure. Heap allocated. + * `fail_msg` is the message that is set by test_fail() indicating the + * reason for test failure. Heap allocated. **/ char* fail_msg; /** @@ -155,112 +177,152 @@ typedef struct test_t { /** * `fail()` marks the test as failed with a message explaining the reason * for failure. + * @see test_fail **/ - void (*fail)(struct test_t* t, char* msg); + void* (*fail)(struct test_t* t, char* msg); /** * `error()` marks the test as having encountered an error with a message * explaining the reason for the error + * @see test_error + **/ + void* (*error)(struct test_t* t, char* msg); + /** + * `begin()` starts a benchmark timer for the test + * @see test_timer_start + **/ + void* (*begin)(struct test_t* t); + /** + * `done()` marks the test as finished + * @see test_timer_end **/ - void (*error)(struct test_t* t, char* msg); - /** `done()` marks the test as finished **/ - void (*done)(struct test_t* t); + void* (*done)(struct test_t* t); } test_t; /** - * `test_t_init()` makes a new test function. Not to be called explicitly. + * `tdd_test_new()` makes a new test function. Not to be called explicitly. * @private + * * @return A pointer to a fully initialized `test_t` structure. **/ -test_t* test_t_init(); +test_t* tdd_test_new(); /** - * `test_t_del()` frees all memory associated with a `test_t` structure. + * `tdd_test_del()` frees all memory associated with a `test_t` structure. * Not to be called explicitly. * @private + * * @param t - pointer to the `test_t` structure to be freed * @return int */ -int test_t_del(test_t* t); +int tdd_test_del(test_t* t); + +/** + * `test_fatal()` is a convenience macro that will fail and end a test. + * + * @param t - pointer to a `test_t` structure to capture the context of a + * test failure + * @param msg - character string indicating the reason for failure + */ +#define test_fatal(t, msg) return test_fail(t, msg); /** - * `test_fail()` marks the test as failed with a message. Failures are - * identified as critical errors that will not allow testing to continue. - * Use `test_fail()` to catch fundamental errors in program function - * execution. To be called within a `testfn::fn`. Alternatively may be called - * through the `test_t::fail` interface. + * `test_fail()` marks the test as failed with a message. + * + * Failures are identified as critical errors that will not allow testing to + * continue. Use `test_fail()` to catch fundamental errors in program + * function execution. To be called within a `runner_t::fn`. Alternatively may + * be called through the `test_t::fail` interface. + * * @param t - pointer to a `test_t` structure to capture the context of a * test failure * @param msg - character string indicating the reason for failure **/ -void test_fail(test_t* t, char* msg); +void* test_fail(test_t* t, char* msg); /** - * `test_error()` marks the test as having encountered an error. Errors are - * identified as non-critical flaws in program function execution which do - * not prevent continuation of testing. Use `test_error()` to record + * `test_error()` marks the test as having encountered an error. + * + * Errors are identified as non-critical flaws in program function execution + * which do not prevent continuation of testing. Use `test_error()` to record * unexpected but valid return values and similar flaws. To be called within a - * `testfn::fn`. Alternatively may be called through the `test_t::error` + * `runner_t::fn`. Alternatively may be called through the `test_t::error` * interface. + * * @param t - pointer to a `test_t` structure to capture the context of a * test error * @param msg - character string indicating the reason for error **/ -void test_error(test_t* t, char* msg); +void* test_error(test_t* t, char* msg); /** - * `test_start()` marks the time at which the test started. This may be useful - * for benchmarking and should be called after any test setup code. + * `test_timer_start()` marks the time at which the test started. + * + * This may be useful for benchmarking and should be called after any test + * setup code. + * * @param t - pointer to a `test_t` structure to capture the test finish time **/ -void test_start(test_t* t); +void* test_timer_start(test_t* t); /** - * `test_done()` marks the time at which the test finished. This may be useful - * for benchmarking and should be called before any test teardown code. + * `test_timer_end()` marks the time at which the test finished. + * + * This may be useful for benchmarking and should be called before any test + * teardown code. + * * @param t - pointer to a `test_t` structure to capture the test finish time **/ -void test_done(test_t* t); +void* test_timer_end(test_t* t); /** - * Testing function. + * Test runner. Simple container with metadata about a testcase function. **/ -typedef struct testfn { +typedef struct runner_t { /** - * `name` is a character string identifier for a test. It is usually just - * the name of the function itself, such as "test_func". + * `name` is a character string identifier for a test. + * + * It is usually just the name of the unit itself itself under test. e.g. + * if you are testing a function called `foo`, then this runner_t::name + * should be `test_foo` by convention. */ char* name; /** - * `desc` is a character string description for a test. It should be a - * humanly readable explanation of what the test is performing. Optional - * field in constructor. + * `desc` is a character string description for a test. + * + * It should be a humanly readable explanation of what the test is + * performing. Optional field in constructor. */ char* desc; /** * `fn` is a pointer to a test function. + * * @param t - pointer to a `test_t` structure to capture context of test * results */ void* (*fn)(void* t); -} testfn; +} runner_t; /** - * `newtest()` creates and returns a pointer to an initialized testfn. + * `runner_new()` creates and returns a pointer to an initialized runner_t. + * * @param f - a pointer to a function that records information about a test * within its single `test_t*` argument * @param name - a character string identifier for the test; usually the name * of the test function itself * @param desc - a human readable description of the test; can be `NULL` - * @return A pointer to a fully initialized testfn structure. + * @return A pointer to a fully initialized runner_t structure. **/ -testfn* newtest(void* (*f)(void* t), char* name, char* desc); +runner_t* runner_new(void* (*f)(void* t), char* name, char* desc); /** - * `testfn_del()` frees memory allocated to a test function. - * @param tf - a pointer to a `testfn` that is to be destroyed + * `runner_del()` frees memory allocated to a test function. + * Should not be called manually. + * + * @private + * @param tr - a pointer to a `runner_t` that is to be destroyed * @return`EXIT_SUCCESS`, otherwise `EXIT_FAILURE` if tf is `NULL`. **/ -int testfn_del(testfn* tf); +int tdd_runner_del(runner_t* tr); /** * Testing suite. Contains all tests, current runtime state, and the results @@ -268,20 +330,29 @@ int testfn_del(testfn* tf); **/ typedef struct suite_t { /** - * `finished` is a boolean flag specifying if all tests have run. If this - * flag is not set by the time the suite has completed all tests, then it - * aborted testing with a fatal failure from one of the tests in the - * suite. + * `finished` is a boolean flag specifying if all tests have run. + * + * If this flag is not set by the time the suite has completed all tests, + * then it aborted testing with a fatal failure from one of the tests in + * the suite. **/ bool finished; - /** `n_tests` is the number of tests in the suite. **/ + /** + * `n_tests` is the number of tests in the suite. + **/ int n_tests; - /** `n_segv` is the number of segmentation faults that were caught. **/ + /** + * `n_segv` is the number of segmentation faults that were caught. + **/ int n_segv; - /** `test_index` is the index of the current test. **/ + /** + * `test_index` is the index of the current test. + **/ int test_index; - /** `tests` is an array of testfn* that make up the suite. **/ - testfn** tests; + /** + * `tests` is an array of runner_t* that make up the suite. + **/ + runner_t** tests; /** * `results` is an array of `test_t*` that details testing results for * each element in tests. @@ -289,8 +360,10 @@ typedef struct suite_t { test_t** results; /** * `outfile` is a FILE pointer which is where the results of the test will - * be printed. This is stdout by default, but may be changed manually - * after the suite is initialized and before it is run. + * be printed. + * + * This is stdout by default, but may be changed manually after the suite + * is initialized and before it is run. **/ FILE* outfile; /** @@ -302,42 +375,57 @@ typedef struct suite_t { } suite_t; /** - * `suite_init()` creates and returns a new test suite. - * @return A pointer to a fully intialized suite_t structure. + * `suite_new()` creates and returns a new test suite. Must be freed by + * `suite_del()`. + * + * @return A pointer to a fully intialized `suite_t` structure. **/ -suite_t* suite_init(); +suite_t* suite_new(); /** * `suite_reset()` resets a `suite_t` to its initial state, as if it were * never run. + * * @param s - a pointer to a `suite_t` test suite to be reset **/ void suite_reset(suite_t* s); /** * `suite_del()` frees memory allocated to a test suite. + * * @param s - a pointer to a `suite_t` test suite that is to be destroyed - * @return + * @return EXIT_SUCCESS if successful, EXIT_FAILURE otherwise **/ int suite_del(suite_t* s); /** * `suite_done()` marks all the suite as having finished all tests. + * + * @param s - a pointer to the `suite_t` to mark finished **/ void suite_done(suite_t* s); /** - * `suite_add()` adds n testfn to the suite. + * `suite_add()` adds n `runner_t` structs to the suite. + * + * @param s - the suite to which test runners should be added + * @param n - the number of test runners that follow + * @param ... - exactly n runner_t structs **/ void suite_add(suite_t* s, int n, ...); /** - * `suite_addtest()` adds a single `testfn` to the suite. + * `suite_add_test()` adds a single `runner_t` to the suite. + * + * @param s - the suite to which the test runner should be added + * @param r - the runner to add to the suite + * @return EXIT_SUCCESS if successful, EXIT_FAILURE otherwise **/ -int suite_addtest(suite_t* s, testfn* f); +int suite_add_test(suite_t* s, runner_t* r); /** * `suite_run()` runs all tests in the test array. + * * @param s - the test suite to run * @param fatal_failures - true indicates that the suite should abort * testing if any test was marked as a failure @@ -348,6 +436,7 @@ int suite_run(suite_t* s, bool fatal_failures); /** * `suite_next()` runs the next test in the suite. + * * @param s - the test suite to run * @param fatal_failures - true indicates that the suite should abort * testing if any test was marked as a failure @@ -371,6 +460,26 @@ typedef struct tdd_result_t { bool ok; } tdd_result_t; +/** + * `tdd_result_new()` creates and returns a pointer to a new `tdd_result_t`. + * Must be freed with `tdd_result_del()`. + * @private + * @internal + * + * @return A pointer to a fully intialized `tdd_result_t` structure. + **/ +tdd_result_t* tdd_result_new(char* name, bool ok); + +/** + * `tdd_result_del()` frees memory allocated to a `tdd_result_t` struct. + * @private + * @internal + * + * @param result - a pointer to a `tdd_result_t` that is to be destroyed + * @return EXIT_SUCCESS if successful, EXIT_FAILURE otherwise + **/ +int tdd_result_del(tdd_result_t* result); + /** * Stats structure detailing results of test suite. **/ @@ -409,14 +518,21 @@ typedef struct suite_stats_t { } suite_stats_t; /** - * `suite_stats()` returns a `stats_t*` detailing the results of the testing. + * `suite_get_stats()` returns a `stats_t*` detailing the results of the + * testing. + * + * @param s - the suite from which to allocate statistics + * @return heap allocated suite_stats_t structure **/ -suite_stats_t* suite_stats(suite_t* s); +suite_stats_t* suite_get_stats(suite_t* s); /** - * `suite_delstats()` frees memory allocated to a `stats_t*` returned by - * `suite_stats()` + * `suite_stats_del()` frees memory allocated to a `stats_t*` returned by + * `suite_get_stats()` + * + * @param stats - pointer to the suite_stats_t to free + * @return EXIT_SUCCESS if successful, EXIT_FAILURE otherwise **/ -int suite_delstats(suite_stats_t* stats); +int suite_stats_del(suite_stats_t* stats); #endif diff --git a/include/timeutil.h b/include/timeutil.h index 53d5c36..972538b 100644 --- a/include/timeutil.h +++ b/include/timeutil.h @@ -2,8 +2,7 @@ * @private * @file timeutil.h * @author Keefer Rourke - * @date 08 Apr 2018 - * @brief Time format and manipulation functions and macros for libtdd. + * @brief Private time format and manipulation functions and macros. */ #ifndef __TDD_TIME_UTIL_H__ #define __TDD_TIME_UTIL_H__ @@ -11,6 +10,12 @@ #include #include +/** + * @private + * @internal + * __timespace_minus() subtracts two timespec structs to yeild a new + * timespec. + */ struct timespec __timespec_minus(struct timespec* a, struct timespec* b); #endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..03c8942 --- /dev/null +++ b/meson.build @@ -0,0 +1,85 @@ +project('libtdd', 'c', version: '0.0.2', license: 'ISC', + default_options: [ + 'c_std=c99', +]) +posix_c_source = get_option('posix_c_source') +if posix_c_source != '' + add_global_arguments('-D_POSIX_C_SOURCE='+posix_c_source, + language: 'c') +endif + +if get_option('use_colour_output') + add_project_arguments('-DUSE_COLOUR', language: 'c') +endif + + +cdata = configuration_data() +cdata.set('PROJECT_NAME', meson.project_name()) +cdata.set('VERSION', meson.project_version()) +cdata.set('TOP_SRC_DIR', meson.source_root()) +cdata.set('INCLUDE_DIR', join_paths(meson.source_root(), 'include')) +cdata.set('DOCS_OUTPUT_DIR', join_paths(meson.build_root(), 'docs')) +cdata.set('README_PATH', join_paths(meson.source_root(), 'README.md')) + +threads = dependency('threads') + +project_sources = [] +project_api_headers = [] +project_headers = [] +project_includes = [] +project_objects = [] +example_sources = [] + +src_dir = 'src' +include_dir = 'include' + +subdir(src_dir) # populate project_sources +subdir(include_dir) # populate project_includes + + +# if doxygen found, generate documentation +doxygen = find_program('doxygen', required: false) +pdflatex = find_program('pdflatex', required: false) +if doxygen.found() + cdata.set('HAS_DOT', find_program('dot', required: false).found() ? 'YES' : 'NO') + cdata.set('HTML_PAGES', get_option('generate_html_docs') ? 'YES' : 'NO') + cdata.set('GEN_LATEX', get_option('generate_pdf_docs') and pdflatex.found() ? 'YES' : 'NO') + if get_option('generate_man_pages') + cdata.set('MAN_PAGES', 'YES') + man_dir = join_paths(cdata.get('DOCS_OUTPUT_DIR'), 'man', 'man3') + install_subdir(man_dir, install_dir: 'man') + else + cdata.set('MAN_PAGES', 'NO') + endif + subdir('docs') +endif + +project_headers += project_api_headers + +install_headers(project_api_headers) +lib = library('tdd', install: true, sources: project_sources, + include_directories: project_includes, dependencies: threads) + +run_target('format', command: [ + 'clang-format', + '-i', + '-style=file', + project_sources, + project_headers, + example_sources +]) + +run_target('cppcheck', command: [ + 'cppcheck', + '--enable=all', + '--language=c', + '--std='+get_option('c_std'), + '--suppress=missingInclude', + '--suppress=unusedFunction', + src_dir, + '-i', include_dir +]) + +subdir('examples') +executable('example', example_sources, link_with: lib, + include_directories: project_includes) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..13d6c8a --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,6 @@ +option('posix_c_source', type: 'string', value: '199506L', description: 'Value for _X_POSIX_C_SOURCE') +option('use_colour_output', type: 'boolean', value: true, description: 'Enables coloured test output') +option('build_examples', type: 'boolean', value: false, description: 'Builds example binaries as well') +option('generate_man_pages', type: 'boolean', value: true, description: 'Generate man pages from documentation') +option('generate_html_docs', type: 'boolean', value: true, description: 'Generate html documentation') +option('generate_pdf_docs', type: 'boolean', value: true, description: 'Generate pdf documentation') diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..b524e29 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,9 @@ +project_sources += files([ + 'runner.c', + 'signals.c', + 'stats.c', + 'strutil.c', + 'suite.c', + 'test.c', + 'timeutil.c' +]) diff --git a/src/runner.c b/src/runner.c new file mode 100644 index 0000000..46b4a60 --- /dev/null +++ b/src/runner.c @@ -0,0 +1,68 @@ +/** + * @file runner.c + * @author Keefer Rourke + * @brief This file contains implementation details of functions pertaining to + * using runner_t structures for creating simple test suites. + **/ +#include +#include +#include +#include +#include +#include +#include + +#include "tdd.h" + +runner_t* runner_new(void* (*f)(void* t), char* name, char* desc) { + if (name == NULL || f == NULL) { + return NULL; + } + + runner_t* runner = malloc(sizeof(runner_t)); + if (runner == NULL) { + errno = ENOMEM; + return NULL; + } + + runner->name = calloc(strlen(name) + 1, sizeof(char)); + if (desc != NULL) { + runner->desc = calloc(strlen(desc) + 1, sizeof(char)); + } else { + runner->desc = calloc(1, sizeof(char)); /* empty string */ + } + runner->fn = f; + + if (runner->name == NULL || runner->desc == NULL) { + errno = ENOMEM; + if (runner->name) { + free(runner->name); + } + if (runner->desc) { + free(runner->desc); + } + free(runner); + return NULL; + } + + strncpy(runner->name, name, strlen(name)); + if (desc != NULL) { + strncpy(runner->desc, desc, strlen(desc)); + } + + return runner; +} + +int tdd_runner_del(runner_t* runner) { + if (runner == NULL) return EXIT_FAILURE; + + if (runner->name != NULL) { + free(runner->name); + } + if (runner->desc != NULL) { + free(runner->desc); + } + free(runner); + + return EXIT_SUCCESS; +} diff --git a/src/signals.c b/src/signals.c index f8538d3..0bcb746 100644 --- a/src/signals.c +++ b/src/signals.c @@ -1,7 +1,6 @@ /** * @file signals.c * @author Keefer Rourke - * @date Wed Apr 11 * @brief This file defines signal handlers for libtdd. **/ #include diff --git a/src/stats.c b/src/stats.c index 3db2578..643548f 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1,17 +1,36 @@ /** * @file stats.c * @author Keefer Rourke - * @data 28 Apr 2018 * @brief This file contains implementation details of functions pertaining to * the suite_stats_t used for reporting results from test suites. */ #include +#include #include #include #include "tdd.h" -suite_stats_t* suite_stats(suite_t* s) { +tdd_result_t* tdd_result_new(char* name, bool ok) { + tdd_result_t* r = malloc(sizeof(tdd_result_t)); + + r->name = calloc(strlen(name) + 1, sizeof(char)); + strcpy(r->name, name); + r->ok = ok; + + return r; +} + +int tdd_result_del(tdd_result_t* result) { + if (result == NULL) return EXIT_FAILURE; + + free(result->name); + free(result); + + return EXIT_SUCCESS; +} + +suite_stats_t* suite_get_stats(suite_t* s) { if (s == NULL) return NULL; suite_stats_t* stats = malloc(sizeof(suite_stats_t)); @@ -19,12 +38,8 @@ suite_stats_t* suite_stats(suite_t* s) { int nerr = 0, nfail = 0; for (int i = 0; i < s->test_index; i++) { - testfn* t = s->tests[i]; - stats->tests_run[i] = malloc(sizeof(tdd_result_t)); - stats->tests_run[i]->name = - malloc(sizeof(char) * (strlen(t->name) + 1)); - stats->tests_run[i]->ok = !(s->results[i]->failed); - strcpy(stats->tests_run[i]->name, t->name); + runner_t* t = s->tests[i]; + stats->tests_run[i] = tdd_result_new(t->name, s->results[i]->failed); test_t* r = s->results[i]; if (r->err != 0) nerr++; @@ -38,11 +53,11 @@ suite_stats_t* suite_stats(suite_t* s) { return stats; } -int suite_delstats(suite_stats_t* stats) { +int suite_stats_del(suite_stats_t* stats) { if (stats == NULL) return EXIT_FAILURE; for (int i = 0; i < stats->n_ran; i++) { - free(stats->tests_run[i]); + tdd_result_del(stats->tests_run[i]); } free(stats->tests_run); free(stats); diff --git a/src/strutil.c b/src/strutil.c index cfd919f..107735b 100644 --- a/src/strutil.c +++ b/src/strutil.c @@ -1,11 +1,10 @@ /** * @file strutil.c + * @private * @author Keefer Rourke - * @date 08 Apr 2018 - * @brief String format and manipulation functions and macros for libctest. + * @brief String format and manipulation functions and macros. * Supports colour printing if USE_COLOUR or USE_COLOR are defined and * the output file is `stdout`. - * @private */ #include #include diff --git a/src/suite.c b/src/suite.c index f99c594..2abef58 100644 --- a/src/suite.c +++ b/src/suite.c @@ -1,7 +1,6 @@ /** * @file suite.c * @author Keefer Rourke - * @date 08 Apr 2018 * @brief This file contains implementation details of functions pertaining to * using suite_t structures for managing simple test suites. **/ @@ -13,13 +12,14 @@ #include #include #include +#include #include #include "strutil.h" #include "tdd.h" #include "timeutil.h" -suite_t* suite_init() { +suite_t* suite_new() { suite_t* s = malloc(sizeof(suite_t)); if (s == NULL) { errno = ENOMEM; @@ -45,7 +45,7 @@ void suite_reset(suite_t* s) { s->test_index = 0; for (int i = 0; i < s->n_tests; i++) { - test_t_del(s->results[i]); + tdd_test_del(s->results[i]); s->results[i] = NULL; } @@ -56,8 +56,8 @@ int suite_del(suite_t* s) { if (s == NULL) return EXIT_FAILURE; for (int i = 0; i < s->n_tests; i++) { - testfn_del(s->tests[i]); - test_t_del(s->results[i]); + tdd_runner_del(s->tests[i]); + tdd_test_del(s->results[i]); } free(s->tests); free(s->results); @@ -80,7 +80,7 @@ void suite_add(suite_t* s, int n, ...) { /* update the test count and realloc test and result arrays */ s->n_tests += n; - testfn** tmp_tests = realloc(s->tests, sizeof(testfn*) * s->n_tests); + runner_t** tmp_tests = realloc(s->tests, sizeof(runner_t*) * s->n_tests); if (!tmp_tests) { errno = ENOMEM; return; @@ -98,20 +98,20 @@ void suite_add(suite_t* s, int n, ...) { va_list ap; va_start(ap, n); for (int i = old_index; i < s->n_tests; i++) { - s->tests[i] = va_arg(ap, testfn*); + s->tests[i] = va_arg(ap, runner_t*); } va_end(ap); return; } -int suite_addtest(suite_t* s, testfn* fn) { +int suite_add_test(suite_t* s, runner_t* r) { if (s == NULL) return EXIT_FAILURE; s->n_tests++; - /* reallocate array and add new testfn */ - testfn** tmp_tests = realloc(s->tests, sizeof(testfn*) * s->n_tests); + /* reallocate array and add new runner_t */ + runner_t** tmp_tests = realloc(s->tests, sizeof(runner_t*) * s->n_tests); if (tmp_tests == NULL) { if (s->tests != NULL) { free(s->tests); @@ -120,7 +120,7 @@ int suite_addtest(suite_t* s, testfn* fn) { return EXIT_FAILURE; } s->tests = tmp_tests; - s->tests[s->n_tests - 1] = fn; + s->tests[s->n_tests - 1] = r; /* grow results array with the tests */ test_t** tmp_results = realloc(s->results, sizeof(test_t*) * s->n_tests); @@ -155,8 +155,8 @@ int suite_next(suite_t* s, bool fatal_failures) { if (s == NULL) return EXIT_FAILURE; /* set up test */ - testfn* test = s->tests[s->test_index]; - test_t* t = test_t_init(test->name); + runner_t* test = s->tests[s->test_index]; + test_t* t = tdd_test_new(test->name); bool bench = false; if (__hasprefix(test->name, "bench_")) { @@ -170,28 +170,28 @@ int suite_next(suite_t* s, bool fatal_failures) { sa.sa_handler = &tdd_sigsegv_handler; if (sigaction(SIGSEGV, &sa, NULL) == -1) { perror("sigaction"); - test_t_del(t); + tdd_test_del(t); return EXIT_FAILURE; } /* run test */ if (bench) { - test_start(t); + test_timer_start(t); } pthread_t thread; if (pthread_create(&thread, NULL, test->fn, t) != 0) { fprintf(stderr, "Could not create thread!\n"); - test_t_del(t); + tdd_test_del(t); return EXIT_FAILURE; } void* retval = NULL; if (pthread_join(thread, &retval) != 0) { fprintf(stderr, "Could not join with thread!\n"); - test_t_del(t); + tdd_test_del(t); return EXIT_FAILURE; } if (bench && t->end->tv_sec == 0 && t->end->tv_nsec == 0) { - test_done(t); + test_timer_end(t); } if (crash_count != tdd_sigsegv_caught) { t->failed = true; diff --git a/src/test.c b/src/test.c index 33b1235..567a032 100644 --- a/src/test.c +++ b/src/test.c @@ -1,7 +1,6 @@ /** * @file test.c * @author Keefer Rourke - * @date 08 Apr 2018 * @brief This file contains implementation details of functions pertaining to * using test_t structures for managing simple test suites. **/ @@ -17,7 +16,7 @@ #include "tdd.h" -test_t* test_t_init(const char* name) { +test_t* tdd_test_new(const char* name) { test_t* t = malloc(sizeof(test_t)); t->name = name; t->failed = false; @@ -34,12 +33,13 @@ test_t* test_t_init(const char* name) { /* set alternative interface for fail, error, done cases (more OO-like) */ t->fail = &test_fail; t->error = &test_error; - t->done = &test_done; + t->begin = &test_timer_start; + t->done = &test_timer_end; return t; } -int test_t_del(test_t* t) { +int tdd_test_del(test_t* t) { if (t == NULL) return EXIT_FAILURE; if (t->fail_msg != NULL) { @@ -64,24 +64,25 @@ int test_t_del(test_t* t) { return EXIT_SUCCESS; } -void test_fail(test_t* t, char* msg) { +void* test_fail(test_t* t, char* msg) { t->failed = true; t->fail_msg = calloc(strlen(msg) + 1, sizeof(char)); strncpy(t->fail_msg, msg, strlen(msg)); clock_gettime(CLOCK_MONOTONIC, t->failed_at); - pthread_cancel(pthread_self()); + void* retval = NULL; + pthread_join(pthread_self(), &retval); - return; + return NULL; } -void test_error(test_t* t, char* msg) { +void* test_error(test_t* t, char* msg) { t->err++; char** temp = realloc(t->err_msg, sizeof(char*) * (t->err)); if (!temp) { errno = ENOMEM; - return; + return NULL; } t->err_msg = temp; @@ -90,17 +91,15 @@ void test_error(test_t* t, char* msg) { clock_gettime(CLOCK_MONOTONIC, t->error_at); - return; + return NULL; } -void test_start(test_t* t) { +void* test_timer_start(test_t* t) { clock_gettime(CLOCK_MONOTONIC, t->start); - - return; + return NULL; } -void test_done(test_t* t) { +void* test_timer_end(test_t* t) { clock_gettime(CLOCK_MONOTONIC, t->end); - - return; + return NULL; } diff --git a/src/testfn.c b/src/testfn.c deleted file mode 100644 index 519f9f2..0000000 --- a/src/testfn.c +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @file testfn.c - * @author Keefer Rourke - * @date 08 Apr 2018 - * @brief This file contains implementation details of functions pertaining to - * using testfn structures for creating simple test suites. - **/ -#include -#include -#include -#include -#include -#include -#include - -#include "tdd.h" - -testfn* newtest(void* (*f)(void* t), char* name, char* desc) { - if (name == NULL || f == NULL) { - return NULL; - } - - testfn* tf = malloc(sizeof(testfn)); - if (tf == NULL) { - errno = ENOMEM; - return NULL; - } - - tf->name = calloc(strlen(name) + 1, sizeof(char)); - if (desc != NULL) { - tf->desc = calloc(strlen(desc) + 1, sizeof(char)); - } else { - tf->desc = calloc(1, sizeof(char)); /* empty string */ - } - tf->fn = f; - - if (tf->name == NULL || tf->desc == NULL) { - errno = ENOMEM; - if (tf->name) { - free(tf->name); - } - if (tf->desc) { - free(tf->desc); - } - return NULL; - } - - strncpy(tf->name, name, strlen(name)); - if (desc != NULL) { - strncpy(tf->desc, desc, strlen(desc)); - } - - return tf; -} - -int testfn_del(testfn* tf) { - if (tf == NULL) return EXIT_FAILURE; - - if (tf->name != NULL) { - free(tf->name); - } - if (tf->desc != NULL) { - free(tf->desc); - } - free(tf); - - return EXIT_SUCCESS; -} diff --git a/src/timeutil.c b/src/timeutil.c index bcb797f..7679782 100644 --- a/src/timeutil.c +++ b/src/timeutil.c @@ -1,8 +1,7 @@ /** * @file timeutil.h * @author Keefer Rourke - * @date 08 Apr 2018 - * @brief Time format and manipulation functions and macros for libtdd. + * @brief Time format and manipulation functions and macros. * @private */ #include diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..e69de29