describe("pkg_links_to_rcpp", { it("works with single package in LinkingTo", { pkg <- local_package() expect_false(pkg_links_to_rcpp(pkg_path(pkg))) pkg$set("LinkingTo", "Rcpp") pkg$write() expect_true(pkg_links_to_rcpp(pkg_path(pkg))) }) it("works with multiple packages in LinkingTo", { pkg <- local_package() expect_false(pkg_links_to_rcpp(pkg_path(pkg))) pkg$set("LinkingTo", paste("Rcpp", "cpp4r", sep = ",")) pkg$write() expect_true(pkg_links_to_rcpp(pkg_path(pkg))) }) }) describe("get_call_entries", { it("returns an empty string if there are no R files", { pkg <- local_package() path <- pkg_path(pkg) expect_equal(get_call_entries(path, get_funs(path)$name, get_package_name(path)), "") }) it("returns an empty string if there are no .Call calls", { pkg <- local_package() path <- pkg_path(pkg) dir.create(file.path(path, "R")) writeLines("foo <- function() 1", file.path(path, "R", "foo.R")) expect_equal(get_call_entries(path, get_funs(path)$name, get_package_name(path)), "") }) it("Errors for invalid packages", { # local_package adds a NAMESPACE file pkg <- tempfile() dir.create(pkg) on.exit(unlink(pkg, recursive = TRUE)) writeLines("Package: testPkg", file.path(pkg, "DESCRIPTION")) dir.create(file.path(pkg, "R")) writeLines('foo <- function() .Call("bar")', file.path(pkg, "R", "foo.R")) expect_error(get_call_entries(pkg, get_funs(path)$name, get_package_name(pkg)), "has no 'NAMESPACE' file") }) it("returns an empty string for packages with .Call entries and NAMESPACE files", { # tools::package_native_routine_registration_skeleton is not available before R 3.4 # R added `(void)` to the signature after R 4.3.0 skip_if(getRversion() < "4.3.0") pkg <- local_package() path <- pkg_path(pkg) dir.create(file.path(path, "R")) writeLines('foo <- function() .Call("bar")', file.path(path, "R", "foo.R")) call_entries <- get_call_entries(path, get_funs(path)$name, get_package_name(path)) expect_snapshot(call_entries) }) it("works with multiple register functions.", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("multiple.cpp"), file.path(p, "src", "multiple.cpp")) register(p) cpp_bindings <- file.path(p, "src", "cpp4r.cpp") expect_snapshot(cat(read_file(cpp_bindings))) }) }) describe("wrap_call", { it("works with void functions and no arguments", { expect_equal( wrap_call("foo", "void", tibble::tibble(type = character(), name = character())), " foo();\n return R_NilValue;" ) }) it("works with non-void functions and no arguments", { expect_equal( wrap_call("foo", "bool", tibble::tibble(type = character(), name = character())), " return cpp4r::as_sexp(foo());" ) }) it("works with void functions and some arguments", { expect_equal( wrap_call("foo", "void", tibble::tibble(type = c("double", "int"), name = c("x", "y"))), " foo(cpp4r::as_cpp>(x), cpp4r::as_cpp>(y));\n return R_NilValue;" ) }) it("works with non-void functions and some arguments", { expect_equal( wrap_call("foo", "bool", tibble::tibble(type = c("double", "int"), name = c("x", "y"))), " return cpp4r::as_sexp(foo(cpp4r::as_cpp>(x), cpp4r::as_cpp>(y)));" ) }) }) describe("get_registered_functions", { it("returns an empty tibble given a non-existent file", { f <- tempfile() decorations <- decor::cpp_decorations(files = f, is_attribute = TRUE) res <- get_registered_functions(decorations, "cpp4r::register") expect_equal(names(res), c("file", "line", "decoration", "params", "context", "name", "return_type", "args")) expect_equal(NROW(res), 0) }) it("returns an empty tibble given a empty file", { f <- tempfile() file.create(f) decorations <- decor::cpp_decorations(files = f, is_attribute = TRUE) res <- get_registered_functions(decorations, "cpp4r::register") expect_equal(names(res), c("file", "line", "decoration", "params", "context", "name", "return_type", "args")) expect_equal(NROW(res), 0) }) it("works with a single registration", { decorations <- decor::cpp_decorations(files = test_path("single.cpp"), is_attribute = TRUE) res <- get_registered_functions(decorations, "cpp4r::register") expect_equal(names(res), c("file", "line", "decoration", "params", "context", "name", "return_type", "args")) expect_equal(NROW(res), 1L) expect_equal(res$name, "foo") expect_equal(res$return_type, "int") expect_equal(names(res$args[[1]]), c("type", "name", "default")) expect_equal(NROW(res$args[[1]]), 0) }) it("works with multiple registrations", { decorations <- decor::cpp_decorations(files = test_path("multiple.cpp"), is_attribute = TRUE) res <- get_registered_functions(decorations, "cpp4r::register") expect_equal(names(res), c("file", "line", "decoration", "params", "context", "name", "return_type", "args")) expect_equal(NROW(res), 3L) expect_equal(res$name, c("foo", "bar", "baz")) expect_equal(res$return_type, c("int", "double", "bool")) expect_equal(names(res$args[[1]]), c("type", "name", "default")) expect_equal(NROW(res$args[[1]]), 0) expect_equal(names(res$args[[2]]), c("type", "name", "default")) expect_equal(NROW(res$args[[2]]), 1) expect_equal(res$args[[2]]$type, "bool") expect_equal(res$args[[2]]$name, "run") expect_equal(res$args[[2]]$default, NA_character_) expect_equal(names(res$args[[3]]), c("type", "name", "default")) expect_equal(NROW(res$args[[3]]), 2) expect_equal(res$args[[3]]$type, c("bool", "int")) expect_equal(res$args[[3]]$name, c("run", "value")) expect_equal(res$args[[3]]$default, c(NA_character_, "0")) }) }) describe("generate_cpp_functions", { it("returns the empty string if there are no functions", { skip_if_not_installed("glue", "1.6.2.9000") funs <- tibble::tibble( file = character(), line = integer(), decoration = character(), params = list(), context = list(), name = character(), return_type = character(), args = list(tibble::tibble(type = character(), name = character())) ) expect_equal(generate_cpp_functions(funs), "") }) it("returns the wrapped function for a single void function with no arguments", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "void", args = list(tibble::tibble(type = character(), name = character())) ) expect_equal( generate_cpp_functions(funs), "// foo.cpp void foo(); extern \"C\" SEXP _cpp4r_foo() { BEGIN_cpp4r foo(); return R_NilValue; END_cpp4r }" ) }) it("returns the wrapped function for a single void function with no arguments and different package name", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "void", args = list(tibble::tibble(type = character(), name = character())) ) expect_equal( generate_cpp_functions(funs, package = "mypkg"), "// foo.cpp void foo(); extern \"C\" SEXP _mypkg_foo() { BEGIN_cpp4r foo(); return R_NilValue; END_cpp4r }" ) }) it("returns the wrapped function for a single function with no arguments", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "int", args = list(tibble::tibble(type = character(), name = character())) ) expect_equal( generate_cpp_functions(funs), "// foo.cpp int foo(); extern \"C\" SEXP _cpp4r_foo() { BEGIN_cpp4r return cpp4r::as_sexp(foo()); END_cpp4r }" ) }) it("returns the wrapped function for a single void function with arguments", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "void", args = list(tibble::tibble(type = "int", name = "bar")) ) expect_equal( generate_cpp_functions(funs), "// foo.cpp void foo(int bar); extern \"C\" SEXP _cpp4r_foo(SEXP bar) { BEGIN_cpp4r foo(cpp4r::as_cpp>(bar)); return R_NilValue; END_cpp4r }" ) }) it("returns the wrapped function for a single function with arguments", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "int", args = list(tibble::tibble(type = "int", name = "bar")) ) expect_equal( generate_cpp_functions(funs), "// foo.cpp int foo(int bar); extern \"C\" SEXP _cpp4r_foo(SEXP bar) { BEGIN_cpp4r return cpp4r::as_sexp(foo(cpp4r::as_cpp>(bar))); END_cpp4r }" ) }) it("returns the wrapped functions for multiple functions with arguments", { funs <- tibble::tibble( file = c("foo.cpp", "bar.cpp"), line = c(1L, 3L), decoration = c("cpp4r", "cpp4r"), params = list(NA, NA), context = list(NA_character_, NA_character_), name = c("foo", "bar"), return_type = c("int", "bool"), args = list( tibble::tibble(type = "int", name = "bar"), tibble::tibble(type = "double", name = "baz") ) ) expect_equal( generate_cpp_functions(funs), "// foo.cpp int foo(int bar); extern \"C\" SEXP _cpp4r_foo(SEXP bar) { BEGIN_cpp4r return cpp4r::as_sexp(foo(cpp4r::as_cpp>(bar))); END_cpp4r } // bar.cpp bool bar(double baz); extern \"C\" SEXP _cpp4r_bar(SEXP baz) { BEGIN_cpp4r return cpp4r::as_sexp(bar(cpp4r::as_cpp>(baz))); END_cpp4r }" ) }) }) describe("generate_r_functions", { it("returns the empty string if there are no functions", { skip_if_not_installed("glue", "1.6.2.9000") funs <- tibble::tibble( file = character(), line = integer(), decoration = character(), params = list(), context = list(), name = character(), return_type = character(), args = list() ) expect_equal(generate_r_functions(funs), "") }) it("returns the wrapped function for a single void function with no arguments", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "void", args = list(tibble::tibble(type = character(), name = character())) ) expect_equal( generate_r_functions(funs, package = "cpp4r"), "foo <- function() { invisible(.Call(`_cpp4r_foo`)) }" ) }) it("returns the wrapped function for a single void function with no arguments and use_package = TRUE", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "void", args = list(tibble::tibble(type = character(), name = character())) ) expect_equal( generate_r_functions(funs, package = "cpp4r", use_package = TRUE), "foo <- function() { invisible(.Call(\"_cpp4r_foo\", PACKAGE = \"cpp4r\")) }" ) }) it("returns the wrapped function for a single void function with no arguments and different package name", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "void", args = list(tibble::tibble(type = character(), name = character())) ) expect_equal( generate_r_functions(funs, package = "mypkg"), "foo <- function() { invisible(.Call(`_mypkg_foo`)) }" ) }) it("returns the wrapped function for a single function with no arguments", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "int", args = list(tibble::tibble(type = character(), name = character())) ) expect_equal( generate_r_functions(funs, package = "cpp4r"), "foo <- function() { .Call(`_cpp4r_foo`) }" ) }) it("returns the wrapped function for a single function with no arguments and use_package = TRUE", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "int", args = list(tibble::tibble(type = character(), name = character())) ) expect_equal( generate_r_functions(funs, package = "cpp4r", use_package = TRUE), "foo <- function() { .Call(\"_cpp4r_foo\", PACKAGE = \"cpp4r\") }" ) }) it("returns the wrapped function for a single void function with arguments", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "void", args = list(tibble::tibble(type = "int", name = "bar")) ) expect_equal( generate_r_functions(funs, package = "cpp4r"), "foo <- function(bar) { invisible(.Call(`_cpp4r_foo`, bar)) }" ) }) it("returns the wrapped function for a single function with arguments", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "int", args = list(tibble::tibble(type = "int", name = "bar")) ) expect_equal( generate_r_functions(funs, package = "cpp4r"), "foo <- function(bar) { .Call(`_cpp4r_foo`, bar) }" ) }) it("returns the wrapped functions for multiple functions with arguments", { funs <- tibble::tibble( file = c("foo.cpp", "bar.cpp"), line = c(1L, 3L), decoration = c("cpp4r", "cpp4r"), params = list(NA, NA), context = list(NA_character_, NA_character_), name = c("foo", "bar"), return_type = c("int", "bool"), args = list( tibble::tibble(type = "int", name = "bar"), tibble::tibble(type = "double", name = "baz") ) ) expect_equal( generate_r_functions(funs, package = "cpp4r"), "foo <- function(bar) { .Call(`_cpp4r_foo`, bar) } bar <- function(baz) { .Call(`_cpp4r_bar`, baz) }" ) }) }) describe("register", { it("returns an invisible empty character if there are no decorations", { f <- tempfile() expect_equal(register(f), character()) dir.create(f) expect_equal(register(f), character()) }) it("works with a package that registers a single c++ function", { # tools::package_native_routine_registration_skeleton is not available before R 3.4 skip_if(getRversion() < "3.4") pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cpp")) register(p) r_bindings <- file.path(p, "R", "cpp4r.R") expect_true(file.exists(r_bindings)) expect_snapshot(cat(read_file(r_bindings))) cpp_bindings <- file.path(p, "src", "cpp4r.cpp") expect_true(file.exists(cpp_bindings)) expect_snapshot(cat(read_file(cpp_bindings))) }) it("can be run without messages", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cpp")) expect_silent(register(p, quiet = TRUE)) }) it("can be run with messages", { local_reproducible_output() pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cpp")) expect_snapshot( register(p, quiet = FALSE) ) }) it("includes pkg_types.h if included in src", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cpp")) writeLines("#include ", file.path(p, "src", "testPkg_types.h")) register(p) expect_true( any( grepl( pattern = '#include "testPkg_types.h"', x = readLines(file.path(p, "src", "cpp4r.cpp")), fixed = TRUE ) ) ) }) it("includes pkg_types.hpp if included in src", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cpp")) writeLines("#include ", file.path(p, "src", "testPkg_types.hpp")) register(p) expect_true( any( grepl( pattern = '#include "testPkg_types.hpp"', x = readLines(file.path(p, "src", "cpp4r.cpp")), fixed = TRUE ) ) ) }) it("includes pkg_types.h if included in inst/include", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cpp")) dir.create(file.path(p, "inst", "include"), recursive = TRUE) writeLines("#include ", file.path(p, "inst", "include", "testPkg_types.h")) register(p) expect_true( any( grepl( pattern = '#include "testPkg_types.h"', x = readLines(file.path(p, "src", "cpp4r.cpp")), fixed = TRUE ) ) ) }) it("includes pkg_types.hpp if included in inst/include", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cpp")) dir.create(file.path(p, "inst", "include"), recursive = TRUE) writeLines("#include ", file.path(p, "inst", "include", "testPkg_types.hpp")) register(p) expect_true( any( grepl( pattern = '#include "testPkg_types.hpp"', x = readLines(file.path(p, "src", "cpp4r.cpp")), fixed = TRUE ) ) ) }) it("does not error if no files have registered functions", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) writeLines("int foo(int x) { return x; }", file.path(p, "src", "foo.cpp")) expect_error_free(register(p)) }) it("accepts .cc as an alternative value for extension=", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cc")) register(p, extension = ".cc") expect_match(list.files(file.path(p, "src")), "\\.cc$") }) }) describe("generate_init_functions", { it("returns an empty list if there no functions", { funs <- tibble::tibble( file = character(), line = integer(), decoration = character(), params = list(), context = list(), name = character(), return_type = character(), args = list(tibble::tibble(type = character(), name = character())) ) expect_equal(generate_init_functions(funs), list(declarations = "", calls = "")) }) it("returns the declaration and call for a single init function", { funs <- tibble::tibble( file = "foo.cpp", line = 1L, decoration = "cpp4r", params = list(NA), context = list(NA_character_), name = "foo", return_type = "void", args = list(tibble::tibble(type = "DllInfo*", name = "dll")) ) expect_equal(generate_init_functions(funs), list(declarations = "\nvoid foo(DllInfo* dll);\n", calls = "\n foo(dll);")) }) it("returns the declaration and call for a multiple init functions", { funs <- tibble::tibble( file = c("foo.cpp", "bar.cpp"), line = c(1L, 3L), decoration = c("cpp4r", "cpp4r"), params = list(NA, NA), context = list(NA_character_, NA_character_), name = c("foo", "bar"), return_type = c("void", "void"), args = list(tibble::tibble(type = "DllInfo*", name = "dll"), tibble::tibble(type = "DllInfo*", name = "dll")) ) expect_equal(generate_init_functions(funs), list(declarations = "\nvoid foo(DllInfo* dll);\nvoid bar(DllInfo* dll);\n", calls = "\n foo(dll);\n bar(dll);")) }) }) test_that("check_valid_attributes does not return an error if all registers are correct", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single.cpp"), file.path(p, "src", "single.cpp")) expect_error_free(register(p)) pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("multiple.cpp"), file.path(p, "src", "multiple.cpp")) expect_error_free(register(p)) pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("linking_to_registers.cpp"), file.path(p, "src", "linking_to_registers.cpp")) expect_error_free(register(p)) }) test_that("check_valid_attributes returns an error if one or more registers is incorrect", { pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("single_incorrect.cpp"), file.path(p, "src", "single_incorrect.cpp")) expect_error(register(p)) pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("multiple_incorrect.cpp"), file.path(p, "src", "multiple_incorrect.cpp")) expect_error(register(p)) pkg <- local_package() p <- pkg_path(pkg) dir.create(file.path(p, "src")) file.copy(test_path("linking_to_incorrect_registers.cpp"), file.path(p, "src", "linking_to_incorrect_registers.cpp")) expect_error(register(p)) })