Skip to content

Commit 98343fe

Browse files
committed
Add a descriptive test
1 parent 293acca commit 98343fe

File tree

3 files changed

+108
-1
lines changed

3 files changed

+108
-1
lines changed

cpp11test/R/cpp11.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,11 @@ rcpp_sum_dbl_accumulate_ <- function(x_sxp) {
223223
rcpp_grow_ <- function(n_sxp) {
224224
.Call(`_cpp11test_rcpp_grow_`, n_sxp)
225225
}
226+
227+
test_destruction_inner <- function() {
228+
invisible(.Call(`_cpp11test_test_destruction_inner`))
229+
}
230+
231+
test_destruction_outer <- function() {
232+
invisible(.Call(`_cpp11test_test_destruction_outer`))
233+
}

cpp11test/src/cpp11.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,10 +422,26 @@ extern "C" SEXP _cpp11test_rcpp_grow_(SEXP n_sxp) {
422422
return cpp11::as_sexp(rcpp_grow_(cpp11::as_cpp<cpp11::decay_t<SEXP>>(n_sxp)));
423423
END_CPP11
424424
}
425+
// test-protect-nested.cpp
426+
void test_destruction_inner();
427+
extern "C" SEXP _cpp11test_test_destruction_inner() {
428+
BEGIN_CPP11
429+
test_destruction_inner();
430+
return R_NilValue;
431+
END_CPP11
432+
}
433+
// test-protect-nested.cpp
434+
void test_destruction_outer();
435+
extern "C" SEXP _cpp11test_test_destruction_outer() {
436+
BEGIN_CPP11
437+
test_destruction_outer();
438+
return R_NilValue;
439+
END_CPP11
440+
}
425441

426442
extern "C" {
427443
/* .Call calls */
428-
extern SEXP run_testthat_tests(SEXP);
444+
extern SEXP run_testthat_tests(void *);
429445

430446
static const R_CallMethodDef CallEntries[] = {
431447
{"_cpp11test_col_sums", (DL_FUNC) &_cpp11test_col_sums, 1},
@@ -483,6 +499,8 @@ static const R_CallMethodDef CallEntries[] = {
483499
{"_cpp11test_sum_int_for2_", (DL_FUNC) &_cpp11test_sum_int_for2_, 1},
484500
{"_cpp11test_sum_int_for_", (DL_FUNC) &_cpp11test_sum_int_for_, 1},
485501
{"_cpp11test_sum_int_foreach_", (DL_FUNC) &_cpp11test_sum_int_foreach_, 1},
502+
{"_cpp11test_test_destruction_inner", (DL_FUNC) &_cpp11test_test_destruction_inner, 0},
503+
{"_cpp11test_test_destruction_outer", (DL_FUNC) &_cpp11test_test_destruction_outer, 0},
486504
{"_cpp11test_upper_bound", (DL_FUNC) &_cpp11test_upper_bound, 2},
487505
{"run_testthat_tests", (DL_FUNC) &run_testthat_tests, 1},
488506
{NULL, NULL, 0}

cpp11test/src/test-protect-nested.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#include "cpp11/function.hpp"
2+
#include "cpp11/protect.hpp"
3+
#include "testthat.h"
4+
5+
#ifdef HAS_UNWIND_PROTECT
6+
7+
/*
8+
* See https://github.com/r-lib/cpp11/pull/327 for full details.
9+
*
10+
* - `cpp11::package("cpp11test")["test_destruction_outer"]` uses
11+
* `unwind_protect()` to call R level `test_destruction_outer()` but no entry
12+
* macros are set up. Instead we are going to catch exceptions that get here
13+
* with `expect_error_as()`.
14+
*
15+
* - Call R level `test_destruction_outer()` to set up `BEGIN_CPP11` /
16+
* `END_CPP11` entry macros.
17+
*
18+
* - C++ `test_destruction_outer()` goes through `unwind_protect()` to call
19+
* the R level `test_destruction_inner()`.
20+
*
21+
* - R level `test_destruction_inner()` sets up its own `BEGIN_CPP11` /
22+
* `END_CPP11` entry macros.
23+
*
24+
* - C++ `test_destruction_inner()` goes through `unwind_protect()` to call
25+
* `Rf_error()` (i.e., we are nested within `unwind_protect()`s!).
26+
*
27+
* - `longjmp()` is caught from inner `unwind_protect()`, and an exception
28+
* is thrown which is caught by the inner entry macros, allowing us to run
29+
* the destructor of `x`, then we let R continue the unwind process.
30+
*
31+
* - This `longjmp()`s again and is caught by the outer `unwind_protect()`, an
32+
* exception is thrown which is caught by the outer entry macros, and we let
33+
* R continue the unwind process one more time.
34+
*
35+
* - This `longjmp()` is caught by `cpp11::package()`'s `unwind_protect()`,
36+
* an exception is thrown, and that is caught by `expect_error_as()`.
37+
*/
38+
39+
// Global variable to detect if the destructor has been run or not
40+
static bool destructed = false;
41+
42+
class HasDestructor {
43+
public:
44+
~HasDestructor();
45+
};
46+
47+
HasDestructor::~HasDestructor() {
48+
// Destructor has run!
49+
destructed = true;
50+
}
51+
52+
[[cpp11::register]] void test_destruction_inner() {
53+
// Expect that `x`'s destructor gets to run on the way out
54+
HasDestructor x{};
55+
cpp11::stop("oh no!");
56+
}
57+
58+
[[cpp11::register]] void test_destruction_outer() {
59+
const auto test_destruction_inner =
60+
cpp11::package("cpp11test")["test_destruction_inner"];
61+
test_destruction_inner();
62+
}
63+
64+
context("unwind_protect-nested-C++") {
65+
test_that(
66+
"nested `unwind_protect()` (with entry macros set up) will run destructors"
67+
"(#327)") {
68+
const auto fn = [&] {
69+
const auto test_destruction_outer =
70+
cpp11::package("cpp11test")["test_destruction_outer"];
71+
test_destruction_outer();
72+
};
73+
74+
expect_error_as(fn(), cpp11::unwind_exception);
75+
expect_true(destructed);
76+
77+
destructed = false;
78+
}
79+
}
80+
81+
#endif

0 commit comments

Comments
 (0)