Skip to content

Instantly share code, notes, and snippets.

@Andrew-William-Smith
Created January 8, 2021 20:32
Show Gist options
  • Save Andrew-William-Smith/63965693ab9a734f026a29346255da6b to your computer and use it in GitHub Desktop.
Save Andrew-William-Smith/63965693ab9a734f026a29346255da6b to your computer and use it in GitHub Desktop.
A very simple generic vector in C, with unit tests. Written for the tutorial series "C for Java Programmers".
#include "test.h"
#include "vector.h"
#pragma FIXTURE_START
FIXTURE(Vector_fixture) {
struct vector vec;
};
FIXTURE_SETUP(Vector_fixture) {
T_ vec = vector_create(10, sizeof(short));
}
FIXTURE_TEARDOWN(Vector_fixture) {
vector_destroy(&T_ vec);
}
#pragma FIXTURE_END
#pragma TEST_START
TEST(vector_create_works, Vector_fixture) {
ASSERT_NON_NULL(T_ vec.con);
ASSERT_EQ(T_ vec.capacity, 10, "%zu");
ASSERT_EQ(T_ vec.size, 0, "%zu");
}
TEST(vector_destroy_deallocates, Vector_fixture) {
vector_destroy(&T_ vec);
ASSERT_NULL(T_ vec.con);
}
TEST(vector_add_single_element, Vector_fixture) {
short value = 4;
vector_add(&T_ vec, &value);
ASSERT_EQ(T_ vec.size, 1, "%zu");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 0)), 4, "%d");
}
TEST(vector_add_multiple_elements, Vector_fixture) {
short a = 4, b = 3, c = 7;
vector_add(&T_ vec, &a);
vector_add(&T_ vec, &b);
vector_add(&T_ vec, &c);
ASSERT_EQ(T_ vec.size, 3, "%zu");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 0)), 4, "%d");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 1)), 3, "%d");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 2)), 7, "%d");
}
TEST(vector_add_with_resize, Vector_fixture) {
for (short i = 0; i < 11; i++) {
vector_add(&T_ vec, &i);
}
ASSERT_EQ(T_ vec.size, 11, "%zu");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 10)), 10, "%d");
}
TEST(vector_insert_empty, Vector_fixture) {
short value = 4;
vector_insert(&T_ vec, 0, &value);
ASSERT_EQ(T_ vec.size, 1, "%zu");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 0)), 4, "%d");
}
PTEST(vector_insert, Vector_fixture) {
ASSERT_EQ(T_ vec.size, 3, "%zu");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 0)), 3, "%d");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 1)), 5, "%d");
ASSERT_EQ(*((short *) vector_get(&T_ vec, 2)), 7, "%d");
}
PCASE(vector_insert, Vector_fixture) {
// Insert at front
short a = 3, b = 5, c = 7;
vector_add(&T_ vec, &b);
vector_add(&T_ vec, &c);
vector_insert(&T_ vec, 0, &a);
}
PCASE(vector_insert, Vector_fixture) {
// Insert at back
short a = 3, b = 5, c = 7;
vector_add(&T_ vec, &a);
vector_add(&T_ vec, &b);
vector_insert(&T_ vec, 2, &c);
}
PCASE(vector_insert, Vector_fixture) {
// Insert in middle
short a = 3, b = 5, c = 7;
vector_add(&T_ vec, &a);
vector_add(&T_ vec, &c);
vector_insert(&T_ vec, 1, &b);
}
#pragma TEST_END
int main() {}
/**
* @file test.h
* @author Andrew Smith
* @brief A testing framework implemented entirely using the C preprocessor.
*
* This file implements a fairly full-featured unit testing framework whose
* logic is implemented entirely in C preprocessor macros, and that has no
* external dependencies outside of the C standard library. This framework, if
* it can really be called such, was written in response to the perceived
* excessive complexity of other unit testing systems, which required external
* dependency management systems and special compiler flags just to get them
* working. With <code>test.h</code>, that is not so: simply
* <code>#include "test.h"</code> at the top of a C file containing your test
* suite and you're off to the races.
*
* <code>test.h</code> makes use of some fairly arcane compiler features to
* auto-register test and fixture lifecycle functions. Its syntax and output
* format are similar to those of the popular Google Test C++ testing framework
* with no runtime overhead (what's a <code>malloc</code>?) and only a small
* amount of boilerplate.
*
* Internally, <code>test.h</code> uses similar features of the PE (Portable
* Executable) and ELF binary formats on Windows and all other common operating
* systems, respectively. For ELF, it makes use of ELF constructors, functions
* referenced in the <code>.ctors</code> section of binaries that are run before
* control is transferred to <code>_start</code> [<code>main()</code> in C].
* Usually, these functions are used to perform shared library initialisation
* tasks; however, in <code>test.h</code>, we take advantage of this feature of
* the ELF format to automatically register tests to run before
* <code>main()</code>, in essence giving us the ability to run functions
* without explicitly calling them.
*
* A similar trick is exploited on Windows, although it instead relies upon the
* CRT initialisation facilities of the PE format and some quirks of the MSVC
* preprocessor. While GCC and Clang support the explicit ordering of
* constructor functions, MSVC makes no such guarantee, os we instead use two
* separate PE sections for test initialisation and registration. The first,
* <code>.CRT$XIU</code>, is normally used for the initialisation of static
* objects in object-oriented programming languages; furthermore, it is one of
* the first sections to which control is transferred in the Windows process
* setup procedure. This section is used to register fixture lifecycle
* functions and is delimited by the pragmas <code>FIXTURE_START</code> and
* <code>FIXTURE_END</code>, a syntactical convenience made possible by the fact
* that MSVC's preprocessor performs macro substitution inside
* <code>#pragma</code> directives. Actual test functions are placed inside the
* <code>.CRT$XCU</code> section, which is normally used for C runtime (CRT)
* initialisation; since functions referenced in this section are run after
* those in <code>.CRT$XIU</code>, this feature gives us the assurance that test
* functions will only be run after their corresponding fixture lifecycle
* functions have been registered. This section is delimited by the pragmas
* <code>TEST_START</code> and <code>TEST_END</code>, which are implemented
* similarly to the fixture pragmas.
*
* As a result of these implementation details, the code in <code>test.h</code>
* is <em>highly</em> compiler-dependent, but is stable on the most common
* compilers for the most common operating systems, both ancient and modern.
* Now that you know the rough details of how <code>test.h</code> works, read
* through the documentation of the public macros in this file (the ones that
* don't start with <code>_</code>), walk through the example test file in this
* repo, and go forth to test!
*/
#ifndef TEST_H_INCLUDED
#define TEST_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* ************************ PLATFORM SUPPORT MACROS ************************* */
/* Concatenate the fully-expanded tokens X and Y into a single token. */
#define _TEST_TOKEN_CONCAT(X, Y) _TEST_TOKEN_CONCAT_IMPL(X, Y)
#define _TEST_TOKEN_CONCAT_IMPL(X, Y) X ## Y
/* Convert the fully expanded token X into a string representation. */
#define _TEST_STRINGIFY(X) _TEST_STRINGIFY_IMPL(X)
#define _TEST_STRINGIFY_IMPL(X) #X
/* The current source line number as a string. */
#define _TEST_LINE_STR _TEST_STRINGIFY(__LINE__)
/* The maximum number of characters that may be in a test failure message. */
#define _TEST_MAX_FAILURE_LENGTH 1024
/**
* Pragma to begin the executable portion of test files. You should write this
* pragma after you have declared all of your test fixtures and their lifecycle
* functions, and before your first actual test declaration. The form of the
* pragma to write is as follows:
*
* @code{.c}
* #pragma TEST_START
* @endcode
*
* While this pragma is not necessary (and is actually syntactically invalid) on
* GCC/Clang and modern MSVC, you should still write it to ensure that your test
* code is compatible with older MSVC versions.
*/
#define TEST_START
/**
* Pragma to end the executable section of test files. You should write this
* pragma after you have declared all of your test functions. The form of the
* pragma to write is as follows:
*
* @code{.c}
* #pragma TEST_END
* @endcode
*
* Like <code>TEST_START</code>, this pragma is not actually necessary on
* GCC/Clang, but is highly recommended for MSVC compatibility.
*/
#define TEST_END
/**
* Pragma to begin the fixture declaration portion of test files. You should
* write this pragma before your first fixture declaration. The form of the
* pragma to write is as follows:
*
* @code{.c}
* #pragma FIXTURE_START
* @endcode
*
* While this pragma is not necessary (and is actually syntactically invalid) on
* GCC/Clang and modern MSVC, you should still write it to ensure that your test
* code is compatible with older MSVC versions.
*/
#define FIXTURE_START
/**
* Pragma to end the fixture declaration portion of test files. You should
* write this pragma after your last fixture or fixture lifecycle function
* declaration. The form of the pragma to write is as follows:
*
* @code{.c}
* #pragma FIXTURE_END
* @endcode
*
* Like <code>FIXTURE_START</code>, this pragma is not actually necessary on
* GCC/Clang, but is highly recommended for MSVC compatibility.
*/
#define FIXTURE_END
/* PE sections in which test functions should be allocated. */
#define _TEST_FIXTURE_SECTION ".CRT$XIU"
#define _TEST_TEST_SECTION ".CRT$XCU"
#ifdef _MSC_VER
/* Disable warnings about platform-specific secure string functions. */
#define _CRT_SECURE_NO_WARNINGS 1
/* Some newer versions of MSVC may have a bug that results in the CRT PE
* sections, which we use extensively to ensure that test functions are run at
* the proper time, not being configured properly, resulting in linker errors.
* Explicitly defining these sections should ensure that linking succeeds. */
#if _MSC_VER >= 1400
#pragma section(_TEST_FIXTURE_SECTION, long, read)
#pragma section(".CRT$XCU", long, read)
#endif // _MSC_VER >= 1400
#if _MSC_VER < 1400
#undef TEST_START
/**
* Pragma to begin the executable portion of test files. You should write this
* pragma after you have declared all of your test fixtures and their lifecycle
* functions, and before your first actual test declaration. The form of the
* pragma to write is as follows:
*
* @code{.c}
* #pragma TEST_START
* @endcode
*
* This pragma is necessary to ensure that your test functions are run once the
* test executable is run. In order to run tests before the <code>main()</code>
* function on Windows, we place them in the PE section <code>.CRT$XCU</code>,
* which is normally used to initialise the C Runtime Library (CRT), but which
* we can repurpose for running tests out-of-line here. Note that the code
* snippet above is syntactically valid in MSVC, as it performs macro expansion
* in pragma directives.
*/
#define TEST_START data_seg(".CRT$XCU")
#undef TEST_END
/**
* Pragma to end the executable section of test files. You should write this
* pragma after you have declared all of your test functions. The form of the
* pragma to write is as follows:
*
* @code{.c}
* #pragma TEST_END
* @endcode
*
* This pragma returns the linker target to the main section, returning control
* to the post-initialisation portion of the process lifecycle.
*/
#define TEST_END data_seg()
#undef FIXTURE_START
/**
* Pragma to begin the fixture declaration portion of test files. You should
* write this pragma before your first fixture declaration. The form of the
* pragma to write is as follows:
*
* @code{.c}
* #pragma FIXTURE_START
* @endcode
*
* This pragma is necessary to ensure that your fixture lifecycle functions are
* run before any test functions by placing them in the <code>.CRT$XIU</code> PE
* section, which is normally used for static initialisations in object-oriented
* programming languages.
*/
#define FIXTURE_START data_seg(_TEST_FIXTURE_SECTION)
#undef FIXTURE_END
/**
* Pragma to end the fixture declaration portion of test files. You should
* write this pragma after your last fixture or fixture lifecycle function
* declaration. The form of the pragma to write is as follows:
*
* @code{.c}
* #pragma FIXTURE_END
* @endcode
*
* The rationale for this pragma is the same as that for <code>TEST_END</code>;
* refer to that macro's documentation for more information.
*
* @see <code>TEST_END</code> for implementation details.
*/
#define FIXTURE_END data_seg()
#endif // _MSC_VER < 1400
/* No attribute is supported to run a function before test functions on MSVC. */
#define _TEST_FIXTURE_LIFECYCLE
/* Prefix for test functions. */
#define _TEST_PROLOGUE(SECTION) __declspec(allocate(SECTION))
/* No attribute is supported to run a function before main() on MSVC. */
#define _TEST_RUNNER
/* MSVC doesn't allow you to declare an identifier as unused, but it also
* doesn't really care about unused identifiers, so no harm done. */
#define _TEST_UNUSED
/* Write an actual function pointer to the data segment to make the CRT run the
* test function with the specified NAME. */
#define _TEST_EPILOGUE(NAME, SECTION) \
_TEST_PROLOGUE(SECTION) \
int (*_TEST_TOKEN_CONCAT(test_prerun_, NAME))(void) = NAME;
/* Colours for test output. Disabled on Windows due to a lack of ANSI escape
* sequence support in conhost/CSRSS prior to Windows 10. */
#define _TEST_COLOUR_HEADER
#define _TEST_COLOUR_START
#define _TEST_COLOUR_PASS
#define _TEST_COLOUR_FAIL
#define _TEST_COLOUR_RUNTIME
#define _TEST_COLOUR_SKIP
#define _TEST_COLOUR_MUTE
#define _TEST_COLOUR_VALUE
#define _TEST_COLOUR_RESET
/* Multi-character newline for Windows. */
#define _TEST_NEWLINE "\r\n"
#else
/* Disable "unknown pragma" warnings on GCC/Clang. Allows the TEST_START and
* TEST_END macros to be used without triggering compiler errors. */
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#pragma GCC diagnostic ignored "-Wattributes"
/* Attribute to run a fixture setup redirection function on GCC/Clang. */
#define _TEST_FIXTURE_LIFECYCLE __attribute__((constructor(101)))
/* Prefix for test function pointers. Not really necessary for GCC/Clang, but
* we'll put the pointers in static storage for consistency. */
#define _TEST_PROLOGUE(SECTION) static
/* Attribute to run a function defined before main() on GCC/Clang. */
#define _TEST_RUNNER __attribute__((constructor(102)))
/* Silence unused identifier warnings to ensure that tests compile properly with
* "-Wall -Werror" set. */
#define _TEST_UNUSED __attribute__((unused))
/* No epilogue is required on GCC/Clang, as _TEST_RUNNER ensures that tests will
* be automatically run. */
#define _TEST_EPILOGUE(NAME, SECTION)
/* Colours for test output, as ANSI escape sequences. */
#define _TEST_ESC(COMMAND) "\x1B[" COMMAND "m"
#define _TEST_COLOUR_HEADER _TEST_ESC("1") /* Bold */
#define _TEST_COLOUR_START _TEST_ESC("1;34") /* Bold blue */
#define _TEST_COLOUR_PASS _TEST_ESC("1;32") /* Bold green */
#define _TEST_COLOUR_FAIL _TEST_ESC("1;31") /* Bold red */
#define _TEST_COLOUR_RUNTIME _TEST_ESC("0;36") /* Cyan */
#define _TEST_COLOUR_SKIP _TEST_ESC("1;90") /* Bold gray */
#define _TEST_COLOUR_MUTE _TEST_ESC("0;90") /* Gray */
#define _TEST_COLOUR_VALUE _TEST_ESC("0;33") /* Yellow */
#define _TEST_COLOUR_RESET _TEST_ESC("0") /* Reset foreground colour */
/* Newline character for all modern non-Windows systems. */
#define _TEST_NEWLINE "\n"
#endif
/* ***************************** TEST FIXTURES ****************************** */
/**
* An "empty" struct body containing only an obfuscated pointer member.
* Required to maintain compatibility with older compilers that do not support
* empty structs.
*/
#define EMPTY { \
void *_TEST_TOKEN_CONCAT(NEVER_USE_THIS_, __LINE__) _TEST_UNUSED; \
}
/**
* Declare a new test fixture with data members, but no setup or teardown code.
* The data members declared in a fixture will be available to all tests in the
* fixture. Fixtures are declared very similarly to structs, as follows:
*
* @code{.c}
* FIXTURE(Some_fixture) {
* char *message;
* unsigned long length;
* };
* @endcode
*
* Fixtures with no data members may be declared with the <code>EMPTY</code>
* suffix, which is required because the C standard does not permit empty struct
* definitions:
*
* @code{.c}
* FIXTURE(Empty_fixture) EMPTY;
* @endcode
*
* Note that this directive must appear inside a <code>FIXTURE_START</code>,
* <code>FIXTURE_END</code> block.
*/
#define FIXTURE(NAME) \
struct NAME ## _fixture_data; \
/* Declare stubs for fixture lifecycle functions. Note that these functions
* will be automatically run on Windows, but since they're empty it doesn't
* really matter. */ \
static void NAME ## _fixture_setup_stub \
(struct NAME ## _fixture_data *TEST _TEST_UNUSED) {} \
static void NAME ## _fixture_teardown_stub \
(struct NAME ## _fixture_data *TEST _TEST_UNUSED) {} \
/* Use stubs as actual tasks unless overridden later. Note that Windows
* throws a memory access violation if the pointers are directly assigned,
* so we have to do the assignment in separate functions. Why does this
* happen? Who knows. */ \
static void (*NAME ## _fixture_setup)(struct NAME ## _fixture_data *) \
_TEST_UNUSED = NAME ## _fixture_setup_stub; \
static void (*NAME ## _fixture_teardown)(struct NAME ## _fixture_data *) \
_TEST_UNUSED; \
static int _TEST_FIXTURE_LIFECYCLE NAME ## _fixture_teardown_init(void) { \
NAME ## _fixture_teardown = NAME ## _fixture_teardown_stub; \
return 0; \
} \
_TEST_EPILOGUE(NAME ## _fixture_teardown_init, _TEST_FIXTURE_SECTION) \
struct NAME ## _fixture_data
/**
* Declare a fixture setup function for the fixture with the specified name.
* The fixture must already have been declared with the <code>FIXTURE</code>
* directive prior to declaring its setup function. Setup functions have very
* similar syntax to normal C functions, as demonstrated in the example below.
* All setup functions may access their fixtures' data members via the variable
* <code>TEST</code>, a pointer to the struct declared with the
* <code>FIXTURE</code> directive. Example:
*
* @code{.c}
* FIXTURE_SETUP(Some_fixture) {
* TEST->message = malloc(1024 * sizeof(char));
* }
* @endcode
*
* Note that this directive must appear inside a <code>FIXTURE_START</code>,
* <code>FIXTURE_END</code> block.
*/
#define FIXTURE_SETUP(NAME) \
/* Forward declaration of overridden implementation. */ \
static void NAME ## _fixture_setup_impl(struct NAME ## _fixture_data *); \
/* Assign new implementation to the setup pointer. */ \
static int _TEST_FIXTURE_LIFECYCLE NAME ## _fixture_setup_override(void) { \
NAME ## _fixture_setup = NAME ## _fixture_setup_impl; \
return 0; \
} \
/* Make the override function run on Windows. */ \
_TEST_EPILOGUE(NAME ## _fixture_setup_override, _TEST_FIXTURE_SECTION) \
static void NAME ## _fixture_setup_impl( \
struct NAME ## _fixture_data *TEST _TEST_UNUSED)
/**
* Declare a fixture teardown function for the fixture with the specified name.
* The semantics of teardown functions are identical to those of setup
* functions, although the operations that they perform are often the inverse.
* Example:
*
* @code{.c}
* FIXTURE_TEARDOWN(Some_fixture) {
* free(TEST->message);
* }
* @endcode
*
* Note that this directive must appear inside a <code>FIXTURE_START</code>,
* <code>FIXTURE_END</code> block.
*/
#define FIXTURE_TEARDOWN(NAME) \
static void NAME ## _fixture_teardown_impl(struct NAME ## _fixture_data *);\
static int _TEST_FIXTURE_LIFECYCLE \
NAME ## _fixture_teardown_override(void) { \
NAME ## _fixture_teardown = NAME ## _fixture_teardown_impl; \
return 0; \
} \
_TEST_EPILOGUE(NAME ## _fixture_teardown_override, _TEST_FIXTURE_SECTION) \
static void NAME ## _fixture_teardown_impl( \
struct NAME ## _fixture_data *TEST _TEST_UNUSED)
/* ******************************* TEST CORE ******************************** */
/** The number of tests in this test suite that have passed. */
static unsigned long test_passed_tests = 0;
/** The number of tests in this test suite that have failed. */
static unsigned long test_failed_tests = 0;
/** The number of tests in this test suite that were skipped. */
static unsigned long test_skipped_tests = 0;
/** The failure message for the most recently failed assertion. */
static char test_failure_message[_TEST_MAX_FAILURE_LENGTH];
/** Return codes for test functions indicating their final statuses. */
enum test_status {
TEST_PASSED, /**< The test passed with no failing assertions. */
TEST_FAILED, /**< The test failed because of a failing assertion. */
TEST_SKIPPED, /**< The test was skipped. */
};
/** The exit status of the last test run. */
static enum test_status test_last_status;
/** A test callback, be it a fixture lifecycle function or a test itself. */
typedef void (*test_fn_t)(void *);
/* printf arguments for the runtime details of the current test. */
#define _TEST_DIAGNOSTICS \
_TEST_COLOUR_RUNTIME " (%7.3f/%3lus)" _TEST_COLOUR_RESET " %s" \
_TEST_NEWLINE, (double) (end_clock - start_clock) / CLOCKS_PER_SEC, \
(unsigned long) difftime(end_time, start_time), name
/**
* Main test runner function. Runs the test with the specified name,
* additionally running the specified setup and teardown functions before and
* after the main test function, respectively.
*
* @param name The name of the test to run.
* @param setup_fn The fixture setup function for the test.
* @param test_fn The main test function, containing the code under test and any
* assertions used to determine the test status.
* @param teardown_fn The fixture teardown function for the test.
* @param data_size The size in bytes of the data struct for the fixture to
* which the test being run belongs.
*/
static void test_run(char *name, test_fn_t setup_fn, test_fn_t test_fn,
test_fn_t teardown_fn, unsigned long data_size) {
/* We want to measure both CPU time and wall-clock time. */
clock_t start_clock, end_clock;
time_t start_time, end_time;
/* Initial setup for test run. */
void *test_data = malloc(data_size);
setup_fn(test_data);
/* Run the test and store its return status. */
printf(_TEST_COLOUR_START "[ START ]" _TEST_COLOUR_RESET " %s",
name);
test_last_status = TEST_PASSED;
start_time = time(NULL);
start_clock = clock();
test_fn(test_data);
end_time = time(NULL);
end_clock = clock();
/* Print results depending on the test function return status. */
switch (test_last_status) {
case TEST_PASSED: {
test_passed_tests++;
printf(_TEST_COLOUR_PASS "\r[ PASS ]" _TEST_DIAGNOSTICS);
break;
} case TEST_SKIPPED: {
test_skipped_tests++;
printf(_TEST_COLOUR_SKIP "\r[ SKIP ]" _TEST_COLOUR_MUTE
" %s: %s" _TEST_COLOUR_RESET _TEST_NEWLINE, name,
test_failure_message);
break;
} case TEST_FAILED: {
test_failed_tests++;
printf(_TEST_NEWLINE);
puts(test_failure_message);
printf(_TEST_COLOUR_FAIL "[ FAIL ]" _TEST_DIAGNOSTICS);
break;
} default: {
/* This branch should never run. */
break;
}
}
/* Test completed: tear down the test environment. */
teardown_fn(test_data);
free(test_data);
}
/**
* Declare a test with the specified name, belonging to the specified fixture.
* The fixture must have been declared with the <code>FIXTURE</code> directive
* prior to the test. For each test, the fixture setup function will be run
* first, then the body of the test as specified after this directive, then
* finally the fixture teardown function will be run to conclude the test. All
* tests should, but are not required to, contain at least one assertion, a
* statement that contains one of the <code>ASSERT_*</code> directives. Like
* test setup and teardown functions, tests are declared with a syntax very
* similar to standard C functions, as follows:
*
* @code{.c}
* TEST(Example_test, Some_fixture) {
* ASSERT_NON_NULL(TEST->message);
* strcpy(TEST->message, "Hello!");
* ASSERT_STREQ(TEST->message, "Hello!");
* ASSERT_EQ(strlen(TEST->message), 6);
* }
* @endcode
*
* As shown above, the data members of a test's fixture are made available via
* the pointer <code>TEST</code>, as in the fixture functions. Furthermore,
* note that tests that contain multiple assertions will be terminated
* immediately once any assertion fails. This directive must be written within
* a <code>TEST_START</code>, <code>TEST_END</code> block.
*/
#define TEST(NAME, FIXTURE) \
/* Forward declare test function to allow standard function syntax. */ \
static void \
FIXTURE ## _ ## NAME ## _test(struct FIXTURE ## _fixture_data *); \
/* Actual test function: calls the user-defined test function. */ \
static int _TEST_RUNNER FIXTURE ## _ ## NAME ## _test_run(void) { \
test_run(#NAME, \
(test_fn_t) FIXTURE ## _fixture_setup, \
(test_fn_t) FIXTURE ## _ ## NAME ## _test, \
(test_fn_t) FIXTURE ## _fixture_teardown, \
sizeof(struct FIXTURE ## _fixture_data)); \
return 0; \
} \
/* Make the test function run on Windows. */ \
_TEST_EPILOGUE(FIXTURE ## _ ## NAME ## _test_run, _TEST_TEST_SECTION) \
/* And finally, the user-declared test function. */ \
static void FIXTURE ## _ ## NAME ## _test( \
struct FIXTURE ## _fixture_data *TEST _TEST_UNUSED)
/** Shorthand for fixture data member access. Save yourself some typing! */
#define T_ TEST ->
/**
* Declare the common test function for a parameterised test with the specified
* name, belonging to the specified fixture. Unlike tests declared with the
* <code>TEST</code> directive, this function will not be run simply as a result
* of the <code>PTEST</code> directive, but must be followed by
* <code>PCASE</code> directives that specify the parameters for each test to be
* run. These directives should be combined as follows:
*
* @code{.c}
* #pragma FIXTURE_START
*
* FIXTURE(Param_fixture) {
* char *message;
* unsigned long length;
* }
*
* #pragma FIXTURE_END
*
* #pragma TEST_START
*
* PTEST(String_length, Param_fixture) {
* ASSERT_EQ(strlen(T_ message), T_ length);
* }
*
* PCASE(String_length, Param_fixture) { T_ message = "Hi!"; T_ length = 3; }
* PCASE(String_length, Param_fixture) { T_ message = "Hello"; T_ length = 5; }
* PCASE(String_length, Param_fixture) { T_ message = "Salve!"; T_ length = 6; }
*
* #pragma TEST_END
* @endcode
*
* Note that the data for a parameterised test should be defined in a
* corresponding fixture. Note further that <code>PCASE</code> directives
* should follow the corresponding <code>PTEST</code> directive, and that both
* <code>PTEST</code> and <code>PCASE</code> directives must be inside a
* <code>TEST_START</code>, <code>TEST_END</code> block.
*/
#define PTEST(NAME, FIXTURE) \
/* Simply allow the user to define the function, but do not run it. */ \
static void FIXTURE ## _ ## NAME ## _test( \
struct FIXTURE ## _fixture_data *TEST _TEST_UNUSED)
#define PCASE(NAME, FIXTURE) \
/* Forward declare the case setup function. */ \
static void \
_TEST_TOKEN_CONCAT(FIXTURE ## _ ## NAME ## _case_setup_, __LINE__)( \
struct FIXTURE ## _fixture_data *); \
/* Declare the combined fixture/case setup function. */ \
static void \
_TEST_TOKEN_CONCAT(FIXTURE ## _ ## NAME ## _case_setup_reg_, __LINE__)( \
struct FIXTURE ## _fixture_data *TEST) { \
FIXTURE ## _fixture_setup(TEST); \
_TEST_TOKEN_CONCAT(FIXTURE ## _ ## NAME ## _case_setup_, __LINE__) \
(TEST); \
} \
/* Test runner: calls the parameterised test with the combined setup. */ \
static int _TEST_RUNNER \
_TEST_TOKEN_CONCAT(FIXTURE ## _ ## NAME ## _test_run_, __LINE__)(void) { \
test_run(#NAME " (L" _TEST_LINE_STR ")", \
(test_fn_t) _TEST_TOKEN_CONCAT( \
FIXTURE ## _ ## NAME ## _case_setup_reg_, __LINE__), \
(test_fn_t) FIXTURE ## _ ## NAME ## _test, \
(test_fn_t) FIXTURE ## _fixture_teardown, \
sizeof(struct FIXTURE ## _fixture_data)); \
return 0; \
} \
/* Make the test function run on Windows. */ \
_TEST_EPILOGUE( \
_TEST_TOKEN_CONCAT(FIXTURE ## _ ## NAME ## _test_run_, __LINE__), \
_TEST_TEST_SECTION) \
/* At last, the user-declared case setup function. */ \
static void \
_TEST_TOKEN_CONCAT(FIXTURE ## _ ## NAME ## _case_setup_, __LINE__)( \
struct FIXTURE ## _fixture_data *TEST _TEST_UNUSED)
/**
* Skip the test in whose body this directive appears and print a skipped status
* and the specified message in the test status report if the specified
* condition holds true. Explanation messages are required for all skipped
* tests in order to enforce proper testing discipline. This directive may
* appear anywhere within the body of a test; if the condition holds true, all
* assertions from that point forth will not be run. Furthermore, skipped tests
* will not be counted toward the total test count printed in the test report.
* Example:
*
* @code{.c}
* TEST(Conditionally_skipped_test, Some_fixture) {
* SKIP_IF(time(NULL) == -1, "Time functions not available");
* // Code that requires time here...
* }
* @endcode
*/
#define SKIP_IF(CONDITION, MESSAGE) \
do { \
if (CONDITION) { \
strcpy(test_failure_message, MESSAGE); \
test_last_status = TEST_SKIPPED; \
return; \
} \
} while (0)
/**
* Unconditional version of the <code>SKIP_IF</code> directive. Immediately
* skips the remainder of the test in whose body the directive appears.
* Example:
*
* @code{.c}
* TEST(Skipped_test, Some_fixture) {
* SKIP("Bug #1: Faulty assertion");
* // Fix this!
* ASSERT_TRUE(0);
* }
* @endcode
*
* @see SKIP_IF
*/
#define SKIP(MESSAGE) SKIP_IF(1, MESSAGE)
/* ******************************* ASSERTIONS ******************************* */
/* Run an assertion comparing two values with the specified representations and
* printf formats according to the specified comparator, printing an error
* containing the specified message and terminating the test on failure. */
#define _TEST_ASSERT(A, A_REPR, A_FMT, CMP, B, B_REPR, B_FMT, MSG) \
do { \
if (!((A) CMP (B))) { \
sprintf(test_failure_message, \
_TEST_COLOUR_FAIL "Assertion failed!" _TEST_COLOUR_RESET \
" " MSG _TEST_NEWLINE \
_TEST_COLOUR_VALUE " Value 1: " _TEST_COLOUR_RESET \
A_FMT _TEST_NEWLINE \
_TEST_COLOUR_VALUE " Value 2: " _TEST_COLOUR_RESET \
B_FMT _TEST_NEWLINE \
_TEST_COLOUR_FAIL "File: " _TEST_COLOUR_RESET __FILE__ \
":%u", (A_REPR), (B_REPR), __LINE__); \
test_last_status = TEST_FAILED; \
return; \
} \
} while (0)
/** Assert that the specified predicate evaluates to a truthy (non-0) value. */
#define ASSERT_TRUE(PREDICATE) \
_TEST_ASSERT(0, "TRUE (!= 0)", "%s", !=, PREDICATE, (PREDICATE), "%d", \
"Expression is true: (" #PREDICATE ")")
/** Assert that the specified predicate evaluates to a falsy (0) value. */
#define ASSERT_FALSE(PREDICATE) \
_TEST_ASSERT(0, "FALSE (0)", "%s", ==, PREDICATE, (PREDICATE), "%d", \
"Expression is false: (" #PREDICATE ")")
/** Assert that the specified pointer is null (0). */
#define ASSERT_NULL(PTR) \
_TEST_ASSERT(NULL, "NULL (0x0)", "%s", ==, PTR, (PTR), "%p", \
"Pointer is non-null: (" #PTR ")")
/** Assert that the specified pointer is non-null (non-0). */
#define ASSERT_NON_NULL(PTR) \
_TEST_ASSERT(NULL, "Non-null (!= 0x0)", "%s", !=, PTR, (PTR), "%p", \
"Pointer is null: (" #PTR ")")
/**
* Assert that the first specified value is equal to the second specified value.
* Values will be printed in the specified format.
*/
#define ASSERT_EQ(VALUE_1, VALUE_2, FORMAT) \
_TEST_ASSERT(VALUE_1, (VALUE_1), FORMAT, ==, VALUE_2, (VALUE_2), FORMAT, \
"(" #VALUE_1 ") == (" #VALUE_2 ")")
/**
* Assert that the first specified value is not equal to the second specified
* value. Values will be printed in the specified format.
*/
#define ASSERT_NE(VALUE_1, VALUE_2, FORMAT) \
_TEST_ASSERT(VALUE_1, (VALUE_1), FORMAT, !=, VALUE_2, (VALUE_2), FORMAT, \
"(" #VALUE_1 ") != (" #VALUE_2 ")")
/**
* Assert that the first specified value is greater than the second specified
* value. Values will be printed in the specified format.
*/
#define ASSERT_GT(VALUE_1, VALUE_2, FORMAT) \
_TEST_ASSERT(VALUE_1, (VALUE_1), FORMAT, >, VALUE_2, (VALUE_2), FORMAT, \
"(" #VALUE_1 ") > (" #VALUE_2 ")")
/**
* Assert that the first specified value is greater than or equal to the second
* specified value. Values will be printed in the specified format.
*/
#define ASSERT_GE(VALUE_1, VALUE_2, FORMAT) \
_TEST_ASSERT(VALUE_1, (VALUE_1), FORMAT, >=, VALUE_2, (VALUE_2), FORMAT, \
"(" #VALUE_1 ") >= (" #VALUE_2 ")")
/**
* Assert that the first specified value is less than the second specified
* value. Values will be printed in the specified format.
*/
#define ASSERT_LT(VALUE_1, VALUE_2, FORMAT) \
_TEST_ASSERT(VALUE_1, (VALUE_1), FORMAT, <, VALUE_2, (VALUE_2), FORMAT, \
"(" #VALUE_1 ") < (" #VALUE_2 ")")
/**
* Assert that the first specified value is less than or equal to the second
* specified value. Values will be printed in the specified format.
*/
#define ASSERT_LE(VALUE_1, VALUE_2, FORMAT) \
_TEST_ASSERT(VALUE_1, (VALUE_1), FORMAT, <=, VALUE_2, (VALUE_2), FORMAT, \
"(" #VALUE_1 ") <= (" #VALUE_2 ")")
/**
* Assert that the contents of the specified strings (<code>char *</code>) are
* equal. Values will be printed as strings.
*/
#define ASSERT_STREQ(STR_1, STR_2) \
_TEST_ASSERT(strcmp((STR_1), (STR_2)), (STR_1), "\"%s\"", ==, 0, (STR_2), \
"\"%s\"", "(" #STR_1 ") == (" #STR_2 ")")
/**
* Assert that the contents of the specified strings (<code>char *</code>) are
* not equal. Values will be printed as strings.
*/
#define ASSERT_STRNE(STR_1, STR_2) \
_TEST_ASSERT(strcmp((STR_1), (STR_2)), (STR_1), "\"%s\"", !=, 0, (STR_2), \
"\"%s\"", "(" #STR_1 ") != (" #STR_2 ")")
/* ****************************** TEST RUNNER ******************************* */
#pragma FIXTURE_START
/**
* Print a summary of the test suite, describing the number of tests passed,
* failed, and skipped.
*/
static void test_summary(void) {
puts(_TEST_NEWLINE _TEST_COLOUR_HEADER
"================================= TEST SUMMARY ================================="
_TEST_COLOUR_RESET);
if (test_failed_tests == 0) {
printf(_TEST_COLOUR_PASS "All %lu tests passed!" _TEST_COLOUR_RESET
_TEST_NEWLINE, test_passed_tests);
} else {
printf(_TEST_COLOUR_PASS "Test(s) passed:" _TEST_COLOUR_RESET " %lu"
_TEST_NEWLINE, test_passed_tests);
printf(_TEST_COLOUR_FAIL "Test(s) failed:" _TEST_COLOUR_RESET " %lu"
_TEST_NEWLINE, test_failed_tests);
}
if (test_skipped_tests > 0) {
printf(_TEST_COLOUR_VALUE "Test(s) skipped:" _TEST_COLOUR_RESET " %lu"
_TEST_NEWLINE, test_skipped_tests);
}
}
/**
* Print test environment information and headers before test suite functions
* are run. Run in the fixture override stage to ensure proper execution order.
*/
static int _TEST_FIXTURE_LIFECYCLE test_kickoff(void) {
test_passed_tests = test_failed_tests = test_skipped_tests = 0;
puts(_TEST_COLOUR_HEADER
"================================ BEGIN TEST RUN ================================"
_TEST_COLOUR_RESET);
/* Register summary function to run after all tests have completed. */
atexit(test_summary);
return 0;
}
_TEST_EPILOGUE(test_kickoff, _TEST_FIXTURE_SECTION)
#pragma FIXTURE_END
#endif // TEST_H_INCLUDED
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "vector.h"
struct vector vector_create(size_t initial_capacity, size_t element_size) {
return (struct vector) {
.size = 0,
.capacity = initial_capacity,
.element_size = element_size,
.con = malloc(initial_capacity * element_size),
};
}
void vector_destroy(struct vector *vec) {
assert(vec != NULL);
free(vec->con);
vec->con = NULL;
}
static void vector_try_resize(struct vector *vec) {
if (vec->size == vec->capacity) {
vec->capacity = vec->capacity * 2 + 10;
vec->con = realloc(vec->con, vec->capacity * vec->element_size);
assert(vec->con != NULL);
}
}
void vector_add(struct vector *vec, void *value) {
assert(vec != NULL);
vector_try_resize(vec);
memcpy(vec->con + (vec->size * vec->element_size), value, vec->element_size);
vec->size++;
}
void *vector_get(struct vector *vec, size_t index) {
assert(vec != NULL);
assert(index < vec->size);
return vec->con + (index * vec->element_size);
}
void vector_insert(struct vector *vec, size_t index, void *value) {
assert(vec != NULL);
assert(index <= vec->size);
vector_try_resize(vec);
// Shift elements after the index down
if (index != vec->size) {
memmove(vec->con + ((index + 1) * vec->element_size),
vec->con + (index * vec->element_size),
(vec->size - index) * vec->element_size);
}
memcpy(vec->con + (index * vec->element_size), value, vec->element_size);
vec->size++;
}
// Include guard: ensures that the structures in this file are only instantiated
// a single time.
#ifndef C4JP_VECTOR_H_INCLUDED
#define C4JP_VECTOR_H_INCLUDED
#include <stddef.h>
struct vector {
size_t size; /**< The number of elements stored in our vector. */
size_t capacity; /**< The maximum number of elements we can store. */
size_t element_size; /**< The number of bytes each element occupies. */
char *con; /**< The actual elements in the vector, as an array. */
};
/** Create a vector with initial_capacity elements pre-allocated. */
struct vector vector_create(size_t initial_capacity, size_t element_size);
/** Deallocate the dynamic memory held by the vector. */
void vector_destroy(struct vector *vec);
/** Add the specified value to the end of the vector. */
void vector_add(struct vector *vec, void *value);
/** Retrieve the element at the specified index in the vector. */
void *vector_get(struct vector *vec, size_t index);
/** Insert the specified value at the specified index in the vector. */
void vector_insert(struct vector *vec, size_t index, void *value);
#endif // C4JP_VECTOR_H_INCLUDED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment