Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wire up support for selftests #102

Merged
merged 10 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ jobs:
- name: Check out
uses: actions/checkout@v2
- name: Build
shell: bash
run: |
mkdir build
cd build
cmake .. -G "${{matrix.platform.generator}}"
cmake --build .
cmake --build . --verbose
- name: Test
shell: bash
run: |
cd build
CTEST_OUTPUT_ON_FAILURE=1 ctest --build-config Debug
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16..3.29)

project(clar LANGUAGES C)

option(BUILD_TESTS "Build test executable" ON)
option(BUILD_EXAMPLE "Build the example." ON)

add_library(clar INTERFACE)
target_sources(clar INTERFACE
Expand All @@ -25,4 +25,8 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
if(BUILD_TESTING)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is if(BUILD_TESTING) but this is option(BUILD_TEST ... above.

I noticed because we introduce the option(BUILD_EXAMPLE ... below. I think we should have the options specified in the same place, and that we probably want BUILD_TEST, not BUILD_TESTING. (I would actually prefer BUILD_TESTS and BUILD_EXAMPLES, as plural, but I wouldn't block over that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ethomson BUILD_TESTING is actually set up via include(CTest), so it is what CMake itself declares to be the official option. So personally I'd agree that BUILD_TEST is saner, but CMake disagrees with both of us :/

But yeah, you're right, we still have the old BUILD_TESTS in here, which has in fact been introduced via f255c0b (ci: convert to use CMake, 2024-09-05). Let me drop that.

add_subdirectory(test)
endif()

if(BUILD_EXAMPLE)
add_subdirectory(example)
endif()
endif()
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ Can you count to funk?
~~~~ sh
$ mkdir tests
$ cp -r $CLAR_ROOT/clar* tests
$ cp $CLAR_ROOT/test/clar_test.h tests
$ cp $CLAR_ROOT/test/main.c.sample tests/main.c
$ cp $CLAR_ROOT/example/*.c tests
~~~~

- **One: Write some tests**
Expand Down Expand Up @@ -147,7 +146,7 @@ To use Clar:

1. copy the Clar boilerplate to your test directory
2. copy (and probably modify) the sample `main.c` (from
`$CLAR_PATH/test/main.c.sample`)
`$CLAR_PATH/example/main.c`)
3. run the Clar mixer (a.k.a. `generate.py`) to scan your test directory and
write out the test suite metadata.
4. compile your test files and the Clar boilerplate into a single test
Expand All @@ -159,7 +158,7 @@ The Clar boilerplate gives you a set of useful test assertions and features
the `clar.c` and `clar.h` files, plus the code in the `clar/` subdirectory.
You should not need to edit these files.

The sample `main.c` (i.e. `$CLAR_PATH/test/main.c.sample`) file invokes
The sample `main.c` (i.e. `$CLAR_PATH/example/main.c`) file invokes
`clar_test(argc, argv)` to run the tests. Usually, you will edit this file
to perform any framework specific initialization and teardown that you need.

Expand Down
7 changes: 4 additions & 3 deletions clar.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ clar_usage(const char *arg)
printf(" -t Display results in tap format\n");
printf(" -l Print suite names\n");
printf(" -r[filename] Write summary file (to the optional filename)\n");
exit(-1);
exit(1);
}

static void
Expand Down Expand Up @@ -650,7 +650,7 @@ static void abort_test(void)
clar_print_onabort(
"Fatal error: a cleanup method raised an exception.\n");
clar_report_errors(_clar.last_report);
exit(-1);
exit(1);
}

CL_TRACE(CL_TRACE__TEST__LONGJMP);
Expand Down Expand Up @@ -814,7 +814,8 @@ void clar__assert_equal(
void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *);
is_equal = (p1 == p2);
if (!is_equal)
p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2);
p_snprintf(buf, sizeof(buf), "0x%"PRIxPTR" != 0x%"PRIxPTR,
(uintptr_t)p1, (uintptr_t)p2);
}
else {
int i1 = va_arg(args, int), i2 = va_arg(args, int);
Expand Down
52 changes: 31 additions & 21 deletions clar.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@

#include <stdlib.h>

#ifndef CLAR_SELFTEST
# define CLAR_CURRENT_FILE __FILE__
# define CLAR_CURRENT_LINE __LINE__
# define CLAR_CURRENT_FUNC __func__
#else
# define CLAR_CURRENT_FILE "file"
# define CLAR_CURRENT_LINE 42
# define CLAR_CURRENT_FUNC "func"
#endif

enum cl_test_status {
CL_TEST_OK,
CL_TEST_FAILURE,
Expand Down Expand Up @@ -86,16 +96,16 @@ const char *cl_fixture_basename(const char *fixture_name);
/**
* Assertion macros with explicit error message
*/
#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1)
#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1)
#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1)
#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Function call failed: " #expr, desc, 1)
#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expected function call to fail: " #expr, desc, 1)
#define cl_assert_(expr, desc) clar__assert((expr) != 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expression is not true: " #expr, desc, 1)

/**
* Check macros with explicit error message
*/
#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0)
#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0)
#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0)
#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Function call failed: " #expr, desc, 0)
#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expected function call to fail: " #expr, desc, 0)
#define cl_check_(expr, desc) clar__assert((expr) != 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Expression is not true: " #expr, desc, 0)

/**
* Assertion macros with no error message
Expand All @@ -114,33 +124,33 @@ const char *cl_fixture_basename(const char *fixture_name);
/**
* Forced failure/warning
*/
#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1)
#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0)
#define cl_fail(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Test failed.", desc, 1)
#define cl_warning(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Warning during test execution:", desc, 0)

#define cl_skip() clar__skip()

/**
* Typed assertion macros
*/
#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))
#define cl_assert_equal_s(s1,s2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))

#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))
#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))

#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))
#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))

#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))
#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))

#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
#define cl_assert_equal_i(i1,i2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))

#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
#define cl_assert_equal_b(b1,b2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))

#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
#define cl_assert_equal_p(p1,p2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))

void clar__skip(void);

Expand Down
28 changes: 28 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
find_package(Python COMPONENTS Interpreter REQUIRED)

add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
COMMAND "${Python_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/generate.py" --output "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS main.c example.c
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)

add_executable(example)
set_target_properties(example PROPERTIES
C_STANDARD 90
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
)
target_sources(example PRIVATE
main.c
example.c
"${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
)
target_compile_definitions(example PRIVATE)
target_compile_options(example PRIVATE
$<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>
)
target_include_directories(example PRIVATE
"${CMAKE_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}"
)
target_link_libraries(example clar)
6 changes: 6 additions & 0 deletions example/example.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "clar.h"

void test_example__simple_assert(void)
{
cl_assert_equal_i(1, 1);
}
2 changes: 1 addition & 1 deletion test/main.c.sample → example/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full terms see the included COPYING file.
*/

#include "clar_test.h"
#include "clar.h"

/*
* Minimal main() for clar tests.
Expand Down
39 changes: 27 additions & 12 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,39 +1,54 @@
add_subdirectory(selftest_suite)

find_package(Python COMPONENTS Interpreter REQUIRED)

add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
COMMAND "${Python_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/generate.py" --output "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS main.c sample.c clar_test.h
DEPENDS main.c selftest.c
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)

add_executable(clar_test)
set_target_properties(clar_test PROPERTIES
add_executable(selftest)
set_target_properties(selftest PROPERTIES
C_STANDARD 90
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
)

# MSVC generates all kinds of warnings. We may want to fix these in the future
# and then unconditionally treat warnings as errors.
if(NOT MSVC)
set_target_properties(clar_test PROPERTIES
if (NOT MSVC)
set_target_properties(selftest PROPERTIES
COMPILE_WARNING_AS_ERROR ON
)
endif()

target_sources(clar_test PRIVATE
target_sources(selftest PRIVATE
main.c
sample.c
selftest.c
"${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
)
target_compile_definitions(clar_test PRIVATE
CLAR_FIXTURE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources/"
target_compile_definitions(selftest PRIVATE
CLAR_FIXTURE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/expected/"
)
target_compile_options(clar_test PRIVATE
target_compile_options(selftest PRIVATE
$<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>
)
target_include_directories(clar_test PRIVATE
target_include_directories(selftest PRIVATE
"${CMAKE_SOURCE_DIR}"
"${CMAKE_CURRENT_BINARY_DIR}"
)
target_link_libraries(clar_test clar)
target_link_libraries(selftest clar)

add_test(NAME build_selftest_suite
COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --target selftest_suite
)
set_tests_properties(build_selftest_suite PROPERTIES FIXTURES_SETUP clar_test_fixture)

add_test(NAME build_selftest
COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --target selftest
)
set_tests_properties(build_selftest PROPERTIES FIXTURES_SETUP clar_test_fixture)

add_test(NAME selftest COMMAND "${CMAKE_CURRENT_BINARY_DIR}/selftest" "$<TARGET_FILE:selftest_suite>")
set_tests_properties(selftest PROPERTIES FIXTURES_REQUIRED clar_test_fixture)
16 changes: 0 additions & 16 deletions test/clar_test.h

This file was deleted.

12 changes: 12 additions & 0 deletions test/expected/help
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Usage: selftest [options]

Options:
-sname Run only the suite with `name` (can go to individual test name)
-iname Include the suite with `name`
-xname Exclude the suite with `name`
-v Increase verbosity (show suite names)
-q Only report tests that had an error
-Q Quit as soon as a test fails
-t Display results in tap format
-l Print suite names
-r[filename] Write summary file (to the optional filename)
Loading
Loading