pax_global_header00006660000000000000000000000064141207260500014507gustar00rootroot0000000000000052 comment=edb30c8f929f945f5d9cb632c79e27509e6a7400 r-cran-svunit-1.0.6/000077500000000000000000000000001412072605000142235ustar00rootroot00000000000000r-cran-svunit-1.0.6/DESCRIPTION000066400000000000000000000016631412072605000157370ustar00rootroot00000000000000Package: svUnit Type: Package Version: 1.0.6 Date: 2021-04-18 Title: 'SciViews' - Unit, Integration and System Testing Description: A complete unit test system and functions to implement its GUI part. Authors@R: c(person("Philippe", "Grosjean", role = c("aut", "cre"), email = "phgrosjean@sciviews.org", comment = c(ORCID = "0000-0002-2694-9471"))) Maintainer: Philippe Grosjean Depends: R (>= 1.9.0) Imports: utils Suggests: svGUI, datasets, XML, RUnit, knitr, markdown, covr, spelling License: GPL-2 URL: https://github.com/SciViews/svUnit, https://www.sciviews.org/svUnit/ BugReports: https://github.com/SciViews/svUnit/issues RoxygenNote: 7.1.0 VignetteBuilder: knitr Encoding: UTF-8 Language: en-US NeedsCompilation: no Packaged: 2021-04-18 19:35:28 UTC; phgrosjean Author: Philippe Grosjean [aut, cre] () Repository: CRAN Date/Publication: 2021-04-19 05:20:02 UTC r-cran-svunit-1.0.6/MD5000066400000000000000000000043751412072605000145440ustar00rootroot00000000000000d101ae2cedacca64744cce3d80e28b05 *DESCRIPTION b3996625655f4add3845c5ef0eb4ceb2 *NAMESPACE c88cff83faa2e27fa647969ae669e71e *NEWS.md 46baace407a56e41d259d195ab45f6ec *R/Log.R 8b85b9616cd0bbfb6a3a66b871f7c65a *R/check.R af067a0b36f04ceea9f8f7a9234c5e39 *R/guiTestReport.R e293f9d0659ff04bd66fb94b9c80a932 *R/koUnit.R 1f81a072d496a5009fe5912045e41da9 *R/runExamples.R 256acfe8726db2c9dd5695b086ffa3d0 *R/svSuite.R c1799fbb6cd6197485f142e892595985 *R/svSuiteData.R bdcd69e1c32a678d2a6a2bfe511cedc7 *R/svTest.R 538e1b7b72c1358b9536c174757ff3e4 *R/svTestData.R 66f55a7b9fd3a83ed5c3d4b29bbfc167 *R/svUnit-internal.R aa2e1e7d2f95d1850d20f724cd5dbb1b *R/svUnit-package.R 40d76c927c709d19cffe0db6c4e6af46 *R/unitTests.svUnit.R 8e85839654725a1408197e2ae2ec6467 *build/vignette.rds 7c049c8753cca079b4ec3d56b9d3af24 *inst/CITATION ac53e684c493ed108e4ad32d54151556 *inst/TestsvUnit.R 1055b8687cb621a530f78d17c9b6f2cd *inst/WORDLIST 1d712dff2b2a630dd98900c430901f36 *inst/doc/svUnit.R 7eca48eb0c08b222609072c3d0750ee3 *inst/doc/svUnit.Rmd ae964a63994b0d55cbcd5ff4097f7a4f *inst/doc/svUnit.html 739d0bb836670322cdc674a76b2e290f *inst/komodo/sciviewskunit-ko.xpi 5aa6145efd4d3877c713256626a5c14e *inst/unitTests/BadTests/runitBadTests.R 74d47091753bf9c5f7619298d490acf8 *inst/unitTests/VirtualClass/runitVirtualClass.R 14d594093c3ac66eeeac3dd28dd9b2a4 *inst/unitTests/runitsvSuite.R 48d3c1f82337f96b4af759ca80f42e02 *inst/unitTests/runitsvTest.R 9ca6d3fe50320aafb98223a53007b89b *man/Log.Rd 53c21252907fa15202d133cb8bef6d60 *man/check.Rd 977e7af33bd1458d02e358fdecabd6bb *man/guiTestReport.Rd b54eb5787908025cff294f8223fb5c76 *man/koUnit.Rd 59e68aac9705ed00b084ba88062441f2 *man/svSuite.Rd 4067ca9eec0dd8314bee33e5dd39fe74 *man/svSuiteData.Rd ffb15fd82a704c485279b4ddb5ac38b7 *man/svTest.Rd 9af2cc06608a310fe7ccd312618d8cd4 *man/svTestData.Rd 69eb5e6ccf9cd57836f7b9f2c0cc7c38 *man/svUnit-package.Rd f1fa70b40d9b89c5fc37fcbf7208a2b7 *man/unitTests.svUnit.Rd 0622a97a2aaa3c342f09636052c2d7f5 *tests/spelling.R 317654b3b6653bb64415bee035119c08 *tests/test-with-svUnit.R d4ba0e2d404b65f9ab03a282e0736a52 *tests/testthat/test.svUnit.R 7eca48eb0c08b222609072c3d0750ee3 *vignettes/svUnit.Rmd e73dad2b6c4bbf6e00dc450ba597688c *vignettes/svUnit_KomodoRUnit.png 86482748de8fc54793d2d85db99f1872 *vignettes/svUnit_wikiReport.png r-cran-svunit-1.0.6/NAMESPACE000066400000000000000000000027551412072605000154530ustar00rootroot00000000000000# Generated by roxygen2: do not edit by hand S3method(makeUnit,default) S3method(makeUnit,svSuite) S3method(makeUnit,svTest) S3method(metadata,svSuiteData) S3method(print,svSuite) S3method(print,svSuiteData) S3method(print,svTest) S3method(print,svTestData) S3method(protocol,default) S3method(protocol,svSuiteData) S3method(protocol_junit,svSuiteData) S3method(protocol_junit,svTestData) S3method(protocol_text,svSuiteData) S3method(runTest,default) S3method(runTest,list) S3method(runTest,svSuite) S3method(runTest,svTest) S3method(stats,svSuiteData) S3method(stats,svTestData) S3method(summary,svSuiteData) S3method(summary,svTestData) export("test<-") export(DEACTIVATED) export(Log) export(as.svSuite) export(as.svTest) export(checkEquals) export(checkEqualsNumeric) export(checkException) export(checkIdentical) export(checkTrue) export(clearLog) export(createLog) export(errorLog) export(guiSuiteAutoList) export(guiSuiteList) export(guiTestFeedback) export(guiTestReport) export(is.svSuite) export(is.svSuiteData) export(is.svTest) export(is.svTestData) export(is.test) export(koUnit_isAutoTest) export(koUnit_runTest) export(koUnit_setAutoTest) export(koUnit_showRUnitPane) export(koUnit_version) export(lastSuite) export(lastTest) export(makeTestListFromExamples) export(makeUnit) export(metadata) export(protocol) export(protocol_junit) export(protocol_text) export(runTest) export(stats) export(svSuite) export(svSuiteList) export(svTest) export(test) importFrom(utils,sessionInfo) importFrom(utils,str) r-cran-svunit-1.0.6/NEWS.md000066400000000000000000000123641412072605000153270ustar00rootroot00000000000000# svUnit 1.0.6 - A few more URLs corrections (for CRAN)! # svUnit 1.0.5 - Correction of a few URLs in the vignette. # svUnit 1.0.4 - Minor change: markdown added in "suggests". Komodo Edit is now Komodo IDE. # svUnit 1.0.3 - During the conversion to Roxygen2, the topic 'svUnit' was lost. Corrected. - A new 'pkgdown' web site is added at https://www.sciviews.org/svUnit/. # svUnit 1.0.2 - http://www.r-project.org changed into https://www.r-project.org in svUnit vignette (required by CRAN). # svUnit 1.0.1 - Corrections in URLs in the svUnit vignettes (canonical forms for CRAN packages like https://CRAN.R-project.org/package= + another buggy URL). # svUnit 1.0.0 - No further problems found in two years => version bumped to 1.0.0. - `NEWS` file reworked and renamed `NEWS.md`. - The vignette is reworked into an R Markdown format. - The functions documentation is reworked in roxygen2 format (v7). - A pkgdown site is created to better document the svUnit package. # svUnit 0.7-13 - Development moved to Github; Travis CI and appveyor added. - Dependency to optional XML package is now managed correctly through `requireNamespace()` and `XML::xxx()` calls. # svUnit 0.7-12 - Rework of Author field in `DESCRIPTION` file. # svUnit 0.7-11 - Temporary objects are now saved in `SciViews:TempEnv` instead of `TempEnv`. - `runTest.svSuite()` does not create `.TestSuiteEnv` into `.GlobalEnv` any more. This object is now stored in `SciViews:TempEnv` (CRAN does not allow to assign in `.GlobalEnv`). # svUnit 0.7-10 - In `runTest.svSuite()`, a `<<-` assignation is replaced by `assign(..., envir = .GlobalEnv)`. - Vignette svUnit is now moved to `\vignettes` subdirectory. `DESCRIPTION` and `NEWS` files are reworked. # svUnit 0.7-9 - Vignette svUnit translated into LyX 2.0-0. # svUnit 0.7-8 - Option added to skip instead of fail missing documentation examples. # svUnit 0.7-7 - The test list was only repeatedly running the last test defined. Fixed. # svUnit 0.7-6 - Refer to last test environment through a local identifier. Closes #1327. - Strip attributes from context fields when saving them temporarily. # svUnit 0.7-5 - XML-encoding entities in `protocol_junit.svTestData()`. Closes #1147. # svUnit 0.7-4 - Backquotes set to test name in evaluation instruction, allowing to use syntactically incorrect names for tests. # svUnit 0.7-3 - Loading of svUnit sometimes failed during checking of the SciViews-K Unit plugin installation in Komodo Edit/IDE. Corrected. Thanks Claudia Beleites. - The `unitTest.svUnit` man page now uses `require(svUnit)` in a mechanism that ignores the tests in case svUnit is not installed in a machine where R CMD check is run. - `errorLog()` now briefly reports statistics on all test run when used in `interactive()` mode. This is more appropriate when the tests are run by using `example(unitTests.)`, while it does not change original behavior (silent execution of the tests, except in case of failure or error) during the R CMD check process. - The package vignette is updated to reflect these changes. # svUnit 0.7-2 - Added a `unitname=` argument in `runTest.svSuite()` to select one test unit to run in the test suite. Thanks to Thomas Wutzler for submitting this patch. # svUnit 0.7-1 - Upgrade to the Komodo SciViews-K Unit plugin version 0.7-1. # svUnit 0.7-0 - Typo correction in `guiTestReport.Rd`. # svUnit 0.6-3 - The task callback mechanism introduced in svSocket 0.9-48 is now used to all running the task after R code send by socket clients is executed. # svUnit 0.6-2 - There is now a vignette for the svUnit package. # svUnit 0.6-1 - Correction of a bug in Windows relative to path separators `\\` versus `/`, especially using `tempdir()`. - Correction of a bug that prevented to list`runit*.R` files in the unitTests subdirectory of a package under Windows. # svUnit 0.6-0 - The package does not depends any more on RUnit. It has his own `checkXXX()` function (they are compatible with those in RUnit 0.4.17, except that here the `checkTrue()` function is vectorized, but they operate very differently), - svUnit functions and objects are renamed `svSuite`, and there is a reworking of objects to end with `svTest`, `svSuite`, `svTestData` and `svSuiteData`, and many new methods for those objects (`print()`, `summary()`, `stats()`, `metadata()`, `makeUnit()`, `runTest()`). - `print.svSuiteData()` and `summary.svSuiteData()` are completely reworked. - `svSuiteList()` now accepts additional, dirs, manages an exclusion list and has an argument `loadPackages =` to force loading packages provided in the list. - Names for test units is now `runit.R`, like in the RUnit package. Same for the test functions which are `test.R` (used to be `runit..R` and `test..R`). - The temporary directory is now emptied from old `runit*.R` files before making new ones, but not any more after running tests. That way, we avoid running old test definitions, while keeping the latest one available for inspection. - `koUnit_xxx()` functions have been added to manipulate the R Unit GUI in Komodo from within R. # svUnit 0.5-0 - Added `guiTestFeedback()` and `guiTestReport()` functions. - A couple of other little changes. # svUnit 0.4-0 - First version compiled as a package and distributed on R-Forge. r-cran-svunit-1.0.6/R/000077500000000000000000000000001412072605000144245ustar00rootroot00000000000000r-cran-svunit-1.0.6/R/Log.R000066400000000000000000000122361412072605000152740ustar00rootroot00000000000000#' SciViews-R log management functions #' #' These functions define the code of test functions. They are designed to check #' the result of some test calculation. #' #' @param description A (short) character string describing this test suite log. #' @param deleteExisting Do we delete an existing a `.Log` object already #' defined in `.GlobalEnv` (no, by default)? #' @param stopit Do we issue an error ([stop()] in case of any error or failure? #' This is particularly useful if you want to interrupt R CMD check on packages, #' when you included one or more test suites in examples (see `?unitTests`). #' @param summarize Should the summary of the log be printed in case we stop #' execution of the code when an error is found (see `stopit =` argument. It is, #' indeed, useful to indicate at this time which tests failed or raised an #' error. So, this argument should usually be left at its default value. #' #' @return #' [Log()] and [createLog()] return the `.Log` object defined in `.GlobalEnv` by #' reference (it is indeed an environment). So, you can use its content (and #' change it, if you write functions to manipulate this log). #' #' [clearLog()] return invisibly `TRUE` or `FALSE`, depending if an existing log #' object was deleted or not. #' #' [errorLog()] is mainly used for its side-effect of stopping code execution #' and/or printing a summary of the test runs in the context of example #' massaging in R CMD check (see the "Writing R extensions" manual). However, #' this function also returns invisibly a contingency table with the number of #' successes, failures, errors and deactivated tests recorded so far. #' #' [lastTest()] and [lastSuite()] recall results of last test and last suite #' run, respectively. #' #' @details #' svUnit records results of assertions (using the checkxxx() functions) in a #' 'svSuiteData' object named `.Log` and located in .GlobalEnv. Hence, this log #' is easy to access. However, in order to avoid errors in your code in case #' this object was deleted, or not created, it is better to access it using #' [Log()] which take care to create the object if it is missing. #' #' @export #' @importFrom utils sessionInfo str #' @author Philippe Grosjean #' @seealso [svSuiteData()], [svSuite()], [svTest()], [checkEquals()] #' @keywords utilities #' @concept unit testing #' @examples #' clearLog() # Clear the svUnit log #' #' # Two correct tests #' (checkTrue(1 < 2)) #' (checkException(log("a"))) #' errorLog() # Nothing, because there is no error #' #' \dontrun{ #' (checkTrue(1 > 2)) # This test fails #' lastTest() # Print results of last test #' errorLog() # Stop and summarize the tests run so far #' } #' #' clearLog() Log <- function(description = NULL) { if (!exists(".Log", envir = .GlobalEnv, inherits = FALSE)) createLog(description = description) get(".Log", envir = .GlobalEnv, inherits = FALSE) } #' @export #' @rdname Log createLog <- function(description = NULL, deleteExisting = FALSE) { # Create a log consisting in an environment with class svSuiteData if (isTRUE(deleteExisting) && exists(".Log", envir = .GlobalEnv, inherits = FALSE)) rm(.Log, envir = .GlobalEnv) if (!exists(".Log", envir = .GlobalEnv, inherits = FALSE)) { .Log <<- structure(new.env(parent = .GlobalEnv), class = c("svSuiteData", "environment")) # Add general informations and variables to it .Log$.time <- Sys.time() # Creation time of the log .Log$.R.version <- R.version # R version .Log$.sessionInfo <- sessionInfo() # Information about current session .Log$.description <- description # Optional description of this log # Create ..xxx variables used for test context # Note: never delete or put NULL in these variables, use "" instead .Log$..Unit <- "" .Log$..Msg <- "" .Log$..Obj <- "" .Log$..File <- "" .Log$..Tag <- "" # Create .lastTest that contains details from last check...() naChr <- as.character(NA) .Log$.lastTest <- structure( data.frame(msg = naChr, call = naChr, timing = as.numeric(NA), kind = .kind(NA), res = naChr, obj = naChr, file = naChr, tag = naChr, stringsAsFactors = FALSE), class = c("svTestData", "data.frame")) # Create .lastSuite with an empty list of test units to run .Log$.lastSuite <- list() } } #' @export #' @rdname Log clearLog <- function() { if (exists(".Log", envir = .GlobalEnv, inherits = FALSE)) { rm(list = ".Log", envir = .GlobalEnv) res <- TRUE } else { res <- FALSE } invisible(res) } #' @export #' @rdname Log errorLog <- function(stopit = TRUE, summarize = TRUE) { .Log <- Log() Res <- table(stats(.Log)$kind) if (isTRUE(stopit) && any(Res[2:3] > 0)) { if (isTRUE(summarize)) summary(.Log) msg <- paste(Res[2], "failure(s) and", Res[3], "error(s)") stop(msg) } else if (interactive()) { cat("Summary statistics on all tests run:\n") print(Res) } invisible(Res) } #' @export #' @rdname Log lastTest <- function() { # Return a svTestData object with data from last recorded test Log()$.lastTest } #' @export #' @rdname Log lastSuite <- function() { # Return data about last suite run Log()$.lastSuite } r-cran-svunit-1.0.6/R/check.R000066400000000000000000000226431412072605000156330ustar00rootroot00000000000000#' SciViews-R Unit assertions (check functions) #' #' These functions define the assertions in test functions. They are designed to #' check the result of some test calculation. #' #' @param target a target object as reference for comparison. #' @param current An object created for comparison (not an S4 class object). #' @param msg An optional (short!) message to document a test. This message is #' stored in the log and printed in front of each test report. #' @param tolerance numeric >= 0. A numeric check does not fail if differences #' are smaller than 'tolerance'. #' @param checkNames Flag, if `FALSE` the names attributes are set to `NULL` for #' both `current` and `target` before performing the check. #' @param expr Syntactically valid R expression which can be evaluated and must #' return a logical vector (`TRUE`|`FALSE`). A named expression is also allowed #' but the name is disregarded. In [checkException()], expr is supposed to #' generate an error to pass the test. #' @param silent Flag passed on to try, which determines if the error message #' generated by the checked function is displayed at the R console. By default, #' it is `FALSE`. #' @param ... Optional arguments passed to [all.equal()] or #' [all.equal.numeric()]. #' #' @return #' These function return `TRUE` if the test succeeds, `FALSE` if it fails, #' possibly with a 'result' attribute containing more information about the #' problem. This is very different from corresponding functions in 'RUnit' that #' stop with an error in case of test failure. Consequently, current functions #' do not require the complex evaluation framework designed in 'RUnit' for that #' reason. #' #' @details #' These check functions are equivalent to various methods of the class #' junit.framework.Assert of Java junit framework. They should be #' code-compatible with functions of same name in 'RUnit' 0.4.17, except #' for [checkTrue()] that is vectorized here, but accept only a scalar result in #' 'RUnit'. For scalar test, the behavior of the function is the same in both #' packages. #' See [svTest()] for examples of use of these functions in #' actual test cases attached to R objects. #' See also the note about S4 objects in the [RUnit::checkTrue()] online help of the #' 'RUnit' package. #' #' @export #' @name check #' @author Written by Ph. Grosjean, inspired from the general design of the #' 'RUnit' package by Thomas Konig, Klaus Junemann & Matthias Burger. #' @seealso [svTest()], [Log()], [guiTestReport()], [RUnit::checkTrue] #' @keywords utilities #' @concept unit testing #' @examples #' clearLog() # Clear the svUnit log #' #' # All these tests are correct #' (checkEquals(c("A", "B", "C"), LETTERS[1:3])) #' (checkEqualsNumeric(1:10, seq(1, 10))) #' (checkIdentical(iris[1:50, ], iris[iris$Species == "setosa",])) #' (checkTrue(1 < 2)) #' (checkException(log("a"))) #' Log() # See what's recorded in the log #' #' # ... but these ones fail #' (checkEquals("A", LETTERS[1:3])) #' (checkEqualsNumeric(2:11, seq(1, 10))) #' (checkIdentical(iris[1:49, ], iris[iris$Species == "setosa",])) #' (checkTrue(1 > 2)) #' (checkException(log(1))) #' Log() # See what's recorded in the log #' #' # Create a test function and run it #' foo <- function(x, y = 2) #' return(x * y) #' test(foo) <- function() { #' #DEACTIVATED() #' checkEqualsNumeric(5, foo(2)) #' checkEqualsNumeric(6, foo(2, 3)) #' checkTrue(is.test(foo)) #' checkTrue(is.test(test(foo))) #' checkIdentical(test(foo), attr(foo, "test")) #' checkException(foo("b")) #' checkException(foo(2, "a")) #' } #' (runTest(foo)) #' #' # Of course, everything is recorded in the log #' Log() #' #' clearLog() checkEquals <- function(target, current, msg = "", tolerance = .Machine$double.eps^0.5, checkNames = TRUE, ...) { val <- FALSE timing <- as.numeric(system.time({ ret <- try({ # Run the test if (isTRUE(checkNames)) { cn <- "" # Since this is the default value } else { cn <- ", checkNames = FALSE" names(target) <- NULL names(current) <- NULL } if (!is.numeric(tolerance)) stop("tolerance has to be a numeric value") if (length(tolerance) != 1) stop("tolerance has to be a scalar") res <- all.equal(target, current, tolerance = tolerance, ...) val <- isTRUE(res) }, silent = TRUE) }, gcFirst = FALSE)[3]) # Log this test test <- .logTest(timing) # Decide if recording more info or not minTiming <- getOption("svUnit.minTiming") if (is.null(minTiming)) minTiming <- 0.1 if (!isTRUE(getOption("svUnit.recordAll")) && isTRUE(timing < minTiming) && val) return(invisible(TRUE)) # Check for error if (inherits(ret, "try-error")) { val <- NA .logTestData(test, msg = msg, call = deparse(sys.call()[1:3], nlines = 1), timing = timing, val = -1, res = as.character(ret)) } else { .logTestData(test, msg = msg, call = deparse(sys.call()[1:3], nlines = 1), timing = timing, val = val, res = if (val) "" else paste(c(res, .formatResult(current)), collapse = "\n")) } invisible(val) } #' @export #' @rdname check checkEqualsNumeric <- function(target, current, msg = "", tolerance = .Machine$double.eps^0.5, ...) { val <- FALSE timing <- as.numeric(system.time({ ret <- try({ # Run the test if (!is.numeric(tolerance)) stop("tolerance has to be a numeric value") if (length(tolerance) != 1) stop("tolerance has to be a scalar") res <- all.equal.numeric(as.vector(target), as.vector(current), tolerance = tolerance, ...) val <- isTRUE(res) }, silent = TRUE) }, gcFirst = FALSE)[3]) # Log this test test <- .logTest(timing) # Decide if recording more info or not minTiming <- getOption("svUnit.minTiming") if (is.null(minTiming)) minTiming <- 0.1 if (!isTRUE(getOption("svUnit.recordAll")) && isTRUE(timing < minTiming) && val) return(invisible(TRUE)) # Check for error if (inherits(ret, "try-error")) { val <- NA .logTestData(test, msg = msg, call = deparse(sys.call()[1:3], nlines = 1), timing = timing, val = -1, res = as.character(ret)) } else { .logTestData(test, msg = msg, call = deparse(sys.call()[1:3], nlines = 1), timing = timing, val = val, res = if (val) "" else paste(c(res, .formatResult(current)), collapse = "\n")) } invisible(val) } #' @export #' @rdname check checkIdentical <- function(target, current, msg = "") { val <- FALSE timing <- as.numeric(system.time({ ret <- try({ # Run the test val <- identical(target, current) }, silent = TRUE) }, gcFirst = FALSE)[3]) # Log this test test <- .logTest(timing) # Decide if recording more info or not minTiming <- getOption("svUnit.minTiming") if (is.null(minTiming)) minTiming <- 0.1 if (!isTRUE(getOption("svUnit.recordAll")) && isTRUE(timing < minTiming) && val) return(invisible(TRUE)) # Check for error if (inherits(ret, "try-error")) { val <- NA .logTestData(test, msg = msg, call = deparse(sys.call()[1:3], nlines = 1), timing = timing, val = -1, res = as.character(ret)) } else { .logTestData(test, msg = msg, call = deparse(sys.call()[1:3], nlines = 1), timing = timing, val = val, res = .formatResult(current)) } invisible(val) } #' @export #' @rdname check checkTrue <- function(expr, msg = "") { val <- FALSE timing <- as.numeric(system.time({ ret <- try({ # Run the test val <- isTRUE(all(expr == TRUE)) }, silent = TRUE) }, gcFirst = FALSE)[3]) # Log this test test <- .logTest(timing) # Decide if recording more info or not minTiming <- getOption("svUnit.minTiming") if (is.null(minTiming)) minTiming <- 0.1 if (!isTRUE(getOption("svUnit.recordAll")) && isTRUE(timing < minTiming) && val) return(invisible(TRUE)) # Get call, without msg call <- sys.call() call <- deparse(call[names(call) != "msg"]) # Check for error if (inherits(ret, "try-error")) { val <- NA .logTestData(test, msg = msg, call = deparse(sys.call()[1:2], nlines = 1), timing = timing, val = -1, res = as.character(ret)) } else { .logTestData(test, msg = msg, call = deparse(sys.call()[1:2], nlines = 1), timing = timing, val = val, res = .formatResult(expr)) } invisible(val) } #' @export #' @rdname check checkException <- function(expr, msg = "", silent = getOption("svUnit.silentException")) { val <- FALSE timing <- as.numeric(system.time({ ret <- try({ # Run the test silent <- (is.null(silent) || isTRUE(silent)) val <- inherits(res <- try(expr, silent = silent), "try-error") }, silent = TRUE) }, gcFirst = FALSE)[3]) # Log this test test <- .logTest(timing) # Decide if recording more info or not minTiming <- getOption("svUnit.minTiming") if (is.null(minTiming)) minTiming <- 0.1 if (!isTRUE(getOption("svUnit.recordAll")) && isTRUE(timing < minTiming) && val) return(invisible(TRUE)) # Check for error if (inherits(ret, "try-error")) { val <- NA .logTestData(test, msg = msg, call = deparse(sys.call()[1:2], nlines = 1), timing = timing, val = -1, res = as.character(ret)) } else { .logTestData(test, msg = msg, call = deparse(sys.call()[1:2], nlines = 1), timing = timing, val = val, res = if (val) paste(res, collapse = "\n") else "No exception generated!\n") } invisible(val) } #' @export #' @rdname check DEACTIVATED <- function(msg = "") stop(msg) r-cran-svunit-1.0.6/R/guiTestReport.R000066400000000000000000000144451412072605000173770ustar00rootroot00000000000000#' Report or give feedback to the GUI client about running test units #' #' These functions are usually not called from the command line. They return #' data to compatible GUI clients, like Komodo Edit with the SciViews-K #' extension. #' #' @param object a 'svUnitData' object. #' @param sep Field separator to use in the results. #' @param path Path where to write a 'Suites.txt' file with the list of #' currently available test suites (to be used by the GUI client). If `NULL`, #' no file is written (by default). #' @param ... Not used currently. #' @param compare Do we compare the list of available test suite and return #' something to the GUI client only if there are changes in the list? This is #' used (when `TRUE`) to avoid unnecessary multiple processing of the same list #' by the GUI client. #' #' @return #' [guiSuiteList()] returns the list of available test suites invisibly. #' [guiSuiteAutoList()] is used to establish a callback to automatically list #' the available test suites in the GUI. It is not intended to be called #' directly by the user. The other functions just return `TRUE` invisibly.They #' are used for their side effect of sending data to compatible GUI clients. #' #' @export #' @author Philippe Grosjean #' @seealso [svTest()], [svSuite()], [koUnit_version()] #' @keywords utilities #' @concept unit testing guiTestReport <- function(object, sep = "\t", path = NULL, ...) { # Report the results of a test to the GUI client if (!is.svSuiteData(object)) stop("'object' must be a 'svSuiteData' object") # For all 'svTestData' objects, create a table with test results for the GUI # Indicate global results of the Unit Test Tests <- ls(object) if (length(Tests) == 0) { Res <- "<<>>|||0|||0|||0|||0" } else { # Get general information about the tests Stats <- stats(object) Tests <- rownames(Stats) # To make sure we use the same! Stats$label <- paste(">", sub("^test", "", Tests), " (", round(Stats$timing, 3), " sec)", sep = "") State <- table(Stats$kind) Res <- paste("<<>>|||", State[1], "|||", State[2], "|||", State[3], "|||", State[4], sep = "") Kinds <- as.numeric(Stats$kind) Kinds[Kinds == 4] <- 0 # Use 0 instead of 4 for deactivated tests Stats$kind <- Kinds # Get the type for the objects Units <- Stats$unit Types <- rep("units in packages", length(Units)) Types[Units == ""] <- "other objects" # TODO: include also dirs! Dir1 <- gsub("\\\\", "/", dirname(Units)) Dir2 <- dirname(Dir1) Dir3 <- dirname(Dir2) TempDir <- gsub("\\\\", "/", tempdir()) Types[Dir1 == TempDir] <- "objects in .GlobalEnv" Types[tolower(basename(Dir2)) == "inst" || tolower(basename(Dir3)) == "inst"] <- "units in sources" # Keep only "*" in Units Units <- basename(Units) Units[regexpr("^runit.+\\.[rR]$", Units) == -1] <- "" Units[Dir1 == TempDir] <- "" # No second level for objects in .GlobalEnv Units <- sub("^runit(.+)\\.[rR]$", "\\1", Units) change <- Units != "" Units[change] <- paste(">unit", Units[change]) # Complete label is Type 0) { Result <- ifelse(tData$res == "", "", paste("\n", tData$res, sep = "")) Info <- paste(Info, "\n", paste("* ", tData$msg, ": ", tData$call, .formatTime(tData$timing, secDigits = 3), " ... ", as.character(tData$kind), Result, sep = "", collapse = "\n"), sep = "") } # Calculate URI (currently, the name of the unit file # and the name of the test function) if (Data$unit == "") { URI <- Data$unit } else { URI <- paste(Data$unit, Test, sep = "#") } if (Data$unit != lastUnit) { lastUnit <- Data$unit Res <- c(Res, paste("<<>>|||", Data$unit, "|||||||||", sep = "")) } Res <- c(Res, paste("<<>>|||", Data$label, "|||", Data$kind, "|||", Info, "|||", URI, sep = "")) } } Res <- paste(gsub("\t", " ", Res), collapse = sep) if (is.null(path)) { return(Res) } else { cat(Res, file = path) } path } #' @export #' @rdname guiTestReport guiSuiteList <- function(sep = "\t", path = NULL, compare = TRUE) { Suites <- svSuiteList() if (compare) { oldSuites <- .getTemp(".guiSuiteListCache", default = "") # Compare both versions if (!identical(Suites, oldSuites)) { # Keep a copy of the last version in SciViews:TempEnv .assignTemp(".guiSuiteListCache", Suites) Changed <- TRUE } else Changed <- FALSE } else { Changed <- TRUE .assignTemp(".guiSuiteListCache", Suites) } if (is.null(path)) {# Return result, as a single character string with sep if (Changed) { if (!is.null(sep)) Suites <- paste(Suites, collapse = sep) return(Suites) } else { return(NULL) } } else {# Write to a file called 'Suites.txt' in this path file <- file.path(path, "Suites.txt") if (Changed) { if (is.null(sep)) sep <- "\n" cat(Suites, sep = sep, file = file) } return(invisible(Changed)) } } #' @export #' @rdname guiTestReport guiSuiteAutoList <- function(...) { # Is koCmd() available? if (!exists("koCmd", mode = "function")) return(TRUE) # Is it something changed in the unit list? res <- guiSuiteList(sep = ",", path = NULL, compare = TRUE) if (!is.null(res)) ret <- get("koCmd")('sv.r.unit.getRUnitList_Callback("<<>>");', data = res) return(TRUE) } #' @export #' @rdname guiTestReport guiTestFeedback <- function(object, path = NULL, ...) { # Give feedback to client about the currently running tests # TODO: feedback about test run } r-cran-svunit-1.0.6/R/koUnit.R000066400000000000000000000071251412072605000160250ustar00rootroot00000000000000#' Interact with the test unit GUI in Komodo/SciViews-K #' #' These functions allow controlling the test unit module (R Unit tab at right) #' in Komodo with SciViews-K and SciViews-K Unit extensions. R must be correctly #' connected to Komodo, meaning that the 'svGUI' package must be loaded with #' proper configuration of client/server socket connections between R and #' Komodo. See the manual about SciViews-K for more information. The functions #' defined here are the same as JavaScript functions defined in the 'sv.r.unit' #' namespace in Komodo/SciViews-K Unit. For instance, [koUnit_runTest()] is #' equivalent to `sv.r.unit.runTest();` in a Javascript macro in Komodo. #' #' @param state `TRUE` or `FALSE`, or missing for `koUnit_showRUnitPane()`, in #' this case, the R Unit pane visibility is toggled. #' #' @return #' [koUnit_isAutoTest()] returns `TRUE` if the test unit is in auto mode in #' Komodo (the selected tests are run automatically each time a .R file #' edited in Komodo is saved). #' #' [koUnit_version()] returns the version for which the SciViews-K Unit #' extension was designed for. This allow to check if this version is compatible #' with current 'svUnit' R package version, and to propose to update the Komodo #' extension if needed (this mechanism is not running currently, but it will be #' implemented in the future to avoid or limit incompatibilities between #' respective R and Komodo extensions). #' #' The other functions are invoked for their side-effect and they return #' nothing. Note, however, that correct execution of this code in Komodo is #' verified, and the functions issue an error in R if they fail to execute #' correctly in Komodo. #' #' @export #' @name koUnit #' @author Philippe Grosjean #' @seealso [guiTestReport()] #' @keywords utilities #' @concept unit testing #' @examples #' \dontrun{ #' # Make sure R is communicating with Komodo before use, see ?koCmd in svGUI #' koUnit_version() #' #' # Toggle visibility of the R Unit pane in Komodo twice #' koUnit_showRUnitPane() #' koUnit_showRUnitPane() #' #' # Make sure that the R Unit pane is visible #' koUnit_showRUnitPane(TRUE) #' #' # Is the test unit running in auto mode? #' koUnit_isAutoTest() #' #' # Toggle auto test mode off #' koUnit_setAutoTest(FALSE) #' #' # Run the test units from within Komodo #' koUnit_runTest() #' } koUnit_setAutoTest <- function(state) { if (isTRUE(state)) state <- "true" else state <- "false" res <- .koUnit('sv.r.unit.setAutoTest(<<>>);', data = state) } .koUnit <- function(cmd, warn = FALSE, ...) { # Look if koCmd() exists, otherwise, we are probably not connected to Komodo if (exists("koCmd", mode = "function")) { res <- get("koCmd")(cmd, ...) if (isTRUE(warn) & inherits(res, "try-error")) warning("Komodo is not available or did not process this command correctly") return(res) } else { if (isTRUE(warn)) warning("You must establish a connection with Komodo/SciViews-K to use this function") } } #' @export #' @rdname koUnit koUnit_isAutoTest <- function() { res <- .koUnit('sv.socket.serverWrite(sv.r.unit.isAutoTest());') return(res == "true") } #' @export #' @rdname koUnit koUnit_runTest <- function() res <- .koUnit('sv.r.unit.runTest();') #' @export #' @rdname koUnit koUnit_showRUnitPane <- function(state) { if (missing(state)) { state <- "" } else if (isTRUE(state)) { state <- "true" } else { state <- "false" } res <- .koUnit('sv.r.unit.showRUnitPane(<<>>);', data = state) } #' @export #' @rdname koUnit koUnit_version <- function() .koUnit('sv.socket.serverWrite(sv.r.unit.version + "." + sv.r.unit.release);') r-cran-svunit-1.0.6/R/runExamples.R000066400000000000000000000022161412072605000170530ustar00rootroot00000000000000#' @export #' @rdname svTest makeTestListFromExamples <- function(packageName, manFilesDir, skipFailing = FALSE) { manPageFiles <- list.files(manFilesDir, pattern = "\\.Rd$") manPages <- sapply(manPageFiles, function(filename) { lines <- readLines(paste(manFilesDir, filename, sep = "/")) lines <- lines[grep("^\\\\name[ ]*\\{(.*)\\}", lines)] sub("^\\\\name[ ]*\\{(.*)\\}", lines, replacement = "\\1") } ) manPages <- manPages[manPages != paste(packageName, "package", sep = "-")] names(manPages) <- manPages lapply(manPages, function(x) { testCall <- call("example", x, packageName) if (skipFailing) { onFailure <- function(w) { DEACTIVATED(paste(w, collapse = "\n")) checkTrue(FALSE) } } else { onFailure <- function(w) checkIdentical(NULL, w) } result <- svTest(function() { tryCatch( withCallingHandlers({ eval(testCall) checkTrue(TRUE) }, warning = function(w) onFailure(w)), error = function(w) onFailure(w)) }) attr(result, 'unit') <- 'rcheck.examples' result }) } r-cran-svunit-1.0.6/R/svSuite.R000066400000000000000000000455341412072605000162240ustar00rootroot00000000000000#' Create and run test suites by collecting together unit tests and function #' tests defined in objects #' #' A 'svSuite' object is essentially a list of test units directories (or #' packages, in this case, corresponding directories are PKG/unitTests and its #' subdirectories), and of object names containing tests to add temporarily to #' the test suite. These must be formatted in a concise way as described for the #' 'tests' argument. #' #' [svSuiteList()] lists all loaded packages having /unitTests/runit*.R files #' (or similar files in subdirectories), and all objects in the user workspace #' that have a 'test' attribute, or are 'svTest' objects (by default). It is a #' rather exhaustive list of all test items currently available in the current R #' session, but restricted by `getOption("svUnit.excludeList")`. #' #' [makeUnit()] writes a test unit on disk with the tests from the objects #' listed in the 'svSuite' object that do not belong yet to a test unit. #' [runTest()] runs all the test in packages, directories and objects listed in #' the 'svSuite' object. #' #' @param tests A character string with items to include in the test suite. It #' could be 'package:PKG' for including test units located in the /unitTests #' subdirectory of the package PGK, or 'package:PKG (SUITE)' for test units #' located in the subdirectory /unitTests/SUITE of package PKG, or 'dir:MYDIR' #' for including test units in MYDIR, or 'test(OBJ)' for tests embedded in an #' object, or 'OBJ' for 'svTest' object directly. #' @param x Any kind of object. #' @param packages Do we list test units available in loaded packages? #' Alternatively one can provide a character vector of package names, and it #' will be used to filter packages (take care: in this case it will look at #' installed packages, not only loaded packages)! #' @param objects Do we list test available in objects? Alternatively, one can #' provide a character vector of object names, and it will filter objects in #' 'pos' according to this vector. #' @param dirs An additional list of directories where to look for more test #' units. For convenience, this list can simply be saved as an 'svUnit.dirs' #' options. #' @param excludeList A list of items to exclude from the listing. The function #' uses regular expression to match the exclusions. So, for instance, specifying #' `"package:MYPKG"` will exclude all items from package 'MYPKG', while using #' `"package:MYPKG$"` will exclude only tests suites defined in the #' .../MYPKG/unitTests directory, bur not in its subdirectories. For #' convenience, it can be saved in a 'svUnit.excludeList' option. By default, #' all tests for packages whose name start with 'sv' or 'RUnit' are excluded, #' that is, `c("package:sv", "package:RUnit")`. #' @param pos The environment to look for 'objects' (environment, character #' string with name of an environment, or integer with position of the #' environment in the search path. #' @param loadPackages In the case a list of packages is provided in #' `packages =`, do we make sure that these packages are loaded? If yes, the #' function will try to load all packages in that list that are not loaded yet #' and will issue a warning for the packages not found. Default, `FALSE`. #' @param name The name of the test suite to build. #' @param dir The directory where to create the test unit file. #' @param objfile The path to the file containing the original source code of #' the object being tested. This argument is used to bring a context for a #' test and allow a GUI to automatically open the source file for edition when #' the user clicks on a test that failed or raised an error. #' @param codeSetUp An expression with some code you want to add to the #' `.setUp()` function in your unit file (this function is executed before each #' test. #' @param codeTearDown An expression with some code you want to add to the #' `.tearDown()` function in your unit file (this function is executed after #' each test. #' @param unitname The name of a unit to run inside the suite. If `NULL` (by #' default), all units are run. #' @param ... Further arguments to pass to [makeUnit()] or [runTest()] (not used #' yet). #' #' @return #' [svSuite()], [as.svSuite()] and [svSuiteList()] return a 'svSuite' object. #' [is.svSuite()] returns `TRUE` if the object is an 'svSuite'. #' #' [makeUnit()] creates a test unit file on disk, and [runTest()] runs the tests #' in such a file. They are used for their side-effect, but the first one also #' returns the file created, and the second one returns invisibly the list of #' all test unit files that where sourced ans run. #' #' @details #' Thanks to the variety of sources allowed for tests, it is possible to define #' these tests in a structured way, inside packages, like for the 'RUnit' #' package (but with automatic recognition of test units associated to packages, #' in the present case). It is also easy to define tests more loosely by just #' attaching those tests to the objects you want to check. Whenever there objects #' are loaded in the user's workspace, their tests are available. In both cases, #' a test unit file on disk is sourced in a local environment and test functions #' are run (same approach as in the 'RUnit' package, and the same test unit files #' should be compatibles with both 'RUnit' and 'svUnit' packages), but in the #' case of a loosely definition of the tests by attachment to objects, the test #' unit file is created on the fly in the temporary directory (by default). #' #' At any time, you can transform a series of tests loosely attached to objects #' into a test unit file by applying [makeUnit()] to a 'svSuite' object, #' probably specifying another directory than the (default) temporary dir for #' more permanent storage of your test unit file. The best choice is the #' '/inst/unitTests' directory of a package source, or one of its #' subdirectories. That way, your test unit file(s) will be automatically listed #' and available each time you load the compiled package in R (if you list them #' using [svSuiteList()]). Of course, you still can exclude tests from given #' packages by adding 'package:PKG' in the exclusion list with something #' like: `options(svUnit.excludeList = c(getOption("svUnit.excludeList"), "package:PKG"))`. #' #' @export #' @author Philippe Grosjean #' @seealso [svSuiteData()], [svTest()], [Log()], [checkEquals()], [RUnit::checkEquals()] #' @keywords utilities #' @concept unit testing #' @examples #' svSuiteList() # List all currently available test units and test cases #' # Exclusion list is used (regular expression filtering!). It contains: #' (oex <- getOption("svUnit.excludeList")) #' # Clear it, and relist available test units #' options(svUnit.excludeList = NULL) #' svSuiteList() #' #' # Two functions that include their test cases #' Square <- function(x) #' return(x^2) #' test(Square) <- function() { #' checkEquals(9, Square(3)) #' checkEquals(c(1, 4, 9), Square(1:3)) #' checkException(Square("xx")) #' } #' #' Cube <- function(x) #' return(x^3) #' test(Cube) <- function() { #' checkEquals(27, Cube(3)) #' checkEquals(c(1, 8, 28), Cube(1:3)) #' checkException(Cube("xx")) #' } #' #' # A separate test case object (not attached to a particular object) #' # This is the simplest way to define quick and durty integration tests #' test_Integrate <- svTest(function() { #' checkTrue(1 < 2, "check1") #' v <- 1:3 # The reference #' w <- 1:3 # The value to compare to the reference #' checkEquals(v, w) #' }) #' #' # A function without test cases (will be filtered out of the suite list) #' foo <- function(x) #' return(x) #' #' # Look now which tests are available #' svSuiteList() #' #' # Only objects, no package units #' svSuiteList(packages = FALSE) #' #' \dontrun{ #' # Create the test unit file for all objects with tests in .GlobalEnv #' myunit <- makeUnit(svSuiteList(), name = "AllTests") #' file.show(myunit, delete.file = TRUE) #' } #' #' # Filter objects using a list (object with/without tests and a nonexisting obj) #' svSuiteList(packages = FALSE, objects = c("Cube", "foo", "bar")) #' #' # Create another svSuite object with selected test items #' (mysuite <- svSuite(c("package:svUnit (VirtualClass)", "test(Cube)"))) #' is.svSuite(mysuite) # Should be! #' #' \dontrun{ #' # Run all the tests currently available #' (runTest(svSuiteList(), name = "AllTests")) #' summary(Log()) #' } #' #' # Restore previous exclusion list, and clean up the environment #' options(svUnit.excludeList = oex) #' rm(Square, Cube, foo, test_Integrate, mysuite, myunit, oex) svSuite <- function(tests) { # Check provided tests and build a 'svSuite' object tests <- as.character(tests) # Remove NAs and empty strings ("") from tests tests <- tests[!is.na(tests) & !(tests == "")] if (length(tests) > 0) { # Tests must be character strings like: # * package:PKG # * package:PKG (TESTSUITE) # * dir:MYDIR ## * test(OBJ) where OBJ is any object with a 'test' attribute # * OBJ being a 'svTest' object (with non "exotic" name!), # Syntax is checked, but not existence/validity of corresponding tests! check1 <- (regexpr("^package:[a-zA-Z][a-zA-Z._0-9]*$", tests) > -1) check2 <- (regexpr("^package:[a-zA-Z][a-zA-Z._0-9]* *\\(.+\\)$", tests) > -1) check3 <- (regexpr("^dir:.+", tests) > -1) check4 <- (regexpr("^test\\(.+\\)$", tests) > -1) check5 <- (regexpr("^[a-zA-Z0-9_.]+$", tests) > -1) wrong <- ((check1 + check2 + check3 + check4 + check5) == 0) if (any(wrong)) stop("Wrong 'tests' data: must be 'package:PKG', 'package:PKG (SUITE)',\n'dir:MYDIR', 'test(OBJ)' or 'OBJ'") } # This is a 'svSuite' object subclassing 'character' class(tests) <- c("svSuite", "character") tests } #' @export #' @rdname svSuite as.svSuite <- function(x) svSuite(x) #' @export #' @rdname svSuite is.svSuite <- function(x) inherits(x, "svSuite") #' @export #' @rdname svSuite print.svSuite <- function(x, ...) { if (!is.svSuite(x)) stop("'x' must be a 'svSuite' object") if (length(x) < 1) { cat("An empty svUnit test suite\n") } else { cat("A svUnit test suite definition with:\n") # Separate unit tests from tests embedded in objects isSuite <- regexpr("^[package:|dir:]", x) > -1 if (any(isSuite)) { Suites <- x[isSuite] msg <- ifelse(length(Suites) == 1, "\n- Test suite:\n", "\n- Test suites:\n") cat(msg) print(Suites) } if (any(!isSuite)) { Objs <- x[!isSuite] msg <- ifelse(length(Objs) == 1, "\n- Test function:\n", "\n- Test functions:\n") cat(msg) print(Objs) } } invisible(x) } #' @export #' @rdname svSuite svSuiteList <- function(packages = TRUE, objects = TRUE, dirs = getOption("svUnit.dirs"), excludeList = getOption("svUnit.excludeList"), pos = .GlobalEnv, loadPackages = FALSE) { # List unit test (1) in loaded packages (2) in objects in pos and (3) in # directories, possibly filtering them using an exclusion list # Note: Komodo should list test unit files in loaded projects too! if (length(packages) < 1) stop("'package' cannot have zero length") if (length(objects) < 1) stop("'objects' cannot have zero length") items <- character() # 1) Unit test files in loaded packages if (packages[1] != FALSE) { if (is.character(packages)) {# We assume it is a list of packages Pkgs <- packages } else {# We use the list of all loaded packages Pkgs <- .packages() } for (Pkg in Pkgs) { # Look for test units in the package path <- system.file(package = Pkg, "unitTests") if (path != "" && file.info(path)$isdir) { pkgname <- paste("package", Pkg, sep = ":") items <- c(items, pkgname) Files <- list.files(path = path, full.names = TRUE) for (File in Files) {# Add all subdirectories too if (file.info(File)$isdir) items <- c(items, paste(pkgname, " (", basename(File), ")", sep = "")) } } } } # 2) Tests embedded in objects located in 'pos' environment if (objects[1] != FALSE) { envir = as.environment(pos) if (is.character(objects)) { tests <- character() for (Oname in objects) { if (exists(Oname, envir = envir, inherits = FALSE)) { Obj <- get(Oname, envir = envir, inherits = FALSE) if (is.svTest(Obj)) { tests <- c(tests, Oname) } else if (is.test(Obj)) { tests <- c(tests, paste("test(", Oname, ")", sep = "")) } } } } else {# We list all objects in pos Objs <- mget(ls(envir = envir), envir = envir) Onames <- names(Objs) tests <- character() if (length(Objs) > 0) { for (i in 1:length(Objs)) { if (is.svTest(Objs[[i]])) { tests <- c(tests, Onames[i]) } else if (is.test(Objs[[i]])) { tests <- c(tests, paste("test(", Onames[i], ")", sep = "")) } } } } items <- c(items, sort(tests)) } # 3) Additional directories (check that they are valid and existing dirs) if (!is.null(dirs)) { # Check if each entry exists as a directory, exclude it if not # Prepend "dir:" to tag them as additional directories Dirs <- character() for (Dir in dirs) if (file.exists(Dir) && file.info(Dir)$isdir) Dirs <- c(Dirs, paste("dir", Dir, sep = ":")) items <- c(items, sort(Dirs)) } # Filter the resulting list with 'excludeList' if (!is.null(excludeList)) { for (pattern in excludeList) items <- items[regexpr(pattern, items) == -1] } # Do we load the package? if (loadPackages) { # Get a list of packages we need for the suite Pkgs <- items[regexpr("^package:", items)] PkgsSrch <- unique(sub(" +\\(.+$", "", Pkgs)) l <- length(PkgsSrch) if (l > 0) { PkgsName <- sub("^package:", "", PkgsSrch) Search <- search() for (i in 1:l) { if (!PkgsSrch[i] %in% Search) { res <- try(library(PkgsName[i], character.only = TRUE), silent = TRUE) if (inherits(res, "try-error")) warning("Cannot load package '", PkgsName[i], "'") } } } } # Make it a 'svSuite' object subclassing 'character' class(items) <- c("svSuite", "character") items } #' @export #' @rdname svSuite makeUnit.svSuite <- function(x, name = make.names(deparse(substitute(x))), dir = tempdir(), objfile = "", codeSetUp = NULL, codeTearDown = NULL, pos = .GlobalEnv, ...) { # Take an 'svSuite' object and make a unit from its function tests # that are not written yet in a test unit in a file # They are saved in a file named runit.R in 'dir' if (!is.svSuite(x)) stop("'x' must be a 'svSuite' object") name <- as.character(name)[1] # Under Windows, we transform \\ into / dir <- gsub("\\\\", "/", as.character(dir)[1]) # Collect all items that are not 'package:...' or 'dir:...' isObj <- regexpr("^[package:|dir:]", x) == -1 Objs <- sub("^test[(](.+)[)]$", "\\1", x[isObj]) if (length(Objs) == 0) {# No objects, return NULL return(NULL) } else {# Make a sourceable test unit file with tests collected in Objs Unit <- .prepareUnit(name, dir) .writeSetUp(unit = Unit, file = objfile, code = codeSetUp) .writeTearDown(unit = Unit, code = codeTearDown) for (objname in Objs) .writeTest(unit = Unit, objname = objname, pos = pos) } Unit } #' @export #' @rdname svSuite runTest.svSuite <- function(x, name = make.names(deparse(substitute(x))), unitname = NULL, ...) { # Compile and run the test for this 'svSuite' object if (!is.svSuite(x)) stop("'x' must be a 'svSuite' object") name <- as.character(name[1]) # Decode tests contained in x tests <- as.character(x) dirs <- character() # Package suites... isPkg <- regexpr("^package:", tests) > -1 if (any(isPkg)) { Pkgs <- tests[isPkg] Subdirs <- sub("^.+[(](.+)[)] *$", "\\1", Pkgs) Subdirs[Subdirs == Pkgs] <- "" Pkgs <- sub("^package:([^ ]+).*$", "\\1", Pkgs) for (i in 1:length(Pkgs)) { if (Subdirs[i] == "") { dir <- system.file(package = Pkgs[i], "unitTests") } else { dir <- system.file(package = Pkgs[i], "unitTests", Subdirs[i]) } if (dir != "") dirs <- c(dirs, dir) } } # Add directories, and possibly make a temporary unit for test objects if (any(!isPkg)) { tests <- tests[!isPkg] # Directories isDir <- regexpr("^dir:", tests) > -1 if (any(isDir)) dirs <- c(sub("^dir:", "", tests[isDir]), dirs) # Objects if (any(!isDir)) { # Make a temporary unit for the tests of these objects if (!is.null(Unit <- makeUnit(x, name = name))) { # Add this path to dirs dirs <- c(dirname(Unit), dirs) } } } # Now, list all files in these dirs with name being runit*.R files <- character() for (dir in dirs) files <- c(files, list.files(dir, pattern = "^runit.+\\.[rR]$", full.names = TRUE)) if (length(files) == 0) return(NULL) # Nothing to run! # Under Windows, transform all \\ into / in the file names files <- gsub("\\\\", "/", files) # Added by Thomas Wurtzler to control which unit test to run if (!is.null(unitname)) { unitname <- deparse(substitute(unitname)) testNames <- gsub("^.*runit(.+)\\.[rR]$", "\\1", files) keep <- which(testNames == unitname) files <- files[keep] if (length(files) == 0) { warning("Test unit ", unitname, " not found") return(NULL) } } # Run this test suite now, that is, source each file in .TestSuiteEnv # and run each testxxx function in it, using .setUp and .tearDown too # Record the list of tests .lastSuite <- list() for (file in files) .lastSuite[[basename(file)]] <- list(file = file) .Log <- Log() .Log$.lastSuite <- .lastSuite # Source each runit*.R file in turn for (unit in names(.lastSuite)) { # Create a new environment for this suite. .ThisTestSuiteEnv <- new.env(parent = .GlobalEnv) # Store it in SciViews:TempEnv so that we can inspect it in case of # stop on error. But please do not remove the local alias. #1327 .assignTemp(".TestSuiteEnv", .ThisTestSuiteEnv) # Source the corresponding file Unit <- .lastSuite[[unit]]$file sys.source(Unit, envir = .ThisTestSuiteEnv) # Make sure there are .setUp() and .tearDown() functions if (!exists(".setUp", envir = .ThisTestSuiteEnv, mode = "function", inherits = FALSE)) .ThisTestSuiteEnv$.setUp <- function() {} if (!exists(".tearDown", envir = .ThisTestSuiteEnv, mode = "function", inherits = FALSE)) .ThisTestSuiteEnv$.tearDown <- function() {} # List all test files in the unit tests <- ls(.ThisTestSuiteEnv, pattern = "^test.+$") # Keep only 'test*' objects that are function keep <- unlist(lapply(tests, function(n) exists(n, envir = .ThisTestSuiteEnv, mode = "function", inherits = FALSE))) tests <- tests[keep] .Log$.lastSuite[[unit]]$tests <- tests # Run each test in turn for (test in tests) .runTest(envir = .ThisTestSuiteEnv, test = test, unit = Unit) } invisible(files) } r-cran-svunit-1.0.6/R/svSuiteData.R000066400000000000000000000230341412072605000170050ustar00rootroot00000000000000#' Objects of class 'svSuiteData' contain results from running test suites #' #' The 'svSuiteData' object contains results of all test run in one or more test #' suites. The `checkxxx()` functions and the [runTest()] method generate data #' (objects 'svTestData') contained in the default 'svSuiteData' named `.Log` #' and located in `.GlobalEnv`. It is then possible to display and report #' information it contains in various ways to analyze the results. #' #' @param x Any kind of object, or a 'svSuiteData' object in the case of #' [print()]. #' @param object A 'svSuiteData' object. #' @param fields Character vector. The name of all metadata items you want to #' extract for the object. The default value is an exhaustive list of all #' available metadata (i.e., defined by default) in the object, but you can add #' more: just add a corresponding attribute to your object. #' @param all Do we print concise report for all test, or only for the tests #' that fail or produce an error? #' @param file Character. The path to the file where to write the report. #' If `file = ""`, the protocol report is output to the console. #' @param append Do we append to this file? #' @param type Character. The type of protocol report to create. For the moment, #' only `type = "text"` and `type = "junit"` are supported, but further types #' (HTML, LaTeX, Wiki, etc.) could be provided later. #' @param ... Further arguments to pass to methods. Not used yet. #' #' @return #' [is.svSuiteData()] returns `TRUE` if the object is an 'svSuiteData'. The #' various methods serve to extract or print content in the object. #' #' @details #' A 'svSuiteData' is, indeed, an environment. The results for the various tests #' runs are in non hidden (i.e., names not starting with a dot) objects that are #' of class 'svTestData' in this environment. Various other objects that control #' the execution of the test, their context, etc. are contained as hidden objects #' with name starting with a dot. Note that using an environment instead of a #' list for this object allows for a call by reference instead of a usual call by #' value in R, when passing this object to a function. This property is largely #' exploited in all svUnit functions to make sure results of test runs are #' centralized in the same log ('svSuiteData' object). #' #' @export #' @name svSuiteData #' @author Philippe Grosjean; Mario Frasca for the junit protocol. #' @seealso [svSuite()], [is.svTestData()], [Log()], [checkEquals()] #' @keywords utilities #' @concept unit testing #' @examples #' clearLog() # Clear any existing log #' #' # Run some tests #' checkTrue(1 < 2) #' checkException(log("a")) #' foo <- function(x, y = 2) #' return(x * y) #' test(foo) <- function() { #' checkEqualsNumeric(4, foo(2)) #' checkEqualsNumeric(6, foo(2, nonexisting)) #' checkTrue(is.test(foo)) #' warning("This is a warning") #' cat("Youhou from test!\n") # Don't use, except for debugging! #' checkTrue(is.test(test(foo))) #' checkIdentical(attr(foo, "test"), test(foo)) #' checkException(foo(2, nonexisting)) #' #DEACTIVATED("My deactivation message") #' checkException(foo(2)) # This test fails #' } #' runTest(foo) #' #' # Now inspect the log, which is a 'svSuiteData' object #' is.svSuiteData(Log()) #' stats(Log()) #' metadata(Log()) #' Log() # Print method #' summary(Log()) #' #' \dontrun{ #' # To get a print of the test protocol on file, use: #' protocol(Log(), type = "text", file = "RprofProtocol.out") #' file.show("RprofProtocol.out") #' unlink("RprofProtocol.out") #' } #' #' rm(foo) #' #' \dontrun{ #' # Profiling of very simple test runs #' library(utils) #' createLog(description = "test profiling", deleteExisting = TRUE) #' imax <- 3 #' jmax <- 100 #' l <- 50 #' Rprof() #' for (i in 1:imax) { #' # Change the context for these tests #' .Log$..Test <- paste("Test", i, sep = "") #' .Log$..Tag <- paste("#", i, sep = "") #' res <- system.time({ #' for (j in 1:jmax) checkTrue(i <= j, "My test") #' }, gcFirst = TRUE)[3] #' print(res) #' flush.console() #' } #' Rprof(NULL) #' # Look at profile #' summaryRprof() #' unlink("Rprof.out") #' #' # Look at the log #' summary(Log()) #' } is.svSuiteData <- function(x) { # It this a svSuiteData object inherits(x, "svSuiteData") } #' @export #' @rdname svSuiteData stats.svSuiteData <- function(object, ...) { if (!is.svSuiteData(object)) stop("'object' must inherit from 'svSuiteData'") # Get the list of tests Tests <- ls(object) if (length(Tests) == 0) { # The object is empty! Res <- data.frame(kind = .kind(logical()), timing = numeric(), time = numeric(), unit = character(), tag = character(), msg = character(), stringsAsFactors = FALSE) } else { # Functions to get data for each test getKind <- function(x) .kindMax(x$kind) getTiming <- function(x) attr(x, "stats")["timing"] getTime <- function(x) attr(x, "time") getContext <- function(x, item) attr(x, "context")[[item]] Res <- data.frame( kind = rev(sapply(object, getKind)), timing = rev(sapply(object, getTiming)), time = structure(rev(sapply(object, getTime)), class = c("POSIXt", "POSIXct")), unit = rev(sapply(object, getContext, "unit")), msg = rev(sapply(object, getContext, "msg")), stringsAsFactors = FALSE) } Res } #' @export #' @rdname svSuiteData metadata <- function(object, ...) UseMethod("metadata") #' @export #' @rdname svSuiteData metadata.svSuiteData <- function(object, fields = c("R.version", "sessionInfo", "time", "description"), ...) { # Extract metadata information from a 'svSuiteData' object if (!is.svSuiteData(object)) stop("'object' must inherit from 'svSuiteData'") # Return a list with all metadata elements found fields <- paste(".", fields, sep = "") Res <- list() for (F in fields) Res[[F]] <- object[[F]] Res } #' @export #' @rdname svSuiteData print.svSuiteData <- function(x, all = FALSE, file = "", append = FALSE, ...) { if (!is.svSuiteData(x)) stop("'x' must inherit from 'svSuiteData'") Tests <- ls(x) if (length(Tests) == 0) { cat("No test records!\n", file = file, append = append) } else { # Print general information about the tests Stats <- stats(x) Tests <- rownames(Stats) # To make sure we use the same! Timing <- .formatTime(sum(Stats$timing, na.rm = TRUE), secDigits = 1) cat("= A svUnit test suite", Timing, " with:\n\n", sep = "", file = file, append = append) cat(paste("* ", Tests, " ... ", as.character(Stats$kind), "", sep = "", collapse = "\n"), "\n\n", sep = "", file = file, append = TRUE) # Print detailed information about each test for (Test in Tests) print(x[[Test]], all = all, file = file, append = TRUE, ...) } invisible(x) } #' @export #' @rdname svSuiteData summary.svSuiteData <- function(object, ...) protocol_text.svSuiteData(object, ...) #' @export #' @rdname svSuiteData protocol <- function(object, type = "text", file = "", append = FALSE, ...) UseMethod("protocol") #' @export #' @rdname svSuiteData protocol.default <- function(object, type = "text", file = "", append = FALSE, ...) get(paste("protocol", type[1], sep = "_"))(object, file = file, append = append, ...) #' @export #' @rdname svSuiteData protocol.svSuiteData <- function(object, type = "text", file = "", append = FALSE, ...) get(paste("protocol", type[1], sep = "_"))(object, file = file, append = append, ...) #' @export #' @rdname svSuiteData protocol_text <- function(object, file = "", append = FALSE, ...) UseMethod("protocol_text") #' @export #' @rdname svSuiteData protocol_text.svSuiteData <- function(object, file = "", append = FALSE, ...) { if (!is.svSuiteData(object)) stop("'object' must inherit from 'svSuiteData'") Tests <- sort(ls(object)) if (length(Tests) == 0) { cat("No test records!\n", file = file, append = append) } else { # Print general information about the tests Stats <- stats(object) Tests <- rownames(Stats) # To make sure we use the same! Timing <- .formatTime(sum(Stats$timing, na.rm = TRUE), secDigits = 1) cat("= A svUnit test suite", Timing, " with:\n\n", sep = "", file = file, append = append) cat(paste("* ", Tests, " ... ", as.character(Stats$kind), "", sep = "", collapse = "\n"), "\n\n", sep = "", file = file, append = TRUE) # Summarize each test for (Test in Tests) summary(object[[Test]], file = file, append = TRUE) } } #' @export #' @rdname svSuiteData protocol_junit <- function(object, ...) UseMethod("protocol_junit") #' @export #' @rdname svSuiteData protocol_junit.svSuiteData <- function(object, file = "", append = FALSE, ...) { if (!is.svSuiteData(object)) stop("'object' must inherit from 'svSuiteData'") #if(!require(XML, quietly = TRUE)) # return(invisible(FALSE)) requireNamespace("XML") Tests <- sort(ls(object)) if (length(Tests) > 0 && inherits(object[[Tests[1]]], "svSuiteData")) { # This is a set of suites (containing svSuiteData) root <- XML::xmlNode('testsuites') } else { # This is a single suite (containing svTestData) root <- XML::xmlNode('testsuite') } with(stats(object), addAttributes(root, name = NULL, tests = length(Tests), errors = sum(kind == '**ERROR**'), failures = sum(kind == '**FAILS**'), skip = sum(kind == 'DEACTIVATED'))) for (Test in Tests) root <- XML::addChildren(root, kids = list(protocol_junit(object[[Test]], append = TRUE))) # Decide whether to return the xml node or write the xml file if (isTRUE(append)) { return(root) } else { XML::saveXML(root, file) return(invisible(TRUE)) } } r-cran-svunit-1.0.6/R/svTest.R000066400000000000000000000206431412072605000160440ustar00rootroot00000000000000#' Create, attach to and manipulate test functions in R objects #' #' Test functions are functions without arguments with class 'svTest' containing #' one or more assertions using \code{checkxxx()} functions. They can be #' attached to any object as a 'test' attribute. They can also be transferred #' into a more formal test unit file on disk by applying the [makeUnit()] method. #' #' @param testFun A function without arguments defining assertions (using #' `checkxxx()` functions) for tests to be transformed into a 'svTest' object. #' @param x Any kind of object. #' @param value The tests to place in the object (as 'test' attribute); could be #' a 'svTest' object, or a function without arguments with #' assertions (`checkxxx()` functions). #' @param name The name of a test. #' @param dir The directory where to create the test unit file. #' @param objfile The path to the file containing the original source code of #' the object being tested. This argument is used to bring a context for a test #' and allow a GUI to automatically open the source file for edition when the #' user clicks on a test that failed or raised an error. #' @param codeSetUp An expression with some code you want to add to the #' `.setUp()` function in your unit file (this function is executed before each #' test. #' @param codeTearDown An expression with some code you want to add to the #' `.tearDown()` function in your unit file (this function is executed after #' each test. #' @param tag A tag is a character string identifying a location in source code #' files (either a test unit file, or the original source code of the tested #' objects defined in `objfile =`. This character string will be searched by the #' text editor for easy location of the cursor near the corresponding test #' command, or near the location in the original object that is concerned by #' this test. Use any string you want to uniquely identify your tag, both in #' your files, and in this argument. #' @param msg A message you want to associate with this test run. #' @param packageName A character string identifying the package from which to #' extract examples. #' @param manFilesDir A character string identifying the directory holding the #' manual pages and examples. #' @param skipFailing A logical indicating whether missing or failing #' documentation examples should be marked as `skipped` instead of as `failure`. #' @param ... Further arguments to the method (not used yet). #' #' @return #' A 'svTest' object for [svTest()], [as.svTest()] and [test()]. Function #' [is.svTest()] returns `TRUE` if 'x' is a 'svTest' object, and `is.test()` #' does the same but also looks in the 'test' attribute if the class of 'x' is #' not 'svTest' and returns `TRUE` if it finds something there. #' #' [makeUnit()] takes an object, extract its test function and write it in a #' sourceable test unit on the disk (it should be compatible with 'RUnit' test #' unit files too). #' #' [runTest()] returns invisibly a 'svTestData' object with all results after running specified tests. #' #' @export #' @author Philippe Grosjean #' @seealso [svSuite()], [is.svTestData()], [Log()], [checkEquals()] #' @keywords utilities #' @concept unit testing #' @examples #' clearLog() # Clear the log file #' #' foo <- function(x, y = 2) #' return(x * y) #' is.test(foo) # No #' # Create test cases for this function #' test(foo) <- function() { #' checkEqualsNumeric(4, foo(2)) #' checkEqualsNumeric(6, foo(2, 3)) #' checkTrue(is.test(foo)) #' checkTrue(is.test(test(foo))) #' checkIdentical(attr(foo, "test"), test(foo)) #' checkException(foo(2, "aa")) #' checkException(foo("bb")) #' } #' is.test(foo) # Yes #' #' \dontrun{ #' # Create a test unit on disk and view it #' unit <- makeUnit(foo) #' file.show(unit, delete.file = TRUE) #' } #' #' # Run the test #' (runTest(foo)) #' # Same as... #' bar <- test(foo) #' (runTest(bar)) #' #' # How fast can we run 100 times such kind of tests (700 test in total)? #' # (just an indication because in real situation with test unit files, we #' # have also the time required to source the units!) #' system.time(for (i in 1:100) runTest(foo))[3] #' #' is.svTest(test(foo)) # Yes, of course! #' # When an object without associated test is passed to runTest(), #' # a simple test containing only a DEACTIVATED entry is build #' x <- 1:10 #' summary(runTest(x)) #' #' summary(Log()) #' #' rm(foo, bar, x) svTest <- function(testFun) { # Create a 'svTest' object, using testFun: a function without arguments # that contains one or more checkXX() assertions if (!is.function(testFun)) stop("'testFun' must be a function or a 'svTest' object") # Check that there are no arguments if (length(formals(testFun)) > 0) stop("'testFun' must be a function without any arguments") # This is a S3 object of class 'svTest', subclassing 'function' class(testFun) <- c("svTest", "function") testFun } #' @export #' @rdname svTest print.svTest <- function(x, ...) { cat("svUnit test function:\n") print(body(x)) invisible(x) } #' @export #' @rdname svTest as.svTest <- function(x) { # Coercion to a 'svTest' object svTest(x) } #' @export #' @rdname svTest is.svTest <- function(x) { # It this a svTest object inherits(x, "svTest") } #' @export #' @rdname svTest is.test <- function(x) { # Is this a 'svTest'object # or do this object contain a non NULL 'test' attribute? return(is.svTest(x) || !is.null(attr(x, "test"))) } #' @export #' @rdname svTest test <- function(x) { # If x is a 'svTest' object, return it, otherwise, # get the 'test' attribute from the object, if it exists if (is.svTest(x)) { return(x) } else { res <- attr(x, "test") if (is.null(res)) { # Create a dummy test with only a DEACTIVATED entry res <- svTest(function() DEACTIVATED("Object has no tests!")) } return(res) } } #' @export #' @rdname svTest `test<-` <- function(x, value) { # Add 'value' as a 'test' attribute to 'x' after coercing it to 'svTest' attr(x, "test") <- as.svTest(value) x } #' @export #' @rdname svTest makeUnit <- function(x, ...) UseMethod("makeUnit") #' @export #' @rdname svTest makeUnit.default <- function(x, name = make.names(deparse(substitute(x))), dir = tempdir(), objfile = "", codeSetUp = NULL, codeTearDown = NULL, ...) { # Take an object and make a unit from the tests it contains # It is saved in a file runit.R in 'dir' name <- as.character(name)[1] name <- sub("^test\\.(.+)\\.$", "\\1", name) # Under Windows, we transform \\ into / dir <- gsub("\\\\", "/", as.character(dir)[1]) Unit <- .prepareUnit(name, dir) # Just get the test from the object Test <- test(x) # Make required initialisation to allow locating objects .writeSetUp(unit = Unit, file = objfile, code = codeSetUp) .writeTearDown(unit = Unit, code = codeTearDown) # Write the test function in the file .writeTest(unit = Unit, objname = name, obj = x) # Return the name of the test function Unit } #' @export #' @rdname svTest makeUnit.svTest <- function(x, name = make.names(deparse(substitute(x))), dir = tempdir(), objfile = "", codeSetUp = NULL, codeTearDown = NULL, ...) { # I know: this is not needed, but it is there in case additional work # would be needed in the future, and also to show that makeUnit is # designed to work on 'svTest' objects makeUnit.default(x, name = name, dir = dir, objfile = objfile, codeSetUp = codeSetUp, codeTearDown = codeTearDown, ...) } #' @export #' @rdname svTest runTest <- function(x, ...) UseMethod("runTest") #' @export #' @rdname svTest runTest.default <- function(x, name = deparse(substitute(x)), objfile = "", tag = "", msg = "", ...) { # Run the test for the 'test' attribute of this object name <- paste("test(", name, ")", sep = "") runTest(test(x), name = name, objfile = objfile, tag = tag, msg = msg, ...) } #' @export #' @rdname svTest runTest.list <- function(x, ...) { # Run each test in x, giving each test the name it has in x lapply(names(x), function(name, item=x[[name]]) { unit <- ifelse(is.null(attr(item, "unit")), "**root**", attr(item, "unit")) runTest(item, name = name, unit = unit, ...) }) } #' @export #' @rdname svTest runTest.svTest <- function(x, name = deparse(substitute(x)), objfile = "", tag = "", msg = "", ...) { if (!is.svTest(x)) stop("'x' must be a 'svTest' object") # Names of object and test test <- as.character(name)[1] test <- .runTest(x, test = test, objfile = objfile, tag = tag, msg = msg, ...) .Log <- Log() invisible(.Log[[test]]) } r-cran-svunit-1.0.6/R/svTestData.R000066400000000000000000000143101412072605000166300ustar00rootroot00000000000000#' Objects of class 'svTestData' contain results from running a test #' #' The 'svTestData' contains results of test run. The `checkxxx()` functions and #' the `runTest()` method generate one such object which is located in the #' `.Log` object in `.GlobalEnv`. It is then possible to display and report #' information it contains in various ways to analyze the results. #' #' @param x Any kind of object, or a 'svTestData' object in the case of #' [print()]. #' @param object A 'svTestData' object. #' @param all Do we print concise report for all test, or only for the tests #' that fail or produce an error? #' @param header Do we print a header or not? #' @param file Character. The path to the file where to write the report. If #' `file = ""`, the report is output to the console. #' @param append Do we append to this file? #' @param ... Further arguments to pass to methods. Not used yet. #' #' @return #' [is.svTestData()] returns `TRUE` if the object is an 'svTestData'. The #' various methods serve to extract or print content in the object. #' #' @export #' @name svTestData #' @author Philippe Grosjean #' @seealso [svTest()], [is.svSuiteData()], [Log()], [checkEquals()] #' @keywords utilities #' @concept unit testing #' @examples #' foo <- function(x, y = 2) #' return(x * y) #' is.test(foo) # No #' # Create test cases for this function #' test(foo) <- function() { #' checkEqualsNumeric(4, foo(2)) #' checkEqualsNumeric(5, foo(2, 3)) #' checkEqualsNumeric(5, foo(nonexists)) #' } #' # Generate a 'svTestData' object by running the test #' obj <- runTest(foo) # Equivalent to runTest(test(foo)), but shorter #' obj #' summary(obj) #' stats(obj) #' is.svTestData(obj) #' #' rm(foo, obj) is.svTestData <- function(x) { # It this a svTestData object inherits(x, "svTestData") } #' @export #' @rdname svTestData stats <- function(object, ...) UseMethod("stats") #' @export #' @rdname svTestData stats.svTestData <- function(object, ...) { if (!is.svTestData(object)) stop("'object' must inherit from 'svTestData'") Stats <- attr(object, "stats") Table <- table(object$kind) # Update the table with the total number of test Kinds <- c(Stats["tests"] - sum(Table[2:4], na.rm = TRUE), Table[2:4]) names(Kinds) <- names(Table) # Return a list with the table of kinds and the total timing list(kind = Kinds, timing = Stats["timing"]) } #' @export #' @rdname svTestData print.svTestData <- function(x, all = FALSE, header = TRUE, file = "", append = FALSE, ...) { # If there is a context attribute, print info about the tests cat("", file = file, append = append) Context <- attr(x, "context") if (!is.null(Context)) { unitStr <- if (Context["unit"] == "") "" else paste(" (in ", basename(Context["unit"]), ")", sep = "") Stats <- stats(x) if (isTRUE(header)) { cat("\n== ", Context["test"], unitStr, .formatTime(Stats$timing, secDigits = 1), ": ", as.character(.kindMax(x$kind)), "\n", Context["msg"], "\n", sep = "", file = file, append = TRUE) } cat(paste(c("//Pass:", "Fail:", "Errors:"), Stats$kind[1:3], collapse = " "), "//\n\n", sep = "", file = file, append = TRUE) # Don't print tests that succeed if !all if (!isTRUE(all)) { X <- x[x$kind != "OK", ] } else { X <- x } } else { X <- x } # Print info about each individual filtered test if (nrow(X) > 0) { Res <- ifelse(X$res == "", "", paste("\n", X$res, sep = "")) cat(paste("* ", X$msg, ": ", X$call, .formatTime(X$timing, secDigits = 3), " ... ", as.character(X$kind), Res, sep = "", collapse = "\n"), file = file, append = TRUE) } invisible(x) } #' @export #' @rdname svTestData summary.svTestData <- function(object, header = TRUE, file = "", append = FALSE, ...) { # If there is a context attribute, print info about the tests cat("", file = file, append = append) Context <- attr(object, "context") if (!is.null(Context)) { unitStr <- if (Context["unit"] == "") "" else paste(" (in ", basename(Context["unit"]), ")", sep = "") Stats <- stats(object) if (isTRUE(header)) { cat("\n== ", Context["test"], unitStr, .formatTime(Stats$timing, secDigits = 1), ": ", as.character(.kindMax(object$kind)), "\n", Context["msg"], "\n", sep = "", file = file, append = TRUE) } cat(paste(c("//Pass:", "Fail:", "Errors:"), Stats$kind[1:3], collapse = " "), "//\n\n", sep = "", file = file, append = TRUE) } # List tests that failed Items <- rownames(object) Fail <- object$kind == "**FAILS**" if (any(Fail)) { cat("=== Failures\n", file = file, append = TRUE) cat(paste("[", Items[Fail], "] ", object$msg[Fail], ": ", object$call[Fail], collapse = "\n", sep = ""), "\n\n", sep = "", file = file, append = TRUE) } # List tests that produce errors Err <- object$kind == "**ERROR**" if (any(Err)) { cat("=== Errors\n", file = file, append = TRUE) cat(paste("[", Items[Err], "] ", object$msg[Err], ": ", object$call[Err], collapse = "\n", sep = ""), "\n\n", sep = "", file = file, append = TRUE) } } #' @export #' @rdname svTestData protocol_junit.svTestData <- function(object, ...) { #if (!require(XML, quietly = TRUE)) # return(invisible(FALSE)) requireNamespace("XML") toValidXmlString <- function(s) { s <- gsub("&", "&", s) s <- gsub("<", "<", s) s <- gsub(">", ">", s) s <- gsub('"', """, s) s <- gsub("'", "'", s) s } basename <- function(s) sub(".*/", "", s) Context <- attr(object, "context") Stats <- attr(object, "stats") .xmlNode <- XML::xmlNode .addChildren <- XML::addChildren result <- .xmlNode('testcase', attrs = c( 'classname' = basename(Context[['unit']]), 'name' = toValidXmlString(Context[['test']]), 'time' = object$timing)) kind <- as.numeric(.kindMax(object$kind)) # TODO: use accessor elementName <- c(NA, 'failure', 'error', NA)[kind] if (!is.na(elementName)) { failureNode <- .xmlNode(elementName, attrs = c( 'type' = elementName, 'message' = toValidXmlString(object$res))) # TODO: use accessor result <- .addChildren(result, kids = list(failureNode)) } if (kind == 4) result <- .addChildren(result, kids = list(.xmlNode('skipped'))) result } r-cran-svunit-1.0.6/R/svUnit-internal.R000066400000000000000000000376671412072605000176740ustar00rootroot00000000000000.onLoad <- function(lib, pkg) { # nocov start # The default exclusion list, if it is not defined yet # Although there are unit tests defined in these packages (as examples), # we don't want to include them, by default, in our test suite! if (is.null(getOption("svUnit.excludeList"))) options(svUnit.excludeList = c("package:sv", "package:RUnit")) # Look if the SciViews-K Unit Komodo extension is installed # TODO: this causes more problems than solutions => temporarily deactivated! #.installUpgradeKomodoExtension() # Install a callback to update the list of units automatically in the GUI # Use the mechanism introduced in svSocket 0.9-48 to allow execution of # this task callback from a socket client too h <- .getTemp(".svTaskCallbackManager", default = NULL) if (!is.null(h)) h$add(guiSuiteAutoList, name = "guiSuiteAutoList") } # nocov end .onUnload <- function(libpath) { # nocov start # Delete the task callback h <- .getTemp(".svTaskCallbackManager", default = NULL) if (!is.null(h)) h$remove("guiSuiteAutoList") # Clear the list of units in the GUI client if (exists("koCmd", mode = "function")) get("koCmd")('sv.r.unit.getRUnitList_Callback("");') } # nocov end .packageName <- "svUnit" .komodoExtensionMinVersion <- "0.7.3" .installUpgradeKomodoExtension <- function() { # nocov start if (!exists("koCmd", mode = "function")) return() # Look if the SciViews-K Unit Komodo extension is installed and is of the # right version. Otherwise, propose to install, or update it xpiFile <- system.file("komodo", "sciviewskunit-ko.xpi", package = "svUnit") # Bug: sometimes this fails, preventing svUnit to load, despite it would # work well past this point. So, I put this in a try() to silently catch # the error and continue loading svUnit anyway (thanks Claudia Beleites) koVersion <- try( get("koCmd")('sv.socket.serverWrite(sv.r.unit.version + "." + sv.r.unit.release);'), silent = TRUE) if (inherits(koVersion, "try-error")) return() if (koVersion == "undefined.undefined") { # We need to install the extension cmd <- 'var res = ko.dialogs.okCancel("The SciViews-K Unit extension is required by svUnit",' cmd <- paste(cmd, '"OK", "Do you want to install the SciViews-K Unit extension now in Komodo?') cmd <- paste(cmd, 'You will be prompted for confirmation (click \'Install Now\')') cmd <- paste(cmd, 'and will have to restart Komodo at the end of the installation') cmd <- paste(cmd, '(click \'Restart Komodo\').", "svUnit");') cmd <- paste(cmd, ' if (res == "OK") { ko.open.URI("<<>>"); }', sep = "") get("koCmd")(cmd, data = xpiFile) } else if (.compareVersion(koVersion, .komodoExtensionMinVersion) == -1) { # We need to upgrade the extension cmd <- 'var res = ko.dialogs.okCancel("A newer SciViews-K Unit extension is required by svUnit",' cmd <- paste(cmd, '"OK", "Your SciViews-K Unit extension is too old for this version of svUnit.') cmd <- paste(cmd, 'Do you want to upgrade it now?') cmd <- paste(cmd, 'You will be prompted for confirmation (click \'Install Now\')') cmd <- paste(cmd, 'and will have to restart Komodo at the end of the installation') cmd <- paste(cmd, '(click \'Restart Komodo\').", "svUnit");') cmd <- paste(cmd, ' if (res == "OK") { ko.open.URI("<<>>"); }', sep = "") get("koCmd")(cmd, data = xpiFile) } } # nocov end .compareVersion <- function(a, b) { # This is the same as utils::compareVersion(), but we don't want # dependencies on utils if (is.na(a)) return(-1) if (is.na(b)) return(1) a <- as.integer(strsplit(a, "[\\.-]")[[1]]) b <- as.integer(strsplit(b, "[\\.-]")[[1]]) for (k in 1:length(a)) { if (k <= length(b)) { if (a[k] > b[k]) { return(1) } else if (a[k] < b[k]) { return(-1) } } else { return(1) } } if (length(b) > length(a)) { return(-1) } else { return(0) } } .kindLevels <- c("OK", "**FAILS**", "**ERROR**", "DEACTIVATED") .kind <- function(val = TRUE) { # TRUE or 1 -> 1 = "OK" # FALSE or 0 -> 2 = "**FAILS**" # -1 -> 3 = "**ERROR**" # -2 -> 4 = "DEACTIVATED" factor(.kindLevels[-(as.integer(val) - 2)], levels = .kindLevels) } .kindMax <- function(kinds) { # If there are no record, must be because all tests succeed! if (length(kinds) == 0) return(.kind(TRUE)) Kinds <- as.numeric(kinds) if (sum(Kinds, na.rm = TRUE) == 0) return(.kind(NA)) factor(.kindLevels[max(as.numeric(kinds), na.rm = TRUE)], levels = .kindLevels) } .formatTime <- function(x, secDigits = 0, minSec = 10^-secDigits, prepend = " run in") { # x is given in seconds, and it returns a pretty formatted string with time if (is.null(x) || is.na(x)[1]) return("") x <- as.numeric(x) Sec <- round(x %% 60, digits = secDigits) Min <- x %/% 60 Hour <- Min %/% 60 Min <- Min %% 60 Time <- prepend Time <- ifelse(Hour > 0, paste(Time, Hour, "h"), Time) Time <- ifelse(Min > 0 | Time != prepend, paste(Time, Min, "min"), Time) Time <- ifelse(Sec > minSec | Time != prepend, paste(Time, Sec, "sec"), Time) Time <- ifelse(Time == prepend, paste(prepend, "less than", minSec, "sec"), Time) Time[is.na(Time)] <- "" Time } # Test: .formatTime((0:10) * 400 + 0.56) .formatResult <- function(result, level = getOption("svUnit.strLevel")) { if (is.null(level)) { level <- 1 } else { level <- as.integer(level[1]) } if (level < 1) return("") # Capture the report returned by the str() function capture.str <- function(data, level) { rval <- NULL file <- textConnection("rval", "w", local = TRUE) sink(file, type = "output") sink(file, type = "message") on.exit({ sink(type = "output") sink(type = "message") close(file) }) str(data, max.level = level) cat("\n") rval } Str <- capture.str(result, level) paste(Str, collapse = "\n") } .logTest <- function(timing, test, msg = "", description = NULL) { .Log <- Log(description = description) # Determine the name of the test if (missing(test)) {# Is it defined globally? if (exists("..Test", envir = .Log, inherits = FALSE)) { test <- .Log$..Test } else {# Try to guess it from the call ret <- try(test <- as.character(sys.call(-2))[1], silent = TRUE) if (inherits(ret, "try-error") || is.na(test)) { # check...() probably called directly at the command line test <- "eval" # Convenient for collecting these tests together with tests run # inside runit*.R files (not embedded in test functions) } else if (test == "runTest") { # Special case for runTest(myTest) or runTest(test(foo)) test <- as.character(sys.call(-2))[2] } else if (test == "eval.with.vis") { test <- "eval" } } } # Do we need to create 'test'? if (!exists(test, envir = .Log, inherits = FALSE)) { if (msg == "") msg <- .Log$..Msg .Log[[test]] <- structure( data.frame( msg = character(), call = character(), timing = numeric(), kind = .kind(logical()), res = character(), obj = character(), file = character(), tag = character(), stringsAsFactors = FALSE), time = Sys.time(), stats = c(tests = 1, timing = timing), context = c(unit = as.character(.Log$..Unit), test = test, msg = paste(msg, collapse = "\n")), class = c("svTestData", "data.frame")) } else { # Just update stats attr(.Log[[test]], "stats") <- attr(.Log[[test]], "stats") + c(1, timing) } test } .logTestData <- function(test, msg, call, timing, val, kind = .kind(val), res, obj = .Log$..Obj, file = .Log$..File, tag = .Log$..Tag, printTest = getOption("svUnit.printTest")) { # Add these data to .lastTest .Log$.lastTest <- structure(data.frame( msg = msg, call = call, timing = timing, kind = kind, res = res, obj = obj, file = file, tag = tag, stringsAsFactors = FALSE), class = c("svTestData", "data.frame"), row.names = as.character(attr(.Log[[test]], "stats")["tests"])) # Add them also to the log of my test .Log[[test]][nrow(.Log[[test]]) + 1, ] <- .Log$.lastTest # Do we print detailed results for this test? if (is.null(printTest)) printTest <- !interactive() # Guess it from context if (isTRUE(printTest)) print(.Log$.lastTest) } .prepareUnit <- function(name, dir) { # Prepare for writing a test unit file dir <- gsub("\\\\", "/", as.character(dir)[1]) # Check that dir exists (do not create it!) if (!file.exists(dir) || !file.info(dir)$isdir) stop("'dir' must be an existing directory") # If dir is tempdir(), then, make sure there are no other runit*.R files # left (should not!) - One can store only one unit at a time in tempdir()! if (dir == gsub("\\\\", "/", tempdir())) { runitfiles <- list.files(dir, pattern = "^runit.*\\.[r|R]$", full.names = TRUE) unlink(runitfiles) } Unit <- file.path(dir, paste("runit", name, ".R", sep = "")) cat("## Test unit '", name, "'\n", sep = "", file = Unit) Unit } .writeSetUp <- function(unit, file = "", msg = "", tag = "", code = NULL) { # Write the .setUp() function in the test unit file # Here, we write a context to localize tested objects and test unit files catUnit <- function(...) cat(..., sep = "", file = unit, append = TRUE) catUnit('\n.setUp <-\n') catUnit('function () {\n') catUnit('\t## Specific actions for svUnit: prepare context\n') catUnit('\tif ("package:svUnit" %in% search()) {\n') catUnit('\t\t.Log <- Log() ## Make sure .Log is created\n') catUnit('\t\t.Log$..Unit <- "', unit, '"\n') catUnit('\t\t.Log$..File <- "', as.character(file)[1], '"\n') catUnit('\t\t.Log$..Obj <- ""\n') catUnit('\t\t.Log$..Tag <- "', tag, '"\n') catUnit('\t\t.Log$..Msg <- "', paste(msg, collapse = "\n"), '"\n') catUnit('\t\trm(..Test, envir = .Log)\n') catUnit('\t}\n') if (!is.null(code)) catUnit(paste("\t", code, collapse = "\n")) catUnit('}\n') } .writeTearDown <- function(unit, code = NULL, rm.unit = TRUE, rm.file = TRUE) { # Write the .tearDown() function in the test unit file # Here, we undo what was done in .setUp() catUnit <- function(...) cat(..., sep = "", file = unit, append = TRUE) catUnit('\n.tearDown <-\n') catUnit('function () {\n') if (!is.null(code)) catUnit(paste("\t", code, collapse = "\n")) catUnit('\t## Specific actions for svUnit: clean up context\n') catUnit('\tif ("package:svUnit" %in% search()) {\n') catUnit('\t\t.Log$..Unit <- ""\n') catUnit('\t\t.Log$..File <- ""\n') catUnit('\t\t.Log$..Obj <- ""\n') catUnit('\t\t.Log$..Tag <- ""\n') catUnit('\t\t.Log$..Msg <- ""\n') catUnit('\t\trm(..Test, envir = .Log)\n') catUnit('\t}\n') catUnit('}\n') } .writeTest <- function(unit, objname, pos = .GlobalEnv, obj = NULL) { # Make sure that the name of a test function is syntactically correct # and starts with 'test' if (regexpr("^test", objname) > -1) { testname <- objname } else { testname <- paste("test", objname, sep = "") } # Write the first line in the file catUnit <- function(...) cat(..., file = unit, append = TRUE) catUnit('\n"', testname, '" <-\n', sep = "") # Get the object if (missing(obj)) { # Look for 'objname' in 'pos' if (!exists(objname, where = pos)) { Test <- "" } else { Test <- test(get(objname, pos = pos)) } } else { Test <- test(obj) } if (is.character(Test)) { # Create a dummy test with DEACTIVATED entry indicating missing object body <- c( 'function() {', paste('\tDEACTIVATED("Object', objname, 'not found!")'), '}\n') } else if (is.null(Test)) { # Create a dummy test with DEACTIVATED entry indicating missing test body <- c( 'function() {', paste('\tDEACTIVATED("Object', objname, 'has no tests!")'), '}\n') } else { # Get the body of the test function capture.body <- function(Data) { rval <- NULL File <- textConnection("rval", "w", local = TRUE) sink(File) on.exit({ sink() close(File) }) dput(Data, file = File, control = "useSource") on.exit() sink() close(File) rval } body <- capture.body(Test) } # Write the body of the test function in the file catUnit(body, sep = "\n") } .runTest <- function(x, envir, test, objfile = "", unit = "", tag = "", msg = "") { # Run one test in a protected environment catching errors and warnings # and preparing a suitable context name <- sub("^test\\.(.+)\\.$", "\\1", test) # The context is written in the .Log, but previous context is saved # and restored on return .Log <- Log() # Make sure that .Log exists, or create a new one oContext <- c(Unit = .Log$..Unit, Obj = .Log$..Obj, File = .Log$..File, Msg = .Log$..Msg, Tag = .Log$..Tag) on.exit({ .Log$..Unit <- as.character(oContext[1]) .Log$..Obj <- as.character(oContext[2]) .Log$..File <- as.character(oContext[3]) .Log$..Msg <- as.character(oContext[4]) .Log$..Tag <- as.character(oContext[5]) }) .Log$..Unit <- unit # The unit file .Log$..Obj <- name # Name of the tested object .Log$..File <- objfile # Where is the code source of 'name'? .Log$..Msg <- paste(msg, collapse = "\n") # Message for this test .Log$..Tag <- tag # A tag in objfile to locate code # Define the test and save possible existing definition to restore it if (exists("..Test", envir = .Log, inherits = FALSE)) { oTest <- .Log$..Test on.exit(.Log$..Test <- oTest, add = TRUE) } else on.exit(rm("..Test", envir = .Log), add = TRUE) .Log$..Test <- test # Define the name of the test if (missing(envir)) { # The environment where the test is run envir <- new.env(parent = .GlobalEnv) envir[[test]] <- x # A copy of the test code envir$.setUp <- function() {} # Fake .setUp envir$.tearDown <- function() {} # Fake .tearDown } # We need this installed in our sandbox .TestEnv to run the test envir$.LogWarnings <- list() # A list to collect warnings # Clear the corresponding log, if it exists if (exists(test, envir = .Log, inherits = FALSE)) rm(list = test, envir = .Log) # Evaluate the test function in .testEnv, catching errors owarn <- getOption("warn") on.exit(options(warn = owarn), add = TRUE) if (isTRUE(getOption("svUnit.StopOnWarning"))) { nwarn <- 2 } else { nwarn <- -1 } options(warn = nwarn) # Evaluate the test function in the .TestEnv environment cmd <- paste("evalq(.LogRes <- try( { .setUp(); `", test, "`(); .tearDown() }, silent = TRUE), envir = envir)", sep = "") eval(parse(text = cmd)) # Analyze error => is it a deactivation or error in the code? Res <- envir$.LogRes if (inherits(Res, "try-error")) { # We record this as a test returning **ERROR** or DEACTIVATED .logTest(0, test) # Did we encountered a DEACTIVATED() command or something else? if (regexpr("DEACTIVATED\\(", Res) > -1) { Msg <- sub("^[^:]+: *", "", as.character(Res)) Msg <- sub("\n$", "", Msg) .logTestData(test, msg = Msg, call = "", timing = NA, val = -2, res = "\n") } else { # This is an error (something wrong with the code in the test fun) .logTestData(test, msg = "", call = deparse(sys.call()), timing = NA, val = -1, res = paste(Res, collapse = "\n")) } } test } .assignTemp <- function(x, value) assign(x, value, envir = .TempEnv()) .getTemp <- function(x, default = character(0)) { if (exists(x, envir = .TempEnv(), inherits = FALSE)) { return(get(x, envir = .TempEnv(), inherits = FALSE)) } else {# Variable not found, return the default value return(default) } } .TempEnv <- function() { pos <- match("SciViews:TempEnv", search()) if (is.na(pos)) {# Must create it `SciViews:TempEnv` <- list() Attach <- function(...) get("attach", mode = "function")(...) Attach(`SciViews:TempEnv`, pos = length(search()) - 1) rm(`SciViews:TempEnv`) pos <- match("SciViews:TempEnv", search()) } pos.to.env(pos) } r-cran-svunit-1.0.6/R/svUnit-package.R000066400000000000000000000162561412072605000174420ustar00rootroot00000000000000#' @details #' The SciViews 'svUnit' package defines a framework for testing R code, not #' unlike jUnit for Java. It is inspired on the `checkxxx()` functions from the #' 'RUnit' package and the same test unit files should be compatible with both #' 'svUnit' and 'RUnit'. However, the internal implementation is completely #' different and svUnit can also be used interactively, while 'RUnit' is only #' designed to run test units written in files on disks. #' #' The test unit framework provided in 'svUnit' is based on tests, also called #' assertions, implemented in `checkxxx()` functions. For instance, the #' `checkTrue(expr)` function check if its 'expr' argument returns `TRUE`. #' Results of these assertions are collected in a centralized logger located in #' the `.Log` object in `.GlobalEnv`. This is a 'svSuiteData' object with data #' about the context of the tests (see for instance, [lastTest()], [lastSuite()] #' or `metadata(.Log)`). #' #' Assertions can give three results: (1) `TRUE` if success, (2) `FALSE` in #' case of failure (in our example, 'expr' in `checkTrue(expr)` did not return #' `TRUE`), and (3) `NA` if the code in 'expr' cannot be parsed or executed #' correctly. All these errors or failures are catch and recorded in the logger, #' as individual 'svTestData' objects. #' #' Both the logger ('svSuiteData' object) and test records inside it #' ('svTestData' objects) have convenient methods to visualize information they #' contain: [print()], [summary()] and [stats()] methods. Access to the #' individual test records in the logger is done with list-like instructions: #' `.Log$mytest` returns the 'svTestData' object named 'mytest', itself the #' result of running test in the 'mytest' test function (i.e., #' `runTest(mytest)`, see hereunder). Assertions run at the command line, #' outside of specific contexts provided by test functions, test units and #' test suites (see hereunder) are recorded under the 'eval' 'svTestData' object #' in the logger (i.e., `.Log$eval`). #' #' Since a 'svSuiteData' object (the logger) is also an environment, you can get #' the list of all test records it contains using `ls(.Log)`, and you can #' eliminate a given test record using `rm(mytest, envir = .Log)`. #' #' Test cases are collections of assertions with the satellite code needed to #' build example or situations to be tested. They are collected together in #' argument-less functions with class being 'svTest'. See `?svTest` for #' further explanations and a couple of example test cases/test functions. #' #' In its simplest instance, a test function is defined as a separate R object #' loaded in memory (unlike RUnit where all test must be defined in files). You #' run it simply by using `runTest(mytest)`. A slightly more structured way #' to work is to attach the test function to the object it testes. You use #' `test(myobj) <- testmyobj` to do so, and retrieve it with `test(myobj)`. Now, #' the test function always follows the tested object. Testing the object is #' still simple by using `runTest(myobj)`, which is totally equivalent to #' `runTest(test(myobj))`. One can determine if an object has a test function #' associated, or is a test function itself by using `is.test(myobj)`. #' #' Several test functions can be collected together in so-called test units. A #' test unit only exists on disk. It is a file named 'runit*.R' containing #' sourceable R code with test functions having names starting with 'test' #' (unlike 'RUnit', the default convention of file names starting with 'runit' #' and test function names starting with 'test' is not customizable in #' 'svUnit'). One can also define special `.setUp()` and `.tearDown()` functions #' in the test unit. The first function will be run before each test function, #' and the latter one will be run after it. Test units are created manually, or #' from a collection of objects with associated test functions loaded in an #' environment (usually `.GlobalEnv`) thanks to the [makeUnit()] method. These #' units should be mutually compatible with those used in the 'RUnit' package #' (at least this is verified with version 0.4-17 of 'RUnit'). #' #' Test units defined for packages should be located in the package /runitTests #' subdirectory (/inst/runitTests for source of the package) or one of its #' subdirectories. That way, they are located automatically by the function #' [svSuiteList()] that also automatically detects all objects with associated #' test functions loaded in `.GlobalEnv`. Test suites are 'svSuite' #' objects with a list of test units or test objects to collect in the suite. #' Thus, [svSuiteList()] automatically builds such a suite with all tests it #' finds in R, with many possibilities to filter packages' test units, objects' #' test functions, or to add non standard directories with test units, for #' instance. See `?svSuite` for more details on creating and using these #' suites. #' #' A GUI (Graphical User Interface) is provided to automatically build and run #' tests suites and to get a graphical (tree) interactive report of the results #' in the Komodo Edit or IDE code editor, together with the SciViews-K #' extension. If you want to use this (optional) GUI, you have to install #' required software components on your machine. #' #' Finally, the 'svUnit' framework is compatible with `R CMD check` (see the #' manual "Writing R extensions"). You simply define man pages (.Rd files) with #' an example section running selected test units from your package. The #' function [errorLog()] is designed to generate and error if one or more tests #' failed or raised an error during `R CMD check`, and it should be used at the #' end of the example that runs your unit test(s). That way, `R CMD check` is #' interrupted and a detailed report of the tests that failed or raised an error #' is printed. See an example in `?unitTests.svUnit`. #' #' @author Written by Ph. Grosjean, inspired from the general design of the #' 'RUnit' package by Thomas Konig, Klaus Junemann & Matthias Burger. #' #' @references There is a huge literature and unit testing. An easy starting #' point is: \url{https://en.wikipedia.org/wiki/Unit_test}. #' #' @examples #' # Clear the logger #' clearLog() #' #' # Design and attach a simple test function to an object #' foo <- function(x, y = 2) #' return(x * y) #' testfoo <- function() { #' #DEACTIVATED() # Use this to deactive the test (notice placed in the log) #' checkEqualsNumeric(5, foo(2), "Check return of foo()") #' checkException(foo("b"), "Wrong first argument") #' checkException(foo(2, "a"), "Wrong second argument") #' } #' # Attach this to the foo function #' test(foo) <- testfoo #' #' # Run this test #' runTest(foo) #' #' # Inspect the result #' ls(.Log) #' .Log$`test(foo)` #' # This test fails. You see that the test function requires that foo(2) = 5 #' # and the actual implementation returns 4. This is a trivial, useless example, #' # but you are supposed to correct the function. For instance: #' foo <- function(x, y = 2) #' return(x * y + 1) #' test(foo) <- testfoo #' #' (runTest(foo)) # Now, that's fine! #' @keywords internal "_PACKAGE" # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start ## usethis namespace: end NULL r-cran-svunit-1.0.6/R/unitTests.svUnit.R000066400000000000000000000020721412072605000200410ustar00rootroot00000000000000#' Unit tests for the package 'svUnit' #' #' Performs unit tests defined in this package by running #' `example(unitTests.svUnit)`. Tests are in `runit*.R` files located in the #' '/unitTests' subdirectory or one of its subdirectories ('/inst/unitTests' and #' subdirectories in package sources). #' #' @name unitTests.svUnit #' @author Philippe Grosjean #' @keywords utilities #' @examples #' if (require(svUnit)) { #' clearLog() #' runTest(svSuite("package:svUnit"), "svUnit") #' \donttest{ #' # Tests to run with example() but not with R CMD check #' runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass") #' } #' \dontrun{ #' # Tests to present in ?unitTests.svUnit but not run automatically #' # Run all currently loaded packages test cases and test suites #' runTest(svSuiteList(), "AllTests") #' } #' \dontshow{ #' # Put here test units you want to run during R CMD check but #' # don't want to show or run with example(unitTests.svUnit) #' } #' # Check errors at the end (needed to interrupt R CMD check) #' errorLog() #' } NULL r-cran-svunit-1.0.6/build/000077500000000000000000000000001412072605000153225ustar00rootroot00000000000000r-cran-svunit-1.0.6/build/vignette.rds000066400000000000000000000003401412072605000176560ustar00rootroot00000000000000mQ 0 9D=  ^봸uU7ܙGHϗv#l]l .sP 9ȃӯn;U,:,Ɋd.d$WVR\ $1*}ϓ*r Y;ı?FVF8b&R~sޚ݁P9Ns6rM~dY30 !RE5z[%r-cran-svunit-1.0.6/inst/000077500000000000000000000000001412072605000152005ustar00rootroot00000000000000r-cran-svunit-1.0.6/inst/CITATION000066400000000000000000000014251412072605000163370ustar00rootroot00000000000000citHeader("To cite SciViews-R in publications, please use:") citEntry(entry="Manual", title = "SciViews-R", author = personList(as.person("Philippe Grosjean")), organization = "UMONS", address = "MONS, Belgium", year = version$year, url = "https://www.sciviews.org/SciViews-R/", textVersion = paste("Grosjean, Ph. (", version$year, "). ", "SciViews-R. ", "UMONS, Mons, Belgium. ", "URL https://www.sciviews.org/SciViews-R/.", sep = "") ) citFooter("We have invested a lot of time and effort in creating SciViews-R,", "please cite it when using it together with R.", "See also", sQuote("citation()"), "for citing R.") r-cran-svunit-1.0.6/inst/TestsvUnit.R000066400000000000000000000021741412072605000174570ustar00rootroot00000000000000# To be promoted as a demo? library(svUnit) options(svUnit.excludeList = NULL) foo <- function(x, y = 2) x * y #is.test(foo) # No # Create test cases for this function test(foo) <- function() { checkEqualsNumeric(4, foo(2)) checkEqualsNumeric(5, foo(2, 3)) # Should fail checkTrue(is.test(foo)) checkTrue(is.test(test(foo))) checkIdentical(attr(foo, "test"), test(foo)) checkException(foo(2, dfgfg)) #DEACTIVATED("My deactivation message") checkException(foo("bb")) } test_simple <- svTest(function() { checkTrue(1 == 1, "test1") checkTrue(1 == 2, "test2") checkTrue(1 == var, "test3") }) # This is for an example! clearLog() imax <- 3 jmax <- 100 l <- 50 Rprof() for (i in 1:imax) { .LogTest <- paste("Test", i, sep = "") .LogTag <- paste("#", i, sep = "") res <- system.time({ for (j in 1:jmax) checkTrue(i <= j, "My test") }, gcFirst = TRUE)[3] print(res) flush.console() } Rprof(NULL) summaryRprof()$by.self summary(.Log[[.LogTest]]) rm(.LogTest, .LogTag) unlink("Rprof.out") Rprof() system.time( for (i in 1:100) runTest(test.bar))[3] Rprof(NULL) summaryRprof() unlink("Rprof.out") r-cran-svunit-1.0.6/inst/WORDLIST000066400000000000000000000010171412072605000163710ustar00rootroot00000000000000addins appveyor Beleites checkxxx ci CMD Comput customizable cron devtools dir dirs DokuWiki DSC eval excludeList expr Frasca github Github GlobalEnv http https Ihaka Javascript Jeffries Junemann Jünemann junit jUnit Komodo Komodo’s Konig König MYDIR myPkg MYPKG mytest ORCID PGK Ph pkgdown pos roxygen ROxygen RStudio runit RUnit runitTests runittotestthat SciViews sourceable sv svGUI svSocket svSuite svSuiteData svTest svTestData svUnitData testit testthat testthis tooltip tuwien unitTests UseR vectorized Wutzler www r-cran-svunit-1.0.6/inst/doc/000077500000000000000000000000001412072605000157455ustar00rootroot00000000000000r-cran-svunit-1.0.6/inst/doc/svUnit.R000066400000000000000000000113071412072605000173620ustar00rootroot00000000000000## ----setup, include = FALSE--------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ## ---- eval=FALSE-------------------------------------------------------------- # install.packages("svUnit") ## ----------------------------------------------------------------------------- library(svUnit) Square <- function(x) return(x^2) test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(10, Square(3)) # This intentionally fails checkEqualsNumeric(9, SSSquare(3)) # This raises error checkEqualsNumeric(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } clearLog() (runTest(Square)) ## ----------------------------------------------------------------------------- library(svUnit) # Create two R functions that include their own test cases Square <- function(x) return(x^2) test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(c(4, 9), Square(2:3)) checkException(Square("xx")) } Cube <- function(x) return(x^3) test(Cube) <- function() { checkEqualsNumeric(27, Cube(3)) checkEqualsNumeric(c(8, 28), Cube(2:3)) checkException(Cube("xx")) } # Add a separate test case test_Integrate <- svTest(function() { checkTrue(1 < 2, "check1") v <- c(1, 2, 3) # The reference w <- 1:3 # The value to compare to the reference checkEquals(v, w) }) ## ----------------------------------------------------------------------------- clearLog() runTest(Square) runTest(test_Integrate) Log() ## ----------------------------------------------------------------------------- runTest(Cube) Log() ## ----------------------------------------------------------------------------- clearLog() checkEqualsNumeric(1, log(exp(1))) checkException(log("a")) checkTrue(1 == 2) Log() ## ----------------------------------------------------------------------------- # Clear test exclusion list for running all test suites options(svUnit.excludeList = NULL) # Clear the logger clearLog() # Run all currently defined tests runTest(svSuiteList(), name = "AllTests") # Get some statistics stats(Log())[, 1:3] # A slightly different presentation than with print summary(Log()) # Metadata collected on the machine where tests are run metadata(Log()) # List content of the log ls(Log()) ## ----------------------------------------------------------------------------- myTest <- Log()$testCube class(myTest) myTest summary(myTest) stats(myTest) ## ----------------------------------------------------------------------------- ls(Log()) rm(test_R, envir = Log()) ls(Log()) ## ----------------------------------------------------------------------------- test_function <- function() { checkTrue(1 < 2, "check1") v <- c(1, 2, 3) # The reference w <- 1:3 # The object to compare to the reference checkEqualsNumeric(v, w) } # Turn this function into a test test_function <- as.svTest(test_function) is.svTest(test_function) ## ----------------------------------------------------------------------------- clearLog() runTest(test_function) Log() ## ----------------------------------------------------------------------------- # A very simple function Square <- function(x) return(x^2) # A test case to associate with the Square() function test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } is.test(Square) # Does this object contain tests? ## ----------------------------------------------------------------------------- test(Square) ## ----------------------------------------------------------------------------- runTest(Square) Log() # Remember we didn't clear the log! ## ---- eval=FALSE-------------------------------------------------------------- # # Create a test unit on disk and view its content # unit <- makeUnit(Square) # file.show(unit, delete.file = TRUE) ## ----------------------------------------------------------------------------- example(unitTests.svUnit) ## ----------------------------------------------------------------------------- # Reset default exclusion list options(svUnit.excludeList = c("package:sv", "package:RUnit")) # List all currently available tests svSuiteList() ## ----------------------------------------------------------------------------- # Clear exclusion list options(svUnit.excludeList = NULL) svSuiteList() ## ----------------------------------------------------------------------------- (mySuite <- svSuiteList()) ## ---- eval=FALSE-------------------------------------------------------------- # myUnit <- makeUnit(mySuite, name = "ExampleTests") # file.show(myUnit, delete.file = TRUE) ## ----------------------------------------------------------------------------- clearLog() runTest(mySuite) summary(Log()) r-cran-svunit-1.0.6/inst/doc/svUnit.Rmd000066400000000000000000001162731412072605000177130ustar00rootroot00000000000000--- title: "svUnit - A framework for unit testing in R" author: "Philippe Grosjean (phgrosjean@sciviews.org)" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: fig_caption: yes vignette: > %\VignetteIndexEntry{svUnit - A framework for unit testing in R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Introduction Unit testing (see https://en.wikipedia.org/wiki/Unit_test) is an approach successfully used to develop software, and to ease code refactoring for keeping bugs to the minimum. It is also the insurance that the software is doing the right calculation (quality insurance). Basically, a test just checks if the code is running and is producing the correct answer/behavior in a given situation. As such, unit tests are build in R package production because all examples in documentation files, and perhaps, test code in `/tests` subdirectory are run during the checking of a package (`R CMD check `). However, the R approach lacks a certain number of features to allow optimal use of unit tests as in extreme programming (test first – code second): * Tests are related to package compilation and cannot easily be run independently (for instance, for functions developed separately). * Once a test fails, the checking process is interrupted. Thus one has to correct the bug and launch package checking again... and perhaps get caught by the next bug. It is a long and painful process. * There is no way to choose one or several tests selectively: all are run or not (depending on command line options) during package checking. * It is very hard, or near to impossible to program in R in a test driven development (*write tests first*) with the standard tools (https://en.wikipedia.org/wiki/Test-driven_development). * Consequently, the '*test-code-simplify*' cycle is not easily accessible yet to R programmer, because of the lack of an interactive and flexible testing mechanism providing immediate, or quasi immediate feedback about changes made. * We would like also to emphasize that test suites are not only useful to check code, they can also be used to check data, or the pertinence of analyses. ## Unit testing in R without 'svUnit' Besides the "regular" testing mechanism of R packages, one can find the 'testthat' and 'RUnit' packages on CRAN (https://CRAN.R-project.org/package=testthat and https://CRAN.R-project.org/package=RUnit, respectively)^[There is also a very simple testing system in 'testit' (https://CRAN.R-project.org/package=testit) using only one assertion: `assert()` for all test. Other interesting R packages on CRAN are 'testthis' (https://CRAN.R-project.org/package=testthis), which adds more tools and an RStudio addins around 'testthat', and 'runittotestthat' (https://CRAN.R-project.org/package=runittotestthat) which, as its name implies, automatically converts 'RUnit' tests into 'testthat' equivalents.]. Another package used to provide an alternate implementation of test unit: 'butler', but it is archived because it is not maintained any more and has given up in favor of 'RUnit'. The 'testthat' package is heavily used by the UseR community (there are many more packages on CRAN that use it than any other testing system). It implements R testing in a rather original way. 'RUnit', on the other hand sticks with the framework initially defined by junit for Java and largely developed for many different languages. In this document, we will not discuss further about 'testthat'. We will focus on the junit-like framework implemented in 'RUnit' and 'svUnit'. 'RUnit' implements the following features: * **Assertions**, `checkEquals()`, `checkEqualsNumeric()`, `checkIdentical()` and `checkTrue()` and negative tests (tests that check error conditions, `checkException()`). * Assertions are grouped into R functions to form one **test function** that runs a series of related individual tests. It is easy to temporarily inactivate one or more tests by commenting lines in the function. To avoid forgetting tests that are commented out later on, there is special function, named `DEACTIVATED()` that tags the test with a reminder for your deactivated items (i.e., the reminder is written in the test log). * A series of test functions (whose name typically start with `test....`) are collected together in a source-able R code file (name starting with `runit....`) on disk. This file is called a **test unit**. * A **test suite** (object `RUnitTestSuite`) is a special object defining a battery of tests. It points to one or several directories containing test units. A test suite is defined by `defineTestSuite()`. * One or more test suites can be run by calling `runTestSuite()`. There is a shortcut to define and run a test suite constituted by only one test unit by using the function `runTestFile()`. Once the test is run, a `RUnitTestData` object is created that contains all the information collected from the various tests run. * One can print a synthetic report (how many test units, test functions, number of errors, fails and deactivated item), or get a more extensive `summary()` of the tests with indication about which ones failed or produced errors. The function `printTextProtocol()` does the same, while `printHTMLProtocol()` produces a report in HTML format. * 'RUnit' contains also functions to determine which code is run in the original function when tested, in order to detect the parts of the code not covered by the test suite (code coverage function `inspect()` and function `tracker()`). As complete and nice as 'RUnit' is, there is no tools to integrate the test suite in a given development environment (IDE) or graphical user interface (GUI), as far as we know. In particular, there is no real-time reporting mechanism used to easy the *test-code-simplify* cycle. The way tests are implemented and run is left to the user, but the implementation suggests that the authors of 'RUnit' mainly target batch execution of the tests (for instance, nightly check of code in a server), rather that real-time interaction with the tests. There is also no integration with the "regular" `R CMD check` mechanism of R in 'RUnit'. ## Unit testing framework for R with 'svUnit' Our initial goal was to implement a GUI layer on top of 'RUnit', and to integrate test units as smoothly as possible in a code editor, as well as, making tests easily accessible and fully compatible with `R CMD check` on all platforms supported by R. Ultimately, the test suite should be easy to create, to use interactively, and should be able to test functions in a complex set of R packages. However, we encountered several difficulties while trying to enhance 'RUnit' mechanism. When we started to work on this project, 'RUnit' (version 0.4-17) did not allow to subclass its objects (version 0.4-19 solved this problem). Moreover, its `RUnitTestData` object is optimized for quick testing, but not at all for easy reviewing of its content: it is a list of lists of lists,... requiring embedded for loop and `lapply()` / `sapply()` procedures to extract some content. Moreover, 'RUnit' is implemented as S4 classes, requiring the heavy 'methods' package to be loaded, making it impossible to use in a lightweight environment without it, or to test R without the 'methods' package loaded. Finally, the concept of test units as source-able files on disk is a nice idea, but it is too rigid for quick writing test cases for objects not associated (yet) with an R packages. We did a first implementation of the 'RUnit' GUI based on these objects, before realizing that it is really not designed for such an use. So, we decide to write a completely different unit testing framework in R: 'svUnit', but we make it test code compatible with 'RUnit' (i.e., the engine and objects used are totally different, but the test code run in 'RUnit' or 'svUnit' is interchangeable). Finally, 'svUnit' is also designed to be integrated in the SciViews GUI on top of Komodo IDE (https://www.activestate.com/products/komodo-ide/), and to approach extreme programming practices with automatic code testing while you write it. A rather simple interface is provided to link and pilot 'svUnit' from any GUI/IDE, and the Komodo IDE implementation could be used as an example to program similar integration panels for other R GUIs. 'svUnit' also formats its report with *creole wiki* syntax. It is directly readable, but it can also be displayed in a much nicer way using any wiki engine compatible with the creole wiki language. It is thus rather easy to write test reports in wiki servers, possibly through nightly automatic process for your code, if you like. This vignette is a guided tour of 'svUnit', showing its features and the various ways you can use it to test your R code. ## Installation The 'svUnit' package is available on CRAN (https://CRAN.R-project.org/package=svUnit), and its latest development version is also available on Github (https://github.com/SciViews/svUnit). You can install it with^[Install the development version with `devtools::install_github("SciViews/svUnit").]: ```{r, eval=FALSE} install.packages("svUnit") ``` This package has no required dependencies other than R >= 1.9.0 and 'utils' ('XML' is also used for some export formats, but it is not required to run the tests). However, if you would like to use its interactive mode in a GUI editor, you must also install Komodo Edit or Komodo IDE and SciViews-K, or use RStudio. Its 'pkgdown' web site is at https://www.sciviews.org/svUnit/. Once the 'svUnit' package is installed, you can check it is working correctly on your machine with the following example code: ```{r} library(svUnit) Square <- function(x) return(x^2) test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(10, Square(3)) # This intentionally fails checkEqualsNumeric(9, SSSquare(3)) # This raises error checkEqualsNumeric(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } clearLog() (runTest(Square)) ``` > Although test unit code is compatible with both 'svUnit' and 'RUnit', do not load both packages in R memory at the same time, or you will badly mix incompatible code! ## Overview of 'svUnit' You ensure that code you write in your R functions does the expected work by defining a battery of tests that will compare the output of your code with reference values. In 'svUnit', the simplest way to define such a battery of tests is by attaching it to functions loaded in R memory^[In fact, you can attach 'svUnit' tests to any kind of R object, not only function. This could be useful to test S3/S4 objects, or even, datasets.]. Of course, you can also define batteries of tests that are independent of any R object, or that check several of them together (so called, *integration tests*). Here is a couple of examples: ```{r} library(svUnit) # Create two R functions that include their own test cases Square <- function(x) return(x^2) test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(c(4, 9), Square(2:3)) checkException(Square("xx")) } Cube <- function(x) return(x^3) test(Cube) <- function() { checkEqualsNumeric(27, Cube(3)) checkEqualsNumeric(c(8, 28), Cube(2:3)) checkException(Cube("xx")) } # Add a separate test case test_Integrate <- svTest(function() { checkTrue(1 < 2, "check1") v <- c(1, 2, 3) # The reference w <- 1:3 # The value to compare to the reference checkEquals(v, w) }) ``` When you run a test in 'svUnit', it logs its results in a centralized logger. The idea is to get a central repository for tests that you can manipulate as you like (print, summarize, convert, search, display in a GUI, etc.). If you want to start new tests, you should first clean this logger by `clearLog()`. At any time, the logger is accessible by `Log()`, and a summary of its content is displayed using `summary(Log()`). So, to run test for your `Square()` function as well as your `test_Integrate` integration test, you simply do the following: ```{r} clearLog() runTest(Square) runTest(test_Integrate) Log() ``` In this report, you see that all your tests succeed. Note that 'svUnit' is making the distinction between a test that **fails** (the code is run correctly, but the result is different from what was expected) and code that raises **error** (it was not possible to run the test because its code is incorrect, or for some other reasons). Note also that the function `checkException()` is designed to explicitly test code that should `stop()` in R^[`checkException()` can also track `warning()` messages with this little trick: first convert all warnings into errors with `owarn <- options(warn = 2)$warn`, run the code that should generate a warning inside `checkException()`, and then restore default warning behavior with `options(warn = owarn)`.]. So, if that test does not raises an exception, it is considered to have failed. This is useful to check that your functions correctly trap wrong arguments, for instance, like in `checkException(Square("xx"))` here above (a character string is provided where a numerical value is expected). Now, let's look what happens if we test the `Cube()` function without clearing the logger: ```{r} runTest(Cube) Log() ``` We note this: 1. When a test succeeds, nothing is printed by default (result is returned invisibly). But when a test fails, or raises errors, the guilty test(s) results are printed. We expected `c(8, 28)` (made intentionally wrong for the sake of the demonstration) from `Cube(2:3)` in `checkEqualsNumeric(c(8, 28), Cube(2:3))`, and (of course), we got `c(8, 27)`. This test shows how 'svUnit' presents test failures. 2. The results of the tests on `Cube()` are added to the previous report. So, it is possible to build rather easily reports that summarize tests on several objects, by adding test results in the logger sequentially. 'svUnit' does this naturally and transparently. Starting a new report is equally simple: just use `clearLog()`... ### Assertions in 'svUnit' The most basic item in a test suite is an **assertion** represented by a `checkXXX()` function in 'svUnit'/'RUnit'. Five such functions are currently defined: * `checkEquals(current, target)` determines if data in `target` is the same as data in `current`. * `checkEqualsNumeric(current, target)` does the same but allows for a better comparison for numbers (variation allowed within a tolerance window). * `checkIdentical(current, target)` checks whether two R objects are strictly identical. * `checkTrue(expr)` only succeed if `expr` is `TRUE`. Note a difference in 'svUnit' and 'RUnit' (at least, in its version 0.4-17): the 'RUnit' function is not vectorized and `expr` must return a single atomic logical value. The corresponding 'svUnit' function also accepts a vector of logical values. In this case, all elements of the vector must be `TRUE` for the test to succeed. When you make sure that `expr` always returns a single logical value (for instance by using `all(expr)`), both functions should be compatible. * `checkException(expr)` verifies that a given code raises an exception (in R, it means that a line of code with `stop()` is executed). * `DEACTIVATED()` makes sure that all tests following this instruction (in a test function, see next paragraph) are deactivated, and inserts a reminder in the logger about the fact that some tests are deactivated in this suite. For all these functions, you have an additional optional argument `msg =` where you can provide a (short) message to print in front of each text in the report. These functions return invisibly: `TRUE` if the test succeeds, or `FALSE` if it fails (code is executed correctly, but does not pass the test), and `NA` if there was an error (the R code of the test was not executed correctly). Moreover, these functions record the results, the context of the test and the timing in a logger (object `svSuiteData` inheriting from `environment`) called `.Log` and located in the user’s workspace. So, executing a series of assertions and getting a report is simply done as (in its simplest form, you can use the various `checkXXX()` functions directly at the command line): ```{r} clearLog() checkEqualsNumeric(1, log(exp(1))) checkException(log("a")) checkTrue(1 == 2) Log() ``` As you can see, the `checkXXX()` functions work hand in hand with the test logger (the `checkXXX()` functions also return the result of the test invisibly, so, you can also assign it to a variable if you like). These function are mainly used for their side-effect of adding an entry to the logger. The last command `Log()` prints the content of the logger. You see how a report is printed, with a first part being a short summary by categories (assertions run at the command line are placed automatically in the `eval` category: there is no better context known for them. Usually, those assertions should be placed in test functions, or in test units, as we will see later in this manual, and the category will reflect this organization). A detailed report on the tests that failed or raised an error is also printed at the end of the report. Of course, the same report is much easier to manipulate from within the graphical tree in the Komodo’s **R Unit** tab, but the simple text report in R has the advantage of being independent from any GUI, and from Komodo. It can also be generated in batch mode. Last, but not least, it uses a general Wiki formatting called creole wiki (http://www.wikicreole.org/wiki/Creole1.0). !['svUnit' test report as it appears when inserted in a wiki page (DokuWiki engine with the creole plugin installed). Note the summary of results at the top left of the page, and the clickable table of contents with detailed entries to easily navigate to the test log you are consulting). Timing of the test is also clearly indicated, since it is a complementary but important information (if a test succeeds, but calculation is way too long, it is good to know it)!](svUnit_wikiReport.png) The figure illustrates the way the same report looks like in DokuWiki with the creole plugin (http://www.wikicreole.org/wiki/DokuWiki) installed. Note the convenient table of content that lists here a clickable list of all tests run. From this point, it is relatively easy to define nightly cron task jobs on a server to run a script that executes these tests and update a wiki page (look at your particular wiki engine documentation to determine how you can access wiki pages on the command line). ### Manipulating the logger data The 'svUnit' package provides a series of functions to manipulate the logger from the command line, in particular, `stats()`,`summary()`, `metadata()` and `ls()`: ```{r} # Clear test exclusion list for running all test suites options(svUnit.excludeList = NULL) # Clear the logger clearLog() # Run all currently defined tests runTest(svSuiteList(), name = "AllTests") # Get some statistics stats(Log())[, 1:3] # A slightly different presentation than with print summary(Log()) # Metadata collected on the machine where tests are run metadata(Log()) # List content of the log ls(Log()) ``` As you can see, `ls()` lists all components recorded in the test suite. Each component is a `svTestData` object inheriting from `data.frame`, and it can be easily accessed through the `$` operator. There are, of course similar methods defined for those `svTestData` objects, like `print()`, `summary()` and `stats()`: ```{r} myTest <- Log()$testCube class(myTest) myTest summary(myTest) stats(myTest) ``` As the logger inherits from `environment`, you can manage individual test data the same way as objects in any other environment. For instance, if you want to delete a particular test data without touching to the rest, you can use: ```{r} ls(Log()) rm(test_R, envir = Log()) ls(Log()) ``` As we will see in the following section, 'svUnit' proposes several means to organize individual assertions in modules: **test functions**, **test units** and **test suites**. This organization is inspired from 'RUnit', but with additional ways of using tests in interactive sessions (for instance, the ability to attach a test to the objects to be tested). ### Test function The first organization level for grouping assertions together is the **test function**. A test function is a function without arguments whose name must start with `test`. It typically contains a series of assertions applied to one object, method, or function to be checked (this is not obligatory, assertions are not restricted to one object, but good practices strongly suggest such a restriction). Here is an example: ```{r} test_function <- function() { checkTrue(1 < 2, "check1") v <- c(1, 2, 3) # The reference w <- 1:3 # The object to compare to the reference checkEqualsNumeric(v, w) } # Turn this function into a test test_function <- as.svTest(test_function) is.svTest(test_function) ``` A test function should be made a special object called `svTest`, so that 'svUnit' can recognize it. This `svTest` object, is allowed to live on its own (for instance, in the user's workspace, or anywhere you like). It can be defined in a R script, be saved in a `.RData` file, etc... Note that this is very different from 'RUnit' where test must always be located in a unit test file on disk). In 'svUnit' (not 'RUnit'), you run such a test simply by using `runTest()`, which returns the results invisibly and add it to the logger: ```{r} clearLog() runTest(test_function) Log() ``` Now, a test function is most likely designed to test an R object. The 'svUnit' package also provides facilities to attach the test function to the object to be tested. Hence, the test cases and the tested object conveniently form a single entity that one can manipulate, copy, save, reload, etc. with all the usual tools in R. This association is simply made using `test(myobj) <-`: ```{r} # A very simple function Square <- function(x) return(x^2) # A test case to associate with the Square() function test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } is.test(Square) # Does this object contain tests? ``` One can retrieve the test associated with the object by using: ```{r} test(Square) ``` And of course, running the test associated with an object is as easy as: ```{r} runTest(Square) Log() # Remember we didn't clear the log! ``` Now that you master test functions, you will discover how you can group them in logical **units**, and associate them to R packages. ### Test units An **unit** is a coherent piece of software that can be tested separately from the rest. Typically, a R package is a structured way to compile and distribute such code units in R. Hence, we need a mean to organize tests related to this "unit" conveniently. Since a package can contain several functions, data frames, or other objects, our unit should collect together individual test functions related to each of these objects that compose our package. Also, the test unit should accommodate the well-define organization of a package, and should integrate in the already existing testing features of R, in particular, `R CMD check`. In both 'RUnit', and 'svUnit', one can define such test units, and they are made code compatible between the two implementations. A test unit is a source-able text file that contains one or more test functions, plus possibly `.setUp()` and `.tearDown()` functions (see the online help for further information on these special functions). In 'RUnit', you must write such test unit files from scratch. With 'svUnit', you can "promote" one or several test functions (associated to other objects, or "living" alone as separate `svTest` objects) by using `makeUnit()`. Here is how you promote the test associated with our `Square()` function to a simple test unit containing only one test function: ```{r, eval=FALSE} # Create a test unit on disk and view its content unit <- makeUnit(Square) file.show(unit, delete.file = TRUE) ``` You got the following file whose name must start with `runit`, with an `.R` extension (`runit*.R`), and located by default in the temporary directory of R. Specify another directory with the `dir =` argument of `makeUnit()` for a more permanent record of this test unit file. Note also that `.setUp()` and `.tearDown()` functions are constructed automatically for you. They specify the context of these tests. This context is used, for instance, by the GUI in Komodo Edit/IDE to locate the test function and the code being tested. ``` ## Test unit 'Square' .setUp <- function() { # Specific actions for svUnit: prepare context if ("package:svUnit" %in% search()) { .Log <- Log() # Make sure .Log is created .Log$..Unit <- "/tmp/RtmpBoZnId/runitSquare.R" .Log$..File <- "" .Log$..Obj <- "" .Log$..Tag <- "" .Log$..Msg <- "" rm(..Test, envir = .Log) } } .tearDown <- function() { # Specific actions for svUnit: clean up context if ("package:svUnit" %in% search()) { .Log$..Unit <- "" .Log$..File <- "" .Log$..Obj <- "" .Log$..Tag <- "" .Log$..Msg <- "" rm(..Test, envir = .Log) } } testSquare <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } ``` Compatibility of these test unit files between 'RUnit' and 'svUnit' was a major concern in the design of 'svUnit'. Consequently, code specific to 'svUnit' (for managing the context of the tests) is embedded in a `if ("package:svUnit" %in% search())` construct. That way, if 'svUnit' is not loaded in memory, this code is not executed. *Note that you should avoid loading in memory both 'svUnit' and 'RUnit' at the same time! If you do so, you will most likely crash your tests.* You will see further that it is possible to write much more complex test units with the same `makeUnit()` function. But for the moment, let’s discuss a little bit how such test units should be organized in R package. If you intend to associate test units to your R package, you should respect the following conventions: * Name your test units `runit*.R`. * Place them in the `/inst/unitTests` subdirectory of the package sources, or in one of its subdirectories. If you place them in a subdirectory of `/inst/unitTests`, then you define secondary unit tests for (optional) detailed testing of specific item in your package. Always keep in mind that all `runit*.R` files in a directory will be run one after the other. So, if you want to make subgroups you would like to dissociate them, and locate them is separate subdirectories. * When the package will be compiled, all these test units will be located in `/unitTests` in the compiled/installed version of your R package. If you respect these conventions, 'svUnit' knows where package unit tests are located and will be able to find and run them quite easily. See, for instance, the examples in the 'svUnit' package. So, with test units associated to packages, you have a very convenient way to run these tests, including from the Komodo GUI. With just a little bit more coding you can also include these test units in the `R CMD check` process of your packages. You do this by means of examples in a help page (we prefer to use **examples**, instead of `/tests` in the `R CMD check` process, because examples offer a more flexible way to run tests and you can also run them in interactive sessions through the `example()` R function, which is not the case for code located in the `/tests` subdirectory of your package). Here is what you do to associate some or all of your unit tests to the `R CMD check` process (illustrated with the case of the 'svUnit' package itself): * Define a `.Rd` help file in the `/man` subdirectory called `unitTests..Rd` where is the name of your package (or whatever name you prefer). * Fill the `.Rd` file, making sure that you define an alias as `unitTests.`. Also place a little bit of information telling how users can run your test in an interactive session. * The important part of this file is, of course, the `\examples{}` section. You must first clear the log, then run each test, and then, call the `errorLog()` function. That function looks if one or more tests failed or raised an error. In this case, it stops execution of the example and causes a dump of the test log in the `R CMD check` process. That way, providing that you have the 'svUnit' package installed in the machine where you run `R CMD check`, your test units will be included nicely in the checking process of your packages, that is, they will run silently each time you check your package if no error occurs, but will produce a detailed report in case of problems. * Here is how your `.Rd` file should looks like (example of the 'svUnit' package): ``` \name{unitTests.svUnit} \alias{unitTests.svUnit} \title{ Unit tests for the package 'svUnit' } \description{ Performs unit tests defined in this package by running \code{example(unitTests.svUnit)}. Tests are in \code{runit*.R} files Located in the ’/unitTests’ subdirectory or one of its subdirectories (’/inst/unitTests’ and subdirectories in package sources). } \author{Philippe Grosjean} \keyword{utilities} \examples{ if (require(svUnit)) { clearLog() runTest(svSuite("package:svUnit"), "svUnit") \donttest{ # Tests to run with example() but not with R CMD check runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass") } \dontrun{ # Tests to present in ?unitTests.svUnit but not run automatically # Run all currently loaded packages test cases and test suites runTest(svSuiteList(), "AllTests") } \dontshow{ # Put here test units you want to run during R CMD check but # don't want to show or run with example(unitTests.svUnit) } # Check errors at the end (needed to interrupt R CMD check) errorLog() } } ``` Alternatively, if you use 'roxygen2' to build your documentation, you can add a `unitTests..R` file in the `/R` subdirectory containing ROxygen code to generate the adequate documentation. Here is the `unitTests.svUnit.R` file: ```r #' Unit tests for the package 'svUnit' #' #' Performs unit tests defined in this package by running #' `example(unitTests.svUnit)`. Tests are in `runit*.R` files located in the #' '/unitTests' subdirectory or one of its subdirectories ('/inst/unitTests' and #' subdirectories in package sources). #' #' @name unitTests.svUnit #' @author Philippe Grosjean #' @keywords utilities #' @examples #' if (require(svUnit)) { #' clearLog() #' runTest(svSuite("package:svUnit"), "svUnit") #' \donttest{ #' # Tests to run with example() but not with R CMD check #' runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass") #' } #' \dontrun{ #' # Tests to present in ?unitTests.svUnit but not run automatically #' # Run all currently loaded packages test cases and test suites #' runTest(svSuiteList(), "AllTests") #' } #' \dontshow{ #' # Put here test units you want to run during R CMD check but #' # don't want to show or run with example(unitTests.svUnit) #' } #' # Check errors at the end (needed to interrupt R CMD check) #' errorLog() #' } NULL ``` Note, however, that if the package 'svUnit' is not available on the computer where you run `R CMD check`, your tests are silently ignored (`require()` issues a warning, but that does not prevent the checking process to continue). This is an intended feature in order to allow compilation of your package without requiring 'svUnit'. Hence, dependence to 'svUnit' is less strict and also allows you to check your tests using 'RUnit' (but you have to write a dedicated function for that). Still to keep such a less strict dependence on 'svUnit', you should add `svUnit` in the `Suggests:` field in the `DESCRIPTION` file of your package, not in `Depends:` or `Imports:` fields (except if you use 'svUnit' for other purposes that testing your package using the mechanism discussed here, of course). Also, this approach, associated with examples, provides a very convenient and easy way to test a package from the command line in an interactive session by running: ```{r} example(unitTests.svUnit) ``` In the present case, the `errorLog()` instruction in the examples returns nothing, because all tests succeed. If there is an error somewhere, you will see it printed at the end of this example. ### Test suites: collections of test functions and units The highest level of organization of your tests is the **test suite**. A test suite is an unordered collection of test functions and test units. You can select test units associated with R package in a very convenient way: just specify `package:myPkg` and all test units in the `/unitTests` subdirectory of the package myPkg will be included ('svUnit' does all the required work to map these to actual directories where the test unit files are located). Also, if you specify `package:myPkg(subgroup)`, you will include the test units defined in `/unitTests/subgroup` in the package myPkg. Of course, you will be able to also add test units defined in custom directories, outside of R packages (for instance for integration of *harness tests* that check cross-packages, or multi-packages features of your application). Test functions associated to your test suite receive a special treatment. Unlike `runTest()` applied to a single test function, or to an object that has an associated test function, these tests are not run from the version loaded in memory. Instead, they are first collected together in a test unit file on disk (located in the R temporary directory, by default), and run from there. Hence, building a more complex test unit file by collecting together several test functions is just a question of constructing a test suite, and then, applying the `makeUnit()` function to this `svSuite` object. Before we apply all this, you should also know the existence of one more function: `svSuiteList()`. This function lists all test units and test functions available in your system at a given time. So, you don’t need to manually create lists of components. You are better to list them automatically. Of course, this function has a lot of arguments for listing only test units in packages, only test functions, specifying where (in which environment) the test functions are located, adding custom directories where to look for test units, etc. See the online help of this function for the description of all these arguments. One argument is particularly important: `excludeList =`. This argument defines one or several regular expressions that are used as filters to hide items from the list. This is required, since you will certainly not want to run again and again, let’s say, the example tests associated with the 'svUnit' package ('svUnit' must be loaded in memory to run the tests, so its tests examples will always be listed by `svSuiteList()`, ... unless you define an adequate filter expression that will exclude them from your list)! As the default argument suggests it, the regular expression for list exclusion could also be recorded in `options(svUnit.excludeList = ...)`. Here is how it works: ```{r} # Reset default exclusion list options(svUnit.excludeList = c("package:sv", "package:RUnit")) # List all currently available tests svSuiteList() ``` Thus, every entry matching the regular expressions `package:sv` and `package:RUnit` are excluded from the list. The entries `package:svUnit` and `package:svUnit (VirtualClass)` match first pattern and are thus excluded. Now, let's clear the exclusion list to see what happens: ```{r} # Clear exclusion list options(svUnit.excludeList = NULL) svSuiteList() ``` The test units associated with the package 'svUnit' are now listed. You have noticed that `svSuiteList()` can also find automatically `svTest` objects, as well as tests attached to objects in the user's workspace. You can create a suite by collecting all these items together very easily: ```{r} (mySuite <- svSuiteList()) ``` Now let’s make a test unit using tests collected in this suite: ```{r, eval=FALSE} myUnit <- makeUnit(mySuite, name = "ExampleTests") file.show(myUnit, delete.file = TRUE) ``` This produces a file named `runitExampleTests.R` located (by default) in the R temporary directory, and which collects together all tests in the user's workspace (either as `svTest` objects, or as tests attached to other objects), plus tests suites in packages that are **not** in the exclusion list. Running all tests in your suite is very simple. You still use `runTest()` as usual, but this time, you apply it to your `svSuite` object: ```{r} clearLog() runTest(mySuite) summary(Log()) ``` There are many other tools to manipulate `svSuite` objects in the 'svUnit' package, including functions to define the content of the suite manually (see online help). ### Using 'svUnit' with RStudio Except by integrating your tests in a packages as explained above in this document, nothing is required to make your tests run in RStudio. The `Build -> Test Package` menu entry should run your tests with 'svUnit' once this integration is done. ### Using 'svUnit' with SciViews Komodo If you use the SciViews Komodo GUI, you can integrate 'svUnit' tests in this IDE and display reports in a convenient hierarchical tree presentation. If R is started from within Komodo Edit or IDE (with the SciViews-K and SciViews-K Unit plugins installed), then, loading the 'svUnit' package in R automatically installs the **R Unit** side panel in Komodo at right. Its use should be straightforward: ![Komodo Edit with SciViews-K and SciViews-K Unit after running tests. At right: the **R Unit** panel that display (at the top) the list of available tests, and test units where you can select the ones to run, and at the bottom, a tree with the results from last tests run. The stripe at the very top is green if all tests succeed, and red (as here), if at least one tests failed or raised an error.](svUnit_KomodoRUnit.png) * Select the tests you want to run in the top part, * Click the `Run` button each time you want to refresh the test tree, * Browse the tree for failures or errors (the color at the top of the panel immediately indicates if there is a problem somewhere: green -> everything is fine, red -> there is a problem). * If you have failures or errors, move the mouse on top of the corresponding item in the tree, and you got more information displayed in a tooltip, * Click on an item to open the test unit at that place in a buffer in Komodo. The `Auto` mode, when activated, sources R files currently edited in Komodo whenever you save them, and then, refreshes the test report tree. This mode allows you to run automatically your tests in the background while you edit your R code! > If you want to implement such a side panel in another GUI, make sure to look at the `koUnit\_XXX()` functions in the 'svUnit' package. These functions allow to control the GUI in Komodo remotely from within R, and similar functions should not be too difficult to implement for other GUIs. ## References * Grosjean, Ph., 2003. SciViews: an object-oriented abstraction layer to design GUIs on top of various calculation kernels [online: http://www.ci.tuwien.ac.at/Conferences/DSC-2003/] * IEEE Standards Boards, 1993. IEEE standard for software unit testing. ANSI/IEEE Std 1008-1987. 24 pp. * Ihaka R. & R. Gentleman, 1996. R: a language for data analysis and graphics. *J. Comput. Graphic. Stat.*, 5:299-314. * Jeffries, R., 2006. Extreme programming, web site at: https://ronjeffries.com/xprog/what-is-extreme-programming/. * König, T., K. Jünemann & M. Burger, 2007. RUnit – A unit test framework for R. Vignette of the package RUnit available on CRAN. 11 pp. * R Core Team, 2019. R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. [online: https://www.R-project.org] r-cran-svunit-1.0.6/inst/doc/svUnit.html000066400000000000000000024242201412072605000201310ustar00rootroot00000000000000 svUnit - A framework for unit testing in R

svUnit - A framework for unit testing in R

Philippe Grosjean ()

2021-04-18

Introduction

Unit testing (see https://en.wikipedia.org/wiki/Unit_test) is an approach successfully used to develop software, and to ease code refactoring for keeping bugs to the minimum. It is also the insurance that the software is doing the right calculation (quality insurance). Basically, a test just checks if the code is running and is producing the correct answer/behavior in a given situation. As such, unit tests are build in R package production because all examples in documentation files, and perhaps, test code in /tests subdirectory are run during the checking of a package (R CMD check <pkg>). However, the R approach lacks a certain number of features to allow optimal use of unit tests as in extreme programming (test first – code second):

  • Tests are related to package compilation and cannot easily be run independently (for instance, for functions developed separately).
  • Once a test fails, the checking process is interrupted. Thus one has to correct the bug and launch package checking again… and perhaps get caught by the next bug. It is a long and painful process.
  • There is no way to choose one or several tests selectively: all are run or not (depending on command line options) during package checking.
  • It is very hard, or near to impossible to program in R in a test driven development (write tests first) with the standard tools (https://en.wikipedia.org/wiki/Test-driven_development).
  • Consequently, the ‘test-code-simplify’ cycle is not easily accessible yet to R programmer, because of the lack of an interactive and flexible testing mechanism providing immediate, or quasi immediate feedback about changes made.
  • We would like also to emphasize that test suites are not only useful to check code, they can also be used to check data, or the pertinence of analyses.

Unit testing in R without ‘svUnit’

Besides the “regular” testing mechanism of R packages, one can find the ‘testthat’ and ‘RUnit’ packages on CRAN (https://CRAN.R-project.org/package=testthat and https://CRAN.R-project.org/package=RUnit, respectively)1. Another package used to provide an alternate implementation of test unit: ‘butler’, but it is archived because it is not maintained any more and has given up in favor of ‘RUnit’.

The ‘testthat’ package is heavily used by the UseR community (there are many more packages on CRAN that use it than any other testing system). It implements R testing in a rather original way. ‘RUnit’, on the other hand sticks with the framework initially defined by junit for Java and largely developed for many different languages. In this document, we will not discuss further about ‘testthat’. We will focus on the junit-like framework implemented in ‘RUnit’ and ‘svUnit’. ‘RUnit’ implements the following features:

  • Assertions, checkEquals(), checkEqualsNumeric(), checkIdentical() and checkTrue() and negative tests (tests that check error conditions, checkException()).
  • Assertions are grouped into R functions to form one test function that runs a series of related individual tests. It is easy to temporarily inactivate one or more tests by commenting lines in the function. To avoid forgetting tests that are commented out later on, there is special function, named DEACTIVATED() that tags the test with a reminder for your deactivated items (i.e., the reminder is written in the test log).
  • A series of test functions (whose name typically start with test....) are collected together in a source-able R code file (name starting with runit....) on disk. This file is called a test unit.
  • A test suite (object RUnitTestSuite) is a special object defining a battery of tests. It points to one or several directories containing test units. A test suite is defined by defineTestSuite().
  • One or more test suites can be run by calling runTestSuite(). There is a shortcut to define and run a test suite constituted by only one test unit by using the function runTestFile(). Once the test is run, a RUnitTestData object is created that contains all the information collected from the various tests run.
  • One can print a synthetic report (how many test units, test functions, number of errors, fails and deactivated item), or get a more extensive summary() of the tests with indication about which ones failed or produced errors. The function printTextProtocol() does the same, while printHTMLProtocol() produces a report in HTML format.
  • ‘RUnit’ contains also functions to determine which code is run in the original function when tested, in order to detect the parts of the code not covered by the test suite (code coverage function inspect() and function tracker()).

As complete and nice as ‘RUnit’ is, there is no tools to integrate the test suite in a given development environment (IDE) or graphical user interface (GUI), as far as we know. In particular, there is no real-time reporting mechanism used to easy the test-code-simplify cycle. The way tests are implemented and run is left to the user, but the implementation suggests that the authors of ‘RUnit’ mainly target batch execution of the tests (for instance, nightly check of code in a server), rather that real-time interaction with the tests.

There is also no integration with the “regular” R CMD check mechanism of R in ‘RUnit’.

Unit testing framework for R with ‘svUnit’

Our initial goal was to implement a GUI layer on top of ‘RUnit’, and to integrate test units as smoothly as possible in a code editor, as well as, making tests easily accessible and fully compatible with R CMD check on all platforms supported by R. Ultimately, the test suite should be easy to create, to use interactively, and should be able to test functions in a complex set of R packages.

However, we encountered several difficulties while trying to enhance ‘RUnit’ mechanism. When we started to work on this project, ‘RUnit’ (version 0.4-17) did not allow to subclass its objects (version 0.4-19 solved this problem). Moreover, its RUnitTestData object is optimized for quick testing, but not at all for easy reviewing of its content: it is a list of lists of lists,… requiring embedded for loop and lapply() / sapply() procedures to extract some content. Moreover, ‘RUnit’ is implemented as S4 classes, requiring the heavy ‘methods’ package to be loaded, making it impossible to use in a lightweight environment without it, or to test R without the ‘methods’ package loaded. Finally, the concept of test units as source-able files on disk is a nice idea, but it is too rigid for quick writing test cases for objects not associated (yet) with an R packages.

We did a first implementation of the ‘RUnit’ GUI based on these objects, before realizing that it is really not designed for such an use. So, we decide to write a completely different unit testing framework in R: ‘svUnit’, but we make it test code compatible with ‘RUnit’ (i.e., the engine and objects used are totally different, but the test code run in ‘RUnit’ or ‘svUnit’ is interchangeable).

Finally, ‘svUnit’ is also designed to be integrated in the SciViews GUI on top of Komodo IDE (https://www.activestate.com/products/komodo-ide/), and to approach extreme programming practices with automatic code testing while you write it. A rather simple interface is provided to link and pilot ‘svUnit’ from any GUI/IDE, and the Komodo IDE implementation could be used as an example to program similar integration panels for other R GUIs. ‘svUnit’ also formats its report with creole wiki syntax. It is directly readable, but it can also be displayed in a much nicer way using any wiki engine compatible with the creole wiki language. It is thus rather easy to write test reports in wiki servers, possibly through nightly automatic process for your code, if you like.

This vignette is a guided tour of ‘svUnit’, showing its features and the various ways you can use it to test your R code.

Installation

The ‘svUnit’ package is available on CRAN (https://CRAN.R-project.org/package=svUnit), and its latest development version is also available on Github (https://github.com/SciViews/svUnit). You can install it with2:

install.packages("svUnit")

This package has no required dependencies other than R >= 1.9.0 and ‘utils’ (‘XML’ is also used for some export formats, but it is not required to run the tests). However, if you would like to use its interactive mode in a GUI editor, you must also install Komodo Edit or Komodo IDE and SciViews-K, or use RStudio. Its ‘pkgdown’ web site is at https://www.sciviews.org/svUnit/.

Once the ‘svUnit’ package is installed, you can check it is working correctly on your machine with the following example code:

library(svUnit)
Square <- function(x) return(x^2)
test(Square) <- function() {
  checkEqualsNumeric(9, Square(3))
  checkEqualsNumeric(10, Square(3))   # This intentionally fails
  checkEqualsNumeric(9, SSSquare(3))  # This raises error
  checkEqualsNumeric(c(1, 4, 9), Square(1:3))
  checkException(Square("xx"))
}
clearLog()
(runTest(Square))
#> * : checkEqualsNumeric(10, Square(3)) run in 0.002 sec ... **FAILS**
#> Mean relative difference: 0.1
#>  num 9
#> * : checkEqualsNumeric(9, SSSquare(3)) run in less than 0.001 sec ... **ERROR**
#> Error in SSSquare(3) : could not find function "SSSquare"
#> 
#> == test(Square) run in less than 0.1 sec: **ERROR**
#> 
#> //Pass: 3 Fail: 1 Errors: 1//
#> 
#> * : checkEqualsNumeric(10, Square(3)) run in 0.002 sec ... **FAILS**
#> Mean relative difference: 0.1
#>  num 9
#> 
#> * : checkEqualsNumeric(9, SSSquare(3)) run in less than 0.001 sec ... **ERROR**
#> Error in SSSquare(3) : could not find function "SSSquare"

Although test unit code is compatible with both ‘svUnit’ and ‘RUnit’, do not load both packages in R memory at the same time, or you will badly mix incompatible code!

Overview of ‘svUnit’

You ensure that code you write in your R functions does the expected work by defining a battery of tests that will compare the output of your code with reference values. In ‘svUnit’, the simplest way to define such a battery of tests is by attaching it to functions loaded in R memory3. Of course, you can also define batteries of tests that are independent of any R object, or that check several of them together (so called, integration tests). Here is a couple of examples:

library(svUnit)
# Create two R functions that include their own test cases
Square <- function(x) return(x^2)
test(Square) <- function() {
  checkEqualsNumeric(9, Square(3))
  checkEqualsNumeric(c(4, 9), Square(2:3))
  checkException(Square("xx"))
}

Cube <- function(x) return(x^3)
test(Cube) <- function() {
  checkEqualsNumeric(27, Cube(3))
  checkEqualsNumeric(c(8, 28), Cube(2:3))
  checkException(Cube("xx"))
}

# Add a separate test case
test_Integrate <- svTest(function() {
  checkTrue(1 < 2, "check1")
  v <- c(1, 2, 3)  # The reference
  w <- 1:3         # The value to compare to the reference
  checkEquals(v, w)
})

When you run a test in ‘svUnit’, it logs its results in a centralized logger. The idea is to get a central repository for tests that you can manipulate as you like (print, summarize, convert, search, display in a GUI, etc.). If you want to start new tests, you should first clean this logger by clearLog(). At any time, the logger is accessible by Log(), and a summary of its content is displayed using summary(Log()). So, to run test for your Square() function as well as your test_Integrate integration test, you simply do the following:

clearLog()
runTest(Square)
runTest(test_Integrate)
Log()
#> = A svUnit test suite run in less than 0.1 sec with:
#> 
#> * test_Integrate ... OK
#> * test(Square) ... OK
#> 
#> 
#> == test_Integrate run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == test(Square) run in less than 0.1 sec: OK
#> 
#> //Pass: 3 Fail: 0 Errors: 0//

In this report, you see that all your tests succeed. Note that ‘svUnit’ is making the distinction between a test that fails (the code is run correctly, but the result is different from what was expected) and code that raises error (it was not possible to run the test because its code is incorrect, or for some other reasons). Note also that the function checkException() is designed to explicitly test code that should stop() in R4. So, if that test does not raises an exception, it is considered to have failed. This is useful to check that your functions correctly trap wrong arguments, for instance, like in checkException(Square("xx")) here above (a character string is provided where a numerical value is expected).

Now, let’s look what happens if we test the Cube() function without clearing the logger:

runTest(Cube)
#> * : checkEqualsNumeric(c(8, 28), Cube(2:3)) run in less than 0.001 sec ... **FAILS**
#> Mean relative difference: 0.03571429
#>  num [1:2] 8 27
Log()
#> = A svUnit test suite run in less than 0.1 sec with:
#> 
#> * test(Cube) ... **FAILS**
#> * test_Integrate ... OK
#> * test(Square) ... OK
#> 
#> 
#> == test(Cube) run in less than 0.1 sec: **FAILS**
#> 
#> //Pass: 2 Fail: 1 Errors: 0//
#> 
#> * : checkEqualsNumeric(c(8, 28), Cube(2:3)) run in less than 0.001 sec ... **FAILS**
#> Mean relative difference: 0.03571429
#>  num [1:2] 8 27
#> 
#> == test_Integrate run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == test(Square) run in less than 0.1 sec: OK
#> 
#> //Pass: 3 Fail: 0 Errors: 0//

We note this:

  1. When a test succeeds, nothing is printed by default (result is returned invisibly). But when a test fails, or raises errors, the guilty test(s) results are printed. We expected c(8, 28) (made intentionally wrong for the sake of the demonstration) from Cube(2:3) in checkEqualsNumeric(c(8, 28), Cube(2:3)), and (of course), we got c(8, 27). This test shows how ‘svUnit’ presents test failures.
  2. The results of the tests on Cube() are added to the previous report. So, it is possible to build rather easily reports that summarize tests on several objects, by adding test results in the logger sequentially. ‘svUnit’ does this naturally and transparently. Starting a new report is equally simple: just use clearLog()

Assertions in ‘svUnit’

The most basic item in a test suite is an assertion represented by a checkXXX() function in ‘svUnit’/‘RUnit’. Five such functions are currently defined:

  • checkEquals(current, target) determines if data in target is the same as data in current.
  • checkEqualsNumeric(current, target) does the same but allows for a better comparison for numbers (variation allowed within a tolerance window).
  • checkIdentical(current, target) checks whether two R objects are strictly identical.
  • checkTrue(expr) only succeed if expr is TRUE. Note a difference in ‘svUnit’ and ‘RUnit’ (at least, in its version 0.4-17): the ‘RUnit’ function is not vectorized and expr must return a single atomic logical value. The corresponding ‘svUnit’ function also accepts a vector of logical values. In this case, all elements of the vector must be TRUE for the test to succeed. When you make sure that expr always returns a single logical value (for instance by using all(expr)), both functions should be compatible.
  • checkException(expr) verifies that a given code raises an exception (in R, it means that a line of code with stop() is executed).
  • DEACTIVATED() makes sure that all tests following this instruction (in a test function, see next paragraph) are deactivated, and inserts a reminder in the logger about the fact that some tests are deactivated in this suite.

For all these functions, you have an additional optional argument msg = where you can provide a (short) message to print in front of each text in the report. These functions return invisibly: TRUE if the test succeeds, or FALSE if it fails (code is executed correctly, but does not pass the test), and NA if there was an error (the R code of the test was not executed correctly). Moreover, these functions record the results, the context of the test and the timing in a logger (object svSuiteData inheriting from environment) called .Log and located in the user’s workspace. So, executing a series of assertions and getting a report is simply done as (in its simplest form, you can use the various checkXXX() functions directly at the command line):

clearLog()
checkEqualsNumeric(1, log(exp(1)))
checkException(log("a"))
checkTrue(1 == 2)
#> * : checkTrue(1 == 2) run in less than 0.001 sec ... **FAILS**
#>  logi FALSE
Log()
#> = A svUnit test suite run in less than 0.1 sec with:
#> 
#> * eval ... **FAILS**
#> 
#> 
#> == eval run in less than 0.1 sec: **FAILS**
#> 
#> //Pass: 2 Fail: 1 Errors: 0//
#> 
#> * : checkTrue(1 == 2) run in less than 0.001 sec ... **FAILS**
#>  logi FALSE

As you can see, the checkXXX() functions work hand in hand with the test logger (the checkXXX() functions also return the result of the test invisibly, so, you can also assign it to a variable if you like). These function are mainly used for their side-effect of adding an entry to the logger.

The last command Log() prints the content of the logger. You see how a report is printed, with a first part being a short summary by categories (assertions run at the command line are placed automatically in the eval category: there is no better context known for them. Usually, those assertions should be placed in test functions, or in test units, as we will see later in this manual, and the category will reflect this organization). A detailed report on the tests that failed or raised an error is also printed at the end of the report.

Of course, the same report is much easier to manipulate from within the graphical tree in the Komodo’s R Unit tab, but the simple text report in R has the advantage of being independent from any GUI, and from Komodo. It can also be generated in batch mode. Last, but not least, it uses a general Wiki formatting called creole wiki (http://www.wikicreole.org/wiki/Creole1.0).

‘svUnit’ test report as it appears when inserted in a wiki page (DokuWiki engine with the creole plugin installed). Note the summary of results at the top left of the page, and the clickable table of contents with detailed entries to easily navigate to the test log you are consulting). Timing of the test is also clearly indicated, since it is a complementary but important information (if a test succeeds, but calculation is way too long, it is good to know it)!

The figure illustrates the way the same report looks like in DokuWiki with the creole plugin (http://www.wikicreole.org/wiki/DokuWiki) installed. Note the convenient table of content that lists here a clickable list of all tests run. From this point, it is relatively easy to define nightly cron task jobs on a server to run a script that executes these tests and update a wiki page (look at your particular wiki engine documentation to determine how you can access wiki pages on the command line).

Manipulating the logger data

The ‘svUnit’ package provides a series of functions to manipulate the logger from the command line, in particular, stats(),summary(), metadata() and ls():

# Clear test exclusion list for running all test suites
options(svUnit.excludeList = NULL)
# Clear the logger
clearLog()
# Run all currently defined tests
runTest(svSuiteList(), name = "AllTests")
#> * : checkEqualsNumeric(c(8, 28), Cube(2:3)) run in less than 0.001 sec ... **FAILS**
#> Mean relative difference: 0.03571429
#>  num [1:2] 8 27
#> A svUnit test suite definition with:
#> 
#> - Test function:
#> [1] "test_svSuite"
#> * 
#>   runTest(bar) does not work inside test functions:  ... DEACTIVATED
#> 
#> * Check a double is equal to a named double: checkEquals(c(x = 2), 2) run in less than 0.001 sec ... **FAILS**
#> names for target but not for current
#>  num 2
#> * Check if two different numbers are equal: checkEqualsNumeric(2, 3) run in less than 0.001 sec ... **FAILS**
#> Mean relative difference: 0.5
#>  num 3
#> * Check a double is exactly the expected value: checkIdentical(2, sqrt(2)^2) run in less than 0.001 sec ... **FAILS**
#>  num 2
#> * Check if FALSE is TRUE: checkTrue(FALSE) run in less than 0.001 sec ... **FAILS**
#>  logi FALSE
#> * log(10) produces and exception: checkException(log(10)) run in less than 0.001 sec ... **FAILS**
#> No exception generated!
#> * Wrong expression in checkEquals(): checkEquals(fff(2), 2) run in less than 0.001 sec ... **ERROR**
#> Error in fff(2) : could not find function "fff"
#> * Wrong expression in checkEqualsNumeric(): checkEqualsNumeric(fff(2), 2) run in less than 0.001 sec ... **ERROR**
#> Error in fff(2) : could not find function "fff"
#> * Wrong expression in checkIdentical(): checkIdentical(2, fff(2)) run in less than 0.001 sec ... **ERROR**
#> Error in fff(2) : could not find function "fff"
#> * Wrong expression in checkTrue(): checkTrue(fff()) run in less than 0.001 sec ... **ERROR**
#> Error in fff() : could not find function "fff"
# Get some statistics
stats(Log())[, 1:3]
#>                                    kind timing                time
#> testBadTests                  **ERROR**  0.000 2021-04-18 21:35:27
#> test_Integrate                       OK  0.000 2021-04-18 21:35:27
#> testis.test                          OK  0.001 2021-04-18 21:35:27
#> test_R                               OK  0.000 2021-04-18 21:35:27
#> testMyVirtualBaseClass.getX          OK  0.000 2021-04-18 21:35:27
#> testsvSuite                          OK  0.001 2021-04-18 21:35:27
#> testCube                      **FAILS**  0.000 2021-04-18 21:35:27
#> testbar                              OK  0.021 2021-04-18 21:35:27
#> testrunTest                 DEACTIVATED  0.001 2021-04-18 21:35:27
#> testmat                              OK  0.000 2021-04-18 21:35:27
#> testCreateClass                      OK  0.001 2021-04-18 21:35:27
#> testMyVirtualBaseClass.setX          OK  0.002 2021-04-18 21:35:27
#> testSquare                           OK  0.000 2021-04-18 21:35:27
#> testsvTest                           OK  0.002 2021-04-18 21:35:27
#> testsvSuiteList                      OK  0.004 2021-04-18 21:35:27
# A slightly different presentation than with print
summary(Log())
#> = A svUnit test suite run in less than 0.1 sec with:
#> 
#> * testBadTests ... **ERROR**
#> * test_Integrate ... OK
#> * testis.test ... OK
#> * test_R ... OK
#> * testMyVirtualBaseClass.getX ... OK
#> * testsvSuite ... OK
#> * testCube ... **FAILS**
#> * testbar ... OK
#> * testrunTest ... DEACTIVATED
#> * testmat ... OK
#> * testCreateClass ... OK
#> * testMyVirtualBaseClass.setX ... OK
#> * testSquare ... OK
#> * testsvTest ... OK
#> * testsvSuiteList ... OK
#> 
#> 
#> == testBadTests (in runitBadTests.R) run in less than 0.1 sec: **ERROR**
#> 
#> //Pass: 0 Fail: 5 Errors: 4//
#> 
#> === Failures
#> [1] Check a double is equal to a named double: checkEquals(c(x = 2), 2)
#> [2] Check if two different numbers are equal: checkEqualsNumeric(2, 3)
#> [3] Check a double is exactly the expected value: checkIdentical(2, sqrt(2)^2)
#> [4] Check if FALSE is TRUE: checkTrue(FALSE)
#> [5] log(10) produces and exception: checkException(log(10))
#> 
#> === Errors
#> [6] Wrong expression in checkEquals(): checkEquals(fff(2), 2)
#> [7] Wrong expression in checkEqualsNumeric(): checkEqualsNumeric(fff(2), 2)
#> [8] Wrong expression in checkIdentical(): checkIdentical(2, fff(2))
#> [9] Wrong expression in checkTrue(): checkTrue(fff())
#> 
#> 
#> == test_Integrate (in runitAllTests.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == testis.test (in runitsvTest.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 15 Fail: 0 Errors: 0//
#> 
#> 
#> == test_R (in runitsvSuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 1 Fail: 0 Errors: 0//
#> 
#> 
#> == testMyVirtualBaseClass.getX (in runitVirtualClass.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 3 Fail: 0 Errors: 0//
#> 
#> 
#> == testsvSuite (in runitsvSuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 7 Fail: 0 Errors: 0//
#> 
#> 
#> == testCube (in runitAllTests.R) run in less than 0.1 sec: **FAILS**
#> 
#> //Pass: 2 Fail: 1 Errors: 0//
#> 
#> === Failures
#> [2] : checkEqualsNumeric(c(8, 28), Cube(2:3))
#> 
#> 
#> == testbar (in runitBadTests.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == testrunTest (in runitsvTest.R) run in less than 0.1 sec: DEACTIVATED
#> 
#> //Pass: 1 Fail: 0 Errors: 0//
#> 
#> 
#> == testmat (in runitBadTests.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == testCreateClass (in runitVirtualClass.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == testMyVirtualBaseClass.setX (in runitVirtualClass.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 6 Fail: 0 Errors: 0//
#> 
#> 
#> == testSquare (in runitAllTests.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 3 Fail: 0 Errors: 0//
#> 
#> 
#> == testsvTest (in runitsvTest.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 14 Fail: 0 Errors: 0//
#> 
#> 
#> == testsvSuiteList (in runitsvSuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 6 Fail: 0 Errors: 0//
# Metadata collected on the machine where tests are run
metadata(Log())
#> $.R.version
#>                _                           
#> platform       x86_64-apple-darwin15.6.0   
#> arch           x86_64                      
#> os             darwin15.6.0                
#> system         x86_64, darwin15.6.0        
#> status                                     
#> major          3                           
#> minor          6.3                         
#> year           2020                        
#> month          02                          
#> day            29                          
#> svn rev        77875                       
#> language       R                           
#> version.string R version 3.6.3 (2020-02-29)
#> nickname       Holding the Windsock        
#> 
#> $.sessionInfo
#> R version 3.6.3 (2020-02-29)
#> Platform: x86_64-apple-darwin15.6.0 (64-bit)
#> Running under: macOS Catalina 10.15.7
#> 
#> Matrix products: default
#> BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
#> LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib
#> 
#> locale:
#> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] svUnit_1.0.6
#> 
#> loaded via a namespace (and not attached):
#>  [1] compiler_3.6.3       magrittr_1.5         htmltools_0.5.0.9000
#>  [4] tools_3.6.3          yaml_2.2.1           stringi_1.4.6       
#>  [7] rmarkdown_2.1        knitr_1.28           stringr_1.4.0       
#> [10] digest_0.6.25        xfun_0.13            rlang_0.4.5         
#> [13] evaluate_0.14       
#> 
#> $.time
#> [1] "2021-04-18 21:35:27 CEST"
# List content of the log
ls(Log())
#>  [1] "testBadTests"                "testCreateClass"            
#>  [3] "testCube"                    "testMyVirtualBaseClass.getX"
#>  [5] "testMyVirtualBaseClass.setX" "testSquare"                 
#>  [7] "test_Integrate"              "test_R"                     
#>  [9] "testbar"                     "testis.test"                
#> [11] "testmat"                     "testrunTest"                
#> [13] "testsvSuite"                 "testsvSuiteList"            
#> [15] "testsvTest"

As you can see, ls() lists all components recorded in the test suite. Each component is a svTestData object inheriting from data.frame, and it can be easily accessed through the $ operator. There are, of course similar methods defined for those svTestData objects, like print(), summary() and stats():

myTest <- Log()$testCube
class(myTest)
#> [1] "svTestData" "data.frame"
myTest
#> 
#> == testCube (in runitAllTests.R) run in less than 0.1 sec: **FAILS**
#> 
#> //Pass: 2 Fail: 1 Errors: 0//
#> 
#> * : checkEqualsNumeric(c(8, 28), Cube(2:3)) run in less than 0.001 sec ... **FAILS**
#> Mean relative difference: 0.03571429
#>  num [1:2] 8 27
summary(myTest)
#> 
#> == testCube (in runitAllTests.R) run in less than 0.1 sec: **FAILS**
#> 
#> //Pass: 2 Fail: 1 Errors: 0//
#> 
#> === Failures
#> [2] : checkEqualsNumeric(c(8, 28), Cube(2:3))
stats(myTest)
#> $kind
#>          OK   **FAILS**   **ERROR** DEACTIVATED 
#>           2           1           0           0 
#> 
#> $timing
#> timing 
#>      0

As the logger inherits from environment, you can manage individual test data the same way as objects in any other environment. For instance, if you want to delete a particular test data without touching to the rest, you can use:

ls(Log())
#>  [1] "testBadTests"                "testCreateClass"            
#>  [3] "testCube"                    "testMyVirtualBaseClass.getX"
#>  [5] "testMyVirtualBaseClass.setX" "testSquare"                 
#>  [7] "test_Integrate"              "test_R"                     
#>  [9] "testbar"                     "testis.test"                
#> [11] "testmat"                     "testrunTest"                
#> [13] "testsvSuite"                 "testsvSuiteList"            
#> [15] "testsvTest"
rm(test_R, envir = Log())
ls(Log())
#>  [1] "testBadTests"                "testCreateClass"            
#>  [3] "testCube"                    "testMyVirtualBaseClass.getX"
#>  [5] "testMyVirtualBaseClass.setX" "testSquare"                 
#>  [7] "test_Integrate"              "testbar"                    
#>  [9] "testis.test"                 "testmat"                    
#> [11] "testrunTest"                 "testsvSuite"                
#> [13] "testsvSuiteList"             "testsvTest"

As we will see in the following section, ‘svUnit’ proposes several means to organize individual assertions in modules: test functions, test units and test suites. This organization is inspired from ‘RUnit’, but with additional ways of using tests in interactive sessions (for instance, the ability to attach a test to the objects to be tested).

Test function

The first organization level for grouping assertions together is the test function. A test function is a function without arguments whose name must start with test. It typically contains a series of assertions applied to one object, method, or function to be checked (this is not obligatory, assertions are not restricted to one object, but good practices strongly suggest such a restriction). Here is an example:

test_function <- function() {
  checkTrue(1 < 2, "check1")
  v <- c(1, 2, 3)  # The reference
  w <- 1:3         # The object to compare to the reference
  checkEqualsNumeric(v, w)
}
# Turn this function into a test
test_function <- as.svTest(test_function)
is.svTest(test_function)
#> [1] TRUE

A test function should be made a special object called svTest, so that ‘svUnit’ can recognize it. This svTest object, is allowed to live on its own (for instance, in the user’s workspace, or anywhere you like). It can be defined in a R script, be saved in a .RData file, etc… Note that this is very different from ‘RUnit’ where test must always be located in a unit test file on disk). In ‘svUnit’ (not ‘RUnit’), you run such a test simply by using runTest(), which returns the results invisibly and add it to the logger:

clearLog()
runTest(test_function)
Log()
#> = A svUnit test suite run in less than 0.1 sec with:
#> 
#> * test_function ... OK
#> 
#> 
#> == test_function run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//

Now, a test function is most likely designed to test an R object. The ‘svUnit’ package also provides facilities to attach the test function to the object to be tested. Hence, the test cases and the tested object conveniently form a single entity that one can manipulate, copy, save, reload, etc. with all the usual tools in R. This association is simply made using test(myobj) <-:

# A very simple function
Square <- function(x) return(x^2)

# A test case to associate with the Square() function
test(Square) <- function() {
  checkEqualsNumeric(9, Square(3))
  checkEqualsNumeric(c(1, 4, 9), Square(1:3))
  checkException(Square("xx"))
}
is.test(Square)  # Does this object contain tests?
#> [1] TRUE

One can retrieve the test associated with the object by using:

test(Square)
#> svUnit test function:
#> {
#>     checkEqualsNumeric(9, Square(3))
#>     checkEqualsNumeric(c(1, 4, 9), Square(1:3))
#>     checkException(Square("xx"))
#> }

And of course, running the test associated with an object is as easy as:

runTest(Square)
Log()  # Remember we didn't clear the log!
#> = A svUnit test suite run in less than 0.1 sec with:
#> 
#> * test_function ... OK
#> * test(Square) ... OK
#> 
#> 
#> == test_function run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == test(Square) run in less than 0.1 sec: OK
#> 
#> //Pass: 3 Fail: 0 Errors: 0//

Now that you master test functions, you will discover how you can group them in logical units, and associate them to R packages.

Test units

An unit is a coherent piece of software that can be tested separately from the rest. Typically, a R package is a structured way to compile and distribute such code units in R. Hence, we need a mean to organize tests related to this “unit” conveniently.

Since a package can contain several functions, data frames, or other objects, our unit should collect together individual test functions related to each of these objects that compose our package. Also, the test unit should accommodate the well-define organization of a package, and should integrate in the already existing testing features of R, in particular, R CMD check. In both ‘RUnit’, and ‘svUnit’, one can define such test units, and they are made code compatible between the two implementations.

A test unit is a source-able text file that contains one or more test functions, plus possibly .setUp() and .tearDown() functions (see the online help for further information on these special functions). In ‘RUnit’, you must write such test unit files from scratch. With ‘svUnit’, you can “promote” one or several test functions (associated to other objects, or “living” alone as separate svTest objects) by using makeUnit(). Here is how you promote the test associated with our Square() function to a simple test unit containing only one test function:

# Create a test unit on disk and view its content
unit <- makeUnit(Square)
file.show(unit, delete.file = TRUE)

You got the following file whose name must start with runit, with an .R extension (runit*.R), and located by default in the temporary directory of R. Specify another directory with the dir = argument of makeUnit() for a more permanent record of this test unit file. Note also that .setUp() and .tearDown() functions are constructed automatically for you. They specify the context of these tests. This context is used, for instance, by the GUI in Komodo Edit/IDE to locate the test function and the code being tested.

## Test unit 'Square'
.setUp <- function() {
  # Specific actions for svUnit: prepare context
  if ("package:svUnit" %in% search()) {
    .Log <- Log() # Make sure .Log is created
    .Log$..Unit <- "/tmp/RtmpBoZnId/runitSquare.R"
    .Log$..File <- ""
    .Log$..Obj <- ""
    .Log$..Tag <- ""
    .Log$..Msg <- ""
    rm(..Test, envir = .Log)
  }
}

.tearDown <- function() {
  # Specific actions for svUnit: clean up context
  if ("package:svUnit" %in% search()) {
    .Log$..Unit <- ""
    .Log$..File <- ""
    .Log$..Obj <- ""
    .Log$..Tag <- ""
    .Log$..Msg <- ""
    rm(..Test, envir = .Log)
  }
}

testSquare <- function() {
  checkEqualsNumeric(9, Square(3))
  checkEqualsNumeric(c(1, 4, 9), Square(1:3))
  checkException(Square("xx"))
}

Compatibility of these test unit files between ‘RUnit’ and ‘svUnit’ was a major concern in the design of ‘svUnit’. Consequently, code specific to ‘svUnit’ (for managing the context of the tests) is embedded in a if ("package:svUnit" %in% search()) construct. That way, if ‘svUnit’ is not loaded in memory, this code is not executed. Note that you should avoid loading in memory both ‘svUnit’ and ‘RUnit’ at the same time! If you do so, you will most likely crash your tests.

You will see further that it is possible to write much more complex test units with the same makeUnit() function. But for the moment, let’s discuss a little bit how such test units should be organized in R package.

If you intend to associate test units to your R package, you should respect the following conventions:

  • Name your test units runit*.R.
  • Place them in the /inst/unitTests subdirectory of the package sources, or in one of its subdirectories. If you place them in a subdirectory of /inst/unitTests, then you define secondary unit tests for (optional) detailed testing of specific item in your package. Always keep in mind that all runit*.R files in a directory will be run one after the other. So, if you want to make subgroups you would like to dissociate them, and locate them is separate subdirectories.
  • When the package will be compiled, all these test units will be located in /unitTests in the compiled/installed version of your R package.

If you respect these conventions, ‘svUnit’ knows where package unit tests are located and will be able to find and run them quite easily. See, for instance, the examples in the ‘svUnit’ package.

So, with test units associated to packages, you have a very convenient way to run these tests, including from the Komodo GUI. With just a little bit more coding you can also include these test units in the R CMD check process of your packages. You do this by means of examples in a help page (we prefer to use examples, instead of /tests in the R CMD check process, because examples offer a more flexible way to run tests and you can also run them in interactive sessions through the example() R function, which is not the case for code located in the /tests subdirectory of your package). Here is what you do to associate some or all of your unit tests to the R CMD check process (illustrated with the case of the ‘svUnit’ package itself):

  • Define a .Rd help file in the /man subdirectory called unitTests.<mypackage>.Rd where is the name of your package (or whatever name you prefer).
  • Fill the .Rd file, making sure that you define an alias as unitTests.<mypackage>. Also place a little bit of information telling how users can run your test in an interactive session.
  • The important part of this file is, of course, the \examples{} section. You must first clear the log, then run each test, and then, call the errorLog() function. That function looks if one or more tests failed or raised an error. In this case, it stops execution of the example and causes a dump of the test log in the R CMD check process. That way, providing that you have the ‘svUnit’ package installed in the machine where you run R CMD check, your test units will be included nicely in the checking process of your packages, that is, they will run silently each time you check your package if no error occurs, but will produce a detailed report in case of problems.
  • Here is how your .Rd file should looks like (example of the ‘svUnit’ package):
\name{unitTests.svUnit}
\alias{unitTests.svUnit}
\title{ Unit tests for the package 'svUnit' }
\description{ Performs unit tests defined in this
  package by running \code{example(unitTests.svUnit)}.
  Tests are in \code{runit*.R} files Located in the
  ’/unitTests’ subdirectory or one of its
  subdirectories (’/inst/unitTests’ and subdirectories
  in package sources).
}
\author{Philippe Grosjean}
\keyword{utilities}
\examples{
if (require(svUnit)) {
  clearLog()
  runTest(svSuite("package:svUnit"), "svUnit")
  \donttest{
  # Tests to run with example() but not with R CMD check
  runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass")
  }
  \dontrun{
  # Tests to present in ?unitTests.svUnit but not run automatically
  # Run all currently loaded packages test cases and test suites
  runTest(svSuiteList(), "AllTests")
  }
  \dontshow{
  # Put here test units you want to run during R CMD check but
  # don't want to show or run with example(unitTests.svUnit)
  }
  # Check errors at the end (needed to interrupt R CMD check)
  errorLog()
}
}

Alternatively, if you use ‘roxygen2’ to build your documentation, you can add a unitTests.<pkgname>.R file in the /R subdirectory containing ROxygen code to generate the adequate documentation. Here is the unitTests.svUnit.R file:

#' Unit tests for the package 'svUnit'
#'
#' Performs unit tests defined in this package by running
#' `example(unitTests.svUnit)`. Tests are in `runit*.R` files located in the
#' '/unitTests' subdirectory or one of its subdirectories ('/inst/unitTests' and
#' subdirectories in package sources).
#'
#' @name unitTests.svUnit
#' @author Philippe Grosjean
#' @keywords utilities
#' @examples
#' if (require(svUnit)) {
#'   clearLog()
#'   runTest(svSuite("package:svUnit"), "svUnit")
#'   \donttest{
#'   # Tests to run with example() but not with R CMD check
#'   runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass")
#'   }
#'   \dontrun{
#'   # Tests to present in ?unitTests.svUnit but not run automatically
#'   # Run all currently loaded packages test cases and test suites
#'   runTest(svSuiteList(), "AllTests")
#'   }
#'   \dontshow{
#'   # Put here test units you want to run during R CMD check but
#'   # don't want to show or run with example(unitTests.svUnit)
#'   }
#'   # Check errors at the end (needed to interrupt R CMD check)
#'   errorLog()
#' }
NULL

Note, however, that if the package ‘svUnit’ is not available on the computer where you run R CMD check, your tests are silently ignored (require() issues a warning, but that does not prevent the checking process to continue). This is an intended feature in order to allow compilation of your package without requiring ‘svUnit’. Hence, dependence to ‘svUnit’ is less strict and also allows you to check your tests using ‘RUnit’ (but you have to write a dedicated function for that). Still to keep such a less strict dependence on ‘svUnit’, you should add svUnit in the Suggests: field in the DESCRIPTION file of your package, not in Depends: or Imports: fields (except if you use ‘svUnit’ for other purposes that testing your package using the mechanism discussed here, of course).

Also, this approach, associated with examples, provides a very convenient and easy way to test a package from the command line in an interactive session by running:

example(unitTests.svUnit)
#> 
#> untT.U> if (require(svUnit)) {
#> untT.U+   clearLog()
#> untT.U+   runTest(svSuite("package:svUnit"), "svUnit")
#> untT.U+   ## No test: 
#> untT.U+ ##D   # Tests to run with example() but not with R CMD check
#> untT.U+ ##D   runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass")
#> untT.U+ ##D   
#> untT.U+ ## End(No test)
#> untT.U+   ## Not run: 
#> untT.U+ ##D   # Tests to present in ?unitTests.svUnit but not run automatically
#> untT.U+ ##D   # Run all currently loaded packages test cases and test suites
#> untT.U+ ##D   runTest(svSuiteList(), "AllTests")
#> untT.U+ ##D   
#> untT.U+ ## End(Not run)
#> untT.U+   ## Don't show: 
#> untT.U+   # Put here test units you want to run during R CMD check but
#> untT.U+   # don't want to show or run with example(unitTests.svUnit)
#> untT.U+   
#> untT.U+ ## End(Don't show)
#> untT.U+   # Check errors at the end (needed to interrupt R CMD check)
#> untT.U+   errorLog()
#> untT.U+ }
#> A svUnit test suite definition with:
#> 
#> - Test function:
#> [1] "test_svSuite"
#> * 
#>   runTest(bar) does not work inside test functions:  ... DEACTIVATED

In the present case, the errorLog() instruction in the examples returns nothing, because all tests succeed. If there is an error somewhere, you will see it printed at the end of this example.

Test suites: collections of test functions and units

The highest level of organization of your tests is the test suite. A test suite is an unordered collection of test functions and test units. You can select test units associated with R package in a very convenient way: just specify package:myPkg and all test units in the /unitTests subdirectory of the package myPkg will be included (‘svUnit’ does all the required work to map these to actual directories where the test unit files are located). Also, if you specify package:myPkg(subgroup), you will include the test units defined in /unitTests/subgroup in the package myPkg. Of course, you will be able to also add test units defined in custom directories, outside of R packages (for instance for integration of harness tests that check cross-packages, or multi-packages features of your application).

Test functions associated to your test suite receive a special treatment. Unlike runTest() applied to a single test function, or to an object that has an associated test function, these tests are not run from the version loaded in memory. Instead, they are first collected together in a test unit file on disk (located in the R temporary directory, by default), and run from there. Hence, building a more complex test unit file by collecting together several test functions is just a question of constructing a test suite, and then, applying the makeUnit() function to this svSuite object.

Before we apply all this, you should also know the existence of one more function: svSuiteList(). This function lists all test units and test functions available in your system at a given time. So, you don’t need to manually create lists of components. You are better to list them automatically. Of course, this function has a lot of arguments for listing only test units in packages, only test functions, specifying where (in which environment) the test functions are located, adding custom directories where to look for test units, etc. See the online help of this function for the description of all these arguments. One argument is particularly important: excludeList =. This argument defines one or several regular expressions that are used as filters to hide items from the list. This is required, since you will certainly not want to run again and again, let’s say, the example tests associated with the ‘svUnit’ package (‘svUnit’ must be loaded in memory to run the tests, so its tests examples will always be listed by svSuiteList(), … unless you define an adequate filter expression that will exclude them from your list)! As the default argument suggests it, the regular expression for list exclusion could also be recorded in options(svUnit.excludeList = ...). Here is how it works:

# Reset default exclusion list
options(svUnit.excludeList = c("package:sv", "package:RUnit"))
# List all currently available tests
svSuiteList()
#> A svUnit test suite definition with:
#> 
#> - Test functions:
#> [1] "test(Cube)"     "test(Square)"   "test_Integrate" "test_function"

Thus, every entry matching the regular expressions package:sv and package:RUnit are excluded from the list. The entries package:svUnit and package:svUnit (VirtualClass) match first pattern and are thus excluded. Now, let’s clear the exclusion list to see what happens:

# Clear exclusion list
options(svUnit.excludeList = NULL)
svSuiteList()
#> A svUnit test suite definition with:
#> 
#> - Test suites:
#> [1] "package:svUnit"                "package:svUnit (BadTests)"    
#> [3] "package:svUnit (VirtualClass)"
#> 
#> - Test functions:
#> [1] "test(Cube)"     "test(Square)"   "test_Integrate" "test_function"

The test units associated with the package ‘svUnit’ are now listed. You have noticed that svSuiteList() can also find automatically svTest objects, as well as tests attached to objects in the user’s workspace. You can create a suite by collecting all these items together very easily:

(mySuite <- svSuiteList())
#> A svUnit test suite definition with:
#> 
#> - Test suites:
#> [1] "package:svUnit"                "package:svUnit (BadTests)"    
#> [3] "package:svUnit (VirtualClass)"
#> 
#> - Test functions:
#> [1] "test(Cube)"     "test(Square)"   "test_Integrate" "test_function"

Now let’s make a test unit using tests collected in this suite:

myUnit <- makeUnit(mySuite, name = "ExampleTests")
file.show(myUnit, delete.file = TRUE)

This produces a file named runitExampleTests.R located (by default) in the R temporary directory, and which collects together all tests in the user’s workspace (either as svTest objects, or as tests attached to other objects), plus tests suites in packages that are not in the exclusion list. Running all tests in your suite is very simple. You still use runTest() as usual, but this time, you apply it to your svSuite object:

clearLog()
runTest(mySuite)
#> * : checkEqualsNumeric(c(8, 28), Cube(2:3)) run in less than 0.001 sec ... **FAILS**
#> Mean relative difference: 0.03571429
#>  num [1:2] 8 27
#> A svUnit test suite definition with:
#> 
#> - Test function:
#> [1] "test_svSuite"
#> * 
#>   runTest(bar) does not work inside test functions:  ... DEACTIVATED
#> 
#> * Check a double is equal to a named double: checkEquals(c(x = 2), 2) run in less than 0.001 sec ... **FAILS**
#> names for target but not for current
#>  num 2
#> * Check if two different numbers are equal: checkEqualsNumeric(2, 3) run in less than 0.001 sec ... **FAILS**
#> Mean relative difference: 0.5
#>  num 3
#> * Check a double is exactly the expected value: checkIdentical(2, sqrt(2)^2) run in less than 0.001 sec ... **FAILS**
#>  num 2
#> * Check if FALSE is TRUE: checkTrue(FALSE) run in less than 0.001 sec ... **FAILS**
#>  logi FALSE
#> * log(10) produces and exception: checkException(log(10)) run in less than 0.001 sec ... **FAILS**
#> No exception generated!
#> * Wrong expression in checkEquals(): checkEquals(fff(2), 2) run in less than 0.001 sec ... **ERROR**
#> Error in fff(2) : could not find function "fff"
#> * Wrong expression in checkEqualsNumeric(): checkEqualsNumeric(fff(2), 2) run in less than 0.001 sec ... **ERROR**
#> Error in fff(2) : could not find function "fff"
#> * Wrong expression in checkIdentical(): checkIdentical(2, fff(2)) run in less than 0.001 sec ... **ERROR**
#> Error in fff(2) : could not find function "fff"
#> * Wrong expression in checkTrue(): checkTrue(fff()) run in less than 0.001 sec ... **ERROR**
#> Error in fff() : could not find function "fff"
summary(Log())
#> = A svUnit test suite run in less than 0.1 sec with:
#> 
#> * test_Integrate ... OK
#> * testBadTests ... **ERROR**
#> * testis.test ... OK
#> * test_R ... OK
#> * testMyVirtualBaseClass.getX ... OK
#> * testsvSuite ... OK
#> * testCube ... **FAILS**
#> * testbar ... OK
#> * testrunTest ... DEACTIVATED
#> * testmat ... OK
#> * testCreateClass ... OK
#> * test_function ... OK
#> * testMyVirtualBaseClass.setX ... OK
#> * testSquare ... OK
#> * testsvTest ... OK
#> * testsvSuiteList ... OK
#> 
#> 
#> == test_Integrate (in runitmySuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == testBadTests (in runitBadTests.R) run in less than 0.1 sec: **ERROR**
#> 
#> //Pass: 0 Fail: 5 Errors: 4//
#> 
#> === Failures
#> [1] Check a double is equal to a named double: checkEquals(c(x = 2), 2)
#> [2] Check if two different numbers are equal: checkEqualsNumeric(2, 3)
#> [3] Check a double is exactly the expected value: checkIdentical(2, sqrt(2)^2)
#> [4] Check if FALSE is TRUE: checkTrue(FALSE)
#> [5] log(10) produces and exception: checkException(log(10))
#> 
#> === Errors
#> [6] Wrong expression in checkEquals(): checkEquals(fff(2), 2)
#> [7] Wrong expression in checkEqualsNumeric(): checkEqualsNumeric(fff(2), 2)
#> [8] Wrong expression in checkIdentical(): checkIdentical(2, fff(2))
#> [9] Wrong expression in checkTrue(): checkTrue(fff())
#> 
#> 
#> == testis.test (in runitsvTest.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 15 Fail: 0 Errors: 0//
#> 
#> 
#> == test_R (in runitsvSuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 1 Fail: 0 Errors: 0//
#> 
#> 
#> == testMyVirtualBaseClass.getX (in runitVirtualClass.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 3 Fail: 0 Errors: 0//
#> 
#> 
#> == testsvSuite (in runitsvSuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 7 Fail: 0 Errors: 0//
#> 
#> 
#> == testCube (in runitmySuite.R) run in less than 0.1 sec: **FAILS**
#> 
#> //Pass: 2 Fail: 1 Errors: 0//
#> 
#> === Failures
#> [2] : checkEqualsNumeric(c(8, 28), Cube(2:3))
#> 
#> 
#> == testbar (in runitBadTests.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == testrunTest (in runitsvTest.R) run in less than 0.1 sec: DEACTIVATED
#> 
#> //Pass: 1 Fail: 0 Errors: 0//
#> 
#> 
#> == testmat (in runitBadTests.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == testCreateClass (in runitVirtualClass.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == test_function (in runitmySuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 2 Fail: 0 Errors: 0//
#> 
#> 
#> == testMyVirtualBaseClass.setX (in runitVirtualClass.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 6 Fail: 0 Errors: 0//
#> 
#> 
#> == testSquare (in runitmySuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 3 Fail: 0 Errors: 0//
#> 
#> 
#> == testsvTest (in runitsvTest.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 14 Fail: 0 Errors: 0//
#> 
#> 
#> == testsvSuiteList (in runitsvSuite.R) run in less than 0.1 sec: OK
#> 
#> //Pass: 6 Fail: 0 Errors: 0//

There are many other tools to manipulate svSuite objects in the ‘svUnit’ package, including functions to define the content of the suite manually (see online help).

Using ‘svUnit’ with RStudio

Except by integrating your tests in a packages as explained above in this document, nothing is required to make your tests run in RStudio. The Build -> Test Package menu entry should run your tests with ‘svUnit’ once this integration is done.

Using ‘svUnit’ with SciViews Komodo

If you use the SciViews Komodo GUI, you can integrate ‘svUnit’ tests in this IDE and display reports in a convenient hierarchical tree presentation.

If R is started from within Komodo Edit or IDE (with the SciViews-K and SciViews-K Unit plugins installed), then, loading the ‘svUnit’ package in R automatically installs the R Unit side panel in Komodo at right. Its use should be straightforward:

Komodo Edit with SciViews-K and SciViews-K Unit after running tests. At right: the R Unit panel that display (at the top) the list of available tests, and test units where you can select the ones to run, and at the bottom, a tree with the results from last tests run. The stripe at the very top is green if all tests succeed, and red (as here), if at least one tests failed or raised an error.

  • Select the tests you want to run in the top part,
  • Click the Run button each time you want to refresh the test tree,
  • Browse the tree for failures or errors (the color at the top of the panel immediately indicates if there is a problem somewhere: green -> everything is fine, red -> there is a problem).
  • If you have failures or errors, move the mouse on top of the corresponding item in the tree, and you got more information displayed in a tooltip,
  • Click on an item to open the test unit at that place in a buffer in Komodo.

The Auto mode, when activated, sources R files currently edited in Komodo whenever you save them, and then, refreshes the test report tree. This mode allows you to run automatically your tests in the background while you edit your R code!

If you want to implement such a side panel in another GUI, make sure to look at the koUnit\_XXX() functions in the ‘svUnit’ package. These functions allow to control the GUI in Komodo remotely from within R, and similar functions should not be too difficult to implement for other GUIs.

References

  • Grosjean, Ph., 2003. SciViews: an object-oriented abstraction layer to design GUIs on top of various calculation kernels [online: http://www.ci.tuwien.ac.at/Conferences/DSC-2003/]

  • IEEE Standards Boards, 1993. IEEE standard for software unit testing. ANSI/IEEE Std 1008-1987. 24 pp.

  • Ihaka R. & R. Gentleman, 1996. R: a language for data analysis and graphics. J. Comput. Graphic. Stat., 5:299-314.

  • Jeffries, R., 2006. Extreme programming, web site at: https://ronjeffries.com/xprog/what-is-extreme-programming/.

  • König, T., K. Jünemann & M. Burger, 2007. RUnit – A unit test framework for R. Vignette of the package RUnit available on CRAN. 11 pp.

  • R Core Team, 2019. R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. [online: https://www.R-project.org]


  1. There is also a very simple testing system in ‘testit’ (https://CRAN.R-project.org/package=testit) using only one assertion: assert() for all test. Other interesting R packages on CRAN are ‘testthis’ (https://CRAN.R-project.org/package=testthis), which adds more tools and an RStudio addins around ‘testthat’, and ‘runittotestthat’ (https://CRAN.R-project.org/package=runittotestthat) which, as its name implies, automatically converts ‘RUnit’ tests into ‘testthat’ equivalents.↩︎

  2. Install the development version with `devtools::install_github(“SciViews/svUnit”).↩︎

  3. In fact, you can attach ‘svUnit’ tests to any kind of R object, not only function. This could be useful to test S3/S4 objects, or even, datasets.↩︎

  4. checkException() can also track warning() messages with this little trick: first convert all warnings into errors with owarn <- options(warn = 2)$warn, run the code that should generate a warning inside checkException(), and then restore default warning behavior with options(warn = owarn).↩︎

r-cran-svunit-1.0.6/inst/komodo/000077500000000000000000000000001412072605000164705ustar00rootroot00000000000000r-cran-svunit-1.0.6/inst/komodo/sciviewskunit-ko.xpi000066400000000000000000000333011412072605000225300ustar00rootroot00000000000000PKc%=&B̟|chrome.manifest=0 F"5X`1"48=AEɶldG-h[KQM(&t_7ѶSd?(n)83h( ϱ4}LL#c4Njgԭ0qSu j@))o:#bvv5PKc%=Ϋ5J install.rdfՔo0+fUnJcto6[[_ {ɞ[|O 儮ItrބӜ*:@ ,KRR`%k u@rCV=E{jV?;($x( nY6y#=d4fa HO!ƸHa$_o™xf:Wz!S~:"a9bo\D:^.0idY-`Si,0 $C2PKc%=X/26sciviewskunit.jar{l&Igffffffffffl33vgf.P)J"C|2 Ѐ?8o={;S;D?A3p t6t4uwvtr^vLʫt"S7sc9~a*⦸ٔ%'F6C*ƻ&1:^D^`Ŏ+蟏CM'U(##N۶{D~?be#_D c5lGqf+{scFt[vc+W{&>[Ѥ:IP3K!Ug ˡ3BAuwEk: * -ذajtم<1 @az8xpe1~k6? AY#g|&Ww`LCoFoU <[.``pFH;{d TxxΖ7/Z$^ɣ>O'AO  :{1b,*aOr- ~cpzẺPsoεބh*hΑ"ـWvrps|p)N{~8w5?n6t͉9+OpA0ВRƜ77[m^x׬@¶daaӎJ@2xW+4T;}gH8 [0Yz;dz|b]?ϭpI@8;PWR߳>~B;E13Xl O~x }mu @T6(-s)=h13,.(B}'59C=$Jup g3c'm)aeQ tcBjl /[@~P&ѡey6R Ҁ]PiNR sтĺ) -Ϸ7>,z-8b%ϕu^E`PhS?ʱ_ Ғ(ΛlMfO8hL_{f5丣" `๵'_L;1[a@5 [P#% 9K-TD㏇`IdAz7OYa 2PѢɈtAi/I(sQ]cL<ˬ|;!5+^LAafaJ!h;I–8v˺Gui)z2ܽkD>s6B{0'QPJv9z2d 9O}Y^[bl]~gO^%,d. ޭFQ ; #sdcIW2ud/B]/ad͠}3"k3-=fvx-cz/[HOOSGq^TH2`PL/ ܎Szf]7N JGe'%4BVpbV&֨l#v/iyTqh{<"@#tӢ'D֣,•;v3s(8V mPƌH:-[7(֥E3ls&|hdvSWxʊrF4 J0đY6F 'QzXwNy@M7W#GpJp3Ww4U*_3EhChlmL=`'k= SAf2ǿN߬KL弃y ɔ|e{ $. iœ++ fKjW'cDWs3hgwcrKա!$r4TvĬRˈ7YN?&hT(bdHcDw跹WV8"v d5'f׸Eb"]z%m΄w!7Qm~ؙxSM4<>m˴%ًo#s`c-w0:4 5[AyzO(QM][Gp L*eUhw cɘCCL*C@T@/xT?t>1V/ q粕Csi{M^X8Uh'kU`KT 5S5jCs@gvVBi, *3c/ 4"]^^ef(y*ɟ5e EZ;jzyJ (ݨr&K{>֩rx!n o.`Y_Ml0/R.J Ux>V1F U;ʧ C4)WଉYn6dmog^E?"A1E¿6EJ̒'}0W$Dk.P*-7繢t?4Z_IQ^2ޮzJyW!2ZOM~ e:[[:< jbPLt#cT ƧU+.A4(ޫ(A坷S=Gs ܩ,9TRpiiT^2Q6k"=rvH#7ŻAD}F Јn}SS]N;ʛLx$z)!R{L ̂ݕY2R< k(Q[ESv* NN~E;"i,_o0\"9;fzU~j-DZد*[oLUI ϕϤb]}ޙihIl]'wP:O;hZw"ZQ{&k (F`ʸGiߏ=e C`["6ơʋM-J+tMc M|Rhqg@3Լz aUH i15o(bJ~\꬏! .}˘QTcEX!i7oF,Y-Slzt~"M5;, um/{Vۥ|`sv=fI?TE/qkV{S>J/Z)Fy2jW 8Ik J4SYz7D$*("t^]t4]DwأR[[JX/߸|yӤ a'~hTh*lJ#a Ҥ/@l:`Svy TgPoRlmEo̤=UY` sq,[0AownY1B]< oՃ)TFr%Щ8v%ܞIC4Y#8jfH&{n?qҙac2zn]uvǛJ`">lÑ:*# X\w䊢2w.!uё)G"ʉ!Y6(88Y4=-!d4-٫3-Pi?cN>1Y,',iWȯ8,KknxtYI|U{d8CT]dQjWU?;$#VW\33-k`ȊƙOSݱ*&?.m$ٕZSvuL֯l変<9hvagj{fy131 -s6z{afur,\j.t%k_~2@#&w=of@3m'>n!<*ը^{5h%ּ  d Se}aյzO쁐 ĦV3 ${@8!Fx3ɛrhg"x='0KnPa24 o(WUR/q`(/B[&}A^OX ق39DG)kXV<Ԏ-j,(R݈UU!pBj] tø#nH+Jk{KBlG/n{]|7./ta,ҁgVGѣS뤦߁mˍ"uD6?>'P9vV+\:yN‚^]5ŝfoN":rP2 X5#q l˜pUzۣүO(L&d=\el8 6^ߙرƕKkȘmP(aKwO/yg[YAʿٻ:z{Lk:o#}Sn1m]T;r6$9I֊j v%!)ID4E\u]-K,oFrC55鬔ꪎ5|Q+.-/֘@X,* B ;U!r@5_sBPq}@S=iB8-㔝Ҵ`dLBXGXā%4Y-K>g&򳓠Dpe:7q/xNܤ_ ރNkO񐌨PPzI!dX戮c> oL^㧠c!*&ɘ""rl^ٶ<# F2j#oLD)a:ȗړ J)2j_F4k$,kCRڍ,M}WA虗Nk~G eb@? +L'LO%$Ɖ G$(燧`Tn65Qssv69k:Gs jhh 'KYѧhgfmn`o<~q KuzNhϽZ*G -_tıy&݃Τ|S$WL6pQ3NWlAUJ@ѠoA{"K6V˕Mdm^0y2@osZ [-b91tE T=BJh fOX|7)bc -LZEm0oD׍dSYOO{$';w󗥹UmzK!:baaJswF`⌀tI%-9 +=x<6}$ 2g g5PoRiQk [J܍=Z?$r Ë">TM1+> BfwV#$f[7@,.b 0E83x,/w7v C;4/\a,ӝZ&WZ^T& WU|u!8cJEaY=ߵd팦$ɬ/*Vqhs -2Y DUpiRJi.u^568ΦsV6,¯o]kv>5kA2M3b(h&#0]OAȐ-$$ߌ`#B{gp '4t˞gFpMv  (FujI*$?z8ZGn\f 燶e3#W*4(p?<ڊKq굦(eb~ÝuĐeǀj(j?7'vnV&I3qb[0hQqhdI`QT`5yTO6+?)9X ٌ 5&bLKܽL"Wz˴BV 1Yk"h_El le<.~uӊHٲߩNqk4nC-懆}'zYeP%&TUXWLp+%&**C VP(7(g$.bIwmڷK7zρӷ:h>ژǖ djG'!Ks}D,ߡ=db(=(ktt#@o׎A\an" %p9l=OPubK֡ .qٯ}Fۀe]d-?=ddAk4jͪnZzD- 3X',:D+ Pu4WQ~[3Yܨh{}*d$S̜ĕV?"뿖?LI vLaY%8]3Hi\"YS@ r%T`cY{7;/@fp:5ӿ-&/)45"#WOpW=Kz{I!H2hTV'ؚ~+mVa*ej7L!GuY=#r0Q[yQ(YQR2ߤ@ag3UȯiF9myfnAX0khИZ֖v[ /ߐ7d:ߐ#%࠱II*q%!xy=qrrt,Gv !v.ET@kBcjTyZ~p~e`%!QZj@e3Uebjx!4 ) Ū7o뵎2{3}{_޿EV0 oTxg痘W /Z`s~ALqA VqI ~ J +K8#? /@#0GH†/SE V$V'ڜF` L5 "wE*UR 0yXm rTw ?*XH5*7v؎ZNjq иO )Q/Y1VI]+SD4 :Wbvdע4R5L9@["ě'o,$4lS<` (h^9 [XB2 _$,lMhj%ibUK*eXEH59ri%58JM5kWF6YXFU`5 H@a2# T󮯞OuSbvYqb ǘe!&- HN+P$HSEYansX c2QqTOE#>fl;%͠bvE 9)6U4& & 1_Q#-s~ ߙyq$ݴtdm8pٹ}T ]p\+C@de9 uG_z1BmOA}J{_^QB̚3RToW5=PX}CˆmXnӾYLYFSQfAKy&3:hdܗI ܡPMߕ랷j楕o>Wv->L!کmB֨gYssA968o(V-nOTץ cm:>ib߯3RIff~Ͽ/%>=Ξ貚IDP)xgM=] A=DPCÞ8ɤfwXVxl͂8;\MAiֆ)~=i7Z _~677RdJ>jwdٝ۱J=^fT8I!gJR$T/@١ ^`+*CA*[&̹֙Ty\,W ~ÎV,`HE0N,fه .tRhv}^!'#ص$\? 5?'CvaWmQc1)ۿ]Ci%QJE5S_3sFŢ<|[?`$NsN s+r{V԰#"؜;1|UB7Ƥsu6)R#@.6?KC:1顁/u{-ϛ:Xx8ʨ>jK^ =x 54i>Oi>=ƣժa_Cz T͆V ]?4Ozjj:k&gu& דzl[m7_p~R]okNׯ,k\r5.ƕ6Q*p#J0vv!{rhQq2P`!VlL̔j(}mRKsTZQi%x[OncspZY?biPҋ96#OSg(ugjcT'g-s `|nOC\ J,k5~[m׃p!@eM$i4@kWߩ_i% E-3e_FOTQ ? `xXPKc%=&B̟|chrome.manifestPKc%=Ϋ5J install.rdfPKc%=X/26sciviewskunit.jarPK5r-cran-svunit-1.0.6/inst/unitTests/000077500000000000000000000000001412072605000172025ustar00rootroot00000000000000r-cran-svunit-1.0.6/inst/unitTests/BadTests/000077500000000000000000000000001412072605000207135ustar00rootroot00000000000000r-cran-svunit-1.0.6/inst/unitTests/BadTests/runitBadTests.R000066400000000000000000000026171412072605000236370ustar00rootroot00000000000000# runitBadTests.R test suite # by Ph. Grosjean # Create a few objects we need for tests # An R object (matrix) mat <- matrix(rnorm(4), ncol = 2) # Create very simple test cases for matrix 'mat' testmat <- svTest(function() { checkEqualsNumeric(2, nrow(mat)) checkTrue(is.numeric(mat)) }) # An example function without test case foo <- function(x) x # Another function with a test associated bar <- function(x) x^2 testbar <- svTest(function() { checkEqualsNumeric(4, bar(2)) checkException(bar("xx")) }) test(bar) <- testbar # The test cases .setUp <- function() { # Executed before each test function # ... } .tearDown <- function() { # Executed after each test function # ... } testBadTests <- function() { # These tests should fail checkEquals(c(x = 2), 2, "Check a double is equal to a named double") checkEqualsNumeric(2, 3, "Check if two different numbers are equal") checkIdentical(2, sqrt(2)^2, "Check a double is exactly the expected value") checkTrue(FALSE, "Check if FALSE is TRUE") checkException(log(10), "log(10) produces and exception") # These tests should generate en error checkEquals(fff(2), 2, "Wrong expression in checkEquals()") checkEqualsNumeric(fff(2), 2, "Wrong expression in checkEqualsNumeric()") checkIdentical(2, fff(2), "Wrong expression in checkIdentical()") checkTrue(fff(), "Wrong expression in checkTrue()") } r-cran-svunit-1.0.6/inst/unitTests/VirtualClass/000077500000000000000000000000001412072605000216165ustar00rootroot00000000000000r-cran-svunit-1.0.6/inst/unitTests/VirtualClass/runitVirtualClass.R000066400000000000000000000056131412072605000254440ustar00rootroot00000000000000# runit.VirtualClass.R test suite # Just one (little bit more complex) example taken from RUnit # Test setup if (FALSE) { # Not really needed, but can be handy when writing tests library("svUnit") } # Package 'methods' is usually loaded, but make sure it is if (!require(methods)) stop("Package 'methods' is required!") # Define class (not exported yet by the program, and defined in .GlobalEnv!) className <- "MyVirtualBaseClass" setClass(className, representation("VIRTUAL", x = "numeric", y = "numeric", description = "character"), validity = NULL, sealed = FALSE, where = .GlobalEnv) if (!isGeneric("getX")) { setGeneric("getX", function(object, ...) standardGeneric("getX"), useAsDefault = TRUE, where = .GlobalEnv, valueClass = "numeric") } setMethod("getX", signature = className, function(object) return(object@x), where = .GlobalEnv) if (!isGeneric("setX<-")) { setGeneric("setX<-", function(object, value) standardGeneric("setX<-"), useAsDefault = TRUE, where = .GlobalEnv) } setMethod("setX<-", signature = signature(object = className, value = "numeric"), function(object, value) { if (length(value) < 1) stop("value has to contain at least one element.") if (any(is.na(value))) stop("value may not contain NA(s).") object@x <- value object }, where = .GlobalEnv) # Test functions .setUp <- function() { # Executed before each test function # ... } .tearDown <- function() { # Executed after each test function # ... } testCreateClass <- function() { setClass("A", contains = "numeric", where = .GlobalEnv) a <- new("A") checkTrue(validObject(a)) removeClass("A", where = .GlobalEnv) # Better to use on.exit() here! checkException(new("A")) } testMyVirtualBaseClass.getX <- function() { testClassName <- "MyDerivedTestClass" setClass(testClassName, representation("MyVirtualBaseClass"), validity = NULL, sealed = FALSE, where = .GlobalEnv) on.exit(removeClass(testClassName, where = .GlobalEnv)) # system constructor this <- new(testClassName) # constructor call succeeded? checkTrue(is(this, testClassName)) ret <- getX(this) checkTrue(is(ret, "numeric")) # class default checkEquals(numeric(0), ret) } testMyVirtualBaseClass.setX <- function() { testClassName <- "MyDerivedTestClass" setClass(testClassName, representation("MyVirtualBaseClass"), validity = NULL, sealed = FALSE, where = .GlobalEnv) on.exit(removeClass(testClassName, where = .GlobalEnv)) # System constructor this <- new(testClassName) # Constructor call succeeded? checkTrue(is(this, testClassName)) testSeq <- 1:23 setX(this) <- testSeq ret <- getX(this) checkTrue(is(ret, "numeric")) checkEquals(testSeq, ret) # Error handling checkException(setX(this) <- numeric(0)) checkException(setX(this) <- as.numeric(NA)) checkException(setX(this) <- c(1:4, NA)) } r-cran-svunit-1.0.6/inst/unitTests/runitsvSuite.R000066400000000000000000000054011412072605000220510ustar00rootroot00000000000000# runitsvSuite.R test suite # by Ph. Grosjean # Run it simply by example(unitTests.svUnit) # Create a few objects we need for our tests # Save current exclusion list and clear it oex <- getOption("svUnit.excludeList") # Create a very simple 'svTest' object test_R <- svTest(function() { checkTrue(1 < 2) }) # The test cases .setUp <- function() { # Executed before each test function # Remove temporarily the exclusion list for our tests options(svUnit.excludeList = NULL) # Create an object with associated tests in .GlobalEnv foo <- function(x) return(x) test(foo) <- function() { checkEquals(2, foo(2), "foo(2) returns 2") checkException(foo("x"), "foo(\"x\") raises an exception") } svSuite.foo <<- foo # Place a copy of 'foo' in .GlobalEnv # Create an object without associated tests in .GlobalEnv svSuite.bar <<- function(x) return(x^2) # Create an integration test in .globalEnv test_svSuite <<- svTest(function() { checkTrue(1 == 1, "example test: 1 == 1") checkException(nonexisting + 1, "exception when using non existing var") }) } .tearDown <- function() { # Executed after each test function # Restore previous exclusion list options(svUnit.excludeList = oex) # Remove our object with tests in .GlobalEnv rm(svSuite.foo, svSuite.bar, test_svSuite, envir = .GlobalEnv) } testsvSuite <- function() { checkTrue(is.svSuite(svSuite("svSuite.foo")), "svSuite(\"svSuite.foo\") returns a 'svSuite' object") checkTrue(is.svSuite(as.svSuite("svSuite.foo")), "as.svSuite(\"svSuite.foo\") returns a 'svSuite' object") checkTrue(is.svSuite(svSuite("svSuite.bar")), "svSuite(\"svSuite.bar\") returns a 'svSuite' object") checkTrue(is.svSuite(svSuite("test_svSuite")), "svSuite(\"test_svSuite\") returns a 'svSuite' object") checkTrue(is.svSuite(print(svSuite("test_svSuite"))), "print(svSuite) returns a 'svSuite' object invisibly") checkTrue(is.svSuite(svSuite("nonexisting")), "svSuite(\"nonexisting\") returns a 'svSuite' object") checkException(svSuite(nonexisting), "svSuite(nonexisting) raises an exception") } testsvSuiteList <- function() { checkTrue(is.svSuite(svSuiteList()), "svSuiteList() returns a 'svSuite' object") checkTrue("package:svUnit" %in% svSuiteList(), "svSuiteList() lists 'svSuite' package") checkTrue("package:svUnit (VirtualClass)" %in% svSuiteList(), "svSuiteList() lists 'VirtualClass' suite") checkTrue("test(svSuite.foo)" %in% svSuiteList(), "svSuiteList() lists objects with tests") checkTrue("test_svSuite" %in% svSuiteList(), "svSuiteList() lists 'svTest' objects") checkTrue("test_R" %in% svSuiteList(pos = parent.frame()), "svSuiteList() uses 'pos' correctly") } r-cran-svunit-1.0.6/inst/unitTests/runitsvTest.R000066400000000000000000000101701412072605000216760ustar00rootroot00000000000000# runitsvTest.R test suite # by Ph. Grosjean # Run it simply by example(unitTests.svUnit) # Create a few objects we need for tests # An R object (matrix) mat <- matrix(rnorm(4), ncol = 2) # Create very simple test cases for matrix 'mat' testmat <- svTest(function() { checkEquals(2, nrow(mat), checkNames = FALSE) checkEquals(2, ncol(mat), checkNames = TRUE) # Not important here checkTrue(is.numeric(mat)) }) # An example function without test case foo <- function(x) x # Another function with a test associated bar <- function(x) x^2 testbar <- svTest(function() { checkEqualsNumeric(4, bar(2)) checkException(bar("xx")) }) test(bar) <- testbar # The test cases .setUp <- function() { # Executed before each test function # ... } .tearDown <- function() { # Executed after each test function # ... } testis.test <- function() { checkTrue(!is.test(foo), "No associated test cases to 'foo'") checkTrue(is.test(testbar), "Is testbar a 'svTest'?") checkTrue(is.test(bar), "Associated test cases to 'bar'") checkTrue(!is.test(mat), "No associated test cases to 'mat'") checkTrue(is.test(testmat), "Is an 'svTest' object a test?") if (exists(".Log")) .Log$..Obj <- "test" # Switch the context to test() checkTrue(is.test(test(foo)), "Return dummy test if no test cases") checkIdentical(testbar, test(bar), "test of 'bar' identical to 'testbar'") if (exists(".Log")) .Log$..Obj <- "test<-" # Switch the context to `test<-`() checkException(test(foo) <- "x", "Strange value to assign as 'test'") checkException(test(foo) <- function(y) y, "Try assign a function with arguments") # Add test cases to an object mat2 <- mat checkTrue(is.test(test(mat2) <- testmat), "'mat2' valid test case association") checkIdentical(testmat, test(mat2), "test of 'mat2' identical to 'testmat'") # Strange,... but allowed test(testbar) <- testbar checkIdentical(testbar, test(testbar), "Assigning test cases to oneself") if (exists(".Log")) .Log$..Obj <- "is.test" # Switch context back to is.test() checkTrue(!is.test("x"), "'x' is is not a 'svTest' object") checkTrue(!is.test(NULL), "NULL is not a 'svTest' object") checkTrue(!is.test(NA), "NA is not a 'svTest' object") } testsvTest <- function() { checkException(svTest(foo), "Functions with arguments not allowed") checkException(svTest("x"), "Strange argument to svTest") checkTrue(is.svTest(svTest(function() {})), "Creation of a minimal 'svTest' object") if (exists(".Log")) .Log$..Obj <- "is.svTest" # Switch context to is.svTest() checkTrue(is.svTest(testmat), "Is testmat a 'svTest' object?") checkTrue(is.svTest(testbar), "Is testbar a 'svTest' object?") checkTrue(is.svTest(test(bar)), "Is test(bar) a 'svTest' object?") checkTrue(!is.svTest(foo), "'foo' is not a 'svTest' object") checkTrue(!is.svTest("x"), "'x' is not a 'svTest' object") checkTrue(!is.svTest(NULL), "NULL is not a 'svTest' object") checkTrue(!is.svTest(NA), "NA is not a 'svTest' object") checkTrue(!is.svTest(function() {}), "A function is not a 'svTest' object") if (exists(".Log")) .Log$..Obj <- "as.svTest" # Switch context to as.svTest() checkTrue(is.svTest(as.svTest(testmat)), "Coercion to a 'svTest' object") checkException(as.svTest("x"), "Try coercion on wrong object") checkException(as.svTest(function(y) y), "Try coercion on function with arguments") } testrunTest <- function() { checkTrue(inherits(runTest(testbar), "svTestData"), "result of runTest(testbar) is 'svTestData'") # Following tests fail currently for reasons I haven't spotted yet, # but runTest() works wine outside of these tests... So, I deactivate them DEACTIVATED("runTest(bar) does not work inside test functions") checkTrue(inherits(runTest(test(bar)), "svTestData"), "result of runTest(test(bar)) is 'svTestData'") checkTrue(inherits(runTest(bar), "svTestData"), "result of runTest(bar) is 'svTestData'") } r-cran-svunit-1.0.6/man/000077500000000000000000000000001412072605000147765ustar00rootroot00000000000000r-cran-svunit-1.0.6/man/Log.Rd000066400000000000000000000062601412072605000160120ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/Log.R \name{Log} \alias{Log} \alias{createLog} \alias{clearLog} \alias{errorLog} \alias{lastTest} \alias{lastSuite} \title{SciViews-R log management functions} \usage{ Log(description = NULL) createLog(description = NULL, deleteExisting = FALSE) clearLog() errorLog(stopit = TRUE, summarize = TRUE) lastTest() lastSuite() } \arguments{ \item{description}{A (short) character string describing this test suite log.} \item{deleteExisting}{Do we delete an existing a \code{.Log} object already defined in \code{.GlobalEnv} (no, by default)?} \item{stopit}{Do we issue an error (\code{\link[=stop]{stop()}} in case of any error or failure? This is particularly useful if you want to interrupt R CMD check on packages, when you included one or more test suites in examples (see \code{?unitTests}).} \item{summarize}{Should the summary of the log be printed in case we stop execution of the code when an error is found (see \verb{stopit =} argument. It is, indeed, useful to indicate at this time which tests failed or raised an error. So, this argument should usually be left at its default value.} } \value{ \code{\link[=Log]{Log()}} and \code{\link[=createLog]{createLog()}} return the \code{.Log} object defined in \code{.GlobalEnv} by reference (it is indeed an environment). So, you can use its content (and change it, if you write functions to manipulate this log). \code{\link[=clearLog]{clearLog()}} return invisibly \code{TRUE} or \code{FALSE}, depending if an existing log object was deleted or not. \code{\link[=errorLog]{errorLog()}} is mainly used for its side-effect of stopping code execution and/or printing a summary of the test runs in the context of example massaging in R CMD check (see the "Writing R extensions" manual). However, this function also returns invisibly a contingency table with the number of successes, failures, errors and deactivated tests recorded so far. \code{\link[=lastTest]{lastTest()}} and \code{\link[=lastSuite]{lastSuite()}} recall results of last test and last suite run, respectively. } \description{ These functions define the code of test functions. They are designed to check the result of some test calculation. } \details{ svUnit records results of assertions (using the checkxxx() functions) in a 'svSuiteData' object named \code{.Log} and located in .GlobalEnv. Hence, this log is easy to access. However, in order to avoid errors in your code in case this object was deleted, or not created, it is better to access it using \code{\link[=Log]{Log()}} which take care to create the object if it is missing. } \examples{ clearLog() # Clear the svUnit log # Two correct tests (checkTrue(1 < 2)) (checkException(log("a"))) errorLog() # Nothing, because there is no error \dontrun{ (checkTrue(1 > 2)) # This test fails lastTest() # Print results of last test errorLog() # Stop and summarize the tests run so far } clearLog() } \seealso{ \code{\link[=svSuiteData]{svSuiteData()}}, \code{\link[=svSuite]{svSuite()}}, \code{\link[=svTest]{svTest()}}, \code{\link[=checkEquals]{checkEquals()}} } \author{ Philippe Grosjean } \concept{unit testing} \keyword{utilities} r-cran-svunit-1.0.6/man/check.Rd000066400000000000000000000104551412072605000163470ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/check.R \name{check} \alias{check} \alias{checkEquals} \alias{checkEqualsNumeric} \alias{checkIdentical} \alias{checkTrue} \alias{checkException} \alias{DEACTIVATED} \title{SciViews-R Unit assertions (check functions)} \usage{ checkEquals( target, current, msg = "", tolerance = .Machine$double.eps^0.5, checkNames = TRUE, ... ) checkEqualsNumeric( target, current, msg = "", tolerance = .Machine$double.eps^0.5, ... ) checkIdentical(target, current, msg = "") checkTrue(expr, msg = "") checkException(expr, msg = "", silent = getOption("svUnit.silentException")) DEACTIVATED(msg = "") } \arguments{ \item{target}{a target object as reference for comparison.} \item{current}{An object created for comparison (not an S4 class object).} \item{msg}{An optional (short!) message to document a test. This message is stored in the log and printed in front of each test report.} \item{tolerance}{numeric >= 0. A numeric check does not fail if differences are smaller than 'tolerance'.} \item{checkNames}{Flag, if \code{FALSE} the names attributes are set to \code{NULL} for both \code{current} and \code{target} before performing the check.} \item{...}{Optional arguments passed to \code{\link[=all.equal]{all.equal()}} or \code{\link[=all.equal.numeric]{all.equal.numeric()}}.} \item{expr}{Syntactically valid R expression which can be evaluated and must return a logical vector (\code{TRUE}|\code{FALSE}). A named expression is also allowed but the name is disregarded. In \code{\link[=checkException]{checkException()}}, expr is supposed to generate an error to pass the test.} \item{silent}{Flag passed on to try, which determines if the error message generated by the checked function is displayed at the R console. By default, it is \code{FALSE}.} } \value{ These function return \code{TRUE} if the test succeeds, \code{FALSE} if it fails, possibly with a 'result' attribute containing more information about the problem. This is very different from corresponding functions in 'RUnit' that stop with an error in case of test failure. Consequently, current functions do not require the complex evaluation framework designed in 'RUnit' for that reason. } \description{ These functions define the assertions in test functions. They are designed to check the result of some test calculation. } \details{ These check functions are equivalent to various methods of the class junit.framework.Assert of Java junit framework. They should be code-compatible with functions of same name in 'RUnit' 0.4.17, except for \code{\link[=checkTrue]{checkTrue()}} that is vectorized here, but accept only a scalar result in 'RUnit'. For scalar test, the behavior of the function is the same in both packages. See \code{\link[=svTest]{svTest()}} for examples of use of these functions in actual test cases attached to R objects. See also the note about S4 objects in the \code{\link[RUnit:checkTrue]{RUnit::checkTrue()}} online help of the 'RUnit' package. } \examples{ clearLog() # Clear the svUnit log # All these tests are correct (checkEquals(c("A", "B", "C"), LETTERS[1:3])) (checkEqualsNumeric(1:10, seq(1, 10))) (checkIdentical(iris[1:50, ], iris[iris$Species == "setosa",])) (checkTrue(1 < 2)) (checkException(log("a"))) Log() # See what's recorded in the log # ... but these ones fail (checkEquals("A", LETTERS[1:3])) (checkEqualsNumeric(2:11, seq(1, 10))) (checkIdentical(iris[1:49, ], iris[iris$Species == "setosa",])) (checkTrue(1 > 2)) (checkException(log(1))) Log() # See what's recorded in the log # Create a test function and run it foo <- function(x, y = 2) return(x * y) test(foo) <- function() { #DEACTIVATED() checkEqualsNumeric(5, foo(2)) checkEqualsNumeric(6, foo(2, 3)) checkTrue(is.test(foo)) checkTrue(is.test(test(foo))) checkIdentical(test(foo), attr(foo, "test")) checkException(foo("b")) checkException(foo(2, "a")) } (runTest(foo)) # Of course, everything is recorded in the log Log() clearLog() } \seealso{ \code{\link[=svTest]{svTest()}}, \code{\link[=Log]{Log()}}, \code{\link[=guiTestReport]{guiTestReport()}}, \link[RUnit:checkTrue]{RUnit::checkTrue} } \author{ Written by Ph. Grosjean, inspired from the general design of the 'RUnit' package by Thomas Konig, Klaus Junemann & Matthias Burger. } \concept{unit testing} \keyword{utilities} r-cran-svunit-1.0.6/man/guiTestReport.Rd000066400000000000000000000034671412072605000201170ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/guiTestReport.R \name{guiTestReport} \alias{guiTestReport} \alias{guiSuiteList} \alias{guiSuiteAutoList} \alias{guiTestFeedback} \title{Report or give feedback to the GUI client about running test units} \usage{ guiTestReport(object, sep = "\\t", path = NULL, ...) guiSuiteList(sep = "\\t", path = NULL, compare = TRUE) guiSuiteAutoList(...) guiTestFeedback(object, path = NULL, ...) } \arguments{ \item{object}{a 'svUnitData' object.} \item{sep}{Field separator to use in the results.} \item{path}{Path where to write a 'Suites.txt' file with the list of currently available test suites (to be used by the GUI client). If \code{NULL}, no file is written (by default).} \item{...}{Not used currently.} \item{compare}{Do we compare the list of available test suite and return something to the GUI client only if there are changes in the list? This is used (when \code{TRUE}) to avoid unnecessary multiple processing of the same list by the GUI client.} } \value{ \code{\link[=guiSuiteList]{guiSuiteList()}} returns the list of available test suites invisibly. \code{\link[=guiSuiteAutoList]{guiSuiteAutoList()}} is used to establish a callback to automatically list the available test suites in the GUI. It is not intended to be called directly by the user. The other functions just return \code{TRUE} invisibly.They are used for their side effect of sending data to compatible GUI clients. } \description{ These functions are usually not called from the command line. They return data to compatible GUI clients, like Komodo Edit with the SciViews-K extension. } \seealso{ \code{\link[=svTest]{svTest()}}, \code{\link[=svSuite]{svSuite()}}, \code{\link[=koUnit_version]{koUnit_version()}} } \author{ Philippe Grosjean } \concept{unit testing} \keyword{utilities} r-cran-svunit-1.0.6/man/koUnit.Rd000066400000000000000000000052351412072605000165430ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/koUnit.R \name{koUnit} \alias{koUnit} \alias{koUnit_setAutoTest} \alias{koUnit_isAutoTest} \alias{koUnit_runTest} \alias{koUnit_showRUnitPane} \alias{koUnit_version} \title{Interact with the test unit GUI in Komodo/SciViews-K} \usage{ koUnit_setAutoTest(state) koUnit_isAutoTest() koUnit_runTest() koUnit_showRUnitPane(state) koUnit_version() } \arguments{ \item{state}{\code{TRUE} or \code{FALSE}, or missing for \code{koUnit_showRUnitPane()}, in this case, the R Unit pane visibility is toggled.} } \value{ \code{\link[=koUnit_isAutoTest]{koUnit_isAutoTest()}} returns \code{TRUE} if the test unit is in auto mode in Komodo (the selected tests are run automatically each time a .R file edited in Komodo is saved). \code{\link[=koUnit_version]{koUnit_version()}} returns the version for which the SciViews-K Unit extension was designed for. This allow to check if this version is compatible with current 'svUnit' R package version, and to propose to update the Komodo extension if needed (this mechanism is not running currently, but it will be implemented in the future to avoid or limit incompatibilities between respective R and Komodo extensions). The other functions are invoked for their side-effect and they return nothing. Note, however, that correct execution of this code in Komodo is verified, and the functions issue an error in R if they fail to execute correctly in Komodo. } \description{ These functions allow controlling the test unit module (R Unit tab at right) in Komodo with SciViews-K and SciViews-K Unit extensions. R must be correctly connected to Komodo, meaning that the 'svGUI' package must be loaded with proper configuration of client/server socket connections between R and Komodo. See the manual about SciViews-K for more information. The functions defined here are the same as JavaScript functions defined in the 'sv.r.unit' namespace in Komodo/SciViews-K Unit. For instance, \code{\link[=koUnit_runTest]{koUnit_runTest()}} is equivalent to \code{sv.r.unit.runTest();} in a Javascript macro in Komodo. } \examples{ \dontrun{ # Make sure R is communicating with Komodo before use, see ?koCmd in svGUI koUnit_version() # Toggle visibility of the R Unit pane in Komodo twice koUnit_showRUnitPane() koUnit_showRUnitPane() # Make sure that the R Unit pane is visible koUnit_showRUnitPane(TRUE) # Is the test unit running in auto mode? koUnit_isAutoTest() # Toggle auto test mode off koUnit_setAutoTest(FALSE) # Run the test units from within Komodo koUnit_runTest() } } \seealso{ \code{\link[=guiTestReport]{guiTestReport()}} } \author{ Philippe Grosjean } \concept{unit testing} \keyword{utilities} r-cran-svunit-1.0.6/man/svSuite.Rd000066400000000000000000000223561412072605000167370ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/svSuite.R \name{svSuite} \alias{svSuite} \alias{as.svSuite} \alias{is.svSuite} \alias{print.svSuite} \alias{svSuiteList} \alias{makeUnit.svSuite} \alias{runTest.svSuite} \title{Create and run test suites by collecting together unit tests and function tests defined in objects} \usage{ svSuite(tests) as.svSuite(x) is.svSuite(x) \method{print}{svSuite}(x, ...) svSuiteList( packages = TRUE, objects = TRUE, dirs = getOption("svUnit.dirs"), excludeList = getOption("svUnit.excludeList"), pos = .GlobalEnv, loadPackages = FALSE ) \method{makeUnit}{svSuite}( x, name = make.names(deparse(substitute(x))), dir = tempdir(), objfile = "", codeSetUp = NULL, codeTearDown = NULL, pos = .GlobalEnv, ... ) \method{runTest}{svSuite}(x, name = make.names(deparse(substitute(x))), unitname = NULL, ...) } \arguments{ \item{tests}{A character string with items to include in the test suite. It could be 'package:PKG' for including test units located in the /unitTests subdirectory of the package PGK, or 'package:PKG (SUITE)' for test units located in the subdirectory /unitTests/SUITE of package PKG, or 'dir:MYDIR' for including test units in MYDIR, or 'test(OBJ)' for tests embedded in an object, or 'OBJ' for 'svTest' object directly.} \item{x}{Any kind of object.} \item{...}{Further arguments to pass to \code{\link[=makeUnit]{makeUnit()}} or \code{\link[=runTest]{runTest()}} (not used yet).} \item{packages}{Do we list test units available in loaded packages? Alternatively one can provide a character vector of package names, and it will be used to filter packages (take care: in this case it will look at installed packages, not only loaded packages)!} \item{objects}{Do we list test available in objects? Alternatively, one can provide a character vector of object names, and it will filter objects in 'pos' according to this vector.} \item{dirs}{An additional list of directories where to look for more test units. For convenience, this list can simply be saved as an 'svUnit.dirs' options.} \item{excludeList}{A list of items to exclude from the listing. The function uses regular expression to match the exclusions. So, for instance, specifying \code{"package:MYPKG"} will exclude all items from package 'MYPKG', while using \code{"package:MYPKG$"} will exclude only tests suites defined in the .../MYPKG/unitTests directory, bur not in its subdirectories. For convenience, it can be saved in a 'svUnit.excludeList' option. By default, all tests for packages whose name start with 'sv' or 'RUnit' are excluded, that is, \code{c("package:sv", "package:RUnit")}.} \item{pos}{The environment to look for 'objects' (environment, character string with name of an environment, or integer with position of the environment in the search path.} \item{loadPackages}{In the case a list of packages is provided in \verb{packages =}, do we make sure that these packages are loaded? If yes, the function will try to load all packages in that list that are not loaded yet and will issue a warning for the packages not found. Default, \code{FALSE}.} \item{name}{The name of the test suite to build.} \item{dir}{The directory where to create the test unit file.} \item{objfile}{The path to the file containing the original source code of the object being tested. This argument is used to bring a context for a test and allow a GUI to automatically open the source file for edition when the user clicks on a test that failed or raised an error.} \item{codeSetUp}{An expression with some code you want to add to the \code{.setUp()} function in your unit file (this function is executed before each test.} \item{codeTearDown}{An expression with some code you want to add to the \code{.tearDown()} function in your unit file (this function is executed after each test.} \item{unitname}{The name of a unit to run inside the suite. If \code{NULL} (by default), all units are run.} } \value{ \code{\link[=svSuite]{svSuite()}}, \code{\link[=as.svSuite]{as.svSuite()}} and \code{\link[=svSuiteList]{svSuiteList()}} return a 'svSuite' object. \code{\link[=is.svSuite]{is.svSuite()}} returns \code{TRUE} if the object is an 'svSuite'. \code{\link[=makeUnit]{makeUnit()}} creates a test unit file on disk, and \code{\link[=runTest]{runTest()}} runs the tests in such a file. They are used for their side-effect, but the first one also returns the file created, and the second one returns invisibly the list of all test unit files that where sourced ans run. } \description{ A 'svSuite' object is essentially a list of test units directories (or packages, in this case, corresponding directories are PKG/unitTests and its subdirectories), and of object names containing tests to add temporarily to the test suite. These must be formatted in a concise way as described for the 'tests' argument. } \details{ \code{\link[=svSuiteList]{svSuiteList()}} lists all loaded packages having /unitTests/runit*.R files (or similar files in subdirectories), and all objects in the user workspace that have a 'test' attribute, or are 'svTest' objects (by default). It is a rather exhaustive list of all test items currently available in the current R session, but restricted by \code{getOption("svUnit.excludeList")}. \code{\link[=makeUnit]{makeUnit()}} writes a test unit on disk with the tests from the objects listed in the 'svSuite' object that do not belong yet to a test unit. \code{\link[=runTest]{runTest()}} runs all the test in packages, directories and objects listed in the 'svSuite' object. Thanks to the variety of sources allowed for tests, it is possible to define these tests in a structured way, inside packages, like for the 'RUnit' package (but with automatic recognition of test units associated to packages, in the present case). It is also easy to define tests more loosely by just attaching those tests to the objects you want to check. Whenever there objects are loaded in the user's workspace, their tests are available. In both cases, a test unit file on disk is sourced in a local environment and test functions are run (same approach as in the 'RUnit' package, and the same test unit files should be compatibles with both 'RUnit' and 'svUnit' packages), but in the case of a loosely definition of the tests by attachment to objects, the test unit file is created on the fly in the temporary directory (by default). At any time, you can transform a series of tests loosely attached to objects into a test unit file by applying \code{\link[=makeUnit]{makeUnit()}} to a 'svSuite' object, probably specifying another directory than the (default) temporary dir for more permanent storage of your test unit file. The best choice is the '/inst/unitTests' directory of a package source, or one of its subdirectories. That way, your test unit file(s) will be automatically listed and available each time you load the compiled package in R (if you list them using \code{\link[=svSuiteList]{svSuiteList()}}). Of course, you still can exclude tests from given packages by adding 'package:PKG' in the exclusion list with something like: \code{options(svUnit.excludeList = c(getOption("svUnit.excludeList"), "package:PKG"))}. } \examples{ svSuiteList() # List all currently available test units and test cases # Exclusion list is used (regular expression filtering!). It contains: (oex <- getOption("svUnit.excludeList")) # Clear it, and relist available test units options(svUnit.excludeList = NULL) svSuiteList() # Two functions that include their test cases Square <- function(x) return(x^2) test(Square) <- function() { checkEquals(9, Square(3)) checkEquals(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } Cube <- function(x) return(x^3) test(Cube) <- function() { checkEquals(27, Cube(3)) checkEquals(c(1, 8, 28), Cube(1:3)) checkException(Cube("xx")) } # A separate test case object (not attached to a particular object) # This is the simplest way to define quick and durty integration tests test_Integrate <- svTest(function() { checkTrue(1 < 2, "check1") v <- 1:3 # The reference w <- 1:3 # The value to compare to the reference checkEquals(v, w) }) # A function without test cases (will be filtered out of the suite list) foo <- function(x) return(x) # Look now which tests are available svSuiteList() # Only objects, no package units svSuiteList(packages = FALSE) \dontrun{ # Create the test unit file for all objects with tests in .GlobalEnv myunit <- makeUnit(svSuiteList(), name = "AllTests") file.show(myunit, delete.file = TRUE) } # Filter objects using a list (object with/without tests and a nonexisting obj) svSuiteList(packages = FALSE, objects = c("Cube", "foo", "bar")) # Create another svSuite object with selected test items (mysuite <- svSuite(c("package:svUnit (VirtualClass)", "test(Cube)"))) is.svSuite(mysuite) # Should be! \dontrun{ # Run all the tests currently available (runTest(svSuiteList(), name = "AllTests")) summary(Log()) } # Restore previous exclusion list, and clean up the environment options(svUnit.excludeList = oex) rm(Square, Cube, foo, test_Integrate, mysuite, myunit, oex) } \seealso{ \code{\link[=svSuiteData]{svSuiteData()}}, \code{\link[=svTest]{svTest()}}, \code{\link[=Log]{Log()}}, \code{\link[=checkEquals]{checkEquals()}}, \code{\link[RUnit:checkEquals]{RUnit::checkEquals()}} } \author{ Philippe Grosjean } \concept{unit testing} \keyword{utilities} r-cran-svunit-1.0.6/man/svSuiteData.Rd000066400000000000000000000124121412072605000175210ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/svSuiteData.R \name{svSuiteData} \alias{svSuiteData} \alias{is.svSuiteData} \alias{stats.svSuiteData} \alias{metadata} \alias{metadata.svSuiteData} \alias{print.svSuiteData} \alias{summary.svSuiteData} \alias{protocol} \alias{protocol.default} \alias{protocol.svSuiteData} \alias{protocol_text} \alias{protocol_text.svSuiteData} \alias{protocol_junit} \alias{protocol_junit.svSuiteData} \title{Objects of class 'svSuiteData' contain results from running test suites} \usage{ is.svSuiteData(x) \method{stats}{svSuiteData}(object, ...) metadata(object, ...) \method{metadata}{svSuiteData}( object, fields = c("R.version", "sessionInfo", "time", "description"), ... ) \method{print}{svSuiteData}(x, all = FALSE, file = "", append = FALSE, ...) \method{summary}{svSuiteData}(object, ...) protocol(object, type = "text", file = "", append = FALSE, ...) \method{protocol}{default}(object, type = "text", file = "", append = FALSE, ...) \method{protocol}{svSuiteData}(object, type = "text", file = "", append = FALSE, ...) protocol_text(object, file = "", append = FALSE, ...) \method{protocol_text}{svSuiteData}(object, file = "", append = FALSE, ...) protocol_junit(object, ...) \method{protocol_junit}{svSuiteData}(object, file = "", append = FALSE, ...) } \arguments{ \item{x}{Any kind of object, or a 'svSuiteData' object in the case of \code{\link[=print]{print()}}.} \item{object}{A 'svSuiteData' object.} \item{...}{Further arguments to pass to methods. Not used yet.} \item{fields}{Character vector. The name of all metadata items you want to extract for the object. The default value is an exhaustive list of all available metadata (i.e., defined by default) in the object, but you can add more: just add a corresponding attribute to your object.} \item{all}{Do we print concise report for all test, or only for the tests that fail or produce an error?} \item{file}{Character. The path to the file where to write the report. If \code{file = ""}, the protocol report is output to the console.} \item{append}{Do we append to this file?} \item{type}{Character. The type of protocol report to create. For the moment, only \code{type = "text"} and \code{type = "junit"} are supported, but further types (HTML, LaTeX, Wiki, etc.) could be provided later.} } \value{ \code{\link[=is.svSuiteData]{is.svSuiteData()}} returns \code{TRUE} if the object is an 'svSuiteData'. The various methods serve to extract or print content in the object. } \description{ The 'svSuiteData' object contains results of all test run in one or more test suites. The \code{checkxxx()} functions and the \code{\link[=runTest]{runTest()}} method generate data (objects 'svTestData') contained in the default 'svSuiteData' named \code{.Log} and located in \code{.GlobalEnv}. It is then possible to display and report information it contains in various ways to analyze the results. } \details{ A 'svSuiteData' is, indeed, an environment. The results for the various tests runs are in non hidden (i.e., names not starting with a dot) objects that are of class 'svTestData' in this environment. Various other objects that control the execution of the test, their context, etc. are contained as hidden objects with name starting with a dot. Note that using an environment instead of a list for this object allows for a call by reference instead of a usual call by value in R, when passing this object to a function. This property is largely exploited in all svUnit functions to make sure results of test runs are centralized in the same log ('svSuiteData' object). } \examples{ clearLog() # Clear any existing log # Run some tests checkTrue(1 < 2) checkException(log("a")) foo <- function(x, y = 2) return(x * y) test(foo) <- function() { checkEqualsNumeric(4, foo(2)) checkEqualsNumeric(6, foo(2, nonexisting)) checkTrue(is.test(foo)) warning("This is a warning") cat("Youhou from test!\n") # Don't use, except for debugging! checkTrue(is.test(test(foo))) checkIdentical(attr(foo, "test"), test(foo)) checkException(foo(2, nonexisting)) #DEACTIVATED("My deactivation message") checkException(foo(2)) # This test fails } runTest(foo) # Now inspect the log, which is a 'svSuiteData' object is.svSuiteData(Log()) stats(Log()) metadata(Log()) Log() # Print method summary(Log()) \dontrun{ # To get a print of the test protocol on file, use: protocol(Log(), type = "text", file = "RprofProtocol.out") file.show("RprofProtocol.out") unlink("RprofProtocol.out") } rm(foo) \dontrun{ # Profiling of very simple test runs library(utils) createLog(description = "test profiling", deleteExisting = TRUE) imax <- 3 jmax <- 100 l <- 50 Rprof() for (i in 1:imax) { # Change the context for these tests .Log$..Test <- paste("Test", i, sep = "") .Log$..Tag <- paste("#", i, sep = "") res <- system.time({ for (j in 1:jmax) checkTrue(i <= j, "My test") }, gcFirst = TRUE)[3] print(res) flush.console() } Rprof(NULL) # Look at profile summaryRprof() unlink("Rprof.out") # Look at the log summary(Log()) } } \seealso{ \code{\link[=svSuite]{svSuite()}}, \code{\link[=is.svTestData]{is.svTestData()}}, \code{\link[=Log]{Log()}}, \code{\link[=checkEquals]{checkEquals()}} } \author{ Philippe Grosjean; Mario Frasca for the junit protocol. } \concept{unit testing} \keyword{utilities} r-cran-svunit-1.0.6/man/svTest.Rd000066400000000000000000000131761412072605000165650ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/runExamples.R, R/svTest.R \name{makeTestListFromExamples} \alias{makeTestListFromExamples} \alias{svTest} \alias{print.svTest} \alias{as.svTest} \alias{is.svTest} \alias{is.test} \alias{test} \alias{test<-} \alias{makeUnit} \alias{makeUnit.default} \alias{makeUnit.svTest} \alias{runTest} \alias{runTest.default} \alias{runTest.list} \alias{runTest.svTest} \title{Create, attach to and manipulate test functions in R objects} \usage{ makeTestListFromExamples(packageName, manFilesDir, skipFailing = FALSE) svTest(testFun) \method{print}{svTest}(x, ...) as.svTest(x) is.svTest(x) is.test(x) test(x) test(x) <- value makeUnit(x, ...) \method{makeUnit}{default}( x, name = make.names(deparse(substitute(x))), dir = tempdir(), objfile = "", codeSetUp = NULL, codeTearDown = NULL, ... ) \method{makeUnit}{svTest}( x, name = make.names(deparse(substitute(x))), dir = tempdir(), objfile = "", codeSetUp = NULL, codeTearDown = NULL, ... ) runTest(x, ...) \method{runTest}{default}( x, name = deparse(substitute(x)), objfile = "", tag = "", msg = "", ... ) \method{runTest}{list}(x, ...) \method{runTest}{svTest}( x, name = deparse(substitute(x)), objfile = "", tag = "", msg = "", ... ) } \arguments{ \item{packageName}{A character string identifying the package from which to extract examples.} \item{manFilesDir}{A character string identifying the directory holding the manual pages and examples.} \item{skipFailing}{A logical indicating whether missing or failing documentation examples should be marked as \code{skipped} instead of as \code{failure}.} \item{testFun}{A function without arguments defining assertions (using \code{checkxxx()} functions) for tests to be transformed into a 'svTest' object.} \item{x}{Any kind of object.} \item{...}{Further arguments to the method (not used yet).} \item{value}{The tests to place in the object (as 'test' attribute); could be a 'svTest' object, or a function without arguments with assertions (\code{checkxxx()} functions).} \item{name}{The name of a test.} \item{dir}{The directory where to create the test unit file.} \item{objfile}{The path to the file containing the original source code of the object being tested. This argument is used to bring a context for a test and allow a GUI to automatically open the source file for edition when the user clicks on a test that failed or raised an error.} \item{codeSetUp}{An expression with some code you want to add to the \code{.setUp()} function in your unit file (this function is executed before each test.} \item{codeTearDown}{An expression with some code you want to add to the \code{.tearDown()} function in your unit file (this function is executed after each test.} \item{tag}{A tag is a character string identifying a location in source code files (either a test unit file, or the original source code of the tested objects defined in \verb{objfile =}. This character string will be searched by the text editor for easy location of the cursor near the corresponding test command, or near the location in the original object that is concerned by this test. Use any string you want to uniquely identify your tag, both in your files, and in this argument.} \item{msg}{A message you want to associate with this test run.} } \value{ A 'svTest' object for \code{\link[=svTest]{svTest()}}, \code{\link[=as.svTest]{as.svTest()}} and \code{\link[=test]{test()}}. Function \code{\link[=is.svTest]{is.svTest()}} returns \code{TRUE} if 'x' is a 'svTest' object, and \code{is.test()} does the same but also looks in the 'test' attribute if the class of 'x' is not 'svTest' and returns \code{TRUE} if it finds something there. \code{\link[=makeUnit]{makeUnit()}} takes an object, extract its test function and write it in a sourceable test unit on the disk (it should be compatible with 'RUnit' test unit files too). \code{\link[=runTest]{runTest()}} returns invisibly a 'svTestData' object with all results after running specified tests. } \description{ Test functions are functions without arguments with class 'svTest' containing one or more assertions using \code{checkxxx()} functions. They can be attached to any object as a 'test' attribute. They can also be transferred into a more formal test unit file on disk by applying the \code{\link[=makeUnit]{makeUnit()}} method. } \examples{ clearLog() # Clear the log file foo <- function(x, y = 2) return(x * y) is.test(foo) # No # Create test cases for this function test(foo) <- function() { checkEqualsNumeric(4, foo(2)) checkEqualsNumeric(6, foo(2, 3)) checkTrue(is.test(foo)) checkTrue(is.test(test(foo))) checkIdentical(attr(foo, "test"), test(foo)) checkException(foo(2, "aa")) checkException(foo("bb")) } is.test(foo) # Yes \dontrun{ # Create a test unit on disk and view it unit <- makeUnit(foo) file.show(unit, delete.file = TRUE) } # Run the test (runTest(foo)) # Same as... bar <- test(foo) (runTest(bar)) # How fast can we run 100 times such kind of tests (700 test in total)? # (just an indication because in real situation with test unit files, we # have also the time required to source the units!) system.time(for (i in 1:100) runTest(foo))[3] is.svTest(test(foo)) # Yes, of course! # When an object without associated test is passed to runTest(), # a simple test containing only a DEACTIVATED entry is build x <- 1:10 summary(runTest(x)) summary(Log()) rm(foo, bar, x) } \seealso{ \code{\link[=svSuite]{svSuite()}}, \code{\link[=is.svTestData]{is.svTestData()}}, \code{\link[=Log]{Log()}}, \code{\link[=checkEquals]{checkEquals()}} } \author{ Philippe Grosjean } \concept{unit testing} \keyword{utilities} r-cran-svunit-1.0.6/man/svTestData.Rd000066400000000000000000000045071412072605000173550ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/svTestData.R \name{svTestData} \alias{svTestData} \alias{is.svTestData} \alias{stats} \alias{stats.svTestData} \alias{print.svTestData} \alias{summary.svTestData} \alias{protocol_junit.svTestData} \title{Objects of class 'svTestData' contain results from running a test} \usage{ is.svTestData(x) stats(object, ...) \method{stats}{svTestData}(object, ...) \method{print}{svTestData}(x, all = FALSE, header = TRUE, file = "", append = FALSE, ...) \method{summary}{svTestData}(object, header = TRUE, file = "", append = FALSE, ...) \method{protocol_junit}{svTestData}(object, ...) } \arguments{ \item{x}{Any kind of object, or a 'svTestData' object in the case of \code{\link[=print]{print()}}.} \item{object}{A 'svTestData' object.} \item{...}{Further arguments to pass to methods. Not used yet.} \item{all}{Do we print concise report for all test, or only for the tests that fail or produce an error?} \item{header}{Do we print a header or not?} \item{file}{Character. The path to the file where to write the report. If \code{file = ""}, the report is output to the console.} \item{append}{Do we append to this file?} } \value{ \code{\link[=is.svTestData]{is.svTestData()}} returns \code{TRUE} if the object is an 'svTestData'. The various methods serve to extract or print content in the object. } \description{ The 'svTestData' contains results of test run. The \code{checkxxx()} functions and the \code{runTest()} method generate one such object which is located in the \code{.Log} object in \code{.GlobalEnv}. It is then possible to display and report information it contains in various ways to analyze the results. } \examples{ foo <- function(x, y = 2) return(x * y) is.test(foo) # No # Create test cases for this function test(foo) <- function() { checkEqualsNumeric(4, foo(2)) checkEqualsNumeric(5, foo(2, 3)) checkEqualsNumeric(5, foo(nonexists)) } # Generate a 'svTestData' object by running the test obj <- runTest(foo) # Equivalent to runTest(test(foo)), but shorter obj summary(obj) stats(obj) is.svTestData(obj) rm(foo, obj) } \seealso{ \code{\link[=svTest]{svTest()}}, \code{\link[=is.svSuiteData]{is.svSuiteData()}}, \code{\link[=Log]{Log()}}, \code{\link[=checkEquals]{checkEquals()}} } \author{ Philippe Grosjean } \concept{unit testing} \keyword{utilities} r-cran-svunit-1.0.6/man/svUnit-package.Rd000066400000000000000000000170261412072605000201540ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/svUnit-package.R \docType{package} \name{svUnit-package} \alias{svUnit} \alias{svUnit-package} \title{svUnit: 'SciViews' - Unit, Integration and System Testing} \description{ A complete unit test system and functions to implement its GUI part. } \details{ The SciViews 'svUnit' package defines a framework for testing R code, not unlike jUnit for Java. It is inspired on the \code{checkxxx()} functions from the 'RUnit' package and the same test unit files should be compatible with both 'svUnit' and 'RUnit'. However, the internal implementation is completely different and svUnit can also be used interactively, while 'RUnit' is only designed to run test units written in files on disks. The test unit framework provided in 'svUnit' is based on tests, also called assertions, implemented in \code{checkxxx()} functions. For instance, the \code{checkTrue(expr)} function check if its 'expr' argument returns \code{TRUE}. Results of these assertions are collected in a centralized logger located in the \code{.Log} object in \code{.GlobalEnv}. This is a 'svSuiteData' object with data about the context of the tests (see for instance, \code{\link[=lastTest]{lastTest()}}, \code{\link[=lastSuite]{lastSuite()}} or \code{metadata(.Log)}). Assertions can give three results: (1) \code{TRUE} if success, (2) \code{FALSE} in case of failure (in our example, 'expr' in \code{checkTrue(expr)} did not return \code{TRUE}), and (3) \code{NA} if the code in 'expr' cannot be parsed or executed correctly. All these errors or failures are catch and recorded in the logger, as individual 'svTestData' objects. Both the logger ('svSuiteData' object) and test records inside it ('svTestData' objects) have convenient methods to visualize information they contain: \code{\link[=print]{print()}}, \code{\link[=summary]{summary()}} and \code{\link[=stats]{stats()}} methods. Access to the individual test records in the logger is done with list-like instructions: \code{.Log$mytest} returns the 'svTestData' object named 'mytest', itself the result of running test in the 'mytest' test function (i.e., \code{runTest(mytest)}, see hereunder). Assertions run at the command line, outside of specific contexts provided by test functions, test units and test suites (see hereunder) are recorded under the 'eval' 'svTestData' object in the logger (i.e., \code{.Log$eval}). Since a 'svSuiteData' object (the logger) is also an environment, you can get the list of all test records it contains using \code{ls(.Log)}, and you can eliminate a given test record using \code{rm(mytest, envir = .Log)}. Test cases are collections of assertions with the satellite code needed to build example or situations to be tested. They are collected together in argument-less functions with class being 'svTest'. See \code{?svTest} for further explanations and a couple of example test cases/test functions. In its simplest instance, a test function is defined as a separate R object loaded in memory (unlike RUnit where all test must be defined in files). You run it simply by using \code{runTest(mytest)}. A slightly more structured way to work is to attach the test function to the object it testes. You use \code{test(myobj) <- testmyobj} to do so, and retrieve it with \code{test(myobj)}. Now, the test function always follows the tested object. Testing the object is still simple by using \code{runTest(myobj)}, which is totally equivalent to \code{runTest(test(myobj))}. One can determine if an object has a test function associated, or is a test function itself by using \code{is.test(myobj)}. Several test functions can be collected together in so-called test units. A test unit only exists on disk. It is a file named 'runit*.R' containing sourceable R code with test functions having names starting with 'test' (unlike 'RUnit', the default convention of file names starting with 'runit' and test function names starting with 'test' is not customizable in 'svUnit'). One can also define special \code{.setUp()} and \code{.tearDown()} functions in the test unit. The first function will be run before each test function, and the latter one will be run after it. Test units are created manually, or from a collection of objects with associated test functions loaded in an environment (usually \code{.GlobalEnv}) thanks to the \code{\link[=makeUnit]{makeUnit()}} method. These units should be mutually compatible with those used in the 'RUnit' package (at least this is verified with version 0.4-17 of 'RUnit'). Test units defined for packages should be located in the package /runitTests subdirectory (/inst/runitTests for source of the package) or one of its subdirectories. That way, they are located automatically by the function \code{\link[=svSuiteList]{svSuiteList()}} that also automatically detects all objects with associated test functions loaded in \code{.GlobalEnv}. Test suites are 'svSuite' objects with a list of test units or test objects to collect in the suite. Thus, \code{\link[=svSuiteList]{svSuiteList()}} automatically builds such a suite with all tests it finds in R, with many possibilities to filter packages' test units, objects' test functions, or to add non standard directories with test units, for instance. See \code{?svSuite} for more details on creating and using these suites. A GUI (Graphical User Interface) is provided to automatically build and run tests suites and to get a graphical (tree) interactive report of the results in the Komodo Edit or IDE code editor, together with the SciViews-K extension. If you want to use this (optional) GUI, you have to install required software components on your machine. Finally, the 'svUnit' framework is compatible with \verb{R CMD check} (see the manual "Writing R extensions"). You simply define man pages (.Rd files) with an example section running selected test units from your package. The function \code{\link[=errorLog]{errorLog()}} is designed to generate and error if one or more tests failed or raised an error during \verb{R CMD check}, and it should be used at the end of the example that runs your unit test(s). That way, \verb{R CMD check} is interrupted and a detailed report of the tests that failed or raised an error is printed. See an example in \code{?unitTests.svUnit}. } \examples{ # Clear the logger clearLog() # Design and attach a simple test function to an object foo <- function(x, y = 2) return(x * y) testfoo <- function() { #DEACTIVATED() # Use this to deactive the test (notice placed in the log) checkEqualsNumeric(5, foo(2), "Check return of foo()") checkException(foo("b"), "Wrong first argument") checkException(foo(2, "a"), "Wrong second argument") } # Attach this to the foo function test(foo) <- testfoo # Run this test runTest(foo) # Inspect the result ls(.Log) .Log$`test(foo)` # This test fails. You see that the test function requires that foo(2) = 5 # and the actual implementation returns 4. This is a trivial, useless example, # but you are supposed to correct the function. For instance: foo <- function(x, y = 2) return(x * y + 1) test(foo) <- testfoo (runTest(foo)) # Now, that's fine! } \references{ There is a huge literature and unit testing. An easy starting point is: \url{https://en.wikipedia.org/wiki/Unit_test}. } \seealso{ Useful links: \itemize{ \item \url{https://github.com/SciViews/svUnit} \item \url{https://www.sciviews.org/svUnit/} \item Report bugs at \url{https://github.com/SciViews/svUnit/issues} } } \author{ Written by Ph. Grosjean, inspired from the general design of the 'RUnit' package by Thomas Konig, Klaus Junemann & Matthias Burger. } \keyword{internal} r-cran-svunit-1.0.6/man/unitTests.svUnit.Rd000066400000000000000000000021761412072605000205640ustar00rootroot00000000000000% Generated by roxygen2: do not edit by hand % Please edit documentation in R/unitTests.svUnit.R \name{unitTests.svUnit} \alias{unitTests.svUnit} \title{Unit tests for the package 'svUnit'} \description{ Performs unit tests defined in this package by running \code{example(unitTests.svUnit)}. Tests are in \code{runit*.R} files located in the '/unitTests' subdirectory or one of its subdirectories ('/inst/unitTests' and subdirectories in package sources). } \examples{ if (require(svUnit)) { clearLog() runTest(svSuite("package:svUnit"), "svUnit") \donttest{ # Tests to run with example() but not with R CMD check runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass") } \dontrun{ # Tests to present in ?unitTests.svUnit but not run automatically # Run all currently loaded packages test cases and test suites runTest(svSuiteList(), "AllTests") } \dontshow{ # Put here test units you want to run during R CMD check but # don't want to show or run with example(unitTests.svUnit) } # Check errors at the end (needed to interrupt R CMD check) errorLog() } } \author{ Philippe Grosjean } \keyword{utilities} r-cran-svunit-1.0.6/tests/000077500000000000000000000000001412072605000153655ustar00rootroot00000000000000r-cran-svunit-1.0.6/tests/spelling.R000066400000000000000000000002411412072605000173220ustar00rootroot00000000000000if(requireNamespace('spelling', quietly = TRUE)) spelling::spell_check_test(vignettes = TRUE, error = FALSE, skip_on_cran = TRUE) r-cran-svunit-1.0.6/tests/test-with-svUnit.R000066400000000000000000000005241412072605000207270ustar00rootroot00000000000000# Note that the message in case of error is not explicit enough.Moreover, # nothing is printed in case of success, while we would like a succinct report # TODO: check if warnings are not converted into errors during R CMD check tests library(svUnit) clearLog() res <- capture.output(runTest(svSuite("package:svUnit"), "svUnit")) errorLog() r-cran-svunit-1.0.6/tests/testthat/000077500000000000000000000000001412072605000172255ustar00rootroot00000000000000r-cran-svunit-1.0.6/tests/testthat/test.svUnit.R000066400000000000000000000017331412072605000216220ustar00rootroot00000000000000# Run tests using svUnit # This is a wrapper to run these from within testthat # so that RStudio's test function and devtools::test() can be used too pkgname <- "svUnit" test_svUnit <- function(pkgname) { library(svUnit) clearLog() # Current dir is /tests/testthat basedir <- dirname(dirname(getwd())) # Look for our tests in /unitTests or /inst/unitTests testdir <- file.path(basedir, "unitTests") if (!dir.exists(testdir)) { # We must be in a development environment, look at /inst/unitTests testdir <- file.path(basedir, "inst", "unitTests") } if (!dir.exists(testdir)) { # We must be in a checking environment, look at //unitTests testdir <- file.path(basedir, pkgname, "unitTests") } if (!dir.exists(testdir)) { # No test directory found stop("No svUnit test directories found for ", basename(basedir)) } res <- capture.output(runTest(svSuite(paste0("dir:", testdir)), pkgname)) summary(Log()) } test_svUnit(pkgname) r-cran-svunit-1.0.6/vignettes/000077500000000000000000000000001412072605000162335ustar00rootroot00000000000000r-cran-svunit-1.0.6/vignettes/svUnit.Rmd000066400000000000000000001162731412072605000202010ustar00rootroot00000000000000--- title: "svUnit - A framework for unit testing in R" author: "Philippe Grosjean (phgrosjean@sciviews.org)" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: fig_caption: yes vignette: > %\VignetteIndexEntry{svUnit - A framework for unit testing in R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ## Introduction Unit testing (see https://en.wikipedia.org/wiki/Unit_test) is an approach successfully used to develop software, and to ease code refactoring for keeping bugs to the minimum. It is also the insurance that the software is doing the right calculation (quality insurance). Basically, a test just checks if the code is running and is producing the correct answer/behavior in a given situation. As such, unit tests are build in R package production because all examples in documentation files, and perhaps, test code in `/tests` subdirectory are run during the checking of a package (`R CMD check `). However, the R approach lacks a certain number of features to allow optimal use of unit tests as in extreme programming (test first – code second): * Tests are related to package compilation and cannot easily be run independently (for instance, for functions developed separately). * Once a test fails, the checking process is interrupted. Thus one has to correct the bug and launch package checking again... and perhaps get caught by the next bug. It is a long and painful process. * There is no way to choose one or several tests selectively: all are run or not (depending on command line options) during package checking. * It is very hard, or near to impossible to program in R in a test driven development (*write tests first*) with the standard tools (https://en.wikipedia.org/wiki/Test-driven_development). * Consequently, the '*test-code-simplify*' cycle is not easily accessible yet to R programmer, because of the lack of an interactive and flexible testing mechanism providing immediate, or quasi immediate feedback about changes made. * We would like also to emphasize that test suites are not only useful to check code, they can also be used to check data, or the pertinence of analyses. ## Unit testing in R without 'svUnit' Besides the "regular" testing mechanism of R packages, one can find the 'testthat' and 'RUnit' packages on CRAN (https://CRAN.R-project.org/package=testthat and https://CRAN.R-project.org/package=RUnit, respectively)^[There is also a very simple testing system in 'testit' (https://CRAN.R-project.org/package=testit) using only one assertion: `assert()` for all test. Other interesting R packages on CRAN are 'testthis' (https://CRAN.R-project.org/package=testthis), which adds more tools and an RStudio addins around 'testthat', and 'runittotestthat' (https://CRAN.R-project.org/package=runittotestthat) which, as its name implies, automatically converts 'RUnit' tests into 'testthat' equivalents.]. Another package used to provide an alternate implementation of test unit: 'butler', but it is archived because it is not maintained any more and has given up in favor of 'RUnit'. The 'testthat' package is heavily used by the UseR community (there are many more packages on CRAN that use it than any other testing system). It implements R testing in a rather original way. 'RUnit', on the other hand sticks with the framework initially defined by junit for Java and largely developed for many different languages. In this document, we will not discuss further about 'testthat'. We will focus on the junit-like framework implemented in 'RUnit' and 'svUnit'. 'RUnit' implements the following features: * **Assertions**, `checkEquals()`, `checkEqualsNumeric()`, `checkIdentical()` and `checkTrue()` and negative tests (tests that check error conditions, `checkException()`). * Assertions are grouped into R functions to form one **test function** that runs a series of related individual tests. It is easy to temporarily inactivate one or more tests by commenting lines in the function. To avoid forgetting tests that are commented out later on, there is special function, named `DEACTIVATED()` that tags the test with a reminder for your deactivated items (i.e., the reminder is written in the test log). * A series of test functions (whose name typically start with `test....`) are collected together in a source-able R code file (name starting with `runit....`) on disk. This file is called a **test unit**. * A **test suite** (object `RUnitTestSuite`) is a special object defining a battery of tests. It points to one or several directories containing test units. A test suite is defined by `defineTestSuite()`. * One or more test suites can be run by calling `runTestSuite()`. There is a shortcut to define and run a test suite constituted by only one test unit by using the function `runTestFile()`. Once the test is run, a `RUnitTestData` object is created that contains all the information collected from the various tests run. * One can print a synthetic report (how many test units, test functions, number of errors, fails and deactivated item), or get a more extensive `summary()` of the tests with indication about which ones failed or produced errors. The function `printTextProtocol()` does the same, while `printHTMLProtocol()` produces a report in HTML format. * 'RUnit' contains also functions to determine which code is run in the original function when tested, in order to detect the parts of the code not covered by the test suite (code coverage function `inspect()` and function `tracker()`). As complete and nice as 'RUnit' is, there is no tools to integrate the test suite in a given development environment (IDE) or graphical user interface (GUI), as far as we know. In particular, there is no real-time reporting mechanism used to easy the *test-code-simplify* cycle. The way tests are implemented and run is left to the user, but the implementation suggests that the authors of 'RUnit' mainly target batch execution of the tests (for instance, nightly check of code in a server), rather that real-time interaction with the tests. There is also no integration with the "regular" `R CMD check` mechanism of R in 'RUnit'. ## Unit testing framework for R with 'svUnit' Our initial goal was to implement a GUI layer on top of 'RUnit', and to integrate test units as smoothly as possible in a code editor, as well as, making tests easily accessible and fully compatible with `R CMD check` on all platforms supported by R. Ultimately, the test suite should be easy to create, to use interactively, and should be able to test functions in a complex set of R packages. However, we encountered several difficulties while trying to enhance 'RUnit' mechanism. When we started to work on this project, 'RUnit' (version 0.4-17) did not allow to subclass its objects (version 0.4-19 solved this problem). Moreover, its `RUnitTestData` object is optimized for quick testing, but not at all for easy reviewing of its content: it is a list of lists of lists,... requiring embedded for loop and `lapply()` / `sapply()` procedures to extract some content. Moreover, 'RUnit' is implemented as S4 classes, requiring the heavy 'methods' package to be loaded, making it impossible to use in a lightweight environment without it, or to test R without the 'methods' package loaded. Finally, the concept of test units as source-able files on disk is a nice idea, but it is too rigid for quick writing test cases for objects not associated (yet) with an R packages. We did a first implementation of the 'RUnit' GUI based on these objects, before realizing that it is really not designed for such an use. So, we decide to write a completely different unit testing framework in R: 'svUnit', but we make it test code compatible with 'RUnit' (i.e., the engine and objects used are totally different, but the test code run in 'RUnit' or 'svUnit' is interchangeable). Finally, 'svUnit' is also designed to be integrated in the SciViews GUI on top of Komodo IDE (https://www.activestate.com/products/komodo-ide/), and to approach extreme programming practices with automatic code testing while you write it. A rather simple interface is provided to link and pilot 'svUnit' from any GUI/IDE, and the Komodo IDE implementation could be used as an example to program similar integration panels for other R GUIs. 'svUnit' also formats its report with *creole wiki* syntax. It is directly readable, but it can also be displayed in a much nicer way using any wiki engine compatible with the creole wiki language. It is thus rather easy to write test reports in wiki servers, possibly through nightly automatic process for your code, if you like. This vignette is a guided tour of 'svUnit', showing its features and the various ways you can use it to test your R code. ## Installation The 'svUnit' package is available on CRAN (https://CRAN.R-project.org/package=svUnit), and its latest development version is also available on Github (https://github.com/SciViews/svUnit). You can install it with^[Install the development version with `devtools::install_github("SciViews/svUnit").]: ```{r, eval=FALSE} install.packages("svUnit") ``` This package has no required dependencies other than R >= 1.9.0 and 'utils' ('XML' is also used for some export formats, but it is not required to run the tests). However, if you would like to use its interactive mode in a GUI editor, you must also install Komodo Edit or Komodo IDE and SciViews-K, or use RStudio. Its 'pkgdown' web site is at https://www.sciviews.org/svUnit/. Once the 'svUnit' package is installed, you can check it is working correctly on your machine with the following example code: ```{r} library(svUnit) Square <- function(x) return(x^2) test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(10, Square(3)) # This intentionally fails checkEqualsNumeric(9, SSSquare(3)) # This raises error checkEqualsNumeric(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } clearLog() (runTest(Square)) ``` > Although test unit code is compatible with both 'svUnit' and 'RUnit', do not load both packages in R memory at the same time, or you will badly mix incompatible code! ## Overview of 'svUnit' You ensure that code you write in your R functions does the expected work by defining a battery of tests that will compare the output of your code with reference values. In 'svUnit', the simplest way to define such a battery of tests is by attaching it to functions loaded in R memory^[In fact, you can attach 'svUnit' tests to any kind of R object, not only function. This could be useful to test S3/S4 objects, or even, datasets.]. Of course, you can also define batteries of tests that are independent of any R object, or that check several of them together (so called, *integration tests*). Here is a couple of examples: ```{r} library(svUnit) # Create two R functions that include their own test cases Square <- function(x) return(x^2) test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(c(4, 9), Square(2:3)) checkException(Square("xx")) } Cube <- function(x) return(x^3) test(Cube) <- function() { checkEqualsNumeric(27, Cube(3)) checkEqualsNumeric(c(8, 28), Cube(2:3)) checkException(Cube("xx")) } # Add a separate test case test_Integrate <- svTest(function() { checkTrue(1 < 2, "check1") v <- c(1, 2, 3) # The reference w <- 1:3 # The value to compare to the reference checkEquals(v, w) }) ``` When you run a test in 'svUnit', it logs its results in a centralized logger. The idea is to get a central repository for tests that you can manipulate as you like (print, summarize, convert, search, display in a GUI, etc.). If you want to start new tests, you should first clean this logger by `clearLog()`. At any time, the logger is accessible by `Log()`, and a summary of its content is displayed using `summary(Log()`). So, to run test for your `Square()` function as well as your `test_Integrate` integration test, you simply do the following: ```{r} clearLog() runTest(Square) runTest(test_Integrate) Log() ``` In this report, you see that all your tests succeed. Note that 'svUnit' is making the distinction between a test that **fails** (the code is run correctly, but the result is different from what was expected) and code that raises **error** (it was not possible to run the test because its code is incorrect, or for some other reasons). Note also that the function `checkException()` is designed to explicitly test code that should `stop()` in R^[`checkException()` can also track `warning()` messages with this little trick: first convert all warnings into errors with `owarn <- options(warn = 2)$warn`, run the code that should generate a warning inside `checkException()`, and then restore default warning behavior with `options(warn = owarn)`.]. So, if that test does not raises an exception, it is considered to have failed. This is useful to check that your functions correctly trap wrong arguments, for instance, like in `checkException(Square("xx"))` here above (a character string is provided where a numerical value is expected). Now, let's look what happens if we test the `Cube()` function without clearing the logger: ```{r} runTest(Cube) Log() ``` We note this: 1. When a test succeeds, nothing is printed by default (result is returned invisibly). But when a test fails, or raises errors, the guilty test(s) results are printed. We expected `c(8, 28)` (made intentionally wrong for the sake of the demonstration) from `Cube(2:3)` in `checkEqualsNumeric(c(8, 28), Cube(2:3))`, and (of course), we got `c(8, 27)`. This test shows how 'svUnit' presents test failures. 2. The results of the tests on `Cube()` are added to the previous report. So, it is possible to build rather easily reports that summarize tests on several objects, by adding test results in the logger sequentially. 'svUnit' does this naturally and transparently. Starting a new report is equally simple: just use `clearLog()`... ### Assertions in 'svUnit' The most basic item in a test suite is an **assertion** represented by a `checkXXX()` function in 'svUnit'/'RUnit'. Five such functions are currently defined: * `checkEquals(current, target)` determines if data in `target` is the same as data in `current`. * `checkEqualsNumeric(current, target)` does the same but allows for a better comparison for numbers (variation allowed within a tolerance window). * `checkIdentical(current, target)` checks whether two R objects are strictly identical. * `checkTrue(expr)` only succeed if `expr` is `TRUE`. Note a difference in 'svUnit' and 'RUnit' (at least, in its version 0.4-17): the 'RUnit' function is not vectorized and `expr` must return a single atomic logical value. The corresponding 'svUnit' function also accepts a vector of logical values. In this case, all elements of the vector must be `TRUE` for the test to succeed. When you make sure that `expr` always returns a single logical value (for instance by using `all(expr)`), both functions should be compatible. * `checkException(expr)` verifies that a given code raises an exception (in R, it means that a line of code with `stop()` is executed). * `DEACTIVATED()` makes sure that all tests following this instruction (in a test function, see next paragraph) are deactivated, and inserts a reminder in the logger about the fact that some tests are deactivated in this suite. For all these functions, you have an additional optional argument `msg =` where you can provide a (short) message to print in front of each text in the report. These functions return invisibly: `TRUE` if the test succeeds, or `FALSE` if it fails (code is executed correctly, but does not pass the test), and `NA` if there was an error (the R code of the test was not executed correctly). Moreover, these functions record the results, the context of the test and the timing in a logger (object `svSuiteData` inheriting from `environment`) called `.Log` and located in the user’s workspace. So, executing a series of assertions and getting a report is simply done as (in its simplest form, you can use the various `checkXXX()` functions directly at the command line): ```{r} clearLog() checkEqualsNumeric(1, log(exp(1))) checkException(log("a")) checkTrue(1 == 2) Log() ``` As you can see, the `checkXXX()` functions work hand in hand with the test logger (the `checkXXX()` functions also return the result of the test invisibly, so, you can also assign it to a variable if you like). These function are mainly used for their side-effect of adding an entry to the logger. The last command `Log()` prints the content of the logger. You see how a report is printed, with a first part being a short summary by categories (assertions run at the command line are placed automatically in the `eval` category: there is no better context known for them. Usually, those assertions should be placed in test functions, or in test units, as we will see later in this manual, and the category will reflect this organization). A detailed report on the tests that failed or raised an error is also printed at the end of the report. Of course, the same report is much easier to manipulate from within the graphical tree in the Komodo’s **R Unit** tab, but the simple text report in R has the advantage of being independent from any GUI, and from Komodo. It can also be generated in batch mode. Last, but not least, it uses a general Wiki formatting called creole wiki (http://www.wikicreole.org/wiki/Creole1.0). !['svUnit' test report as it appears when inserted in a wiki page (DokuWiki engine with the creole plugin installed). Note the summary of results at the top left of the page, and the clickable table of contents with detailed entries to easily navigate to the test log you are consulting). Timing of the test is also clearly indicated, since it is a complementary but important information (if a test succeeds, but calculation is way too long, it is good to know it)!](svUnit_wikiReport.png) The figure illustrates the way the same report looks like in DokuWiki with the creole plugin (http://www.wikicreole.org/wiki/DokuWiki) installed. Note the convenient table of content that lists here a clickable list of all tests run. From this point, it is relatively easy to define nightly cron task jobs on a server to run a script that executes these tests and update a wiki page (look at your particular wiki engine documentation to determine how you can access wiki pages on the command line). ### Manipulating the logger data The 'svUnit' package provides a series of functions to manipulate the logger from the command line, in particular, `stats()`,`summary()`, `metadata()` and `ls()`: ```{r} # Clear test exclusion list for running all test suites options(svUnit.excludeList = NULL) # Clear the logger clearLog() # Run all currently defined tests runTest(svSuiteList(), name = "AllTests") # Get some statistics stats(Log())[, 1:3] # A slightly different presentation than with print summary(Log()) # Metadata collected on the machine where tests are run metadata(Log()) # List content of the log ls(Log()) ``` As you can see, `ls()` lists all components recorded in the test suite. Each component is a `svTestData` object inheriting from `data.frame`, and it can be easily accessed through the `$` operator. There are, of course similar methods defined for those `svTestData` objects, like `print()`, `summary()` and `stats()`: ```{r} myTest <- Log()$testCube class(myTest) myTest summary(myTest) stats(myTest) ``` As the logger inherits from `environment`, you can manage individual test data the same way as objects in any other environment. For instance, if you want to delete a particular test data without touching to the rest, you can use: ```{r} ls(Log()) rm(test_R, envir = Log()) ls(Log()) ``` As we will see in the following section, 'svUnit' proposes several means to organize individual assertions in modules: **test functions**, **test units** and **test suites**. This organization is inspired from 'RUnit', but with additional ways of using tests in interactive sessions (for instance, the ability to attach a test to the objects to be tested). ### Test function The first organization level for grouping assertions together is the **test function**. A test function is a function without arguments whose name must start with `test`. It typically contains a series of assertions applied to one object, method, or function to be checked (this is not obligatory, assertions are not restricted to one object, but good practices strongly suggest such a restriction). Here is an example: ```{r} test_function <- function() { checkTrue(1 < 2, "check1") v <- c(1, 2, 3) # The reference w <- 1:3 # The object to compare to the reference checkEqualsNumeric(v, w) } # Turn this function into a test test_function <- as.svTest(test_function) is.svTest(test_function) ``` A test function should be made a special object called `svTest`, so that 'svUnit' can recognize it. This `svTest` object, is allowed to live on its own (for instance, in the user's workspace, or anywhere you like). It can be defined in a R script, be saved in a `.RData` file, etc... Note that this is very different from 'RUnit' where test must always be located in a unit test file on disk). In 'svUnit' (not 'RUnit'), you run such a test simply by using `runTest()`, which returns the results invisibly and add it to the logger: ```{r} clearLog() runTest(test_function) Log() ``` Now, a test function is most likely designed to test an R object. The 'svUnit' package also provides facilities to attach the test function to the object to be tested. Hence, the test cases and the tested object conveniently form a single entity that one can manipulate, copy, save, reload, etc. with all the usual tools in R. This association is simply made using `test(myobj) <-`: ```{r} # A very simple function Square <- function(x) return(x^2) # A test case to associate with the Square() function test(Square) <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } is.test(Square) # Does this object contain tests? ``` One can retrieve the test associated with the object by using: ```{r} test(Square) ``` And of course, running the test associated with an object is as easy as: ```{r} runTest(Square) Log() # Remember we didn't clear the log! ``` Now that you master test functions, you will discover how you can group them in logical **units**, and associate them to R packages. ### Test units An **unit** is a coherent piece of software that can be tested separately from the rest. Typically, a R package is a structured way to compile and distribute such code units in R. Hence, we need a mean to organize tests related to this "unit" conveniently. Since a package can contain several functions, data frames, or other objects, our unit should collect together individual test functions related to each of these objects that compose our package. Also, the test unit should accommodate the well-define organization of a package, and should integrate in the already existing testing features of R, in particular, `R CMD check`. In both 'RUnit', and 'svUnit', one can define such test units, and they are made code compatible between the two implementations. A test unit is a source-able text file that contains one or more test functions, plus possibly `.setUp()` and `.tearDown()` functions (see the online help for further information on these special functions). In 'RUnit', you must write such test unit files from scratch. With 'svUnit', you can "promote" one or several test functions (associated to other objects, or "living" alone as separate `svTest` objects) by using `makeUnit()`. Here is how you promote the test associated with our `Square()` function to a simple test unit containing only one test function: ```{r, eval=FALSE} # Create a test unit on disk and view its content unit <- makeUnit(Square) file.show(unit, delete.file = TRUE) ``` You got the following file whose name must start with `runit`, with an `.R` extension (`runit*.R`), and located by default in the temporary directory of R. Specify another directory with the `dir =` argument of `makeUnit()` for a more permanent record of this test unit file. Note also that `.setUp()` and `.tearDown()` functions are constructed automatically for you. They specify the context of these tests. This context is used, for instance, by the GUI in Komodo Edit/IDE to locate the test function and the code being tested. ``` ## Test unit 'Square' .setUp <- function() { # Specific actions for svUnit: prepare context if ("package:svUnit" %in% search()) { .Log <- Log() # Make sure .Log is created .Log$..Unit <- "/tmp/RtmpBoZnId/runitSquare.R" .Log$..File <- "" .Log$..Obj <- "" .Log$..Tag <- "" .Log$..Msg <- "" rm(..Test, envir = .Log) } } .tearDown <- function() { # Specific actions for svUnit: clean up context if ("package:svUnit" %in% search()) { .Log$..Unit <- "" .Log$..File <- "" .Log$..Obj <- "" .Log$..Tag <- "" .Log$..Msg <- "" rm(..Test, envir = .Log) } } testSquare <- function() { checkEqualsNumeric(9, Square(3)) checkEqualsNumeric(c(1, 4, 9), Square(1:3)) checkException(Square("xx")) } ``` Compatibility of these test unit files between 'RUnit' and 'svUnit' was a major concern in the design of 'svUnit'. Consequently, code specific to 'svUnit' (for managing the context of the tests) is embedded in a `if ("package:svUnit" %in% search())` construct. That way, if 'svUnit' is not loaded in memory, this code is not executed. *Note that you should avoid loading in memory both 'svUnit' and 'RUnit' at the same time! If you do so, you will most likely crash your tests.* You will see further that it is possible to write much more complex test units with the same `makeUnit()` function. But for the moment, let’s discuss a little bit how such test units should be organized in R package. If you intend to associate test units to your R package, you should respect the following conventions: * Name your test units `runit*.R`. * Place them in the `/inst/unitTests` subdirectory of the package sources, or in one of its subdirectories. If you place them in a subdirectory of `/inst/unitTests`, then you define secondary unit tests for (optional) detailed testing of specific item in your package. Always keep in mind that all `runit*.R` files in a directory will be run one after the other. So, if you want to make subgroups you would like to dissociate them, and locate them is separate subdirectories. * When the package will be compiled, all these test units will be located in `/unitTests` in the compiled/installed version of your R package. If you respect these conventions, 'svUnit' knows where package unit tests are located and will be able to find and run them quite easily. See, for instance, the examples in the 'svUnit' package. So, with test units associated to packages, you have a very convenient way to run these tests, including from the Komodo GUI. With just a little bit more coding you can also include these test units in the `R CMD check` process of your packages. You do this by means of examples in a help page (we prefer to use **examples**, instead of `/tests` in the `R CMD check` process, because examples offer a more flexible way to run tests and you can also run them in interactive sessions through the `example()` R function, which is not the case for code located in the `/tests` subdirectory of your package). Here is what you do to associate some or all of your unit tests to the `R CMD check` process (illustrated with the case of the 'svUnit' package itself): * Define a `.Rd` help file in the `/man` subdirectory called `unitTests..Rd` where is the name of your package (or whatever name you prefer). * Fill the `.Rd` file, making sure that you define an alias as `unitTests.`. Also place a little bit of information telling how users can run your test in an interactive session. * The important part of this file is, of course, the `\examples{}` section. You must first clear the log, then run each test, and then, call the `errorLog()` function. That function looks if one or more tests failed or raised an error. In this case, it stops execution of the example and causes a dump of the test log in the `R CMD check` process. That way, providing that you have the 'svUnit' package installed in the machine where you run `R CMD check`, your test units will be included nicely in the checking process of your packages, that is, they will run silently each time you check your package if no error occurs, but will produce a detailed report in case of problems. * Here is how your `.Rd` file should looks like (example of the 'svUnit' package): ``` \name{unitTests.svUnit} \alias{unitTests.svUnit} \title{ Unit tests for the package 'svUnit' } \description{ Performs unit tests defined in this package by running \code{example(unitTests.svUnit)}. Tests are in \code{runit*.R} files Located in the ’/unitTests’ subdirectory or one of its subdirectories (’/inst/unitTests’ and subdirectories in package sources). } \author{Philippe Grosjean} \keyword{utilities} \examples{ if (require(svUnit)) { clearLog() runTest(svSuite("package:svUnit"), "svUnit") \donttest{ # Tests to run with example() but not with R CMD check runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass") } \dontrun{ # Tests to present in ?unitTests.svUnit but not run automatically # Run all currently loaded packages test cases and test suites runTest(svSuiteList(), "AllTests") } \dontshow{ # Put here test units you want to run during R CMD check but # don't want to show or run with example(unitTests.svUnit) } # Check errors at the end (needed to interrupt R CMD check) errorLog() } } ``` Alternatively, if you use 'roxygen2' to build your documentation, you can add a `unitTests..R` file in the `/R` subdirectory containing ROxygen code to generate the adequate documentation. Here is the `unitTests.svUnit.R` file: ```r #' Unit tests for the package 'svUnit' #' #' Performs unit tests defined in this package by running #' `example(unitTests.svUnit)`. Tests are in `runit*.R` files located in the #' '/unitTests' subdirectory or one of its subdirectories ('/inst/unitTests' and #' subdirectories in package sources). #' #' @name unitTests.svUnit #' @author Philippe Grosjean #' @keywords utilities #' @examples #' if (require(svUnit)) { #' clearLog() #' runTest(svSuite("package:svUnit"), "svUnit") #' \donttest{ #' # Tests to run with example() but not with R CMD check #' runTest(svSuite("package:svUnit (VirtualClass)"), "VirtualClass") #' } #' \dontrun{ #' # Tests to present in ?unitTests.svUnit but not run automatically #' # Run all currently loaded packages test cases and test suites #' runTest(svSuiteList(), "AllTests") #' } #' \dontshow{ #' # Put here test units you want to run during R CMD check but #' # don't want to show or run with example(unitTests.svUnit) #' } #' # Check errors at the end (needed to interrupt R CMD check) #' errorLog() #' } NULL ``` Note, however, that if the package 'svUnit' is not available on the computer where you run `R CMD check`, your tests are silently ignored (`require()` issues a warning, but that does not prevent the checking process to continue). This is an intended feature in order to allow compilation of your package without requiring 'svUnit'. Hence, dependence to 'svUnit' is less strict and also allows you to check your tests using 'RUnit' (but you have to write a dedicated function for that). Still to keep such a less strict dependence on 'svUnit', you should add `svUnit` in the `Suggests:` field in the `DESCRIPTION` file of your package, not in `Depends:` or `Imports:` fields (except if you use 'svUnit' for other purposes that testing your package using the mechanism discussed here, of course). Also, this approach, associated with examples, provides a very convenient and easy way to test a package from the command line in an interactive session by running: ```{r} example(unitTests.svUnit) ``` In the present case, the `errorLog()` instruction in the examples returns nothing, because all tests succeed. If there is an error somewhere, you will see it printed at the end of this example. ### Test suites: collections of test functions and units The highest level of organization of your tests is the **test suite**. A test suite is an unordered collection of test functions and test units. You can select test units associated with R package in a very convenient way: just specify `package:myPkg` and all test units in the `/unitTests` subdirectory of the package myPkg will be included ('svUnit' does all the required work to map these to actual directories where the test unit files are located). Also, if you specify `package:myPkg(subgroup)`, you will include the test units defined in `/unitTests/subgroup` in the package myPkg. Of course, you will be able to also add test units defined in custom directories, outside of R packages (for instance for integration of *harness tests* that check cross-packages, or multi-packages features of your application). Test functions associated to your test suite receive a special treatment. Unlike `runTest()` applied to a single test function, or to an object that has an associated test function, these tests are not run from the version loaded in memory. Instead, they are first collected together in a test unit file on disk (located in the R temporary directory, by default), and run from there. Hence, building a more complex test unit file by collecting together several test functions is just a question of constructing a test suite, and then, applying the `makeUnit()` function to this `svSuite` object. Before we apply all this, you should also know the existence of one more function: `svSuiteList()`. This function lists all test units and test functions available in your system at a given time. So, you don’t need to manually create lists of components. You are better to list them automatically. Of course, this function has a lot of arguments for listing only test units in packages, only test functions, specifying where (in which environment) the test functions are located, adding custom directories where to look for test units, etc. See the online help of this function for the description of all these arguments. One argument is particularly important: `excludeList =`. This argument defines one or several regular expressions that are used as filters to hide items from the list. This is required, since you will certainly not want to run again and again, let’s say, the example tests associated with the 'svUnit' package ('svUnit' must be loaded in memory to run the tests, so its tests examples will always be listed by `svSuiteList()`, ... unless you define an adequate filter expression that will exclude them from your list)! As the default argument suggests it, the regular expression for list exclusion could also be recorded in `options(svUnit.excludeList = ...)`. Here is how it works: ```{r} # Reset default exclusion list options(svUnit.excludeList = c("package:sv", "package:RUnit")) # List all currently available tests svSuiteList() ``` Thus, every entry matching the regular expressions `package:sv` and `package:RUnit` are excluded from the list. The entries `package:svUnit` and `package:svUnit (VirtualClass)` match first pattern and are thus excluded. Now, let's clear the exclusion list to see what happens: ```{r} # Clear exclusion list options(svUnit.excludeList = NULL) svSuiteList() ``` The test units associated with the package 'svUnit' are now listed. You have noticed that `svSuiteList()` can also find automatically `svTest` objects, as well as tests attached to objects in the user's workspace. You can create a suite by collecting all these items together very easily: ```{r} (mySuite <- svSuiteList()) ``` Now let’s make a test unit using tests collected in this suite: ```{r, eval=FALSE} myUnit <- makeUnit(mySuite, name = "ExampleTests") file.show(myUnit, delete.file = TRUE) ``` This produces a file named `runitExampleTests.R` located (by default) in the R temporary directory, and which collects together all tests in the user's workspace (either as `svTest` objects, or as tests attached to other objects), plus tests suites in packages that are **not** in the exclusion list. Running all tests in your suite is very simple. You still use `runTest()` as usual, but this time, you apply it to your `svSuite` object: ```{r} clearLog() runTest(mySuite) summary(Log()) ``` There are many other tools to manipulate `svSuite` objects in the 'svUnit' package, including functions to define the content of the suite manually (see online help). ### Using 'svUnit' with RStudio Except by integrating your tests in a packages as explained above in this document, nothing is required to make your tests run in RStudio. The `Build -> Test Package` menu entry should run your tests with 'svUnit' once this integration is done. ### Using 'svUnit' with SciViews Komodo If you use the SciViews Komodo GUI, you can integrate 'svUnit' tests in this IDE and display reports in a convenient hierarchical tree presentation. If R is started from within Komodo Edit or IDE (with the SciViews-K and SciViews-K Unit plugins installed), then, loading the 'svUnit' package in R automatically installs the **R Unit** side panel in Komodo at right. Its use should be straightforward: ![Komodo Edit with SciViews-K and SciViews-K Unit after running tests. At right: the **R Unit** panel that display (at the top) the list of available tests, and test units where you can select the ones to run, and at the bottom, a tree with the results from last tests run. The stripe at the very top is green if all tests succeed, and red (as here), if at least one tests failed or raised an error.](svUnit_KomodoRUnit.png) * Select the tests you want to run in the top part, * Click the `Run` button each time you want to refresh the test tree, * Browse the tree for failures or errors (the color at the top of the panel immediately indicates if there is a problem somewhere: green -> everything is fine, red -> there is a problem). * If you have failures or errors, move the mouse on top of the corresponding item in the tree, and you got more information displayed in a tooltip, * Click on an item to open the test unit at that place in a buffer in Komodo. The `Auto` mode, when activated, sources R files currently edited in Komodo whenever you save them, and then, refreshes the test report tree. This mode allows you to run automatically your tests in the background while you edit your R code! > If you want to implement such a side panel in another GUI, make sure to look at the `koUnit\_XXX()` functions in the 'svUnit' package. These functions allow to control the GUI in Komodo remotely from within R, and similar functions should not be too difficult to implement for other GUIs. ## References * Grosjean, Ph., 2003. SciViews: an object-oriented abstraction layer to design GUIs on top of various calculation kernels [online: http://www.ci.tuwien.ac.at/Conferences/DSC-2003/] * IEEE Standards Boards, 1993. IEEE standard for software unit testing. ANSI/IEEE Std 1008-1987. 24 pp. * Ihaka R. & R. Gentleman, 1996. R: a language for data analysis and graphics. *J. Comput. Graphic. Stat.*, 5:299-314. * Jeffries, R., 2006. Extreme programming, web site at: https://ronjeffries.com/xprog/what-is-extreme-programming/. * König, T., K. Jünemann & M. Burger, 2007. RUnit – A unit test framework for R. Vignette of the package RUnit available on CRAN. 11 pp. * R Core Team, 2019. R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. [online: https://www.R-project.org] r-cran-svunit-1.0.6/vignettes/svUnit_KomodoRUnit.png000066400000000000000000006334271412072605000225420ustar00rootroot00000000000000PNG  IHDRiriCCPICC ProfilexZ{\W& PkBt<_UURD@JiEeER*ZEk>]J-uKQ[Vʪ>N@Bpws'}; 0 dp u^1fxr+Z@9nGSƾ<6m;ۃ ؞Ǟ>1)=Y`0f w5EY/b&C3gpǛ|=$̢sfbz9eWe(c ىIF ; C t(?jJbJ  $ݞvSwHzM:eT\F\B4t蹳3?/Pbٽd[~p8֎}tc߸_cf:8i\ nH<bzQ?Djkg wtrV\}Sz0w?@6H_ 94*zذ#Fzi1c_֏;O&L 2eΘ4sV씔9sS祥323dzNo.]xI^ޟ/[⭕V~5W~au߰x7o)ٺɮ{~oϏ|Qѿ~UUǎۓNW}s5;bm._rZ}~hqfccS-b1 {̀[O` H~" T\g 7g WC7!g넚w~{^K6ޡ3|3 i$A$ AL$p)!@R -i 5L$58FH F:F A9j`b -H UHNW9WsBW7h|{-?/oiƄۘFGGRFS74{B *ZUj^x5e~̭=&=KCy絁NiZ>Djx΢9SgV )We8 Y_X)XJZ-oMǀ(JpYz)<[+V=pXc4OG.r)8lkQRg!Yպp pSpz/4 ^#9\ޘIObΨ9;@ ?qP X1PP vwY!tCb "&ke^ 1+WA7 ePj5Fry/<Y*-<(uՂ\ c)Gl_bc0pO3Fy@-G- <s^1u 16&4`)CC('i:V{]L1Fp^pTbl!}oC[V #l[C}Fa#BJmpL,ޑWѯZ[T?4J7iEL荺l,>#k1 b))QH9sH^b?P*.`z-.ep5{ jpOLuƾ4'CԜ?ycr_ L"qދ*W<ڗМ?xL8&LB:y0%Bx,ASNa|zWsuW5hj}^ևTgsU'W";@B~ ѱc5/ IDATx}`=I6aoA:Pg(V;Uk]J։[A"2doYa7|&ܛݐ` os9_깲Γ8ɾ^<丹+"("("4OWm"󥯇Zyyyݢ~!gjׯ+"("("4$nc{mڴIц>|kdd* GD0SE@PE@PEi ApD򋕕LIId4N&9\e2+SE@PE@PE@hzȻ*e3͑`;wbڵؾ};Gyر#zA[n^(NI*"("("`V,Va!iı!pB_ԩL8`?nZpȴ!(@+B`_`W5&bE\(wcH1#(Jľ[5)EIP:>NhE@P Qx#N3#hK,Aff&뇶mBv 1 ѹsglڴ N=Ԡ A;d͈zPE" g4M恼-ᛍq ?fTeؾ={ O}t|Li0d!?=zkgCwlߋ=>anWFuB  `d*.1ǵADPEF|o5VA5/ss^h9ƢCG1¬A[G+ ggNQܬg-sݻ?q dhs˱fk׮~Ƛ(2[E@P sx̉܉%}0i(ۄ$g-xE:ٖ4<0 9+^. 22υ_Ļg߈:yC箻2x:%y%\:9/Cǡc۸oF\{0m}$"( yܹse=z4ȃ;š,k;߿˖-,5Ô)Sjfajwʅ%#Ϲ&!?s} q}ZQQmۆHډ"ʣE@PF]]a2]z{?Zo='80w%H1]pϧㄛk=(xTf@?"$,]7oƘ1c~qqq>` &{ }("Pt@nw5?8ҫ`3bezlb<r!&wL\66(>; /LH!gQ#NG]OO]oIGb|%d\2=bI:Iso US&ݒWi1\WeOf"iSls7?ndz("pB#addd}9HhAh4\fJHM7qJs ᮓj8 %!A1m*˫c"qXsq=x屷16s56>mc|jVqv-gN;-QzTE@8I8x zixt$jYwĎ*Uj9يaFԒ}FMM!*!'`sE@P/Y&vK>xKXw셔>"xxx}fr%H #\o"[Lk]vӆ\qfX0Y, 38 C)\:e0Iii^ i9;::("p"@%`q!c?YHȗȓ}v;')(R:U+A4m䏎994N A)"4+n€#:|W۱0sQw YlsoNc( B& YdgB0vtr^ܩSE@h=PKHH,gtD<eJtMUrd}t9#sH#$2 ȝ(2_+"E>΍Wd8QVxeH-3|s;N\ NJ[Ƽ>Fb֍+NZ% [*&< 1$UXąiaHU[7 d.w;xupީH>5s݂3h ByLMR5QEG;$X\[F.D"fq"z#ںuЧOe~$P`҄9~R1vA,@D(z_PE.%6aT6"y |yK|X:㋰Y>wU^1)a7&C Fخ%xoJud׍ߺ^Vma|sot&uM/ptzTl.>Exq/b%Sko㰉HCbL{=|yz: qVb("p sVXavk˽HJJJСC-Z|_z.@aWEvZ>Y7]RkƭAk͚q։"3P"(@h* [LдOnTU <*OblkƼ@LTUӡ>E@PZ,Ԅbpʕ̷3:*r6,߹sgÆ 3|X-AIޒ ILLD,f‹n31܉"3X>"("("qD*ʹŋHG0FyaG(_1P{FGnRs=?!kkA]b<:QdI^("("(@E> =Wk$c$dtd%ZL#. ƴń8hv,Z ]^ߏJʫę/{8پ *8~-sÏ]p}7C|B#ݥشS4.WVbldpV[^8\R +PZe;FxAJs1%]-%R'$E{F9V|NVwV-UpGIr_CjD9!c)nb'#ڇoJ8~M"Ъ8xS9wъ4_ Lf^jWeۅG lA{SݹL/Ģo(BI SF}zHV\%D 7h2-*Q)!.Qw&1.ɳ$ֹԎ+,g Bn[ BK.Bu>.{6bgi!S;FIRxߍD!gy$ϱbX+8ٳlEM#k&'c])4WT‰TVK2sMKsՈHs86999(((0 ktt4-ۍ}$#q-6ALyME)1U14]/~ { /?1U,\}[U#w|τ6W4"?68eddէO>|0JKK(kf8o7|߿Q߿3ڸpMZXL gJmLk]> Np ) 8K9"ua jkq ,-靈'F ثEV&j!UaHZEyFIb9@yx>zZ$F\HCbL4\G-xnr []2/ GjTJ2{4FLĆH>\ EKK.G/O7Ĉi nd2I %MlLj$(e<1}dbh2*9OWS$_&QP 8)͉{K3Ս %w2H ~Ԕg|m߾2Y1lذF~5'}W(>%ϺϿްIh "?n`W6n <_U3j UhK|DI t)Cjj b":;gYV^b>xd&tAIYY`6--͐2m||<Wsɷ |K~sOPw9`k ؍P0$9t!^G)*G^ XbJw۾?9gAa⏃+6V޶m[3Ӿn: 8nG68yԚ?\n1.W< O^;'݉xסЃvI9:`| D6뱝4,[xݻ)fKVRR/'NDDD#U!" x.4mJ<(m| g1!ߎ4Da2@`hHFmLBn2}E b q 3V(r@-Z 9cg[dEo؜ Z=u1LTE޾۵AbBVl >h_EҎ"m o%g_=6W,5˱B4i Zi!hR5RXԌQ3wWt7Bo]p,)ZJLw3-]&%'lϡCh- !=aW!V\i yil|y}ůץvGݫHď] !wݰŀkVO@34rc׮]֭!8fO`:uT̀\2k׮9L/X2oM=Ȝ\vFcA}z!!WXwځC.*‰xҽ:uH?2r"{սr%(g @TIGjh?krVeR19pW : c8{`>]7A*om!z7>I Ռ~"YRESRy%|@xW8wp^&d\$#gߕEn)i}BiF J#Se㪄UGׁpWK/W٤ݾ7iߕ loP0̔!ebfMHbEq+{[6u80cg&cEgc~8|ƍX~+ MJb.1#LpP6Э^h|⾲s+M(,#<2i>WAʧz֑rJsz j6tR/X3ٗY6추uěz2_A+μr҂M&Ԡ݈ݰ3ndiLvtu1 :/]d ,Nedx :#5iԮ1V2eSGԠY-PjI7cLda& C+=||-Mz_,+ES"t7 bHuνu\~gcsj_͗ԛ؋bQvNq@V>2 %IaI+ĐawL=[PM-.xdYP•6,Dջ4Yߏ WeB64xd@p~Bʛ9_eOr޲4i i%1zayy 2pDzBRA͐L}[lAݱi&pݻwcÆ FfS~sx  7};z5֭ꐉ(L9`$xufr 웃9bBa IDATzs-Gbjy[>6K5]ɝ!& ұ QBkr64yb#Q "i\S: dޫS &*T;r.sN~#L0V2y5- Eށv!,XSwBf$lWQb6Xg0cv$gę%qe#JӖkbСp޶n ;ËǕkRڜΩ5vUJYa^r,KcW~ MyxJe)?^%c.37]5BC l͚52x&`AV9f[yI9dv4$$N4D4qۖ]C3Nƭk{7blċ5BΝ}Oh|>R&~j}衿2:s cK:gBsJE>7 AQk{'I\@k\`$8>ܤ?B )=0.3 9yU8cLGp$ ^Sn0߳hkfɵZߤpȄmL;dـѦ)9J&a' +|X"*y9S"fW?/߶RCp!e2 K&Ѱٳ*ɺ_~xs+I>~(9CIf >UhH hHfiк&;1P6l)(InyQO.=p܉]cKzٗxBjhIʶ}186EOt|_p<1bD?^cQ8nn0g8)GNT$'"+' [voEgl^$Y-͋q)ռ_{1|pVuԨQկ~k{űmxY:Zg\/b/$'Nud`>9(̚R9)_Ah$;|ӟdR&5_+T§D6Ccr⼍2&8&$sL")3.eWa16gYr ;A[`A)#dWLo`.p5adL^bX!!&EbY;^!lj[+wrٷgr=^RFrFLnCֶf} Qٻʮ`ʀ0ڗ \DZ"|j+m ۫#_cm9D ;DMh'v8(TD`uԠ!Aѣi#A#ArO4w;w Fל}*oۏnXd݈/BvfL[(R}69ƨqSc̙m[V[v}/g"vqx[W xS. $/9!>}n2?C˨++Pֱ@G4Rv7튡ifŢ[b{,OYe ?!pY)yOh$ "8jX8xb]\{SnMxpS0HH;tKigL=m/d(9dtx f zv);ҕ`iЍcY%`EHά{NQ8dĬc$]w] us}4kyn]8-7jCb! G.gǟUF\E욀2)|u/Tx4e {x,b$(2!Nikdyۏrdl!pt,MA uƖ1FuP{ .TkN>[oJ T$>ڈ%Ya#UgTbdbdv‚8mgm]0y)ܕVfP}%CΌ)Ubm @k-)BpL̂;#] W܅ k޽G+]O[_MN=ԺIXc$*_g4K>'hi~IͱZ~W2JkwV:| PGf~IX1TV̄ɤ~غH q\ }}W%1f)DF W|id+m)W#bh?$ '8Λ Z9YUhǑIdA!EfEw%)8[&;YB7;p̓YL1!MhmUXq;nO4qgG@N<¨n] +0Ym89zd ݜU흦o# ^Xu^= T?ɼMe:Æ3m4Ύ$,vӦM3q^ao(yiG) Ctl˾O.TW »K&*<22)۱8RIcOX`Hk,bF&5v;"SC3Y}dM +q?_f4(DyXh)F,|,=z8,]}g9|6X-iVƕG̑K-&D7Q$,\d#"g1\4u7Ub~uw㗿yu}b\VڣkղD>m/yȂ'Y?J9˳(Rumx#3X&JywB*J0k(ntPKhѦ@L7M9k.ӟ^lv7UlkĬ`eBjicK4Wf^W(Serky Tf#xSk'np>Čyȧ hZI @L2*vs,~AJZ;ؿWK)"eRl'^qLnI, efnhj[ČOc[6Ʒ;6|+8]b.VTt Y>jJ< 7Ψ1\rY83G `Kj*VsΜ@)mWEIf̘KQUW`]TUyr.ɹ!jS-y xwyobY(ࣦ0p8P1J; 4XL[.؈9Y2#&Iƙj@94$ގ%`^!{ 􅫬)?rF n.,[c>zDEa]ffe'4]%~8&M:yB6P0dZ_^űfRJWe9uxC(?ALB(=Κ[vȌKltvO"2N4$i4# \vlVM61V7x''';3RX$IcAg+K qƒoK1 M2KV̰DI|9#d|Z8_{-[_<|U(dK&dYHKЌH"Flk7"~lLZp6_t(keъ3ر{MB򿢇g :FfSde V 69TM]d6}ؿmLye0Xٟ^ֱ&3^ })Oy{d<7ô_}6+ӤS1QC_6w4Xc<[D\[2㏊# VO363^B_OAɺ7#h΄Q{V-DhHм$ͬMnAR}|۬RH܎䶡I[qP \?}Pf̤:ՕfL L4PdĤfS36Ks7"}w))Gũp]e8v^;NƷ:dѠ'A1;Ӈȹ!Rjԩt qX> { ;-&iWzhʼ2qHz' <@Lr!kɾϵ=Z$lN0n߾݈7?Hh X?97C{=LKXCo{ )eT,kՆmqxN\`b{r~D&q\HzYַT[+5,`s ]W_>4G:ALĚ.R'&G^65g O{jS9wLpn\*)\\cg8 kʖֆ И2FM;Nb S`oc^WR+r{j>xzXahѢ"°`E'T7d(w ll,z4UbIHҘ"Q ͽ_SIwm9gތ_'fˋg<&! ~FSVlǗp:bz@po YqN::?'_- vߗ*,vwA:i>u~B}<іPnM͡I|B@±x7G1 x? q\{vEwP]CAe{/cV_hwlE> iRϸN$jo q*\9۶mnö֟qcohj5: NHd<#9ќF3GѬYyi6cIm7/[1c__`_-_ynF/oGf0oE|҉KƙSi)N̻BwlpebwUbdaxoe a869$f_򝴕_!^*LW)|=AJ3ٔQ$C2uidwYr#⹖(K=IRyef15|߸6oDNÐG1~'ͻ |Q]NL&HQ9' V4i«jf7|ޠ\nLǙ}6ޙ%ׯXINq=? MsFuƻ D̰80=6Lf}Qܭ;CqƋ)ԬqPKИ^@$8B%S)oNq2{:#W=nFdu5m9[.n;¯knf[#S^*0KS)qN=f?s.s#?YY{czezXAJKb>BWYK7> # eR{"?N8qnMrG߿l&čOiKg z#Ztxٵ{dhl?%e icA|dmð/Worp)mqv5iD}j+e |Fl50K֤aY-ZJR|&\|ew)IlLNfu>&$HK6 ;n5hklD/ַ36fZW]j62?:[ѫܣFڤrx=Y/k̎EULm%SnAcf!\>4K͝[dYhŠ" 2= _[WJssVD"kv~앉 IDc;.qh-SX p/_ I3[5C {95FU%î-+eqUQ&r'H/#3,@VwxalHCv4TsO7˕ޓ4@H7̘m& ng -/d%1cr-wv,)iJ⎷Ve.J @Z*e2?4״L̘f9gHYe娼f'{iJgwW>5joS3fJb^ IDATn6kݢc 8jږ/4&Vi*=3ro'P@im)B$#PRl|0Od[>HOqk,kK,#?7 s(;rOq%TͲo1r0f\ўqn qmۗy|΂O{h21풏g-;1wgפI<+`醽xB>Ib26 YݐR'E̳ӑ[FGcZܲqȢƍn,yz>}\aCe)? ײi%BЪdφP>\Q}1omV;µ{hR,@A()L"S5J.հaLʏ1sӈ[g I](fjBo֦c[,>V|ݧ6X\̾1L$]Dv,,Ti9X8I$vVx8|.L6t/:G g?Yد|V:|4h8 .j$uᢻgnE}@՞B#PlrD:˖xikҳhK eiِ2l(4g>2:pѲ6w\MrĎ5̖h;fl 㽅qۿa;VL$)#pq#3-b!} R3L. !ipzemJ1nYi:Ϭ];排j"ѳ]/c8i1`L gřs,sJG.j/3 F,rf{gmeHd!)];p֨ ֍2Y5Ԡa__!$$H yf-|$sM=ѥ{`NTwAq]xߥC!+aSs%12?(ً_zFQGX RaEњs^b8{!=`o^/fҎy 6[>f}-Ql͚SKB /يܭ':o+؈vCEڹWfJ7ȳ>HIÄ8q7|c6če:7PSOX;,z:wq>KwD|w :~%Lj-GMYm֏mu:~ oN;Sǻ+C̪9}QW\ ~Z-A)n7iR!jB792ήҀM㌦ZigHbhc+c=5;RY=ι?^*ڔ_~|G0'_(m )q\(H7B3v٧ҼO>Ȥl/T 9;ɣw=;7iŋ͎4)fN*閭c38#(ݮ=JW~woDEaJָO$7ɥr|Xm1foNe,.:iї$ڴ܋yHxb_RV]W}R(ߌwP` [F({õi1oܨ٠qai61 }6"XRa %cw)Ef V )k@+dd==1M#RTD|(yJwX!kl {[);8[y-ZPM7a gȹo{5r֛$V%E,B!Cb w̄_Mu6yNfƎƼd#+ HxDTtJWGC$C:kFSZÚII/ϑO1MHn_7W5ڡ z,D6U 2ڸvρ}ؼqJ r^V#&1m:}qAT! `[vs$:w;v1(+O>c!5t|ozG\a0Uv5lSŵOrfKl529-DI/d](0r/EnHف Vk&BzvƙR1{uckګg>ڍ=ظ%-%nZ ߣ0x?6ʁM6A!JkHll`pã QGH Ñ{H~y|JV;fR39"dp֐c+ZPE@PkGPLmI1ٵOQjրakcoY*"("p!PKNiE@81DZŸ,GSjl4"("R63 5}&-TԿ"("("(͈&MԡNPE@PE@PEhjפ͝;ҢrE@8&Zj{RuL`NǚCE!`I ={f*"("("CZX49"("("^;ikE@PE@PE"TkV4I"("("jh;u]HLLDddd}qE#PYYB{ュ mZnmz%@@S'vl$mذa֭bccE$C;wl\ܙgoy#F`ƌͭ}h}x"T\|k"2hnZ%IĠAd'('M6Q3GYw`Ĉ1c`xrCخz!jP jdY MUϵ4>TZM՞U4?E@P!%h ќG\6kfͺ0Q\PE@PN`v9Ǥ{~nQPVf&8nAUE!?0r?h\4 Ҩj E@P1omg5lf;wȑ#''YQ_+w >8s`D׊"("ФG}Dd^羽Ӡ]].^9w"{սrK׸D9'fNn?ߌo6{inۿ(@ Ekό2:CxGؚWFfQ7?k0 Mb;uGAO}^ lQ,кXӨ:պ@*@(%us/<*X~gcsP]]m~԰=ns~koJP-^ܕ'+GpDw<F?$č-GŅa!iuOB$Ks1u8TY;&[ӣ"( "caƍL0W\qE'3΢ncx F.Y !4 +}GoTj)Xh:Eh&)4k5[wŮ]еkW|mzTU A%Pv3䮺#(X= ?*l[>4ޏ>?{=F9N쿘޶-;ĸG5ޛ5a>[>fք՞zP(YKNN>kꛖg̪Yƣw#{1& r:E <+bDwSf~GȄ*%{_w2 y:̍6l[4mZg?ϑgwˁ #f ށ#"~XtiFÇo ˢ3hq,n? < y4*>q܏)2hSE.ow} JN- O܀Q M:EAYyuN3(G!I-IBrDE5i֥cp&"}ihxzr! =r*LB$0mh;0R[ ɨ+^EEp+C=dr?qg9^ D26bw}n$bI!4y$I 81uִ0طe>jR.Pij4uX"p |?}׿vni"Mos ɈHSj{wȭV.]30P&p9 5ht>!f5$fn$g 3OcՑvB>lŻO뚴Ӈ'$ǻ iEMٞ!iv~0RوaWz\ZZGc(-хDZS"0cRTbX pZ9{&NYhJ0k>Ku BD-GWYEU pyPrGtkYK3k.vȪi{~1.ܾz5QZ2MQ}ɷZ״)M@S'LPȚ4 s$Mԯ,>ydF8BJIrFY%<1Q <.٪?^51r!u>fMӟ}O=bِ$42}eX3.8U Ƌ9orBv\mx=Q lxW7k} 1{Fh.UәoJIAPmZ`~Պrީl鷽77'Ƀ1suLs(8Fl-I&Mq\h@A5iQ}c'Pg v/$|9zEٶ\|rduTV.^Z݇5W|NPㆀeI.;HܲAHP2vwEk,PE@PE ~yxG%>^ͣuͣu0Dv$-v%6Cђ5o4*]PP )w"h2bF(ԯ"("$۷oGYhN\q=z yD:E@PZ999G%)l$7طQA"("4DB^&if&T߷YRU"rGZnj-9}6E%"㆖V4Mɉ@$iرcOηRpHixoSs7i"T]4d}SE@PE@PE@xcs5zE@PE@PE@PZJZ*"("(@KG9?-=>E@PE@PE@PNrrssuMI5{"("("p |OjJE@PE@P"p->9{l+=Uقԩ--mE@PE@PfB@~UC\.WS1"("(vtv}_x֎Q~7qEv\,S]@@ux E@P콏2ܾ Is:rTUU5w'W_}W^y%N >裨_[z5}"(@ӱ_H1.~'xq)"("("kV9rC@ 4"(!"`߽W'ZZKTMZx E@PE@PE@P ~פI}[eK82}AM"(@0u,0'6Hoe,FI㕎OK.'ظqc,1$Td} mt) @dz Cz"ߠ'8' 9cT** TŕݣooU1\t|PS'.?XcSE@8XjR_lܫwyd"#Q%ŕmpe˭xn>:V<sz@# 4c %q>ޏ/=/u}ZK'}uH%pQU_}UP1pE-MAM[7L_JZ.ثos+{7ŷ\KrM4sATE`;sngys̝s.3M^EkqX>N+ҔթC=V™cUb¹ о IDATy]4!pl_ޚ3 DHMt(‹]Tk(.{w8,[ŤchMlt D]Qn<ƿ? z6@}_\HGےbع5@z_DTBRL5 'ڨJUOG# ЦM07[U7,߮];AVE浉ZX\9jb>K哊SK*MC^Lz Y!8BC^^@_(+Il}CECHL\;hb}8aq6 kf|z/<6yZZg>xhG-؅ẖjps/C{^aY%O-dq jx*dxO+Loa}u\٨]wz5DkpaZ,_f#+F4v'4&FYK/k⋆=u -A,a ! N.Q۶4&>'cP7[wx[ob4Z/,:Z?/q9EU=כf@CX8r^`:RRJW"')G#pydY"XIQ~JvbxQ(3ofanZ^Fy]|"(0(Y>4\Р0 ~=;twMJ4})z5@./uch 3$3K5h~8MXG ':7 w!-/[PAªvb>]5xiG#x`5Msڷo/LR86ItqdG1<%I哊SK*g ) VhŶf*(g[J'As ƓC kȗ3H7晵s oӈ>_@ p >C^13>BfT< FʜKd-kxR $#h[D@ kI˿v {%#WLP2DžMu@q֙4(4P"-"Ҍ(ʼn 1ѨK Jti_bظtf]ЄIr)kE Rr!s-K]q |eYi". J·S0f(Blm|ogDՑ3)Yt{xac1xrz*8G#P)*̐Urq&Ex#_u!0dRm=[8TT{9i;#&[@{-AOZ8yb6@dޔ"qa vȨƄBRq|| 0%B;JVvW*Y)fJCR*Δ0!zR|m9lt<(A2w= aScXg _ϞHe\ګhz{|6{,\ꃬZNF`4'M+mT_ t:_}zaoc|cp?(Oɔ-|%j9lȜm6O>M?׾5dn3n?o9K Cxmh| ZVI4d;d?8m#:]~DG>Lo9QB:rY=zi+T_>('r8@8ɼV1++S9h50()xòR2 s|Rqb~s(4xsn$V^)Flo“^L/o&b ^:-xM]"^ ًꀗEDW:32i9>ЖV^@C1*,:co1bKᄡoxҡ8̜%>OnX ky敼 z6mڄ#GVaCVe&drvs W|[g !C.T]7 b)ci($դsiJSg"ERʦZZ r޽@12WYu*SGe(` '͒hrYG#p8G*y\G#p8D ++ ^^^B-[(((έ{{{ GF۱cGY[wE<;GHZ+p8G#(sWlذ!7n F |ᚑ3=QIڽ{@J[م5VfZ 2Y9HI0Q?%EIˀDjKV /4&11B 6T&$ailN{o)gxmL 1V]6q(.S2>IU#eKasPWyg+$ơ ۟R2<3GaG.F؇2777ԭ[jh̒3r,ɝ?p8FHvƼ˱|=>򜁔 Xm":z.ZUK= S`) dZwV.7p  U[ebZTLe5,:ԃGYv뢬SۮT%pܯpWJ 2z/C sjJinEܹH6 ig/fR8pdUO ?åߟꩍK̵0kFYc"AcqN$ie)ّÎ@ a p]W4!ܴ*u'߀&QhIBԾ\J~&@DEʙ,9!Wz#-g =hڠIǁ6BiB;ݵ|Ky%7FJZLOB7Tɬ465w6>ڷfURLgPh. AQ xLK1BX) D][_uH29 F?"41M$m,}X(..ż$v\ u<$T&}NNNye˒1Fܘhz0p@4jHV&OqtbvӆEH25W]cN<3. MS7`ŶB, cp=NcꝸI/!i=_ԸzF{]̠e6.'/SkvQ/ЍEq.&eitc"6vcD2><^ mT/GNAfHU3QQI'dU LIRIכ xĈ0]%87qؖ@! =0j\ۅl}07^;̋غq$d?S[>S ý? OwL]7^g' }°2!³g Ǯ}h8L_o荔j5c~R~NO ӗ@j벵y#}[y%Osn^8wR"95#ǎó>+2)VL Woblw+U"}I󪞰eQI&#j, ɭRxIi`v)o[}c?uZ1SZJAqqZsbl& 2OaC=& aVL_5;;"'[ 8ftD!;>T 0}NGkHO4e> u*$vٯAXDVO[M!,ŜD(Z hrئѳptѸ5;Uq&IQUi>:r4~[]ut;J2mPSlKce,&o3,G1oKb ࿓AUNL,>"HЁ8z>]1nj$I%:zVmpoM"'r3%iη4bL'MƲ9C[A}c?"笃oa \cYp\IY= Jܭ1 f4FڎXw-*<4y7v6=2Dcoplf~Zu=+qZ<4R݊;Ϟy6~3RSʷ!*3AS:AeЋK$|X@}%PsI<#PeD]p+VZn- .~ʘ OZ6J@ҝ3Hs2VPUqNb.CDwMUaV:[TPZX۷ѹsg4Vcȑ#B~!ΦSfQs2&?X}+^3U' ɱ_#xTklA@o҈h 1hM#$%8, |ɗ:LRZEE+EӺYʕ˹Ob5@5 '<޾y ɲ3d=]>a=Q;Waʡ<Խ3½Rp'z7>un˗` 8mD eS釋Gͣ@8~ 2k7I%1bR_dF$)·Nj@^usiD0Ѻ[Pj6ftpoeח"h=ưcX箃G MMeݩ\ەt}W "hFѝ|n߻ v-/:'3cq"^ۗ젡Ɂ;Y询K zx(ԗ}4uAz;OЈd/}Ş6% `߷bxl Sk;{O$r}BE} 2GyMU85#O>D'2^K뻐SGOoD\(QU_ j5oU3WZswdn\=E)cn,#iBq v_ѱ\2IS@Zd9V:z]}ġJ;P6'5Ip.n싆QnKe&c6"w-3% %(I잿9d>/_.iG4T/b@Ш*Lڅ ԅ7ů=MYO&Ow m4p%hc.8z,~h1PwSq"]񮎠X9N -pX]] 6S_2Xˤ,Ͼ1GWcɬD>d,_yiLfIhq z!fKHGH;!\yԪq~.V*XVm(`t&o~J2֝@1:ƪ 2|;CاrĸEfp!ve=YicL4?}hBJvIߐ1)nCWE6"ddѤ)]3U'IMӱJ9y R|?wRiEfs"ibg/}ogs҈@E-QJ})molFMjEp~XݻKr ߏ~ e@ +TvNZ:{~}[ {7A^a܇#io[! 5DTj W%] eyzzY+# e1a䌵EauXEŅ:YeքOBĠoL6Lxոq|  ;n'E(01%4qvGXwv' @_}V Eq6֮k4)46NcEקơv4/χZis4^RZ7\78@}!wTG'Hsy@O2OJAdg%SzIWO3lA0%Ƨޟ)GqdF6ˇ 4_Djh}/n^Ћ͏~1W d\$/^σ, yU tQjXpouLIt˸y*OiP *v2TDuWXTkm+ IDATEw4xtG) Ai>kR1ݕGw)Dvm\qs)~T,ھ+!ҜDB^Էi$g^mJ֥1&<'Č]Յ%6f87})6u4 k*a&mL"бc6fmZ.T-Ko. 4((Ծ(e3w81зK"_Fam!T=hh9 }ڝk-"a4a6)Gs~f HC5mn҂V#؉%G1(j~pn\Nm$c`62v*KR'#Dh.]ns`'1c׹ $A6HyVs FC!Rњ2x d I:m\d<E 3eڠNكFu8>)䕗;v#EYw b*>''S,V#-U jM&vFaY'D"Kf&"]Gq!+Iv ?t"6({.uo(Xy3D)Ӱa \Boza(-٧~i_o_Km4.JMr͡7;q1w3};*GΝC3hxłB4Oh%|Xfg=\#Uδύ숉6fRQ2sB+%a>S4}/#yE -SO?CD9 -AsQls1hb^x)غ2eQp ȝ2i-0(XlS&23Ƭd* w $f+>Gws-oU<ۃcDsX0W3ҙK(]\iE޸:9^2qm4M,~PF|,|:|L/rݻW*kWT͍݈Mhy .]mNIVu /^zz틄{2`R: G?mp !b$ V-F!ع/$A0w=4[6uhLdI)h,#'M{cP6l^/'Ȯ@W#d?)v D; $\hu{cѮ&9=E<\5Vc΄B6<ߛGfn\*:k9ʞݿ}t"qL@~}Z2Y9EpRW>l*rKRdXA)ᒣX_p5'U-YxwUп78՛5X9ݬWJzj[%}wG۶$Bb8nѴJE{q֝S`h}:hmPӷ^щ=f~NGV&'rF1p>D'ԥlh(Q9}Gl_(TY[mfW)'ds&Iβ#PD_OFRKsr\ߗbuJn#[0lѽɭ>>脶m.*b2fqaq"vhEΌZX '7"2\CѷĐN[Fg+h;γi)$7ߕky3Pp]v x5lP cp[ŐY텏p̸w<<<)MI-y@ W8}{ے ێ{*Pîqc7呺%JOOիW,|1`KcX%A%Z8O}`DFMa F:ؑ|BD%=ZWZnI[*iN-;74ӡa0#HDwFai,?[ّYD&|o%,&/2XV\(!O>BSAC#-?22vI nA EC]G9Kf*0{uD *U"8A lDZEq#PaQKG#p8CY:qv=?Y򽺸m4Q>ix>?z,B"I"GN^N\>IВhBry,nJ[CZi >liF55?)S,Ð!=C*&@j Pݱ*#P#$0;Sp-=~4\K'vZYfMP#6Oc' X1/cU""4jPoM%|fRiIT[ϠW S'۩d։ {Em?@sq/Î>Aӿ \v4U2V ŋ#ۡYgp87\0ǟv$a1$hMغ:εœ(""U}@F5j$c9i ǪCːt,6"W!i\I4}")c3<cGfqt]=ɴk4O"nUNj?ۻ-հs% dˇ6aɿW RXJN.֎ ELvS²Ri 9e-~w_5ZR&z $A#r/[FH0hůJ׌ vem,?^óu0Dm+pzL$&_ŐBXy1S\sx^D}> 'Rh?n/wI ApNGKKD]LDn.plw: Fg\`V;Ұ[EeJyP0sѣ'TF1ڮ@ 'D߾-Q40 U Ҵ9h-0n,= QGJ)5Ӯj5zZ !1;O/JmOGvv+]ν&ܢxG#"C;F\+Zjq-9ՙ589ʣn'^W4ZhQ9i"66 mf%cA<>"js59T% lY.F԰zZq|j *g.q~b6}=PQj1MMO[t} 2hcTdDU""iP{䇛``9|8pxT>K(E-82R#Jee+1+4r*p "&kT9HE_(vĚk(UMsF VV9*8IYH9X| ]/܎o0ؑO+ }ؽB.6EsT/*t;" ,yT嗾&dE+h{|XL1ṖXoG#0B@ReziX={7Dhh=nw'xz8 ky֭ .~Uep85@Jۨs wm+XȣlEK&m|6Yu@8{%&at\NKa&cmD i oqƈ9Fp 8?A$7%l?.ڈ?b0gKXBg*uW0Aѯ ܷp;Ac z*]ZMb޶HNFؑ^ obr*5tϛƴocl7ǧ)}̜L\:<M͢R(ʿ,{p-_ ԑ/"d}8 ׯdsz*է_{TF>HMX+d@.(/r)4Mc9+'p8-l>U;<[wFHszWvl/7gx{y-5{ $4< lNc oG1B@ykɆf6v[qhQV"ִH?_9~mH,+ 3Šd";nl7v0lٶ%1@$0Oy'oL‚->Ɠ+]ق8Rn YC(V$[|+ "s,K²B`͓KiY ň Xyl@ԋ;Q4K:d$ ZaU}u`yuQQWBvh줽M*~BKѩèj[7YX>WYA3dZO室1SLG`J(WCcu'hSj17cf8~Ĵ2GR&R6n.P)G#xLTYiykZ?;CWz BD͉؉`M}Sx8U]KW!0D"kT5<6rI̸ ~ЭFhL#R;#̇D>/T@ϨŴo0qq2~ #򚔃QJ/m Z?H#ntNt G>Ѻ\[9T G!O!n@ Ҁ;y 3Bhx>?ܒђ\ĦFXHCK]JMM²~Ioۍ^썆^LBL˜m\_|hϨM?+ǾV!'qBA9di Qҝ-Bf[FkQiGL L:+{S΂%Tgv1f{0<[W<b&d.L+FzHv~찖桭" `7G`to: bұ%FcX>fY驸V($2F[&0[ {qJ/`Rt%iN̥tv7Doңzj[5g׭Xzi`fQI%< O}Okhy2|KY/'GaPP/zڮ- ?< ^b"?r8'h fCю,hogC558#i%2O:@&MM 0R&X܆ ifm@:7_VKpISJc+T],SNb[-7BTG# a>5lah.h2 IDAT]K!KS-J"=T`/::Z h!!!I8D$i 8ķѤJ(@!h!k !^#c#L>=Ζp8C1Z<쾕5Їm֣c~|+3'k6ua+1&W`GFؚyqQQ9@UxbH[UoU끔 QJ R@!{qA9' ʎfɺo%,HD̒VDLȐ/Yf5>Wt Y]MNl EL3 {wjhqװde}B&V<1<1$퉹G#<X1+5аkSp fV3l4S윴c!m|s>&邤;j83 i6 8p"h?aNS eIYd3c~|T+ Ox ;:G#p8DJ c!ϊi2#o45VAŶjO{Si"ܡGn}ɺL0f:<=< d\FV ĩ#*G XM\ m"P X3'?; n$!)[:͟F6|Dz;Fq8G#` x!cc4HgheDlPTbMHBͰ>KIc5$^m;~B"O m[wnqgж3\LC= {զUv ®ՔeU!f<tv+9FߒVp_r\.ǬU^<s8G#TiEl2V$ѥVy!?J+( ;!b,|77R RSwRay#q-X9$MHlݱL}lM68G"8Xmף 0Rj$3EbmD0"&8rҏ ȑVoӨ5u5aeO+,:HŞLDK:/ҵEέ5bǁҶ-ޓ-sq,iN;q9H_Sy@[ҜZ"eCDjh,aGG#p8zJhY{/{)"mB\ZÎU$nJ_ƪb9iL F\r; -Gp.v>(BH)ikDJJ7VV1.eB ֜؝4EE댞0Mk2k|I(ŹD.FɂS1|Q0jrrꇌFLikFÂZ!TG8Y^j 0</.>omˉ>oI+(&9A+ p8G#C1XǣJ`OD͎#il1|J޹8n4^%(wf'uv? o/6vu b sE16zίp,*e냶gZ|,Yq妝(C"G7/w>y2$ rҋ0-<:yXtY#6fmۣTE`Ӂpsd? #_caJP2*P"}Oou)kX pqsV˧#yj.÷ý\qq-G#p8RX̊,ilFsD`C4bn,ژ5MSf%1Ŗwss'>. _q-arqQd9#RFPZ٫,e2;yAfin]XqED0~'fDBGIV\yҤc1lpxaȐԪu!>-Ā {S,0LL fB/G?Q n >Z 5Zpv]}AQϒL AtN屏(zh߭3:ujlۆ[&,G#p8'fE5ǚK->t%q6TIrs }iZ4ppqG3}K;P<ʚ D\& يSwcѱͽzt#3om6%&2{% 4gYs椙:-`dI# "j 4hg.Џ GMÓ񜝵'Q|1pfh>߾HNbpo2&܋chaP\߿7Җq|:o ?~8{]xi'=V}ʝwbI"H1Np4ԟ򶝈]YJ/٫I&~QL#P$0)k_KȲ/oA~?G#p*Ÿ V3V%G-13F7b5c,1'sqqAmA\I]/:)93sqLd@T*ۇmMt-2ÕDtmpʮEFT&%' ˃1V8 {{"""$ZTD 2Nq_Ǿzrj,_=~vjjm[CK҇}; g}竩v.#p8i(u-bG-aGg-[űC,X}xk]z =}&杉sȬN~mڴaÄq$ lE"f(#9IVMC? ('rj'iM1΅^+g5rsy%G#<}JdE "jQL7fɢbh,O=:v숧z YYYwّF2˛֭ VTp%ʔDm@JO<Ob{'_6 G#p80+c4'Ć6s`ČF!ScjZ\EST3Ƃx.?GIv G#p8'FƄ_x}ZYȌv1bqlNR2 &Ix#^8I^|tG#p8FDCa!̍>4FΘ%Z{,pH+8jZl+W#p8##+)ZcM>l^p %fV3H3gΚ8OȬ׼ysaUV]gp4i\x,G#p82#si_"s,$Y1MFӔ禩,bi bWs#/"$$cǎ#$5L q+oC-Y13-Z㜃571 *Vch'JL `Ny =suF"6~Ǔis9u2y=1jsNFGQg47^3j(WUڒYrGb_BfJ!aXS<:\BP%礱U_ 5R%d$.>ZTԇtG#PUj֑X|9~K-?O¢zuhr X*V_oSi .UšuK2z6g!j"C}id"6Y ;2Ki#cw%E3 k(!S)LvM:$E$Tץ֬GHrW4ov];^&4>VEM'&5w6>OFXѢx '*VL@V#K,_FOL&+8GJ k5mdMWӹ;'ځ#sp4n%1RXXXWIw8~'C|ص<{w wo;x e $5Uqv݊j"ֶze%#1]Uu#=Qf!##Y:5<aP\>^XP<^|6#o3wm7qؖ@)8ipW}>HU3QȡQɐ:hdۗg[*d܅41, vvKBXtqZݍ,yR+%[ &nAJ*($00 ÜA0s{{y󽼵(LC%\( ~f<99q/k9va6;=VG?_t 2s+ڴb}lSݛDxy>7., -sۺ'$̭{>E1X?2fPA@A}D/*`/6 & ׹k4\kg\ZQǭh):ٺjt;m<=8Qѱn|9Qcxn5g$Ue!°0sQ[jns86٥qĖt%$_'!upv%4e[cbIL6jHOGsNܚ$O{sR  bqjۙm]OK_}O}8Y[̿S.vv|R;Kꇏv047VBѳwW!6bpκOb ?<* "է3w爉7f! ky>EӶe$1t_]iFYM>Y!هvطf9 U oY ^Ꮴ 4/b1=ٴ Yxpcg‡y$i!˶oD$} \kg*y ʌ/)n|w}F߮?'3^8jE `nG< |WR MWXKV!)/&\LZX#8*+L2vkRmu 4X[bԽXWXwuőT, uGbLO Vnj.;e2L pU9eAmʃ1qneA6(+ 4kB*y  펀5fvԤuKq{5hڴz晦I״ 8a u½kjAYBFGp{@GE4g㱃wǥZ";bpL϶oCT4$..FUn9G҄U0{߆8{iIX⚪3FOʱ"v e%:Ӳ*7ѝ1|x1v4'HJ9n',ȸ_P7>sqC_CH{ѻ0T!eN ы14t2>1$ѯ iO i\}Y'>+B{ E-;Nkr*;O|b(\p$M`bj^SMn垯.$h'&bt]'!)1.rXO3vyQ{`b*LO#& kMNX)+Ȱ?foC[`UN%SA@AʎA9$=ū_*.g@$ڽo6XvnNCGHQ]U^o;+=kH0fJ\ *r#$MSy;s kn+Er|4mɃђ0vv8WizW#-! iV)31NHrc尡XeՇE5$ EMK|}o̜%gkiN"i8q(NL^&/, _'I^Oa.^CFPkHD%-%)۞%E=뾘y WdO;m%y8MoO\Х~=q+_$y{ M<_PIQ8Rm/OcM%ךJޅN#cr= z4gW5 sT $@85K$-wT nHp,~2 yM=Ξf ȜPCQM*Jd*C7_߼Cm_6ab_P{k̯e4xwssAobG>Krg xz>Oڄ_`03k2re=~ggyORMkbZBbe6 3ǛY+d _!ZYۅfVep_n?KGsKQOpsZ\=(fM.ԞD(s{P^lʡ@#@`ϗfT UTwL"kP<7Qm%A\]:4]CIl٣8U?}Uއp7tt$j B훨k5QKw:5d`wU  }01~.!\!p "\Xژ`$LVP 18됵wf&N@TT + LEKFGVV4 «6Ly,Ѓa䜍2^5b)BĨYndzRlgy X쌂=o)GL_B,I#'˿Cmtš IDAT?q+ mjN|!h\B(+򌆪,3 dq9ئAkq nnplONRu&;ĩ\I%ogh+fi٘Z:T݀ǡcݤԦL?\:#Nb&4T* Fݽq?VD-.ޅOxoG91t֠FfEq6u7H4䋏c8Q\>mj|\mrPW$_4$W>lRM/cn<E43S֞ל?ZSwbϻ\|Б FzZ_~Bū?UžeʍO/`=}V/'Mc;VYYD'j+NS \;q\~[y+_g^Fًl8={keW,"H,_M~k.)8kV?:£+\߼϶&;z-\B88PC_9uquQZ픲WD1%i z|3PpV_"+= 'D 83HUFaH}5bx#C4BU.b,Mɡx0#|jĽTV@xcdZ~.kijS}5._]Sբ` {!}.Tܜ(K1՞Ŏe5 R嗝 L5YtXW`_J"V}@?..sL9 #hEh."k it,[!.0tAm4B)C~|Vdp:D~iRyrZXU~D9mx 3Q+hM;oq.y$}hY[]mkn|*q8O>'wOX,b%<@mf/7ؾ4Zr3c0O9WP@"fh󨢍O=bgu(cOiyKǂnn f /tGĄ%4Qo3n8/x;]gW\Wv8%1D"Q@cɵ -tB4'M1 (**ŒfK|U/PܹvxDӷs,+@%H\^#|s&v B罹̘|` (nXs>7]34OU ;vcl9)E547j+f c]dᄰ dۓ\:\nV-z _\ѭ,%0V&@R:?͈OMm#d#$:?؎?:8Yͳ="#kzV*yH07i4 gG</ꈀ?^co!3MS0VapT WG˧9#ކ s)8FG>==ƴG/"Ӓ.Xڲ|6Ƶixȏ>yݑw ANfDSG͢~:+9"}D,0; 4ocۤdAnb)!v즶V}2b]2q8CFjq5L"f(=h$΍E99=ee}ʵiMG1?+uYnf dU6'";6M>!s"=ЬICڷg}ky9Í ;v(%o`a1 j,$ROGxRspI\qo5aָs2};6f0ŒHh p+4fEsG5tj|0 1~ [nǹ[0kbcfbRD>%ڕc4:R>~Tj1z6s~AB£Vc|x$6#dA9li"fl3% .qkcDD n-x?U7jRcr|0p Kd3l>ɛ&x`_8RK3g5T/CDmO'uј 0=5uC[reØ1cؑ7U-{ײZ¯,)KKԺU箖X*)ʵ6I7h^֍Wih'7 Qiַi-r`:խZH,U=(ߦ5yْc& *++EmH AMIF)-|}mgx񙟇;J5PXX;}dvA|JB VZZ@tPH轂mݡt/X< U4j^zI䲱Q)|8⢐-,scHs0o82x5vR_B7Qm90|%CwZH!uY/D7nvYeD%"Yg.lI%bų?TvXaͧX/\OEBZ ql!lŻKZʿD-CMFYV#ǍyB:!I"rN*R m |ƤC<;<6f~)Fs?Yn6#QnVScE|?,jL({ 4I4g{07GfMapLIdyA 0 Ikr pkHfD?'ICU{EW74:jL<I&AҖ?I;wC_Z2J4x $dqБ KqL5|УG$M& P;6b@M\,7'FK:ђ ݕdƺ \sdMIM^mtO&϶U@ _N`SѼ_FawY2GE-cl5Ts.&ܤ\{r^9}({{Z"iS|{@NA@A@A@A@A@A@A'XIg:shc{JCT ']5qDsI%kA!;^W WGxf-i$?D6ON$h+fZ 歈-۩ZjY1Q8ڌ|}466lާܹ|M\rl\W_S8; jTwϪ5$;^K[n gKړüu3qpR?5|H E^^IQ M+ؒpk$|\\\ݰ}?Qsn "%Ϛx@1www 8S'N6dS2Bl³yUGPv9@;lZ r41 ISe ;1##kmam}'ԋst&S&dOZΝqKfڵkL@% h"4o¯[num%y͌7/x+-dPHOf*(((((1Ȏ¤zhiX:ɛJ !4j$e¤OhDA j"?MA@At>iBz|_vg6 ҎJ3!9 3f-KC|twݘ&?[=fJlo^^$G)Wu\ۯS+p1jԺec>NķIIir S'TiQkVσEЉiȯ:ʹ{[{݈Ϛi{tЎY{˙Uڎ- iu٨ ڸQcYQ(^ODZ~MFD8&1 )8n*f?Է٦۱iPL[']UW`sj,1O~KI$h8|_ w=|88%JڎyVGF9`Cpj1#QHRAršoq3?-P-i@g#FqC!GыkxS~\4Pl>&tF $ T@횊+Gf^7~^>iESKLӉ'aL>M(L4}o:ͭ0yi+MGW;0DG#X Mn^F75#yFΖ3'baz!J1ܻD8֊6k Kv7Gy.2>nhw(qii{*MY²B$Nj1I&&g&ƭLr3rߟ;}AD:,8xV۔OS™SȜ*K`T~/H#dApP6(o)KkY E@15ҁM4ܞy;x%'xƗKIIIATOUޞt^T48J$&&"i{(ϥ] DN ]B22CL!lDXv*NU렭>}2ri}ʲRqy|~bcXv$Οd }=ѬV} ICga*:I^_O?a|v*p1<eKѻ!:~cx/sX80pZ{5e,Ah@(N<;q:i<֓WY`Q $**p2wɌE`vP667R/k/ȍDX\BўRcۿ-`ȍO%abLRD%qm_COL,Q&+<9}CLe0>çsVYߥqHY=.n%37aWא{5}9H+9 m.?+v[?pm<"cqQM-8i e9AZgƕ   ?q1&`jLq4xމvPB&iIQGZȾkT9|k%g3o2^xa)?;qTXA@AsGgD3z\>ĿV~M.mJ/H<~`iF*s0}N6Rj0)X4Hm9J⩔yҔȤd9ѽ\&pjp51,W9m> y1~Ocs3CcA {z\*_|&?}_GlC#mZg6+   ?Y$eڞ59_쑜 ^mѢ1$z"^xnwTI5Үm iQ=G<_?_tE*IA@AtX{fZ|r _ܷYzG`9=09A"YE-%%FDКNU]M0mua0#!%?#CqWُO䲌Fe`SPo!e?4-]Yzee좍*Z4Z2$e-k-rx>1jFy, Vrwq8԰>j)F.A\Jez{7We8faI4=l_s䔘|j '?}V@HA{(n&IXnT5^돼!1#Q2XkqރC 0{^MHyZ'W/Dp`ʔ̎ZYUTA@A@A@A̞/B*j3-J{X5k4aXWWTc+(\ *"uv^ldc!ŗ3Iއ/Fq?H([:%( @"ԕPkt{y1`DW"thˬ(/.@UsYn CIIx ' KXk7bg0vY#-6C]zoI-#oš埘5jv>"&43w8b'&5xMYg ]{j%/;S0E Q{2 3J$7\PVۄ5 ^6,OؤujnuIy? =>0ӞĜ!yxrXZ>2كeU.+62Br!1;.>:&޻L ӡ KrAJr=BMgRas\NsEΕQ|H3}rpq` uYFJyȇ8 21@: AF|x&yEح ԥ;> Y%b:T t)we).C!jP/rKQIk`MI]P\Hi% θw'ԛ X ~hvmp2O3$HLSٯ2E#{1[-m Q p)'S_e:ރ]U5ʎa렷pV_6ۆқ4vꂀ (qU0D.x7+((TL!5+fQEffO&pv3&#,&5Vi{7Ho=k|wߞE dx8Ja˳aՖsĥ7hIU}??_x ]FOMS^EԜpR)XGIZ,]Hg IDATEX+ 7 7[Zs@8KD"tDUNLU[p GX%|HD.yÙ+X?cVXd$FlÙWӁ=d07k<%y:A)k4="Y}F`^W%%=[LE/ދ>}x9C57Lz{'.(T d*9c--@3'n֏k`>h<͏5Vji*=1ضL2%19u:6-ރQoNz#p,Y3-NL;gHa}fTϹ[Fn[x/|=8^ ~)aʨO?6$nWK zy:/zSj-M5#&& |Ϩ0āMh:Ь/tDQhXYc?*JO#{1V*kI+N 6=ўҤ5@yCm;r0$55X^mQ_zZSLL/#IF~6A}o1Yg&y?<[3pb($vAnʕ cCS}{ÍiέYqޠ`AǓ/VjQ3Rħ0R` 7kk٩z+,^Q3$^^2?Ekc%NF#ts#iPYY b+Poɷ&z/}֣]nF1HK^n}ݗN;heN zjxYZ;avݶ(&\Zr:@) p! (zy" A3ɚj,/>/o>@ 6,Gll,Ǝ 7qQi ':607FB&^Q.pq'H|5G;kɅw+%ZOnp\p?ų}2| :K/08Z7||fx O*fZFA|j\ Wli#Eд)%/>n Ϊ 9IҜ}OaIw>}[!pkHm5d0x(Mh{2N6W>\QAḽS ~\ aUVN~Bx"" ȘyFedtq77 t5\ wwhC^f&tL4 ZR^j@mHk"k~K$+TjDz>(؃փq#}_dAN\.{7XyQ>l"p |lPaI0  twqնŴFkzx@khhOuj+)u4A؄&[B+Eak:PV4/7gxyeONKހ;xj,<\d~Ojl@/*2ޟ٭@J%Z?T͙etj?pѳ^g3ЂSu5(m NAI^y䪁B$<ݩ;/HE($q]^W"#JRh+?aVȔz       И 5T7XARh>i^*X81jI;}źWO$o=s"*-((((`u!"uCЉi]ߥ:T']58sS0$w+M԰ulɧ^WQFK;Q+gJuFtlQN_Y}_ ת'o 2t'R ~}N"e*sߙ8xUCA}%:n뻀CuPg/@cR#->Hh'6ڼznIƔ|8e ӻ,%H\jc ?趷I+¨g1rZ*IA@A@A@AE:mœZ&V17tk"c{'a? [,uc@_};"'Ju;T!`Ȑ[CBnyJ?~uiﱋs1}"|r.vo$U?-* SF49JJPaӫNSv9:XB b Ap4Hg g<Y&8δȊه*T?q}/@mo8kDK͓*3:5v (ld+/1DNrpFeTZ:fd6e, Y ǰI?zz@ƇXUtg ]4.Jl/'}}m7 s~ ,HE:.8#dNNmV8 rO's:@$M˚M ı=hPLCZdr4ù-JN((((QcfGMZ'Ԙ:;0H=yމvPB&iI3)d%c^rzʧ!"Oظ93/`ob;{IoV;x4LY aθdf(CSч.W!34 (7M a0"[mT?k!~ EX8K3xt0@YFJQ & ; C+RһxBf4%;0ы2^bLKPnި8T8\VnO72#`Ii3Dmpg>KKKhVD4*OGg+lb~r44+?ɲ\ y\-YU/|V“VWoDdT*Y_/j Q~o ,3`;4[?sea+I$ۍC=5Jm~dm)    w fv ɎYq8K=IEE%)(0]{v*=E@ Lw$i6yT5oZp iR>"ژr/%~F~s\©bL6.x-o{SIl}&M@L<<+edә7cZɖ!e?lVp T! M$|PW-Ed,~1$|YD~Uy,F/~otDҏ'n{ض p @rf=@Qm -dxf_Z [?`8q6l[ۄR^A@A'i "T7DV: IrvB[2K*ߖ%p ĎPM לwȡ/Hc0,a5݈֯!"F Rcd4OziTQ F1RAi埘5jv`k즅C ؽcIEMj,zASCC[j۸J0*UH\jY&aF,ͭ1ӥkkM3qjiQAl:OUb,h.r p!@gN$e[4 Тuu*J>kf=LĘOზ$HQ>n旊Nrq!-.7JdТq:5O!܍>ƀ: ~Kܣ*==0zLҨ1q&zsq 2.,%'] gaMcPØ ."3*(>S S$x>9`v0H, ~#X![~nDK  c)XBm_Bt!̏j-,KSe|>M*G    wB-7HCh(jx]hĞ"xHG$5[C O&WZҗQxf9WqU#O5 )m*[reTeUA?km$((ȬzpSߖūz:3d(*4jnH:fqgzA,&y/}֣] :X8{߫zJ'TTpO5/iMyf⩔q˹u )3҉t`kebK1j<9gyn)R «E{5j_ މv`c=aX[[ 0a ]%ɡՋy,<M;.f:cAZ4ۂ*A|&/dMAa/^2vahD}4'h"ۭQ>-$JESR7)6-.}Vé Zs̚ArC'b~}}=a `߅f0pq2ܞB+m)(((t8 4)&.4h:z5HMhҸ)$lR,FhhYTYcᏐo<$JO#{9`̬}OnTLCtB]?y['5$IL-k’PuETwYTIVRƥ'L1CSwM1ߢE4s 1 0oK"@/B̋ݙɇdy,y W(o PH-|[ИQ`stQ 4Mz;$ե3N_`^eTI'*JوU Dz9N^j@jD]k)x {VȭH m8L GZ -0rF쓮SuRɼÉ/:&5lޜH?ېdX"bXj3tCv%Z(8Ѹ= ٩" H.hkS`i6~ o'+0=~rcaqk7`q=*IA  I΍d߿F)en1QKsԤiiXGh BQF7썖C:£+<<нO(Tb IDAT;c­ ͋AM2؉,g'78 䥢_W,kaٲ׸IPrQ1rlsΛ7RGIhyM e8OYl5uBPnլ^S ՊK*_jBwW((SMyM@/\e>" I# JQkdKԠ%~Yn $iYu2bĈC^HN|$FpYMjb)&btPHi@yI\1im'MQ'm}jJv#qcCx~vt"$1z̿#hݳ DNKpDF=Yz8L0<d08S?t W.`а` _X t5׬@#SUPPSFuѼB$6tu*qHFMLR{ %\ 7Gn͸,CϖzL23 A]ZHuE4rͲcIbpݡK ݽ `\d͏`=bPx4li31@E&9$xp=HvkR¶E5ލ*IA6I)ſ֮Z9!th.akqń4~,l3(Xb` '"a;2{+bd?YB+YQ(;kc%\ԇ3t8%wx?$'7wGd LVbWPP#p6b~h$\n=rQCpH0͙etjnuㆈѳ^g=5Ђ0QYR2FՍ<1n 8*(*hz"~jhΈXHoO))8Q1YR`=G4pd*>w7ܼ~8ύK_8/Z\ vob] Ԇb(e)FaTXa^l7sbnT6§jS0ǚ%_ !e[Wf)?vv/潉>ԻZMd}d". +2K Ww$Lr0YOҗKE 9oӤnhH+ 4"DŤA^ C&FoGzlqLB!>>/i3lYh3dF;uI#,.0j~-^n,ŴuMMW] L~H"h>/{"qzSǖSR QF<̻.r_( `>_ϗ D]I۠¦7Ԡ  t: Ʈ-XFv %*$:69T΅$삶 EEȝbBMUꄧ3;Yc_ ې Y R=0A٪jkbǛD/ fRlw !f #R>mnjq7Q|~iĈt% 7j3ON.=4Cd`(Ndԡ84ۤpCPCg<Y7֦48ĩ1继|~ȑXR% Qx0KK~ 3"?a٩8U_ٹ2˲Rqy|VbcXv$Οd='5ޙ6Y1P/'1AXcۿmiK'z3>^+:)ۜTATFKHFqes;-_%EᇱcXJC =.Ģ$o'` :Hf邨]EÑ    fQcfScr y/ m^&g[*U@A/(fxÔL*׵XfVM-vWiZii즶z)uMkxAf`g6 9<{̜<6i]tAw]/|@Z /ܝ$k=:֓'BY ۔&HvQ)ՕU&[5fVVf2I ufͨ좡H@3zjFf=s {quolco77TJ*|ݺda('1A㠯W9{T2r}>zQY/ (9faUH:,g"y g#BԘlҬ垵)MvR<J#Z=į0HAvP|*O.0gU.ZDfD Ͱ LFaN= )跛<16y%>Xɣ}q8bww-IZuyZ F3!u'g@@ \A34P{dMH7+uGۭ5?ef5mP-ۡ(jJ裭U$CT/V%;}~cVGb(,}{r2T sqL;F(y=FLHĀc?dhMhS^%Qi0a]jQʊ QXG1)YϦZv.H<ؒA mX_]a$ҕ)\.#Grpns-:^ 22Xg'Y/,L]Zmr7h>ʩwRט׊YAKiT*EeI8Sh*]!;'E &:qTvjp#\t8S?kG6(ݤ8"V] (Ϫq)I".&I\6MMb3VsT3s%/p*Qeh))cKr9e]Z@4(K3/څ-@€bvC@,uЂ0L3m¢^$g&0GmRXt s 9U!rb8v/K1a Fm2mHHRQxzPb0d!|@gdlK ll8h½DzY`/7͏TZ.O~P3$[r ; h dQܩത*`WZ(9̆^In̑m.n{PaJ:>;4ؖ(=q] 6tgk"M ,0%m#ba&zsstG#!+~˚J P-_8iRYgTDYgмqtiEBԸ5eDOQ̞OK5/{`$B#ߣK?(t1TkPQZJ7 x+s :{_-!RT?fkRCX^L>JaA;w'sɓLx՗1@@ P 6_l!##SyE *hP<(! mE?ZZݷd:0ydoZcz'N`̘1Us'{!CDzz:f͢UV&!p,|RxhW4Ř$ײ\Q_Ȳ\K۴lNLYG}^}y9(O.cV1v"#u;zKEL"5w"t G!6 r{EkޠP P}+Zz*v6c( @SazV B2]Qc8]/i͓m|a` r9I_ZZDbbbAg@+$qvIkXo\u/ m{lqyG 3BϦE>ג[0F' q /ǽ5,-R@L$3RkdTKn H\ l=i,J*G3{dKnC>逸_^$v 4ɷ$34gnsAjBA h%%Ik۹#ss"M #6^JuF\\CQ^@XC6iaIV$hroKJdȽW$^}̋sLH'ـK9=Wᡇj%!v IZ+@@ h%fDFj\Pt(M$ժa"8c4VqTb[]p  C4YYs$IN"h/  ~~)𐈜bh"*4،uݱ#+@  Bƞz#Ij'=jȣ76m7 tVԠB*k֢:>^Zs[׮큐66/;8UUyOh7vq3&E& FFNV[a΁Wo,vOA08799DZ\ww]vKNƼ t՞}Țy%.y mCh^)ͿةӔ;$[05qF|*{Od aFo⊓NZUh\xg[<ރ^/sc98Q\_[V,f6F6i<"7P::җkYڦYFy:|FcpEJUb(tޅaMACRTaO-im}9=ۻ?ug@]HZI'XᧃXd- ,[c8+蝏p88 YWlfkGaР'0{l9ܧU y\⏎T6M=8vhyAqmV3fxKWjI[% U-]AxoQO_ȷ/وw_q6:WԙV-QTt!*OP"jv^8~@-4KH^hkV)mjHREٔiU=~CQY^9O"ϝmnrTTklI٘mQ(>=F;b{V%@cE[@w a L7j9l(9<ӧO?oߙ.wrb8U1|iyK y7-`VGIݎWRz{ m{rGFHR vdQݒ20 k χSPEbH)yev#WM8k v۫W~8d%z`J<de{1|S߽VK@v2<5&Š|6vG؈xjDN܈^9Ϣkn!ŲrτV͠-+;l67 ƪո1u<5qnE)džt jH.p#FxPf3lo&i]qpBۧ¥?G^kpn@~~0\4Q3‡o\Q*-1> jJT8V')=_ߠ< /Bv< t|4hx",f&fb0_Y7+z0N8SZ0ǖoPkp F7+Eo A^8Wr`ÃwgDFHrvp*J5o5CGߤhf3iaW4#ctjp7ɘoS.Ag^J7ԩXf7̫bR?9 L 2MF3 [h#u'4{h̴R"lCu8޷M~=_%FHlʃk _O@׵1"[* Ov;9{)>D?fvNDzӊ󟉜 0r=3uϒg5:;gʋCՊ#Hd7/LP׌p'FIR{ɱΧtAn9 O*}D}E ګ6Bp+l _xҺr"2jO54dŦbw]^! R6; M4Q pcg.#X6?^R/ܜ.9s2-z1OįBA ߻y+m! =pY83Obiz \L܍Rm9J `ʹBjt/gSJd˘k%"?Av2<13Zt6T&>e|6X_\Ho:bh>3לDڔ( =4%kiz=󭼷2Qܝdł-Og#.? sDL 5PL墂 35 nnp%q+]O XNaPM IDAT$]v_n' 05<5ߑ^X^}]R]D4YxrTLSNʥ׍84wV@SU_x ^Xmgc 4!(ן\Ko-̏jN$~PTJ/yQ3)kSGJNDt,n9Erfӷ5,.Kd[Qc';"L .&ɢ ⍔,g׏Y*.CٳmFǬX{T"YTC$E-nFBZ6g"/?azR5u98{ym<3#_GQf ^N 5)X%ئ4dc/ڤN#Wzvʾ̇޺8>ߞ2܆!3@U+Iޔ1AV߲vK[{q]`GltL}—~Uɋ^Cbp׃A۹Iny67ot#Xm$.r0,bܻ _,=ʦ WSImu*cРXщsg|ah&:jbe{ƣ$bBJw[(-~1q$r$gд6,2n )iP}+#Dqf|r :F6*%n,_;XNU/zG/c5K1ؑ>9˿XpþRA*E4S 4zyTrҥyV"N"hr~Sξxٸ4D/ IX-̈ԀwF6I-:_$F '\~s(1 WAs憠H.=c;vH3DhSאj>7F&bQY)Mާ1A?*R^YCKq̛Zsoݭw3+ =i`\֎,"9sHԌj&BFb35\' z7gؤO%iШ`v cCd~fP^M+Flk~F< $oʦr$D(|phٮܲC#nӶn "eF@S ;-6i<Tl۲kVVe;nK5Is`n^D?obҏUVAIZlI%Lq`<{T&v/& %1o%ߐC,I+^L2O!4}buD(Mu<mFD:)շPzQI<_aW2 a> sVo )a@`F$&v2'8JDv5쾕)^=gC50'Ꭱ1qcj9lHFݽ4S]pHwHN`|M>&~{>yz6zކq@r%IsfQA ͅH IXf,Est5(# fI =H~Gwqt;,qp\Ҁkue>ȅ笇~jLeCG@Ihʈl4d~6[ՙeT<Oo1p6l[a#׸ as`̒/-Ti[3QlԴX-5K2a%sY{q&mѮ?.q&)Q{ Iҷ/wp}$ԑ$ cÞ2׀\eQوIPH{:ϚЦj^%Qi0a]jQʊ QXG1)6oWZZ#ߝ +>y~1*q AWxte nCS RYJiM{@{MedGO"%^$Y=u&w棜z'*ԣ[+EU KBkAKˡ:ɑ{ $g5BFǓ&wLHГV:ּ6;Ǘ&Ib$;{| SݟGnPl"Gjg{TGϼghf^{q.^_; G` k<8 .dFf.d&9118噅^u8&ҥ 벐[ss >D$Yc\=urQב1'|er;6(gʔGN]={uV&G+X ٝ>9CB'Hs`AMz3UoMڜg' uRqH58vF wE%< /f ]m"Y_gb)os"9oy 4Xt s 9U!0E<˒|Ls>c4"1$a+,CvĨnyFNƶ8< XƖY+{y/ix84^n47O>=/ˍԝ~=@U^8IR@ُ]C-8-.XN*7lVG[7M` r\兝cT#GP0xnvDDZ?c-9ҿ͞ l U|kmtY?B.Aa +0z&8C[X.0Ú[q'" @[HkErFj.-5}Ŵv[w7~uO*>^?s~3lR KcС9%2|̥?{/4afL2iΛهQX$Kn;#W-_8iRY[WʫCpڙ:UX'h ӠLC#Z]R46tMY1Q%OJuhiך2z"`O҇{R?_ʮϓKן<_g~[_N/I< je.!"#ߣK?(tGE7)hݿPXnW7kiBs۬֔z!o A;w'sɓLxa5?DZR) C(9V>moAsz[w2aMk>}I@;Q<nZҟ<$r=p0;{>ʡ<=IˢIՕW)}K#)) 'OF)}7\HJ 'N1cIׯҥKqȁ723F"ŭ뉳@@ `-(CO B͜?fخ6iY*rG}%S-mvkm!YJᯢ>amGy Kק|GǢݡHݎ?}6jX=[}qZuM^fMw"t4"zu6ciI@Z'7^o~=-~ > rg/֎dY 5 0ja 4ѕ68]/iAȓm|A`j,QL8gBZ@WlWyaOm+zg6FM5MKKZLδڦg8>vzcj9wֹy4!kGyMhZn^Ig<Hm.cJcgaUR9.k lXPJ++.Xy/8?mFI:jLa[Y*;6mhͩ:2ʧ_pTʹmAssLX#nM FڀXKժHf uCDXYH͇ iBJEu@@  b3Q5JI}O9nx!Ik%@@   BƞYzs&񙁼uiU73B^^3mNBH9 q "u3'٩tI9 X9&A̠tTON% (=m?(o35yQ>̘ow$7v1 ƃ#LnQEL1GG.q44|[Bᶻ !6}?r+>LQ^F#* +W(<(ɍ+FJ'UGEf%6i^͉@@ ]]Y`U8-^Җ;Mtsd.n`CO^G|k dlSs8 YWZA4 ̞=[:=~ګ9#"|ޏ;(UM=LmxGߏs_i|fcmdg"|i |8n ni9Ôuܖ4{TaSu}s[tT? gbp}3ra^,,=L35sĆƤ{x7<2Vm#l\ʛ0)tz]lME19J7ІئceyP|8Ó眹.Kj?= `?;nǤIoع#xciW큎3݅(Ջ(.,ĹsPƙc7|8i鄇FJk~댨)ޒ܌d #,8t8sh)~؆(Mxgltu0S7džcgD9jSb[8R.?e㥗wc8vrΙE_`n$/F!Ѩaqد0klԜS_m[h\}|w ) xsO!l{aaQXLiIQ Cxx86HQ#yauRa189q8<̔*qrbDQgyX5?uoKņP%BNLgMهW=wWN  ݀Jض$p\<سp bg3(8CqzIM€#Xq*KmgOsF=`hoa<3D҂G^sV㟳7/!pMkSʫ烸!r]q'g@@ ps "$i.(itB?,hci8e`eyTTT`˖-9rtkn\q؇w2oC񶪛?wGC^ 0uNDc+V< Mز,]|9]0a,dS$*DO[3/DHpf٫gy>cex" z.NJ/p4vyox}b_qch~:3V"}8歭W34"E"@&`QMm`?0uQȭhvXH|E1x9: ֤`ɶSXH7+e/֞bEHGRmmU]f$ec[~&G6LQOF!<#} HR7*W]<';jó;z)'o? :kؒ:cYQeIq%:9TdEE6vWNg* v؁yaܸqؿ?OK"t~ڜPK!30TZ<]%iJI][7TraCL8눰Q'x>z $M-9n%1QSߐtC,ckX)BlBѿndf(5H6]Zܴ$Rǒ(9qj4͈ٖ])ry>8)ߓ%HM HV%*ŧNZh O.E^p2/V!쉱+5|LʼnZwJwhk(`IrYVyu,"q4mM.m]oflx2½<MJ.8O bV:ʳՖH#)s!VidK\(]M͍(YҍDuHKK#Vw<߁˗ ܺtGW.y5b(~6{w=g{ȌACC$!J\t(љN=R!MNHڙ&D*+و{ݠ(1\v93tMDl^`a+>y~1dEH޹X S "'wte nޤWLNM4 y4FH`~mvދ%K7࿒Gy[z| wg-&:-$1#.&I\6MMb3VsT3s%/p*Qeh))ѣqmH h^5wtmOҴyxgFǫt4X$;Z;/څ-@€bvC@,u&:&+' 31wз9 z3TijIv(ܽ)N6yIdUȴ! 8wnOfԇI6`c5!9'$yA)Y8TB狩߂XzE+~ayNfE,@HRX:&* k31t=JuOG>=/"̆K™-9DZ{Y Wp4znۇqlF6M%2;(^a1>HbKY-5UKy2 qԷ.Z Ct9j˔mxe_>_}to]8^ƒN:ȓ8fr*%ˠB$!7|Z7BBBpuKGZ:˗'{ h4 ^wDD34mti 0;3ѐzɀɢTK۴hX:}E_n_\ew:i'\8ei' IDAT}o݆miP159 }̆^Q)ʵRPˁhH *OgvvRJ{Kix ؝\-NE@%Ob3=;ʫ/%b@"PF߁ Ly CX)8Cyz!dE?ZZݷd:0yd6 ID:6-xkܽ|H&MA\Q_R"68Q )9('QL@hX j#cPdn[~џIl텆J#v:+oZH:Ͻ#@gA@3bdF U5.k: &Y &Sb5E)Q@'F@N|@@ I BƞY$izs&񙁌j:۴jϿR2 TԛDunFt%#8Zz3=A hKNJ.\@ow/)&$򆈶@@ j}vR#qBHhfiؤi\W..Z$vFTݡz"ҋa~5rH".TըpURcNuJ 9hcmnZݧ_G#nHgo1j\4}x~D8ߣxq`T?gnACl[ш@@ @-4*C#DM/ywdI<ˣ32ƨEOJwwxz^:=4ӥD|_p j_a(ӹ-\wG[ ӇZ"lߟۂTs>p |dDc MJA+C/-ģ oEUiNa?60o(@@ Ά2sQvV]Fjq$ %+Sla 1AQ]GЃds5Bis,Em5If[.G ˺r*O{ vgvng7`mץW.]AE1)ժp }Ԩ E=v +tXJNN"iJ|7mX䅣oqWmn)J@NVG R|VA@@ C*W"f$ycƚ79AcGJ-IϮWCSe$3v.,;5(TРBr@cy 6&i6X d$ xd`$cIqں,*}cʬqn~7 r; }sZp<2k O÷W35b#pCaulͼ Mu /ip?|F8s&Nhp\ńZZ-PР%槖l M a@arΌj.úkw\E[@@ |Hb"I 35 nnp%q+][iLm;9NB#72A#-KI/oO_DB`Kua20Sh"aKwwGtC*O>O1Fi~/-y[0i!4ͭEX7mv<`ɓD$jCshe~iYbII^[G>"ёPX^ށΣQYQi}@{x`=N 'ߜ*IRlAG-4pǻ;00/&mIAw"*Xq@.4rKu@@ ,E#pw&tYM/P@ڏAGOlмpnس$ZH{})q?LX1[WTI 9(ƺa0eN4"jN nl 'LNT''bX7V`_uu'e:g?}mbmx #h4<һ9QMq"K?UZjw7oOO)UȓTC?Mt$~{ܽN[Vp%+dihR|/Klo޽ҝ݅׈N"% s"UڈB= k$RsjfߕXcj̴4 NcǮ$MiԹ:ޞLD(14z~TAwz#7$]nSYLwjf(CNKYXk;ЍMYKQ!}"Rak^)I XQtxgދswg0i0/kSHTOlߐ lܸlcfgnvq4Cpx&V&v'~;_ & > =)š_0a`.qoh2hKkQrb6=JZZ$/.'=5䠧iGiwB/WT\^*d4rrGG`~4/swG3>t}^ ]HM @@ T\$UmXҍ$iI,9`4>i0T]GTIMGyPsygq vny=Μ8CMMRla* )󳐒W-:Sjhp>ENw ǾqDvwtytOab^:5x8zAwa7O-Ysgf֮/I*v>pqP7*V!Q0../r o@n>VQNC`3I$e|)NCʬ$s-~yjrR[c(![M=;ȾH09փ?Iए6M@EudVmM56\xac<.jֳ@m})xIA*@   $ 155ػ+4~S(CchQ㍪ɭEe5yv^Fɘ+uf×ǐF"o("js]L61Y(Oۋ%V>2olwDP`*a""5+BǢwKBÅ3y]nCB( Eyj{Stå>,6,ۄIcѝ0;7R ζМfx#w9\OjVެ7^8'.;JÞt>WF-KPK׿I??~*"E=|ғgL$ŞwWW;&iG7a+/ϒt?z6W#55KF07{{lMa=pMU@@ m1IC9s#=G K ]6t4B[ IK)2AFvh|M5.7 p]txRm[U9v v.ysoepS`{n8 ʔvb7W}pde 8ZI^:ջz"mfl]7;ʢB)91i_aq r_aGqUv栍Acv*yu[@)d#ٳ n6FIyreD:ͼW#.+RK~EW!wçQ_; DVC5~y743Z&Q=2D;ltj S0Nw ԾNI'%4E!f@@ hH *0U@S#z-[[6g…]C*W-{>=GP>|y,^T_#삼«?sAוC%W!tLƛSNc8mJ{3x/R˜Kan8|!<oƐk;(ǃPU~wIOtu3ex큷q~ T\(@t9!٥ZhnPo4%ړhσYj;$ u:d"2F` !`ЏY =6{Nnb˘!8q/y\gVm8>NkT/l;xFr =9u\j݃h[sFD|rI m#""waCf]w7'sT@@ t\oZgS9C+zVU늃?V{UvW :2ڷt:0ydBc3&i\L+zE9r-wKIۧs˯vwѳW8b",>wJd؞wv(^ކۉPiTo^tIOvr&BLJD2dWTaPm #uMT` tu!6w(t!ƥH-W,#6A ?=yx9cqmN &a=➡$5e㥤wHHm6@؜nlYX,n jw{7D@@ IgjRZro`C\-oGT˚-39O>ײ΢UA~MW#~ΈK%|Bѽg?;-wUw攄mG ["_ AQgp2Ȭ̉95ze(,QQׂq=f)tH CA,Vu4"}0*\:<#\-v;SONtƝ=t3ߠu @@ Y0K-;Ebhh M\qHKEmE8s׊xwoPDދ$cu+|>R#N"iET @uLEN]4F֮ t#q6I:Zב6GGG;)ц@B 33SoK??\_6L@@ 7"gtPDHQM.^'~Fd}뿺J,Wת~~ f@@ p0 2H'mDH6knNI2EVגqe2.M(1Q>7/>HK??r}N#i,sՠjCJ5dFV䁈@@ &^HRRI&yz$ 4f*HMZbՙ &H f̦lyQC̙3G]regMe.$ŞwrsG7aQgp@@ nyU{v4?~Yd!nXBl8v/|~ل. z"IK]? [:a%<>Z |dr3?+wyGrNxΝ &e\KN?CRCN"i^ʖqGyb"*@@ @hFJ"jC- .č)U&ʌ̞T&Oց_J>?hЦgf !gTSH\1&X |$nC d)CJJ,ב[RZ @}azVk&:Dxj$bf ?DИ(Il͉#~i;&?W}H]rdž xpеkW~ Q{R-Jݺ=m/S)Nezn(n4~_22M&(Ç{ZbtR,YL#|4I4e".@@ XJ&&!SZro`ȗN媤r\Zjƞ`4IV'b6⃨ƻzrOꟈ"{$/_ g~OOZ c7j(R>}F"#Qz oĎEƦ;cRwS4y,L68Jr\ |n!{u??<9.Ku:]keLNsV]$ @@ 10kH$iLPq1Iq鉩YqHKG΍]FʡFMVס;_N s;\ې)u5n nM=wT%Ç2*kW`*Z <<<(fd{Y|YX"Ϗ0(,A ?tM\_ςH@@ %fm4PUBvitMۥIq"iJN&d6iȫB+Lj]ϋ4U<E$; )؁O|}'we&0Iev*RCgޔ%TTzL<Gf" Or ֍31ZQ]gVe85CgsR)"[o"T.xdP !HV[XRq0?pH%P[*$VW39Z^Ci _:F"7h"IQ-A~0"+ %&x{#~?%)[jPw?y$@p7wT_-ܳXX _(G߁Pr[VP#vBń#~gs{f2:&F,H] %@҃`aݲc0yQ>̘b;g^ǡ[x`t_4d+SctI\+B0bl' IDAT$ DT t -Ei2NB݈BottRu$[jM9l{2le\WznlJTg8V{ b{OK@ xExi>y@t5ד6f1}ĹWݷGij\޷W>/$/D<>7?7yϪ|} z :8F[Vc*x^{4ԗ6o-+oFC -fES3ZeG0= 0 +&y(5ۢ8IKcwd34DbfbٶLqC9rkg[ࢬ`@d9,A&vKRݵ]tU3/HlmAbRPP!eAyyafZ^3=}rys U( vPKp4wvGXk(;~ 1c Na:p`h4w6Wn/.MbwD2왆laT@ޫ;1g8*x9G@D42lQ &NpҘǮl&鱲==h)}\ZY=ϴ*Ctvp$mgTOd\DںbsH{l*h_J #Y7RL'%<#'~D >?f޿7ndo٦χuܺ_-YACqV&AeYCX8rQ:|[-SGUF,Z6eJl&U70z[Tڀ$ JwjMĮ-;P/A „m;(/BHL}M֨iUP\DQ(N!x:\X98:x9G!H6q : kF ֶ kؒ€bݍV7~:'9:`M6eWs4e}{ȭ`(gбmrnJ1ğc#9 a}iGosi 8 b?ǙV} $44J=}5poW4^}Sm^ ݈_B$Y4+/ 4U yi'HuՑ"uPG2uK(xIIO`aR+;q| Q-GAZ<_VVXV]uʊQN\(FEk 1 Dxt4bH$RWb[VVHʭZK׻q7$h9!Y2I_Mc ~>%V>oQ'Df߽Oib1 ը*(YSzW*Q~j X1.4y_dmOS>AqMea!K ǥ_4j!"?;@ ap3ROC#p:#b-Z`!"Que#SiR4y19>=!KLe՞{ʫ [:Oe5\>&XAmDk늑8ge$BLD_"ƸysNB9j?\EHƟ^8O-"ҫ> 3O4'3d! u2lŃGdb(5/a? \ :s;(9㳱-d\S0*e>>M5meEMjy*ʿz`Z&+Tct-`!b^LqCib۠%:"l@x'\ZD/\[ElBiSQj5 wsW`_Q0&=Ndkvp߳ωfozw'J{d'W~v"I fۻa<ܿe+Hz6^xA>hK#Qp"I( 9ԥQDXq#0Ӹ6p8!`E3+ҤYF=MUlackC4hcڴʓi_<842ơbًGct"4VaPC7Wcd5 ؄fl oDV̅bٗ !l&V:EAEX y1 ʉyOgN!ii`6 n@eij Ř=%Bd >5+qthjqTZYJ*Ժ5~#K11DK hp5 Ƀ I+!%(_IH(&ɾ*:ji=󐯝1SN7?I իM [O?:6z'gR]_|> H>ŸW'M_炚a=(LL|;= .EpsO㓠p?}>l\/z@<s9p7Vh? G#tE#'̄!o6^}j#q6(:Hs{8v#k.ڳ+jg&dUh'CȮRydCX YFӱvmbmp5#hR@H)&5<HE0n$tl uDl@syV}d/VzQ|р@, ysP=g鋾~{P/z Z 7qGOY[8|۾FTx<6tPD}=GciDť jȄ(|%`ŒicoD- yZzJ򛅵T&jQ{EcBªXq@8 %[p8N!@;z55#HQA*4I]F?Zt-Lݷo_O0m':9cKDM"i׮]Ù3gZt4EN$罍8D:h$#L$-HÉS!)hȚ4pU(gw*D(BHBNTf݋!52(D k0t4 m6k`<Ew9kIԴhڽ7i׮IXT 0OM{M:o@J-lr`p25J {:v?B[֩E_ L& >[di/X6y O`ޑWoΆ[d}€3 5t8G%1"e1Ӣi "o۳fVKx GW@.^!sttիWkrрy8Yw*B0?t%x8G"45cB+HmƼ;*Ac8:Z(##M-]y#&D999 q1R&4fuB k`[gv,98J׭\W LeM@JTn6[]E=yqi pMPڗ!u]D8;=GÕ,Š馕 zYS(.}aȣڍVȖ 1?> "|TO(?V j}vMNN ۹,G8Eb@L\8*fW,\cOvMvaR hT,FBMg{I;Bc٤GM"LFL].x.u*TOn7֎`Vٿ( Wc| z%IDRRR7A2sHFRg8p ?Y}}46"'rq)׍GP؛%hb][6>߳/ww|.b`"ʏmNgõ6C1G*-lF{7۷q{=gzV eJSA ʊJuvI0]J/tPZxu 3|Qc ޱ4 yEv~_MۆF{wAp8 '4it A46d4:bZ"l/.#^\R`!h4pE\| =Ԩ.ʢiߚm"7 ђv'|!r*gnA>nq;E6YH9-:{i.3KfXLs#h bfJ"f"<6$۴h1QA5X6_84ic>iYͲ3̓^&GS%ȋMAc-=.&BDWBpo1ZB(DH| TH"˂Xl9&+ 4@qq1uɵm$*<#p8Ә#klk4{z(MIc5̵2`qу1X:H9*~R ~aPo'rE<'!:JR#c1$Hү5JcqDWr-řЏnc105IEd7ҕ?5Z҂k1\O|{Gayo8Nb܍H@Ï{:lUjaOMxnF(YǃmQZu[G-މ5SCL[+.¥[PBP1խ191wu`ä h]c1v5O Ҷo.o18I 4|G#f!cFҠ5@Kv>f4Fޘ9$Ө]GT-H2,Fe?|}uy4>5nԗOSPöbUQZ$-Gy2MA_r:2JqR^qeb{Kt:j (TCLH{=9;9݀{șw83jl,J7.AwoJ$qT[|OS F0& w "#9 S6WMG<'':naLKK=MX\8KIyO$장w$\/9Nhz;S$nhN ۆk#ӱ& 4q"G"??3L8ȔVh w=o ֍Cpn5?eNqeZhDWV>8'CF!!2HfnifLNa<$~[/t)ӽ=R%/?b֭HJJ´i \Uu9$a]ƙԧy6\sӺͶuxs7v&m{X+2kهy,ˢ14@a&8BwƩHނA 9L! 9k-Z?⢭L?-kÈ93pTU^&eɢMY/mGaX(&Tc ;'GUF,Laʔ[eG;` #6#!2 Lmk6/S9SWK` Qkx]dl &bVS:fw9hȤpxozG#p#2%F' Y:Va [Ү)hލ2o Z*!I9xv/w` u8˂gt^DD8iUsPxAajR6seM=Iɰ *-O-6{8i0:QA`J߿#|ܱ@;jvaBNFh+N%aKZLw & ł|BϹ9 y\H6]I1p{yaQM> L1 Cw*$螘#`5Q`^X&$l77FYP]iPxLN$t.NPȗ1VF$ y9S(u&5| IDAT'<>1b/=WHߦSh.++F9 reeji5K^ U֞(ni^k2+ލXMm^ kD4EM ~ZL$HƀЖbE؟ շ0I~/b6>, 6 C+PNo(^RR&5Xʎ gL$ǂxR:˳ Ċ"X|FZӁ1&-ue9Xܣ[!}2:=УO?w1G#p"|v6 H>P]3f ySWv2'Car1bяxͣ1-=wNY;Z4t޽5d cfL-n UQ7bAd_}}[TTxLF5j@M{b2 kjDb!sBKi&dQ VF="*H:>Gj^(}7A9-ˤz͟?l%&UHM݃*rظ˧ێ`pN HՋDQ)UO6$ѸkPfW11FᮺHċ]ȫիWqAbEm:}h j!eF,*=,L j0Vb1qeQJxx{;׫$3!ga&TQȈLGU8Y4ͳ,'CFX!rhXںbdfh2qNDʄK_Ĺ Ʒ49^[Fo ɭi{$w4Ps{xc4=eQ𦸏8Ip2e1 ώڏ¼8l< '8Xތ7 K K>MoO/ˤ7 "pSCq22SbR(w9!碎Sd>;G#"iҬS`kg [=;J)ژ68yY˷%~㡇rq!GF`ce9 TtY 07o*Le'|۵Hdipo Ko]q@e6VyB-#-+~Є⿲^`aJ2 ,QL#>}!)fL>94PRp_lőG#p @܌v#2:چ~ m=#q6ExlH霬SfPm݃:m–(dHؙ3g?\Ф1s???lW^`iI&ɑ9WG/:4@ yケ'E[Xhn)i&D038$`L0<\}}2IWaJ4Q6-S|{ZR[Q SLLkljl3- øf]l=z:$Om>+Ϧ [NrF׀SbOZ&^`}/to)Emyy4$)6an#T bIRzQ|рC, ysP=g5SϽYks`Zo1={|OG#p#@̆^DH&2R)H,Y_Jޓv"pr'kyoGf}9~AИ3;;;AVZZJ&{O?!"" uIv}k'+9 ibkr%Ի?W"[}!-4[E>lbN|uF5"=G#%4f4k"e42ӢYS,!mx09L#v%:(!c7Tw}o~'O D.55Up$ʙ ]IgKlT#%u5Z#+xViH\Lui̘1kp2K.C}<%j8Pv;u {#ex13f#Nm 2)큽 PMjOdx}ߟ"fF 4w#WP\Χ0{+**b[8:pq #<1.O9<OWFcCڣtGK;?aX+0v(1uFL V|EM?#9Ti)'kt"Fཌg4^,2MvCL܍j9Hm`ԔWG#{ q1Akf}ÊwF 41$i<8nj/ spG#9f̬{te裏"-8 h\]]M6E/jdY7\I;K&|}u+|s:{ w9a;qy~D+MHT3X0e+[Y[9k(-r-S9Ib"yGlRdplRzH8E3ļ8%&W L9GF52:4һ20QكQ,l) Gyym d[coY!E48Ux~~X>3 0IoV2_,e8;Ť5eo4c)MT[K]ߡ>([ĐǯG#p"H]#w%8 %;GUtYc28_zhH(%{1SGFкu&ęVmذa! 3d/Sjʕfz5m#o&dF]Ѷ P8H~U_G; yiTP1 o:e!x&*h:UJi-1e/Yjr.oO:^ z&Ǡ# i{P\#-4:ʞS yXj3(VAA^\mޙ)»;0Q漅G#h@V`g g=B ƨiwDEEaرj.sN8AZp#7߀iyQcNDgGI4Frssk.<]L3Y9ÜÇh Fneȭ`^iNAXE}g#ZGU'UQT65~rbC`h'K4$nRUȢLݼ@UM>KsAk=s8L-y<Q?U~Lyfᾞ]ַcZ$<׃fZJG{o^0~QeLFƘ)1#c ,-HyH36?8IWG#p8VLcF*F+rkvO/ q"iLFT^nmm)jΦ©g4Ԥc$\=LNVLn _y~{|%\Dok42Nvjj{2*7%b ||ВBY*g)Ob~u x˜>g;b-].FXKcq9a!WNht wU8G#X@Y#iδFҠ5@Kv>f4Fޤi[KY~f5vQu&ϓ[\| =P|k9&95< {dtpf9ykҪiNms3YNH=NLh4 u4gD =} OlH0-DŽ:QxqݫQ"F464n 'GknL'S0IC7CWOH}scK"e IKM2)ܺ]>W}cԣ/^;%9 9.] mB."Q-E((h~+(9])AScc1B,Wަ >tKwKd-aX0M,j#uPUx , ؔ91 q[#1l=2bOj-ز/s7ڈzі%̍p| @r.w!xf$D&ᾂi,lp8@h},s1SXTk :VA HF70}3l?ZkA2jܭ*S%iH#eeCDpCs =ܭ+o8h꾻o =(%m<%SpVk=^F nW gcZi]f2gB׺ seG`(_)ө ||1}15U?Sos}ntInQZ[{E4D,^41o\bvp )xm["H$cjd4htH^#:J 7/SVLWBږpĐ_ 0&$2UH$lYՋIMwɠ*pޗ}8I+$Iz7c2&HQ44=:@ ک82 ]7DZh6*4Yq4+1M>-<[c5Nџapkbb~/b6gX,(8G}0"F/+-*Q%HP]׹_tőmpMkg`so4B WkQuψXem0}H,$'b|KpٸrKެr&mw!|~9 ~==RG4iG0DA|̾_% {W E~>5ݽ5$wwErodCY 0yo~[Rقׇ`qD~iMZQr,gc[v!FpE,&>Fg 'p%/אˑ_UI%惾ZD%£eKm8j=J7Ӈ^nsGtmn¿8ωږW/Pֶ)وcdK SML<%萱pF-뇔ÅxJˏ-fy > ,qNTzMi9 eiLZL$2 %Qkjծ;#C[Di,~PDmۏ9'5 .ŢpW ű>t#*34bD[6 * RAA~p8D4fVIf?i6N!Ҙi6 /iurZ~}]-r7$8Tid{~^ZڽwQ=(&&t܅1.'LճϤB5w nN>G00]P}0Us ޞUo`hO'?>'|;#$QW_o#rdP gr>=dQs}r, =)"inLIe\F+H.Vo` Ak!3NdSڑPuxoI ,Ɔ)4iQ^!^.!cbX=Ο%`*^e;ҍSG|K79,LkTH2cJbQX<9U[4kHqqϙ\Ńi0cn&՘/# ;X?M"hyH)>1}[[V;0/Pj/^3" GބרPvDB }2nϭ`HM2\<.R8" X}<y6l\,A "W ıs9wOwonn ٸ\ YcQ7'_9G0-9ٷ&k:چ2m=#q6Exu@mYecvW*QKS#ìX.΢::\N7e*+BAz>2 ^+t@6kJ-A=!;g0kjC fQKy#i4+aQܘ<#/ XPϦv IDATQ֜YQw-#i t6Ӣ/H-8x+R˨]Bܔm%;i6}ٱ/ns4N0CP[x 5d,#h,hNE 038, щ,Мn7Fa_%#hœ*MŞ48<)4lA i2Z 9aڧydA2 S*=F{Bm#iJ6LYzqb,"mH7P-"6q#%Iϖm:C93VO͍ #0lqPbq..=RKG#Ы)DBFv O3"kiv'Ik7&*)eTY`OR`]rPih¥gx G4ݞ$K,1w NjfƤtYg(fɃ;Jer$_'ٗBANEA F_,mVդ) N/_[!lN%Gb9Q95·׆p]Bk=iBY潇H%&NNqJ ~hVEn8h'iyU‚G1'ۺQLW8d*% ưɜͰ>JH[Du\M*flOp\ eGܥږ2p8G6Y)&#$'i͚͘Dޘ3)g͖xPx/NL=TFΘL2ylMez*1Md$ L!Bq8$mȑ7%J,1E%eE8&-47"*Д u݉;O ȭ, n-܁jP@VXPA;XI;4fl"C@qMB쟠 ZE1DG".K-ec(,= eR߫Qf$ 01w0YxD-͎]S~_ݎ# aDng$r8@{&yDhdM{6cf bf6DDPhkgIZ!Z#JLoO&S]*Fƚ%Y8K1PC!9Oܓ&rőդQ`z:F{@}զۦ#Q"pǂ`6}+0G(kVQM弰@B͆L!Ð] H_NBiٵQ|plRʧbwrjoAy\}|u]DѴl͎e@0PqlR bdjZwoZ>Kyr<p8Gi 6cCl153œtE{B rզY\1:vEvzӨPMb흜$ي4UuQ%{F^22qU5i\lt"#ETs^;X횽\ >*">=J"5nB+PquxˁXHѩ*PZN(]]%{pT;Mcٚ{uRA}u\NSi{\!ga<#pL#PM..crr2 8ZAMgpwskG:d|@?j2ٿ$QQQ;v,L7"K5's 6LVG9">%%%0asG@Y9|ޞ;WIvLݻ7G2<_Z&uMADݐHYN.d"g&tTqpTW k9OJiZk.2B/S)(ҽ|6[Q3U|ZE 5¡ Q8CZA KcWf]K=[sc!\MuGIez෉C0>Zk.cG#Y=!56ڠAGqagU/a͞؋GA@E3^jm,'imAB_tO~Yt9yi65w^1 j4w/Z܏jr Ю`фI_xL9G#p'#ȘAnudH\M8Ј1gaׅr=g| >S!22/"""BrsR=~piZ{پS:Ӥ 3.5Ȍ`ykpmAOmc1MA-8G#tړv t AљKth3 e4-%k M4' %%E0v\X&=Va{5}O$y$"<\ak@7oܛ ș\k&]N.?+o\);.ikoor!~~< 3AڊSkt(*@AAɛ4E1?6. m<wCW#Xh]Ղ,싏GzR*L.W!?-$sɼd{}UxTvVuȥS$g1jJ2v[6D{v|"9YhtpJ436F{Ҙ ֔NX׶Ic}fW/^$MEr>^k )d}f"VkyLǘF2mԢjm:6ǤTn c8ш`XgXZ*g)#QNXQ#=.>& o FRQWO*nZ;-XN2֠.Ǟr:8#C=6RKεg,wEoMZ%6ٌduASo,JS3d5>y!/b֬Yk+p&=Vs7_c*/h7ڦ{X[WN8T|$|֬Ŏv+0v*Gb9pϽW=""Q4"i&n4v0|p(Jhw~Lm2=8v8r/^HO3=aE<(7UlA ! GLL4cȻ11"Wۂ-" pR=;K; V3?|>vms8F8kC(kB8)MQ*@Ťk3˛2ÇmYM_mg-aX0gU#%K֢u沽A*AeR1CI]#^֢yԒK 4Z4\ĶHļX.Ug!"t*ȯD[luz Z;MA[WDoe ?^Е$=N?if(7 x~F`1ѷiNLx:ևHF(WM;"ʅuo T`L,TiOunD:=EC47&Pǒ|hٜI.=)%4u'ɘ?5u]I#'<>D:M?Фŋ>JE V$/#@a#zarj\W1Ր@l*1߼LY1rD_l1+2#0&9I++GAr l1:|,S1H<1e~|T/Z'56Ӝ(U*$ $8P&hnh1},'3]ƫ=\(UT]dNk( ɬ=I2?}_0 lzL>)M?2Y'-Ŋh]-;~>G_,ac(! nF$f ;GG1zYڐhэl m1֮l(Ϯfr#=PL6zTWk'01qC# oљNu;a-^g_:`: EFr6RR\eTJ_ _:8<-.#[K]QyVK3gyBbhT\TR^$ ᮡK-ƂI&k̡7vX$g‰ 1qIwrirƴfLΝH{Z}mHNdl[ȶò_ಠwm㿴&(97BTT0{)wF B}8;=eUPMkG|Z)? 3G M6$Sx0)a^z n+*ñ\Hȉ|oD -?ѪO1R2 22qh aԓXzT/Eg BFd:5*-镊86Ҥ&-S}{r?-3{4*ak[>\ ei:RH#Vh[{[6V¥6cw &1~2a?#q(+-;3ƫՆKNEi4&[hA`<xG#p#`E3+k+X3F66{km`61'Ҿ3KWy5>y5/`WgC&Xb^b$%-B|iz]nX9#Y<JL<!x|`DŽ˄ۓv,I!h.""F5Yc2u4/pYj,+4⟢/mBX7+rdELu2m&W&J=԰>ڦt6a|L{)n⣕pro&MWoD\z*&…V(|C`\xk|2,kt#~T0^CromiE@KbEON@ծX!8z_aԩXY,HO΁o)ND}-ځQR1?{Ut@@@ P 5QL4ZZWZeW{V^[X>2|UME1M4@LD%ga΁Q3}c̙Z{PJlT:92;0zFuc<(B?+2:5f0 X%ZfcHg!h+FJq\wV+1;"1o39e(״^D:'ggC,Z|ڠä!|rF$#aJ R`#h0+'=>PQ}飦⤳OC'bfWS:#&s̷1-qq6$<36/JRSCXD(xt c!KfsHkGvh׮5<\-ztCD)C: ;2wtZR?UijR;:  A$M! PeUbiuhT3(6 Ad >+ӨT4C}1vfP?ۜ3Y1]KGЭl EڥkeWٸx=/WLpU+I۶mޤ$.h/l53[$r˫:^!dW _%kЗc8]@Mܚ0KUBvf?+298޸`^I*SS;?Fm-|^Otb1qbg ꏧhR~$ʺo'mi iG|A5ݶu\F]E~e˘x$g 1L 3:@9詄 jfj2D9[=ؚ>w7psyPpZH/D W8 ~5xmA_ ~>Kn򺖏NAT$YfM@ɦ#LH~pp,y-GqGB[ZCd,!!re/u$ٝV˱Us3]D3S2dzQy:`tQ!z ;lk1lK]!V+ڌBg*eWGK\:RT)mMyFB.Nӂ*nvm@d2<"4AQNFC]\`x']n^A?oṶ%]Q {gZIAfBhL0-pV]Ѫ13[oYq߄tFm]ƜHNlÏG-:#]L9%MM>,43)hl,jF֢u %xBW(8 IDAThLo_Bb:ӘbNcRة5/HmD*Lp^^&HV&l )Չ|T,Y'fr%f/m0II %M,[o-Q؄o~ 3 M~c(pX$Ggm[k~oƖӚ'5M|*ھ_1shӜ xA|ϔhh0[u )39B. ߐ1س$ Sտ 'by_2YfD`\\Þà11}-A.[.Hc3jIRq|aoLN+ufAm{`8A 5zד,`㗣_ 45u ,|f$Y|O!vB:Gbw[ ]}{n{* 5bp;fk}]Wed])Foe2aɮr|eǟC7+\9:q~p8 f-PF# q!?GfUsrfY!1 y6 ^Xգ G疒7V[7h !ZXWYszwˆp6:uQ3ժ(:k̛HR!)FCAx&:NrWO2D쁹N݃e*+W )0~P5)uϠ]iSX0/?R{My1fb Pr*TȕD#R3"l6Md{_`Ǻl wI0S#G#`ȣKooc\\FYjJʚyk>^'@u%.(9~C$2Z9uyּ#{E3bq4^H"LFj4:iwyЁiKt!)vpV^8nLu&M`ƒ嬇}0]jJSnD[Pt,N%b[PQWR]%oJ@{n[_p59٫gk,V}|-z/!fy#@47NW)J$6#E ?~TֆZڸLJ]M썱\kz5[T[E-0Ss8#Sn@ET0(O'PxYa6#70˙h5~!"Zժì´:y:]vMo}u%[h{<=EZ5&h_/hp+h^'g]%xɠ}5~@0S{ ށ#h(0eLXyFnLIa Hz`a%B;֔غ4u'G = 1&]r=So8U^عUꌠVд=4KwPvuNj5??fJu{p8 2#I #wI41K,lzR؄X .%G@)i͑YѬ_ \uJZuPRh-i *G#pjFVV3>* (\"=ҪJ.XU@*fQ j@]Gw /G#p8wȎz}7BO~d>3P"kJJy8SΤV3i08\I6ګ8{'ٹ AQu9%g$k:v-l?Jɀ3^'L(dLl_~×"QowӞZls28Xk"b[:.H^qVUEw) #.JqW$,+K/>9Pfl^GF92m֤!F! 2g*'WG(5[ۤ@{)y.C ?$?†"d堨V19.lTuˆBIH0%HJxwJ{ނRڴxe\Hߍ?-Xr:;.qHʱ˧7L6MR!SGu(b~Xb[R._ߡEir SG0s0.C8~֟UYXJ\)~2]֭Q@i.`ITHD&öˏ?v!D:x G#h$F6Iӳuh.)jz!ɒƼY!k%9ؘNLƴdžqMc$<$%sR17d<.g;DQSZ^q|>Z]ѫ\4hWYsМߓGd!\qǶ0{pk"=:VƒFS7?Ԏ86ym#x9{ؤVY%#l[W;ݨM8׷;lҘ`}UmsDˮ\&@O Bzk!HH-N24"Eͅ7wz*k䠙ܝOىh$ )'qq4x!ܞ;Cb1 ^Y!;ef}3Й[%;96` ,PL2=wǚh'USgOxs7WW.-WFt?- ܂\;5boLflG璷 }ht{‘[ 0wveJ4.lb.!߰~¤1eFRr8\4)~ݞk?d2IyAǔ^9gacgwf7y,PBBBG_1rR`IAqzpPS)ԕsGu21T'GG#аp"YҜFɥ TdNSѹ33C1N֢uwjt0h^MkpJz% 3/\AJl ٣_q?`G='l[򑟟(܁. V*nG82z3pŸ؁ٳ\{<ϱ58OSj'Uw>:tFiJ;-] %L^tA_Ydl99RnbD=wĝ0IeQAG!5RڪrP  BUY}t[0KZ)j3?z b\3L waeL[RlT;Vix, ~4G7t%JkbsgX`~_)ͯ%qn橑 X@ T|0700Q./ؔ/d-ϋeFxK/ѻTy/;cR转B5͚Sa\߃{pR؆9@F )#GRh#k9}#li8~HA+^^94A(@nړ $i>@w鷽Ts߬Yƭt(*8A#tdg1OBD_o[E d'vx&ņfVh#il*6a&>;Q|Re9YlYn-'#J/˩DSCQH@ܾwb#V*PdxԕkU/⸲GZ&)hm{7EؔVZTb%zQ&ANlÏG!#PFќI)sf2ƬhT< Y)xgr*و٨|&@5~^"fw8p&_ԞkCmѶ}pרg9X42&bN9@/ykVT1 *LӘ'0cAt8u<U_%k߾J։m<lB!S7 :&+I#.BȆ"d[4:ԫ}JZ hFJƫ~񮸈ujISädMc!A LCW`ID2LjD L9lj!V_Ƃh dbI;'[\-7:5붑qgf(ħyBeR|@n/<,؄Z϶PHC!A\D&C45*LۅKҾҼ+ڌBZMg[Cao&YCv=HjF ܙ y$D3dS>E\x$muVMmWy1k.TgEFvy~XhJu $yG#h8Ō i̙455͘43fN̢FmI\wtoy]QC,+*Tn!|{#u{'}|"F@?asr`k='/9^iїY`gx{Ze.8=xIؑbȎ4v2edd_9C…>F+`MeĂt"Kc"K@`NUV,~Rtb[g35^ K*Rwz4&KI[ѿ%*۞+?-0Ť&}"O?#,;͟I9Qz+!a Ɯp^&`)HՎތǒ Q2+ Ri}cpx-X.3LJ.@!04f u Eûv֘g`fx)~:"sp֣Mۄu_φHst,_T+.9Kt`f YLnX;SO)b$x#9;pz}~O v ([w,-l O?cȞխyG#А(#i4?6cC\ϑY՜GSo “2Ǥ/RB{t%>rڻw·ߚ;w†<- k?j<8u rƿƴP'q8?EA4!QqB>cL'}W@(|2Ԥ$U_3OxÛ5%}Õ~ʵ`0rHV):SN!<<~AUcU'/OlI(Զސu Spu'A?ixoWxQj̘1h֬raf4_h4 U8cL`6o:njqG;٢ʻ,յ%MO_ d%I5)Cb*)ݸFtJ) *w%~5#rxmKYq=в.L<qeF[QO[Y 3x5tiXn ϣ#C(U9G#02& Ltb b~fnc ^?EJ6VԹҢhNRx2! YjJa:XMIkpņض r mg7H 2wyф`psoTNUܚp8SƄg@6*ڱ֢UjC T7o}7 5ڣْo^eI+gd~xF؃~ rw #>^TɡTW?p\p8@Y֤݄TNd1&)iLac4=ijzwsZqߴi9[ p8m@YH#++sBFvN yI|ђPIpRK=l65쁈p8G#PB";edA3BO~d>3b4kJ$[\1|8G@n4/ӥRedqu)Zdf<(C]ĊO'6@bS[O8G#Cx)u1 aDTЗ9S9:ZRM>_&E9R&ÍÔ4*6Ğ%h9E|} IDATsTz:/iKɤ-Un!FC#p8@@&42wYŌEwdFoVƢ</"*kRҊe#HȺCq#::TA.)B)ײ7PRrn8G#T PFRpv< FִM `v Ț6nLNK7vik صgD@tYz<92&OcUwDIs]cG0mX0#xn@+V*=w nE7G#p8ljh*A_+C#ָƔ4ѠFյ9lĞ/=4MNANqvОǐqQ֞T((W䮺H^HFwR+u{5cP X-ս ƫGlY /tݺdq9H[)p8G2Nd1s"K3PpF.P"͍1C8bMZrr2ߏ/Vf\ҲeK<裈mTJ+Kpl=5&wFO֡*/ѡnx|2MIk\5DK= -N5lZ A-p=cPm]{^p8@}EY(>iq@keǙj~>)^.Uh9 7!s4 CJIvM5^ؓKV1Rlo.d&wg61['ğG0~ e3Z3Z{4.Ž]+W|ɍo"*.ܑf*&nߞ96NiSrbhM@P`('%QdOTsc)idG£J8NЁ.I ~\9WN#p8A&z*ᣂ45A!#G5T~&(kΔ&&\>** ^^^6Y9M*nP\zzt0(\]axr'u!ڶ{-,’cAeA؎\-.`F ]N/8Lꂍi: Gz-+˗]Oo@l¼ؕi|ގ,جJO҅x%vZZ- ޖ#p8@]#PFќI)c4&)i̊Ll3t [4 @lY}mRڶ? _$ 3Qr 7)O?O!C0&y@ hƴ2"g"{kpOPs)8I飅X|@gi9x,>+MLd֔g}"vD 3)lbN$!;LNQMܲd%ksPiш;pL }#IsWTEii!)jH$eyTZB_ODn: z6ڸ[<<'p8GvŌ i̙4558jTT QibkҪH+{1x#M윕6 FΣ_chT삑]Ԁ-wfhbV))3=<VBj栽uMtF56Yl0RĢ\+{FGlE[4oRҴ2eJډMX[t/fS<ϣ.[HA.u1<#p8@-PFe3s"=R9>&@ 4iZ`笼'bfVɵy;*.{NW=Ht MvЮ]kxÖ(Ss. ik2)7Gk ^G? B%ˬV;O;;k6tK>]hKXuxN8ReL\5QY.RҪ;ܭmW|r7>)= #p8; #SH1#/2LIet0M}naRnذaτ#;)tk(ӿB=l]gn$yr&0؅ÏBՅ7K*f r8G#("@ 쨧Mo4BO~d>3b4kJ(@ }nImLevO-@t~ yL:I G#yL#)<(QA_LHkJ6t(iu^_~GGHy{:̒f^xwJ{ނkn€CqX #p8G27ɧMlKc^hi+cQypҠ!b^eAJZ_ M-aEj+'j+3x0&6\n_w#p8z@Ѯj3mdM),DB-rZjlISopBʘhfUWd)i0_=q%7B{#=~?Qyɣ(@ |,!-zG#p8 lj<UVF BSHG9h&נsSDE:p=* `Vw4u>Kݘ,tçOD kG#p8@%bD4gQriTtLKƬiL&&}r{KKMm X5e(z4pJ;C"~w~U;E::)"ש9RRA@Y>52oϥ"$)=]T^sB*d9c/% ]|1%~6&/?TG氯vVBSԐ hFH;oWpڴlyp8G#YȐ&X͜ISSٌEwT"MEHSUѪ^}͗&7E(.M=J :uqLwXX^EOGb@58xz|/yh⁕xz3pŸ˂| t@# ZoZ65J%g/W(IR;:ַ<?g(/*9kB>Yݮ`}frz&%/dsh rqϥDM=K GnҼBJU[RZ`؄dbal l| Tz`Vx39G#p$h4?6cC\ȽY՜Y(F|"|4mZ,󇫹7?T2&OcUZ^ݶjȽV+ygG#pFıFbFx(%= ZRH_W?<<>ۄN=$gdE$S:bsH Ƿ>:B3XuxN1E/>V}1w2 ?x8!@HSlH,|߻1((PE:) (co݃SHmh>kpE9C`(*"ˤ\&V43=Hh/DQ&vG#p80+.3]& ̢Ff4#2=)k%ℚYj(kL)B@ v&6c0gH {1g,²) (4{2i1 wG?r8G#2#6%n:@{.f4=ijzh8ޕ5yNx6V{$ald@;+40iCN8067/>u9d18˦񿥠bmG d;?4DZ4 _TZ<<8DQj'!?i2r%}|xF}ҘMtw xt\:f#p8Gpb3ʜPbtuitN.Mȓ6l55iW3#FHcTtՐf>SScM{L]Sdz ;|Db#T)SDV)Yד|yϪ-a[f ~b.<ًT1vwVuoC{hG#p8A2ّ4޼iK|fh2uڴ!u@{vo}О+h>L9cV3 8פ]|'govbRK-2p.f ==ñ~}rSGX':#pk(HCYꖊdRz`zϣAZ{kUs7RA*>=դ!6+IGWcHwI,(ƷgcG[}[A:A[ȝ܉QA_LHkJ6)K|M J4o7Wi%-AaS|\PI[<ڽ&xsL'zT|ZWGYʯAY{⯡?>\g=~ncW :rwmq 7zƤUc_=Փ斷*_GU1׊t{kqǎ}W ^*%(Tq,90dd=%`Z,$?po_cX5QeԫgzM÷~ƈ00 J&47%ljLQ %›(HM6uN# kjMs%/X&]Жk,]ykRк0x9Ifׇғ6ʘ50k|X5F,Əky6Q4{dK4l48v-qw MI\+Jiǩw_G1KOJMY{wW.ƈޑ嬶(DSNL]\'g~}es2ȈvO1Y[ID"{tE[$3nԨoV!{?E.]sp8_AZΘ1IhL/#[JuldJSqtbpL~*M RvmG&Fzkp:E Wg@5R]Þ0$V~#k1~ 6 ˋ1KǃbGUi'JV~ܸxW '47 |Li&sQ rkLS&UҔj3xY r(_?aF_|}H';/iIvb+Z_w95 `KʆRF6eB]VmڏLJzX({b`t~O2cج܆irW}RZU$O'6'4ɽXˮ|.UJHegD4!Z5sKϵ1J%w?^lM+؃7kOY^c6$ťn%Bר~> T6r$=vb~ʥiч iP Z)l)oLI# *k k.D<VxjPtdz" IDATeo H ܣk7xYL:q0~O=_~m 𒗝SEt_.TO;&{/5-;z>.Y~H%\RcAٕYIv7fU}Sդh]=߮ ~+z?zSI)}Pr_l2ҲIͭsWݡFFa~!7jN>-?H{Bi'!'7i&n9AC4&đcO[r)؃"z-Z4@!ׯ_GǎVn9+( CL{T<ŷn5}wY4N f*~,wҺPxڀGn``[LX冀fc+/iҫleVDJG׶2]LRI*]']*u4nT7_Na,z>?F7,l[z #8J~M =L#O m sh_+8N@:;]Xf=%)jcc7Y"wGhFސ-W~]D7_FZO& 4ea&PM1a>$:rէFO?ק1}K7>;|xxMk'ژׁ@C`aרU3I"ì"ֿdZ)'("U(}k*+M6K|){b+]"g U>S(BG{ ,V׈X hOut}B,(ߏ7P7E ն\| YӋB]S S>(hgA{i^/뼒mJnMY}k$LUHJ){>77OZfzU˗OBцZGSE+Y|Uςle0J@daDOE2RhI!#G5YTgQ0+>iwbƮ?#b0r]mE\ذvw:*@ty>gWP :UPm oá` 邝~DpB[Nsd 1m(y(hk)o+@z4grunֶ8sV3RhP՗~+~[boq_9Oxf˚Irz 'E('cx!)Z,-z_ %N=ڳqħlMiaƮM E9^jK \& dZSiēS>&6ݷJnb&xRSڸ/3_Yv[=IJ8elq4w£A\>B߱I?{m^1NhU"uTFY` /[Z9Ռ2g#4fEsr5)ocYS\Qqecۑ@}a!#&>8"\`MaK6j6@2 1d\-C [6SΘEMt-!ӮTEii!KD{Zc)l#"?_kU+ة {R=dOb/81˰rJZld~9dExk2A?ꪖ ;hfc vx{[B1ڃt Oy{DܛQHH-`CG J.~Kgxh=(<~mJ/% cg6\?Vgݠ+1#+#+)T+2fl09Jܙzw{1| lGgl̋c)R)d`%y$QufLFNtOJxJ #{r{W䋽 8y&۔MD*/IB#پ~&h*єSYvkT璜 Ua]~ldd\tYr 7(rGI9QRɏ\סĘK`iD YNId%nИFW$ﳠ K|A##m԰˄I:SG5͘43 <"9bd|xWd?ib =d*&zZ[Ϙkהf~| O˘pXXOwRLGYҴg/Y g3|N4Vxdr|b&uƻnH\_ED%nK#? "?z?/߄Wfo1}z4:%#?~WȳWب3d׉CWa WL/uAkRطpDѻS4\:E2 G9ep>ӄ^BLd,-2fl$9\P>\K͗O[_Oe^jFDZʴ={qAin)?&k z4RE%+{@&x­}G|Y._GJkcf%1^d7lHݧpOL Xr?Lٖ}fjƐπIMucu+MLB jœ}H#<Ԛ >ܾl]-Baa7C#;Wyh(0/̖PJuvb&p7a$cI1AiO 1~ ꊺrZ9MJa5~%( ˟i5~Ua^ ėi܄U x.de[.4|%gud+NA+7?D+T%^d7e?هVIK }ªP(]iږ*xF_rg-:ee`q̲YU[eKF>kF宑dkOop˳HG~L{8MyHa^^i0} GP3Wrptl>_3OxÛ+4%֕fB qDEEa$Eͥ~z̩EUe/0(( J&V4 JR)s]鮦kg~-J%TRhaLR0 s̃7ü=̼sϹR|РՅ i&cFӧ[߻Oi)8u]o;~ La]a߰fҘƒ4́"-8bDexQ0׿b2K"dw'v.Ph1 {+RBھL?RKĎOԇ6K4Bi g4$!j|FcӈVY{E?5Pϥ@4.VJL%ijjr2?UIzc?UaOeiYg'ND.]@͑ deQ\YghPfvnBL<Q̕4;x7l ȩ}tC)8C/ꬊEBھXn2)|v0YY9DE sXEPTuY{.# G@VMg }\2 )h9YƮv NNiM Bw])`СC(hC>a(*PkIIAx12!0Qػ)HU MK2H/LqHyc&1~zaH\6=Y?C'a f"ݽ_}V)ϱt2;K-YMً˩6cA@jE3jf75x@*\Ì~}nQ~eZ FN##FeK867#;޵]p`ո/Co1eLXyFnLFaHђ.\걺:bk,4L$sE巖<'?#50h[k&HdXPIga߂2mlkg]WR[gX;j[IϮugH |t#rӓ& ŏ̒  gSklQLI6ՊDG舛H^.@0IdhmKe#;FZUF8|ź[POkҮԱkdA)iL9c4 Y4 cis;g}ؖ]֒w /vXGR£%@z5֢Wތ4?UСdi*QtqsB= %)ilVkɃV})Š̭q/:v܉x.b4yEt`tdGbNhOM -2Y `aZ#RHlLCJpr1@ &arqNnKbJ=I[t.As8G#80id =VK37HꗖS, ,\ :l #vf7(a +~\1t,1ȟ03j> ZS{I1*w|^sT,"320xF2(9sGxy"2b}^tP\*,/`h>%Y76є@KgXTB3>% 80C@j6vwقp8GF @ \]|h ƵiW뮯0Nyܸq3@}P#` R }"ACNSYZCBsNR_4Ngf2FLڛ駱w]/zUAEIt PSE}aPe,&kkơA* usBi ."סSBTKFGT4U83NXpuۀ=I3H BSպ˥8SPPxtŊ@wrC>+\#&ZJFE{j#SV $=us,U8!3£|K'RAN .D &g.êO]}SN޷nGǿO1ѵCʢoK(֜gF/9ͅ{c4!B'h)\|?ؙ]BϿC~v'֯/>Qk6ե}$b±!)F-.;O=Yo_vۂ9ceہ_'2/6 G15vڷopK}<R&~1sxjlw[G"p|KCuiLQ%)md,&2|% ~i ᚃ,*b7t ^u%[5%E> 0Vn@mgҠ!bVe>_o,C;q{쵍8s-zyćߖ᱿ۡ+^[!)ijwg3O !{6Q=9APlCز-bQX+cLei#(WG5vɴcV]>ٰaHH| OWQL,,7Hg1B^NrIf\$7v8loBbD2̟ d74~w_B6 p86@#h޲Za#k G8i=}]3d,QkXikFSٍGC.s㥿5z*c4ʚ%b55*hsO'({C-2 HA.#m^AfԚzڪ20XOPFDZMXdSF_Mn,e>wЗ9 h2cEd[LiKbP#!j J6FgTB*hBi8|I(?9 y(܀}y691?'G#p 1:j$k h8Ɣ4eDe&wƇ`ʘOآǒ&"+?&Go*T[sJGG!%Zڥ~t65Q02 .AOq'?=Uth؋h} ,,Ǫ0}iD.\Eӻ6A*f^ J,XGBڤeY6ވ%N6Y!AZ/PK@$4ѲR$҂~XWLФ,Ev,پ ZzYz|#Obg'mo$p8m9P}G~q]Ig-ept0%I߿Zm]!6. ѱ=^֜:mÃBScwwG]Ab")) <}-< ?EKS0D޷05\|3S4 SuWM֏AGMt\AE<-M a&de^UߏOdQ3Ʀ@[x=lݏ?j+=> Pw7sz"nKŭ-Jz1L0CyIgK)5L e%Uӳ)0s&1%KZghy j΃k̍]ɽ^-su((YJ]q9@E^h Ռj:dBcQI?#eɍ6{G` :46\cwG[ žw6pX&QИ ^ox[SqZ:څDRrCR`(34; r0gmJZѽ5hӈ쥇Pb(b{ gn cP_d obg&nRi˾髈2m,FM .mc7-`7/-0wM0|z䐵q}nuTf'3ƌR죤*b g^ݮ[|$W3T{1%qCW?7gI&^kPc+&Z:c(:66Fu%G#9hh9l7[Ƭf̊H Rޜɒ֬9S'Gm!`}V]=p'j+hwQ[G ܢ|]?Ua`J!=A*)}0bX.@e"" 2.^;lid,q1ӬZڬP eoB˕PB &33N̅n5Sc1 L o Qei&|F{%JPs3D/yBzA,?xcq ak01I -^\qxO(7 Abd6. ݕ*)hG\=2Zih +ҠyBZW5`Fcl0 K \@Vc2`#UX6H5ߋn&&SL0,'0SUfpnbF`5si 217GifN8fQ:Rbk,<} -ޔݸh4'r\}oh2uouiL(i!9vKbJkJ_Q~Qwx^ŘlӆI *掤G4d1o_4:S3gWD%#C{1 ({al"i\fZɢI6?ʘ○Wx^63{Yn""`".a~pv0:7ƗFi\#-> 3i !\ҚomdG2p)ksc6"ج\<<&4Z|옪*ULawwL]I-b}r1ogRЎ{f$_|R=HIޝg_o,s^__ފWJӛt+{,{,`iQ,zr}_׻;W.2Ώw5ךq8@ E[׭0ęH,k Cڢ[H>Yg`֑CT;:==q^gIyxʰ 990ljg<⋁cGNDFdq׵3ACĴʚ)T QjzS2~-WB>kdZ.E@UUU*(\iwy%' -*Zt1mp/FoCybc~Ež6Fk4/e;} fG5WX]w/f gzySI0$lL4d. ZXHcPfM흵MO=Mh-MVU .-2EZv#Ӗ_ˤLc>p _fCcۛ}Ldrep!~Y1Oɱ3%RJmSXW.''x7+#3%^3P¦%::|1˙-3C c{ؗ8Vq \&Dfy,PU&O`jN:4 MS+u&<<IҠdAcp5-3Qōt[o.b<-`H  X]𐗋ъxPz;q,21T'/n!*/:nnnp 1{R^Vb伍ٯ5h#>uQ~uC`O ?u>ƽ;.cjP+o7STlQLŕ4S܌y / *7NnȖMcw":Os(9яU?J2G#6hh5idIckdA)iL9c4Joc54AUIASV wu,F>zAصHRwx4AvG{r3HLҌB"g'׍G: ubLJoOC+#2Y]p -hU\@ /Va*r^"bÄ|\.՘f}nrc89G#f1#\9]ӡSS(_"wCO"H6ol<_syg :!Z6'd& DUsd)z`";9k#ȑ''+i|J9G#p8#H!c5wZ=4XG3--Fcʛ6*mwVcǎ 3]RWWo<<>'a3yLp6Ӭԥ]ם5n LQ}2an3n"\kZs>'3cB७96BcͥO \UN£G%:AdnoL$BGuku >ib)^[sVw'!}N&FS;$MEt XQz(w kE~r";^rFTV|SGlhx#H_aoaqJ̮ / p]$b0E[C~whL}R::|[caJ,/eg1[QnWYG@91m)^CcaDip|ru:M:bK{YfMʯ>>:Bvf7\cnB(& Oոg&Jmc@USpD,ح ]Ti=[5EA= Ljix4`q[ 1+4肘4 7*|uF<=}1phĺOXQ?'|U{&@7 H2i_e?z =ˑoPX7fW]BUd-2ZUy~ EM}!X⧯գSb*p.4<0݌ & VlbFm6 g)$Éaa1e1.#s< s Ȫ6 Gb.w&.fLA* =83y蟱ij8' om~h4-8<8ްc1xf@ ~#qgt-b3Oq8!HN`VZɴp&뚂S5QTwyI.FZ"xK6|euaz(a\[6*3^=;ހ>Iw0$#K1:}f<r%'G#0Sp G'A_ARԜIycJtvvz3%YѤ{|8m -?X0a:z N6}Nxuf[XmI+HC߱JYTTlN74 Fwp67ˢǔ5R AсY9:OaF`?/8Q²`y NgӶڹ8E ͕"edZI"> N*c?\sl'{R/Yȿ4E݉ɴ'vv .zxv@VGxt֏ 0_&?V%rG5&#p.O0:|6,8]Gfb߇-|g*8ۋX/'\hfMyf.R{1?7-#kS&Oq89%͑ijDDNtH?ƬiLR[[&9fBf 3FRS-Ѯ+V5<S%bX4%ISaO@9~ *WWnR:"/gw/TU $ y1(u2o,:%fc|DOڴZpOYu }'t'VLĿTmSU+)@vpfyb]- o/ G>3S.T! $ۆ&#^0+w$Gډ!5t6I8 `G#zf}짤bpcz~7 jI^!M)L5rSi]KɎ<Hq~!Ŏv4dz=WƎ&a8PiZD4IVyYRY9*B̊c:+"f,l da@kD\LdPߨӓNI'1'5MGEMˣ(Ιy9Ҍ(T9{PY_Qp}4&{Gᾳ4'G#09@t dBsBHY_{Ŷ&xGE@\hhˠ睍(4F Z`W3\%Fhݗ!Y*hTιSϚB`Mit#;'f)ZԸ@l93z iQ4+'Yw\ɷ.h90e!C^fرՖHyZ"%y:߮DQCG:MI v̜"?6%fy*S&ۦmxn2VwcJ~I؊gɘ'p8 ͑2Gg.jƬh ͙,iV€#p" &<.>mU1P<): uuJ\!#UoN*-@7ԒĴX4r ZT OH`,Űx1ydYS LP[r"M 2_TuauIWz2''g'b\TN]dT_Ҫ&4IZn /c޶8Cg'3DNс--EHTYb_ic^ ?<̽Efv, &-ԣD>Ϭy9xO8UA2B}P|"^t3V q8 .&XimfQA)h΁fY)5iR4x# Uʤi[乿@'[ i3xoI3yC-iQ=HQ޺I \sdB7ƅsy0RJey]YXx0뎚 {PLI o(\s*܃y쫓,_;B_02/1@y_hDf^hdc֜AzrI'AڜHz/C80TO IDATfhefC''}~U4wWx1| (ϽOW3c/c6/p8' 4k<0ęU͑eMA?,x'Gm! Rk}6n^Y^CתRjw]͊*# !!뮔Ud t"MחSI/K8n#&i4;73#U{Ѱ*nh*ҕ6)v\[UM}oi3JaY? @Y%y(\%s2)ҷe!x8t7?TUɥ/rZ%. !\o+gS 9S'ND.]@<}mAjELgrN@Y~b_LɱmRAcch)ϖK^Act8 /րcqf\nWѵM ͫI'kk5 RJ繂f5p"G#p"PԳkB8v kR'W5BY>iqZ@ gf>g36;u1c|Õ^£pE_dN;9H`]P} (;#Ǭ3?\=seP}'_a \`F"AE!3Ō+iP! -[} Ǡ\Ak1NVqp8khh5idI31 HIc 05 a Qt:-&\8BBô Oi[;4qH[EYO/ r16@| Sa( d vڦ!݅EX\dgL׳[bMD"}R^vS8FeyUcz͑4?8D\IrnI/p8GfŌ4\wD=CKyB¦lϔ9KGc8@4RY'ioY ]fZos,ʡqOd+Xȭ{>eVL3```wpIS$H,}_we1t_(գn^A6&ݣ7KxC_mRN%Kb?]cs̕s1)0i$(M]GGА߰f-ﯢ/(뤷T13vwWlAD]nI3 p8Gu";jHF4 9֑LKј!E~uv~' E󁲺=ƘBSwd~c&Eج]`CfcNPq>~g ,YtUPζFR)YJP͂hSh.@G 0 r-X$ח/qH,io /FCiJ3XZ$IzVe)S$wF9 UegqJA IFv9u(sqjkfb% && iMlc7%r)NdBΝM6Jkuf>֑2Q-i@e~>VB-3qhoZ[DA~1bڪb6 qx"Z_Ɉ CHR[EwDl9@Fcڧx!qYGʧezIc5fN_&Lr\h;|hwL4>O>d纋 O,4 Qq8}4fF[- #N$c7oo,:})zbiC%ǺⰮ\Tm$4;u(CE9fGs̕/@d| L1 gnYٛH>$R*)"+HIV4mb%M;KhT_+kMΖze1Er"XClE/cjd싵qV}7M}ʩ4} k8,Ǧ՛P-Bas߰϶`Θ{9q8\#Fa)j!ΒƦYv;`Ɛm|fE̅IF.c108f-Ee^ \6u126@@GRD<1ݒhU"2ea)z6" u_厡ad\>OOC{⛋KÄM"cEWړCKNűR o }{E"K @.{hDQf02R YAGڽ7ֿU{Ji[5(io,C|DD@XX̧גV4ttbkP̢ 0*n!^]OaphBf~&,^]< /}{uf?LwLݮnP"jɮ~0]-iA?^,‚q3 A.fʋ8G@@#9:ZP8FOigfYn{z4VVcaiR=Ucӄ WØw8J g|DQ̘W .Ae` (CLa9iX/l_ڟ0g>zFK>m%>!A72{K^w"y,=ٴzf#Pr%g9pdcHgW!1vo0 ʤTק1EMT֬d#LX[L='/ j:AFY7YHOdJHMf%(;W[܌XdtGh3΂ou]&>yD.BAJфl ?5^w*a7[#M? /^cUː$Fb?3F3 " 9_ITk).zWRTp(mǢZ?u>+ech:O_`Eznfd1 /p87@N@ ɑƔ465De3~u4iK.FdJ lo؇F2݇NEꯩ<-DͼC.QbW~J0tN455xJBY20^ew KAPo]ו+,W1S}ٲ5w&~,$lG͕5u9#g܌r8vC݋r#N,Hɫ4Lu$SDE8p-?X0a:zz+37ض\v,i.JY8UR^% &h7*zkP]A:>pLEXQ<:Ioդߨ8d2!mLOL| yT=c0 GS.FNW? :#+kPt`fҌQQX\Sxk(JҲ`y Ng+C._aSxc"wY$( d̴GS !؏@;ơMx%,Vl-`^&-'G#{#@392Mȹ3ȜD׎ m̚8)5iR4v򆷷7܍L]]L fF!K5^W:YR }1r>.AH,Y5ݽ!J6Kg#Sdςٹ;bnu٢b.dydž=˱*"Teb, }ir7H :E dQ=piE{ unCxP̤VG^\d]R۾O6v wFq=G!@qjRNcՄ@Ƭ`5R~i„v1/!;\d f,iQ?t $6c6S+Zd],67\Ǽٴmz5tXܴqPp#̊FAɛ6v6'vMs{$ t(g0{$&܌݃tVΔ4O"?Q͝Z\W˷}t,feا2Ú7ՍRtD)d؂ P:0%cVj9NJ'cgcd sCg5Ds bV)hWp ~, ])l n a~禥ґ!HQ-sѫ$ Ɠ~99aԐhcLAcD{P$<Hq~y5W)Eͪ[$4WӔ%YZ)ircp ’,@?G#{#@/zNt4jFF5BFf399 AIYo}:d͚3c,i{pZT1kgNJ {وOcFK*ZB[h}#-ilϦWași&Z9<mc7-`7/z}~28>گWgsc [*2#-xR:+`mlo (H9R H.L"諿I|ci,FPKG#p]hh9RGȬf̊H RXD@ lx]9`.X죤q"o>}'j+*gۻw[2IQ|c-iEG|cqFI6֠Ҫwx1~dO1gm=(vîYKN7c X]}D$Y@@[)q9k/k%{N\נ٢`\I3 y̒Ɖ#p8=hh5iPOg\c4- (l̒FۚBC 8H>Yg`֑CTMsR [Q+Q~SS\IZy&ʉdȩpjԀ=" 3]I [SщcGNr ;}/+TI[-ɔ48HY3fY:ܒf^p8@p`32548j#5ZHIz4dϔ9KaK)Zgwʂ+ٶYLRg܂LfQS[(lZ]8 vѺcG~>HmF)5'ePlEyDP.*Gyѷu򹜂)\5U8t2._lف,p~u퀱pRѨKr!Yƈp8GFH!c5|hLyצ]/. pyOX1trā{͢'~F_;bVhwM´Įρ}OʲrDU-__QP[/ςO=k=?*چG+S7b*ERluO{,Xc,aQ f:P6g?N+˱ext!NhV$R</ ic:ł aDip|ru7:MsKI ŕhQ&@exT\^Ұ<+폁ۉ釦 :~qsSobԻᅍc'ked$3;!g,ZG_dd6XWJ4m _XTxk3gm*PfwT?aƭ8_xtPCU&~ngKZ}"9Q7bSf#%x$.^:vFȾXz<*ew PQWpJk_%ᔲVoBI%*iVI|)=ENsYFJ$DŽcCŠd%39mQm߰϶`Θ{綄5id/i|񘢦;,it)(lG`׃(*ieu B}GaTjq<5aaRG/DF"|d1MjG*C"g?qUa: a zIiE,hO-ttwBa(K@Z vw 1&ɤ   !c:J˵%l^b! pP[\$3X=Ҹ*Ԥg^dcE[mq|H&ށ@z@r2bkni1mdlO IDATf%e1] 8ߺ"~2L~2c"& ]/ גV8y:z}tv~SC1~l/èx1w= y6"aHOsavt*TsMvАABm-SpD=2e_7mBI1O/3<)"U,,7=ic n6@#9:ZP8FJigfYn{rKjo`i=+DxTװ=kvڎkx<$ 4޸PX=uh&na.bR`绯cG`@Bn(J~I3~jA(c‡'G0>cRQ.V.aϬ@؁x|Q өx2:QMBJQ& "% )oVbX\f0b+2&OcYۅ}484n(,k2YҔH0`IT ҋYZ 8fFnF"J3΂oQWpw^0Q˺w4ulpo3FW7 ːTXw4bdB;1!ƉQ I[%1BC6d%2g50?9Ϡ\]QQK <~$m Ɣ4BF' qVO<'lP KK@FY\>_2,m"YC[z LFvD!X]䧯ՏO7Bu9K11QPp7f1cZcXY}I$ sC#m 3K|(|0k.j...mfG.; +eM}7lP3E|j6@)bt8ǣ5Fsr1%M:QY;; 7 ~LHƇ?às1/YmFfSE6W'`ƶ &i?e7p/#ZR0IF.d_dgⲒ| 1! $gm劚S{"pQ-)Db,d~Tn׏܌B7>}l.}"]4 (̹Uls |dKIyuCϔ1QQ3bfv}E6V~-LzE4l;1Q7.(-8]MX[3 mu:Yp泝>J kӯTv4ocu8ͤuIL6 t\ţq[%ߨ8d2Yn cp| yyfJ35KiK,m#]ٚXRf|%b& 2y?qsg.fr=9{g=LsCFUI^؞U^jef,H"\JYvY7#ɟ0*aylWGH? s#CyԠy<;.ASI,!ek{Z}G…6ܿaI_/ a&gYعBȣuM݊Y}"X̊4i֌Q lٚ~i~N up 1^$ lIw~H w'xJ_zRo,]%- C1sʻa.ivjtZE;]? Ó_79JxUr6B6ړ0 F&'"T]U沲$3hXu`,1 yβc.Z&..m-b43^&+yl?< jҶ>`Sq 2cVX=ڜzX5i!'v68ENǜ!Ay8Eů81:C!K@KjISD-um(^oM!9E3^o9Qq@`F($mD~0Q.xGq0>ٯW 6zd~UTAҹ(X_'|zKD횈Q}Q"}t"Ei)-`yLi l Gi|A@>y i`.ޏ1UD[rj(+NwCAea4 IWف[ҞOkBdyFJ'&k m/|`ERQVXu)FnLL?# Aȉl-߲+XU#/ |Bf!f?5 {6d3chr"ܘi NC_cꃧn٘-qh'p;dd H~3nҬhf&?gf/=@qq1\]Dƭcnnr_YccY5pm WvpsF;ssMx:jƌ2ͅ9D,ٳgѣGߛfz<і*O~̱HCȢrR#W^*IH~]US`jcje4Ms}Jdb\7Ǿ;-ӢZY899A.^RL~wѭK8IR35وL"hL\l}&ݫ;>Zv{]4vǻpI|q05KЊOgRH-ퟃd7\ԸyX$[a 5;fG0ظ{#z0؇ѱc8JR#1q=ҳC q薀B,1H\tbJ+>ŶaV'$HZDmLڤ,Ǖ;1NUN]Aӊl_+-#k5L8^}M^4RS>i羣8(.T驋Kϭ9df$ɪB(L Ec FX>W@8TV!d:b+ Snu R, {`Y|k?j`C9IFqHm&%a-.ih̞3C,i}o>9{W`GUXiAzHt.gAθ6(.d$T]"zB%Ņ(աcANڵ5Mm\_X/Jxm<&g>0>bllLM.wItp& րիc g`~v'5inqx4Ŵ7sƍJ¼,Mp 9;`cT%G[vt:h>y' .V?{l170mx ɞmBشi-+_C'of46kHz9D}606'I Nc`Cxԓ]ZVDm ὃ/Lt/$cfr2af&`N3ƐڢBwTLlxsB3wmqDs3IjEaL1ҟOQόGZD笹}1Ă?hh352krLI&fM2"odg,!m ߽#hJ\5 {q/QVFػ`䌙=& dSW*NO}(]5ԕdecm(t|wȽ1!49m`E&\25O5\H_u#Loo˅懚r(UZbQuA}$j&ۋ aĤ{sfb2DMĈ^.`ĩ:! >X>䘤XB^F2M8 /.i}b5I(o@+,X䄣P°P 
  • ghn_jIӞg#iOa#&pVLFDM4)w6LZc}{?,%exSށRjSjJȄo֯7^/v,7 ^Wߒ$mԤ̟N!!0;{i N  GOq:z=2Ĭin@2谦noZ:.ӏD`op$l˔ ]]B連M7EўlOп19^4k(EOybmP8jK+x<-Mz۷jc)FYTot" Ǯ v?\/ ǝkPtUt^ .)ذJoNN2sِ^F})v( Ob~oq6߈Ej",x!LL^KEyL&c1D4yؿ}7 4-=pEy1io3kKKP;5I9t5ip8Gi!cJ ƫ$;Rh3#o޴jcg~4t x>/x\ *<)c=B)[wM!DPjQQFIhH-IEO0QɊ6i~e\I ]QˑNdPdaP;a/9S[4V/lI Xb$$gN{'WU i5*cy%VIc_Ȕ,X> _B~V&23뮌,3 ݈B…X|S…omb頻V>n:p8 1ړƜ7"6Pj)LMs~N =ג/”QUי_ UQ=(d,2$JlEq \Q#{FOۙ30!qH esw"bw\Z\HK#\Mr/}/۠/M2e-II4ndːL|l%|,1 Vwn߉Uo?h yfhP iƘW>{ tyg-z2x;oo>&7Sq ~Ut~]/r?:ME"p8wȦMҔlL7q7ҘGἃԶ 2J 0.8x~YA!Hdž=uesU:fN+G˓4qYј6 'OO9|Z6ME'no=|vL4D7BּNwލ;!8\`@I:#dZUAh` wG7ˆvxikҏ&g0 ʟ>CsAM ;s\ 5g|aߴCOp8Vƚ(J%dM ֶ2kز6yp;d}kHsSYʚ1SI^2P+H5i"a2%uƸQ!/2v܁97{T& MyF6[$M)k#|U2fo YaJKbqidmV/j݀KEe3:=cٲo+'/B\WVp8`@u5!jro,Ip&_؛o;ţUU974pUI;ӫϧHޮWm0JN:eDMYn/T8_ǥdLܟƈH+244jZUpFHQ[=?Ghǒ(TRLۣ֮|gv:te9Xu/L҅}p5iYQ2l3\BQY.,EG;7Iȏp)}'ca/{a8O3WƬد6'SxYEߟò& pde}iHME&hڧg~rWJK=+UPV^A=̼C FyC i%azġr|9#DQUr/!nC:b }+ڼ2@c%GpGVBR;Scz7]M`X{ZWũ7[~|jf IDAT&ʴg0c<0M s .Q2AKI9qё,CtT^1t"Ki}>Cx_Syݰ~`" 'b(?:=CX7׏쯩6MKŚ-SlA$hM!diN._H%HP8 0s`/XPhٜFZWIcDMw!`.0@@S vǪLlŧ4pr@UF`fўUfZ&0Iǰ|n$ޞLjS~tHql1~pFYv6:E'`TR'cÈ?_*/BD Qd@dl:+')h$~#3~Fa!:x(U"28ݣ^-ϓ?6XUC O0ZwwKpB!xELt?!HؕQ ^߶6$6ҹq RG#p#@̊[u5dmCJL3{$ ׽EK 3zU+MzO| K TIAQef"}vv`Z(E6Q8N‘s[|ɭ=uDu1!sI;)2# #i%eq7 i)d yʑ_#_58_w6Y؃6P %4 W>a*u< jd q.FO/eK KG*3i1IcZ4QHgswsEȫɦ 68EĜ!AUn.W#5i7ͦO~=ȫi@]}BU)djQ*#=S=}.V}J ޏwaFW"v l,Z A#ok.JK4J׶'tj~]77+1V IYZ@`@ܝ0t"F*rH4whd6%fl] Lމ!0y8iNboRv`<6^8con{G#ܣDFl #@Hm& Fi {k茴YGa-#og1䮛˃I!޳EnL%y-v3wi\hk&>n%`>im97N:<6O=}f' F r6BRD!Q}[gD&΄ʒLa!/ڹgrSⱬ3eOYK>XTvcso/nAG̤Zob_LT&ӯaq8pl;M1_|URY&"fRbfhhZ,U8Q& vkB4/g? 2SNa'w8V"wT&s7iD|'T:)Uimα5S2;y>Зz!.)3I3:GByђNESO;'@ 쏢N P̳oMëk[cT$-/`]hag"aU#Ȇ}H#߆$^LpæOJ?hqTOBMDBz G#kf4k"ejI##iLfM243- r]Uw\4t͐fi&{zbT@9 ͯȃ*9K]mdV(ʞրj"Xt"m@P.dQ ,!G&mc+aJd]%ɬ-/{*4P^-8!4x8_8I&w8OtIXJ?\24PS× e<^#g܊&MѠr-ծ}DEa! uWE |Ch( "k7( { 4HIJ`L[bjO,dq3cY#:Q!/#FTc7bIH,X zB`%7rQ\$lM2MQQկ%'T(^˜ꗬb.=RZ''u,&wkڦ͕1_C|EIs2i)2-Clj++BwĂsԎLv'CĢCvY-|5]bY$75g|C :˜L T?cyNdúzie0/>(,I# Ő.nnS#w1HYw c';vrzp%^2%RǾVbIX,G#p?#t.tD410ĖҙfMFkyߧף$sAc85+"TѣVP頼)QҔrR? a+P\^L>{Wj"Z' yNTiJ`kzur"E?3ڿ$&F jVXuUM*\>K&G#p PL_jLLLĸr,_w{2prY Owg8 hG'G.QFa̘1(**j7}2gϞţ>`=^#0^zǏ8`DaOJ90enݺ½`{$3_Ϻw,1u"\9a'ҾMjdJ]WRgXp E}/z=Q3cf52I9<""otG#ܓ=!i46P(NjZÌS]I{NXe9r"]fcl;Ai>=#,CЄl8Akq^#p݂4h*:sI#6ISRxpZ13G DDט6)qI_>:G#pZ+1#uFcj5. qfIm悵h9h&Xy#mvk\vۯ p8G#`a1ώJVPc T7qoZ0rq?FΤZ3i&$q-2gt 3|zjWr֯_N[s*ҧSo# soNN>ޅSS:,jG#p8"(t9Q nDlXS::^Mݓ&w:Hi :oLo^e4iUp:9K`mqX"cd ;5H[_[ AasËv"W 3a0Cʹ| p8-id4%ۇFQS 46ai =g+f'^¶OPXU`Y(i₟ VT90!Ax߀dT_j7 1Tfn {7vflC}+0`^ƳA,4X%y;:Cd7, M?t;̜\Vq8G#*А p5ik[5lAtuw2Ǵ=igΜ?X޽22{cai^5.fb=x4s՚'ݙptd*zl7M(Pr35\*ytzmIA & jk5)1&J& 6uZxepFHJQ>vKWPOz1chp>6#p8@kC1FkѴƖ#i`CyvYrxڲ,I!(:킌3H-LE; ^:hA9?i:6 #-5skQT\Ƈ/pa盈޻s#cļںUyB]V?\3G`CZyDYkRԠ2xxh|_ ~1p[k?FDf8)D_muIQq>@LL b ewʩE7x:bp,@,G#p8SX̊4i֌Q lٚ7LƴiIۓ%-Rz ڵ3* rrSYʚtv΋S@MzFYg܈<1y+%&0ud$7G~3?B`jnnk*21-}%%1N0rÏǛte,`ˌ->3\MSw`H:'(|BˏagBh)X/ Ɗ4!V3HGtL UW^Gcp/+Y?/;/ac)U-5}]]a6Fy8։| ԇר)6 v, k*~ 뻭BqKpj&dD0ʩ~_*rI;d|˗]Idw4Kf9Ve9sAww/>g×N镱 ۢpSmNGfԖ^ ;{ NaLY8se*7~ /.G@܇Z24wlJS Gq!kx %r&Rwz'\yP+ံBx+<#p8@f4k"e4]b&1-5ˈ-_rMd5ڶmcϞ=%3KaC^JF 3R]~oV5Zm)0&Erf >R*DT( a+!!cCqjP$<;#$sJJ}V6}qJD=ș (Oh礉& dSavU * Q*ֳBdfJdj W=:=Д~6,})؛ՔI: <ߦ&up8G#HcF\LКY&MFj3QF̆Y155ۍ?b&8wj^Iyf0:M* sI69=V[1s .B_t)Ȼ '.ޏ a"j&1 y@2]0d\Mą-i^CNuJ~D8L^ PQ W#zkf ou6%By(4jB0Os}i$X2%*E:vgzxl62.sbcM>1KnJPOogU߫*9MU  ~Y_2[('s8G#hrH.}&R݂#ṅ!(f;[9i{v#`\sɳ;s0${ gh`i\{NҶ|,1u;5yΒUwY w{%25~ƞ8G#4IC#3)FԔwGIcLG##Hbvdff"??_ g1ӞUWW egzǩ &;n,DB[q8u*Z ΏMkCF{p8G"wX[MTAtp5`m+-;Ț,P-v2$=i0Z[8 U{irP@ 'ȍ8?3 N!F D dv@Ƙi̤XiRSSQ\\nݺ9qF>XF:FMT\<28##jr~)xbOp8G#G1Hkf#5 Җ&],6gg~S"Oš2iWbM0ur2TYv{ٺUy0C&{v4[N%ZZj_m_aSco$"1pzJm p8G#p!hdx]d.kHF d(\*ἃԶ 2hZvr +dh).]UPmz fAc%Nػ3u葇C@4}ddwttDgJ&v2_8A{- * Q*O7N^_}n#W"ݹM| p8G@ihQ^КYSڌywCc;IΊiԨ44M\ֹ#{N|){`Wi1722[\q+p}jhH"}!Nm;z؅lq:R(%[:}MRuAX~\̡LL8FGՕeUUGy&r)A@p(C_RF%>71-%oLIseld+\g]0 s,>67`(.5QG#p8;tD4G0"rfKfTLfML,fp5~JROzD!e6hއT!AUcTJ8`goOKze/wB}v{ɯ;I, L$P {,y:;CeA,%^M %|m ̉5Y= k /w$X<#p8rH.}&R݂#nl( fJup=iuXX*(GQRODӡĶp5%=mȥC(BL&fe݋q$3;DL>F6[ݡ?Y uDZ8T{;bǂ<U_]A-Hs*-Ƈ T_ƞciul ݠj0&3HK4ݓ.Fyi'ڡ[߁7݀|y83 h ʼ{=p8A4IC}i)ZMrdi#dFv ;;!8\`蝡V?Z3؁c>#^ߥA, tFp嚐C -N ebܝ "=['gbD ̵2͵wxC;{lㆰ~n) D{\OEYi=kL}!#hY[MTAִE'XʬaKB!m(V?'lXީnϠ P8 vsZ̩W~Nv8wO#\ȩheg#&PY^cjT$%MUz7^Z =}.v,`!#:A-<ӣm2M3~pS ~xxSݽIvx(dDFۛv.?QT Vg=RҒ X%~TCF4 B .J~=jMMl..:8] 'y#i˘g[8S DDGC2\ԅ'c>v:D7cծ = Ke`p fČ7bn_d77'3^/K0>=|hڤ}8Oyg&-D_8E&?>>/'h@%&/W!6߈ WͶ 9JA82l/Ѷ[Ik_*O~_e|~7 m+/c[o"vj7[/o1!zE?;>+{Yaw9Bߎ~wE>x3"ȣ-i/&S꼳8ⷠ1_ &MbMneaӱ<~>G!7'>V8B: }@鑏j" $ݔL3v`<pQU!/䂟.>*ɮQ&OwEcfM> lOZC~ RTw"$~Ġ@ohʙi{lHF­YKSXrW$#r:&{.vw+*̣ڻWଢ 킬O: O27sQ+Qy[|y .¾7Lx+I3y3BU6XA旵ڎ6b4j4Ƈ DKA$Nf܌hLJ l>=Y]ߌٞi[5[oN16n2җWF{>^h5|H_Xgq;a;ԀE4hۮסzMuB FK%u# Um׋sa4]^̍J=KaB;< ?Áư̵C߁.wcxβC? n+$dWtKQRW",ϣNQf߭\z8=/ 3v.wVПje ]JTWAZ,NܓfEFxh<}(A34ݠ=nʏbN" k%x!uyl,翧iX ]Fp zԟ+g.ܶ8H3+?ŴC/| vb,IFܲmp9Cu0{δy kɽY4>5P'Jp|C+n q ϻz4Fef@i.e,>ǷY~Y9aFeA#= S {L_"Pr_(%~9-<>D3՗8^Oa@xO Fe{֦6D~>(1^޵ WcqD!ǀpl>u_옏p,-2W'A/f~84* =ckB|{!GΥPo!GM~Xx|H#grTo?9EWS:䃭}t mLfsq#G^B(*>4%5~UֱR*ҞU ofO#5FIk躗jXA *eL AY4W^ӟA{ JJPBڴ+Ic ݙ.4'MTodlJ_DBǏ{Jp~w(7sg&^Kp|6p=w>vf7 I6f?ge5QX@Zx-uM0[=eR=ƭiߟ$++/ k&#~,"[oLQ9ر?Q=sf=+οH/tTDJO|GF_؟y;l]1Oy'g/!(^wcP}N D7mJglvEW1I棦\La2V\+$xo+7 S2YzJc'r~i-Ynh hQI?pħ2'1Æ1Rw,xy=5\3dصt?yW]iG6yޢ&՘Lw['p86l9ʔȼXB0jҘ1cj%nT(2q~~?Nm R SNhG7̃pş#f 9hυ0sL*kR&2xx_ sXuxaFFe|=\&1o_3յp[?gs>EwXx2LoGĀoO͕3'6.wq\GG8kN65sIL^5X= I#7}DEa! uWE?2.gD:6W,s1dl QgAd12:B^]D.OWb@3Μgij"[K?&M$6&=&Era1ݞHEdnO~p0"MM3&,aݷ~^A-tUⷜ"V`;qLaeQ6H+qbAdBŃ"]D[!7ڗ{u/zdUZMJu|(O`LZc:?VGdvFmP]Y)F1˸"7z<'et '~p8lO59mt!2rre7kPV^RU 7~DAv@Qch,2'Co*+PYsIɄTٕ 4c1#1 ÏǛ^,[flElC<0D{D,-# r$(׵X2fq nx7gH&AC>b}\_n={B;XWY{Ѭ`@ $\9sys[?ğ>&);QeScb^@POׯ-C: s\ҒZց{X9YkiB|ICƺ!ƌ̲\\7DQLy$L[G#`$*,8Qo_UqC;g;:;3F yig{=>]|h/vo}WQ\)΂O2 =aFF#' bT_*`4t:Nϰ*,cSk[M{˞ۉW֒@ƃt#h=*~9GŒUr"ʷ_l)i2wV"M+tèrHNiq(%9'N'z(M`Wk|Le,58}N rM#~ZbfwH%inp0̛k>̌~E#V4\2V%)Uљ1m@uu%JD&l\f$ݒDA =6kLCЦRW8݉@qq1\]]ݻ<`''ż3woKfevtX,vcCйsgtԉijEܿ&u]C|/UpJoa {. ~mM3a*BL~ɗ@̙G\\W1-,Gp8Z,8㐧w}Cy%0e~ }Tmep"D<ͧ#<t-$$0 O=s1rMm?&Im7͕Q2 #s`P @!M 󚘗R+-/enZ^+hYZ+[fzJmKi%x!T4Q @`{f03Eyy<<繾ϙ9_@DRڕeN72oH]O^%3̅<۲et[zZe K~QkngȸSH;w]vůI^'2kdTw&pԡ|-4b0£*+ǫUFi:{TZ5^=3:)}\tҬ#ֶ4M*\c  jڸ.Mr XHNvH&)C dHI&V1AP}wAH4Ox>oSgI~aʱ·.wy,~/p,FY9罽h MʑqaYM"zx~pvPn RBcۊbJ-۶A~M_WwA])t"6b}bIwTtWf?+wM9j.݄߬v!WBc;JӰFR\t:S-jMkLܑ &"M'ڹ@} 4Zg (+ mHVyg;{B֫/m\Tk`k1KZ[2[chFWCsKb*47ߌ7"}Je?W}_,b_U>%D*w݅# vp/;_uRճlM@_aIXtewKY!   K{w?ܣ{v*,dr'Z_Zt v[;y IDAT1uB9LjcwGW.3kꐊPxİT"v[LEȸuK`6M;"==иa= =rtg0nTKVX̢ȬK a%bH4W|I *ubH?܇N^iuwww.Z$$@$@$@$PKx:ZԪY\~zN)TȕJI+/<+8( s dTxTĩl[6F$m1o ӓ),RF_8_ԿAGdwLkG8{_{͚ EMblHS!UFGJҨ{Fmk@;Xmc ҰsOmH|adމrm\;q>]Y#:^7ĺ5_a'<8mf܉ފrm֚$Q^בP6=l9в`/tDŽK%ʴR&iF@Di_2j9Xޢ ؂]Nrʒ{}eh :E1e[֜z[3=[; SޛM?"Msp⮼sθ$@LzDZȵgY^0\4o_DĽ]k"h|X*mƶlLSVtXaX{ X uo^V/Ә:[zA"zuE ;!cG #:W,=4Iyjf= @K(3ǝfzn^S?I3pO6jXD };p$4A$VSrQf~91c8aJgٰf@Lx/#7ۡO0pKnI<-UWw0HwD[2&t ]bL*>.gLGZA#C!LǞ/t.h 6ry'?.,'?Iƣo 4R|>:oo T+Bf/TnZ&F$pATH+)S0͸nhᴔ]7p4+3'MŒwc9>X:dg#~, 2i~1{Hq7Hk[11qꌀ1sY( 7l񰆥_#y1K?{cgic'>LM3}@bbca1=ddƱxg֤[BO~ڵ{e)Bo+B^e%%?g"HHH+mAS!EEELM?zM]y!LH&THډWgD>cxWye8V@7Y, w+XH*IvD6Gu({܏XkdnB1b8 K8XkCKZo7Lur(16-?s!d.̐SOc߲ 2tm'cǥ 8NZ?܈ @$@$@$P!VPލQ)Tf{sP D$pI>Vx g,CXxRV7/q'w?qy:ėi2(] I2tx}3flf[jtKL^0bF"X"jј2 k">.V{Y!aI`99j:<ֹ/:wE&(e"NBwWwM{Y֫c W> @ P Sb]f \#//eI3Ŝ} U"#M3sJԅ4s#wґ٬^6YZ֢(]zJi]5|}c!{X\$i#:Vmhw$naˏn@~~$ι p-ɢ]OG[7o6`=_RXc/"u3pbX*ncl߁;̭ ln&V,"O":뢞gu|   fZtBMzMӧO#77Ƃו˩v>iKǢMCq!8^=P0wd£bs=nێ[*[I 荭;U%ebB=8l)c``8Ax@lJ4i`ҩ綔 CvWsm> P{Mw^&hCFtoSpʹ励,(RMUc5s3aUWe]+7!]52w~4=G sQ"*s1n$0!cP}D&`ѣ^ܓ邁#loy-@yl~N.w7/Kltx;wv]r"Y=6= 7Ϝ0 (J=+?5 +zN[?VpdYTGձ?Μ2?B`M$LzQ,n7U*H"l'bBV@H^i5nI 5Ўń88{`L7dOZQd4?)D!$ڭۂ$@$@$P=tG}$-x- *[B}cC ur47ֆ&>)s&2ΜBq( ޙWo d,pD|'"{PtGkR'mygsR!p/razydu1.A \c͍Eř|(-܂=X- >>>M t J?LJ$#hu^}s~kX|ʑ.ZvZ?G&  *tkӦ u][ϴKc_M J- W!&(9dwӾ iIr`妣ޡ^/+1ٶfz-ceXDu5yvPyh U: E9S|77^K/͆LNԓeVp[ w3TLmH|VdljS8|pT3[o7||$<&M5,5DH} wX1ݲOT"grq"B0~ӨJ)̘HHV0V!'CCC +YPPVaas?. G4-V~Efg`'K0g ̛'Na$,|+'NY'<2=a}QWLlv<;x&Syr4 +0xԏ5{`yYWٽጭsxexlOhf;-32].ߞ}*$m@R,ۓa$/xT7^"sIg@ڳ^\4m㪗_(ꄆٱ4g [$N$@$@9V600 64fpvL~VJF׍7Xvx}|xG3(!Q_DuY] 筻PYHHH.S"""!#fuܚn1im}x41)]LdưND6Y,b%|> ~~q5Idʼn2xX*ZwLfYt!:gtrLDG,Œ6r%̀@"zjDЫJgKaf0|BLDM1j; 6;K^>k;_$/Eat|F0QG:n".gf$ʜz.3n#L,=[ExՓ%#   K@ff1v]˽vZ\uUh޼6Ś /hc\Qǯ58D s' Jnv.%v.S&X#ezsƱE24"JcIK2q=w"!0:c=zI-Y'lDTɿ:/ZIar"[1gxGu wYn]"3oU(ݑ͏+\~h׮Gy*u=b:)J8Kxc|=#ew4MJ%S    **+',7%:-Z6oZt,NQQaK|?]y8SǢs-<-RT~+ﺈ;0mVO?WWʤcZKjcܗNzDZ~ ^.м#:^7ĺ5_a'<>> Zny<o w$@$@$@$ ̆˱bL[. :zߜ;رcɊjEW)_cx#=Bexaϸi|E'x+HHHH0qy>_$l*,òYLC Q7ك,^89b-fm>QmKXQ`p\Tg 4vVi;']Q}&9旘6y#TW.#9wWgeV2_eUVfs@Rgcuf_Kӯtzf\w}~c(C@3}>cs+$@$@$PCTi{{kޜkXwg{ht\c-#>OHHH h[GUgmvA4-Xڍ] 2SixsYf峠6PuR[y faU@%=dXÀ*lHϦl& d twGUxZ=r<ޝ<7nBb IDAT7b67 #tLYyfarӲ)g{tn _zdaM>ez2+r'F ""° n +若y^EZR:N-a\u]=!L(:mZ\p5vl>Z~MugψN*`qy)5][+wk :ŲDsY"eo&$vqIr03Q8"ZyHHHRFFcSPS{MXwK6?=ifdlsRC\bk|޽{(:Ӆ/?WwG׾OjLJ8Sp=߆;BɖIQtUoKSL:;W:❩k.@ x.|[l߭On?dhW{^"D쒷<|U@\  ]bJI?11C5vqծb "@,g+B&]q-Z ҃" ]R \+4W KllWH<bN䢑&txQ2~mW(Od>W>\)} @;#`9yQґ @u0EM{0hNܡk/lnj)Ү|/_e3YPvչ[|?_K.F'NӍyw2K=0{1kXR:ڕxSgEWE߾q?=ncVv6e1 _B>f݇Fۙ]سM n.R&"i+?kE+V0>ĨQK x̱c,̜9XgV덗nc֭ftJ?a$?p}!Wu qLc ZжE$nj BEƣHT[hDȊӊX wE1WeM!$x_;("Jؼ3؞}b&yhY7o*z|dp׉CI dQZP)U.]#F@-j*rӵiKѥ-]Ұ[%2.Ddkn TP& ɒ𴼮|\ΐϑ7|c|h޼;Q-ƍɓ' 4}/hР~w?tيvܾHYۮBLEː~ٻw/ycoBMLܚ4\VVNI?A mP7(!Au1g+ѥbM^vN 9BBJXtv#")ϊv˫a,Crm8(F3/"-O#ƃ-;ct  0"Mn!MJi;J!m(mGy-[4ğ=]ZaVML]I9w+_yaz&M q "xթhri&,^tbEh^lWK7\Bӯ&3m]M{ tC 1Lo{9`u`9=޶mz2qIRxgC"+Âaŕ%-(twKNDZaI5(2q#jvv3N:.1~/ϊl%''O&BDp13фu?|[nEhtoW7L:{uV4!tYeL"bL tGit˹E31{_m =FΞ gCIGaۋ0(cCG$@$@F@FMm#m)TiF|hp:}g57Pf|mP.1iZ''KMl3߽~fX!0-iK /.R5A˪k׮Pe\Pb ;#"/h"tI (bK-c:ź6^$3@:Db нP:ZLӱk_ sB^U<&C`V|N ?[CddHG$@$@It#l|!111СaIs_yk? 4ݛ$uyL$pk]CTyg*"M6tͯD =-SWDj8vNs  +D\]!m⛖ipj!;+ i_?8IF$iC*3`;_ QaV(5rdMӇP|" GkV]菃HHƔQA{mxM'O6Tǎ=i6Ib\c{yNBG$P7\k:{yrw<6~e" omwFѣGwCt5E@P$"[5F\\+dcܙϰ'  0^wFT*LSkO֭[t54ˠ 7yБ \wGBJ\گH)cu}[iUԙ/m4YiKL0=ZE2I@}v[9 g~ = Tmp)Si#:M]i{\]E.;׼Htv@[waCΝ`/3Usii]t)5jVZc*N$@$@$@T^?^l' \6T]#--L܀ 6(Sz TO#Y 8KE.x) T u<׬Y;X0nbjF$@$@-i$@$@@`HLLDv6o|1* \x2yi\b$@$@$Z҆ _/4nӌuh/ \.{ !C;aСtǏHHH'1i=b IHgv7sIGӦM%   4Nq#HHHHHZtYU     w޹K @- OØ8oeIHHHHH'u.{ "%}UU!CT$Ð T뤍9 4ĥN`֬Y DQQѥ^ ^~B{o @$@$@$@=C,%gzcpd*$@$@$@$p9n$@$@$@$@$@2c.煀e 9^7"   j$@KZU`ڲp1تq.9q<+7 L$@$@$@$pi1imbq&,];n\u;2quq|,/w:/<_Lg,]> ~sSdo\.A`}pIG$@$@$@$@@5I򧆊^|yd!:8-KmŊoQ΂i0$+)v_p~e̙N+g=*3HHHH.MUi%10?G f)#h  IoŧHgJ|:e7joU4{>.M~}<?ַJEѥy#XOKZs_Ӵ|gBۦ-d^ ϊI#<" w"8cR4t8fQ_<6,W#^.F4FsPgzmkp\?x t}EiYW>085<裘4}1QkNKYD]ԍGܧ`Njv#qUp˯{鹧kN~W5ks. V23p&3 4''tD7 T\~gc.:>աz..F qQPW=b] UD09𘬣ر/3yv1JVZ.ZJWFB_ި #?'~9 ͼn/2]ߛ[>Ӝ`I,, OsU4cx;3bY~5e^A)wLf3rxmwSTam@Y^+>]q6uɳ(N 4,Iމ?AEv|5|nDDch\e+yF,E”E    K@6m>>U\m{k!YسgAJZh.zg&֯:U&gu̎Yڏis$sTö{ n\؁EK7Wr8k+ g9KB*=XX`\Y$X"~I?(-J 2󒻟=;֖WWe_4bWa@+ꆵx'o]OjR&麍ۊov[ 9G1 8ޙEcut膜#oUmưv΀H{ jX6 ^*T 4k(te潸鉡x ]2v4yc<-`^zM cЎ%%1R'Ũ9gw"',Sr܋T֜W*ouO {>è~ϊ1iIEar/1jaŸ1u5Y6'scod&;WV4FL+6_M"v{`mw+98V:M_w3f i[Z[ rKUCZa=-0b $RڀVUH tyA;/?K#>Hy883O~qnŸ}/xDD7Dsϐ)(NAO}LZf7z!ᚰ` ]#tjASʴŌvB| >ң݋Oa&_ ۀhvcvd?Sыq*ϧ>YF2|݆Ў!a5Gy%sYd*{w~m 09f!О\;@Z88DŽ72^|W𲈥l]M=騖5c)ʻRiby=5k<5V= 33:J-n:&QqO-/GAXkaKPiPRn]NuokȐ!s @iZrtw,}+42nyO%TD΅ryXTITU*R4jTPCTb>W' Q@LOR`D6(+ Q7rzeV!/=e5    ˇ@qhլ D}LZy%j$`EϕbejLbIUmRȱvw֚HHH&+kV˘aυmǃn IDAT%a%    H"k7]$w".9X= \+Ǻ Fc.;v[^ TK.[rn L$@$@$@着!V\y3f>#sȱz82   \ k:i>9 EH?j5z#((X,03gΠN:eԈ/" ^Ze&/F"~>ldFvsqNRRBNZ*R+慩[a9~ c`xizze} %=5/g" (yWwl1&&/X4) @\"-!c 5Ξ8!!DzZ:Let7=p FN^}[ &| iɘ;ґ@,mNzLp3ƔE:J/*Ju3{-E‹+IHHH 8QI~XEb䫟PiUT\%>@ Xس& Rm(;gYnDҺX:^[!66S>],X1Ç$sK,SmyQ+?  A=g#'郱[wC3~,iaH1W<+:_Eb$v5)ATO9/ذ {2qpX<3kw[>qS&#V>Sb+ǠM-[^5A Eya4mRs:Ntlك`sV~Dy߫γl*ST Xl4]B)V"P$͜!?pJDeP    5"ҬD~N=ޕY?[F\x̭di_ K"ޞxah}D$f|Õ1mXV~{\|̗n3eqi/,Nָ}s@D$H~W{ o0Vҷ9j&2Łl=;-8EH|wo*x= :No6]ǥ^~rr ^Z̟9 -Bn \cZq1)a:bB0xxY4Ş?^IVcXxDE"ԻLܽJ!FۀE{%iFfZ㚿r:xa}ݿL^lZ$Ms?pqGq)a1SƦt{8#DNB귖[p2a*C @ P#"MYCC=ďcfGq6 Hw~A+b,!n}mEf6P Tɹe;_~ځ,[Ɓ6Fՙq I2tnx}I= Q]:a̲o8O^0bF;b?Ϧww9ݼY<#ޟAo9]ѣeg3T 9+D'QuyR3Kx$L=\HHHH@ F#=e=f=EIwvwb*V<}o"0s&>m=m ŪXj/YRYkΙg-"B'.فK^ŀ>-=xP4]W}s;O,~w=)ᣭݱC,D7*b&qCr$t{$GY0 kRJZV1^#`6#%!: NW\hÚ_'*XDc:N*9;Zx~Yug2<~: ؁J qd?bmLb#*cq<HHHj+cdB/w"&cR66dSwwBd9)hiNTN<9]0ZeȈ#[L'o<^bjR ˪ ".kE2f1cކ 96PH~]ǶT\ 钎52w~bvC`ü pLy%Qbꋽ0[O5jwMnQ$?;)f6JQYw3?f-wƌITHKބMd6'KVa bcKk)=TSڑ)k%fit,gu!bڀr۷bLTO9s+l*#ҷcBx7$;[NGSǏ ͘t&M&ICNXYyȻgБmOb0oM 9r|$3 =/UEqބ{    ̜9xɘ/6Xr.m6߶9'-R-4M/r4&o9Qpn~RI <*B-$e[mE{'ɱYF'^T|,M~R(qˋ'&龸GmF{7`bLW?fdƶO(^?xx)=Ό%F(vx(^1ig=0(g9BZ\vqW9B`݌f$n,&Q}S\4=/xiW>;iN1渞ba-Lw^|>/~^>-{6oQ9 fk֬)/lR\o߾#G/yyyŅEEEv877833dgϞ-P6P?W'~JCW#K[$v3sdΏHDZ$/@xx_f_6!23a ]]1%Dr$Tf c1c5f<̦ moIǡEȊV[J|1os %DDF:,~Su5,LQ _?'So>kU+|bWb% ?%nL * \>sXVcW App0\*gΜ6c;u暚fAWW/=ѥD1\T4 P6ʆ*cQq&7i ?D+ rQ[Z%,U ZYooyu ,;όj9K˿xNfϫymGtm /x>.rLK P Z޲2? $@c L(gA; WgtcxkW\I&y%+`p#j;m1l c [EooEDm+c\[԰\.@ wgh=" lkơ lYm%322]genfm9yGzʩ56s6)q(@ O@85Gq)P,Cv,"lq ~B9Ek.YQOz{rv`SS-OYM^7^%ld}3#el;ꎈؑ8ypDv; ݻF1TY0.deM-w:xmAMIV@XX Wh›6ǖ $vɈ C6he ު={D}9s E>J{;&,w6+4(X*g QRe+PX,sBpV=+uߩݽjj3 K癘3.lsrrh"ٳ%%%xwо}JzNj>5#;I2>{;ehJ=]`Sc~Q c6b\^`nsAMXzux8dׯς9 A!& 2:`hK7څTo)C@NĜ$%[;et"utWŸ!v(ϢO Ԛw,2V=u֓.ǀy_+`4(]m'lf )~J?4<$1c̡XT%a2w~k(PY 7ZL:IVfJ!%Yl~pz{oF-*g"߯h۷J'!09X>7 U503k{xJx {Mْ$l<2N`)Zn 'TRo,.~Bmo7یXVʀ C9@Uk؄'mK<J0E7Jy1z^vX 4IDAT+}o(9 bTSjΖ&d]4U!P;Ѐ/ʯ6dn= bfFTm8ho,iv+W)fsy8q~`ѴT$&!TdFa1fX? 1d̈-CDľbücFvga\Nyoȼ`0m4EʴW;뜁* P,N1#UYN&-ҙxz|_$ˇ- = +Z#??ȑ#BRNHzww#S=1Ӱ0al[?dKY miF=`9Q[U(z}xiQq04=w/.W`>vf/Pzz?F7"vj7=5^m㏊5o db/Idcʳ@ )97CUIc.q7Ww}mvUYP"-mōk4=ftY ߒ_e{Sx|=X܉YkPrK;to9C:Q|lT-E"'%7"[\ VZĺ, CSq* ݣ7Fj0<$;X& p\&WBd}]`oa܂i* Z]ɟ(ЈVvЮX&WR#2Q.3Q 2OVL×W JdeFA4 ƮH\Jkfjm\LM]5hSTl 8!fv!$@SK,Ad^lJhiv->#%#?/TSO[TQUg,5o@dLLG Aoˏo<_x^Vl>Y/P WIV:4E"sOFkJ/ZA9V8,hkNnID`a::<^=zWƽޫmwKN5<ݠkq >,]Jy;qװ.32-H.. b4?2NhjU^v V{AF\=ʳ鞥 @' S0r:/-}~'弶2V]mdFf+ZkFͱ˔jjeһOߥ\/9-ZZW[=ml0m>|ekOO+VCb =Gk͕DҷG:\`T8&WD-s\"V-J7@5o̧~7r]_xwZ}{J3Ed FvVlqk;QW1PI&M暽#^R>&6 2ixjYM;yCGJ(@ x(4g*.PgGqC-|:;&i,lHM\s6o\%M}WXeѵw I-yhe~ޭZ\i6f['/d~x\9,::Q#jrrq xuh[+Ū-C R/k[ rOJ g gw)Xi y+e,I. ŵzx)kYT\΅M.Ӵu(ѲlKqnVоw'icğkF_7 _:mLMh v~ xd$5&>গ0a@$r m”w#~m0AD#zYɣczEh2mvlõ5n7!Q?1=7zvZFA PY~ʧp-t"K,/bS<3[7E^iNE=TDŠA~UIS1מyT]٢FaSj*B3B&pps:62od9*WS;x^Wq]/Z)uzz<ݝ(bʙa\/ᜤт ߣWbI=#$>2YbuU~r^+#}ǤH:3 1;e66E'iԋvFu^bbfekWk~zTt1+P۴\BF`r ߎԝ0sPv& vu^vp®%!" $V38Ň\K(r7ƽ}^ۏÇcڵGO晓%/OW6bB C\~JIv$AY&ͪ^6B ˫a;#㿿EBdyF(@ P#@6^H#MV Cy'(ErN`̫}Pt%rAQWʟK,Y7 mɱ\ n% UC=qKPf t̺$2rBY7صq7Q\@/86x -==ׯ/xazߣGWָ/'7~_s8+ eY4l!+Z͊}Ku|mZ_]FIQY_,.MmV Ҫ ֬YS%`SIk9i&4(@ P 4Z9iz b P(@ P'PӅCj@ AIENDB`r-cran-svunit-1.0.6/vignettes/svUnit_wikiReport.png000066400000000000000000005650551412072605000224700ustar00rootroot00000000000000PNG  IHDR riCCPICC ProfilexZ{\W& PkBt<_UURD@JiEeER*ZEk>]J-uKQ[Vʪ>N@Bpws'}; 0 dp u^1fxr+Z@9nGSƾ<6m;ۃ ؞Ǟ>1)=Y`0f w5EY/b&C3gpǛ|=$̢sfbz9eWe(c ىIF ; C t(?jJbJ  $ݞvSwHzM:eT\F\B4t蹳3?/Pbٽd[~p8֎}tc߸_cf:8i\ nH<bzQ?Djkg wtrV\}Sz0w?@6H_ 94*zذ#Fzi1c_֏;O&L 2eΘ4sV씔9sS祥323dzNo.]xI^ޟ/[⭕V~5W~au߰x7o)ٺɮ{~oϏ|Qѿ~UUǎۓNW}s5;bm._rZ}~hqfccS-b1 {̀[O` H~" T\g 7g WC7!g넚w~{^K6ޡ3|3 i$A$ AL$p)!@R -i 5L$58FH F:F A9j`b -H UHNW9WsBW7h|{-?/oiƄۘFGGRFS74{B *ZUj^x5e~̭=&=KCy絁NiZ>Djx΢9SgV )We8 Y_X)XJZ-oMǀ(JpYz)<[+V=pXc4OG.r)8lkQRg!Yպp pSpz/4 ^#9\ޘIObΨ9;@ ?qP X1PP vwY!tCb "&ke^ 1+WA7 ePj5Fry/<Y*-<(uՂ\ c)Gl_bc0pO3Fy@-G- <s^1u 16&4`)CC('i:V{]L1Fp^pTbl!}oC[V #l[C}Fa#BJmpL,ޑWѯZ[T?4J7iEL荺l,>#k1 b))QH9sH^b?P*.`z-.ep5{ jpOLuƾ4'CԜ?ycr_ L"qދ*W<ڗМ?xL8&LB:y0%Bx,ASNa|zWsuW5hj}^ևTgsU'W";@B~ ѱc5/ IDATx] `ELHBB ! x*"** ϯ*ިr/ª"`D9_Ԥg2=3 TA_zUM|\NNN߰%W[ arqy6 A `0N!D[R111_#xb:t(%**N C"A `0 A `8 g Tf4fE)TP\\ }Q!0Ja2 `0 A pz"@eWxx璜hBBNr-fA `0 A p#@2< :^y?[HKK͛cдiSt)))ʯ,Neɓk0 9G#/<5b\X q`\5k &\"ZH!bp f0*n K, sB0fBUVG||]h! `(Ѵ XzwkbX!֮¾ڝѭտ"?`+:K-t K@ !V|Xt(<ֻ}_xi4t!9*6oh휂pE5~h^}5;av[[ o]=z"Z;k0h&DLqɟ^$2zji[F:u r*4h[qc*͠1 0Om(bd#i] t2-Y)Z"jiT9"Q0 vÒo~S"j4qE8.Ԕ.ڮh#rX> ; pG'QC%hמV󛋝_,CwQ90ieoFش̡Www\jUԯ__]vU{to @]%j-Aȼk&MFFM6x5}8]hm #PU[PsCHQC!wGfx 4*?rf(3k:֬r1m`+@vsn?tB^`ر/%ȓ6fV,ˮֿGan8׫64k7$,ɺ .b P({zHgo+E_b듦+Wo> ` @|Մ|A}׵rwSB8oXv-dk.O~K^ h|~j4QN!fGO?@ BS s}?4mK0'.F K1ip7xsq.Bj1S]E#gx;P w:Q9X;durj;V-qeW놂h яF4 cѤwQQ\yGkYYW]By6\2^DVU w/.Eb9=e4[":u)E-}?4 @ߝz ZC([G5H9^~oPp՗uke? l+}YoaQ|"BH*vU ]7k5)t|X-dU XuVyר.oߕ ܓف46vlABɴYIX,m9WukpW>D5ףSRA p"fl۶ ={T{uc2@@3T QZ5|אSУGRiٟ| ?e2B&t|U[ɸ8ԫWO>dO0z3Xs' >4G,T-.D4KUI?_!~CUF![B\q2w_UR|x~6.1CEbタc_"!.GӨ#\{Hm"2gںyH{3 !fж&g"2PK.$_q.kR&L_>f18Yx>;񹞾1/HOQ-DlLMŪUr};_ylD=kçz;q5<CϮa{Gl\o#ФH\=-I1QCG ugHM틛_ØﻥxgPu$?؋wE[Vwo^78+5‘㛦Kל-T"Oka8S命,L/ٽ8vɦb.|~»5D9U~{^F&zߛhڣo cU0$6sð}a*zR}o ;EEY?1Xx(.~B|.K㝾y3NWlقƍ=i-r޵R?>;%yUu?ORA (sѰdWcO>PqP> ~Əq;i6|\>C_~ڵ1v2c:}ȋukX%!l{{9N} &^q6֨]$<" 𘚨%b nl1/E\sФ]|\Z_Jfs/DJڨ&䩞lKV-yj۫O/;Qx stK{_[ѨUqr!y/(?9Zߋ*DKE/O/CpQEB>pN=?cB%/M+6" ТI#=?AN>lՕݛluA'-xnY6r.ƽ7YYn01طoiC Mx~~_~[K{}46p^)3>k)1d@tO~0^d&ؘRm%vb-}{§Z4YEB+pʹ+|NIL@$5OFtWc< ]n8>l?&ۅh\<&^y?bq)+'z]q,L}g:VAފ˺YgvETײoް.}mQB 'bl)@ظ\9̲$G" ޼jVaX.0}+OqQRf<h }n1\E9+5¢m4:TW_ vעlȖ=U{#{@x V.|':m!Xi%מ6:z:&i#8;?@fB_H1Rûkkұ30%uC|B&ft$[{Qs$y2NW8ωw^M?\3/:a|lN%ӿw6H$$da/:g~ȝ.4 %#"zʒpSlXn=}Gl[0S>9_ipYg_kCuXS樧Ū#ߢnNpZ`ɸ8c[ 0OCorMfs۝ɓ`֕:k=s^?8 7K΢fJD= / 6!Y/GvjųisvqӮvȓ4'}TQ-3!'`\\6mIد+KY% 2`$κ@VC?^CZ' ~S.vD0*ǎSL.'vApVhimq qr M'A @$X69~c/=u)64{h^&:]?'D².[I5[fcުtvS3e?LDթL!\%#CB| \_5Z;Ɠth'TKn&g5fX]l1u1P`R.x3oh:Oe9s⎇B=xtLe{Zm|\$lV|e'g FWbw}(KFN6r̳z 7}_ z&܄aCb<);7;nD7=xݙ#EG4iM7MFy!O^>q"B|-30m˼]Xn?EhR)vǫ.Xcǣ& ..\aΉ?͙ /ݺuS'w~'С:r8e@eq7oVߑV0&瓖Ly(5 n!hԾ}{-;UR8]hx Aȓ8<1QZ(A`17 UN@P;H9FΞ!GRN?DBO%z'¢6\nBo=nudCxT l@'cA>Zmҋze?DT+4O㦽Vbs}Æ ؾ}]0 Z!8UHēAZBΝ`ރ*+ׯWG||LA `8WGOc߽g&A{8Ήvm(=-Sq  ;p@z  ]v)+LGa x.46/AF{Sh4d3yA PP֗/i?=d X}FӅf1 A `0 ! ˑFT;]hVT~ A `0 F }^~HE@?]hj~ `0 A p#z7N< A `0 r"&{*槜L A `0 <Oީ{iG7%f0 A `8s( Ov A `0037{L_Oc'8]'n0 A `8]p"3svZNq4hu<wptt\aoͳA `0 ALA d!B1:a&+DigQGVcLԳzqf/4hV> PI 33O>d%P>{O?ߏÇ+hmqP>Fr*5jN:W5kJ?Ν;UCH f! o*))hܴ$5rCe,>TSoN ·Ν;#E˪U4>J@رcb~L8fdd`ƍ`>N(&%%!&&F)CV,d9R*9998r~g㏠שS'$$$ Azz:x+:,oW*۷}6mڄ*Up\*JÆ <Çw5j` ˩)SXڴi:z˼*  mT@O+h&MnݺVRDDD2dC@ =z{U_|ʕ+ƕ )CDǎѿ/+IE K~MU;l"UV‹ûr-fTÔY}ϩRf5zo@BÈ ۅ*0V׮~)B߲_Cf3~GN;vP2 e޵w0=Sa_yf lR*<LBTNW9cH(؋hUSB2Q;"9aYpi^lm%]s`}`}bPV-Uo߮ʘqƿ'둟={;6o?]==lVäz %40s$ ߐr#ɻ鄉ty\~4`;+G* 7X`1sL|ת3>z7\yGubڙw_[xR*,œ#%&=4@c.uQ>z-9R#;ݻw΂v*vNNJ+4¢veD{*V.>l"/ 7F Lb)D q#[3W=t3pZ!.tZ*h"JV Jk֬QcHኂ5Bxd DDZ{ޙkĚM8[\ѡmu C&->Co >X4w>º792=JfΜ&)3 XG'N믿^Sp'|"%l?/* eqAhbz0}Tnv";5!N?R5/[,?;b#/J8<3Jl׮]WY\?S|g^s˕$x*x,[1M¸a|8,_ÇW6&o2̟?_{n0eFi:xp5J^ Tu\=B v"{`E4}}wKyz7/{B@?/`~Kޫf.}SXJ<g?wT|*\Ua jNyۧIΩxzE eRQ`O (+,wYAG,8a;Lɖ-?HYu.,C3;M (xwky)W_}5Cų<oTLcƌQ 6c=^xA=SkԀŽ".f̘Qae?޲r i%@x9Y#_MHPVxY8]Yx^l͏|=T K(0xQ{:}̐T(@q,NIy<_e@QۏX16(Ԯ"t\JX e>Խ5`m(:*5mXSW fպuktK4ұ+k2@ nݺycڵ*eKaV X֜gSq 8 EYc1*[@ 6lyŇ@ #_}+^֭úR?E}:Ne@P fN.=؝r8(r.xq&B2㲄ΙaβFDVu V0D-1?+vgDxq27T JY|0\PJAy׿ jVjTV/ӧOW8W8+=;;"@ksz4pP?>:ΜhsJg:>P g}b|q"ev;q嗫> 1r<^~n_zy \K ~yxrHѧEaJf&M0q\;ˢEd+‘=z:\x,ڑ?Qh@;ݭu_P`98O4 O@nEbOLM;bB+ڱcGYY|V: Lߟ?D 0m;>Ͼp'Z4m;g|B{ShRrpLn#FܨlQɞ#q"eY_)ˣ&gNJ~|I|foOxoYf U20/Bӝj`Ń :-aY5Y:ixθ峍88KV"UF?b=twcجM{}ksnV ,mַV0 'E u{8K/ܿhw5ŗEJ{ͥ.7O<~`{^]r-CTrv3ber;>9P ?X\ ,{NR)$w”5W@<_<[r2fYP3R_f|*@z&<+hı(47/Z)?ҼJ@PX8rb R/a7\ }(|N4Mb<w.?7(5BFObsh46?yu=$$DždW(JwyHO-3%skHnh?y#1jn*ɒe_ n~Mx:& c \- lVtɖǬ͋f?5<$p0Rf\h7 D=&rճe- q$ԶQ b#!(NC^tC~'G|9Zll RƍÐ!C3nT8g?sƆ C-=O< aƌJtq+?^p?L H('U&lk3!MWd8:˜e1.9Np҇¿#lG3m ] 4s^4*`O8"} ?Su嫹FК$|N6z`o{7<5uo޼pyb)w\$95kTXJ fH|<w<<͚Ѵ6v \tFq&Q&l4|W/ĦFho.msb,ڎ|ʬt㮘H6]u3ERy7#aw|%>i pvpɉ'cp+k)\|@0}(Y^^sBh(ΩUT\B9'†LmA@9IL`*:, j_E^X!ǂnXeɞlQSSJDT)tm/nF;+%?l`BZDhj*V诅V*f QA+lDCSB8Wi^Ƌc*7R!iEՏ9T i3!~vk2g˱3,\+8AbLYdKs$'oFe+ Nq4ΕzwAHOx*bZ_zAn #Kh+ D(G <Ґv}v2(N !UX=@N.; E`Igbz.wv~g;"V UԼGƍJ±Z$iK+ ċN_} ?27pkWK*!~#B\v ʡ ]Ż/ڱNcr/;\C#|K%_2k<skvg u"UaE?|U'hgyu$$YavҷaKQ xw}i=C8n$O#2[ٚ.č[wt7}a<*P۞ϗ#!l۾ٖ,3M/ JWQ|{Ei~G P|y(@i'`GOfh˙9[h^Z)*]V- G_R6pf;vMN_~(z$M;IϷ~LO,9v/Q/G~>If-'/|^M^8 Ьy.g)q'T9)Yd/QQd=AN}#6ɶqX~) kC|۹sDGKߏ ǭ0͸gl'KHʖIu45bp˯{׷*yO:%~' TiBS@,C(ΒSu}&] x|W_Y\ЗA$eJ78 q%} tP'_|9 I(lUt /4:S@v=~Gӟ(/N|M9˄p)/:}}#F:=zM 4RզMѩc'ԑ:RY|h(E*?rc0$&''G ;|3U{5l: x/P&>UXtN>ExjkホIlʝ+ ~ԇcS Eٰ~2Kҝ"r}bl[8rq"e[5FPP8fϋBp[Udfqc]=4Eh.>t~݉?byWU 6P?&Vha; @lDrδC/VScpmL ˳uXHy) #mTYy'򔖐_dc_+cpU9*|}xnZpK"eį 9zUW\‡%{?|»>ҳ_Wk8֠M|UjQDM@QAXϛu [Nl),"`gmAT*ؒ"7R1܌MDC1c UNf…jƚm/34鴻@43 : T(}:D"͘)I:"J'ˇ4Ĝ$ME;O^tL_~QMN2S 9Pny˗epqJ?䳸+#t4+ zQVz& ;ĵ` V;|;O<2A1Q6P ʼnvv5k&F[]`8u|(&6Fi&:i`Æ.„pYQDqVZ2SS.UO'?T/jXzܜo/uǎwle zwX\ IDATYXs:\5Jr>3i%W3%-NJ,3%}0cGd"5F.XP W*ha®]U7 k)`avP)8?:|9w:;7n{6Zrɣ(+ĠxΜ&KMqq_Jͦ;Gȱ 0吘Ȓ5 7ܽaK;O.t?#[|ϼoTx:x&<їwo,Ө)43gz}ͯVK{8U%RU|^U|ğϬnGkM"UXQt +Wjp,вoG7mx?{^Ζcdy MLwSAۻw:|mݺN.qAN >xy穾=5ivG^'wi۟iC]vTR@)̲qu'hd3s* &b;ʃ1 9‰^q@|<ߤ㸬M Ãxr :uj'-=yԼ*y=DP(ٰbO ]T'XfEҵ+&,30F۶mv%]%ޘ֝-3bQU<辥lt$Ll(\#d艿=z"K ˷SG9Re)3S~EX&7M+PИ;hLN>)~ՠv8 Ą3ܘE~ɑw]/emOkH&5Nߩq0-OERKepn*NYnlٸW >k(vܬ6E\fOb~bp}@bpYgMxBpƕ3LB=ۗ(ip3E*M>JAuE/?|Fv!@%R+,cβ WNQ>e]. ۱cGɈKjPsv'Py\)0Or(ؾ4˞e!4RN1Lv(L;W;=ВͥSs|yW(++ez1Xycu͕۩£û\x=gGj_MżBGpnו'*RrKP>Q *,7FG>L0 0ڵ><)Oگ+JʽK)N/W.9;uD:P݉x1|Zb'ZOMYh+MooTN<2oA GF L~ $ 2XPYy.+/(r^^+݆RMYe VR;U객'l0 \*BV0 A `0LBa3kA `0  org0 A `8p͟?F `0 A p#&3 A `0m A `0 g&eY3$kA `0 QK `0 A F!$ `0 A GE(Ԓ52 A `0@ĸq&D P eg"3+ሪrP$x&^+ >pVn'zQ,d- NC(ڇUbgAՉ= \Ia_z:P>4<~T@9Mȗ~SSAm_:ӗGS*N=P8Sc=zSN|˗F/嗜T[<1P=p}:E0^-MdzI6^ȏu&_ML٘?f(̇R唁S`-oMïlUd^nqM1ءq*ku|C1q f' H;o'Ok5^܂-HIa#1tlZ ݳ3=y35{Td6thpY:mØ0NOE;0ETAK Jv<Rcb;{ m h]?~}>"9 ӡ$GO PQUhѢTضmJ]2O% <-^k^(ݹz1T(\):K弓^b[q_Z0=J2lL] h|d~~v,W9k0Ꜻ8Vb>ѓqcf=ޔD2ۃd9 ݻaX_3sq8,_F]y&QNc1p2^ֲDYRa0o%-zK/~sdz˲psה cHA.CSפ +}fL׻ôQ=Q'PosN ,rځёJ߱MˉP9}#'XVB,]W8۴Gj.EX!o;wSmWv,\VN$lHLsXI?UL+pČ"W+,K3guK,vEۤ(ayRL~-F=: 'yocꥠQf*FMaL_7ߝ1銽GZmgYŸIp9敼׋k[Q 〬eO6>Uem/NF(N>^ YջRK՛ݘ#^>/b2i]8o=?Upw@N_`L?\Ri[Nߛ&Am5 \&!`>elNGvqܝxz~}BWǼn缀 ցR異1t/mJhLyj$vC$?&23<0 Hۈq0~H[(})Ʊ_'3dOU|*9 A"Yo{:;.3|:xQ:;ێ*Vý啓o9-=/Ho!-;`qBl8^RD7k;x2^06.?e7)QH۟rq}k1G4Æag-nI czvm'J|vb{7 2#6 ϕJ+DLbt9-xjztO]ӯ8ay>7t{4(P_m22 S=򵡒:egfOkIO#.K) .DVVV)?_˥PiwpbkzG!\ <*˸fpysN F zK 0!ݫi?Sԇ\̔'Qˎhޟ^06}.-[3o:=EZsBA457\W(WAхKN|T'-ƽ# @ڈinǔ;Edi9:ke7Z9& Tˆ)[L[gaZ|KfEfxz8 ? .|~bS;CP_@^)BT$#f%&`O^)ݢ4'eLt;eW*"^q,'z'U{UW+rW!iwZ Q:ǖVӋlj@0-zk\A Tto?4J[>DE5B熹XH-zSQu!ٶt3M;nPO8XJk+z%ʔW~TZ!b-zpwk\JСrHʶN_7E8b?r߃i>Oky~B(P;~Or!7 qalI|y;)s"P)7t$RJ;ǚafE L!D'πNX0c\7,ĭjK5k6bьEAD.!p!س$J!d_˗Iu/Ctvn3 bǮjj/sOȧ w}k ׏ ľ'[Wu9w-[ErrS{y˳B~lXC<'$ax \#6"V|8s1p«!\&0US5=4G|5hg˭ҲOoj^r wV9V=p*MT(J_glD>qFLyeWxcg';e劥Bb2b\V*iWhmSbQynӥsGu4eV ih\٫6HUqIO!=7n|MHq0a̲,ٽC) "1 +k>wUs^Dc*dDZaoLD Vۧ}-u&TރiWns~)g]swx/KW<2yXs/W3D4;6kIz֦}A Q?t"##1LVn֭W^yˏ&E^Lm@nvY+`uz&ao@pk/tQ A_Y-,ݕ;Ɖ ;wbǎ.BJ'33H,_w2'ׯQ@ݭ?ǏŠq͹[[]'<=M6N2-Gn_1on:6#6ZHcAxKÇ_30sRԲrq8d-Hnik>Y.f>vn`L[R%u雵jD9[zVJU1nk5gOtؾ3&SbZ-Ee0~@k'ږ,}þ4[%‹F!Zn޲[ z㻺c٫r;ӳ.g|X&E:aʴd%Ǻޘ+A'Om(Y2$5p͡I+TLDx_wCS_'ՉNùo0r7C4c-{ךly~'Ǖg,LBl߽mH:06JϧN}P)>`#GW_EFFGW^z%/-[R[VdsϨCg[ܰac˳J3&7jvT˼THI3R|LHْշClL~y.b :?_~eV['aX1fcuC JD#6I+`1bfȊGP:oGSsWzBOռ?OJu}_!jMFb s-ufm /N}!Z`kAc0`l3:N lLzaƮa}2'ô +%!=5=͎ʪ}]w,޲刏?Vy[>Eb1e1h}X6Y,/?mnI=2iڕVU1YUsMk:ʬhcYEFdi//l[oiӬ=vy01jq1ңaSqӖc"q4<;&$vŪN7]wyu{ֳ0Kw؆+mI1_+LxP|Nj'nV){C0X[ؘu#8s62NmTa-s_ZC _v xܧ]ꑕRmYkkicۮꙭL&C.2¤elC^p߱ⓞw@eA* ^{ \!뮻xo/͓؄xr,n eiU,t _;*\/ $(hfIe*%[mBnyB&}iGVK&%hT ҋ) $93w;wOEqfΜk\pr"}bQG&zO:o]<'`'g#W!wNW:iSW6:^iZuuW]s߸&gڽgrWg }x6kieӎb.AzaάhCL _;ug@6h"n ޘN2oyec5hjWjY 0&hu:bV@PQ#L@kXjN1`L 0hL 0= DwØ`L 0#иYZ`L 0&5 `s J&`L 0&!'|r ճj&`L 0&%Cnn./^|-muW^uRkhq0OO_\?ż o.TI[H3Y=xiZ| xhJs9g1a</,b,`L 0(؋+yWZCS&Jd>^;4Cڮ ^^r yKO:udh$ŭ(''6w]fuJg.T7Gb* qOlv)݁TLyG~SZdSk-x96n=>;R-_ѻ-C r7ŖN}lҋ{"zľ#Eߩ,KZً*PU~}mkcU>2&`L@K$ YYYV]O9_ŨG xnR-8Q)(FONAݬWhdv@ޅ{GQE$/~d%:}u^)R)FE``z֡!(tjEMj: wŞ ?WEQ*k]NK` |,#pVSYRD3~$Cl9a<~ПQtճ,E`L 0&@lǏǛo q!\9|lI.eg{4xE(jހEWо(<16Ѐ_\՟xC 8J|JA ~]Ɵ!GUG 2# ğ~ Aśj:*?vtn<_!@pzmM:;eMi<0Nxr $''Rw') 9 p}kCoF{ ~xي{0̠'p&K#wO '!dc;-XgTYJX" 0& [ ot,M3Qfz^@AvJ#zT3msTS/7ª(5{3עgtLO50PT1OqLu4ƀ~;y( 9uUd^xzaZ״X̿ J8!VVMpL^4*tfoʤ &`L ^h4"((zfx7 ≖3g"**J~T90iGw :qݾ3 Gjg@2xgG˟ν\8"]:Mu,}kkwD"'9q՛`1l,jqʹ^Z`L 0WRRR6m$MR$Ib:"ޘ|︊ Y`L 0&pP iiit~VIPᅬgyex]_0&`L 0& (N)S0uT[PIIMHLA*V+V(SFa*|dL 0&`L ?[a:$u"q1 0&`L 06E@;@84؄#L9*N;oL 0&`LP (֭?h͕aPw1VD|`L 0&&ua/^@ՋC%L 0&`L݁.8w;&.Cbر#ptɚCv( 0&`L Mb؅#K(Osrr?`С޽Yv3g`L 0&PZZ_`0X ]rb|wX `L 0&`m.ty#uRql߾uh)cJm2l4`L 0&`1v[ZxL,> &.nE"hA3 K`L 0&8G@E@8k1ې^$.D3&`L 0QW;!P΁`L 0&8Q|krT C E\Ѣ R6w6 0&`L Q~3 g@qX"ظ@!G&`L 0&І ( PqE؄3t3Cpt&`L 0&Bm)N @8`L 0&n+Ȧ8q&eHB0&`L 0M@q¿SRm 0&`L 0.C͡i`L 0&@'Al>`L 0&hvC2&`L 06N6|&`L 0&4eL 0&`m;mL 0&`L9!h=N`L 0&8vd`L 0&@sCz 0&`L q3&`L 0`98-`L 0&h!hgL 0&`!`hNbN f3LK ^͓ L2LTE1t I>ab`lVb*79%S<:`P$ԌB;{?Zb@hp(jM}9k6[s,l5M柍fL 4ZڎL {cZ%۱Q>^51&Y2zE2`mfAL/Y܈l!].f\<^1b&v@dH妼mhH.bݽiZYXRRRn qn@ 2eJ J1ECp-kɗX8B*|Z "1ZzL?ֺI6 );nƄan45Msή^wm jK\Zb@P#:+"NczO1.g|3ؙ8|9$dDp.Z0kׯ([رc[Ygm]ڞb׿LAeIT@Po{q"}A}<`@ܤ?zm hd̖fJW[KK -B!SDkD'bS.yr766WB΢a(| (`P4Ƒo l>G:ɳ٤ldsQGc-WSKlCjjtMKUcd['h]ШX̘:0fLٝ4CD/@ZiE!Qo\>Gcc\37n{0.|v"LqݲveΉVeޘ-9" pDVG6"E+WĄZEYNqhJ/w$qkwws=S5WdcG;.D~x8y1ѷbV:z<($3r?@x;b|0$?#V o0۳lߣhr/a7oL 0FuJ*.E/^X( #')? m|Yڸ+PZ_*֞Ir 4Va]ژDctT_8iʘyq|lPeu[v9| kbd@?N^"UJ 1I!p/]R?IK4="E8Uw=V'nNyӥ$ XoeӦK[m#Xt',žÉPG>+h7-1;,o|s._}qhj t#b0V/媰F Wvck{F#0!c Q1eb0 Ns[1M: [X*"Lǰ,jFvv4.S ;Q?MYX;KfL xѫس~1aTPl,(AN9^:V#I3f"-i)fγD?z&^QޒƗ%ʅ8aKE9 Xn{xZY";a;֪Xj/Y6Uޖ>޷E!Pإ؜wst']Κ96gf<= ܺdwٺ*Tsó5J N.gȶ1iOT-@h5'?n[*]v*96ݵj~8Jz\бl?$ڜىn(t1YcF{g v܇ 0z4:zYS?Gg@ErԊ-ΰγlc3;eL 0;W eZqrIyٶr*[K̮ E(EA0᩹Xr"a"KUEH!υ")IyGLX{lA}b5Qq[DQXbHh(~ ,%մU;w6ךwu8lZH6V$lB ǜ!-5r^I=[z*uNC'yZ;-ڍק\8y-n|]x._vGcs &q znβԒ;vq=k߈iugiyv*T,%} D!6wj[^4*\㚨hY)LJ1LZ|h!jO.MnLČ1Qq1Ȗ>]'3l1ik1;z8ñ2ީmm*P\7X%hU[*4e ᨚ`^k^V) ƌ1x`$UƖưmWCGclk,#wdNFiwPeLU5|],3 KwĶr}d=;lWn5r(+2] W`_"a,)7Y1' m^FuM~9|`` Jm4IKKW'TWhګ^?%zZ%#14VaeU;`jƓR')軺׏f4 q\Ɔϡ :veBتԫ'NNih¥n0jX4Z;4U-@X:g(pY%%{-e0ҧ>5֢fȧMzxA{E} `ң/fR1PFivOR>EKC'[g3Q 0&pjĀcǎY'aDm_XuVe+%Z^s>^}T$ȷP8źs{[-(_?N~[xK[p\-81? >ʳE;Ɉ<3fW'ok+/]aGk-yؗ:i@)yOZ+Vyk-oGQ@mZғ`֝z5tqy'wbUct|l^&`qlkSaw*h !htfH=Ok5m#rmrqݲ?sb/58 5&cdYo"dz.CZ UjN{+/&\{٘ yVq T͎US/o6Y }%`{ρwR90*NY#5YMh&sdwfKgxϨ.]:aSq @w0WecTˠ8s||^'|% ?FN,v/| <xk2/nqSeO@+i?KrU.>>~pn>YlBʊ ph}.+&1SWbNr!v#E"G?7z8^: 8ЍR>% [bkڱ4UnyGw[ƽgRF<se4*y^V\s7kPkKIIرcmzLF>JxM ڵ SL6ܔvo*xwCh߾= tkա:n&^^0K{km!fZ4r  FsF:/_7Tn,[7jrH hlzn#=\~ybҳ?Z:6=R6܊ uN?ww_XیRRz.Uv WRį|#ZײCf:z { yg*.~tG]BuRqEcн']FަSHA[MI:X =صcv[Fd_  'o^8  555|2\"f)Fcǎ/~! pR`L \Gе{wtW_vGgM;(h!f=@3}nAWOѿUVp3AMi7g ?Q#"$ىpcԆ3V qrTQ%/Dф'.R3iqI#ni>+5WC, !d5E !snuĹ j2MW4믿O?sI-"94[2@h)8~8 ~_ `L 0눀h1G(:jb%G#V\PmsȟvkM!=봩 B Za* ] ,h!qذa(**B~~>uA<`L 0&1pX $}0)E2 q.BTTT_Ě`L 0&@Pz.]ȵV2 )c r'Vy$, 0&`L \=ː(.@k؅ "\e@!p@dn!zω51&`L 0V# vI:3 '!P6H8(D`L 0&0/8"LuE+;j"|`L 0&(QWq.68RC—L 0&"1[oL ĬBb݀M8ېYP#;j|`L 2eJMfLn@J8r Q0&`L 0& JKp @/KBGØ`L !)))mZ6 \]7[ u@CwC#`L 06Lf+GŦ_E78+.CzT8 0&`L A!,W )PZ9C#`L 0&hNːC 3&`L 06H@0Yˀ5v|dL 0&`7>w5vp&`L 0& +vCf`L 0&u_O 1KcL 0&LdBYU.$V2"߼h;s !!`L0=v#b3 Uze֬8Xe p<1Fبd[M 9 iNZFjMZ'[QSWh7n3LF`&2b֨$v'&q16 __7gY_Pvp`L 0@V1ʵI]hv+*?͸ q8ꉯc0ćqPRmPM+./ݵե ;56#Xe.xqX(2v=79|u !3&hT6<krQH}y3"$ѾjMxwVl?(cANHlЕ}~vvY,(rrrL"ƭǻG{y黭6UZK&o¦3x6kTJ91a4`=*X*@-)N8a)[U|H녡]E? U10C'}:(?pIugUP .Cq 327k%%%()+ɬqĄԍ8XdvF 7WI*M`L(;=L'-O0S?>EN/DD PTyxKg_:.٧4=Nߋ:? ?QAQF'!$[ޅ#3 ɦWh8Z^|c9oV'(G(`]gf&j(~mp1VZ,΋vBhQmL&X$EߪħA!h 7Jjv~ BP@=<2ilumSfQ3h*Sy0#}~tֻ9F)'kv˰}q<$^~L 0@BBna{g03펵E&5N= cxo?EO} ze J5o00jjU](u'/ {%'~jJHzBV@7AkJVg(`t?ba 㜕"Imi݋J+_uJ2?G5h UIĩ/gy/cL %Ugl֮J7ɈK/^=- OsčH@5o b<ނIBPQtTn~I"g~?.+~#GލUaЦgbY|-lC/*̩=8ʩ@vG.+5e68PrıW{?*P:']-]rU+vZ1'TQJnk TԘǽ+|ke|uD/VgǸ aNx7GEI rJlVNm k(/Ǩa0b#1$TmHE3G%HUŵrG"0ČH|dL \/_=FKtK a;tG+/`h S[Š>=P("][=>q0!';ctXߒ؅}-~(F!B %pZ^=ZR;{-x}.qI4ßEzKL 0MKHu'fuFqRy;xЉS7bO0&%2;OחKKn>]V-YOzHuD: +$VQBA\˲.;]0YS Ikʿz_* '7HvJ/-Eg()%}jʕH֘Y}r30k%`LlܸD;ʭR_QYR]]_\RQ_ftjptUT^VJ*'ɯL={=Nq?4(ڜwZ^yFV:^տVʃ+ʾyW^y̙3/_駟o}QQTN-66^SVOXdk RUQ}@PߟX?kV[cSsv2j+.\ۉT:SPԝ;Q{EDRչ׉}-v9zG~ (b_7{eI^T{ K筝=XU5I$'bL \ مڨP`W_]+Ӌ85ptH[*w'2Q89IF|:"k /eYPV ^^,H0 c7>^/{Y@ֆyX8)  ,Y.({iO~nSn41Y.E.՛Hl9Pei>%M+8!!! 4cڅ{h"DAb{UiYi״m#zC[[}C 0A+ގuxu3!2*Yo!'&fC4̌ݣr[]JDP \܂Ln9׌ \Ue

    BJu8~~4!OBl?EڌsPE'Ołf3 +QnQ&pm h;s !ό3&"cbN#{"6yr QD_ڨP:OSZ*iּ ^4㚈}{ ȱZ2&*gY_O)Q0&`7x IDATA1'ůQG#xD ,`Y;yrB2C@8b:tR4+΂r@!G&`7+οD"LKF鋰{;!xDLCQ!vMU547&&2vp&`7 45 }(A{ҿ^xnqA #4x `e_bA7&pU G@>3 ^/1&`7!3@^ˆr.~-{^=s$oL0D=a $ swT%|`LP;DC 3&`N xI΀| 0kH@=Ӑ2ؕc5|Xכj-[A+(pOq/;ZiJAvS46}Z2MH&^h7a2uܫG$Gg0?T:G Q*- z9d@U KHHvoHuλla4z|:\r6[֠HU})\kGmy @[@yiI!Y.񮴚%%1b~x횤oSO5^9k%U7oDٿ˄7lC`qFXf0"H@2lɵ SJ iI$c& TH|p&d7J|3BIض;OF\]*{ u8o&r?.c2U[Tͫe@DKC 6ː=qOoc@u6 ϘG%,HB[Aq,TW㵃r̹{t"/ե0Qﯕ>a, ۃ^Xj2-dXx_ BY}22{l$"]KQ700}cQ[70(#8%p97-o"}x="1\\"BKbv &J g!졎=4Ge /}TnVUZ.#pQM[}.Uċ۲>%į-Z9)XcGS]=u7r-5vDb⋂u?[MxvvY,(PUI8[Uӄ(µ}di9y?J¬u֮Sy5a~c.ᢆ8C$v_A5 5HzedH'*^pT&=W;.CΐCun0"$4X0<:6r1ZxPqYs4վ4H^`F++ ah,oKKsS=@_ yI$_DN`݆=OT37 ?4ծaK2g P~R /X[f O?`HC,BVL|5~ ؽ~bQOG+~J4J0S?SxGQjiGPqekb ?lƌL5o;qj`/Ku8~~ u8]t OuCiz4#,ΙߜAsg4!pF|@m4tRbOQy 1 0jðK=@g!1XQ-}*UhOy(Reu>4PpƧ@"Ur${B ÒE=f %SŐV=&) Djk&TL1W=1rZ&.y@Ҷ(N୓+$<2>CO`7w1T$#c a;[&B?5wXg7vg[Z(3Vr\*hlݩ<faM*2K4[PLOD8*eg{S#ơrZZp!,B*wyo;"8r9٬8d6{F4i澓8S~${kW}tu]ѤPk(~lŞ2*tKE2W_Gb e7Ѐȩi0S;O=K {=lXcԿ PUgIU&ˢO;ՠ1(iom$cziJeTa1TI~<WAhb3}P $UVS_C ^J1w-V8֑/tWUײp%K ޜuWVà lȶjtL 0%)S7h1#PRms=|}TjTGa*LҦAeؕОw$ٕW,*Iqe ޝU6[Pl ?H{W.ۍ=׳]ZfZPW- 83(ÔJz'du܃g*)[^*^sYqrmajغ?ݻ]Z[Ν;'|(vo*+*[\\{|l ^T@o[!v/!+ߵAiD<'F[%~.C$vD,gZ~`L 4O`Q됖 ]]΀!3١}KRuxsWAv/lƴfDp uҊ`=vz<#7zW4$)a>: !J!%r\+G 8'ؓǀ t| 0&Mˉq}[-ԥqz o3pMիm!gg!`@phpbL 0&^((c (b`L 0Yh-ep'EOuQ)eT|4fv32`L G")klR \3KzoBq1}f2"_>L4QM jBr,NJ35&tq4R 4FmG!^iCAG} ǡ6[KK73 {:c[W kB69IPZ@ܕcAgAL 0&nR4(ˮKe@PAq,8fN:㆛|[>vaXrlEvNݎ54m*`:~~0|!*|[ BnBM?/,.1u։djZ?nj푑S੾ S,:K;hx'6IBO+%9oYML^W+[%shk)*vCO3&\8>0xQF 1L^Qk05AQi9.v~JI׌sPE'O6셉/Ptd³;h-iK}J_Vj{ vlR|,yqƂ{-8oM&c}+)3`a]ɦU}96`_n΍y@R?Bay9Ccm:n̤?wQbQ Zm3R-hqmqGi3賁=*Al*CIQJp1&nr%'~i R:7VEb!M*v0qA[Y]܉QޯY85 5|K EFzҲ̔ƶƷv<.;!zsƢ) 8 yiBi{ 3 &b nWW `fL7鋇i5EkW l=1)~+_DzzܐQZp:bS )gAV($C Gò2{$jX9 A?\2^k"XIqָd˔`mbrzbدzbȻ*L^̫E `čHzp3S.Nd#B@*8SXm|#lpų^(xk|g,?Q޻hp~b S܋{:SHE׮J.VɈK:vyNhAGD>9 a?r`jG8aj'@eYjvniE¸pQFX5Pe\rGܑ~kWXhF|?WHukyx*"CyL<͸~a۷_VQ~-wWQaV)N3ę*[w"9:yq"DbtYrͲSUM[&IA#Fއzit(|xqдVNJFxuA+!eg-}vސ9ccрYȦ^*撃O5aZ"矻})\kǭ'!6|㬉x`6>.ɋ|]Ici4术ߞSN}1鮎 u+pTR5f$}@kF>ʿ6K(2{ue^-\Rfok;Vl D@n2UڭtǓT}u+ʚž>xy+MItykG'w3.ɷ׷MV<(d(,%}[[&{Sm},e=Bh)[Cz'ڔ,]Ky6=%9PZ$GG>=ʤނ/N:V6j~-ćD vH`f5_6(tStx5,շ)0Pȭ#`7ۺ SCI}C;OP__oVد^jvp/^777w<))MSo x0"I1^6LUّOA!rc~ӶЄS!4"=XQkO'3黚HdDG@41H7g&"`:pU zÛI! P'8LBr{:orO8BU*6뼩NLV@GOm:ܮB|HP$Cz MAV_԰ f:VO}0*}C^ɟL n{l_L MX c"G`݁<2Upܹ7ѓG(q!Bϖ'? VA^5P2[*l~& i{YX5W\W9=yYpX25&O،蘕xΞx G#p8@ %@TBQ0ׅiLVcX}~Rald$G`ɗB5b lb݂zر/DiJvc 6+'se/~TVʹbGeH2#('G#Dz f̡WIp-Ww˿6n1Dkܱ#s N# Z5F PCխ^ʿΈO9*PB]PLq%R`)̎h$<)K6d E1Z Z,5>Pr d'gQ~RkYS=řFdj^ζ:l\a2r|JG@fq_J:+SuU^>>U0CewDqUsɍPvaG8| +GiJm-20OI'uB\t2hy6 IDAT`CЋ L] SxZJFy.FxdHB^>Mؿ4bXڞNFBf! f<>;߀:J%khwR:׵;vumLI؅_e.TS(jjfESU6\gшsEZڭ1]-;]+  c Td؆*>t&j@aZ,J5G#sA3SE! ]Q==o6{7H)_G~Q8R4Qe<Nu? O==s,g⩮Y.A%yɰ/,3i4.߃+2B~+H~rː@eTbmkrDub%yh7'i˨H\ trQح&!zSRyPt"y)r<W7GH*B;&fJܵQ;I=;vm.5V]Ebم̌BQN[}Ӎ63z{lf/h]/͞tvb֩) 'NwuQUh_ɡ/ګ?JyZjStܳǿm#+zKV9=S3'g sV]}(W{¦c{>COf_A}ޮf7rG3*F}*ED[2p8]@<9 &eb;}rv`<]Ҋ"4q?e(W *8dmTOpiP"R0OSOy_u.񺯿@QJ4B+}%n]{$ڶ0JyIq*h\EamJc]Naeڎ>e,S˭؆a0m)AA4կs&S`  ʀ׹yy/F.h+x9)p=/_'L"n߰8N *Xz-o#nNbw⽹d.RP3r9=)[B!WZ.c^A{F2؆#*x{oy@ʏIG"Ma𞮐8^,yp8GD`֭={mWJ@YfM6014Ycy[k9AcbH46cǏò^wZ J~L) ґz+wˌ}R$wvUc' |%̘)|i^Os#xm?MWQ\{_y:jF"Xi.^2H<VIr$cB/7H=eQSN3|,>^.E߸sU};J@"gX)1f+f P|ې0-1Tڲj5nr5!kUt*4D4hH^<Xߕ[ L#p8@B Wu5Qo7-N9+1LC.=# uH6$m#BŵLs8G#I Z8^{;/ۗwlkSk֫>Ws- BVlf4О^y־qGCBlȹ#|˷^_v ocGK hi3xMGʼ͈{㔐tMbC2-A29Z.)s6k>V#y!N#-]2:%gpz=36%{ KNKzN%kS㏡AA ,7GPȎrjCIVdVspNG# ph&k뇉 qYᙩ\~I$>Ow.0U.?U ;ʉRBs<5Ee!ր`_xl~smn Nc㋝9ȅ.O0P 5Tt9%G"e; ,F[K= g͔xtwd EK}19 8P"|qz ?R7x.5&gq*(G#KHKT|O[jpNd XйP#h(pB2ډQt^ z :SW\Lo?R'Ÿn:)V{rР9;dH!E;*l C\o>;[J/p @E/Xl!RЗBsX^_nCb؀QȘ**C\x+?%5RTkBZ ߝ$'`(P:k OYJ.?vMixy bavc)͚:ٻΕf~&!-BA=NA3-@wmZݪ@z v8o0XX~,blV 'gR G#tb# ?7PU׈g(x+t'K'܆})hYt0m; }QO֏0>9ID#_D$~lHŦnj?΄xNkkl:je3YFMz6^-}‚CU`YsrF:Ly=Jty%" FQ->C%,nf;Lw15W 7Puc~%߂e9\pC 2ƢqnjZ|}X4~_ZUv$+fFy_ie&nЍ;mu:O+n³'K%c֔cz˶G#e\ڦS11qץI (xO`Nh8JO@΍%L(CU]DXQRT2qUzm3 uY?-,jvzoa_tn*=&[r[ϻfܸ!Җ![ ]Kǎ1%? t~!6# Gph0po| 1#  â%agfDDDgPɣ@j v藏c=@8f~Џ! uD_֥()=/G#:VXO<9 &eb\}}rv`<m=juTX*'4VI?.+Jz-G ^WM0> ,Zy;W!ɿGbQpVik.q o}cl@RZke@  ;x 8/v,Ugq[[Pe 2#w0PE! Ep2iwa8r?f`^TS;!ɔg'?p8[@?S-8/8u#Q[¦\D~D6>D;cN DVU? &TW!%|X?$G[ `R*iyړMn8^>n'Z2eĻgɭ| fK/ZX;#s\©ؘe9w;f}ޡ8)u!1Q/ q̐; ?PZZ!yshqQe)< 1;iEEiۭ켭n:U+K`ϯfw'm T#<}2|hB>Mk4b xYg\wo@%543+n:(CvpǩhDRtt-IǸek!Lcr3Q{` bU7I8#G#aȜeeEMu@ju&TOm:5D[fQZOr;W ! 4^jE bc8ݹ~C M;'S!+?!b+WCtEa ̲:? VoV ~0HB1`\oBnjlfԛMz|u9@_C`֭={v_S m 11Nbf\p `CcE$''#!!Ԟo3: >LGG9TH<"^͆!xHp8GnAr&wˏ6# X*zaWyii-|PgtG#h"`HN$7:lk9CVnڥ;7%)*ɸtI:ыvaW#AuJĆw2<=W& §{IG Ahë9G#%hwQ.銘Nm톾$J{Sr("_GQ8R4Qy!>iWSxxwVSPKKa1 ?_Y0fh\TB@`< ?r8G#Ы~FM>g('坸EՃk(x#Yfڶ D;|B P㣮pіd~w;"BYsbJXb4*]"~$;\BZʧ8Ό'8۱^4^.f{}+O3-^2ys1T;Yw㴰*`& 5!RƾsN3[ \?)Ӯ#[jp<np G#pzG`$D$ۥ[nCE|Ic; }QO֏04{PkT 5O>:0.oA7< oD{^AAH4?38vҵtZ+~b%G?L\b# E"kJJGթk΁j s\L= `/,%?PZBs~Wq跍CU`Y]rFx< Ægppk H$xǁf7J~ቀG=)C p8G  9 ('6T| ^GCEcqS:z*m1QuqB-˖<??&N:[Pҡ+>Afpxy؞.ߜ/VFsNʑȅ# ٵG/nz \ڼ kuaG42(dV Za$O Z3Ј~qd+Y(oY;t>=u)w(3 ApO}=xg9G#4֓'Ga„lQVBΎxB9Cchdf؁tRCcWNeXةRQ?A ' :pvLU.$Z}aЊVV^<7=^hk C'sU*mfG>Ӏ>&[ҟ%fHZ+n8G#PE.R_D#t< 0Qy<Q,hOK6|~Aeڪ tB`3t 5G]Њ$T$~90 n3ˑ6.&X;_b>^fr SIZa.!)># J(2Q-Om-|g_2&^ %M{@NKT\D|GctsVqxW]Gl/Jb-ȗ7ia 0[,6 OXB7^8GЏ @,9UX6J"u |K9y?0ҶE[[uhL5fW&|tЇ?I7/9pELu 21ln0tv4^H?ycܥ,c#BDŽi +pNFG~<DDaP d4Jhe4zBqX;%#)9HR끙r236Q @xlR~,&byD@2-πg*V׵@3vf !I GHk &dĶGdً'f]i\2&ej IDAT%cfdUMX,Ɇg0y,ԗ\3Go WZZH~,4lalnD sזq dm7pO²i%%>:܀t W =9#e:JRHW u U-dߎHLK_f=LX+1THԼTnԦxN66O5_g L_Œ)bGɈ&՞1Ix=p7Vxz6dMğV+@ZYfaРABQe&?L,K1X,XB;RFjva9>@i1:4םA+ .z`2ݓο6-oÁ &}(\ADS[.E*_crF@gQt^1g#iw+:owqSj:u"r҅H 9u7J0Y u2^T3U4h[nЭ8p ܊L:%FDÌV `7/!aZ,"%cej0Fs5& PC׍M ~ 堺r) 5ƹehkka]B:~ymM0-6G­E"ьO?xƽFVA>dڋY(t]︮-ތkVOAI}52'Oy ³lB˥%c֔czH,o9GNE@G3~>=wG V;nh!Gu~!6# Gp(E=^7 腘d` ~Bڟ'ϠPaР6؛60. q3s0je$d$F'. Yh26A@(M.7yÛXQH! uB)QHZOGrD7p8E)8'!GZQAY<ݣ{ʵ>cu`#w(3 Ap>v^/&ć ?;1yd{{lqg_7Zt?z2>rPbW;Ǻ@yeH؛fiV\ܪ;fݿRvrD19I/4TRZ0a}fDǬtdsG#-,⒢ޔwvoыvwAo+ma9 _|Hqor9x"3kg℟^oZdh pMZ9:ȷ(\+/_'`Fd) 5T:B0M*>ht  e[l{Ru:fu!o: s(Wf"k$7}m4NsR 54kA}F{²yQ:w8R-C)A/i'1?p8A@? Gf"Jg]?L]\n=9<3]K_0ܧqުkWKE(.HƎ)X CޝrA|gMQZNEq¯~m$yX||J:|3d#pK-َ)n?`za{.彾'id-D;fB|ɢ|l zFVQƛ‰^Cԗ:ۏ є[aӝ/]QN,6{Y"o7O aܽ@l).܊ /@|cHA_ 9by W_cF!cuRX5)9P_hb)̼f$c@k7Pu4k8d%m T#<}2|hB>MOűrlYH,ijsY0D䖀Ik*OQڢ$EbD<]`kB~}|UD#:ϵ$ѵCܲCص@NѾb% TP?czqJ6ALX´Xdɣ.)kG#p"pUuWP?M[Uܦzڂ:CSQ2++Wb# c@tamh8-(tI ?ݾzGxVdyPԖ ǙBiXaMǺ[l5ir&GVE YVegIIAtz `_}K*mb˸K ' X(KfX Tze,)ȼ_`R)/a&ۚBjE7o/n6T=sqIk8rN1ƾC[rȰzzy,\^}ebUӫca`"zF+!X;Omq,ؘR#2lB© O9bN4$F>14qdOn%HCsq{WZZE#p8;[b}Nys-SfͶ~$['p-7QS(i>Π̅Bt$]z [5[~!J/jX|շe _ 4_&'q0:v,)FOuMk/ [sq]] }?j#%HHHUjˏG#p:DL+CE`@`juCHzzbngۥָ5,N^{? @TvJbi@~G#p85Hkn8G##\1;܉0!vV˝3ws=p8mGfQM():lKYfBFN”\>Rĭ3K7{aTqd[y^tYs?ƹE :IZz)P^艧?R]G".>.]6H~\2LSr$*=#!Ҕ[Vj+H >ʢtMF5r~jY]hqѮh/&[,z ?E!rO3`8ڌG#pG!87Ii~–P/-|nΟBn_Sxԣk ,ގ䗘OzOQa3cV:<[G#p"Xr*Ϋ@72e!lJfE뢃!Gs0` qE[[uhrå+hhuͺNx{'A*X*u:ÿI7WQ>g$8!]__?V!j]Q3 M=D@ߋ˰e]9<-ayo'W5H䕬%~곑! !@ZZʿh1¾E m)[cm+v{ٖ%DZF҃n.gǤ-<$={SK}XcuvZ ]l_!o.r~Zںtvizw6V~<+^ҡno+1/NWp8C //xuQsfo1˝_TVkOm}b6{΋ۻx>闇3EMr#kYJx7@_m䱛j+)O}`_QQ}[ߚ;}z[__oVfի_m755/))InG Hˮcgİ=[H g06]XjP<q%R`)4"v'Vr^lp q _W%O._F TUfAvrآ2\~}lv&X,j?rGi>S3h ]1L#2 E5k[6H.P9飰2%⥵??p8w:T,^Oy:_mԉ];+o X6mccVo]5=o+G賋Z<^C лUm䱛j+)t*4_Q}|;=C(iL] SxrIFy.Fxd ф|04iwhʱAfC'#!  f-:_4?&A|o"r|j}D@"Up8]&F@j9griq|!RlQ\yMD)~dLyBš?RWT{[Ғ!{};vUbne!DbkNtw"V$!;1G#p<gE3^4 o;˅Y~:Wo?4™n:)Z,OD>sEAsw,BZjpNdw*l CX7b-e4V/}Q`+js:rJ毾BT)h?ĥkSRs yQ_*P, ڻ`Ag:K8G#t[nCE|Iɶ?ݾzGx #X:&"i,`Cah*J@fefmzL؉wV,1^&je3YFM*E܂(UՇ,H$$ :Ly=Jt>ziB^G00QbT+KX݈zӇs{ft57 5G#pzoiul<u, wS:z*mB$ :7L(cB-˖<??&N:[ +JPXf6Jy=]nxR;kEѳ@3YTrĄCXd*ѭcN}ɽ7PnBoVK^p8G@@c=!yr&Lň@ +y(bz.gKE *\xJz7 LJca|,@0Xk}v>s3V, RY5bD-J \i5G#pÿѥnd4~-¦ !L'_Hԡp` N=:泟y9B? &Tec"dwunьy0#~=iK lk =up|>KP8KYZa^A$ 8mɡ'u/Շ]*ձ00Rk=#F_!X;OmqdؘR#2lB© Or1 ԉGQ21N2' HCsq{OLv|3~,hn]>8̯s8>|f͔ OelAu4ZZ-Qvg1&kKۧWȰ^C"MAV_԰ f6o#Jt\n,`P7tX]E'7zad/", 0⺺: 9|(-;J'&GBk#=TJkr7bFTxch9k+. P3㼰\ %YqD np8tLʲf h)wd 8H}3Dc2sʢ<&1Jbi@ٜ;+%G"fV >ڕs%fxƆn'[[hH^`.5&gq*=~p8G#i *nPu$4لI1r ;`k84;٨&+IW&!J' u%J.FZ^UK[hQȻ)o%VsHf :!LF^t5(M{<s x0"Iy_I&ЪCOM|~ <6 iQ§z(̵7=A <:N6 rS7S棐=7 Լ G#p#@gzmOCƣaϛc^A 퇠#E3ŢܔkXxѴ_K[bݑ˻)i-Ŋ&)Ci^2;&1 ~=0<y< *(0پ 64$_1>텬V<8A=3c!lG[Ь/*#(ļDxiCFO9G|1ܰЙ Ϗr[*xY(3Hw~%iX "nh!g 81XS] `h6 4b xYgY+bjcC\E/v(xCшΣѵD'®ćPwR*2QVg',d}ߥЙ=i*ᛇ$#tz @̓0aP(\냐#cg蒴 H]fX[]?D  Ql\~ZUq.vJev۱$q_>J%x1BXblNsơApQtWdt67ae-A97Q7 qȶ[F\h6mzvCO@'2y2ӨŴ JVѵq \XZԛM8ݨ H=&@ ֫Şx G#+0Ј3FZ y)~zh?6Wҟz:qsU>j0`IC<͗7u7lZK'ok䗘Ovޣi*ks  qnɕitbҽ`e!0r2sm 2Job,zaܠE@>(\8G#p8vLOڎROG[_zoWqb`j/>ϴBǦ{tR)ӒO6g+|~*M|k RT^bp5#;[)X2FZ-OB !?9G#pҁRLëdR=Z:KRUX$Nڅ‹ Mʳ5C]'p96ޟ+=tvӹjl  O ƶk#l9zQ:[q쥳޻@"o!륝(V~0˧I 1c_!;bG`Ag!AFؘ_@#$>+Qz]zumYp`e"dÑ{pzqrߣ ߋBVTV\gv\95}Lɶ\,G>4Q5]Ub4(ڒҲ/)lh1`%[:m7ٌc?a(;Zv-Q=j_aAeXds r HS~65'.B?} FtW )nuEe]C^K7!754LW 5=%WeARL״QVs&L&8[LY3 ͕8G#x,y@L-i_EYcU=wF&8Ndz>Gŗ$ӜDj}1h6sTmlƁX;nؾ߫;vZaQw<(C1c`ûA$SlsHۈkg1k,XWGOm[0/%F2PJ;f,ǰfٗz4=Ktv'u3WFfc&g\w&),fl<`{h_bFTv7oݍ@Vi ƬU{[lY@ ?NmDt& F1bgG#̶ߢ{*G FOG`ğmx69j=3l[YSE |Wclw˹"e '>GNJ_aaLB+{:K"hPzQ!_?tU8M*DS~r@H/6%de9\(]-$in rO(N N>0cd?F޴p8!I? !xq/E]_QE ;xW|JYu?9 #p@ z6ft#rgfb֣ARąVv"9*}qH ;k:L\V&C++XLMFA<&9Ԏ`e6OKTbޞcL}?0Wz0rSXM;&'@1\5q*,1162h =f.%Eux51r, muH3K[,d`j,6lf er 4mіM+yĔ|׿.#"0?5NyiM[E C"rj TzG78%_> CP Bs *n2 gw]m }2bA½(vSìaN:4xs0 S 9j*F~=d"CVSrm > S+ʰdVpO)uC=o:p „c+u'_5p8@O!@iG~J3B:0dUu'Xa2=9 hhI/fhk5A#qg#8!Xl^=LL;~'%NxǢlAw)Ke ӿJW:7}Qł=gL_AQ+~8[TiOGh|{F88ҥF؛'L@e%(9!_w#F yJqz3c}tlp2hBZt=rΠx|MZS)~[](˓|=шe3JpD|0G !<~Z9弨`G#6 0vAѤ{O@(N$GkJNC&o]aA4n QuVNw5TnmP1UoezDc6C# 9ґU(R]J4cߠ9b)8 NB9*cne)mU G#p8&W IDATsU#e㓊Ҷ1PSSO>>~I)l.:2xJUȠN_-CjQYRFߌ!3( *ު?C\9;k;(!)` GZ 4_5 uע 3+d^8GnBzSVWdae+;y'n-Pt78[̴ܵYj^G9٩~&+Hk&f_W쫫 O1`ڠ "$_s,Kv]iax 6R=99݃1{~KwVgڲӆ_iCބݿVXv'.`cX@H[rZve_+׌`<&GMWi ۵ѝ[OVD'Bł?{cմ@9wѮݼH{!"99AAA)6t%c4L8155|P7;\C8f g@ku.9]e8_cp3 ht}i]>qg+6Gi;PJY:]lާXXsg/`p)YZhRͶV-]7qo59ަk{ŷo:rsSa=㝓&8G 9'! {/q2p8GuR>XG>7BJ9)^PxQVD<;rUy.(s?ƅAe:˘şcroG6tiNw۪u:؃d\Ef}3;Ts OD\E2 + fut#Kϵ~}\&Es6rG Rz{wqTG#pG%; QUy*++FSB0yEi K 0iT\:*Q{٩Sk::`Ӊ;[% @Iӵ%-0G<]?˲S_orVboSiw.l/9ʠbwNwڅp8G45ĉ?Azm8rv'"zf. HۀdxB=Yɏ5ebZud:dUxTjv2ڈ*|9ٶq@ @o_{ fCP<HUݼp7Wk5T8#p8Us\o҈6$Gezh[&,'ťD/I';Bg~x>0*f1mG,FRߌf2/7(uskZN-o!8xf=;2UqDJ\hu8PI|fRbc()퓻o:s9o#p80,$PPqn q(Ss0s}翄ϜyÐ0+ڪPF.毅gnzl{{,ų&Dݐu'"}٥B;[k8E>$!gl_ .gU; hBGa-qо*bG`@.Hci2>'YWx!}쨒DՄ5akqi?vk.Hqrː !p s8G [rZLqn4l @,Mt'/NmYB򯷚4H|zOwlte;OL[AR:~-G@#`4y]#Khr;L(1:鱷:Ne#p8} mf1{Kx h"K [6f/! OJ^(uRnb L$l8}vq8@`/@=[lzzd:ʶg;=7Yݓ[)9cfwP4rSc};*2(Rkp$`oK1%8\"A[ KG\`p$VŔ(8<ȁ q8^IN1"n>|)GBE&I+ 9e=_|iyHKߊUnJk@l:ygbjU%C^.C*i/isg 5HC''75\'(9!wvL< R@)MUXa)͎/?6#Ҥ9 ;gbnҝ283EČ29yAI؈*3}1b7 HZ dm:&VlŲg`:ɛPP.kdžW8@@@;XO3Kj`m‘-{ xrJ_0~Q S;7/./o``Sok4"3ziX vyEՈ|sblAƓG=>ȕ" 9-qZ/D ] 堦r)tX%hk1rYfXנeqALŨ,ۋMi_}Ć@AOr;#jS&rHjM@F|,2~5SSE)x(8tZx0  \)~33Fbq8N!`9<̠cL/\,=ȝghd-A{PO{I,A 2&z¯0H=i /J37lc:.>N:t@RlNݟ+M)Uc1Hxj0_cN%`x0F_p|cX^Jy Y/Db؄X>MZk@LSWCf vL(n'C!HA=QtB'q򋏉FgЦ CL(W`s%E xuFxe|%K!@L-fgb+-{z0a zqp(Vd=P;mo+ ; CD-+m UI&Xoz:[FOL@Ge0=k,럷B<-ҥ ߇40_h@>B7;,׫JQZ;I`4cߠ("7VO!X(;mҎŻy(mS8x#pzz;#53_b k=;5"4c JiY%\L|O)0'ahA@Lnt$RZ.㌰wߌҢVz~y)iJ\hul'ШwX0@ zej/1RGlXacSAh[a!r ^R=ǀ;6e'O*J`CMO?$㇐)Єue)0(SBBmE> J|#~3>D҆ťԧbCqU\G*޵:$E?dcZ 4_ЎkQ!%#x#xV߄ \5d]A%+ҐCGT[` 5P; "R-/6 ?e\AfW*aX0H {!G55)yvĽ(v>lׄ5Ðm+?'"LאvT g?>:8d=c f vFҶՑ8{'>SCwsPlbLO8M?D`H  kü&sd D@> S 9jdffZ,5/} N+M]X`d1|N+U.bal$ɽN,X^6lt#-CЧ>n6|`:!H~ 9J 2|b(uy@To1A?Cp8IzנhK2RFiD4e^|r$G%AZpiqrfl ᜐMw^47xn `,B|ZrJiƄo+wW-LXV`r: !BQΟođa0ĊJy}8T.&IC9R<s $@ iG#"`S\C!V#m+Qs)ggmC鐲zN͕٫f'O{T炓ie_ZgG#F=4F`상-:B{_\Kd[p-G['j(>\ 3h-~?. sEK}3X/1|%w;z;5<0PVGT,>ETv?@1˿wEwl>rm* QP>,m?N\ #إ]D.߇_XO[>33Vx0앯(Iҗi^O֒Z6[L&Y:CJyZ+=$6)9VbhnK7Xerm%֜2ւIE2X'/qH %bs{ JźAZ4WFTO{*٩ѡ\o࣢<}jRN&]2P+//[kkk~͛_m|տ IDAT&3_/%%I.[]D^2iT.EC4mmuxb<&m9 anC٫Gw\e,̛0|,̴$[v-Vs J-d= ?(GW6p !4ltl;Aͫvӷd9G%hRL2,5ǯrVr|HiFP vui5B~,o ׽с-Sq8Yy#P/@Igz8%xFsU-^cqnrD%@T)peyPOֱ cC6d"(x5`hL8UC1c0$x5g\Fp  D5A34C9^UJ Qལkl,f|_me<_??P>/NdLnrm9쥝\ITy/g[,h *1kQX'GM5&''D^ee}G![VSKO {8JGGtq>0㌓B˴/S9u=_|E0Yp>^UQdSA1hyߩtt~wlC ܇?M(闰1qv-'}ttF}p7W!_!PCWt*P_"9=<(O}Z-mnfk jQ<ֶFv M[WRzZqmֺ:JFnr#kj&Z?3WoN Roy^"_SYn9_S&uBk y37XWfDE-K YֶaӋoU8dնjaA+%V)}Qj9|Īa3gJ ZN~wik-j$.#4짾N&yR%o0RQ)O;j4,(K;Z__ӎSfj:{@@4崉ZyF2v.Wz.sj Cpprm2p8=ӈ\cAݧihyIM[.#y?n( "5ܸ(E[2Z}`{\h [YXaMo¼ ~;fs͊˶B)wX=bY,ٜ0_Me͉O =8/IW2tWuDf?!,v@:ĺ/ iG#t Ɗ=wƒ4kCzBIנhQCX#p8#0?$!q(UDŃ!RAƟ翄g)P BsC; & Ӷ`ۻɘ38e3bsQSO+!-$܋m?9b[U^4xs0 Pnr5CZ,hGu wmy8G~¯khmJ=9};c#e'+qci/6f/!9>+Qz]J!'m]u[,82xء׋Mp8wеҝJʺVl~v zZ]U՛x |flA,|&v܂gرoMJr&?BKۏ͞+ mo םW|EC!5٧:p\wdohnnA[K 6L+BfUy| IdM ~Qܣ "]X>(zmфf3dڨĄwaEO }Jʹ*ϠeL0s6rAw1C9?|/0@r s@ ^L6%d߂e9\(]-$in rϜr܋hϗMwH?߈#`1`>0JN+vIty=_r Ah/ Hڕ3(5C 'iA|Ȭ?S6]bbF yf2tnb y+/\)p2z&n_ :MF bTOy(s!0 G#! l;By/=tVͣ4V\o!ݣx={3VJOzi^]!)24ۇu`e)l w|a!Ct/ق0Ar7mw`{*քEz͖j@ [~xp8"@`t&N J֋IpN E\ڶ7~愧YwTQ?!EIwx`Am{:.$iuC lJ|ɲaz?67`*GH~t]Y֒L6bIu>f}J}y &C519V-C ]K!s05, c-/ggjnAɶ%X~ylAOi+ӱ6"c?'z&tIA"_[Qv%&(d(AJbg۷Lx#c֯_r-4FD#F=4F`상-:B_\Kd[p4wuJ01I{Z4.Cp%۾WS jd<%EN ٨ 7C|aTҷ--7Q|ZxLBʱX],>v?8{t @r'@e]3C6Хl'VI7؃ETSgs>1U܅"vFN5 m8q^IERV90vt$"lV|oPX+>`)8 %cCڱx2%㹍 r8 0,$PPqn q(Ss0s.ʜ/ ? 9'!aVjYa] q6 QS)N__NUMb(E5W%.81dZdmy]@g!@ff&M*җٻJ_WRkz:ǧ!< G#? 9}ws?E,Ρӓm! KE@r Հ*p8h`mNa xrJ_0~Q S;9]_(O*s"?&ЙXۖFdA= 럟=/(|nw\aQcϿTۼWC!>7G#pz Uf}&BEi&W,щSN 2Q:gD?QܣAJ6{oy@J-j9{ߡsqA苄c[5X |cX^Jy Y/Db؄X>MJ9LML:[gT vrݎ72|2p8@rk,4 -#iq0mX}h $GW~Fs2P닡4P'@ mB-K<m &M[{1f(,AA8*k{Cmu'ažb<}0)p(kHT&=ҿ ;T: sג9G#(hj ' "?blm8rv'"zf. ٶ5Bڥٶk瞁TQ?!E4Bs%LEC?,8,N| 7[T ˷ fZ?J&%E bEӈ?y&JC@P,s CD:0xh~&^"z䶪]&碸^6^j1]Gc}=h.#I"@Y#G#0@!2$/.%hK4jWOE݊ IDATYsOŧUgc0igm&F7:)-qFػoFiQ+f=Jt(iJ\hul'ШwX0@ #zee"1RGlXacSAh [a!kP)9cwP4#l̀?BBB?__$n,hgGǾ l1~ %@$Du2";JqXHsцW,AMRDj<$MsKV+!c(YfXנeePRFX"c=hލW 0oR\hO\WM=#nQn~͎r^'Z[S=!|p72[|2 H=0(58UՔa":`1s*3J1Q H-N*x8gb &U R۵춺 78ez_ "FMBJy(chdp8@ACj1I/eH A G}Tۇu`e){'f=6u]7ziXRaPܓ)$^9zk{ lېTu+JD̵m;04,@=@ X-(ٶ˓/0A>k; iG{Hx 8y` > ߱E :wtݭBG E 1V^FU|)e/l%G#Ћ>ptjkj@ܢ}8#Ni zjЏ/=9/>ϴH0B+du'3vvKU(R];ZR-lV|oPXkcoROS UX[G[F[ ]t;SO:ǿ)  bRP()ZyJTXPHx[^i_r?*T+4^%($VhKh j"l $$0!f,=gfgwvvfy;gΜ|y9sމBTm|NQvG=ֱh'1;l5XdֽBtVߣҾTY+v` W,-o|(_zՊIqy$R?J/`1rS >d%2C"0yc{ y È[)\e5Xr9m]،Cؾ__HŻ(YYxf(P;|>gs9G#}o@49g0, ſ2`r] +$G^/DN dayp,@CTu]cvr>EN{3g{ocD |ب*=į>uࡉ'%xei ^^/݊臷Q[ebѴv˒ fdȝx9}Uv(E 1?/d r~+ش]|eH>NŬ2G 93kG#\ر/vmO~$4BluAp=j cyP`c ĬM%TJ)GyQaaeUm\/0h Mh `:\ZeV6ϐHY[vp8AG_H;aysDJ qz2BR8V *%Czd 2=6߆Hl)_mTO(WxgJ1TERg#X/2'1 ylH>[ΘpW-@J~|U2Kr oQtڡ XSg*+jZl2d"\)BNFjq2F%G 9jagt6ƻjЉ䡰|0w6Oq8݄˯H+"`V4ʿ?.8O-m50'BΛs0dl9|kߕ/ݚ'2-X;FlQ\GaS6 d#-2䌏\9X-ߠQHVJcԁk]T׌?>}} 2#Jth̑wN'&rʳT'Nv6iV&T\ܥw@CG1t7 -E=moLvSZ6G0ebZ>L2Uu? Vm \8<4KE@ Kb,IX1IM1gGnļHYR1uh4a XIwOV ̩DUWAp#g`7 oC,m>+,㹧"z#?muRGFCg>CF_7I$ʵ*_1HhncKZ8N7A6J^SDwiM&$2Ϝ2> 4q㖫菉?q-Ѩ' O7aㆽ[r֑]L]̬`ite.DZ'#hه&yOxwficu\^F8ʹU(>+bAC3@g6e;Y6֕IM94GD]S=@799NzB<=I38DhSfuus3$ 1P<@{΁cf|xܔԢ:)'rCVtT1w]ĕtJb90husN~ `pc0bMRgEȔadaʕȴ;>;^pL8J\W,19}̯ '+~6j|$.$X~8"Z`_<.NTc[q?qThe;)7*F=u V_IsBL Eas&=\]6nL3lI9~,L.q#f"#y!‚1q ѳ"_yHИ"eSҜNZ*jyqH',ˆYk%CQ"qh{* ~O`Ȯfc4|`F8%ۧLJݙCᶧOLgA&,/FVí'~m8$&֞Eɿay9@":aXxpAɟ r.LoZed7l9*-%rU GC`(P; 軃sؙ2b,?G=2|j{'!y + $%J F4шD$=lyI$ _%A.Ckɓ?aӆWaZx cJF/$FHelo (h^lMri14vU4t441sDyW-&DPS&5#_Ή|&#MC+JA&pO<.: wX*xXQ:0f&qA›ňЄlqu'8C8eDx.D.݅3o0?B4(5l^9(}9sz)1RZtQƍiIl-EM(p({ҵբD-`LW'(ç0c Z\" w1#E ?y-G~3-:݂?҅<{eejlO U4C@phlV%2Qnr7 MC3_8F<=+iy9+0W";˄)oSw8o*l-g|Lj}OڊÇ n`Ъ/_E([I|v4A"Rr嬒XQ qҵ*=ssbBgWwb뿛ȥxV=(ACL2K0RG҃H yvBr6hz-cMC3/ j#_F$y8g74W/C# 11Q`pЎ3((/pvw byFȭRGUV՛?І_ B[m #]F-F5f֋:V/h[w[ g;iaN#K-Y[xIMy(ł@b,MH(f^ϔNfm^geQ"Lr^;x^ W#qF}{P]]EaРAYo߾M QOlV-"89NF`ZycvԛA kisL(ֱc@r{(tui1 rAC3RgI61K~=zJN)r ϴ*+ IDAT iY~vFXb32XY}+)wUy8}pz:K48DPCp=]+?K3eZ9!Ck"8d?w\Bq9җCd mOvD'G#p ఩#{{zrWoCmO :6l=޺hi>ePǟ+V ӌ()E]QS.5w W>g?k5ҡXN2 AmЫ)F<|Om`j)Sga:q 6x7QҺ>B{zhĒqrph9a 璃86bCJ@ϻ E^LDnKFکX(Ap8GL14 "Ďc5rbV[Hq3eX7uo[ u7oOA_,⥉Dž*iY,?} H)?Ydr2p.'5[Q8inn~jܼ5"*ʆy5(-7QAHH9pt= w`5apZ*^L$*ۍl u%)n)G#pZ@`%ŗfH{pc(|BCm"C \"QX!_J-{S;cU]Fd,]t(YrIwQ1ψD+^\HpnIM ۉLe{goU"exݥBۺaږo'FvSR \5p8@B0Lg8OśMR8PdY;//g.O [4 u(Kn+鎐I`G.#D:Z!uso ~!ab~5#ʩ)ٲSdHOw>C p8G wEԃwG!K=9S~Iw nhDZWԚGgg:z1F4_`9z^y%SmYOLM~x虛DAZh86Sq\Ar|}mC$ gxH<96eaM5! /bz)Zzr@ p8o@49g0, ſ2` ,XJ!@WHN_ BQ.XñhXJL +܌L z rEQr;BKT/H![݂q[E'(-2䌏XQqed%DbUbV8nNd^_+:ꚱX\+hço@Ysh}a!60#7jO`M,l-g%N|Ml$ީӬMK 0c v1:oZ1z8*ކ즴m;.Àq21\t}L.΄^9=ٽqx(H7csa)m4 |$ؾkGnļHYR1u`4a XIwOV ̩DO†'8Zy w7>洴BCmt%%F k*=8oH"V@U Fh4,-Fss =CpXq1/QX MdBRz /̹2㓬0OR̷?q-Ө'c2&C<ބ.Xr֑]L]^or|4_w"JXI]׉Ͱ),5qcu\^Jp̹*T?[S1Is(D+pvh=P]L*ҺezB<=I38DgSfues3" 1P<@z΁cf )R6%餥BN٦c4SQ+nf7V/yX )9=p|Z()k-QoMH$>7mv100K]S}4(V%#sR;kaA}B$Zxz$0b6col|x/$zQWC?JΆYk%CQ"qQJvBj(tO`Ȯf>hjklh o2)tYNGvdN?ldU ^tbMΆc9@TJ7X[Yf&)hS~p8= y$$jjI.8rYWKegBmz/tKZyn`EQӭǥw5 ,zcl$x!Dv!< D{0V.X<58DR7&fWwQG LE@lpdn9Z `-V\eNk6lˏ3S> 8u1sZj^*аMCp4!v̰%q^趧\a§&@7x{@2\ٮbO:xJD!oG0ZK⟰UHZZ=< ^6mx*k{^N1gW@#NXڶ7qq`/ X&9 `Դ%*io9oyjo")#r>dwdhh'MwwL*xX"Q:Sr2i&i#[$YXj MV8G#m|j X~4晑R~ o}=1~Ɂ4rbV[߷L A;hA+Ec -BX*YF,Bюt/c{5N2-nлndkM)~*iꢇG==F2ܭDʀ9+vYM%qU:>9 ٰ%u?6bɋGDX2n #`7C2I|V`ٓco!z|U{\jQ.\`,kُLg|yGl=EM(pP_[/l(=Q !~:G,/?)iy |*3Πe(r{Ӣt$h'Oce#V[gZ'xyV-q4@уcG`l6Uz E|M}ΆbJZd g 矂;!OuxC/^yVaCnC/YE Ld?`ⵝ4Mz+JPRqukWA=j:SG-Faã)q^+'MN_E pwǷIw!83qU7Kh*.4 Z<§(wZy$`SMYfGA^1r#=.( UQ奖.zX9MKaz:1ё F6ryg8~|O8a#_c[(Mi\6AwqOыYꃩF"}s9cś>S~¤Ѹvrڗa k1XVO#STԪ|Y=!b }W| 0~(Y0SEa.Yx"\UѲeԁ)tTah 7}=D C+=AكTf#b!&u%FQV3w$<;A!q9Ii9:|lWg2r&vP{iߧێTenDfiKHuWVQE+V$f鶆.zX8%)(x0sߌ:u<0PBLn!y kb*;( Fd_O W^{jYʎO0 11ZC$45bۅFkV䱤]hnbʲRoM;5'S-]GnSScUS;l1዆>fo-%FFk--}쫽}ɮ^IҬ%Ҟ]%%deeu`ŒY'V3)]tm!o [Q)?ޣ!m <&;*/`Y,Y^k)DEtm?'N۩w_l{ɁSB.\) o5 ]E.hQuth>W..$}LUXͪ_)\zfgź˲P)PU|ӗ~ Yp:2d8T}uӢ|b*<pY:sH;w /P]]-\rE|W_}% Od#66V`|& h3ȥZ 0j\xhY3kisL(ڦ\r=J1ܞ:T;\/58K PS$G[xIW9}+)vUw8}\ .i+%UdQR*o_}s2`m@eЦ{_P0?e(O_8*j3tB'."WB!ߊW)…Џp8@`ǎXxqW<їMcھ41.J|:\o#:Qv*3 A۞G#p]GbGQy"wF=vوw]u؅3[x\ºT6>8!C[܅j$ֆ#fطGbGO[ V-Ŗ7>Xz͈2u!\N՟#D Y+v` УcqBȀ0I :"CmEӴ=ul u *<#p8s'me@EK0%jǣĬgʰho(A#eam^(1$ JUts V,?} H)?Tge.!o$ߞPǿPYFq .TOJ_OGRŁӧQs(?7'өk{0Qs㣏fKdHF@6k4<~p8uÇD)' IDATSt `rJǦQdJQϢ7@Udv6ǥpkІZ@h‚^8FIUe&>0pJ,FJ-j*wuwh,8.1ψ>+tXi9T|H2q6ڽxOv;vŋrwSvtO~2{ƣN_V?_YeE_<)!m%|zKG!bK :w]`M;6%똜z?q7Gsqmm-aQPPx+l6 ~p8G@-a`Ybinl)MA…"oVj4=QyVJCプe0춦j 0~= n\:HC]=ikaۗ G5gؙ}J*eOs8GEM+? <*??\ps$qjg8E#^[pAōhdDm1]2frǟl!I8f<p86:5_0p9?oil0Π(-yL!$}54~dLiɑHH|%FiySryS{ivp}%ˀfOz2'Jd#uA`e!`H#p8NT#MÄI?Ę!7c=C\f/Vw#mhί p7zn/wf"Ns DLz#$Mu!r|[r:ΜD9$stâi ܄cXxNrȡ7[}[~ ru-o%Caaa:r6rgf '/ŋM7H-Xa20~hD @D&dik+ < }ms sr)9ؚ ]УUmc+Ah.RҖ:6Gns,Z P'"Z^xܥbYg<L _w)I#DѪgxhؿ?::LL5ԑ) wxId1x ylYV;7z϶SxCIc(ϽHM]ރ?EΫ9zDEr\W )&Fz9eBNyHL0ǮٖXQ KP3%ɼR/siYOwYe*g%6 {jlzFaXagՕ_'r٩ɜ$Qᙐ3넢 Ɨ3XȡC=%YuBJ ob=ANڦLH1vyO{,BZ2z6ҾL{J-V<2&eE)wUHvG{P?,5ϓMg{ݨĎ[ngʟ2ޒzF(klTU$cew WFG`A9Ȓ47 M+lT?Tv.Cj3Ӻ~yqщg1->!I!i)v̔][X$<6#>\|?/Ud.`\q:r2Ayr*4%cYR Rh %g#foYLlwn0^iu3Qs-T?0tZ<0k='ŒL/bЖHiTk07beJ!Ukij=tۓP`8BZt1&Ds=32 yϡ8gjS$ _L+q=E|3-˯wHE GIp։w :cE!U_n"ҟv)%gD.3Z-zk'Wb~)*f_Lga(VKWPTTŹ"C,H|[,aTʴxDqڿvQ?f5|o߾E?+>,O{f\!@3){Y81Y'd#ZE1Q*G雅ˤQ=ܴkH n :,g fX1/d,u5GO-F镥F<]2NPBS"JZr]?V(S4  ?C=7">3"˲6Xs@+񅙩Nd>:as4vYE묭ϴ1ʐZ#1$&IјjϢ٥U)O~]Zh=n\3>tigʟ2ZJyψhKe6kK4f #0עQGKp";+g(p)ٳȏ@ b ~T%D-% $D~jW[O`˵<"//aG鍘4t̉ÖByoЗ&}wՎ~'&*& &iɑ|ʎ"v zT)%J06aBEmC=eg¬"52UNyHbxv^||=38bkA;yD6.7cl3_̤X7Qn{_*yҤO;VkOg-?:QV6lXV~Hl(?y^񒁜K>Ag]zvub:Ѹ'fĘ@+([6- 1OА@.ii6 rQ׉oS# Nb,g짞^ uWϽCƱ#WYjbxv^|㹔uK*ϴL&4c#;!jGSNy|3)ֆGgkEV @os*nKhxrg ~/[_@]v$hߐgG]楧 o^|2<\Jf쀴<#qp􋒳,_FMA=*\FLMxBs#N9#yQOcC0$/"`{;(Dz6-'Zp.mmʳ?%FlxTY[3gap?ʪb:t*6秈ϡT|uTOF{UmMFw}kkSY ;ْH m%S)ep Z/9\ Atq ƒn=.+( (؅3dӕFu_y!nڟ`Ւ]X ј&G=-a%l4:ԼXV}2JÇkP멝'j$ֆ#f<?iιƇ>K/@A'9mEmEIT()Wg(^jvU]%Kq V% Kb˂ĭۂ%(96;MH+Ss؋9!Hذ)EBdo݄f<4 kH;11 [...bJ/_/D`8\zafx?z*)z'/~22糶rHt[{lKɿYOxQMO3 1w(XsF@x4q]L A;h\]j^dA~"oGKO= IDATH&>3.jʅ2q&!I2_Xml!Toԥ)s\H}7͐Cc tݵt'?'YOfQ Ыߒ rg] N_ ? 4?]C/QAi(Csfph3] my3_ANP#F!\:˧,F~ 2*6 00* ):jueo$i@hw8KQ|>o)ZŎ[o[ַ%Yz|UuYtryaWIj @Oi+AQ/_A>#^xlt.[.N[ 1FBG"GolZ6^ Ё(GJi]ƼҡOd9.}wUpZ[+hx1bpn6rEFsuo>< Cpnq4C[z"޸Oh3@0kPfG;ViWV> ! Ё(GJi/+8GFp8G#ph ^#?CʀR_SoN0,< %:[QSS&tD F46as'B9\ C+łwзԐӝd΃wB/4r1/G.j[I͕U Ak I;G;ZQW h O|=k;h@1UU J3QT#i\שglTjWU}\۱G`h!p -WPo4TPoct8;?QWw`qv|M"K %!e uAx:ƈ6{)α۪|t2jD|a75;@xKD.g B*竑t$8L#kJi gP+ɳJ|8$q s}mt{\LN/_a/LuVV JUJqADkh'sDݥxA텯DfB55[ EcǖBx>5UAڴ [WcLz23bF96b UTd% S_5b޽(m/hjE#<;ϗXys- ~b+,Cr"<"C΋;qyIWd9A~sN?m&Xu7`B-z?i,E@&) ìFuuxgm|@W*F> A니@J+PTa$ ] L&0V[x'cRd?񺗊`%/ ov+6Km Z=zl[-uxzP(x *lNJir x|esaIemhDmaqԌX`_DD}6l}&hRPUy<I[=M usPZȁOD2B /cqdo(Ů ,+]oʗ|'!!'p[͂]a}ini){*RIML$ފ :k+823&> vTֻVEޗsI4!<l[RҬ#nA*\qsˊͭM+Kt4 D̽1{=x#Rk(W)MR_> TYF2)MY[Fv6ѝKŇK3{6W4bw+"ys3  ”TPY%Q!1e8ci1.HO34Ykِo<@ ےT`^)Wq7yiG#0tt_82(nvqWh:IjL/[>J::,ɱ1ˈv/w_^u; ."cF:ǰψhԡF;ˏ\7 +:;cDZ\j8%,mJ0 bƴ)꫔*qn  $7v"4_JoJxOo{&oh<|dP**qz7!-NFUb嵠z|BГ HPi9 `z&!mLÇCWN&EڙAIw1<}bӲ6(xU<8"Kős# +4)#RNaގ*ޱ- ɓhw_obXѤ K|u؈#r-SB\Sk*AO0ُekʐ)ɧ^E߿Lێ_ @Z҉Yo 0)J6hU׌Fů.!l~.4h|/pM ,y|40\3ZuM[meH0쨶pvwD&crFwTQD-Uf]&dD:5fŪuTѡBxLV?x||E2QRڹ.S*8@o(((mA]a9רlQYko5:ںL_ӨtJi..ivTl֬]MPRM wonmm5IbӡCL&rk5]pfꫯLW^5} =h4zzzLݦN4\wRS5Rx *ZzS-F] jȓqM}~A4e vcƢxh@#m 8j%Ss% E^2oVZIX1ظJ,32vz^6꡷ہŊ~.pױo9RX[磨 >H/?#ӨtJi.xmA *kLkP_&(W)ͦ ] =2g[W"##W!m>i%~V,]-Dx4{܄\SxT7|&CBˆGJZsꐓ[31-p}B?GQlم>xwHi2f,?}5E9A{57 D yft`'[0khD-%%{ĊY},*'Y+Ϣ1G#H1^#B-k2#eŦې<:{cG6AlHu)DbGg4]ucqغ!C*,M[?h%g&&Ŏo1'oxv#B¼hbÆ({MHL62Mm[ ^^I-p8G#0`gͳCK[U#Ze0{T&vSw Fh$Ifțl*Z VZ#::iZ__j^ى.o m8` sN,Yd0D'T[W%Y4(.  9e0CYCJrF?A,+++iV1Bz{{ vنpdRr>{uL<)0㑁F@I0u jiG#%*)m'G;Q~`JߊmiX i1o}INSQ$$#c~2u՟?THK·C0ݍHM>jѩ]zLCC G#V#@2^*%H-FB"s8X=)xrlLI`ocoՖېF(L)}'oG뇽20p^"s8G#`'Ȏ;`iqTG~1XzMuױ{6g7J^Jދ\LB1s4DO|B`uUeѡҼQ_Lz:c+QBz.>G,{nCſȐxX y"_Jg֢P9᦬6O 5ۿG#pnU"Bd?Z^+/EGL~1miD8^ò}8KlK ->{-}բ`;rzDw4*WW#Z%<3b?GF4Jᷲ kkb$NDւ|2gx<"gk'ϐgbTڎ}=]u?Ţ GȌ;r34e^0μjG໊MxzbuG1:( hPŪ+Y!6N*";PR\+E^2H/e*]eׅGo:3 29]?DhDf{P(]]@0\gH$L&#EE¢BĈrb8*?23qMB{b9G#v# %vx\)MօԞަiksb/ywd-چTQ2 A8<&'- ;g߃7"HƯiUb{3wC+gkbl~b=3T_ -brZq@n\&G#pn9bYYx?cc^uTS%N { <؍G2qZ~e[=4R <1l 4 G#ܢhoNJ@,*.'G>:h'XT(AatW@o-M1M~}4̊c3S֔zqPՀrx~o:$\ `$M͸hTJ4Q?ft/&"jQ.v4ؼ=|lw lˍEŋt7>t g㦣,Be@FR_9wL6ȀsqG#o%=9g47z$w%93v_E{M n4:u6Vt陑ү鬄pEd#.)ns,1YoApp8G#`־mOɃ^kV"O G0 ԗ@:wv4J5QIw ȋh%.<#0[B+p8G#p87 > IsG#СOa` kṙ$& ѹxr>#c)9G#PA8I)?T:+c񰯔jR\.1w!+4 K^Ę|LWK~>c#ty=^iwǍ|3H'~ϼeIR\7 $2r>-t~zڰ±?W vHs8GPGR20~ eY>g-,%#2IYgyF<@DP:{ _YJf7q3 E 6|Z:0^/S?~SA,x1R'&;T4d;,gi6X 5`oAV&m;i;-+p8#pj _?-vP'dNbTWi_wp}m{& IDAT'a&s̴SMߌX*(>ם:Y+= HV řVrh<ɸlOfE#v/jxiaW/^Ro W^^Ж>W#~s8NMl FKwhy,Q4YH5@E΃#p8a"B$=)9^6͔q+{j$C+i4cP& -(HP\x;Ǵ mH_9V^ ´QAx:ƈEɽXlu_ hhB(t%n7lܨWɨEoa"v#k|K.~TG_C<jnh% `B־V0F܎w˪x>SU=O$l f*01(uN%TNx\)Wt,\Y'9!gvRղ xaw=^`X9Fz#  Rm$j:Zexsz$rc gd9樴1GHiuBʓ<^#p85>z>[ PՆwGC1cZE#ǰψ^H|d16ob/r&FbWaB i]-IiGMSlwtXZ82hlV2jQgvH^Fjr:.dePd^u .UT42aZ~hP6vR }WT }WQDA3ϡGUӪG(h(=|txDp8v ҡ:_i6? K\<;qBr2|4}8m ph=яƮf>g[g=|8RGB"E({>y\bA(,lG(2mqb{.2?rgk˪hۖk>ªu?Fx"z%k&a#ZVX&?,Sb&~pbG#pn;wĒ%KntX=99)fuau)M8A Q¡%G9bx_Aǡ9tQ]uggHu}i+AjeU` 22z&S#+,5fYzj?РWE֕f y=Ut4XyCџ,>Py7׸wmm㼨gS+|F!\n !su cyg{'Ss8G#E@kg=gqwNi;@0g9Q~+^I {FDcF/$#K~><?t:hH[|:vt pAISI":LW?,\LJX> Anӡ9d4T8)`_~:$)XSb2gir:7´g.t`(N;Ews#}ǰwʐ%fǰw|Jy8G*JNNnwn7QQ(a;?Fd\NgMRw"4t{¬ңhb{oմ}4MM&LS_wǥ:dOLfUTVNm#;>l i&ƓK+n/jOޒ_ZdM * 6fJRdwÏ"e @ڏ@vuUvb2jky_Jiv UHȯZV4VGlQ6S\zr蟘 Qp`}wO2݌F4ҧy#ظ?3|و fָo\?2͚! Fޡav~L&*EܛC4phhB(t%Lc%^n+ÿѵ#Xtcm]-퉿3_k*w"$L m1"Mz,{rsl-'p=5Z$6&:<[Ypj<%"N9*竑t$8L4GF=ۆ%X`G?Q0|a?6KUT׮!neT_4'D`ir|.s,ADp &D܏@=d6͈c{ ͊e6ﺦcؼ:XSfY:UKٛ%M4 bȋ#yNGS^w0'LkGپrFJ">w`ΕnldSY8SgLr6PӍ! rPRS됾n/itf"5+MXRv!9ŸdjxM[3mxp86;wk%s6i]Ū.sj#M K6l('a4[ߨJ0Ŗ`cVJk)ED%(0l K&0`OP;4{~Tu/K_L0x[0_HS/]YŲmȁ\ TNJir x|h; ~Aj} 2›o9X<X,NP  O#kُ1Z: E2QJRh#) #J1[Wp3:'pvk6ADq̺-N0,'2Px~?_4jTx Og=w 6$ljKS=\խ8XNd^4Y0 f:_i7G#%)͂a}inrվwOEJW=<)bN x<. ČɤOc;U.WUy=qxMO(@1ۖf'uߥ3 9k6WAs ܥooA@h bٳAR}$uPpMTn]aw ; vHl)ߏyB?|HBmb1[uYc0A E{Jξ-BVU;ӾE! ૮E 4G 8fMtF> v!QPdHH7TMAyGb؄J::,ɱQVbD0'#_!} bitՏ8~ F~1m4PϘEg'b22?6Oh8%| J0}}$oCw1rg2 =卭\S܁jtN"=QSa}ĮDˋ4.ـPOR\oX-B$VY(]CBZ\{rL&kp;YEHWv]h+ntBZrmJ,4ݩ"t.ʟL&s<y+nw:UL\S3Bhϴ2 t D"WG#0Јy>8t4~eaAr(պgncd۱b6K j-kM.[v!V\Ei!mXr࣢G`{%(l1I: oL\:, y|t7J;i_"/MX$$ 'IӇC) X [)Ѐ/OĚ>),~/J}E}et`m1WVf\4*~A|VOԡ_ Ƴ}PSd}DzO-:q^uaOʦJ/a oiiAKuNu- I[!)J7&W7V!lzf8̂\y\ʴD_ :]'T`` " ү̇z;Qn>YrXM)SH"4X4kچVJV`M}T5p8 >-81.`GQ'ī5t"wG"ppu5^ [)EEx^[ʚ Ӛ51ԗ JUJ)o>  YF$8,ӌdhfC\<  #UDmƋWq"hҩda}3Zz!3!!!x3aBQ[ۙl[E!y܅nLzg'/~)h,&:`wh}}Y`g:% 0j>Z pmA\\ߊٻpեӏ`Җcjitx'LXD =Ғ]ݍHM&/fIoBԫ̈2tۀkp8o7sxx;:a.rյ6^~ S O~^͟2/Ƕ/X2*Y;c2S?O?ŞO`xž2puG뇽2`p Ar8G#'d*tiqT~Gl>Pygisv9bAߌyێ^I4 /3V?ȸhO|B Ą۽|+(\ˢCyL*>o]`+QB:|_rfdt?Ⱥ្>dYw[<+S9 \G#pnI"B$}KBiYƢ8kj4o"᷉0J# Ap -8kG/f tHx$: aQ!c?\wDG` :rM8G#!`0%x\)Ƕ26/. 'D݁B^,l҆ wOD PLۖ[i{c{PF`5Hvo&{hŢ,c;뙡/`Ony }:rn |@pS`B9G#p!@pYs*": Grx2˽6c;% )]_ⴰw߀ʃH!m!GpۺHC-5 SsBh 6k \4G#zhoNJ@,*.M6'G>:h'XT(AatW@o-M1M~}4̊c3G֔zqPՀrx~o:$\ `$M'Q+ SM32P_`(F%݁PH /ʣ(STP@o up8G#$&r8G#p|@0Zp8rTi3detWkX ~+:\z/Np8Fd-H!EMY9ԝ̢ (\ )zX]fBVq^n@$YTȷ2u/3۬Y:h)_gGڲffTS)DZ㥅A_K;>vz%KYUr~3~5/AQ8t܄̖`ıt6ڋ?GdeՎ> ?,9'G#|' M`^RPo{وC4S;?ĭf竑tn[ьCYOhFEӀ«D@FEli-Eϱj 1F-BNDz'g̗ZT^GCzE/1^wPM~.F=2NF-~^Y[t8r"+JxaxKD.@(X/d)ʗqUWi[v&l.6CR*,Fup(46jGٱ(ںZ,60|@00rG#%Xb&îQ4 ONhF{P(x hёc/`OPǜ1&veY0p(m@sO3sbG@ŏa '^0+i"QGy<`uEԹK9SOJW/" ()@QƎDDT*ᡆ!>ݳ YJUSqy@փK.%_5ڨbFPiI^ > 7(9#G#ps D̽1{=.mrIʇ'Xm LUh*11qȡ=[nY5O':{*RIML3f>$ʉ]a>~#1c2HĶW֑}`t8jݪd쾂˶jU=dA)L9=K!_M?W <?VO+:VW;FZf#7|G#|ގX:T\@}5Q+ "aitug?74NH.@O0mY ?lG3lK\JZHPDhE}x|$"n1ő%=\-N,|#r^a,v3drs~D]ZղJxafĥ?; @IAMO%YJi_Ʋ5exJٶcᚏjݏk#:}$}CuY6T2`FD tz6Ki.tf:t <5]SRSߩSz9;qF2٦`___h`?uB7Rdj ׯ_`>|VЀIOyawO&=t 5A#XιYH;:c[%^JEDlu`}=^I_Ot s<y{ k1 ෺Ė;J6,sL@dJ%=. ٶ;鹛G`ũ"{ Dq^Ht9KHڮn DDz)]ѻ3\K:'6th3RIxG5V/&^`l07V@ 1>?jFYjږqx8 _Y pF> \&HZr "z?e^=UڃՅڋّH̗rH"z"ǦH@.Ps4'˰rUzMo\3`2uaGVzEGGOWʚZ i/ԧ{P%dLt4Wc\k~6>-ݶQ *~-T\ z\ul)8'{z%ă1+Pz"O)v14uJ?-tتk"tI dݠzOB.} ?Fիf̡/į/ EG3ҒG6uJju**~$#ز4wS7b N9(]{kt/;k#2ĄYzpwDJ.p2c4 (y.ŝ sЃ?]N~lMCȆ23'ZqWx,+@t1vT8HR2|XQ>-6R ZGUFoY99RPK2bϒGh[(~8/{S7]qz1ueYe@]ٹn-q}",\̂Q/Y1py.VxU Y4Aa[N,m򱦹_BsY'3H~ϼeI'snG6W19dx,tM}sar?>jV .pu=P8 ݯ@| Y/D C]u16P1&sA+n=d:-> 9OEP(_QLwq4 `6s8 @1ԷǺȇ0qK^'T ~^B- KۑUuO*fɢl`bKAܙpoyɉB8wy=b4m&P,?]!UsUꦫ "KذS-;V# l)Yȴ"$yY⒎)CܟOS1]N"kDoYǢyF<'[٣f>Ӎ{:;$ ?yƋeG8R`K9l{CL ؈Rq~_|؟#vdoV@ i_!;CC<ܪCiE v>U6tx4DI3kh_̰:4SSA}LL24gzc )S8VZ؆p 2ͺ A7lA~CS֥]b9)A?F- JbY,vYyEu]H*)RD%.bD_=#GJ9M=쑳MGRpI47xNBx-]U[2>KL3gSwMr XVPKWTM ::/IWOOY° 3keĚU1z0~gܬ@雑^E3Gi{9dM ;s6~ t{¬ңhbK̴g~N>Ay#Ʉv*8s:N4S_[I:òr"oN6Kx6?g7J,&0`龨A>yK~I ,pX͠ Ԫp=ac!(YeJ1n)$ mar˯H$ⵯm 怫Bd\DT_4;fvPAy|bDG/m͑$G*;z2)~ӿl܊zaO.NY) ,ҁL?6b8VNZ! Ct ]ر k"!u }'C ,aR7:E Hb ^Ϟ& ]haNZQI,fle϶KY26QQt_wJl-7NHztͧ;tN7|6m>=m:R-+F1k9Qz+f({TNI64ڬZO ؈"6T+zWJ:JSY[θ˿+(B_bo74"WkGٱ(ںZglT ".`^RPXc?!p;˞b[ˉ:oGM' MPv#k_Ykė\8ɼnMw2F? 0*(9#ϞmC2L(qH[fr*^9^6;?ĭ)W)MƉG> DEIW{SLUxpm!N45)J2E}G[KC08@JQ /=ztet:C;n aρ1:A~|8QGl뛐J|mpSVtN6Hr i!;B>&'SJ3WbպщFCB"t/Qz+YxeoHjhJ{qՕtrrxUy`4 %Qmhii>l\ Gsq\_̈́+hJj:V_ֈmFys؂'@yΞ5Fqlґ[+td/=iA=b^Eݝ,Xj ڱv $Kbwpdг[JY-50=] Z8lUY[05n LOGQ'B0Jo죟Crltp Xi-ŵ4EFoZh)lQ "g-X1?TL)͎bq@)恐' Pc"|rz\ z\TיTAH JڔjLmf؍|ymwnۭMl`Ŧ6)M"42F̸}3w.a=<{νss=" ݻ&=\weBV'h ZS胗'oֹ_B&2+QK*uͧ|$FhKGo@im;6.M== w10^30b)KaSPXHT52Eow!C0]KWd=A)uow'J-r\UU-(tO9kg{-\p1j3Pe^ L>}դ5N&z%Nq͸Ud;t~q)޾: >ZǏo|3J~ɒ]eAO7kpAg㎏ k/P~ {j ;2<"szY-̡_F!Ȱ283T zp0U==anp(#e+e -P8C?`S]:*yUCE_y`JT)FRTzE~ٿV+#ХVTF(VR}s? ,XCǚXOO#{:x:}96>sRgD‡+-Y(~B_g%J͝팜)r:Iw?D!-ɯ8Or A,Hx)TtW g;~oL@BРJss֦>}Zy뭷X9wrSiө*ϟWLQڄG*NkѥaU Æs'ϐ'דSVe"H}B" O}@y (>dWn5zpaX"Ac :3M\-vx8Lnԑ9'Xރǘ+XQOa#/}c?=%+b z0C VP3⽻λFV;,y4|LQ}R!zGfA[~!K5OރM]|}?j.uSY1rz ūGl|*O)[3?,NɻiUOއ W`jW\iċGݼ xlo2O}` Wd+u6(uG05~?2Ao  ?b]U,Ǚ9x#BFtO8u@΃.'cg IDATd}"dDё @lxgp}Ŝ6+y>m/:7^^jϥ:ɒvunj%I"dc0,za/@~a0u<.& lv$MfM h{#< +˭I*;wD̞=[Vpz-v/^R3t#cEe,  &0^c@͔i?9uc@46`Iya͂Ʊ*;ߘ4}=:ֻxձg;6Ht @ 7Pb]`L$@$@$@$p Fa \n]!@`5%  Ay앷&ᐩ(CZғ {D$ǸU!h̐d6HHHrp C?ïݻ O^|qjselPwJeB&ew"/R|غq_x]绱eN;Xo). K$@$@$pq>{h Om|8vd ?~r{d}__%^ZSy8Pp?| |<-=Epwj/"NU)8:y'@5   iMНX!K|ePSIy^F4wߝ?@Az{}Q_xUWߝAuJnV=Mw !\ҰC4pZQYzԭWÛua_ E=fkvӗ50w՜' vJ^-EQ<;֓$0uhLkD$@$@3@v-0KC f}Xo] J;Oפ7i†oV}<"Ŷ7h9j@o㞟_tp(Rx7X/N7Rê;?x{E|TY q+qMݿ~O/6(2`H>6ϰjgY"O/6u!={`IH`0%i(ϕ' ePKGd&]v -kqJ0XQħ]/"fQye|aφ@|&jhY&Rbf|J 57vt&Aކ¸8Ϧ{Z ҝhx4W Sx/N=#}B-HHtOIBB;O8 Dֳt >S}u/Q\jJ=?`Gz=V)CLT=ϣK7FL[:/F@VVş;>'weDz?Q8Jγѷfͻ#M(wC)]c~"@rl: hjj1|s7z5rqgi<=^ eg5rkߏf>!oHHfi􋲪Χ?M %:q61ƶ*&E^;z<6LБ%N:D.=#˖:?YTtW"1`Vyhw-reta??:fd"Kd OKUMXFC|GjKѮ i;޸ w\իW'zg=vic GݍV;m:9&uiגظq؝ & LG*yiRC2{}Z 9\kM#_[\~!YVe7݊_ ;(yr 7t|'>|.Ư$/ oteA +.!,{~Ϳ06Ta*l1K0]'V`~ ~t7/eHaˍVT ]I~+**R˧8Y&w[%is/"ũ蘐7gY:'ڐ(czWOR n@#rr#Տb]1 $@$@Bg}w)"&$=PҜչ2y.wKO߇k Tt/"Yt9d%FSF!DHw%18vb ʽ1SԏVLgܹwRR1{l$$Ƚ&//^á}8BSҾ(s x0-C:HP4Z| "tXrQaȄ1 @03 HCY51Ɖ7ҌMHXc@-1ki (xL4MQM9ݽ_,l0:<Ӯq)`Za4Ukl[c]+yF"   CA@3rJ:22`,p? +y\^.Eξʊemi?"f37L2V߿ =/#g$@$@$@$@@i~A&VYFϋE35в1,L{tڶrl.B}W6ewI5;s ?jlރ3faUZ7mFHyNS%eHHHH e g+FԴ I` |HHHH`J G( A兩lӯzb¦l~O\#`sPlJMC2k#$@$@$@$  ؜ nl\q&$g B F$@$@Ӊ ))-nC\\!+]GڦDq=ߏD<*$_CQƋ4/z^X~Ko#M%&9'73m ^fnR#==)Iq~GVGz⤧ڰ_{ :س} Ix}NOdk5\HaR< 887Iزu0:IHHAʤxi x=sc$ـiv8E8e{,>c'8 H4 zt0Wb\lܸ;7ѦݷG~vLj@l7Uo~g(\$|%K+E=k:eq-z{-j,k2>d;hOgw;j DkVpি*)kﯢ{QtHI tH^E o-+aw\jUضO3_y<#  %@ǤL_qή/EI-Vz9O?p]   h3ޣfTWݽ~JKbZ=ؙ gЂ^Ar)ғ0960dHLw82sֿdTEoyg~ҍ҂Gޏyf$m?{_nl=^xu ~*,i o׸ uiPLQ࿢o@VFvXeoޑ$  ?F?',|rEMè\0|ҳ"oQǜ_småXYL4!s-<0*+ѥ]x7 ׵`cA 䍘1^RrxUI}t!A"IWz}b&xΣF?n|B }Gi#|"h΄cytXF}z›ǵ(Gٌ%p=ŠV?˗cdy:>Sh9;cn3$uyp%"l9 xLr]GcuXPUQKXZԕY`-_$@$@$@4 0&ix@3-X$zJ9w"x~%co߁Җ>,$ a<1 1F$: +XʟEX"E֢R\4B`9;tg E~@aZ!1;/t uɪ,O3K@Ǜ61R4)saALuT]p77cLPI7䡾n ޓ@gϖq:s燡c~nyiP-տ֋$@$@$@a h.ٮ&T*\35̚GLQ2_cXc-Rd$?,|](F9:I/Z!|R1%7Zt(z:(#X#zoUdiJyPC{m vZ")!%*CS{>WNOcjϹNg=Azg|{!?oÊ֓#vwт$dŪfh.Guz ?gdV␵vJk۱QuHp_7onFU7:c97g谵=%d!b;)6>j qkk$6~!"k~:233OUՈWY~yͪ"MO643^־OUoy iy/d$^$   Ls8csʛs{o2ٝ. eJHOOGJRHўbeٙ8]z"ޅ=2?]ҜBn痈G 㐐IٿW?l/y7o=>IHHH @`sCcrS \= ň@ORhe,_?v?3vƝ0j,P\[V :\7W|k^X9]>)Q )D^    + ))snm8}k l{]m6&v~k-x&q\߅n̉7eu|YE8l97㑶 3(Dp#RLx#QIWFyO~n-CNt_?6cBle#VG/Kd gaq=y "$@$@$@$pEA0Emc2%BUZ[Qml4[c]VR*ٱFqu*ph;NDyq`8@Hnk^BF= h@u'xF K6,g7&mwx1榍 {8\dXh.cզ"2Frf[gނ3;FQFqɓX%G1#L-k:Դca}x1D @ @JqTr'qM:}oMjNv;Iq5ѥGٌ%B2v_ӕ-h,\øwfdι낾ut;YQ9 \;;cn͈Q$@$@$@$0 p0-ȓe@F tl9{xia kw";ތr~R*Oyi1ҖoBU\;KqPr l V(C     1hA27ȴ̙Y IDAT~8=p5zB5X!UcG{?dU>w+_eI>6C~d"|wk5r*6m uӀTF$@$@$@$@3ɓ'$+Wts"4sQw   nI^T3E8 ͔d>HHHHH`h L=?s)  @3z _LNFm6$'Ի 8.FUw{_1Ȍ|ǚ^| J JkpyQx̹* q>L&qn4#9 p jd@$@$@$@$0<^}U$Q3?g̛w50j_Uj^GF~ۯ]e#Q GF$@$@$@$@A+fH}? x[o 57P~맠&z~N]o`Grp3^F U9IWDhDHHHH&Y"g&,@BUx7~MU-ƧZfaxğ87w~ f+r4"gŐ$@$@$@$@H}3^X[W%nIt%}A.z8}:z.4'~Fqe(WfHHHH #ޏY?/s2vO|;jJTj#O2Rˌƴ*4bx \z}={wߍn _mvM`w11[}t3K7.qTNe x I~Rul8@#X<Kmt:(cTY_W{кش9Kp9kB{g'M(a~"(ξ|nVQ롇6PwNI'A LLl:buٓ$ $& *<&hl^1MJڝ@q _ܿ_.? ]Qǥ^rRqIiR0[·b" к3˵Ä_;(/gBzIjKR C'V週T˵H;ߥ`kQ;0e8 iImؒ`FudIl*3\ {y$/\Nl>[yY86}ffPWFfyi UH1:JYmA)|%?ߢ*dGNQiXirM~,%uA(\t:|9>X\:KjIRxw֕))ԕ(2Jei],yUz tW+UŮY*=y ƫS嘕zcBr\YgRnӃ*aJu5[J^c=Q|U4z#h5u(O݁9_*bP*k^[ k+:8ҮZrvߪT(fw=J:t>B>N|U z} H _+Uzu]ruJ4u+~Uʍr򍤌" EuzV#y0(ԟϞ2*g|sw&|~!F7|Y*J{UMx^)X^sW^Lh{]S(MT/׊Ur\!~uRo +AyfM9}[o)ʹs .(p8QFGG+CCC hL~5mQJ \ 2KARVVVSGH{CsRsR[Q:/ծڵ׸~-%J]}RSY T& )-3;9JEmR_+tO͢4JW>|X|l߰-ejʢU(ub -Y\ZG#QjU]JncW|{M(e%.ޚ O|7/Q9D>~~UuJ1)8-qqVsgAQaseh1z #K]ˡ\:|UZ-Z=@6n[M n%$+A)KxN)C ޚ#kTϣyAsп;^չf=poϢ5"IYzWUmoky<)Ri;TChvYzVY׵_ҟ1~ hƆ"{b5Z^Zza\/AFJa|4*JXc F~m (7A`v V iT!"6>\'#֠?:JUjn4fhHmF*=#p J`IVI?T` [\[c =P_)sTY#{ P=_ŕ.ccYd{2dG q?]z;yITN!|#)H2gp^j|7Qxy\NSyy%ós-CA$|#)UUmϊ5&*W'a" C_ '0Q_*.ѩ8FZ2or7\sepQ44ŏrSÚ'#-l /|i 9T|:yv|o&?M&e8P:bwg54oDlY=&P@~1;}Y̽@Q(>~cxX:C)[g⮪K2,UhJ}i*+ E]pz:걼 T10`G6 ֬h:#;-c`#J֥12#)߈(g٘#p$<"M=T,H2{Uu||g/|O<3DY9R #@ ɼ.0$uؚł< vu5\ %DSeʪͲ) uհB8%p&:5-"!2F}| "s2?K?핗Ьߨ&w^=McRINglݖf>^GJNtr,ːe|qr@Es?dD2Mj f$b݊OX]|%H(0pI^yE"_nj:ވ3~N{U{98gGw;?aFRx?Q8CAdti{߇o_9V%?j雕ÿ &H+T{_`EvHX^>l$1W2$`FRVzS *H*P=J143 UMڈG81Ip ]wCP2Er%L&9ui8~ (GayI:|~ W@0W#L; HR2&غ? {ŵVq7K}߬Xr!E h }땒|3d@@z?vH62bl4&R>vWtuޘ9~ū^=~ᙐ#;"_=8 ^}KSqeIz>IneO)rDj")qT,mO?%vΣBOǭ:zOM{2xf$eA9gON|a p\S:/ıRM֥_F_)'?ȘjTQQ)m`&9J"T$vϣ0]M#LQ󜱎cYVS%Ia>'FjGo$e^O|uowHɗgdU:L ܔ̗1H`b a$q5ő'#TMvIݾdQ^Z e-d+ZOZz^m'$%,=p8g ޘEzc!yX[][V2#YQQ 9][jw)sk·dWXy7R9ǙWbG_F&*6z߷xTh {z#l"eJHz*'2ŪJo 6pԁ8nܣWarcw^6:m8qd,j]ǮMنPHFy`ݏ'pTņB9}Dτd䖠XÆC_s2N2HXao$eI>Eప^/y//XiMKfƞmQyn V_p~^\9wGdc=<e"IYFv;,;n4?e<ܯH|Y?i6ߢoS.I9e8Zg#WW-n<6}H"eGsҤ/)-QYYB]Ľҏ~JM ʈRW4<=c~'ˉJ-SP᳒"ԗKm•ַ+VmSߕѺĻgʱnJH+ֳWTZ,)U+σ/ϩRwzZ6e?^?<ˑ ,'zXc>dư|ЗH-r/bُo&mO x")NYܳ/Fa@W2ۮ^[R>ڽQ\뺗+s}}e0Fxr=ƺXu)RjIE&F_z8q? V*+ɳ/߽1N^Eo>9AG7tY wX]+K"QqEPoVst*2{HOey qAj{c m4Sr*M!&@Id&kۧz?C[ֈIK5=c;aబ!DFjvIP%$#-5m7 ڊ,ݳ~TU]HiiAuڇ00)= dJG<|d <,}-U-8t@!/bVׅ:140ԁx{zae.jd@ҥRڶIR1Xx,eE$J=R4{ 8PR9Qzة,w=ͩYQ$ad$`6=tN5X ;'Q YZըwӯ \A~$p%MKd )\X2_Q$aX/ =Aϣ}<㑱pLڻ G|Cz{5'c,U L$a29Fh nRb.rnǞ#oc Ha.#gFsp!BL> !@ z(:? _K-3z,.%~zN{#>:;h֥$tⲞ\&LHH & bxg IL1)z ~oTHH$p}]"45ޠ%x$@$@$@$@ML6a'    &@ dA0ل)HHHHb .F$@$@$@$@Md|    a4bp L6MIHHHH ¡j$@$@$@$@$0hL6a'    &@ dA0ل)HHHHb .F$@$@$@$@Md|    a4bp L6MIHHHH ¡j$@$@$@$@$0hL6a'    &@ dA0ل)HHHHb .F$@$@$@$@MdqN6IӉhGf7#Y` P4`N`˲8Ź>6lѶp`葐$$hWޚ[JF,[ 4 yt}p { IDAT8BE^4PKJIAJR.B͛Ԕ۔kIHH44Ļ^:jk cU]9,0&\/e؊ Jëw/z-}1)eU5 9Q-e[` Cf8z{ՠƥ[Pi{!ڼM6N$@$@$@@p۬#.t5s3m[--{+zܹ.vl}vDZAJ*<Z3bx? ! 䕣iAÁ_izT#'ށA1*lފ?6Я8'uk(G9ukʼn:1 q.H lCC&ڼ k.B}UːOeK|M$@$@$@IH15:k )KL)ȁJesZ)yԣ,%J_껳T MsI''#PԼ)wګI*ѮrR͝b!_iVpcJ,J J♕aU2G@@Gy ($@$@$@$&߯蟆_T6[oHgr9… {ァ8t*畡!#imȖ-O 7j;PRD"W>wsi>;oXٍ8iH ±pc8%tº;O{=إwNET"8cAHm8MZ/S oAsz]T8UIN:y@ޅ>MMWϨXy$ھmsҸ߉+' V_$@$@$@$08B0EFf1wֿk]oɗkJKڵݩWZ{x w+5js)'@hV o2`6P+1}$=V.œTR"5c>nC'@kQK=J!i֎(G7=~ @ɶ&@~KU"w,7W+XfCÞ߱T]ǎ6 sٓ<5jK_p5_ wz0djElXE{au:VʊB3Յtw8ݒ_,#N4>9%JϺ^zV\| ؆䣽Y]HEӾs/:]waJP$s6o>HHH& M"\hNkatΩޡtV<(.q i؊5U7>웶G=ѭc죐W2;Q=B ?`2!i2}!cvtӎMי$@$@$@$0)C2ך$RS];''=򲪏m$$/hCқ>"iH6 r(}CFwfՏHr%)#.*Ta.8kb/GIHH+uuEhmmd>sվdD̞= l{Ey/T}7}t;c zx,dmQ{7IZ'(N$@$@$@$@4XHHHHH &@ .|fHHHHh L\: `     + +u    A:@$@$@$@$@W0Wp3$@$@$@$@$@uHHHH`4gIHHHH \3$@$@$@$@ɓ'$+WNI:9ӹ; LcrˤjkM"SfJI2$@$@$@$@$04QHHHHx o9 ifHHHH V =/&'#p|p]u YjqĪ=/zdjcM/Ck%B}HHHH %Ї58Op<@<\S8MK&kwG8B52F    w*sv3ͻ~/ *5g #O ׮B2z#Q#c    3$HӾw-7׿_y(SP= m'~7z#9/*yYʤ+"4"@$@$@$@$@M`֬Y^\WȌq !*|o?w-~?0KjOJT;?g{9bH    $f]­$ \ =>H=C? TsƋ2Œ+3H$@$@$@$C,뗹l~xG w5_{W%]a')ePVcZ1]P'^/#b=?Feܟ}8KMS}\gR# 0f}"։Nk0c}}@F NTڋaႄ9>kLlMLc3}|ks=HB\g<GǗ, #z!ٌvpfMW͸M~hu;,;0G]Y3dׅÎvblߪqm2{eulfwa8c7ƾs2;Ip!?SgmI@梥0X {iͲu~lߒ; Ѱ )rs1/]k?jKփ 00Uy4,o7j0W0ڨN *\DC"}\G%w4]ЗG;&CρX*O`ʥ3gWKχB4Kq*+*PRl8vn]m^%<ׯ,GJe7xz6PT 6u; uꩥۼ ֳP7&ʖOK,+q䠢 흝6բ88P{cp߷z:[DFtF2Mtw:4pդA0UE]5yy8$%8C}9ށ\(Ca˒CQlBcF0-!|r ˇ*y<‡{Hgrٺ ~6~F61|.İV-lގ;#|[İhW"-m?vU@]Q#^P?jDb.u_% "{FmDM܍w`7CL6}NAQ0<֧=X;#rcJ"iLuJ٬H+|%?ߢ*DGNQhXirM~,%u pbv&esBuJoSb15[JOpiϛ wSJ /94J~-J7)ťSRٯ4WXX*5Jeipcp:zz2vtש:z=O}|)OO?Gw֕IY\ J]ib/ 0|#CGRQlqZOJeUq#kUYv)c>-ʱNpg~])(%nW$uoU*K ixPP||10C]I@F+U>ecq^r$npҥ@Un򍤌" =B |C>UiEjKc7(ԟϞ23jAE gip>",%Խ&9S_|9࣎ҫ-~=p"vǢ4rhQo^,GxM1J_? ʋ/477+mmmӧzKPΝ;\pAyáȨ2::?^R8B wdI jՍZ27.+p`85i]cP[Q;,H/< qAٺ)%GMe)#|=Lg׈հlkd4 e%ƚLtPiG7[a,ZgQZSFU@ ` *UЀZ]B[UB+ eƭ V6nIuh5TֆZ&J`۰nLDI$$B"D2@ y̙3L&o$^:,<ror8 p2xv_+ ^]`72 &+QIzn~C ѳHt]3)S0.=2#'cP>(eG$vPT~ӵ(Jl5! K=2J\}q -"5++n;>t `pwL,GσGWaiH$6K7jI5m6vA0= <-# _=uDzrlzؚa)tN;00lw;g&NFqYksGOZ JjhCf^(ϣwZ1ro|fؾպ"1pU+a[V.ameYDKHwd|4|#{͗P[s e ù#J@m_5×8 گݕW]󌰈rLSFqq^ &<:#CAP٣zw=B `2{UԖL'gfZk/=.s*)S0HУrA0Oinu{ҋkE% OW;7mO>Έuhu-eTv0yb&<&kF㚙ғ{K/ނ9c={##u(+CJ6=Bӧ 1>|ىPG9&=ij|!fj|f|!|~kuSX50alV.(izvԾ.@y.LUFz4,wtJ>PA ,?{U4 w=}k15!QMU[ׂC#җVXȊZTŊE#[-!'K3p~}1UV%i7-Kgu:qؘ &oYe!"\nl"dVnV5y0zzE1/C S_y^Gn]CfÝ<6alGBLNt:j$n]2zm8 Wz`GC ݙnKJ32 &L(L^;, F^;=#e=g}5b-jMznvCeUk6.SԯzC"sz~{] j*(7?9(V) r%hq&C2WQS04Wz#1of'ԜLx}C~7ͺ[G<&o:̘?;%5BVLc (|͹rwKT%i=۫Dl#^gx;)n]Ós$ުxė<ڬ͡/& j rCk^sKXgxd,ǎis풋GX": "3f6dҰu%S]5SFq}p0|u(f1ȱ իZ57ԛlj6=0/mwь>i"\j:f!?M~]7!~FI&NOhr9iu}5$`> YWܵ>>!ߝRyLޢy :5pJ̗ZjJ͗7R0oospcNi:QAћQv% kGiM=Ǘ8}E/࿷=k^r߹ uVS[f%=A)= <24|]x fwLKjo{L+2:RAYSy_{l`*3@}^<ȧ#qkr U/[n_TU)#:C5$?XIXqJބ MNIHqx KQ; hfoތF5&b1*_^nn^yxjJ2{:jAo܆(='3duM|xﴃXxzEԗ!g"," WQ+ψ9qcGԗa{ԗ*Vԙ Ǎ?0ç~`#߾,KۆĒ+]}#{nīi}_}itVy‚TKi;>򛐩 59rC֥߮)`(0SwXqk" 7vwx@+_L*oR4R- kz jW>[&Pu={8Ӽ+RueNg=ֺ[7u)X.pSF񥑸љ,<Fs/o|[p IDAT8{Qǵo othѽlsз8#\ir3=?63ú[*wʷP4!5_.HݵPgupa-pfdt,#={OuDEF,/\Nm-h7{ccLgߛU>nB\%][N3N`Gkڝ[;uxlDl42 '9n,5Ëgh(q UQ^Ki1Xϰ hS᪎p%܆dZJ}U 'ЪDSunPe+۳ x9uQ#og>$: +=5O.ϝ'{C/872 &| ?a}:,SmAz'jDLuN 6p',K3`rVhp䩈 wS 10 =1Ǝ9<1l0Deծ05&+|NqeNYT'FX_ozJtf^=Skk(Č~eHɩĶSk<.2]ڃb/HHH &qP/|MTc͊5X(!e+~xv쬕8Q},?bsS_}t0$GS^FSas# } YN|:4>s2];&S줁h˚=Tnšc)y :RKXR @A},Jzѣ=u|24? 4K$@$9 "eELHHHH $AHHHHH      1 4F92$@$@$@$@$!ac$    h rd.HHHHH $4BH$@$@$@$@$00 \ @HhHHHHH``A00ʑ       # @``#sA$@$@$@$@!A6F"    A(GHHHHB"@ $lD$@$@$@$@ Q DAHHHHH      1 4F92$@$@$@$@$!a@`YjCYAJZ:R޹(ߗr+&   |f%WuR ADrhy{m|m(̰(lPEIh RSS-&j N:^׎ 64zī؎l[*lcnn_@oTVFs%Ru/Kíw-jQ-@#rpXfrHǢIb~-5y!n>hŞ",z-BͯlzVJ'    z@#^ߘĭ6̘ZnƬ&T3~ZԛdlX9Ҵ^ ţƒ &`yXOyt{t>x$   $f[I1TYFJ"y]'ӕ_Q*LPשFZG;<Y)JvqޢcWXsos% g=uYk4lnr#E$|TVlέ[U"mbVfx Fq^Fh;7BhݼNW>J2VM9J^D['w:o:IHHH=: G RRp$/amlQԿ==-(-Ce$jt5ޟkh* U={3 \ա5+4{eu:;VwM7W=*an5PDlJ/N/qz%w-s|KnoTTp|QAV5ȾLSQvWk.e&O!#t͛֙G   i2ӄMv:I[kcpZDgӢblר6q-N@*lEubsW1K~9 s7;WmUe&oɈ7'N/>;j&މ[;2-9YQ9]uށnֆq<Ѽ=vitݮ2e~K!&&jU0k&5!i=&߅S'֡Y*wQSy:H$@$@$@=O L e@+ƀjΪOFGyQN[sWthA]1DMhk~"-&XŸ]y(?'  duE"22Gi0|p 6 cᮏ?nh+C!1e nc@m.3*W͟;DϞҹ+)Gxϫꊼ`F*ZNA]y & C$@$@$0 p`.}HHHH`A0 f4s3$@$@$@$@ A_HHHH`0A0Ky'   h *@$@$@$@$@ \; '@`W    h gIHHH=  `&@`0>N$@$@$@$0 UHHHH3w    AO@$@$@$@$0 ̥ϼ z4}     L |0gy'   蕄̙+Dhң$@$@$@$Џ \{={ףpN(%| @hQHHHHzg>kx4x3{$@$@$@$p k?{ߍFM7&GG2;x4F?[E慚 M/Cp!   AJQq(o2*'#2BFހ7>"#y;X|*CU0\!*#anq< Y1$ @722{]\8Kv#߫K@ӁZM2yrF5(K) 2$@$@$@$@!pfm̙ƧT9v>:FGXs=q9ΙD*hxWΐqp<tـQc5mZ3`6nI0BV[SFC?'W%a8 6m2b|GYH{#֣aUX6;y5-!奯"OS)Vh3Wz}![G`x$$$`,y֬ چu ,*rT-XN. 0.*+˟oz>Kw8~lA^7^&G::FHrK0NiOXke TuN.?{L,im:ff"um/†qcA)P 7TӘ;;sxw([>HNۉ s-训d=49)@4Xl391E==FX:kԜbW#7- [gh*ڎ;O>{ԟ1֠݇J`m"}"-x 5Vyߞ۱_BH۟P @[[zr\ 譂EPtZWͯ2 JUۦ,vdôT 7kslJ0m0 ~kc),0k ̻k)VWV-GozOQkl m#P^{'ÞJ/vX/&s_a22K9;v?5)( [{#o2p$*ZQPTՋaԩ1o)6T/Z`0J#a.L~)zr0s}jff֓*sӌĄCl$''ɩ9F3}FZr/aRsz}YJٓRs];!Džmk)3۷P=Ű5}UUV cmFN^i$)MF,#)Tc_m )))FneQ%I[MJY4i)FRrQnq;PY59FRJQ0ˍtU/RFSh $"ioNZqvI{"ţ\K27C әzKǽaTg:`谎c7DnSu5mz9oy>GD5js~_\HY@WSY(/8aی)j;i(xj7R/6~(OFwfuqkk5;-H29\3K$Z#=ѡV:%$@zC޽{b8tq8yqܹsFkkFegN2 eOoQS1w3)W]3fIqk|@KnDM sd`$[5bØbbR3[pa9W/E,L8PA8ϞO 7Rs;a!rL#=5M?)$ۑ4mڴT~i'UdN<~RUΝ;x.h\3I^n_]!?2Eix Fg u9s=WDKM=u]wC|)ˈo@LLbFxeji837g5zM*:ZL!I\ڵq<ҡf+a)+];Db0O$7S5 Ƴe^C~gf6be>omƲxh1-zp^% Q܆uU;.9IHU,R늖G>vK$<{=xGI둇/IN Qs\蒋Gf0ܪ>j=r4j5po9VL[dnRϲ(|͹!:˜K ^ W{Cbeco0eDm_"bo}do ]i:f!|lB˓K$@ ^.7mP=2r0ŬūRywԹyxzOgEP)lnGYEo:`8X=q嚼dy9a5)~y,I_ fz\Zy{:@[ Z|4Ν8nfٳ]Cu RQzpnJ IDATTA3?<<v +*0.zv𬥦\ !-&9G9{ t(59]*Η,xuzuewLwwX_d7a~g͇KNLª͎M>z؝:3,hqyK2,&/G:Q J ^*͛{ըT,9zԗ[mۭp0O[]f/ko܆(='.eU|=s"toGwwna.oyәoXt&FS_`:Ywa0]ed{\7JefJ$f"57m;"fbS;Pr,ʐT G^W;In5$_}1`_jh$!S5C+U?27*? 0,03^+y8amxD}\K}57naoE#҇<)]szc]ea} `Y0a-Pӹ '9n, jgN^M3:)uRJH;7`YQ߇v͎֯zs4F%#ӢH!>:Rj7Gi0|p 6 j!Y,L8~y%͝ 9@$@$@@w] ͘zm„m";k%NT߮8`'0%e?<)ރ O$@$@}JAxN:2u+֑nIa$@$@$e4c/bǁHHHH .s A %ʊ  @H1 4F92$@$@$@$@$!ac$    h rd.HHHHH $4BH$@$@$@$@$00 \ @HhHHHHH``A00ʑ       # @``#sA$@$@$@$@!A6F"    A(GHHHHB"@ $lD$@$@$@$@ Q DAHHHHH      1 4F92$@$@$@$@$!ac$    h rd.HHHHH $4BH'І<ֵt*j{tJ~_yKLHHHshtWC7a s3C^Yc [6ϒ-%xm|b5bfL6@C¢$Tk)JWTL b !nzSyu `#@KȶV[j[>f )IЂw!8ڀ$zעj D9Yq,P?[LǢIb~-5y!n>hŞ",z-BͯlzVJ'    z@#^ߘĭ6̘ZnƬ6T3~ZԛdlX9Ҵ^ ţƒ &`$=؈HmS5ԭzO܂,țhۑU ֭ːSfHhiR?az&J]AQ <~'iXب¾px䇫1g0%cp|G @ApTo_azTlR سjjs1o~#x9f7b&E.\}E1hkϽ#lr2Mye@[ZZZ:-_l|"֭ X.T HFqpw} 썍hhhPMxtxd*,Ѹ㶙eyXyk8ZF܉kP%>Oyt{t>x$   $f[I1TYFJ"y]'ӕ_Q*LPשFZG;<6JjEwǮtsu58YϽy]*,۽Hf9I 99UU:$ŢskdlHۧY*^Q$ͥOHHHHzC޽{b8tqCu'O4Μ9c;whmm5ڌgN2 ![Mmyz;Rq𕟕 VΑ6T @]dLuN|*&LSU.]:8ixk7.N,jgCU"?IuȜ"XMwPQ ɉu?Ho5!ӅDܕn&7IWydwQqn5> {T   i!anSw=ǻk]U3Y5JkjS֭عVz./NZd %8ܵ^u>PF*?)M*IFkHUrwu:оVh: 7z(JGx"鼹 &GA~YJJn%?_bX; GxYehї`DMsWc U8PZ/}l--mhk(Éf:tPgNJB6м B#ݢ_<͒t MM]TrZ`n'+i]y,:\>oz5j<݇йw6oHHHz \h;w%Pk yncpZDgӢblר6q-N@*lEubsW1K~9 s7홆q?@ĸj*afng a9w蝸O,rӅ\A6F䬵a~7O4D}jϴ])r D!FW Eͪ |BP,ӎ| NM# <02ZsKmhllFlcU4ot^TcoFkD#/s|ҹ+:"&bl5]QxTbQخpu WJKKi#G4QQQ>|8 >{y绡طp1 z Gdt̨\7={H箤;=+wh 8u{_-3 `&)Cw    AO@$@$@$@$0 ̥ϼ zM$@$@$@$@$Г ~޵(8BГB$@$@$@$@$Ћ}6D9? @&`mk@rԛk? $@$@$@$@$Џ H#_Зއ b~@ :ġAЏ СC]rfƀӆ4 7    _֩@( sȹlG8U'    +ik@Hv?=H8w)#V<'    ~H <<gΜ15DD9b 粉1G8cɓ8q" HHHH11]Fr-gϞũSѣG NIHHHH$ #^z)jjj9b(ha"Nb%"    ~N@qqq6l{=s:ÇwLFdZѸqp 7ӧOsE'$@$@$@$@$O KIJ#::hjj2 J4b`1cfΜi;q:&    M@ .S&OQFAze@\Ǔ# + J:st:YMH($ ?|qXF d{JIHHHz^ۣ\醿L袋̑^eH}# #@`9sL$@$@$@g>s/3p*)ChP-   <\| ~{uNewo݂G)T|/Ogœ7`|ӿM^{_DNNCdz?*x @_|^bBp =JƧp ʐX|mxpҕw1cK(+w}' MG    ^%_GI|Xcv?U/ƪc^70ZV9!O09û~k5ч^U$yGp2H$@$@$@$Ыjjji 7+ _#mH2nG~AAG5y}"ƛH}= _:nL ^f3M0|`5 8B`s    ^#0tPwG/$`gq]\oX.*ӿ.DgpH#Q޲kP5*|T/99dxrB)AA$@$@$@iōk;\~F.YNOk5qta*%wHn_}9$!-fHHHHG熻̙Ƨ2p;xÚ#j CMww\=X7m H?{lic@F`HHH(W4Wލ!KMW^+~a/#ȫ퇀ߍΘZ^gW?_zkmm5#8# iZ<E ǚZq 6?kGgpG1+:l2Pg=]OtwopսBxpe 0UJit}Sz!m31f2NQFԝ&L ͮ^E₈?yF)rZ}alN}a|ȑp$R!FÿrG?PfnV#@:okkCxx8(#(b !Zk:o{Zi&#.& wuVbJӂZR#p-HPnz/mGz=OA"8s6p"Ua8{4"SWB_hCSҶlY⑐ic[Z*Pk-_8L\zI\Q "b0%!~EE`喂vR] 4p.قPi8g#~t 3p% {9!pQ?~2JG fy/dK9.}f^߀-IX_$ĺ%3p(QݵD 7a{8wWֻMNO0t+O3f֦cєH.Ħ\lXQ'Sf"'e;&xg1\{{,رt29Xwxm$_oDs:LX1a2XogDd w^;uo㹟܋5tT(&V= VW^dj$0=eee%N8fc)R}yذajvGy2B ƀ `Ё8J$0m!X#:X$-[# #`Ҷs2G$9]u{Z|m.a'G1,Yby L4ɼa%Uʋ(,zıcPUUe D?$ܙ3:FdH6~+|X?x#ן}>|&ƊҮ|Jرc1zhgR6WxIe6\KXz/Rře #Z7o::ʹWV!E)S4%YgPϞ=~4E]kǏ7N>3J+qXȺ~{<+y8~l| D Ԅ#G&L0*3h>s#F@9-GOdr/5K)#z֣-2O(zu<\EDI@+Oc࣏>[oeZ=cƌ1 뮻'O6eZ{A!ײ\iuQ&V&s!/.CO?IQFF;hllDLL P]7C=!r-Gy.]:{=@v '2=ߕ͜2$& ,)xwL@2!ÃTK>^kk#E[3Dw#|#뇣1#7?xol_?(*i =҆ǸqƎ4x` r] @Fۓ'O4ev~ _|III77@ޕ-\:uTªgSY0%Sr!B=p0A6qG>@>ҟ|X??G?xco?onH@icHGV=FFv@H`Bro!9}!/2NFԤM.ƴn䠰_pa;T"C̮lXbH^U| 94EqI>3ÄA 7X)|Δ(-q)Ma:|>^GiH 9FEEr-F htƸ҆#@vr/{Yx{B҆k,?Sk|;MwioK缴##%4ᒸ$(օX&r^RRgyg@t$Ÿk q9zYݬwT(+9\#9׆t&n2OZB w- Lf/n [rh@ϲm ޖxz?|MG>/磴dc@7F7iH(imt%E@ڿ=!?7gdzЌ3\Stq}F.qu|ioQ0lR +++3.r/BubrԛReʐ<ϛ/5̝;״^}erx$  dd@vi8p1/@Q$Iȍk] fe4@v5y6w24G.mr1 grwikE'.GMw%oG~~>ߏG/:L>]ƼHHH``c@Q$[GdE+µ4` bHC]F6 7`.=vGn+Z]E1ٵ`5, /b'˥g@2eX#aHHHi D&L0ALV/BʵtZ@HX6b!YfT޹th nV@FWpeB +{VJnx $aٴ,9Q@ X G$@$@oL @=z9Yt䈚@Hc]r"uĉ!;iȖ|W^xatE Ka 8t\шg>?,χ Re'KTHHHH{ >ӂ zq՜)Ҧ\5y6>-̝=#(x^cO9z̓QQqQ(#Qkjpd>;߈smy>F_{n&Nkg 9= ֠8N~֤>Bv8ސni9kN ^Pjz`#G0d\ܝK|95L}\׀APՔqi+1\]AIƖe^KاX()tsLE"rMi" S?b:9vo W|q~(n<H\ S/5Ν;cGkqeQPY*y[]C)k/Jy0\6\5m*]IL6(0<ܝ[AA뮀"/Y!K/59v0T$@$@$@$r ?/oc0~x ֐2FKگo6:D*(~^UKpPMMWxU Ɵ^.rDG&\y%4L8D W,EB,W@'mݕM>o}7k: {nXsu+U^全Z5 @||</(W{g>jh}e1f̘vmSgȏ"߷2p U_:}f\sM9F>ޮ??~#=22@{1r9zGjƮ_3'#N ؇oï\1emWD+cUjYP%s?ZO?F#z44bo7G?&X7 ϕ?#bT{5ZeBX1WxxwN ek;vم믟|_z~gpì·M8zz?8Fi1j:̟;܌{säIۡn-2'LRLb1]%_V4 $u1P:^r/_+--5?`֬YfځXLwPܗ[o<7_5"$@$@$@:cm^ViZ!+/yGwLr Fa/K@_ziv|Qf];#NFUjTAVTkaoCG%;__m=Ǝ¿.=qi71f.r᷈OƯ)wcΥ_!0uT&{**>ĬvZslC1n*zM%jX@ĈibujzH?ޓWr_6ix_k?bcΗG-/ȑ#MTŐAˆlZwZkFҜ?9_ͯ+>xWoo朹&uC7-۽ݵ7'Ј0S6GO?O)2>=kn.́yy._X>z6~[ +_nv;{O>0t   L7!N%K0^5Æ jF0l( [oYwf#_ i&aVXavJ.QH{>O~!F|_v1dD=sOj##Vn1__F,6OORiG HqF U^^J8ҁ;T a0?ծdoQ6aJ|WF*Դn t)GUTǗEIot>:])1cwNiNC"Lc2 (w)b+-M}֍ Ge~jyNYk둿ZG d2i)y;8Cl:R'lĚ0$es40000 Ԉ1)F}W:4'M$mٳ `(wީDf { {^^j8b'SNIIK=ztT 4H4VyN;t! 1ì|<  ?mJux09vԧQE<>R~]I `GMx廉}8/I w [P".F(&No-RMb6 )<cB*p׵m_IN jEtbd)H | #[IT >[Zpww ny).[6Ùq.E@ +~ktGdX0բRz+$$X2zK@?m|ԨQj>D‡N=\g7Ҽ*>L*|rNwWcٟ)ʗm;vn!=VlmIN" p=`b1: 4[d\} QE_.8xAsD^&Ñ\ƦFe34ff$t Sp8 (0?SSc% sxe8?bPPRSf ^`l=so/"  i00f] q0p :v2;2kb˖$|}h 'e( ˪)G1lC<8d]|*ć}Q %i.Dl~6(nCii)tR5/ʦ2z i@sm1~{.?t6O{et{OއJVwn,hDٶ> r<`R)e^¼Ȑррррр@ ,[2+76 v@AANۂ/3?qΜ9@x+/… ۍNiXe{vxQC q|X2ZV|qàX I C N!6<e5ͿړC:s@RajeH DwWP/IuMg"PUR/?O/\>5'"rʳ_(C*vÅ6 >8%d6o7/2[kw[FlPہihycvvKqm9z:(*ZnIPħHHGC 6}2ۙزuf;+w엌!w`es %[[<h3f vڥ<?X۷+e9Rek^vvN={"׭[Q7( þ)Pӥ<)!_7c{9Hdt[b$?Í4 @즀+(~Hֲhhhhhh8r%%D(K+ #EB>Eg1Ϗ;v̱TD>$1 `>)N7R f̐22+?Ő!]n<5 T76_[[X_n +Q©;0?c4kaBf,Ǡ )" i|w$iJw~C$ ;OnCaeOU!8I<3aK㴘N ط3 P iv+8r䈲crry'Isxbg"R⒲xL&رUX $.:c1jCOA/o/,ַo_0<=X&{,s/EáP]+ <*^50 Xi;9JCP[-Qū-=/ohhhhhhe|&='Ut[>}S;<_rX%Ho 14v)St;m4 O:UJLۭ̿ ]L u.1HMehӥgQ^݀ Y]Z$ߴA],¹#2g~2w o_ʑRX$LK3=֏сV;J '9ZKx(7,Մ$aYyd!ZEհ"):%_>i9a8p!DĀ,P3nX6E$F^7ig9n; Y8l~ uAx >.܉~M5PUqC{")K+4 1vXf늴ܮtuO.=<Oshhhhhhh̽U=,H*(+:°]UA]*^w&P(5۱?TʎZL40>~sxi;}^¯%F#*uCSr IF_0I\;@0Аӊ?_v|.ҽXWۊwo=waZzlY_M=2r  :11n -@=[1ʹC[2sc'/w<4yse/Itiw+WKv?p` rd1UjZ+ sg[kwIڜeCݔO&Fh`F6_]5# ?s@<6m%;DP]aI!jUMH8u>`v%ɮʁÆ([%v~\΀3Vp!2um>/9@$ןS&&sgoy2w?u̚GjՒ{5>i4`4`4`4`4`4`4ЙTY;ĒBT8op~7??s&↰`f o/ٻM;[e`)U*(@x,}zgYbIDATl6w\;} [n\T5T(dk[gcw{9* L tey~ ^FFFFFFFFFFWRt\CrIENDB`