Browse Source

Add a simple unit test framework

master
Daniel Scharrer 1 year ago
parent
commit
6376c1ae30
  1. 1
      CHANGELOG
  2. 73
      CMakeLists.txt
  3. 7
      README.md
  4. 4
      cmake/FilterList.cmake
  5. 120
      src/util/test.cpp
  6. 81
      src/util/test.hpp

1
CHANGELOG

@ -2,6 +2,7 @@
innoextract 1.10 (TBD)
- Added support for Inno Setup 6.3.x installers
- Added support for a modified Inno Setup 5.3.10 variant
- Added unit tests
innoextract 1.9 (2020-08-09)
- Added preliminary support for Inno Setup 6.1.0

73
CMakeLists.txt

@ -27,6 +27,11 @@ option(DEVELOPER "Use build settings suitable for developers" OFF)
option(CONTINUOUS_INTEGRATION "Use build settings suitable for CI" OFF)
# Components
set(default_BUILD_TESTS OFF)
if(CONTINUOUS_INTEGRATION OR DEVELOPER)
set(default_BUILD_TESTS ON)
endif()
suboption(BUILD_TESTS "Build tests" BOOL ${default_BUILD_TESTS})
option(USE_ARC4 "Build ARC4 decryption support" ON)
# Optional dependencies
@ -83,6 +88,15 @@ else()
set(OPTIONAL_DEPENDENCY)
endif()
# Test configuration
set(RUN_TARGET CACHE STRING "Wrapper to run built targets")
mark_as_advanced(RUN_TARGET)
set(default_RUN_TESTS OFF)
if((DEVELOPER OR CONTINUOUS_INTEGRATION) AND (NOT CMAKE_CROSSCOMPILING OR NOT RUN_TARGET STREQUAL ""))
set(default_RUN_TESTS ON)
endif()
suboption(RUN_TESTS "Run tests as part of the default build target" BOOL ${default_RUN_TESTS})
# Install destinations
if(CMAKE_VERSION VERSION_LESS 2.8.5)
set(CMAKE_INSTALL_DATAROOTDIR "share" CACHE
@ -441,6 +455,7 @@ set(INNOEXTRACT_SOURCES
src/util/storedenum.hpp
src/util/time.hpp
src/util/time.cpp
src/util/test.hpp
src/util/types.hpp
src/util/unique_ptr.hpp
src/util/windows.hpp
@ -448,7 +463,15 @@ set(INNOEXTRACT_SOURCES
)
set(UNITTEST_SOURCES
src/util/test.hpp
src/util/test.cpp
)
filter_list(INNOEXTRACT_SOURCES ALL_INNOEXTRACT_SOURCES)
filter_list(UNITTEST_SOURCES ALL_UNITTEST_SOURCES)
create_source_groups(ALL_INNOEXTRACT_SOURCES)
@ -481,6 +504,44 @@ install(TARGETS innoextract RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES ${MAN_FILE} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 OPTIONAL)
# Test target
if(BUILD_TESTS)
enable_testing()
set(run_tests)
if(RUN_TESTS)
set(run_tests ALL)
endif()
add_executable(unittest ${UNITTEST_SOURCES})
target_link_libraries(unittest ${LIBRARIES})
target_compile_definitions(unittest PRIVATE INNOEXTRACT_BUILD_TESTS)
set(unittest_binary "$<TARGET_FILE:unittest>")
add_test(NAME "unit tests"
COMMAND ${RUN_TARGET} "${unittest_binary}"
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
)
add_custom_command(
OUTPUT "${PROJECT_BINARY_DIR}/unittest.check"
COMMAND ${RUN_TARGET} "${unittest_binary}"
COMMAND ${CMAKE_COMMAND} -E touch "${PROJECT_BINARY_DIR}/unittest.check"
DEPENDS unittest
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
COMMENT "Running unit tests" VERBATIM
)
add_custom_target(check ${run_tests}
DEPENDS "${PROJECT_BINARY_DIR}/unittest.check"
)
endif()
# Additional targets.
add_style_check_target(style "${ALL_INNOEXTRACT_SOURCES}" innoextract)
@ -525,10 +586,22 @@ print_configuration("Charset conversion"
message("")
if(DEVELOPER)
file(READ "README.md" readme)
parse_version_file("VERSION" "VERSION")
string(REPLACE "${VERSION_2}" "" readme_without_version "${readme}")
if(readme_without_version STREQUAL readme)
message(WARNING "Could not find '${VERSION_2}' in README.md.")
endif()
foreach(file IN LISTS ALL_INNOEXTRACT_SOURCES)
file(READ "${file}" source)
if(source MATCHES ".*INNOEXTRACT_TEST.*")
list(FIND ALL_UNITTEST_SOURCES ${file} result)
if(result EQUAL -1)
message(WARNING "Could not find '${file}' in UNITTEST_SOURCES")
endif()
endif()
endforeach()
endif()

7
README.md

@ -68,13 +68,16 @@ The default build settings are tuned for users - if you plan to make changes to
| `DEVELOPER` | `OFF` | Enable build options suitable for developers⁵.
| `FASTLINK` | `OFF`⁶ | Optimize for link speed.
| `USE_LTO` | `ON`² | Use link-time code generation.
| `BUILD_TESTS` | `OFF`⁶ | Build unit tests that can be run using `make check`
| `RUN_TESTS` | `OFF`⁷ | Automatically run tests
| `RUN_TARGET` | (none) | Wrapper to run binaries produced in the build process
1. The builtin charset conversion only supports Windows-1252 and UTF-16LE. This is normally enough for filenames, but custom message strings (which can be included in filenames) may use arbitrary encodings.
2. Enabled automatically if `CMAKE_BUILD_TYPE` is set to `Debug`.
3. Under Windows, the default is `ON`.
4. Default is `ON` if `USE_STATIC_LIBS` is enabled.
5. Currently this and enables `DEBUG` and `FASTLINK` for faster incremental builds and improved debug output, unless those options have been explicitly specified by the user.
5. Currently this and enables `DEBUG`, `BUILD_TESTS`, `RUN_TESTS` and `FASTLINK` for faster incremental builds and improved debug output, unless those options have been explicitly specified by the user.
6. Enabled automatically if `DEVELOPER` is enabled.
7. Disabled automatically if `SET_OPTIMIZATION_FLAGS` is disabled or `FASTLINK` is enabled.
7. Enabled automatically if `DEVELOPER` is enabled unless cross-compiling without `RUN_TARGET` set
Install options:

4
cmake/FilterList.cmake

@ -131,6 +131,10 @@ function(filter_list LIST_NAME)
message(FATAL_ERROR "bad filter_list syntax: unexpected end, expected }")
endif()
if(last_item)
list(APPEND filtered ${last_item})
endif()
list(SORT filtered)
list(REMOVE_DUPLICATES filtered)
set(${LIST_NAME} ${filtered} PARENT_SCOPE)

120
src/util/test.cpp

@ -0,0 +1,120 @@
/*
* Copyright (C) 2024 Daniel Scharrer
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the author(s) be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "util/test.hpp"
#include "util/windows.hpp"
#include "configure.hpp"
#if INNOEXTRACT_HAVE_ISATTY
#include <unistd.h>
#endif
namespace {
bool test_verbose = false;
bool test_progress = false;
int test_failed = 0;
} // anonymous namewspace
Testsuite * Testsuite::tests = NULL;
Testsuite::Testsuite(const char * suitename) : name(suitename) {
next = tests;
tests = this;
}
int Testsuite::run_all() {
int count = 0;
for(Testsuite * test = tests; test; test = test->next) {
count++;
}
int len = 2;
int r = count;
while(r >= 10) {
len++;
r /= 10;
}
int i = 0;
for(Testsuite * test = tests; test; test = test->next) {
i++;
if(test_verbose || test_progress) {
std::printf("%*d/%d [%s]", len, i, count, test->name);
if(test_verbose) {
std::printf("\n");
}
}
try {
test->run();
} catch(...) {
test_failed++;
if(test_progress) {
std::printf("\r\x1b[K");
}
std::fprintf(stderr, "%s: EXCEPTION\n", test->name);
}
}
if(test_progress) {
std::printf("\r\x1b[K");
}
if(test_failed == 0) {
std::printf("all %d test suites ok\n", count);
}
return test_failed > 0 ? 1 : 0;
}
void Testsuite::test(const char * testcase, bool ok) {
if(!ok) {
test_failed++;
}
if(test_progress) {
std::printf("\r\x1b[K");
}
if(!ok || test_verbose) {
std::fprintf(ok ? stdout : stderr, "%s.%s: %s\n", name, testcase, ok ? "ok" : "FAILED");
}
}
int main(int argc, const char * argv[]) {
if((argc > 1 && std::strcmp(argv[1], "--verbose") == 0) || \
(argc > 1 && argv[1][0] == '-' && argv[1][1] != '-' && std::strchr(argv[1], 'v')) || \
(std::getenv("VERBOSE") && std::strcmp(std::getenv("VERBOSE"), "0") != 0)) {
test_verbose = true;
} else {
#if defined(_WIN32) || INNOEXTRACT_HAVE_ISATTY
test_progress = isatty(1) && isatty(2);
#endif
if(test_progress) {
char * term = std::getenv("TERM");
if(!term || !std::strcmp(term, "dumb")) {
test_progress = false; // Terminal does not support escape sequences
}
}
}
return Testsuite::run_all();
}

81
src/util/test.hpp

@ -0,0 +1,81 @@
/*
* Copyright (C) 2024 Daniel Scharrer
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the author(s) be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/*!
* \file
*
* Test utility functions.
*/
#ifndef INNOEXTRACT_UTIL_TEST_HPP
#define INNOEXTRACT_UTIL_TEST_HPP
#ifdef INNOEXTRACT_BUILD_TESTS
#include <stddef.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
static const char * testdata = "The dhole (pronounced \"dole\") is also known as the Asiatic wild dog,"
" red dog, and whistling dog. It is about the size of a German shepherd but"
" looks more like a long-legged fox. This highly elusive and skilled jumper"
" is classified with wolves, coyotes, jackals, and foxes in the taxonomic"
" family Canidae.";
static const size_t testlen = std::strlen(testdata);
struct Testsuite {
Testsuite(const char * suitename);
static int run_all();
void test(const char * testcase, bool ok);
inline void test_equals(const char * testcase, const void * a, const void * b, size_t count) {
test(testcase, std::memcmp(a, b, count) == 0);
}
virtual void run() = 0;
private:
static Testsuite * tests;
Testsuite * next;
protected:
const char * name;
};
#define INNOEXTRACT_TEST(Name, ...) \
struct Name ## _test : public Testsuite { \
Name ## _test() : Testsuite(# Name) { } \
void run(); \
} test_ ## Name; \
void Name ## _test::run() { __VA_ARGS__ }
#else
#define INNOEXTRACT_TEST(Name, ...)
#endif // INNOEXTRACT_BUILD_TESTS
#endif // INNOEXTRACT_UTIL_TEST_HPP
Loading…
Cancel
Save