diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca61ae6..b8d10f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,16 @@ You should have received a copy of the GNU General Public License
along with DrMock. If not, see .
-->
+# DrMock 0.4.0
+
+Release 2020/08/16
+
+### Added/Changed:
+
+* Add `DRTEST_ASSERT_DEATH` macro for death testing
+
+
+
# DrMock 0.3.0
Released 2020/07/05
@@ -50,6 +60,8 @@ Released 2020/07/05
defined, but rather serves as a catch-all (fallthru) state (the
documentation regarding this has been clarified)
+
+
# DrMock 0.2.0
Released 2020/05/15
@@ -108,6 +120,8 @@ Released 2020/05/15
* Throw error message if `DrMockTest` can't find files specified in `TESTS`
+
+
# DrMock 0.1.0
Released 2020/01/10
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1d5c3ab..5d38c87 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,7 +23,7 @@ cmake_minimum_required (VERSION 3.13)
project(
DrMock
- VERSION 0.3.0
+ VERSION 0.4.0
DESCRIPTION "C++17 testing and mocking framework"
LANGUAGES CXX
)
diff --git a/README.md b/README.md
index e8869a8..d4cf223 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ features for mock object configuration.
### Announcments
-Release v0.3.x is now available. For details, see
+Release v0.4.x is now available. For details, see
[changelog](CHANGELOG.md).
### Getting started
diff --git a/docs/samples/death.md b/docs/samples/death.md
new file mode 100644
index 0000000..86341a5
--- /dev/null
+++ b/docs/samples/death.md
@@ -0,0 +1,99 @@
+
+
+# samples/death
+
+DrMock v0.4.0 introduces death tests. A _death test_ checks if a certain
+statement will cause the process to raise a certain signal. This may be
+used to assert that in certain unrecoverable situations, the program
+exits before causing further damage.
+
+This sample demonstrates the death testing capabilities of **DrMock**.
+
+### Table of contents
+
+* [Source code](#source-code)
+* [Running the tests](#running-the-tests)
+* [Details](#details)
+ + [Supported signals](#supported-signals)
+ + [clone, fork, signal, multi-threading](#clone-fork-signal-multi-threading)
+
+### Project structure
+
+```
+samples/death
+│ CMakeLists.txt
+│ Makefile
+│ deathTest.cpp
+```
+
+## Source code
+
+Open `deathTest.cpp`!
+The `DRTEST_ASSERT_DEATH(statement, expected)` macro checks if executing
+`statement` will cause the signal `expected` to be raised. As with
+`DRTEST_ASSERT_THROW`, statement may contain multiple lines of code, if
+they are seperated by semicolons (see below).
+
+In the test `catch_segfault`, we test the classic segmentation fault
+scenario, dereferencing a `nullptr`:
+```cpp
+DRTEST_TEST(catch_segfault)
+{
+ DRTEST_ASSERT_DEATH(
+ int* foo = nullptr;
+ *foo = 0,
+ SIGSEGV
+ );
+}
+```
+We expect this to raise the `SIGSEGV` signal. The test will verify this.
+
+## Running the tests
+
+Do `make`. This should yield the following:
+
+```
+ Start 1: deathTest
+1/1 Test #1: deathTest ........................ Passed 0.00 sec
+
+100% tests passed, 0 tests failed out of 1
+
+Total Test time (real) = 0.01 sec
+```
+
+## Details
+
+### Supported signals
+
+The following POSIX signals may be caught using `DRTEST_ASSERT_DEATH`:
+```cpp
+SIGABRT, SIGALRM, SIGBUS, SIGCHLD,
+SIGCONT, SIGFPE, SIGHUP, SIGILL,
+SIGINT, SIGPIPE, SIGPROF, SIGQUIT,
+SIGSEGV, SIGTSTP, SIGSYS, SIGTERM,
+SIGTRAP, SIGTTIN, SIGTTOU, SIGURG,
+SIGUSR2, SIGVTALRM, SIGXCPU, SIGXFSZ
+```
+Note that `SIGKILL`, `SIGSTOP` and `SIGUSR1` are not supported.
+
+### clone, fork, signal, multi-threading
+
+Using `clone()`, `fork()`, `signal()` or multi-threading are not allowed
+when using `DRTEST_ASSERT_DEATH`.
diff --git a/docs/tutorial.md b/docs/tutorial.md
index ebfe30b..2ade822 100644
--- a/docs/tutorial.md
+++ b/docs/tutorial.md
@@ -51,3 +51,9 @@ Learn how to use **DrMock**'s mock objects as stubs for state verification.
Learn how to use **DrMock** with Qt5.
---
+
+[samples/death](samples/death.md)
+
+Learn how to use **DrMock** for death tests.
+
+---
diff --git a/python/setup.py b/python/setup.py
index 975a5f8..5750a4d 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -20,7 +20,7 @@
setup(
name = "DrMockGenerator",
author = "Ole Kliemann, Malte Kliemann",
- version = "0.3.0",
+ version = "0.4.0",
scripts = ["DrMockGenerator"],
packages = ["mocker"],
include_package_data = True,
diff --git a/samples/Makefile b/samples/Makefile
index f23e73f..3c2bb24 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -1,4 +1,4 @@
-dirs = basic example mock qt states
+dirs = basic example mock qt states death
.PHONY: default
default:
diff --git a/samples/death/CMakeLists.txt b/samples/death/CMakeLists.txt
new file mode 100644
index 0000000..ea83bef
--- /dev/null
+++ b/samples/death/CMakeLists.txt
@@ -0,0 +1,40 @@
+# Copyright 2020 Ole Kliemann, Malte Kliemann
+#
+# This file is part of DrMock.
+#
+# DrMock is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# DrMock is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with DrMock. If not, see .
+
+cmake_minimum_required(VERSION 3.10)
+
+#######################################
+# Project data.
+#######################################
+
+project(DrMockSampleBasic)
+set(CMAKE_CXX_STANDARD 17)
+
+#######################################
+# Dependencies.
+#######################################
+
+find_package(DrMock COMPONENTS Core REQUIRED)
+
+#######################################
+# Testing.
+#######################################
+
+enable_testing()
+DrMockTest(TESTS
+ deathTest.cpp
+)
diff --git a/samples/death/Makefile b/samples/death/Makefile
new file mode 100644
index 0000000..58fefeb
--- /dev/null
+++ b/samples/death/Makefile
@@ -0,0 +1,18 @@
+# Discover operating system.
+uname := $(shell uname -s)
+
+# Get number of threads
+ifeq ($(uname), Darwin)
+ num_threads := $(shell sysctl -n hw.activecpu)
+else # Assuming Linux.
+ num_threads := $(shell nproc)
+endif
+
+.PHONY: default
+default:
+ mkdir -p build && cd build && cmake .. -DCMAKE_PREFIX_PATH="../../prefix"
+ cd build && make -j$(num_threads) && ctest --output-on-failure
+
+.PHONY: clean
+clean:
+ rm -fr build && rm -fr prefix
diff --git a/samples/death/deathTest.cpp b/samples/death/deathTest.cpp
new file mode 100644
index 0000000..dbc0586
--- /dev/null
+++ b/samples/death/deathTest.cpp
@@ -0,0 +1,28 @@
+/* Copyright 2020 Ole Kliemann, Malte Kliemann
+ *
+ * This file is part of DrMock.
+ *
+ * DrMock is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrMock is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DrMock. If not, see .
+*/
+
+#include "DrMock/Test.h"
+
+DRTEST_TEST(catch_segfault)
+{
+ DRTEST_ASSERT_DEATH(
+ int* foo = nullptr;
+ *foo = 0,
+ SIGSEGV
+ );
+}
diff --git a/src/test/Death.h b/src/test/Death.h
new file mode 100644
index 0000000..3b681c3
--- /dev/null
+++ b/src/test/Death.h
@@ -0,0 +1,130 @@
+/* Copyright 2020 Ole Kliemann, Malte Kliemann
+ *
+ * This file is part of DrMock.
+ *
+ * DrMock is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DrMock is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with DrMock. If not, see .
+*/
+
+#ifndef DRMOCK_SRC_TEST_DEATH_H
+#define DRMOCK_SRC_TEST_DEATH_H
+
+#include
+
+#if defined(__unix__) || defined(__APPLE__)
+#include
+#endif
+
+#include "TestFailure.h"
+
+namespace drtest { namespace death {
+
+static int pipe_[2];
+volatile std::sig_atomic_t atomic_pipe_; // Self-pipe write end; required due to https://en.cppreference.com/w/c/program/signal
+
+#if defined(__unix__) || defined(__APPLE__)
+static std::vector signals_ = { // POSIX signals
+ SIGABRT,
+ SIGALRM,
+ SIGBUS,
+ SIGCHLD,
+ SIGCONT,
+ SIGFPE,
+ SIGHUP,
+ SIGILL,
+ SIGINT,
+ // SIGKILL,
+ SIGPIPE,
+ SIGPROF,
+ SIGQUIT,
+ SIGSEGV,
+ // SIGSTOP,
+ SIGTSTP,
+ SIGSYS,
+ SIGTERM,
+ SIGTRAP,
+ SIGTTIN,
+ SIGTTOU,
+ SIGURG,
+ // SIGUSR1,
+ SIGUSR2,
+ SIGVTALRM,
+ SIGXCPU,
+ SIGXFSZ
+ };
+#endif
+
+// Signal handler requires external linkage according to https://en.cppreference.com/w/c/program/signal
+extern "C" {
+ void signal_handler(int x)
+ {
+ write(atomic_pipe_, &x, 4);
+ exit(0);
+ }
+} // extern C
+
+}} // namespace drtest::death
+
+#define DRTEST_ASSERT_DEATH(statement, expected) \
+do \
+{ \
+ pipe(drtest::death::pipe_); \
+ drtest::death::atomic_pipe_ = drtest::death::pipe_[1]; \
+\
+ pid_t pid = fork(); \
+ if (pid == 0) \
+ { \
+ for (auto s: drtest::death::signals_) \
+ { \
+ std::signal(s, drtest::death::signal_handler); \
+ } \
+ statement; /* Child exits here if signal is raised. */ \
+ int no_signal = -1; \
+ write(drtest::death::atomic_pipe_, &no_signal, 4); /* Wake up parent if no signal was raised. */ \
+ close(drtest::death::pipe_[0]); \
+ close(drtest::death::pipe_[1]); \
+ exit(0); \
+ } \
+ else \
+ { \
+ assert(PIPE_BUF >= 4); \
+ std::vector buffer(4); \
+ if (!read(drtest::death::pipe_[0], buffer.data(), 4)) \
+ { \
+ throw std::runtime_error{"read to pipe failed"}; \
+ } \
+\
+ int result = *(int*)buffer.data(); \
+ if (result != expected) \
+ { \
+\
+ /* Error message */ \
+ std::string e = strsignal(expected); \
+ std::string r; \
+ if (result != -1) \
+ { \
+ r = strsignal(result); \
+ } \
+ else \
+ { \
+ r = "No signal: -1"; \
+ } \
+ throw drtest::detail::TestFailure{__LINE__, "!=", "received", "expected", e, r}; \
+ } \
+ } \
+\
+ close(drtest::death::pipe_[0]); \
+ close(drtest::death::pipe_[1]); \
+} while(false)
+
+#endif /* DRMOCK_SRC_TEST_DEATH_H */
diff --git a/src/test/TestMacros.h b/src/test/TestMacros.h
index de6f773..e24a947 100644
--- a/src/test/TestMacros.h
+++ b/src/test/TestMacros.h
@@ -19,6 +19,7 @@
#ifndef DRMOCK_SRC_TEST_TESTMACROS_H
#define DRMOCK_SRC_TEST_TESTMACROS_H
+#include "Death.h"
#include "FunctionInvoker.h"
#include "Global.h"
#include "TestFailure.h"
diff --git a/tests/Test.cpp b/tests/Test.cpp
index 12d4e30..c8144fb 100644
--- a/tests/Test.cpp
+++ b/tests/Test.cpp
@@ -203,3 +203,22 @@ DRTEST_TEST(streamIfStreamable)
// not having a streaming operator.
DRTEST_ASSERT_EQ(A{}, A{});
}
+
+DRTEST_TEST(death_success)
+{
+ DRTEST_ASSERT_DEATH(raise(SIGSEGV), SIGSEGV);
+ DRTEST_ASSERT_DEATH(raise(SIGCHLD), SIGCHLD);
+ DRTEST_ASSERT_DEATH(raise(SIGABRT), SIGABRT);
+ DRTEST_ASSERT_DEATH(volatile int* foo = nullptr; *foo =123, SIGSEGV);
+ DRTEST_ASSERT_DEATH(assert(false), SIGABRT);
+}
+
+DRTEST_TEST(death_failure_no_raise)
+{
+ DRTEST_ASSERT_TEST_FAIL(DRTEST_ASSERT_DEATH(int x = 0; (void)x, SIGSEGV));
+}
+
+DRTEST_TEST(death_failure_wrong_raise)
+{
+ DRTEST_ASSERT_TEST_FAIL(DRTEST_ASSERT_DEATH(raise(SIGXFSZ), SIGSEGV));
+}