Skip to content

Commit 17ff513

Browse files
committed
run tests in parallel
1 parent bacc15a commit 17ff513

File tree

4 files changed

+70
-43
lines changed

4 files changed

+70
-43
lines changed

pkg/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
version 0.9.7
2+
- Run test files in parallel (e.g. use test_package("pkg", ncpu=2)) on OSs supporting forks.
23
- New function 'expect_null'
34
- Added second vignette with real-life examples of tinytest tests.
5+
- build_install_test gains argument 'color' and 'verbose'
6+
47

58
version 0.9.6
69
- Fixed error caught on CRAN

pkg/R/tinytest.R

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ reset_options <- function(env){
565565
#'
566566
#' @param file \code{[character]} File location of a .R file.
567567
#' @param at_home \code{[logical]} toggle local tests.
568-
#' @param verbose \code{[logical]} toggle verbosity during execution
568+
#' @param verbose \code{[integer]} verbosity level. 0: be quiet, 1: print status per file, 2: print status per test expression.
569569
#' @param color \code{[logical]} toggle colorize counts in verbose mode (see Note)
570570
#' @param remove_side_effects \code{[logical]} toggle remove user-defined side effects? See section on side effects.
571571
#'
@@ -615,15 +615,15 @@ reset_options <- function(env){
615615
#' @export
616616
run_test_file <- function( file
617617
, at_home=TRUE
618-
, verbose = getOption("tt.verbose", TRUE)
618+
, verbose = getOption("tt.verbose", 2)
619619
, color = getOption("tt.pr.color", TRUE)
620-
, remove_side_effects = TRUE ){
620+
, remove_side_effects = TRUE
621+
, ...){
621622

622623
if (!file_test("-f", file)){
623624
stop(sprintf("'%s' does not exist or is a directory",file),call.=FALSE)
624625
}
625626
# convenience print function
626-
catf <- function(fmt,...) if (verbose) cat(sprintf(fmt,...))
627627

628628
## where to come back after running the file
629629
oldwd <- getwd()
@@ -699,6 +699,9 @@ run_test_file <- function( file
699699
}
700700
prfile <- paste("Running",gsub(" ",".",sprintf("%-30s",basename(file))))
701701

702+
# internal use only: load pkg if passed by run_test_dir
703+
L <- list(...)
704+
if (!is.null(L$pkg)) require(L$pkg, character.only=TRUE, quiet=TRUE)
702705
# evaluate expressions one by one
703706
for ( i in seq_along(parsed) ){
704707
expr <- parsed[[i]]
@@ -707,20 +710,27 @@ run_test_file <- function( file
707710
o$call <- expr
708711
out <- eval(expr, envir=e)
709712

710-
# print the test counter.
711-
catf("\r%s %4d tests ", prfile, o$ntest())
712-
# print status after counter
713-
if ( o$ntest() == 0 ) {} # print nothing if nothing was tested
714-
else if ( o$nfail() == 0) catf(if(color) "\033[0;32mOK\033[0m" else "OK")
715-
else catf(if (color) "\033[0;31m%d errors\033[0m" else "%d errors", o$nfail())
713+
if (verbose == 2) print_status(prfile, o, color)
716714
}
717-
catf("\n")
715+
if (verbose == 1) print_status(prfile, o, color)
716+
if (verbose >= 1) catf("\n")
718717

719-
718+
# returns a 'list' of 'tinytest' objects
720719
test_output <- o$gimme()
721720
structure(test_output, class="tinytests")
722721
}
723722

723+
# helper functions for printing test status.
724+
catf <- function(fmt,...) cat(sprintf(fmt,...))
725+
726+
print_status <- function(filename, env, color){
727+
catf("\r%s %4d tests ", filename, env$ntest())
728+
# print status after counter
729+
if ( env$ntest() == 0 ) {} # print nothing if nothing was tested
730+
else if ( env$nfail() == 0) catf(if(color) "\033[0;32mOK\033[0m" else "OK")
731+
else catf(if (color) "\033[0;31m%d errors\033[0m" else "%d errors", env$nfail())
732+
}
733+
724734

725735

726736
#' Run all tests in a directory
@@ -738,10 +748,12 @@ run_test_file <- function( file
738748
#' @param remove_side_effects \code{[logical]} toggle remove user-defined side
739749
#' effects. Environment variables (\code{Sys.setenv()}) and options (\code{options()})
740750
#' defined in a test file are reset before running the next test file (see details).
751+
#' @param ncpu Number of CPUs to use. Paralellizes tests over files.
741752
#' @param lc_collate \code{[character]} Locale setting used to sort the
742753
#' test files into the order of execution. The default \code{NA} ensures
743754
#' current locale is used. Set this e.g. to \code{"C"} to ensure bytewise
744755
#' and more platform-independent sorting (see details).
756+
#' @param ... Arguments passed to \code{run_test_file}
745757
#'
746758
#' @section Details:
747759
#'
@@ -756,6 +768,10 @@ run_test_file <- function( file
756768
#' to survive a single file, use \code{base::Sys.setenv()} explicitly.
757769
#' Similarly, if an option setting needs to survive, use \code{base::options}
758770
#'
771+
#' @section Parallel tests:
772+
#' If \code{ncpu > 1}
773+
#'
774+
#'
759775
#' @return A \code{tinytests} object
760776
#'
761777
#'
@@ -782,10 +798,12 @@ run_test_file <- function( file
782798
#' @export
783799
run_test_dir <- function(dir="inst/tinytest", pattern="^test.*\\.[rR]"
784800
, at_home = TRUE
785-
, verbose = getOption("tt.verbose",TRUE)
801+
, verbose = getOption("tt.verbose", 2)
786802
, color = getOption("tt.pr.color",TRUE)
787803
, remove_side_effects = TRUE
788-
, lc_collate = getOption("tt.collate",NA) ){
804+
, ncpu = 1
805+
, lc_collate = getOption("tt.collate",NA)
806+
, ... ){
789807
oldwd <- getwd()
790808
on.exit( setwd(oldwd) )
791809
setwd(dir)
@@ -795,17 +813,21 @@ run_test_dir <- function(dir="inst/tinytest", pattern="^test.*\\.[rR]"
795813

796814

797815

798-
test_output <- list()
799-
800-
for ( file in testfiles ){
801-
test_output <- c(test_output
802-
, run_test_file(file
803-
, at_home = at_home
804-
, verbose = verbose
805-
, color = color
806-
, remove_side_effects = remove_side_effects))
816+
if (ncpu == 1){
817+
test_output <- lapply(testfiles, run_test_file
818+
, at_home = at_home
819+
, verbose = verbose
820+
, color = color
821+
, remove_side_effects = remove_side_effects
822+
, ...)
823+
} else {
824+
test_output <- parallel::mclapply(testfiles
825+
, run_test_file, at_home=at_home, verbose=min(verbose,1)
826+
, color=color, remove_side_effects=TRUE, ...)
807827
}
808-
structure(test_output,class="tinytests")
828+
# by using '(mc)lapply' we get a list of tinytests objects. We need to unwind
829+
# one level to a list of 'tinytest' objects and class it 'tinytests'.
830+
structure(unlist(test_output,recursive=FALSE), class="tinytests")
809831
}
810832

811833

@@ -847,7 +869,7 @@ locale_sort <- function(x, lc_collate=NA, ...){
847869
#' direcory where \code{DESCRIPTION} and \code{NAMESPACE} reside).
848870
#' @param testdir \code{[character]} scalar. Subdirectory where test files are
849871
#' stored.
850-
#' @param ... passed to \code{run_test_dir}.
872+
#' @param ... passed to \code{run_test_dir} (e.g. \code{ncpu}).
851873
#'
852874
#' @rdname run_test_dir
853875
#' @export
@@ -883,7 +905,7 @@ at_home <- function(){
883905
#' @param testdir \code{[character]} scalar. Path to installed directory, relative
884906
#' to the working directory of \code{R CMD check}.
885907
#' @param at_home \code{[logical]} scalar. Are we at home? (see Details)
886-
#' @param ... extra arguments, passed to \code{\link{run_test_dir}}
908+
#' @param ... extra arguments passed to \code{\link{run_test_dir}} (e.g. \code{ncpu}).
887909
#'
888910
#'
889911
#' @section Details:
@@ -908,11 +930,10 @@ at_home <- function(){
908930
test_package <- function(pkgname, testdir = "tinytest", at_home=FALSE, ...){
909931
oldwd <- getwd()
910932
on.exit(setwd(oldwd))
911-
require(pkgname, character.only=TRUE)
912933
testdir <- system.file(testdir, package=pkgname)
913934
setwd(testdir)
914935

915-
out <- run_test_dir("./", at_home=at_home, ...)
936+
out <- run_test_dir("./", at_home=at_home, pkg=pkgname,...)
916937
i_fail <- sapply(out, isFALSE)
917938
if ( any(i_fail) ){
918939
msg <- paste( sapply(out[i_fail], format.tinytest, type="long"), collapse="\n")
@@ -938,6 +959,7 @@ test_package <- function(pkgname, testdir = "tinytest", at_home=FALSE, ...){
938959
#' @param testdir \code{[character]} Name of directory under \code{pkgdir/inst}
939960
#' containing test files.
940961
#' @param at_home \code{[logical]} toggle local tests.
962+
#' @param ncpu Number of CPUs to use (see \code{\link{run_test_dir}} for details).
941963
#' @param verbose \code{[logical]} toggle verbosity during execution
942964
#' @param keep_tempdir \code{[logical]} keep directory where the pkg is
943965
#' installed and where tests are run? If \code{TRUE}, the directory is not deleted
@@ -955,7 +977,8 @@ test_package <- function(pkgname, testdir = "tinytest", at_home=FALSE, ...){
955977
#' @export
956978
build_install_test <- function(pkgdir="./", testdir="tinytest"
957979
, at_home=TRUE
958-
, verbose=getOption("tt.verbose",TRUE)
980+
, verbose=getOption("tt.verbose",2)
981+
, ncpu = 1
959982
, keep_tempdir=FALSE){
960983
oldwd <- getwd()
961984
tdir <- tempfile()
@@ -992,11 +1015,12 @@ suppressPackageStartupMessages({
9921015
library('%s', lib.loc='%s',character.only=TRUE)
9931016
library('tinytest')
9941017
})
995-
# testdir pkgname tdir at_home verbose
996-
out <- run_test_dir(system.file('%s', package='%s', lib.loc='%s'), at_home=%s, verbose=%s)
1018+
# testdir pkgname tdir
1019+
out <- run_test_dir(system.file('%s', package='%s', lib.loc='%s')
1020+
, at_home=%s, verbose=%s, ncpu=%s, pkg='%s')
9971021
saveRDS(out, file='output.RDS')
9981022
"
999-
scr <- sprintf(script, pkgname, tdir,testdir, pkgname,tdir, at_home, verbose)
1023+
scr <- sprintf(script, pkgname, tdir,testdir, pkgname,tdir, at_home, verbose, ncpu, pkgname)
10001024

10011025
write(scr, file="test.R")
10021026
system("Rscript test.R")

pkg/vignettes/using_tinytest.Rnw

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ checks. An example test file in tinytest can look like this.
221221

222222
A particular file can be run using\marginpar{\code{run\_test\_file}}
223223
<<eval>>=
224-
run_test_file("test_addOne.R", verbose=FALSE)
224+
run_test_file("test_addOne.R", verbose=0)
225225
@
226-
We use \code{verbose=FALSE} to avoid cluttering the output in this vignette.
226+
We use \code{verbose=0} to avoid cluttering the output in this vignette.
227227
By default, verbosity is turned on, and a colorized counter is shown while
228228
tests are run. It shows number of tests uncolored, number of failures in red
229229
and number of passes in green. If you work with a terminal that does not
@@ -236,7 +236,7 @@ test can be found.
236236
By default only failing tests are printed. You can store the output and print
237237
all of them.
238238
<<>>=
239-
test_results <- run_test_file("test_addOne.R", verbose=FALSE)
239+
test_results <- run_test_file("test_addOne.R", verbose=0)
240240
print(test_results, passes=TRUE)
241241
@
242242
Or you can set
@@ -257,7 +257,7 @@ By default, this will run all files of which the name starts with
257257
To create some results, run the tests in this package.
258258
<<>>=
259259
out <- run_test_dir(system.file("tinytest", package="tinytest")
260-
, verbose=FALSE)
260+
, verbose=0)
261261
@
262262
The results can be turned into data using \code{as.data.frame}.
263263
\marginpar{\code{as.data.frame}}
@@ -460,8 +460,8 @@ options(prompt="R> ", continue=" ")
460460
All the interactive test runners have \code{at\_home=TRUE} by default, so while
461461
you are developing all tests are run, unless you exclude them explicitly.
462462
<<>>=
463-
run_test_file("test_hehe.R", verbose=FALSE)
464-
run_test_file("test_hehe.R", verbose=FALSE, at_home=FALSE)
463+
run_test_file("test_hehe.R", verbose=0)
464+
run_test_file("test_hehe.R", verbose=0, at_home=FALSE)
465465
@
466466

467467
Here is an overview of test runners and their default setting for \code{at\_home}.

using_tinytest.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ oldwd=`pwd`
99

1010
cd pkg/vignettes
1111

12-
R -e "Sweave(dir(pattern='Rnw$'))"
13-
pdflatex *.tex
14-
pdflatex *.tex
15-
pdflatex *.tex
12+
R -e "Sweave('using_tinytest.Rnw'))"
13+
pdflatex using_tinytest.tex
14+
pdflatex using_tinytest.tex
15+
pdflatex using_tinytest.tex
1616

17-
evince *.pdf &
17+
evince using_tinytest.pdf &
1818

1919

2020

0 commit comments

Comments
 (0)