|
| 1 | +# Unit tests for the install-time helpers defined in src/install.libs.R. |
| 2 | +# |
| 3 | +# These helpers run during 'R CMD INSTALL' (before the package is loadable), |
| 4 | +# so they live in src/install.libs.R rather than in R/ and cannot be reached |
| 5 | +# via library(RcppParallel). Instead, we locate that source file, evaluate the |
| 6 | +# helper definitions that precede its '# Main' section (evaluating the whole |
| 7 | +# file would trigger an actual install), and exercise them directly. |
| 8 | + |
| 9 | +# locate src/install.libs.R by walking up from the working directory; this is |
| 10 | +# reachable when tests are run from the package sources. If it cannot be found |
| 11 | +# (e.g. only the installed package is available), skip rather than fail. |
| 12 | +findInstallLibs <- function() { |
| 13 | + dir <- normalizePath(getwd(), mustWork = FALSE) |
| 14 | + for (i in 1:8) { |
| 15 | + candidate <- file.path(dir, "src", "install.libs.R") |
| 16 | + if (file.exists(candidate)) |
| 17 | + return(candidate) |
| 18 | + parent <- dirname(dir) |
| 19 | + if (identical(parent, dir)) |
| 20 | + break |
| 21 | + dir <- parent |
| 22 | + } |
| 23 | + NA_character_ |
| 24 | +} |
| 25 | + |
| 26 | +path <- findInstallLibs() |
| 27 | +if (is.na(path)) { |
| 28 | + writeLines("skipping: could not locate src/install.libs.R") |
| 29 | + quit(save = "no", status = 0L) |
| 30 | +} |
| 31 | + |
| 32 | +# evaluate only the helper definitions, stopping before the '# Main' section |
| 33 | +lines <- readLines(path) |
| 34 | +marker <- grep("^# Main", lines) |
| 35 | +if (length(marker)) |
| 36 | + lines <- lines[seq_len(marker[[1L]] - 1L)] |
| 37 | + |
| 38 | +env <- new.env(parent = globalenv()) |
| 39 | +eval(parse(text = paste(lines, collapse = "\n")), envir = env) |
| 40 | +splitCompilerVar <- get("splitCompilerVar", envir = env) |
| 41 | + |
| 42 | +# minimal assertion harness |
| 43 | +failures <- 0L |
| 44 | +check <- function(cond, label) { |
| 45 | + ok <- isTRUE(cond) |
| 46 | + if (!ok) |
| 47 | + failures <<- failures + 1L |
| 48 | + writeLines(sprintf("%s - %s", if (ok) "PASS" else "FAIL", label)) |
| 49 | +} |
| 50 | + |
| 51 | +# run 'expr' with the given environment variables set, restoring the previous |
| 52 | +# environment afterwards so tests can't leak into one another (or into CC/CXX) |
| 53 | +withEnv <- function(vars, expr) { |
| 54 | + keys <- names(vars) |
| 55 | + previous <- Sys.getenv(keys, unset = NA, names = TRUE) |
| 56 | + on.exit({ |
| 57 | + set <- previous[!is.na(previous)] |
| 58 | + if (length(set)) |
| 59 | + do.call(Sys.setenv, as.list(set)) |
| 60 | + unset <- keys[is.na(previous)] |
| 61 | + if (length(unset)) |
| 62 | + Sys.unsetenv(unset) |
| 63 | + }, add = TRUE) |
| 64 | + do.call(Sys.setenv, as.list(vars)) |
| 65 | + force(expr) |
| 66 | +} |
| 67 | + |
| 68 | +# the regression this branch fixes: '$(CCACHE) g++' with an empty CCACHE |
| 69 | +# expands to ' g++' (leading space). scan() yields a single token, and the |
| 70 | +# old early-return left the leading space in place, forwarding an invalid |
| 71 | +# '-DCMAKE_CXX_COMPILER= g++' to CMake. The compiler must be normalized. |
| 72 | +withEnv(c(TEST_CXX = " g++", TEST_CXXFLAGS = ""), { |
| 73 | + result <- splitCompilerVar("TEST_CXX", "TEST_CXXFLAGS") |
| 74 | + check(isTRUE(result), "leading-whitespace compiler returns TRUE") |
| 75 | + check(identical(Sys.getenv("TEST_CXX"), "g++"), |
| 76 | + "leading-whitespace compiler is normalized (no leading space)") |
| 77 | + check(identical(Sys.getenv("TEST_CXXFLAGS"), ""), |
| 78 | + "leading-whitespace compiler leaves flags untouched") |
| 79 | +}) |
| 80 | + |
| 81 | +# a plain single-token compiler is already clean, but should still be |
| 82 | +# re-set (and report TRUE) so the normalization path is exercised uniformly |
| 83 | +withEnv(c(TEST_CXX = "g++", TEST_CXXFLAGS = ""), { |
| 84 | + result <- splitCompilerVar("TEST_CXX", "TEST_CXXFLAGS") |
| 85 | + check(isTRUE(result), "single-token compiler returns TRUE") |
| 86 | + check(identical(Sys.getenv("TEST_CXX"), "g++"), |
| 87 | + "single-token compiler is preserved") |
| 88 | +}) |
| 89 | + |
| 90 | +# trailing tokens are split off as flags and prepended to any existing flags |
| 91 | +withEnv(c(TEST_CXX = "g++ -std=c++17 -O2", TEST_CXXFLAGS = "-Wall"), { |
| 92 | + result <- splitCompilerVar("TEST_CXX", "TEST_CXXFLAGS") |
| 93 | + check(isTRUE(result), "compiler with flags returns TRUE") |
| 94 | + check(identical(Sys.getenv("TEST_CXX"), "g++"), |
| 95 | + "compiler with flags splits off the compiler token") |
| 96 | + check(identical(Sys.getenv("TEST_CXXFLAGS"), "-std=c++17 -O2 -Wall"), |
| 97 | + "compiler with flags prepends split flags to existing flags") |
| 98 | +}) |
| 99 | + |
| 100 | +# a whitespace-only value tokenizes to nothing: report FALSE and change nothing |
| 101 | +withEnv(c(TEST_CXX = " ", TEST_CXXFLAGS = "-Wall"), { |
| 102 | + result <- splitCompilerVar("TEST_CXX", "TEST_CXXFLAGS") |
| 103 | + check(identical(result, FALSE), "whitespace-only compiler returns FALSE") |
| 104 | + check(identical(Sys.getenv("TEST_CXXFLAGS"), "-Wall"), |
| 105 | + "whitespace-only compiler leaves flags untouched") |
| 106 | +}) |
| 107 | + |
| 108 | +# an unset compiler variable is a no-op that reports FALSE |
| 109 | +Sys.unsetenv("TEST_CXX_UNSET") |
| 110 | +check(identical(splitCompilerVar("TEST_CXX_UNSET", "TEST_CXXFLAGS"), FALSE), |
| 111 | + "unset compiler variable returns FALSE") |
| 112 | + |
| 113 | +if (failures > 0L) |
| 114 | + stop(sprintf("%d install.libs.R helper test(s) failed", failures)) |
| 115 | + |
| 116 | +writeLines("all install.libs.R helper tests passed") |
0 commit comments