Browse Source

[Project] Add doctest unit testing library

tags/3.0
Vsevolod Stakhov 3 years ago
parent
commit
57fe176918

+ 3
- 0
CMakeLists.txt View File

@@ -118,6 +118,7 @@ INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/"
"${CMAKE_SOURCE_DIR}/contrib/lua-lpeg"
"${CMAKE_SOURCE_DIR}/contrib/frozen/include"
"${CMAKE_SOURCE_DIR}/contrib/fmt/include"
"${CMAKE_SOURCE_DIR}/contrib/doctest"
"${CMAKE_BINARY_DIR}/src" #Stored in the binary dir
"${CMAKE_BINARY_DIR}/src/libcryptobox")

@@ -250,6 +251,7 @@ SET(CMAKE_C_FLAGS "${CMAKE_C_OPT_FLAGS} ${CMAKE_C_FLAGS}")
SET(CMAKE_CXX_FLAGS "${CMAKE_C_OPT_FLAGS} ${CMAKE_CXX_FLAGS}")

ADD_DEFINITIONS(-DHAVE_CONFIG_H)
ADD_DEFINITIONS(-DDOCTEST_CONFIG_SUPER_FAST_ASSERTS)

# Check platform specific includes
CHECK_INCLUDE_FILES(sys/types.h HAVE_SYS_TYPES_H)
@@ -652,6 +654,7 @@ ADD_SUBDIRECTORY(contrib/kann)
ADD_SUBDIRECTORY(contrib/fastutf8)
ADD_SUBDIRECTORY(contrib/google-ced)
ADD_SUBDIRECTORY(contrib/fmt)
ADD_SUBDIRECTORY(contrib/doctest)

IF (NOT WITH_LUAJIT)
ADD_SUBDIRECTORY(contrib/lua-bit)

+ 63
- 0
contrib/doctest/CMakeLists.txt View File

@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.0)

if(POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
endif()

################################################################################
## DOCTEST
################################################################################

project(doctest VERSION 2.4.5 LANGUAGES CXX)

# Determine if doctest is built as a subproject (using add_subdirectory) or if it is the main project.
set(MAIN_PROJECT OFF)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(MAIN_PROJECT ON)
endif()

option(DOCTEST_WITH_TESTS "Build tests/examples" ${MAIN_PROJECT})
option(DOCTEST_WITH_MAIN_IN_STATIC_LIB "Build a static lib (cmake target) with a default main entry point" ON)
option(DOCTEST_NO_INSTALL "Skip the installation process" OFF)
option(DOCTEST_USE_STD_HEADERS "Use std headers" OFF)

add_library(${PROJECT_NAME} INTERFACE)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

if(NOT CMAKE_VERSION VERSION_LESS 3.8)
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_11)
endif()

set(doctest_parts_folder "${CMAKE_CURRENT_SOURCE_DIR}/doctest/parts")
set(doctest_folder "${CMAKE_CURRENT_SOURCE_DIR}/") # in order to have the mpi extension files, not included into the doctest.h single header

if(MAIN_PROJECT)
# use a special hidden version of the header which directly includes the 2 parts - proper reporting of file/line locations during dev
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/scripts/development_only/>
$<BUILD_INTERFACE:${doctest_parts_folder}>
$<BUILD_INTERFACE:${doctest_folder}>)

# add a custom target that assembles the single header when any of the parts are touched
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/doctest/doctest.h
DEPENDS
${doctest_parts_folder}/doctest_fwd.h
${doctest_parts_folder}/doctest.cpp
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/scripts/cmake/assemble_single_header.cmake
COMMENT "assembling the single header")

add_custom_target(assemble_single_header ALL DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/doctest/doctest.h)
else()
target_include_directories(${PROJECT_NAME} INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/>)
endif()

# hack to support building on XCode 6 and 7 - propagate the definition to everything
if(DEFINED DOCTEST_THREAD_LOCAL)
target_compile_definitions(${PROJECT_NAME} INTERFACE
DOCTEST_THREAD_LOCAL=${DOCTEST_THREAD_LOCAL})
endif()

if(DOCTEST_USE_STD_HEADERS)
target_compile_definitions(${PROJECT_NAME} INTERFACE DOCTEST_CONFIG_USE_STD_HEADERS)
endif()

+ 21
- 0
contrib/doctest/LICENSE.txt View File

@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016-2021 Viktor Kirilov

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.

+ 153
- 0
contrib/doctest/README.md View File

@@ -0,0 +1,153 @@
<p align="center"><img src="scripts/data/logo/logo_1.svg"></p>
<b>
<table>
<tr>
<td>
master branch
</td>
<td>
Windows <a href="https://ci.appveyor.com/project/onqtam/doctest/branch/master"><img src="https://ci.appveyor.com/api/projects/status/j89qxtahyw1dp4gd/branch/master?svg=true"></a>
</td>
<td>
All <a href="https://github.com/onqtam/doctest/actions?query=branch%3Amaster"><img src="https://github.com/onqtam/doctest/workflows/CI/badge.svg?branch=master"></a>
</td>
<td>
<a href="https://coveralls.io/github/onqtam/doctest?branch=master"><img src="https://coveralls.io/repos/github/onqtam/doctest/badge.svg?branch=master"></a>
</td>
<!--
<td>
<a href="https://scan.coverity.com/projects/onqtam-doctest"><img src="https://scan.coverity.com/projects/7865/badge.svg"></a>
</td>
-->
</tr>
<tr>
<td>
dev branch
</td>
<td>
Windows <a href="https://ci.appveyor.com/project/onqtam/doctest/branch/dev"><img src="https://ci.appveyor.com/api/projects/status/j89qxtahyw1dp4gd/branch/dev?svg=true"></a>
</td>
<td>
All <a href="https://github.com/onqtam/doctest/actions?query=branch%3Adev"><img src="https://github.com/onqtam/doctest/workflows/CI/badge.svg?branch=dev"></a>
</td>
<td>
<a href="https://coveralls.io/github/onqtam/doctest?branch=dev"><img src="https://coveralls.io/repos/github/onqtam/doctest/badge.svg?branch=dev"></a>
</td>
<!--
<td>
</td>
-->
</tr>
</table>
</b>

**doctest** is a new C++ testing framework but is by far the fastest both in compile times (by [**orders of magnitude**](doc/markdown/benchmarks.md)) and runtime compared to other feature-rich alternatives. It brings the ability of compiled languages such as [**D**](https://dlang.org/spec/unittest.html) / [**Rust**](https://doc.rust-lang.org/book/second-edition/ch11-00-testing.html) / [**Nim**](https://nim-lang.org/docs/unittest.html) to have tests written directly in the production code thanks to a fast, transparent and flexible test runner with a clean interface.

[![Standard](https://img.shields.io/badge/c%2B%2B-11/14/17/20-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Version](https://badge.fury.io/gh/onqtam%2Fdoctest.svg)](https://github.com/onqtam/doctest/releases)
[![download](https://img.shields.io/badge/download%20%20-link-blue.svg)](https://raw.githubusercontent.com/onqtam/doctest/master/doctest/doctest.h)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/503/badge)](https://bestpractices.coreinfrastructure.org/projects/503)
[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/onqtam/doctest.svg)](https://lgtm.com/projects/g/onqtam/doctest/context:cpp)
[![Join the chat at https://gitter.im/onqtam/doctest](https://badges.gitter.im/onqtam/doctest.svg)](https://gitter.im/onqtam/doctest?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Try it online](https://img.shields.io/badge/try%20it-online-orange.svg)](https://wandbox.org/permlink/nJIibfbivG7BG7r1)
<!--
[![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/)
[![documentation](https://img.shields.io/badge/documentation%20%20-online-blue.svg)](https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md#reference)
-->

[<img src="https://cloud.githubusercontent.com/assets/8225057/5990484/70413560-a9ab-11e4-8942-1a63607c0b00.png" align="right">](http://www.patreon.com/onqtam)

The framework is and will stay free but needs your support to sustain its development. There are lots of <a href="doc/markdown/roadmap.md"><b>new features</b></a> and maintenance to do. If you work for a company using **doctest** or have the means to do so, please consider financial support. Monthly donations via Patreon and one-offs via PayPal.

[<img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" align="right">](https://www.paypal.me/onqtam/10)

A complete example with a self-registering test that compiles to an executable looks like this:

![cover-example](scripts/data/using_doctest_888px_wide.gif)

There are many C++ testing frameworks - [Catch](https://github.com/catchorg/Catch2), [Boost.Test](http://www.boost.org/doc/libs/1_64_0/libs/test/doc/html/index.html), [UnitTest++](https://github.com/unittest-cpp/unittest-cpp), [cpputest](https://github.com/cpputest/cpputest), [googletest](https://github.com/google/googletest) and many [other](https://en.wikipedia.org/wiki/List_of_unit_testing_frameworks#C.2B.2B).

The **key** differences between it and other testing frameworks are that it is light and unintrusive:
- Ultra light on compile times both in terms of [**including the header**](doc/markdown/benchmarks.md#cost-of-including-the-header) and writing [**thousands of asserts**](doc/markdown/benchmarks.md#cost-of-an-assertion-macro)
- Doesn't produce any warnings even on the [**most aggressive**](scripts/cmake/common.cmake#L84) warning levels for **MSVC**/**GCC**/**Clang**
- Offers a way to remove **everything** testing-related from the binary with the [**```DOCTEST_CONFIG_DISABLE```**](doc/markdown/configuration.md#doctest_config_disable) identifier
- [**thread-safe**](doc/markdown/faq.md#is-doctest-thread-aware) - asserts (and logging) can be used from multiple threads spawned from a single test case - [**example**](examples/all_features/concurrency.cpp)
- asserts can be used [**outside of a testing context**](doc/markdown/assertions.md#using-asserts-out-of-a-testing-context) - as a general purpose assert library - [**example**](examples/all_features/asserts_used_outside_of_tests.cpp)
- Doesn't pollute the global namespace (everything is in namespace ```doctest```) and doesn't drag **any** headers with it
- Very [**portable**](doc/markdown/features.md#extremely-portable) C++11 (use tag [**1.2.9**](https://github.com/onqtam/doctest/tree/1.2.9) for C++98) with over 180 different CI builds (static analysis, sanitizers...)
- binaries (exe/dll) can use the test runner of another binary - so tests end up in a single registry - [**example**](examples/executable_dll_and_plugin/)

![cost-of-including-the-framework-header](scripts/data/benchmarks/header.png)

This allows the framework to be used in more ways than any other - tests can be written directly in the production code!

*Tests can be considered a form of documentation and should be able to reside near the production code which they test.*

- This makes the barrier for writing tests **much lower** - you don't have to: **1)** make a separate source file **2)** include a bunch of stuff in it **3)** add it to the build system and **4)** add it to source control - You can just write the tests for a class or a piece of functionality at the bottom of its source file - or even header file!
- Tests in the production code can be thought of as documentation or up-to-date comments - showing the use of APIs
- Testing internals that are not exposed through the public API and headers is no longer a mind-bending exercise
- [**Test-driven development**](https://en.wikipedia.org/wiki/Test-driven_development) in C++ has never been easier!

The framework can be used like any other if you don't want/need to mix production code and tests - check out the [**features**](doc/markdown/features.md).

**doctest** is modeled after [**Catch**](https://github.com/catchorg/Catch2) and some parts of the code have been taken directly - check out [**the differences**](doc/markdown/faq.md#how-is-doctest-different-from-catch).

[This table](https://github.com/martinmoene/catch-lest-other-comparison) compares **doctest** / [**Catch**](https://github.com/catchorg/Catch2) / [**lest**](https://github.com/martinmoene/lest) which are all very similar.

Checkout the [**CppCon 2017 talk**](https://cppcon2017.sched.com/event/BgsI/mix-tests-and-production-code-with-doctest-implementing-and-using-the-fastest-modern-c-testing-framework) on [**YouTube**](https://www.youtube.com/watch?v=eH1CxEC29l8) to get a better understanding of how the framework works and read about how to use it in [**the JetBrains article**](https://blog.jetbrains.com/rscpp/better-ways-testing-with-doctest/) - highlighting the unique aspects of the framework! On a short description on how to use the framework along production code you could refer to [**this GitHub issue**](https://github.com/onqtam/doctest/issues/252). There is also an [**older article**](https://accu.org/var/uploads/journals/Overload137.pdf) in the february edition of ACCU Overload 2017.

[![CppCon 2017 talk about doctest on youtube](scripts/data/youtube-cppcon-talk-thumbnail.png)](https://www.youtube.com/watch?v=eH1CxEC29l8)

Documentation
-------------

Project:

- [Features and design goals](doc/markdown/features.md) - the complete list of features
- [Roadmap](doc/markdown/roadmap.md) - upcoming features
- [Benchmarks](doc/markdown/benchmarks.md) - compile-time and runtime supremacy
- [Contributing](CONTRIBUTING.md) - how to make a proper pull request
- [Changelog](CHANGELOG.md) - generated changelog based on closed issues/PRs

Usage:

- [Tutorial](doc/markdown/tutorial.md) - make sure you have read it before the other parts of the documentation
- [Assertion macros](doc/markdown/assertions.md)
- [Test cases, subcases and test fixtures](doc/markdown/testcases.md)
- [Parameterized test cases](doc/markdown/parameterized-tests.md)
- [Command line](doc/markdown/commandline.md)
- [Logging macros](doc/markdown/logging.md)
- [```main()``` entry point](doc/markdown/main.md)
- [Configuration](doc/markdown/configuration.md)
- [String conversions](doc/markdown/stringification.md)
- [Reporters](doc/markdown/reporters.md)
- [Extensions](doc/markdown/extensions.md)
- [FAQ](doc/markdown/faq.md)
- [Build systems](doc/markdown/build-systems.md)
- [Examples](examples)

Contributing
------------

[<img src="https://cloud.githubusercontent.com/assets/8225057/5990484/70413560-a9ab-11e4-8942-1a63607c0b00.png" align="right">](http://www.patreon.com/onqtam)

Support the development of the project with donations! There is a list of planned features which are all important and big - see the [**roadmap**](doc/markdown/roadmap.md). I took a break from working in the industry to make open source software so every cent is a big deal.

[<img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" align="right">](https://www.paypal.me/onqtam/10)

If you work for a company using **doctest** or have the means to do so, please consider financial support.

Contributions in the form of issues and pull requests are welcome as well - check out the [**Contributing**](CONTRIBUTING.md) page.

Stargazers over time
------------

[![Stargazers over time](https://starcharts.herokuapp.com/onqtam/doctest.svg)](https://starcharts.herokuapp.com/onqtam/doctest)

Logo
------------

The [logo](scripts/data/logo) is licensed under a Creative Commons Attribution 4.0 International License. Copyright &copy; 2019 [area55git](https://github.com/area55git) &nbsp; [![License: CC BY 4.0](https://licensebuttons.net/l/by/4.0/80x15.png)](https://creativecommons.org/licenses/by/4.0/)

<p align="center"><img src="scripts/data/logo/icon_2.svg"></p>

+ 6507
- 0
contrib/doctest/doctest/doctest.h
File diff suppressed because it is too large
View File


+ 120
- 0
contrib/doctest/doctest/extensions/doctest_mpi.h View File

@@ -0,0 +1,120 @@
#pragma once

#ifdef DOCTEST_CONFIG_IMPLEMENT

#include "doctest/doctest.h"
#include "mpi_reporter.h"

#else

#include "mpi.h"
#include <numeric>
#include <vector>
#include "doctest/doctest.h"
#include <cassert>
#include <string>

namespace doctest {

inline
int mpi_world_nb_procs() {
int n;
MPI_Comm_size(MPI_COMM_WORLD, &n);
return n;
}

struct mpi_sub_comm {
int nb_procs;
int rank;
MPI_Comm comm;

mpi_sub_comm( mpi_sub_comm const& ) = delete;
mpi_sub_comm& operator=( mpi_sub_comm const& ) = delete;

mpi_sub_comm(int nb_prcs) noexcept
: nb_procs(nb_prcs)
, rank(-1)
, comm(MPI_COMM_NULL)
{
int comm_world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &comm_world_rank);
if (nb_procs>mpi_world_nb_procs()) {
if (comm_world_rank==0) {
MESSAGE(
"Unable to run test: need ", std::to_string(nb_procs), " procs",
" but program launched with only ", std::to_string(doctest::mpi_world_nb_procs()), "."
);
CHECK(nb_procs<=mpi_world_nb_procs());
}
} else {
int color = MPI_UNDEFINED;
if(comm_world_rank < nb_procs){
color = 0;
}
MPI_Comm_split(MPI_COMM_WORLD, color, comm_world_rank, &comm);

if(comm != MPI_COMM_NULL){
MPI_Comm_rank(comm, &rank);
assert(rank==comm_world_rank);
}
}
}

~mpi_sub_comm() {
if(comm != MPI_COMM_NULL){
MPI_Comm_free(&comm);
}
}
};


template<int nb_procs, class F>
void execute_mpi_test_case(F func) {
mpi_sub_comm sub(nb_procs);
if (sub.comm != MPI_COMM_NULL) {
func(sub.rank,nb_procs,sub.comm,std::integral_constant<int,nb_procs>{});
};
}

} // doctest


#define DOCTEST_MPI_GEN_ASSERTION(rank_to_test, assertion, ...) \
static_assert(rank_to_test<test_nb_procs_as_int_constant.value,"Trying to assert on a rank greater than the number of procs of the test!"); \
if(rank_to_test == test_rank) assertion(__VA_ARGS__)

#define DOCTEST_MPI_WARN(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_WARN,__VA_ARGS__)
#define DOCTEST_MPI_CHECK(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_CHECK,__VA_ARGS__)
#define DOCTEST_MPI_REQUIRE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_REQUIRE,__VA_ARGS__)
#define DOCTEST_MPI_WARN_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_WARN_FALSE,__VA_ARGS__)
#define DOCTEST_MPI_CHECK_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_CHECK_FALSE,__VA_ARGS__)
#define DOCTEST_MPI_REQUIRE_FALSE(rank_to_test, ...) DOCTEST_MPI_GEN_ASSERTION(rank_to_test,DOCTEST_REQUIRE_FALSE,__VA_ARGS__)

#define DOCTEST_CREATE_MPI_TEST_CASE(name,nb_procs,func) \
static void func(DOCTEST_UNUSED int test_rank, DOCTEST_UNUSED int test_nb_procs, DOCTEST_UNUSED MPI_Comm test_comm, DOCTEST_UNUSED std::integral_constant<int,nb_procs>); \
TEST_CASE(name * doctest::description("MPI_TEST_CASE")) { \
doctest::execute_mpi_test_case<nb_procs>(func); \
} \
static void func(DOCTEST_UNUSED int test_rank, DOCTEST_UNUSED int test_nb_procs, DOCTEST_UNUSED MPI_Comm test_comm, DOCTEST_UNUSED std::integral_constant<int,nb_procs> test_nb_procs_as_int_constant)
// DOC: test_rank, test_nb_procs, and test_comm are available UNDER THESE SPECIFIC NAMES in the body of the unit test
// DOC: test_nb_procs_as_int_constant is equal to test_nb_procs, but as a compile time value
// (used in CHECK-like macros to assert the checked rank exists)

#define DOCTEST_MPI_TEST_CASE(name,nb_procs) \
DOCTEST_CREATE_MPI_TEST_CASE(name,nb_procs,DOCTEST_ANONYMOUS(DOCTEST_MPI_FUNC))


// == SHORT VERSIONS OF THE MACROS
#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES)
#define MPI_WARN DOCTEST_MPI_WARN
#define MPI_CHECK DOCTEST_MPI_CHECK
#define MPI_REQUIRE DOCTEST_MPI_REQUIRE
#define MPI_WARN_FALSE DOCTEST_MPI_WARN_FALSE
#define MPI_CHECK_FALSE DOCTEST_MPI_CHECK_FALSE
#define MPI_REQUIRE_FALSE DOCTEST_MPI_REQUIRE_FALSE

#define MPI_TEST_CASE DOCTEST_MPI_TEST_CASE
#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES


#endif // DOCTEST_CONFIG_IMPLEMENT

+ 34
- 0
contrib/doctest/doctest/extensions/doctest_util.h View File

@@ -0,0 +1,34 @@
//
// doctest_util.h - an accompanying extensions header to the main doctest.h header
//
// Copyright (c) 2016-2021 Viktor Kirilov
//
// Distributed under the MIT Software License
// See accompanying file LICENSE.txt or copy at
// https://opensource.org/licenses/MIT
//
// The documentation can be found at the library's page:
// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md
//

#pragma once

#ifndef DOCTEST_LIBRARY_INCLUDED
#include "../doctest.h"
#endif

#include <memory>
#include <vector>
#include <string>

namespace doctest {

inline void applyCommandLine(doctest::Context& ctx, const std::vector<std::string>& args) {
auto doctest_args = std::make_unique<const char*[]>(args.size());
for (size_t i = 0; i < args.size(); ++i) {
doctest_args[i] = args[i].c_str();
}
ctx.applyCommandLine(args.size(), doctest_args.get());
}

} // namespace doctest

+ 236
- 0
contrib/doctest/doctest/extensions/mpi_reporter.h View File

@@ -0,0 +1,236 @@
#pragma once

// #include <doctest/doctest.h>
#include <fstream>
#include <string>
#include "mpi.h"


#include <vector>
#include <mutex>

namespace doctest {

namespace {

// https://stackoverflow.com/a/11826666/1583122
struct NullBuffer : std::streambuf {
int overflow(int c) { return c; }
};
class NullStream : public std::ostream {
public:
NullStream()
: std::ostream(&nullBuff)
{}
private:
NullBuffer nullBuff = {};
};
static NullStream nullStream;


/* \brief Extends the ConsoleReporter of doctest
* Each process writes its results to its own file
* Intended to be used when a test assertion fails and the user wants to know exactly what happens on which process
*/
struct MpiFileReporter : public ConsoleReporter {
std::ofstream logfile_stream = {};

MpiFileReporter(const ContextOptions& co)
: ConsoleReporter(co,logfile_stream)
{
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

std::string logfile_name = "doctest_" + std::to_string(rank) + ".log";

logfile_stream = std::ofstream(logfile_name.c_str(), std::fstream::out);
}
};


/* \brief Extends the ConsoleReporter of doctest
* Allows to manage the execution of tests in a parallel framework
* All results are collected on rank 0
*/
struct MpiConsoleReporter : public ConsoleReporter {
private:
static std::ostream& replace_by_null_if_not_rank_0(std::ostream* os) {
int rank = 0;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank==0) {
return *os;
} else {
return nullStream;
}
}
public:
MpiConsoleReporter(const ContextOptions& co)
: ConsoleReporter(co,replace_by_null_if_not_rank_0(co.cout))
{}

std::string file_line_to_string(const char* file, int line,
const char* tail = ""){
std::stringstream ss;
ss << skipPathFromFilename(file)
<< (opt.gnu_file_line ? ":" : "(")
<< (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option
<< (opt.gnu_file_line ? ":" : "):") << tail;
return ss.str();
}

void test_run_end(const TestRunStats& p) override {
ConsoleReporter::test_run_end(p);

const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;

// -----------------------------------------------------
// > Gather information in rank 0
int n_rank, rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &n_rank);

int g_numAsserts = 0;
int g_numAssertsFailed = 0;
int g_numTestCasesFailed = 0;

MPI_Reduce(&p.numAsserts , &g_numAsserts , 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
MPI_Reduce(&p.numAssertsFailed , &g_numAssertsFailed , 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
MPI_Reduce(&p.numTestCasesFailed, &g_numTestCasesFailed, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);

std::vector<int> numAssertsFailedByRank;
if(rank == 0){
numAssertsFailedByRank.resize(static_cast<std::size_t>(n_rank));
}

MPI_Gather(&p.numAssertsFailed, 1, MPI_INT, numAssertsFailedByRank.data(), 1, MPI_INT, 0, MPI_COMM_WORLD);

if(rank == 0) {
separator_to_stream();
s << Color::Cyan << "[doctest] " << Color::None << "glob assertions: " << std::setw(6)
<< g_numAsserts << " | "
<< ((g_numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
<< std::setw(6) << (g_numAsserts - g_numAssertsFailed) << " passed" << Color::None
<< " | " << (g_numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6)
<< g_numAssertsFailed << " failed" << Color::None << " |\n";

separator_to_stream();
if(g_numAssertsFailed > 0){

s << Color::Cyan << "[doctest] " << Color::None << "fail on rank:" << std::setw(6) << "\n";
for(std::size_t i = 0; i < numAssertsFailedByRank.size(); ++i){
if( numAssertsFailedByRank[i] > 0 ){
s << std::setw(16) << " -> On rank [" << i << "] with " << numAssertsFailedByRank[i] << " test failed" << std::endl;
}
}
}
s << Color::Cyan << "[doctest] " << Color::None
<< "Status: " << (g_numTestCasesFailed > 0 ? Color::Red : Color::Green)
<< ((g_numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
}
}

void test_case_end(const CurrentTestCaseStats& st) override {
if (is_mpi_test_case()) {
// function called by every rank at the end of a test
// if failed assertions happended, they have been sent to rank 0
// here rank zero gathers them and prints them all

int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);

// Compute the number of assert with fail among all procs
int nb_fail_asserts_glob = 0;
MPI_Reduce(&st.numAssertsFailedCurrentTest, &nb_fail_asserts_glob, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);

if(rank == 0) {
MPI_Status status;
MPI_Status status_recv;

using id_string = std::pair<int,std::string>;
std::vector<id_string> msgs(static_cast<std::size_t>(nb_fail_asserts_glob));

for (std::size_t i=0; i<static_cast<std::size_t>(nb_fail_asserts_glob); ++i) {
MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);

int count;
MPI_Get_count(&status, MPI_BYTE, &count);

std::string recv_msg(static_cast<std::size_t>(count),'\0');
void* recv_msg_data = const_cast<char*>(recv_msg.data()); // const_cast needed. Non-const .data() exists in C++11 though...
MPI_Recv(recv_msg_data, count, MPI_BYTE, status.MPI_SOURCE,
status.MPI_TAG, MPI_COMM_WORLD, &status_recv);

msgs[i] = {status.MPI_SOURCE,recv_msg};
}

std::sort(begin(msgs),end(msgs),[](const id_string& x, const id_string& y){ return x.first < y.first; });

// print
if (nb_fail_asserts_glob>0) {
separator_to_stream();
file_line_to_stream(tc->m_file.c_str(), static_cast<int>(tc->m_line), "\n");
if(tc->m_test_suite && tc->m_test_suite[0] != '\0')
s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
if(strncmp(tc->m_name, " Scenario:", 11) != 0)
s << Color::Yellow << "TEST CASE: ";
s << Color::None << tc->m_name << "\n\n";
for(const auto& msg : msgs) {
s << msg.second;
}
s << "\n";
}
}
}

ConsoleReporter::test_case_end(st);
}

bool is_mpi_test_case() const {
return tc->m_description != nullptr
&& std::string(tc->m_description) == std::string("MPI_TEST_CASE");
}

void log_assert(const AssertData& rb) override {
if (!is_mpi_test_case()) {
ConsoleReporter::log_assert(rb);
} else {
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);


if(!rb.m_failed && !opt.success)
return;

std::lock_guard<std::mutex> lock(mutex);

std::stringstream failure_msg;
failure_msg << Color::Red << "On rank [" << rank << "] : " << Color::None;
failure_msg << file_line_to_string(rb.m_file, rb.m_line, " ");

if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==0){
failure_msg << Color::Cyan
<< assertString(rb.m_at)
<< "( " << rb.m_expr << " ) "
<< Color::None

<< (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")
<< " values: "
<< assertString(rb.m_at)
<< "( " << rb.m_decomp.c_str() << " )\n";
}

std::string failure_str = failure_msg.str();
int failure_msg_size = static_cast<int>(failure_str.size());

MPI_Send(failure_str.c_str(), failure_msg_size, MPI_BYTE,
0, rb.m_line, MPI_COMM_WORLD); // Tag = file line
}
}
}; // MpiConsoleReporter

// "1" is the priority - used for ordering when multiple reporters/listeners are used
REGISTER_REPORTER("MpiConsoleReporter", 1, MpiConsoleReporter);
REGISTER_REPORTER("MpiFileReporter", 1, MpiFileReporter);

} // anonymous
} // doctest

+ 3838
- 0
contrib/doctest/doctest/parts/doctest.cpp
File diff suppressed because it is too large
View File


+ 2661
- 0
contrib/doctest/doctest/parts/doctest_fwd.h
File diff suppressed because it is too large
View File


+ 0
- 1
test/CMakeLists.txt View File

@@ -14,7 +14,6 @@ SET(TESTSRC rspamd_mem_pool_test.c
rspamd_test_suite.c)

ADD_EXECUTABLE(rspamd-test EXCLUDE_FROM_ALL ${TESTSRC})
SET_TARGET_PROPERTIES(rspamd-test PROPERTIES LINKER_LANGUAGE C)
SET_TARGET_PROPERTIES(rspamd-test PROPERTIES COMPILE_FLAGS "-DRSPAMD_TEST")
ADD_DEPENDENCIES(rspamd-test rspamd-server)
IF(USE_CXX_LINKER)

Loading…
Cancel
Save