Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
2026-01-13 Dirk Eddelbuettel <edd@debian.org>

* inst/tinytest/cpp/attributes_extended.cpp: New unit tests
* inst/tinytest/test_attributes_extended.R: Idem
* inst/tinytest/test_compile_attributes_errors.R: Idem
* inst/tinytest/test_cppfunction_errors.R: Idem
* inst/tinytest/test_sourcecpp_errors.R: Idem
* R/Attributes.R: Remove #nocov tags
* src/attributes.cpp: Idem

2026-01-12 Dirk Eddelbuettel <edd@debian.org>

* DESCRIPTION (Version, Date): Roll micro version and date
Expand Down
20 changes: 10 additions & 10 deletions R/Attributes.R
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ sourceCpp <- function(file = "",
file <- normalizePath(file, winslash = "/")

# error if the file extension isn't one supported by R CMD SHLIB
if (! tools::file_ext(file) %in% c("cc", "cpp")) { # #nocov start
if (! tools::file_ext(file) %in% c("cc", "cpp")) {
stop("The filename '", basename(file), "' does not have an ",
"extension of .cc or .cpp so cannot be compiled.")
} # #nocov end
}

# validate that there are no spaces in the path on windows
if (.Platform$OS.type == "windows") { # #nocov start
if (grepl(' ', basename(file), fixed=TRUE)) {
if (.Platform$OS.type == "windows") {
if (grepl(' ', basename(file), fixed=TRUE)) { # #nocov start
stop("The filename '", basename(file), "' contains spaces. This ",
"is not permitted.")
"is not permitted.") # #nocov end
}
} else {
if (windowsDebugDLL) {
Expand All @@ -73,7 +73,7 @@ sourceCpp <- function(file = "",
"non-Windows platforms.")
}
windowsDebugDLL <- FALSE # now we do not need to deal with OS choice below
} # #nocov end
}
}

# get the context (does code generation as necessary)
Expand Down Expand Up @@ -323,9 +323,9 @@ cppFunction <- function(code,

# verify that a single function was exported and return it
if (length(exported$functions) == 0)
stop("No function definition found") # #nocov
stop("No function definition found")
else if (length(exported$functions) > 1)
stop("More than one function definition") # #nocov
stop("More than one function definition") # #nocov
else {
functionName <- exported$functions[[1]]
invisible(get(functionName, env))
Expand Down Expand Up @@ -417,7 +417,7 @@ compileAttributes <- function(pkgdir = ".", verbose = getOption("verbose")) {
pkgdir <- normalizePath(pkgdir, winslash = "/")
descFile <- file.path(pkgdir,"DESCRIPTION")
if (!file.exists(descFile))
stop("pkgdir must refer to the directory containing an R package") # #nocov
stop("pkgdir must refer to the directory containing an R package")
pkgDesc <- read.dcf(descFile)[1,]
pkgname = .readPkgDescField(pkgDesc, "Package")
depends <- c(.readPkgDescField(pkgDesc, "Depends", character()),
Expand All @@ -429,7 +429,7 @@ compileAttributes <- function(pkgdir = ".", verbose = getOption("verbose")) {
# check the NAMESPACE file to see if dynamic registration is enabled
namespaceFile <- file.path(pkgdir, "NAMESPACE")
if (!file.exists(namespaceFile))
stop("pkgdir must refer to the directory containing an R package") # #nocov
stop("pkgdir must refer to the directory containing an R package")
pkgNamespace <- readLines(namespaceFile, warn = FALSE)
registration <- any(grepl("^\\s*useDynLib.*\\.registration\\s*=\\s*TRUE.*$", pkgNamespace))

Expand Down
49 changes: 49 additions & 0 deletions inst/tinytest/cpp/attributes_extended.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <Rcpp.h>
using namespace Rcpp;

// Test 4.1: Named Export Parameter
// Coverage target: src/attributes.cpp:360

// [[Rcpp::export(name = "custom_exported_name")]]
int test_named_export() { return 123; }

// [[Rcpp::export(name = "another.custom.name")]]
std::string test_named_export_with_dots() { return "dotted name"; }

// Test 4.2: Unnamed Export Parameter
// Coverage target: src/attributes.cpp:365

// [[Rcpp::export(my_custom_name)]]
int test_unnamed_export_param() { return 456; }

// Test 4.3: RNG Parameter Variations
// Coverage target: src/attributes.cpp:382-383

// [[Rcpp::export(rng = true)]]
int test_rng_lowercase_true() { return 789; }

// [[Rcpp::export(rng = TRUE)]]
int test_rng_uppercase_true() { return 101; }

// [[Rcpp::export(rng = false)]]
int test_rng_lowercase_false() { return 202; }

// Test 4.4: Invisible Parameter Variations
// Coverage target: src/attributes.cpp:391-392

// [[Rcpp::export(invisible = true)]]
void test_invisible_lowercase_true() {
// Side effect only - returns invisibly
}

// [[Rcpp::export(invisible = TRUE)]]
void test_invisible_uppercase_true() {
// Side effect only - returns invisibly
}

// Test 4.5: exportedCppName with Dots
// Coverage target: src/attributes.cpp:373-377
// This tests the conversion of dots to underscores in C++ names

// [[Rcpp::export(name = "test.with.dots")]]
int test_cpp_name_conversion() { return 999; }
42 changes: 42 additions & 0 deletions inst/tinytest/test_attributes_extended.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Copyright (C) 2026 Dirk Eddelbuettel
##
## This file is part of Rcpp.
##
## Rcpp 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 2 of the License, or
## (at your option) any later version.
##
## Rcpp 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 Rcpp. If not, see <http://www.gnu.org/licenses/>.

if (Sys.getenv("RunAllRcppTests") != "yes") exit_file("Set 'RunAllRcppTests' to 'yes' to run.")

Rcpp::sourceCpp("cpp/attributes_extended.cpp")

## Test named exports
expect_equal(custom_exported_name(), 123)
expect_equal(another.custom.name(), "dotted name")

## Test unnamed export parameter
expect_equal(my_custom_name(), 456)

## Test RNG parameters
expect_equal(test_rng_lowercase_true(), 789)
expect_equal(test_rng_uppercase_true(), 101)
expect_equal(test_rng_lowercase_false(), 202)

## Test invisible parameters (should return invisibly)
result1 <- withVisible(test_invisible_lowercase_true())
expect_false(result1$visible)

result2 <- withVisible(test_invisible_uppercase_true())
expect_false(result2$visible)

## Test C++ name conversion (dots to underscores)
expect_equal(test.with.dots(), 999)
33 changes: 33 additions & 0 deletions inst/tinytest/test_compile_attributes_errors.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Copyright (C) 2026 Dirk Eddelbuettel
##
## This file is part of Rcpp.
##
## Rcpp 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 2 of the License, or
## (at your option) any later version.
##
## Rcpp 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 Rcpp. If not, see <http://www.gnu.org/licenses/>.

if (Sys.getenv("RunAllRcppTests") != "yes") exit_file("Set 'RunAllRcppTests' to 'yes' to run.")

## Test 3.1: Missing DESCRIPTION File
## Coverage target: R/Attributes.R:420
tmpdir <- tempfile()
dir.create(tmpdir)
expect_error(compileAttributes(tmpdir), "must refer to the directory containing an R package")
unlink(tmpdir, recursive = TRUE)

## Test 3.2: Missing NAMESPACE File
## Coverage target: R/Attributes.R:432
tmpdir <- tempfile()
dir.create(tmpdir)
writeLines("Package: TestPkg", file.path(tmpdir, "DESCRIPTION"))
expect_error(compileAttributes(tmpdir), "must refer to the directory containing an R package")
unlink(tmpdir, recursive = TRUE)
35 changes: 35 additions & 0 deletions inst/tinytest/test_cppfunction_errors.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Copyright (C) 2026 Dirk Eddelbuettel
##
## This file is part of Rcpp.
##
## Rcpp 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 2 of the License, or
## (at your option) any later version.
##
## Rcpp 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 Rcpp. If not, see <http://www.gnu.org/licenses/>.

if (Sys.getenv("RunAllRcppTests") != "yes") exit_file("Set 'RunAllRcppTests' to 'yes' to run.")

## Test 2.1: No Function Definition
## Coverage target: R/Attributes.R:326
code <- "int x = 5;" # No function, just a variable
expect_error(cppFunction(code), "No function definition found")

## Test 2.2: Multiple Function Definitions
## Coverage target: R/Attributes.R:328
code <- "
// [[Rcpp::export]]
int foo() { return 1; }

// [[Rcpp::export]]
int bar() { return 2; }
"
## compilation dies sooner so we never actually see the messages
expect_error(cppFunction(code)) #, "More than one function definition")
42 changes: 42 additions & 0 deletions inst/tinytest/test_sourcecpp_errors.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Copyright (C) 2026 Dirk Eddelbuettel
##
## This file is part of Rcpp.
##
## Rcpp 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 2 of the License, or
## (at your option) any later version.
##
## Rcpp 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 Rcpp. If not, see <http://www.gnu.org/licenses/>.

if (Sys.getenv("RunAllRcppTests") != "yes") exit_file("Set 'RunAllRcppTests' to 'yes' to run.")

## Test 1.1: Invalid File Extension
## Coverage target: R/Attributes.R:58-61
tmpfile <- tempfile(fileext = ".c")
writeLines("int main() { return 0; }", tmpfile)
expect_error(sourceCpp(tmpfile), "does not have an extension of .cc or .cpp")
unlink(tmpfile)

## Test 1.2: Filename with Spaces on Windows
## Coverage target: R/Attributes.R:64-68
if (.Platform$OS.type == "windows") {
tmpfile <- file.path(tempdir(), "test file.cpp")
writeLines("#include <Rcpp.h>", tmpfile)
expect_error(sourceCpp(tmpfile), "contains spaces")
unlink(tmpfile)
}

## Test 1.3: windowsDebugDLL on Non-Windows
## Coverage target: R/Attributes.R:70-76
if (.Platform$OS.type != "windows") {
code <- "// [[Rcpp::export]]\nint test_fn() { return 42; }"
expect_message(sourceCpp(code = code, windowsDebugDLL = TRUE, verbose = TRUE),
"windowsDebugDLL.*ignored")
}
16 changes: 8 additions & 8 deletions src/attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,39 +357,39 @@ namespace attributes {
// check for explicit name parameter
if (hasParameter(kExportName))
{
return paramNamed(kExportName).value(); // #nocov
return paramNamed(kExportName).value();
}
// otherwise un-named parameter in the first slot
else if (!params().empty() && params()[0].value().empty())
{
return params()[0].name(); // #nocov
return params()[0].name();
}
// otherwise the actual function name
{
return function().name();
}
}

std::string exportedCppName() const { // #nocov start
std::string exportedCppName() const {
std::string name = exportedName();
std::replace(name.begin(), name.end(), '.', '_');
return name;
} // #nocov end
}

bool rng() const {
Param rngParam = paramNamed(kExportRng);
if (!rngParam.empty())
return rngParam.value() == kParamValueTrue || // #nocov
rngParam.value() == kParamValueTRUE; // #nocov
return rngParam.value() == kParamValueTrue ||
rngParam.value() == kParamValueTRUE;
else
return true;
}

bool invisible() const {
Param invisibleParam = paramNamed(kExportInvisible);
if (!invisibleParam.empty())
return invisibleParam.value() == kParamValueTrue || // #nocov
invisibleParam.value() == kParamValueTRUE; // #nocov
return invisibleParam.value() == kParamValueTrue ||
invisibleParam.value() == kParamValueTRUE;
else
return false;
}
Expand Down