Skip to content

Instantly share code, notes, and snippets.

@ingramj
Created November 30, 2014 02:31
Show Gist options
  • Save ingramj/ba3fcd776591fa5d9bef to your computer and use it in GitHub Desktop.
Save ingramj/ba3fcd776591fa5d9bef to your computer and use it in GitHub Desktop.
Unit Tests for C, in a single header.
#ifndef TEST_H_
#define TEST_H_
/* Unit testing framework.
*
* Copyright (c) 2014, Jim Ingram <[email protected]>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* http://jingram.sdf.org/2014/11/29/unit-tests-for-c-in-two-macros.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
* Begin a new test definition, with the given name.
*/
#define BEGIN_TEST(name) \
static int test_##name(void) \
{ \
pid_t pid = fork(); \
if (pid < 0) { \
return -1; \
} \
if (pid == 0) { \
int failed = 0;
/*
* End a test definition and set its expected exit status.
*/
#define END_TEST(expected) \
exit(failed ? !expected : expected); \
} else { \
int status; \
waitpid(pid, &status, 0); \
if (WIFEXITED(status)) { \
return WEXITSTATUS(status) == expected ? 0 : 1; \
} \
return -1; \
} \
}
/*
* Set the `failed` flag to 1 for this test, if `cond` evaluates to true.
*/
#define FAIL_IF(cond) failed = (cond) ? 1 : failed
/*
* Set the `failed` flag to 1 for this test, unless `cond` evaluates to true.
*/
#define FAIL_UNLESS(cond) failed = (cond) ? failed : 1
/*
* Container for summary data about a series of tests.
*/
typedef struct {
int passed;
int failed;
int errors;
} tests_summary;
/*
* Reset passed, failed, and error counts in a `tests_summary` struct.
*/
static inline void reset_summary(tests_summary *summary)
{
summary->passed = 0;
summary->failed = 0;
summary->errors = 0;
}
/*
* Print passed, failed, errors, and total tests run from a
* `tests_summary` struct.
*/
static inline void print_summary(tests_summary *summary)
{
printf("passed : %d\nfailed : %d\nerrors : %d\ntotal : %d\n",
summary->passed, summary->failed, summary->errors,
summary->passed + summary->failed + summary->errors);
}
/*
* Run the named test, and update `summary` with the result.
*/
#define RUN_TEST(name, summary) \
do { \
int ret; \
printf("%s: ", #name); \
fflush(stdout); \
ret = test_##name(); \
if (ret == 0) { \
printf("passed\n"); \
(summary).passed++; \
} else if (ret == 1) { \
printf("failed\n"); \
(summary).failed++; \
} else { \
printf("failed to to an error\n"); \
(summary).errors++; \
} \
} while (0)
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment