## simple call, only field names fg <- setRefClass("foo", c("bar", "flag")) f0 <- new("foo") # deprecated, but should still work f1 <- fg(flag = "testing") f1$bar <- 1 stopifnot(identical(f1$bar, 1)) ## add method fg$methods(showAll = function() c(bar, flag)) stopifnot(all.equal(f1$showAll(), c(1, "testing"))) str(f1) fg <- setRefClass("foo", list(bar = "numeric", flag = "character", tag = "ANY"), methods = list(addToBar = function(incr) { b <- bar + incr bar <<- b b } ) ) fg$lock("flag") stopifnot(identical(fg$lock(), "flag")) ff <- new("foo", bar = 1.5) stopifnot(identical(ff$bar, 1.5)) ff$bar <- pi stopifnot(identical(ff$bar, pi)) ## flag has not yet been set ff$flag <- "flag test" stopifnot(identical(ff$flag, "flag test")) ## but no second assign stopifnot(is(tryCatch(ff$flag <- "new", error = function(e)e), "error")) ## test against generator f2 <- fg(bar = pi, flag = "flag test") ## identical does not return TRUE if *contents* of env are identical stopifnot(identical(ff$bar, f2$bar), identical(ff$flag, f2$flag)) ## but flag was now assigned once stopifnot(is(tryCatch(f2$flag <- "new", error = function(e)e), "error")) str(f2) ## add some accessor methods fg$accessors("bar") ff$setBar(1:3) stopifnot(identical(ff$getBar(), 1:3)) ff$getBar() stopifnot(all.equal(ff$addToBar(1), 2:4)) ## Add a method fg$methods(barTimes = function(x) { "This method multiples field bar by argument x and this string is self-documentation" setBar(getBar() * x)}) ffbar <- ff$getBar() ff$barTimes(10) stopifnot(all.equal(ffbar * 10, ff$getBar())) ff$barTimes(.1) ## inheritance. redefines flag so should fail: stopifnot(is(tryCatch(setRefClass("foo2", list(b2 = "numeric", flag = "complex"), contains = "foo", refMethods = list(addBoth = function(incr) { addToBar(incr) #uses inherited class method setB2(getB2() + incr) })), error = function(e)e), "error")) ## but with flag as a subclass of "characters", should work ## Also subclasses "tag" which had class "ANY before setClass("ratedChar", contains = "character", representation(score = "numeric")) foo2 <- setRefClass("foo2", list(b2 = "numeric", flag = "ratedChar", tag = "numeric"), contains = "foo", methods = list(addBoth = function(incr) { addToBar(incr) #uses inherited class method b2 <<- b2 + incr })) ## now lock the flag field; should still allow one write foo2$lock("flag") f2 <- foo2(bar = -3, flag = as("ANY", "ratedChar"), b2 = ff$bar, tag = 1.5) ## but not a second one stopifnot(is(tryCatch(f2$flag <- "Try again", error = function(e)e), "error")) str(f2) f22 <- foo2(bar = f2$bar) ## same story if assignment follows the initialization f22$flag <- f2$flag stopifnot(is(tryCatch(f22$flag <- "Try again", error = function(e)e), "error")) ## Exporting superclass object f22 <- fg(bar = f2$bar, flag = f2$flag) f2e <- f2$export("foo") stopifnot(identical(f2e$bar, f22$bar), identical(f2e$flag, f22$flag), identical(class(f2e), class(f22))) stopifnot(identical(f2$flag, as("ANY", "ratedChar")), identical(f2$bar, -3), all.equal(f2$b2, 2:4+0)) f2$addBoth(-1) stopifnot(all.equal(f2$bar, -4), all.equal(f2$b2, 1:3+0)) ## test callSuper() foo3 <- setRefClass("foo3", fields = list(flag2 = "ratedChar"), contains = "foo2", methods = list(addBoth = function(incr) { callSuper(incr) flag2 <<- as(paste(flag, paste(incr, collapse = ", "), sep = "; "), "ratedChar") incr })) f2 <- foo2(bar = -3, flag = as("ANY", "ratedChar"), b2 = 1:3) f3 <- foo3() f3$import(f2) stopifnot(all.equal(f3$b2, f2$b2), all.equal(f3$bar, f2$bar), all.equal(f3$flag, f2$flag)) f3$addBoth(1) stopifnot(all.equal(f3$bar, -2), all.equal(f3$b2, 2:4+0), all.equal(f3$flag2, as("ANY; 1", "ratedChar"))) ## but the import should have used up the one write for $flag stopifnot(is(tryCatch(f3$flag <- "Try again", error = function(e)e), "error")) str(f3) ## importing the same class (not very useful but documented to work) f3 <- foo3() f4 <- foo3(bar = -3, flag = as("More", "ratedChar"), b2 = 1:3, flag2 = f2$flag) f3$import(f4) stopifnot(identical(f3$bar, f4$bar), identical(f3$flag, f4$flag), identical(f3$b2, f4$b2), identical(f3$flag2, f4$flag2)) ## similar to $import() but using superclass object in the generator call ## The explicitly supplied flag= should override and be allowed ## by the default $initialize() f3b <- foo3(f2, flag = as("Other", "ratedChar"), flag2 = as("More", "ratedChar")) ## check that inherited and direct field assignments worked stopifnot(identical(f3b$tag, f2$tag), identical(f3b$flag, as("Other", "ratedChar")), identical(f3b$flag2, as("More", "ratedChar"))) ## the $new() method should match the generator function f3b <- foo3$new(f2, flag = as("Other", "ratedChar"), flag2 = as("More", "ratedChar")) stopifnot(identical(f3b$tag, f2$tag), identical(f3b$flag, as("Other", "ratedChar")), identical(f3b$flag2, as("More", "ratedChar"))) ## a class with an initialize method, and an extra slot (legal, not a good idea) setOldClass(c("simple.list", "list")) fg4 <- setRefClass("foo4", contains = "foo2", methods = list( initialize = function(...) { .self$initFields(...) .self@made <<- R.version .self }), representation = list(made = "simple.list") ) f4 <- new("foo4", flag = as("another test", "ratedChar"), bar = 1:3) stopifnot(identical(f4@made, R.version)) ## a trivial class with no fields, using fields = list(), failed up to rev 56035 foo5 <- setRefClass("foo5", fields = list(), methods = list(bar = function(test) paste("*",test,"*"))) f5 <- foo5() stopifnot(identical( f5$bar("xxx"), paste("*","xxx", "*"))) ## simple active binding test abGen <- setRefClass("ab", fields = list(a = "ANY", b = function(x) if(missing(x)) a else {a <<- x; x})) ab1 <- abGen(a = 1) stopifnot(identical(ab1$a, 1), identical(ab1$b, 1)) ab1$b <- 2 stopifnot(identical(ab1$a, 2), identical(ab1$b, 2)) ## a simple editor for matrix objects. Method $edit() changes some ## range of values; method $undo() undoes the last edit. mEditor <- setRefClass("matrixEditor", fields = list(data = "matrix", edits = "list"), methods = list( edit = function(i, j, value) { ## the following string documents the edit method 'Replaces the range [i, j] of the object by value. ' backup <- list(i, j, data[i,j]) data[i,j] <<- value edits <<- c(list(backup), edits) invisible(value) }, undo = function() { 'Undoes the last edit() operation and update the edits field accordingly. ' prev <- edits if(length(prev)) prev <- prev[[1]] else stop("No more edits to undo") edit(prev[[1]], prev[[2]], prev[[3]]) ## trim the edits list length(edits) <<- length(edits) - 2 invisible(prev) } )) xMat <- matrix(1:12,4,3) xx <- mEditor(data = xMat) xx$edit(2, 2, 0) xx$data xx$undo() mEditor$help("undo") stopifnot(all.equal(xx$data, xMat)) ## add a method to save the object mEditor$methods( save = function(file) { 'Save the current object on the file in R external object format. ' base::save(.self, file = file) }, counter = function(i) { 'The number of items in the i-th edit. (Used to test usingMethods()) ' if(i > 0 && i <= length(edits)) length(edits[[i]][[3]]) else 0L } ) tf <- tempfile() xx$save(tf) #$ load(tf) unlink(tf) stopifnot(identical(xx$data, .self$data)) ## tests of $trace() methods ## debugging an object xx$trace(edit, quote(xxTrace <<- TRUE)) ## debugging all objects from class mEditor in method $undo() mEditor$trace(undo, quote(mETrace <<- TRUE)) xxTrace <- mETrace <- FALSE xx$edit(2,3,100) xx$undo() ## will not have changed the xx$undo() method (already used) stopifnot(identical(xxTrace, TRUE), identical(mETrace, FALSE)) ## but a new object works the other way around xxTrace <- mETrace <- FALSE xx <- mEditor(data = xMat) xx$edit(2,3,100) xx$undo() stopifnot(identical(xxTrace, FALSE), identical(mETrace, TRUE)) markViewer <- "" setMarkViewer <- function(what) markViewer <<- what ## Inheriting a reference class: a matrix viewer mv <- setRefClass("matrixViewer", fields = c("viewerDevice", "viewerFile"), contains = "matrixEditor", methods = list( view = function() { dd <- dev.cur(); dev.set(viewerDevice) devAskNewPage(FALSE) matplot(data, main = paste("After",length(edits),"edits")) dev.set(dd)}, edit = # invoke previous method, then replot function(i, j, value) { callSuper(i, j, value) view() })) ## initialize and finalize methods mv$methods( initialize = function(file = "./matrixView.pdf", ...) { viewerFile <<- file pdf(viewerFile) viewerDevice <<- dev.cur() message("Plotting to ", viewerFile) dev.set(dev.prev()) setMarkViewer("ON") initFields(...) }, finalize = function() { dev.off(viewerDevice) setMarkViewer("OFF") }) ## a counts method to test usingMethods() mv$methods( counts = function() { usingMethods("counter") sapply(seq_along(edits), "counter") }) ff <- mv( data = xMat) stopifnot(identical(markViewer, "ON")) # check initialize ff$edit(2,2,0) ff$data stopifnot(identical(ff$counts(), length(ff$edits[[1]][[3]]))) ff$undo() stopifnot(all.equal(ff$data, xMat)) rm(ff) gc() stopifnot(identical(markViewer, "OFF")) #check finalize ## tests of copying viewerPlus <- setRefClass("viewerPlus", fields = list( text = "character", viewer = "matrixViewer")) ff <- mv( data = xMat) v1 <- viewerPlus(text = letters, viewer = ff) v2 <- v1$copy() v3 <- v1$copy(TRUE) v2$text <- "Hello, world" v2$viewer$data <- t(xMat) # change a field in v2$viewer v3$text <- LETTERS v3$viewer <- mv( data = matrix(nrow=1,ncol=1)) ## with a deep copy all is protected, with a shallow copy ## the environment of a copied field remains the same, ## but replacing the whole field should be local stopifnot(identical(v1$text, letters), identical(v1$viewer, ff), identical(v2$text, "Hello, world")) v3 <- v1$copy(TRUE) v3$viewer$data <- t(xMat) # should modify v1$viewer as well stopifnot(identical(v1$viewer$data, t(xMat))) ## the field() method stopifnot(identical(v1$text, v1$field("text"))) v1$field("text", "Now is the time") stopifnot(identical(v1$field("text"), "Now is the time")) ## setting a non-existent field, or a method, should throw an error stopifnot(is(tryCatch(v1$field("foobar", 0), error = function(e)e), "error"), is(tryCatch(v1$field("copy", 0), error = function(e)e), "error") ) ## the methods to extract class definition and generator stopifnot(identical(v3$getRefClass()$def, getRefClass("viewerPlus")$def), identical(v3$getClass(), getClass("viewerPlus"))) ## deal correctly with inherited methods and overriding existing ## methods from $methods(...) refClassA <- setRefClass("refClassA", methods=list(foo=function() "A")) refClassB <- setRefClass("refClassB", contains="refClassA") mnames <- objects(getClass("refClassB")@refMethods) refClassB$methods(foo=function() callSuper()) stopifnot(identical(refClassB()$foo(), "A")) mnames2 <- objects(getClass("refClassB")@refMethods) stopifnot(identical(mnames2[is.na(match(mnames2,mnames))], "foo#refClassA")) refClassB$methods(foo=function() paste(callSuper(), "Version 2")) stopifnot(identical(refClassB()$foo(), "A Version 2")) stopifnot(identical(mnames2, objects(getClass("refClassB")@refMethods))) if(methods:::.hasCodeTools()) { ## code warnings assigning locally to field names stopifnot(is(tryCatch(mv$methods(test = function(x) { data <- x[!is.na(x)]; mean(data)}), warning = function(e)e), "warning")) ## warnings for nonlocal assignment that is not a field stopifnot(is(tryCatch(mv$methods(test2 = function(x) {something <<- data[!is.na(x)]}), warning = function(e)e), "warning")) ## error for trying to assign to a method name stopifnot(is(tryCatch(mv$methods(test3 = function(x) {edit <<- data[!is.na(x)]}), error = function(e)e), "error")) } else warning("Can't run some tests: recommended package codetools is not available") ## tests (fragmentary by necessity) of promptClass for reference class ccon <- textConnection("ctxt", "w") suppressMessages(promptClass("refClassB", filename = ccon)) ## look for a method, inheritance, inherited method stopifnot(length(c(grep("foo.*refClassA", ctxt), grep("code{foo()}", ctxt, fixed = TRUE), grep("linkS4class{refClassA", ctxt, fixed = TRUE))) >= 3) close(ccon) rm(ctxt) ## tests related to subclassing environments. These really test code in the core, viz. builtin.c a <- refClassA() ev <- new.env(parent = a) # parent= arg stopifnot(is.environment(ev)) foo <- function()"A"; environment(foo) <- a # environment of function stopifnot(identical(as.environment(a), environment(foo))) xx <- 1:10; environment(xx) <- a # environment attribute stopifnot(identical(as.environment(a), environment(xx))) ## tests of [[<- and $<- for subclasses of environment. At one point ## methods for these assignments were defined and caused ## inf. recursion when the arguments to the [[<- case were changed in base. setClass("myEnv", contains = "environment") m <- new("myEnv", a="test") m2 <- new("myEnv"); m3 <- new("myEnv") ## test that new.env() is called for each new object stopifnot(!identical(as.environment(m), as.environment(m2)), !identical(as.environment(m3), as.environment(m2))) m[["x"]] <- 1; m$y <- 2 stopifnot(identical(c(m[["x"]], m$y), c(1,2)), is(m, "myEnv")) rm(x, envir = m) # check rm() works, does not clobber class stopifnot(identical(sort(objects(m)), sort(c("a", "y"))), is(m, "myEnv")) ## tests of binding & environment tools with subclases of environment lockBinding("y", m) stopifnot(bindingIsLocked("y", m)) unlockBinding("y", m) stopifnot(!bindingIsLocked("y", m)) makeActiveBinding("z", function(value) { if(missing(value)) "dummy" else "dummy assignment" }, m) stopifnot(identical(get("z", m),"dummy")) ## assignment will return the value but do nothing stopifnot(identical(assign("z","other", m), "other"), identical(get("z", m),"dummy")) ## this has to be last--Seems no way to unlock an environment! lockEnvironment(m) stopifnot(environmentIsLocked(m)) rm(m) m <- new("myEnv") stopifnot(length(ls(m)) == 0) ## used to contain the previous content ## test of callSuper() to a hidden default method for initialize() (== initFields) TestClass <- setRefClass ("TestClass", fields = list (text = "character"), methods = list( print = function () {cat(text)}, initialize = function(text = "", ...) callSuper(text = paste(text, ":", sep=""),...) )) tt <- TestClass("hello world") stopifnot(identical(tt$text, "hello world:")) ## now a subclass with another field & another layer of callSuper() TestClass2 <- setRefClass("TestClass2", contains = "TestClass", fields = list( version = "integer"), methods = list( initialize = function(..., version = 0L) callSuper(..., version = version+1L)) ) tt <- TestClass2("test", version = 1L) stopifnot(identical(tt$text, "test:"), identical(tt$version, as.integer(2))) tt <- TestClass2(version=3L) # default text stopifnot(identical(tt$text, ":"), identical(tt$version, as.integer(4))) ## test some capabilities but read-only for .self .changeAllFields <- function(replacement) { fields <- names(.refClassDef@fieldClasses) for(field in fields) eval(substitute(.self$FIELD <- replacement$FIELD, list(FIELD = field))) } mEditor$methods(change = .changeAllFields) xx <- mEditor(data = xMat) xx$edit(2, 2, 0) yy <- mEditor(data = xMat+1) yy$change(xx) stopifnot(identical(yy$data, xx$data), identical(yy$edits, xx$edits)) ## but don't allow assigment if(methods:::.hasCodeTools()) stopifnot(is(tryCatch(yy$.self$data <- xMat, error = function(e)e), "error")) ## the locked binding of refGeneratorSlot class should prevent modifying ## methods, locking fields or setting accessor methods ## Nothing special about refGeneratorSlot in this test -- the point is just ## to use a standard reference class known to be defined in a package evr <- getRefClass("refGeneratorSlot") # in methods stopifnot(is(tryCatch(evr$methods(foo = function()"..."), error = function(e)e), "error"), is(tryCatch(evr$lock("def"), error = function(e)e), "error"), is(tryCatch(evr$accessors("def"), error = function(e)e), "error")) ##getRefClass() method and function should work with either ## a class name or a class representation (bug report 14600) tg <- setRefClass("tg", fields = "a") t1 <- tg(a=1) tgg <- t1$getRefClass() tggg <- getRefClass("tg") stopifnot(identical(tgg$def, tggg$def), identical(tg$def, tgg$def)) ## this used to fail in initFieldArgs() from partial matching "self" selfClass <- setRefClass("selfClass", fields=list( self="character", super="character", sub="character" ) ) stopifnot(identical(selfClass(self="B", super="A", sub="C")$self, "B"))