Skip to content

Commit b6a790a

Browse files
committed
Add unit tests for jumping across cpp interfaces
1 parent 1ee996c commit b6a790a

File tree

13 files changed

+286
-0
lines changed

13 files changed

+286
-0
lines changed

ChangeLog

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11

2+
2018-06-01 Lionel Henry <[email protected]>
3+
4+
* inst/unitTests/runit.interface.R: New tests for interfaces and unwind.
5+
These tests build two packages, and that exports a function via
6+
Rcpp::interfaces(cpp) and the other that calls it. The attributes are
7+
regenerated and the packages rebuilt each time the tests are run. The
8+
tests check in particular that the C++ stack is properly unwound when a
9+
long jump occurs.
10+
211
2018-05-31 Lionel Henry <[email protected]>
312

413
* inst/include/Rcpp/api/meat/Rcpp_eval.h: Fix protected evaluation.

inst/unitTests/runit.interface.R

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env r
2+
# -*- mode: R; tab-width: 4; -*-
3+
#
4+
# Copyright (C) 2018 RStudio
5+
#
6+
# This file is part of Rcpp.
7+
#
8+
# Rcpp is free software: you can redistribute it and/or modify it
9+
# under the terms of the GNU General Public License as published by
10+
# the Free Software Foundation, either version 2 of the License, or
11+
# (at your option) any later version.
12+
#
13+
# Rcpp is distributed in the hope that it will be useful, but
14+
# WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
#
18+
# You should have received a copy of the GNU General Public License
19+
# along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
20+
.runThisTest <- Sys.getenv("RunAllRcppTests") == "yes"
21+
22+
if (.runThisTest) {
23+
24+
test.interface.unwind <- function() {
25+
exporter_name <- "testRcppInterfaceExporter"
26+
user_name <- "testRcppInterfaceUser"
27+
28+
tempdir <- tempfile()
29+
dir.create(tempdir)
30+
old_wd <- setwd(tempdir)
31+
on.exit({
32+
setwd(old_wd)
33+
unlink(tempdir, recursive = TRUE)
34+
})
35+
36+
file.copy(system.file("unitTests", exporter_name, package = "Rcpp"),
37+
tempdir,
38+
recursive = TRUE)
39+
file.copy(system.file("unitTests", user_name, package = "Rcpp"),
40+
tempdir,
41+
recursive = TRUE)
42+
43+
exporter_path <- file.path(tempdir, exporter_name)
44+
user_path <- file.path(tempdir, user_name)
45+
46+
Rcpp::compileAttributes(exporter_path)
47+
Rcpp::compileAttributes(user_path)
48+
49+
lib_path <- file.path(tempdir, "templib")
50+
dir.create(lib_path)
51+
52+
install <- function(path, lib_path) {
53+
install.packages(
54+
path,
55+
lib_path,
56+
repos = NULL,
57+
type = "source",
58+
INSTALL_opts = "--install-tests"
59+
)
60+
}
61+
install(exporter_path, lib_path)
62+
install(user_path, lib_path)
63+
64+
old_lib_paths <- .libPaths()
65+
on.exit(.libPaths(old_lib_paths))
66+
.libPaths(lib_path)
67+
68+
# Without this testInstalledPackage() won't find installed
69+
# packages even though we've passed `lib.loc`
70+
old_libs_envvar <- Sys.getenv("R_LIBS")
71+
on.exit(Sys.setenv(R_LIBS = old_libs_envvar), add = TRUE)
72+
73+
sys_sep <- if (.Platform$OS.type == "windows") ";" else ":"
74+
Sys.setenv(R_LIBS = paste(c(lib_path, old_lib_paths), collapse = sys_sep))
75+
76+
result <- tools::testInstalledPackage(user_name, lib.loc = lib_path, types = "test")
77+
78+
# Be verbose if tests were not successful
79+
if (result) {
80+
log <- file.path(paste0(user_name, "-tests"), "tests.Rout.fail")
81+
cat(">> tests.Rout.fail", readLines(log), sep = "\n", file = stderr())
82+
}
83+
84+
checkEquals(result, 0L)
85+
}
86+
87+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Package: testRcppInterfaceExporter
2+
Title: Exports c++ function via the cpp interface
3+
Version: 0.1.0
4+
Authors@R: 'Lionel Henry <[email protected]> [aut, cre]'
5+
Description: The API package.
6+
Depends:
7+
R (>= 3.1.0)
8+
Imports:
9+
Rcpp
10+
LinkingTo:
11+
Rcpp
12+
License: GPL-3
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Generated by roxygen2: do not edit by hand
2+
3+
export(peek_flag)
4+
export(reset_flags)
5+
export(test_cpp_interface)
6+
importFrom(Rcpp,sourceCpp)
7+
useDynLib(testRcppInterfaceExporter, .registration = TRUE)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#' @useDynLib testRcppInterfaceExporter, .registration = TRUE
2+
#' @importFrom Rcpp sourceCpp
3+
NULL
4+
5+
flags <- new.env(parent = emptyenv())
6+
7+
#' @export
8+
reset_flags <- function() {
9+
flags$cpp_interface_upstream <- FALSE
10+
}
11+
.onLoad <- function(lib, pkg) {
12+
reset_flags()
13+
}
14+
15+
#' @export
16+
peek_flag <- function(name) {
17+
flags[[name]]
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#define RCPP_PROTECTED_EVAL
2+
3+
#include <Rcpp.h>
4+
#include "unwound.h"
5+
6+
// [[Rcpp::interfaces(r, cpp)]]
7+
8+
//' @export
9+
// [[Rcpp::export]]
10+
SEXP test_cpp_interface(SEXP x) {
11+
unwound_t stack_obj("cpp_interface_upstream");
12+
return Rcpp::Rcpp_fast_eval(x, R_GlobalEnv);
13+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef UNWOUND_H
2+
#define UNWOUND_H
3+
4+
5+
#include <Rcpp.h>
6+
7+
#define PKG_NAME "testRcppInterfaceExporter"
8+
9+
struct unwound_t {
10+
unwound_t(std::string flag_) {
11+
flag = flag_;
12+
Rcpp::Rcout << "Initialising " << flag << std::endl;
13+
Rcpp::Environment ns = Rcpp::Environment::namespace_env(PKG_NAME);
14+
flags_env = ns["flags"];
15+
flags_env[flag] = false;
16+
}
17+
~unwound_t() {
18+
Rcpp::Rcout << "Unwinding " << flag << std::endl;
19+
flags_env[flag] = true;
20+
}
21+
22+
std::string flag;
23+
Rcpp::Environment flags_env;
24+
};
25+
26+
27+
#endif
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Package: testRcppInterfaceUser
2+
Title: Calls exported c++ function of testRcppInterfaceExporter
3+
Version: 0.1.0
4+
Authors@R: 'Lionel Henry <[email protected]> [aut, cre]'
5+
Description: The client package.
6+
Depends:
7+
R (>= 3.1.0)
8+
Imports:
9+
Rcpp
10+
LinkingTo:
11+
testRcppInterfaceExporter,
12+
Rcpp
13+
License: GPL-3
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Generated by roxygen2: do not edit by hand
2+
3+
export(peek_flag)
4+
export(reset_flags)
5+
export(use_cpp_interface)
6+
importFrom(Rcpp,sourceCpp)
7+
useDynLib(testRcppInterfaceUser, .registration = TRUE)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#' @useDynLib testRcppInterfaceUser, .registration = TRUE
2+
#' @importFrom Rcpp sourceCpp
3+
NULL
4+
5+
flags <- new.env(parent = emptyenv())
6+
7+
#' @export
8+
reset_flags <- function() {
9+
flags$cpp_interface_downstream <- FALSE
10+
}
11+
.onLoad <- function(lib, pkg) {
12+
reset_flags()
13+
}
14+
15+
#' @export
16+
peek_flag <- function(name) {
17+
flags[[name]]
18+
}

0 commit comments

Comments
 (0)