daqana / dqrng

Fast Pseudo Random Number Generators for R
https://daqana.github.io/dqrng/
Other
42 stars 8 forks source link

Incomplete testing on CRAN #15

Open rstub opened 5 years ago

rstub commented 5 years ago

While everything works on rhub's Solaris machine, some of the new tests fail on CRAN's Solaris machine:

Version: 0.1.0
Check: tests
Result: ERROR
     Running ‘testthat.R’ [25s/48s]
    Running the tests in ‘tests/testthat.R’ failed.
    Complete output:
     > library(testthat)
     > library(dqrng)
     >
     > test_check("dqrng")
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 49: Warning: Shift count is too large.
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 83: Where: While instantiating "unsigned short dqrng::convert_seed_internal<unsigned short, int, 32>(const int*, unsigned)".
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 83: Where: Instantiated from unsigned short dqrng::convert_seed<unsigned short>(const int*, unsigned).
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 88: Where: Instantiated from unsigned short dqrng::convert_seed<unsigned short>(Rcpp::Vector<13, PreserveStorage>).
     "convert.cpp", line 12: Where: Instantiated from std::string convert_base<unsigned short>(Rcpp::Vector<13, PreserveStorage>).
     "convert.cpp", line 20: Where: Instantiated from non-template code.
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 58: Warning: Shift count is too large.
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 83: Where: While instantiating "unsigned short dqrng::convert_seed_internal<unsigned short, int, 32>(const int*, unsigned)".
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 83: Where: Instantiated from unsigned short dqrng::convert_seed<unsigned short>(const int*, unsigned).
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 88: Where: Instantiated from unsigned short dqrng::convert_seed<unsigned short>(Rcpp::Vector<13, PreserveStorage>).
     "convert.cpp", line 12: Where: Instantiated from std::string convert_base<unsigned short>(Rcpp::Vector<13, PreserveStorage>).
     "convert.cpp", line 20: Where: Instantiated from non-template code.
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 49: Warning: Shift count is too large.
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 83: Where: While instantiating "unsigned dqrng::convert_seed_internal<unsigned, int, 32>(const int*, unsigned)".
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 83: Where: Instantiated from unsigned dqrng::convert_seed<unsigned>(const int*, unsigned).
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 88: Where: Instantiated from unsigned dqrng::convert_seed<unsigned>(Rcpp::Vector<13, PreserveStorage>).
     "convert.cpp", line 12: Where: Instantiated from std::string convert_base<unsigned>(Rcpp::Vector<13, PreserveStorage>).
     "convert.cpp", line 25: Where: Instantiated from non-template code.
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 58: Warning: Shift count is too large.
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 83: Where: While instantiating "unsigned dqrng::convert_seed_internal<unsigned, int, 32>(const int*, unsigned)".
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 83: Where: Instantiated from unsigned dqrng::convert_seed<unsigned>(const int*, unsigned).
     "/home/ripley/R/Lib32/dqrng/include/convert_seed.h", line 88: Where: Instantiated from unsigned dqrng::convert_seed<unsigned>(Rcpp::Vector<13, PreserveStorage>).
     "convert.cpp", line 12: Where: Instantiated from std::string convert_base<unsigned>(Rcpp::Vector<13, PreserveStorage>).
     "convert.cpp", line 25: Where: Instantiated from non-template code.
     4 Warning(s) detected.

     *** caught segfault ***
     address 0, cause 'memory not mapped'

 Traceback:
     1: convert_16(-1)
     2: eval_bare(get_expr(quo), get_env(quo))
     3: doTryCatch(return(expr), name, parentenv, handler)
     4: tryCatchOne(expr, names, parentenv, handlers[[1L]])
     5: tryCatchList(expr, classes, parentenv, handlers)
     6: tryCatch({ code NULL}, error = function(e) e)
     7: capture(act$val <- eval_bare(get_expr(quo), get_env(quo)))
     8: quasi_capture(enquo(object), capture_error, label = label)
     9: expect_error(convert_16(-1), "seed element out of range")
     10: eval(code, test_env)
     11: eval(code, test_env)
     12: withCallingHandlers({ eval(code, test_env) if (!handled && !is.null(test)) { skip_empty() }}, expectation = handle_expectation, skip = handle_skip, warning = handle_warning, message = handle_message, error = handle_error)
     13: doTryCatch(return(expr), name, parentenv, handler)
     14: tryCatchOne(expr, names, parentenv, handlers[[1L]])
     15: tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
     16: doTryCatch(return(expr), name, parentenv, handler)
     17: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), names[nh], parentenv, handlers[[nh]])
     18: tryCatchList(expr, classes, parentenv, handlers)
     19: tryCatch(withCallingHandlers({ eval(code, test_env) if (!handled && !is.null(test)) { skip_empty() }}, expectation = handle_expectation, skip = handle_skip, warning = handle_warning, message = handle_message, error = handle_error), error = handle_fatal, skip = function(e) { })
     20: test_code(desc, code, env = parent.frame())
     21: test_that("conversion to 16-bit integers works correctly", { expect_identical(convert_16(0), "0") expect_identical(convert_16(65535), "65535") expect_identical(convert_16(c(0, 0, 0)), "0") expect_identical(convert_16(c(0, 65535)), "65535") expect_identical(convert_16(c(0, 0, 65535)), "65535") expect_error(convert_16(-1), "seed element out of range") expect_error(convert_16(NA_integer_), "seed element out of range") expect_error(convert_16(65536), "seed element out of range") expect_error(convert_16(c(1, 0)), "vector implies an out-of-range seed")})
     22: eval(code, test_env)
     23: eval(code, test_env)
     24: withCallingHandlers({ eval(code, test_env) if (!handled && !is.null(test)) { skip_empty() }}, expectation = handle_expectation, skip = handle_skip, warning = handle_warning, message = handle_message, error = handle_error)
     25: doTryCatch(return(expr), name, parentenv, handler)
     26: tryCatchOne(expr, names, parentenv, handlers[[1L]])
     27: tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
     28: doTryCatch(return(expr), name, parentenv, handler)
     29: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), names[nh], parentenv, handlers[[nh]])
     30: tryCatchList(expr, classes, parentenv, handlers)
     31: tryCatch(withCallingHandlers({ eval(code, test_env) if (!handled && !is.null(test)) { skip_empty() }}, expectation = handle_expectation, skip = handle_skip, warning = handle_warning, message = handle_message, error = handle_error), error = handle_fatal, skip = function(e) { })
     32: test_code(NULL, exprs, env)
     33: source_file(path, new.env(parent = env), chdir = TRUE, wrap = wrap)
     34: force(code)
     35: with_reporter(reporter = reporter, start_end_reporter = start_end_reporter, { lister$start_file(basename(path)) source_file(path, new.env(parent = env), chdir = TRUE, wrap = wrap) end_context() })
     36: FUN(X[[i]], ...)
     37: lapply(paths, test_file, env = env, reporter = current_reporter, start_end_reporter = FALSE, load_helpers = FALSE, wrap = wrap)
     38: force(code)
     39: with_reporter(reporter = current_reporter, results <- lapply(paths, test_file, env = env, reporter = current_reporter, start_end_reporter = FALSE, load_helpers = FALSE, wrap = wrap))
     40: test_files(paths, reporter = reporter, env = env, stop_on_failure = stop_on_failure, stop_on_warning = stop_on_warning, wrap = wrap)
     41: test_dir(path = test_path, reporter = reporter, env = env, filter = filter, ..., stop_on_failure = stop_on_failure, stop_on_warning = stop_on_warning, wrap = wrap)
     42: test_package_dir(package = package, test_path = test_path, filter = filter, reporter = reporter, ..., stop_on_failure = stop_on_failure, stop_on_warning = stop_on_warning, wrap = wrap)
     43: test_check("dqrng")

CC: @LTLA

LTLA commented 5 years ago

Hm. Is the mere presence of UB code sufficient to cause the segfault, even if it never gets executed? I will try to circumvent this at compile-time with by using an specialized template function for the shift that simply returns zero (or does nothing) for the most common (yet invalid) integer size/SHIFT combinations.

rstub commented 5 years ago

I am not sure about the UB code causing these problems. My current theory is as follows:

So maybe it might be sufficient to add // [[Rcpp::plugins(cpp11)]] to convert.cpp. What do you think?

LTLA commented 5 years ago

I thought we already had C++11 turned on in convert.cpp. Hard to imagine anything compiling without it, given the constexprs in convert_seed.h.

rstub commented 5 years ago

You are correct, C++11 is requested right in the first line of the file.

I still find it interesting that it fails for convert_16(-1), and that CRAN seems to use a different compiler than rhub.

LTLA commented 5 years ago

I've been reading up about UB and it's pretty crazy. They weren't joking when they say that the compiler can do literally anything it wants. Possibly even more so if it knows that it's UB at compile time.

Getting rid of the UB may or may not fix it, but at least we don't have any warnings anymore. That's one less thing we need to argue about if we need to escalate this to the CRAN maintainers.

rstub commented 5 years ago

Interestingly, meanwhile MacOS also reports ERRORS:


Result: ERROR
     Running ‘testthat.R’ [24s/24s]
    Running the tests in ‘tests/testthat.R’ failed.
    Last 13 lines of output:
     `convert_64(c(1, 1, 0))` threw an error with unexpected message.
     Expected match: "vector implies an out-of-range seed"
     Actual message: "c++ exception (unknown reason)"

     ══ testthat results ═══════════════════════════════════════════════════════════
     OK: 79 SKIPPED: 0 FAILED: 6
     1. Failure: conversion to 16-bit integers works correctly (@test-convert.R#15)
     2. Failure: conversion to 16-bit integers works correctly (@test-convert.R#16)
     3. Failure: conversion to 16-bit integers works correctly (@test-convert.R#17)
     4. Failure: conversion to 16-bit integers works correctly (@test-convert.R#18)
     5. Failure: conversion to 32-bit integers works correctly (@test-convert.R#33)
     6. Failure: conversion to 64-bit integers works correctly (@test-convert.R#55)

     Error: testthat unit tests failed
     Execution halted 

This is odd, since MacOS is tested via Travis CI.

Anyway, the failed tests are all the ones that check for error conditions. So my suggestion would be to just skip those tests on CRAN. I don't like that, but I see no better immediate solution without the ability to reproduce the issues first.

LTLA commented 5 years ago

Is this with the shift-avoiding code? Might be worth one last roll of the dice, especially if it is some UB. I suppose UBSan doesn't pick it up because it's not part of the dqrng shared library.

rstub commented 5 years ago

The tests on CRAN use the original version (it typically takes a few days for all CRAN machines to build a new or updated package). However, I have meanwhile been able to reproduce the MacOS failure with the most recent version. In addition, one of the failure happens for 64bit integers, where there is no UB.

LTLA commented 5 years ago

Possibly relevant:

https://discourse.mc-stan.org/t/error-in-stanc-c-exception-unknown-reason/2120/6

rstub commented 5 years ago

That looks very relevant. And it fits to my recent experiences. Even the examples from http://gallery.rcpp.org/articles/intro-to-exceptions/ do not work properly on MacOS (CRAN build with matching clang). Only Rcpp::stop does work properly. I guess a custom exception class derived from std::exception would also work. So for MacOS I see four possibilities:

  1. Skip the exception checking tests (on CRAN).
  2. Dump down the test, i.e. only expect an exception, not any particular output.
  3. Use Rcpp::stop in an otherwise Rcpp-independent piece of code.
  4. Introduce a custom exception class derived from std::exception.

    I don't like 2. and 3. The cleanest solution would b 4. (if it works). And 1. is the easiest solution, but also the one most likely to help with the Solaris ERROR. I am currently leaning towards 1.

LTLA commented 5 years ago

Yes, agree that 1 is the best, if we can just do the skips on OSX. Alternatively, we could write a wrapper around expect_error that just does 2 for OSX only, something like:

safe_expect_error <- function(..., msg) {
   os.type <- Sys.info()["sysname"]    
   if (os.type=="Darwin") {
       expect_error(...)
    } else {
       expect_error(..., msg)
    }
}
rstub commented 5 years ago

Thanks, I like the safe_except_error and put it into #17. I have also put in a skip_on_os("solaris") for all the exception-triggering tests. I am not in the mood for gambling ... ;-)

Upload on Monday, since one build result is still missing and it avoids a NOTE.

rstub commented 5 years ago

Skipping on Solaris was successful. Whether it is necessary or merely sufficient is a different question. I meanwhile fear that it is some sort of ABI incompatibility triggered by compilation with the Oracle compiler instead of g++, c.f. https://stackoverflow.com/questions/54492667/segfault-when-throwing-stdruntime-error-on-ubuntu-xenial-with-rcpp.

rstub commented 5 years ago

Version 0.1.1 has been build on CRAN on all platforms. However, there are currently two limitations concerning the tests that deliberately throw a C++ exception:

It would be nice if a better solution could be found.