From 1de359f3e6b095bcf7d577933bb2823b30da2f58 Mon Sep 17 00:00:00 2001 From: Greg Lin Date: Wed, 10 Aug 2022 17:17:01 -0500 Subject: [PATCH] Add tests for R builds --- test/docker-compose.yml | 75 ++++++++++++++++++++ test/test-centos.sh | 25 +++++++ test/test-deb.sh | 25 +++++++ test/test-opensuse.sh | 23 +++++++ test/test-r.sh | 18 +++++ test/test-rhel.sh | 26 +++++++ test/test.R | 103 ++++++++++++++++++++++++++++ test/testpkg/.Rbuildignore | 2 + test/testpkg/.gitignore | 2 + test/testpkg/DESCRIPTION | 12 ++++ test/testpkg/NAMESPACE | 6 ++ test/testpkg/R/testpkg.R | 32 +++++++++ test/testpkg/README.md | 3 + test/testpkg/man/add_it.Rd | 19 +++++ test/testpkg/man/square_it.Rd | 17 +++++ test/testpkg/man/subtract_it.Rd | 19 +++++ test/testpkg/man/testpkg-package.Rd | 14 ++++ test/testpkg/src/Makevars | 2 + test/testpkg/src/add.c | 10 +++ test/testpkg/src/init.c | 33 +++++++++ test/testpkg/src/square.f | 5 ++ test/testpkg/src/subtract.cpp | 10 +++ test/testpkg/testpkg.Rproj | 19 +++++ test/testpkg/tests/test.R | 5 ++ 24 files changed, 505 insertions(+) create mode 100644 test/docker-compose.yml create mode 100755 test/test-centos.sh create mode 100755 test/test-deb.sh create mode 100755 test/test-opensuse.sh create mode 100755 test/test-r.sh create mode 100755 test/test-rhel.sh create mode 100644 test/test.R create mode 100644 test/testpkg/.Rbuildignore create mode 100644 test/testpkg/.gitignore create mode 100644 test/testpkg/DESCRIPTION create mode 100644 test/testpkg/NAMESPACE create mode 100644 test/testpkg/R/testpkg.R create mode 100644 test/testpkg/README.md create mode 100644 test/testpkg/man/add_it.Rd create mode 100644 test/testpkg/man/square_it.Rd create mode 100644 test/testpkg/man/subtract_it.Rd create mode 100644 test/testpkg/man/testpkg-package.Rd create mode 100644 test/testpkg/src/Makevars create mode 100644 test/testpkg/src/add.c create mode 100644 test/testpkg/src/init.c create mode 100644 test/testpkg/src/square.f create mode 100644 test/testpkg/src/subtract.cpp create mode 100644 test/testpkg/testpkg.Rproj create mode 100644 test/testpkg/tests/test.R diff --git a/test/docker-compose.yml b/test/docker-compose.yml new file mode 100644 index 0000000..91ea366 --- /dev/null +++ b/test/docker-compose.yml @@ -0,0 +1,75 @@ +version: '2.0' + +services: + ubuntu-1804: + image: ubuntu:bionic + command: /test/test-deb.sh + environment: + - OS_IDENTIFIER=ubuntu-1804 + - R_VERSION=${R_VERSION} + volumes: + - ./:/test + - ../builder/integration/tmp:/packages + ubuntu-2004: + image: ubuntu:focal + command: /test/test-deb.sh + environment: + - OS_IDENTIFIER=ubuntu-2004 + - R_VERSION=${R_VERSION} + volumes: + - ./:/test + - ../builder/integration/tmp:/packages + ubuntu-2204: + image: ubuntu:jammy + command: /test/test-deb.sh + environment: + - OS_IDENTIFIER=ubuntu-2204 + - R_VERSION=${R_VERSION} + volumes: + - ./:/test + - ../builder/integration/tmp:/packages + debian-11: + image: debian:bullseye + command: /test/test-deb.sh + environment: + - OS_IDENTIFIER=debian-11 + - R_VERSION=${R_VERSION} + volumes: + - ./:/test + - ../builder/integration/tmp:/packages + debian-10: + image: debian:buster + command: /test/test-deb.sh + environment: + - OS_IDENTIFIER=debian-10 + - R_VERSION=${R_VERSION} + volumes: + - ./:/test + - ../builder/integration/tmp:/packages + centos-7: + image: centos:centos7 + command: /test/test-centos.sh + environment: + - OS_IDENTIFIER=centos-7 + - R_VERSION=${R_VERSION} + volumes: + - ./:/test + - ../builder/integration/tmp:/packages + centos-8: + image: rockylinux:8 + command: /test/test-centos.sh + environment: + - OS_IDENTIFIER=centos-8 + - R_VERSION=${R_VERSION} + volumes: + - ./:/test + - ../builder/integration/tmp:/packages + opensuse-153: + image: opensuse/leap:15.3 + command: /test/test-opensuse.sh + environment: + - OS_IDENTIFIER=opensuse-153 + - R_VERSION=${R_VERSION} + volumes: + - ./:/test + - ../builder/integration/tmp:/packages diff --git a/test/test-centos.sh b/test/test-centos.sh new file mode 100755 index 0000000..3cee256 --- /dev/null +++ b/test/test-centos.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -ex + +PKG_FILE=/packages/${OS_IDENTIFIER}/R-${R_VERSION}-1-1.x86_64.rpm + +if [ ! -f ${PKG_FILE} ]; then + echo "No package found, skipping tests" + exit 0 +fi + +yum -y -q update +yum -y install epel-release +yum -y install ${PKG_FILE} + +# Show rpm info +rpm -qi R-${R_VERSION} + +/test/test-r.sh + +yum -y remove R-${R_VERSION} + +if [ -d /opt/R/${R_VERSION} ]; then + echo "Failed to uninstall completely" + exit 1 +fi diff --git a/test/test-deb.sh b/test/test-deb.sh new file mode 100755 index 0000000..b4ee24b --- /dev/null +++ b/test/test-deb.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -ex + +PKG_FILE=/packages/${OS_IDENTIFIER}/r-${R_VERSION}_1_amd64.deb + +if [ ! -f ${PKG_FILE} ]; then + echo "No package found, skipping tests" + exit 0 +fi + +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -f -y ${PKG_FILE} + +# Show deb info +apt-cache show r-${R_VERSION} + +/test/test-r.sh + +apt-get remove -y r-${R_VERSION} + +if [ -d /opt/R/${R_VERSION} ]; then + echo "Failed to uninstall completely" + exit 1 +fi diff --git a/test/test-opensuse.sh b/test/test-opensuse.sh new file mode 100755 index 0000000..174b9c5 --- /dev/null +++ b/test/test-opensuse.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -ex + +PKG_FILE=/packages/${OS_IDENTIFIER}/R-${R_VERSION}-1-1.x86_64.rpm + +if [ ! -f ${PKG_FILE} ]; then + echo "No package found, skipping tests" + exit 0 +fi + +zypper --non-interactive --no-gpg-checks install ${PKG_FILE} + +# Show rpm info +rpm -qi R-${R_VERSION} + +/test/test-r.sh + +zypper --non-interactive remove R-${R_VERSION} + +if [ -d /opt/R/${R_VERSION} ]; then + echo "Failed to uninstall completely" + exit 1 +fi diff --git a/test/test-r.sh b/test/test-r.sh new file mode 100755 index 0000000..628acce --- /dev/null +++ b/test/test-r.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -ex + +DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" + +R_HOME=/opt/R/${R_VERSION}/lib/R +"${R_HOME}/bin/R" --version +"${R_HOME}/bin/Rscript" -e 'sessionInfo()' + +# List R devel dependencies +gcc --version +g++ --version +gfortran --version + +# List shared library dependencies (e.g. BLAS/LAPACK) +LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${R_HOME}/lib ldd "${R_HOME}/lib/libR.so" + +DIR=${DIR} "${R_HOME}/bin/Rscript" /test/test.R diff --git a/test/test-rhel.sh b/test/test-rhel.sh new file mode 100755 index 0000000..c2c7918 --- /dev/null +++ b/test/test-rhel.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -ex + +PKG_FILE=/packages/${OS_IDENTIFIER}/R-${R_VERSION}-1-1.x86_64.rpm + +if [ ! -f ${PKG_FILE} ]; then + echo "No package found, skipping tests" + exit 0 +fi + +dnf -y install dnf-plugins-core +dnf config-manager --set-enabled crb +dnf -y install epel-release +dnf -y install ${PKG_FILE} + +# Show rpm info +rpm -qi R-${R_VERSION} + +/test/test-r.sh + +dnf -y remove R-${R_VERSION} + +if [ -d /opt/R/${R_VERSION} ]; then + echo "Failed to uninstall completely" + exit 1 +fi diff --git a/test/test.R b/test/test.R new file mode 100644 index 0000000..360c9b1 --- /dev/null +++ b/test/test.R @@ -0,0 +1,103 @@ +# HTTP mirror to support R 3.1 +options(repos = c("https://cloud.r-project.org", "http://cloud.r-project.org")) + +# Create a temp lib to avoid installing into the system library +temp_lib <- tempdir() +.libPaths(temp_lib) + +# Install a package from CRAN +install.packages("R6") +library(R6) + +# Install a package with C/C++ and Fortran code, which links against libR, BLAS, LAPACK +curr_dir <- Sys.getenv("DIR", ".") +install.packages(file.path(curr_dir, "testpkg"), repos = NULL, clean = TRUE) +source(file.path(curr_dir, "testpkg/tests/test.R")) + +# Check iconv support +if (!capabilities("iconv") || !all(c("ASCII", "LATIN1", "UTF-8") %in% iconvlist())) { + stop("missing iconv support") +} + +# Check that built-in packages can be loaded +for (pkg in rownames(installed.packages(priority = c("base", "recommended")))) { + if (!require(pkg, character.only = TRUE)) { + stop(sprintf("failed to load built-in package %s", pkg)) + } +} + +# Show capabilities. Warnings are returned on missing libraries. +tryCatch(capabilities(), warning = function(w) { + print(capabilities()) + stop(sprintf("missing libraries: %s", w$message)) +}) + +# Check graphics devices +# https://stat.ethz.ch/R-manual/R-devel/library/grDevices/html/Devices.html +for (dev_name in c("png", "jpeg", "tiff", "svg", "bmp", "pdf", "postscript", + "xfig", "pictex", "cairo_pdf", "cairo_ps")) { + # Skip unsupported graphics devices (e.g. tiff in R >= 3.3 on CentOS 6) + if (dev_name %in% names(capabilities()) && capabilities(dev_name) == FALSE) { + next + } + dev <- getFromNamespace(dev_name, "grDevices") + tryCatch({ + file <- tempfile() + on.exit(unlink(file)) + if (dev_name == "xfig") { + # Suppress warning from xfig when onefile = FALSE (the default) + dev(file, onefile = TRUE) + } else { + dev(file) + } + plot(1) + dev.off() + }, warning = function(w) { + # Catch errors which manifest as warnings (e.g. "failed to load cairo DLL") + stop(sprintf("graphics device %s failed: %s", dev_name, w$message)) + }) +} + +# Check for unexpected output from graphics/text rendering. +# Run externally to capture output from external processes. +# For example, "Pango-WARNING **: failed to choose a font, expect ugly output" +# messages when rendering text without any system fonts installed. +output <- system2(R.home("bin/Rscript"), "-e 'png(tempfile()); plot(1)'", stdout = TRUE, stderr = TRUE) +if (length(output) > 0) { + stop(sprintf("unexpected output returned from plotting:\n%s", paste(output, collapse = "\n"))) +} + +# Check download methods: libcurl (supported in R >= 3.2) and internal (based on libxml) +if ("libcurl" %in% names(capabilities())) { + download.file("https://cloud.r-project.org", tempfile(), "libcurl") +} +tmpfile <- tempfile() +write.csv("test", tmpfile) +download.file(sprintf("file://%s", tmpfile), tempfile(), "internal") + +# Check that a pager is configured and help pages work +# https://stat.ethz.ch/R-manual/R-devel/library/base/html/file.show.html +output <- system2(R.home("bin/Rscript"), "-e 'help(stats)'", stdout = TRUE) +if (length(output) == 0) { + stop("failed to display help pages; check that a pager is configured properly") +} + +# Smoke test BLAS/LAPACK functionality. R may start just fine with an incompatible +# BLAS/LAPACK library, and only fail when calling a BLAS or LAPACK routine. +stopifnot(identical(crossprod(matrix(1)), matrix(1))) +stopifnot(identical(chol(matrix(1)), matrix(1))) + +# Check that R 3.x depends on PCRE1, and R 4.x depends on PCRE2. +# R 3.5 and 3.6 will link against PCRE2 if present, and take on an unnecessary dependency. +# Some distros do always require PCRE2, however, such as Debian 11. +ld_flags <- system2(R.home("bin/R"), c("CMD", "config", "--ldflags"), stdout = TRUE) +has_pcre1 <- grepl("-lpcre\\b", ld_flags) +has_pcre2 <- grepl("-lpcre2-8\\b", ld_flags) +if (getRversion() >= "3.5.0" && getRversion() < "4.0.0") { + stopifnot(has_pcre1) + if (has_pcre2) { + message(sprintf("Info: %s is linked against PCRE2, which may be unnecessary", R.version.string)) + } +} else if (getRversion() >= "4.0.0") { + stopifnot(has_pcre2 && !has_pcre1) +} diff --git a/test/testpkg/.Rbuildignore b/test/testpkg/.Rbuildignore new file mode 100644 index 0000000..91114bf --- /dev/null +++ b/test/testpkg/.Rbuildignore @@ -0,0 +1,2 @@ +^.*\.Rproj$ +^\.Rproj\.user$ diff --git a/test/testpkg/.gitignore b/test/testpkg/.gitignore new file mode 100644 index 0000000..508df78 --- /dev/null +++ b/test/testpkg/.gitignore @@ -0,0 +1,2 @@ +.Rproj.user +.Rhistory diff --git a/test/testpkg/DESCRIPTION b/test/testpkg/DESCRIPTION new file mode 100644 index 0000000..8ab34d8 --- /dev/null +++ b/test/testpkg/DESCRIPTION @@ -0,0 +1,12 @@ +Package: testpkg +Type: Package +Title: A Test Package +Version: 0.1.0 +Authors@R: person("Test", "Pkg", email = "test@test", role = c("aut", "cre")) +Description: A test package. +License: MIT +Depends: + R (>= 3.1) +SystemRequirements: C++11 +RoxygenNote: 6.1.1 +Encoding: UTF-8 diff --git a/test/testpkg/NAMESPACE b/test/testpkg/NAMESPACE new file mode 100644 index 0000000..20b3781 --- /dev/null +++ b/test/testpkg/NAMESPACE @@ -0,0 +1,6 @@ +# Generated by roxygen2: do not edit by hand + +export(add_it) +export(square_it) +export(subtract_it) +useDynLib(testpkg, .registration = TRUE) diff --git a/test/testpkg/R/testpkg.R b/test/testpkg/R/testpkg.R new file mode 100644 index 0000000..462e518 --- /dev/null +++ b/test/testpkg/R/testpkg.R @@ -0,0 +1,32 @@ +#' @useDynLib testpkg, .registration = TRUE +"_PACKAGE" + +#' Add it together +#' +#' @param a Number +#' @param b Number +#' @return Sum of numbers +#' @export +add_it <- function(a, b) { + .Call("add", a, b) +} + +#' Subtract it +#' +#' @param a Number +#' @param b Number +#' @return Difference of numbers +#' @export +subtract_it <- function(a, b) { + .Call("subtract", a, b) +} + +#' Square it up +#' +#' @param n Integer +#' @return Square +#' @export +square_it <- function(n) { + result <- .Fortran("square", n = as.integer(n), answer = as.integer(1)) + result$answer +} diff --git a/test/testpkg/README.md b/test/testpkg/README.md new file mode 100644 index 0000000..edbda2f --- /dev/null +++ b/test/testpkg/README.md @@ -0,0 +1,3 @@ +# testpkg + +Test package with C/C++ and Fortran code, which links against libR, BLAS, LAPACK (see [Makevars](src/Makevars)). diff --git a/test/testpkg/man/add_it.Rd b/test/testpkg/man/add_it.Rd new file mode 100644 index 0000000..7f00cf5 --- /dev/null +++ b/test/testpkg/man/add_it.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/testpkg.R +\name{add_it} +\alias{add_it} +\title{Add it together} +\usage{ +add_it(a, b) +} +\arguments{ +\item{a}{Number} + +\item{b}{Number} +} +\value{ +Sum of numbers +} +\description{ +Add it together +} diff --git a/test/testpkg/man/square_it.Rd b/test/testpkg/man/square_it.Rd new file mode 100644 index 0000000..e19a4b4 --- /dev/null +++ b/test/testpkg/man/square_it.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/testpkg.R +\name{square_it} +\alias{square_it} +\title{Square it up} +\usage{ +square_it(n) +} +\arguments{ +\item{n}{Integer} +} +\value{ +Square +} +\description{ +Square it up +} diff --git a/test/testpkg/man/subtract_it.Rd b/test/testpkg/man/subtract_it.Rd new file mode 100644 index 0000000..1b7bb2d --- /dev/null +++ b/test/testpkg/man/subtract_it.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/testpkg.R +\name{subtract_it} +\alias{subtract_it} +\title{Subtract it} +\usage{ +subtract_it(a, b) +} +\arguments{ +\item{a}{Number} + +\item{b}{Number} +} +\value{ +Difference of numbers +} +\description{ +Subtract it +} diff --git a/test/testpkg/man/testpkg-package.Rd b/test/testpkg/man/testpkg-package.Rd new file mode 100644 index 0000000..529cf99 --- /dev/null +++ b/test/testpkg/man/testpkg-package.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/testpkg.R +\docType{package} +\name{testpkg-package} +\alias{testpkg} +\alias{testpkg-package} +\title{testpkg: A Test Package} +\description{ +A test package. +} +\author{ +\strong{Maintainer}: Test Pkg \email{test@test} + +} diff --git a/test/testpkg/src/Makevars b/test/testpkg/src/Makevars new file mode 100644 index 0000000..03c539f --- /dev/null +++ b/test/testpkg/src/Makevars @@ -0,0 +1,2 @@ +CXX_STD=CXX11 +PKG_LIBS=$(MAIN_LDFLAGS) $(LDFLAGS) $(LIBR) $(LIBS) $(LAPACK_LIBS) $(BLAS_LIBS) $(SHLIB_OPENMP_CFLAGS) diff --git a/test/testpkg/src/add.c b/test/testpkg/src/add.c new file mode 100644 index 0000000..77cd5f3 --- /dev/null +++ b/test/testpkg/src/add.c @@ -0,0 +1,10 @@ +#include +#include + +SEXP add(SEXP a, SEXP b) +{ + SEXP result = PROTECT(allocVector(REALSXP, 1)); + REAL(result)[0] = asReal(a) + asReal(b); + UNPROTECT(1); + return result; +} diff --git a/test/testpkg/src/init.c b/test/testpkg/src/init.c new file mode 100644 index 0000000..7d40e5b --- /dev/null +++ b/test/testpkg/src/init.c @@ -0,0 +1,33 @@ +#include +#include +#include + +#define CALLDEF(name, n) {#name, (DL_FUNC) &name, n} +#define FDEF(name) {#name, (DL_FUNC) &F77_SUB(name), sizeof(name ## _t)/sizeof(name ## _t[0]), name ##_t} + +extern SEXP add(SEXP, SEXP); +extern SEXP subtract(SEXP, SEXP); + +void F77_SUB(square)(int *n, int *answer); + +static R_NativePrimitiveArgType square_t[] = { + INTSXP, + INTSXP +}; + +static const R_CallMethodDef CallEntries[] = { + CALLDEF(add, 2), + CALLDEF(subtract, 2), + {NULL, NULL, 0} +}; + +static const R_FortranMethodDef fMethods[] = { + FDEF(square), + {NULL, NULL, 0} +}; + +void R_init_testpkg(DllInfo *dll) +{ + R_registerRoutines(dll, NULL, CallEntries, fMethods, NULL); + R_useDynamicSymbols(dll, FALSE); +} diff --git a/test/testpkg/src/square.f b/test/testpkg/src/square.f new file mode 100644 index 0000000..77971e9 --- /dev/null +++ b/test/testpkg/src/square.f @@ -0,0 +1,5 @@ + subroutine square(x,answer) + integer, intent(in) :: x + integer, intent(out) :: answer + answer = x * x + end diff --git a/test/testpkg/src/subtract.cpp b/test/testpkg/src/subtract.cpp new file mode 100644 index 0000000..c9ef0a1 --- /dev/null +++ b/test/testpkg/src/subtract.cpp @@ -0,0 +1,10 @@ +#include +#include + +extern "C" SEXP subtract(SEXP a, SEXP b) +{ + SEXP result = PROTECT(allocVector(REALSXP, 1)); + REAL(result)[0] = asReal(a) - asReal(b); + UNPROTECT(1); + return result; +} diff --git a/test/testpkg/testpkg.Rproj b/test/testpkg/testpkg.Rproj new file mode 100644 index 0000000..cb6ae55 --- /dev/null +++ b/test/testpkg/testpkg.Rproj @@ -0,0 +1,19 @@ +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: No + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: knitr +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes + +BuildType: Package +PackageRoxygenize: rd,collate,namespace diff --git a/test/testpkg/tests/test.R b/test/testpkg/tests/test.R new file mode 100644 index 0000000..7bf3ce7 --- /dev/null +++ b/test/testpkg/tests/test.R @@ -0,0 +1,5 @@ +library(testpkg) + +stopifnot(add_it(1, 2) == 3) +stopifnot(subtract_it(1, 2) == -1) +stopifnot(square_it(3) == 9)