tinytest/0000755000176200001440000000000014071100132012122 5ustar liggesuserstinytest/NAMESPACE0000644000176200001440000000220314071046211013345 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method("[",tinytests) S3method(as.data.frame,tinytests) S3method(format,tinytest) S3method(print,tinytest) S3method(print,tinytests) S3method(summary,tinytests) export(all_fail) export(all_pass) export(any_fail) export(any_pass) export(at_home) export(build_install_test) export(exit_file) export(expect_equal) export(expect_equal_to_reference) export(expect_equivalent) export(expect_equivalent_to_reference) export(expect_error) export(expect_false) export(expect_identical) export(expect_inherits) export(expect_message) export(expect_null) export(expect_silent) export(expect_stdout) export(expect_true) export(expect_warning) export(get_call_wd) export(ignore) export(puppy) export(register_tinytest_extension) export(report_side_effects) export(run_test_dir) export(run_test_file) export(setup_tinytest) export(test_all) export(test_package) export(tinytest) export(using) importFrom(parallel,makeCluster) importFrom(parallel,parLapply) importFrom(parallel,stopCluster) importFrom(utils,capture.output) importFrom(utils,file_test) importFrom(utils,getFromNamespace) importFrom(utils,install.packages) tinytest/README.md0000644000176200001440000001744314063211116013420 0ustar liggesusers### A brief overview of `tinytest` #### Package setup A quick way to set things up is as follows. ``` tinytest::setup_tinytest("pkgdir") ``` where `pkgdir` is a package source directory with a valid `DESCRIPTION` file The setup is as follows. 1. Files having names starting with `test` are in `pkg/inst/tinytest`, e.g. `test_haha.R`. Test files are R scripts interspersed with test commands, such as `expect_equal(myfunc(1), 0)`. 2. `tinytest` is added to `Suggests:` in the `DESCRIPTION` file. 3. A file named `tinytest.R` is set up in `pkg/tests` to make sure that tests will be run by `R CMD check`. A nice way to set up a completely new package that passes `R CMD check` is as follows ``` pkgKitten::kitten("hihi") tinytest::setup_tinytest("hihi") ``` where `hihi` is the name of the new package. #### Interactive package testing | Function | description | |---------------------------------|----------------------------------------------------------| | `test_all("pkgdir")` | run all test files (pkg must be loaded). | | `build_install_test("pkgdir")` | build, install, and test in temp dir. | | `run_test_dir("pkgdir")` | run all test files in a directory (pkg must be loaded). | | `run_test_file("testfile")` | run a single test file (pkg must be loaded). | All functions return an object of class `tinytests`. Results can be printed to screen, summarized with `summary` or converted to data frame with `as.data.frame` for analyses. The option `verbose` (default: `2`) controls showing test progress in the terminal. #### Test functions The syntax of test functions resembles that of [testthat](https://CRAN.R-project.org/package=testthat). For expectations comparing two results, the first argument represents the _observed_ value while the second argument represents the _desired_ value. |Function | description | |----------------------------------|------------------------------------------------------| | `expect_true` | Argument must evaluate to `TRUE` | | `expect_false` | Argument must evaluate to `FALSE` | | `expect_equal` | Data and attributes of arguments must be equal | | `expect_equivalent` | Data of arguments must be equal | | `expect_identical` | Target and current must be `identical` | | `expect_inherits` | Current object must inherit from the desired class | | `expect_null` | Expression must evaluate to `NULL` | | `expect_equal_to_reference` | Object must be equal to an object stored on file | | `expect_equivalent_to_reference` | Object must be equivalent to an object stored on file| | `expect_stdout` | Expect a printed message (via `print` or `cat`) | | `expect_message` | Expression must yield a message | | `expect_warning` | Expression must yield a warning | | `expect_error` | Expression must yield an error | | `expect_silent` | Expect no errors, no warnings | For tests in a script there is an alternative syntax in the style of [RUnit](https://CRAN.R-project.org/package=RUnit). For each function of the form `expect_lol` there is a function of the form `checkLol`. #### Monitor side-effects Side-effects, such as changing environment variables, locale settings, or changing the working directory can cause hard-to-trace bugs. Add the statement ``` report_side_effects() ``` to a test file and certain types of side-effects, if any, are reported. Alternatively, use the `side_effects` argument to any of the test runners, for example ``` test_all("/path/to/package", side_effects=TRUE) ``` #### Run test with custom environment variables set Temporarily set environment variables for the run of the test. For example: ``` test_all("/path/to/package", setenv=list("wa_babalooba" = "ba_la_bamboo")) ``` #### Print options Test results (objects of class `tinytests`) have two printing modes: a long format and a short, one-line format. Information that is always shown includes: - File name and line number of failed test. - The test call that resulted in test failure. - The type of failure. This can be 'data' (for differences in variable content), 'attr' (for differences in attributes like column names), or 'xcpt' for exceptions (warnings, errors). In long format, the test call and difference between desired and realized input are shown in full. Global printing options can be set with `options(option=value)`. |Option | default | description | |---------------|----------|-------------------------------| | `tt.pr.passes`| FALSE | print passing tests? | | `tt.pr.limit` | 10 | how many results to print? | | `tt.pr.nlong` | 3 | how many tests in long format?| | `tt.pr.color` | TRUE | print colored output? | It is also possible to influence these options using `print.tinytest`. Colored output is suppressed on systems with a [`"dumb"`](https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals) terminal. #### Run tests for an installed package For a package called `haha` that is tested with `tinytest`, any user that has `haha` and `tinytest` installed can run tests as follows. ``` tinytest::test_package("haha") ``` #### Run tests in parallel Run tests in parallel over files. ``` tinytest::test_package("haha", ncpu=3) ``` Or, for more control: ``` cl <- parallel::makeCluster(4) parallel::clusterCall(cl, source, "R/functions.R") test_all(cluster=cl) stopCluster(cl) ``` #### Use extension packages Add the following to a test file to use assertions exported by [ttdo](https://CRAN.r-project.org/package=ttdo). ``` using(ttdo) ``` this will give you excellent diff output of the [diffobj](https://CRAN.r-project.org/package=diffobj) package in `tinytest` test results. The high-performance [checkmate](https://CRAN.r-project.org/package=checkmate) package also extends `tinytest`. #### Skipping or ignoring tests Use `exit_file()` to stop executing a test file, with an optional message. ``` exit_file("I'm too tired to test today") ``` Use `ignore(testfunction)` to run a test but not include the result in the output. ``` # both tests run, but only second is recorded. if ( ignore(expect_equal)(1 + 1, 2) ){ expect_true( 1 > 0 ) } ``` Note the placement of brackets. Use `at_home()` to detect whether a test is running interactively, or via `test_package()` (i.e. the way `R CMD check` will run it). ``` if ( at_home() ){ # run tests requiring lots of time. } ``` The package vignette has some tips on how to use this feature, and how you can set up your package so `R CMD check` also runs tests protected by `at_home()` in your environment. #### Comparing with data stored on file Data can be loaded from `pkg/inst/tinytest` (or subdirectories). A simple test file might look like this. ``` desired <- read.csv("mycsvoutput.csv", stringsAsFactors=FALSE) obtained <- compute_my_result() expect_equal(obtained, desired) ``` If you wish to publish the package on CRAN, make sure that the files are small enough for the package to be acceptable. See the [CRAN repository policy](https://cran.r-project.org/web/packages/policies.html) for explicit bounds on package size. Alternatively you can avoid installing the data and associated test files by adding them to [.Rinstignore](https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Package-subdirectories). #### More information See the vignette. ``` vignette("using_tinytest", package="tinytest") ``` tinytest/man/0000755000176200001440000000000013630256307012715 5ustar liggesuserstinytest/man/tinytests.Rd0000644000176200001440000000612713605276301015256 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/methods.R \name{summary.tinytests} \alias{summary.tinytests} \alias{all_pass} \alias{any_pass} \alias{all_fail} \alias{any_fail} \alias{[.tinytests} \alias{tinytests} \alias{print.tinytests} \alias{as.data.frame.tinytests} \title{Tinytests object} \usage{ \method{summary}{tinytests}(object, ...) all_pass(x) any_pass(x) all_fail(x) any_fail(x) \method{[}{tinytests}(x, i) \method{print}{tinytests}( x, passes = getOption("tt.pr.passes", FALSE), sidefx = getOption("tt.pr.sidefx", TRUE), limit = getOption("tt.pr.limit", 7), nlong = getOption("tt.pr.nlong", 3), ... ) \method{as.data.frame}{tinytests}(x, ...) } \arguments{ \item{object}{a \code{tinytests} object} \item{...}{passed to \code{\link{format.tinytest}}} \item{x}{a \code{tinytests} object} \item{i}{an index} \item{passes}{\code{[logical]} Toggle: print passing tests?} \item{sidefx}{\code{[logical]} Toggle: print side effects?} \item{limit}{\code{[numeric]} Max number of results to print} \item{nlong}{\code{[numeric]} First \code{nlong} results are printed in long format.} } \value{ For \code{summary} a \code{\link{table}} object For \code{all_pass}, \code{any_pass}, \code{all_fail}, \code{any_fail}: a single \code{logical} For \code{`[.tinytests`} a \code{tinytests} object. For \code{as.data.frame.} a data frame. } \description{ An object of class \code{tinytests} (note: plural) results from running multiple tests from script. E.g. by running \code{\link{run_test_file}}. } \section{Details}{ By default, the first 3 failing test results are printed in long form, the next 7 failing test results are printed in short form and all other failing tests are not printed. These defaults can be changed by passing options to \code{print.tinytest}, or by setting one or more of the following global options: \itemize{ \item{\code{tt.pr.passes} Set to \code{TRUE} to print output of non-failing tests.} \item{\code{tt.pr.limit} Max number of results to print (e.g. \code{Inf})} \item{\code{tt.pr.nlong} The number of results to print in long format (e.g. \code{Inf}).} } For example, set \code{options(tt.pr.limit=Inf)} to print all test results. Furthermore, there is the option \itemize{ \item{\code{tt.pr.color},} } which determines whether colored output is printed. If R is running in a dumb terminal (detected by comparing environment variable \code{"TERM"} to \code{"dumb"}), then this option is set to \code{FALSE} when the package is loaded. } \examples{ # create a test file in tempdir tests <- " addOne <- function(x) x + 2 expect_true(addOne(0) > 0) expect_equal(2, addOne(1)) " testfile <- tempfile(pattern="test_", fileext=".R") write(tests, testfile) # extract testdir testdir <- dirname(testfile) # run all files starting with 'test' in testdir out <- run_test_dir(testdir) # # print results print(out) summary(out) dat <- as.data.frame(out) out[1] } \seealso{ Other test-files: \code{\link{build_install_test}()}, \code{\link{exit_file}()}, \code{\link{run_test_dir}()}, \code{\link{run_test_file}()}, \code{\link{test_package}()} } \concept{test-files} tinytest/man/setup_tinytest.Rd0000644000176200001440000000177613514410021016304 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/setup.R \name{setup_tinytest} \alias{setup_tinytest} \title{Add tinytest to package source directory} \usage{ setup_tinytest(pkgdir, force = FALSE, verbose = TRUE) } \arguments{ \item{pkgdir}{\code{[character]} Package source directory} \item{force}{\code{[logical]} Toggle overwrite existing files? (not folders)} \item{verbose}{\code{[logical]} Toggle print progress} } \value{ \code{NULL}, invisibly. } \description{ Creates \code{inst/tinytest}, and an example test file in that directory. Creates \code{tests/tinytest.R} so the package is tested with \code{R CMD check}. Adds \code{tinytests} as a suggested package to the \code{DESCRIPTION}. } \section{Note on \code{DESCRIPTION}}{ Fails when it does not exist. It is assumed that the package is named in the \code{DESCRIPTION}. } \examples{ \dontrun{ # an easy way to set up a package 'haha' that passes # R CMD check pkgKitten::kitten("haha") tinytest::setup_tinytest("haha") } } tinytest/man/register_tinytest_extension.Rd0000644000176200001440000000565313632777604021112 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{register_tinytest_extension} \alias{register_tinytest_extension} \title{Register or unregister extension functions} \usage{ register_tinytest_extension(pkg, functions) } \arguments{ \item{pkg}{\code{[character]} scalar. Name of the package providing extensions.} \item{functions}{\code{[character]} vector. Name of the functions in the package that must be added.} } \description{ Functions to use in \code{.onLoad} and \code{.onUnload} by packages that extend \pkg{tinytest}. } \section{The tinytest API}{ Packages can extend \pkg{tinytest} with expectation functions \emph{if and only if} the following requirements are satisfied. \enumerate{ \item{The extending functions return a \code{\link{tinytest}} object. This can be created by calling \code{tinytest()} with the arguments (defaults, if any, are in brackets): \itemize{ \item{\code{result}: A \code{logical} scalar: \code{TRUE} or \code{FALSE} (not \code{NA}) } \item{\code{call}: The \code{call} to the expectation function. Usually the result of \code{sys.call(sys.parent(1))} } \item{\code{diff} (\code{NA_character_}): A \code{character} scalar, with a long description of the difference. Sentences may be separated by \code{"\\n"}.} \item{\code{short} (\code{NA_character_}): Either \code{"data"}, if the difference is in the data. \code{"attr"} when attributes differ or \code{"xcpt"} when an expectation about an exception is not met. If there are differences in data and in attributes, the attributes take precedence.} \item{\code{info}} (\code{NA_character_}): A user-defined message. } Observe that this requires the extending package to add \pkg{tinytest} to the \code{Imports} field in the package's \code{DESCRIPTION} file (this also holds for the following requirement). } \item{Functions are registered in \code{.onLoad()} using \code{register_tinytest_extension()}. Functions that are already registered, including \pkg{tinytest} functions will be overwritten.} } It is \emph{recommended} to: \enumerate{ \item{Follow the syntax conventions of \pkg{tinytest} so expectation functions start with \code{expect_}.} \item{Explain to users of the extension package how to use the extension (see \code{\link{using}}).} \item{include an \code{info} argument to \code{expect_} functions that is passed to \code{tinytest()}}. } } \section{Minimal example packages}{ \itemize{ \item{Extending \pkg{tinytest}: \href{https://github.com/markvanderloo/tinytest.extension}{tinytest.extension}.} \item{Using a \pkg{tinytest} extension: \href{https://github.com/markvanderloo/uses.tinytest.extension}{using.tinytest.extension}.} } } \seealso{ Other extensions: \code{\link{tinytest}()}, \code{\link{using}()} } \concept{extensions} tinytest/man/report_side_effects.Rd0000644000176200001440000000364314063205173017224 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/expectations.R \name{report_side_effects} \alias{report_side_effects} \title{Report side effects for expressions in test files} \usage{ report_side_effects( report = TRUE, envvar = report, pwd = report, files = report, locale = report ) } \arguments{ \item{report}{\code{[logical]} report all side-effects} \item{envvar}{\code{[logical]} changes in environment variables} \item{pwd}{\code{[logical]} changes in working directory} \item{files}{\code{[logical]} changes in files in the directory where the test file lives. Also watches subdirectories.} \item{locale}{\code{[logical]} Changes in locale settings as detected by \code{link[base]{Sys.getlocale}} are reported.} } \value{ A named \code{logical}, indicating which aspects of the environment are tracked, invisibly. } \description{ Call this function from within a test file to report side effects. } \section{Details}{ A side effect causes a change in an external variable outside of the scope of a function, or test file. This includes environment variables, global options, global R variables, creating files or directories, and so on. If this function is called in a test file, side effects are monitored from that point in the file and only for that file. The state of the environment before and after running every expression in the file are compared. There is some performance penalty in tracking external variables, especially for those that require a system call. } \section{Note}{ There could be side-effects that are untrackable by \pkg{tinytest}. This includes packages that use a global internal state within their namespace or packages that use a global state within compiled code. } \examples{ # switch on report_side_effects() # switch off report_side_effects(FALSE) # only report changes in environment variables report_side_effects(report=FALSE, envvar=TRUE) } \concept{sidefx} tinytest/man/expect_equal.Rd0000644000176200001440000000734214065357611015674 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/expectations.R \name{expect_equal} \alias{expect_equal} \alias{expect_identical} \alias{expect_equivalent} \alias{expect_true} \alias{expect_false} \alias{expect_silent} \alias{expect_null} \alias{expect_inherits} \alias{expect_error} \alias{expect_warning} \alias{expect_message} \alias{expect_stdout} \title{Express expectations} \usage{ expect_equal( current, target, tolerance = sqrt(.Machine$double.eps), info = NA_character_, ... ) expect_identical(current, target, info = NA_character_) expect_equivalent( current, target, tolerance = sqrt(.Machine$double.eps), info = NA_character_, ... ) expect_true(current, info = NA_character_) expect_false(current, info = NA_character_) expect_silent(current, quiet = TRUE, info = NA_character_) expect_null(current, info = NA_character_) expect_inherits(current, class, info = NA_character_) expect_error( current, pattern = ".*", class = "error", info = NA_character_, ... ) expect_warning( current, pattern = ".*", class = "warning", info = NA_character_, ... ) expect_message( current, pattern = ".*", class = "message", info = NA_character_, ... ) expect_stdout(current, pattern = ".*", info = NA_character_, ...) } \arguments{ \item{current}{\code{[R object or expression]} Outcome or expression under scrutiny.} \item{target}{\code{[R object or expression]} Expected outcome} \item{tolerance}{\code{[numeric]} Test equality to machine rounding. Passed to \code{\link[base]{all.equal} (tolerance)}} \item{info}{\code{[character]} scalar. Optional user-defined message. Must be a single character string. Multiline comments may be separated by \code{"\\n"}.} \item{...}{passed on to \code{\link{grepl}} (useful for e.g. \code{fixed=TRUE}).} \item{quiet}{\code{[logical]} suppress output printed by the \code{current} expression (see examples)} \item{class}{\code{[character]} For condition signals (error, warning, message) the class from which the condition should inherit.} \item{pattern}{\code{[character]} A regular expression to match the message.} } \value{ A \code{\link{tinytest}} object. A tinytest object is a \code{logical} with attributes holding information about the test that was run } \description{ Express expectations } \details{ \code{expect_equivalent} calls \code{expect_equal} with the extra arguments \code{check.attributes=FALSE} and \code{use.names=FALSE} \code{expect_silent} fails when an error or warning is thrown. \code{expect_inherits} fails when \code{\link{inherits}(current,class)} returns \code{FALSE} \code{expect_stdout} Expects that output is written to \code{stdout}, for example using \code{cat} or \code{print}. Use \code{pattern} to specify a regular expression matching the output. } \note{ Each \code{expect_haha} function can also be called as \code{checkHaha}. Although the interface is not entirely the same, it is expected that this makes migration from the \code{RUnit} framework a little easier, for those who wish to do so. } \section{More information and examples}{ \itemize{ \item{An overview of tinytest can be found in \code{vignette("using_tinytest")}}. \item{Examples of how tinytest is used in practice can be found in \code{vignette("tinytest_examples")}} } } \examples{ expect_equal(1 + 1, 2) # TRUE expect_equal(1 - 1, 2) # FALSE expect_equivalent(2, c(x=2)) # TRUE expect_equal(2, c(x=2)) # FALSE expect_silent(1+1) # TRUE expect_silent(1+"a") # FALSE expect_silent(print("hihi")) # TRUE, nothing goes to screen expect_silent(print("hihi"), quiet=FALSE) # TRUE, and printed } \seealso{ Other test-functions: \code{\link{expect_equal_to_reference}()}, \code{\link{ignore}()} } \concept{test-functions} tinytest/man/get_call_wd.Rd0000644000176200001440000000111313632775737015463 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{get_call_wd} \alias{get_call_wd} \title{Get workding dir from where a test was initiated} \usage{ get_call_wd() } \value{ \code{[character]} A path. } \description{ A test runner, like \code{\link{run_test_file}} changes R's working directory to the location of the test file temporarily while the tests run. This function can be used from within the test file to get R's working directory at the time \code{run_test_file} (or one of it's siblings) was called. } \examples{ get_call_wd() } tinytest/man/test_package.Rd0000644000176200001440000000464713762245311015650 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{test_package} \alias{test_package} \title{Test a package during R CMD check or after installation} \usage{ test_package( pkgname, testdir = "tinytest", lib.loc = NULL, at_home = FALSE, ncpu = NULL, ... ) } \arguments{ \item{pkgname}{\code{[character]} scalar. Name of the package, as in the \code{DESCRIPTION} file.} \item{testdir}{\code{[character]} scalar. Path to installed directory. By default tinytest assumes that test files are in \code{inst/tinytest/}, which means that after installation and thus during \code{R CMD check} they are in \code{tinytest/}. See details for using alternate paths.} \item{lib.loc}{\code{[character]} scalar. location where the package is installed.} \item{at_home}{\code{[logical]} scalar. Are we at home? (see Details)} \item{ncpu}{A positive integer, or a \code{\link{makeCluster}} object.} \item{...}{extra arguments passed to \code{\link{run_test_dir}} (e.g. \code{ncpu}).} } \value{ If \code{interactive()}, a \code{tinytests} object. If not \code{interactive()}, an error is thrown when at least one test fails. } \description{ Run all tests in an installed package. Throw an error and print all failed test results when one or more tests fail if not in interactive mode (e.g. when R CMD check tests a package). This function is intended to be used by \code{R CMD check} or by a user that installed a package that uses the \pkg{tinytest} test infrastructure. } \section{Details}{ We set \code{at_home=FALSE} by default so \code{R CMD check} will run the same as at CRAN. See the package vignette (Section 4) for tips on how to set up the package structure. \code{vignette("using_tinytest",package="tinytest")}. Package authors who want to avoid installing tests with the package can create a directory under \code{tests}. If the test directoy is called \code{"tests/foo"}, use \code{test_package("pkgname", testdir="foo")} in \code{tests/tinytest.R}. } \examples{ \dontrun{ # Create a file with the following content, to use # tinytest as your unit testing framework: if (requireNamespace("tinytest", quietly=TRUE)) tinytest::test_package("your package name") } } \seealso{ \code{\link{setup_tinytest}} Other test-files: \code{\link{build_install_test}()}, \code{\link{exit_file}()}, \code{\link{run_test_dir}()}, \code{\link{run_test_file}()}, \code{\link{summary.tinytests}()} } \concept{test-files} tinytest/man/expect_equal_to_reference.Rd0000644000176200001440000000412014065357611020403 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/expectations.R \name{expect_equal_to_reference} \alias{expect_equal_to_reference} \alias{expect_equivalent_to_reference} \title{Compare object with object stored in a file} \usage{ expect_equal_to_reference(current, file, ...) expect_equivalent_to_reference(current, file, ...) } \arguments{ \item{current}{\code{[R object or expression]} Outcome or expression under scrutiny.} \item{file}{\code{[character]} File where the \code{target} is stored. If \code{file} does not exist, \code{current} will be stored there.} \item{...}{passed to \code{\link{expect_equal}}, respectively \code{\link{expect_equivalent}}.} } \description{ Compares the current value with a value stored to file with \code{\link{saveRDS}}. If the file does not exist, the current value is stored into file, and the test returns \code{expect_null(NULL)}. } \note{ Be aware that on CRAN it is not allowed to write data to user space. So make sure that the file is either stored with your tests, or generated with \code{\link{tempfile}}, or the test is skipped on CRAN, using \code{\link{at_home}}. \code{\link{build_install_test}} clones the package and builds and tests it in a separate R session in the background. This means that if you create a file located at \code{tempfile()} during the run, this file is destroyed when the separate R session is closed. \code{expect_error}, \code{expect_warning} and \code{expect_message} will concatenate all messages when multiple exceptions are thrown, before matching the message to \code{pattern}. } \examples{ filename <- tempfile() # this gives TRUE: the file does not exist, but is created now. expect_equal_to_reference(1, file=filename) # this gives TRUE: the file now exists, and its contents is equal # to the current value expect_equal_to_reference(1, file=filename) # this gives FALSE: the file exists, but is contents is not equal # to the current value expect_equal_to_reference(2, file=filename) } \seealso{ Other test-functions: \code{\link{expect_equal}()}, \code{\link{ignore}()} } \concept{test-functions} tinytest/man/exit_file.Rd0000644000176200001440000000114113605276301015147 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{exit_file} \alias{exit_file} \title{Stop testing} \usage{ exit_file(msg = "") } \arguments{ \item{msg}{\code{[character]} An optional message to print after exiting.} } \value{ The exit message } \description{ Call this function to exit a test file. } \examples{ exit_file("I'm too tired to test") } \seealso{ Other test-files: \code{\link{build_install_test}()}, \code{\link{run_test_dir}()}, \code{\link{run_test_file}()}, \code{\link{summary.tinytests}()}, \code{\link{test_package}()} } \concept{test-files} tinytest/man/ignore.Rd0000644000176200001440000000274013624033325014466 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{ignore} \alias{ignore} \title{Ignore the output of an expectation} \usage{ ignore(fun) } \arguments{ \item{fun}{\code{[function]} An \code{expect_} function} } \value{ An ignored \code{function} } \description{ Ignored expectations are not reported in the test results. Ignoring is only useful for test files, and not for use directly at the command-line. See also the package vignette: \code{vignette("using_tinytest")}. } \section{Details}{ \code{ignore} is a higher-order function: a function that returns another function. In particular, it accepts a function and returns a function that is almost identical to the input function. The only difference is that the return value of the function returned by \code{ignore} is not caught by \code{\link{run_test_file}} and friends. For example, \code{ignore(expect_true)} is a function, and we can use it as \code{ignore(expect_true)( 1 == 1)}. The return value of \code{ignore(expect_true)(1==1)} is exactly the same as that for \code{expect_true(1==1)}. } \examples{ \donttest{ ## The result of 'expect_warning' is not stored in the test result when ## this is run from a file. expect_true( ignore(expect_warning)(warning("foo!")) ) ## Note the placement of the brackets in ignore(expect_warning)(...). } } \seealso{ Other test-functions: \code{\link{expect_equal_to_reference}()}, \code{\link{expect_equal}()} } \concept{test-functions} tinytest/man/using.Rd0000644000176200001440000000240013672467350014335 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{using} \alias{using} \title{Use an extension package.} \usage{ using(package, quietly = TRUE) } \arguments{ \item{package}{the name of the extension package, given as name or character string.} \item{quietly}{Passed to \code{\link{require}}.} } \value{ A named \code{list}, with the package name and the names of the functions registered by \code{package} to extend \pkg{tinytest}. A message is emitted when the package registers no extension functions. } \description{ Loads and attaches a package to the search path, and picks up the \pkg{tinytest} extension functions registered by the package. Package authors \emph{must} call this function in \emph{every} test file where an extension is used, or otherwise results from the extension package are not recorded (without a warning). Calling \code{using} in every file where an extension is used also ensures that tests can be run in parallel. } \examples{ \dontrun{ # In interactive session: see which functions are exported # by checkmate.tinytest out <- using(checkmate.tinytest) print(out) } } \seealso{ Other extensions: \code{\link{register_tinytest_extension}()}, \code{\link{tinytest}()} } \concept{extensions} tinytest/man/tinytest.Rd0000644000176200001440000000344214063204646015072 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/expectations.R \name{tinytest} \alias{tinytest} \title{Tinytest constructor} \usage{ tinytest( result, call, trace = NULL, diff = NA_character_, short = c(NA_character_, "data", "attr", "xcpt", "envv", "wdir", "file", "lcle"), info = NA_character_, file = NA_character_, fst = NA_integer_, lst = NA_integer_, ... ) } \arguments{ \item{result}{\code{[logical]} scalar.} \item{call}{\code{[call]} The call that created \code{result}.} \item{diff}{\code{[character]} difference between current and target value (if any).} \item{short}{\code{[character]} short description of the difference} \item{info}{\code{[character]} other information, to be printed in the long message} \item{file}{\code{[character]} File location of the test.} \item{fst}{\code{[integer]} First line number in the test file.} \item{lst}{\code{[integer]} Last line number in the test file (differs from \code{fst} if the call spans multiple lines).} } \value{ A \code{tinytest} object. } \description{ Each individual test in the package generates a \code{tinytest} object. A \code{tinytest} object is a \code{logical} scalar, with metadata (attributes) about the test. } \section{Details}{ The \pkg{result} can take three values. \itemize{ \item{\code{TRUE}: test was passed.} \item{\code{FALSE}: test was failed.} \item{\code{NA}: A side effect was detected.} } Authors of extension packages should not use \code{NA} as a result value as this part of the interface may change in the future. } \examples{ tt <- expect_equal(1+1, 2) if (isTRUE(tt)){ print("w00p w00p!") } else { print("Oh no!") } } \seealso{ Other extensions: \code{\link{register_tinytest_extension}()}, \code{\link{using}()} } \concept{extensions} \keyword{internal} tinytest/man/at_home.Rd0000644000176200001440000000061213457665033014626 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{at_home} \alias{at_home} \title{Detect not on CRANity} \usage{ at_home() } \description{ Detect whether we are running at home (i.e. not on CRAN, BioConductor, ...) } \examples{ # test will run locally, but not on CRAN if ( at_home() ){ expect_equal(2, 1+1) } } \concept{test-functions test-file} tinytest/man/run_test_file.Rd0000644000176200001440000000675213672467350016070 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{run_test_file} \alias{run_test_file} \title{Run an R file containing tests; gather results} \usage{ run_test_file( file, at_home = TRUE, verbose = getOption("tt.verbose", 2), color = getOption("tt.pr.color", TRUE), remove_side_effects = TRUE, side_effects = FALSE, set_env = list(), ... ) } \arguments{ \item{file}{\code{[character]} File location of a .R file.} \item{at_home}{\code{[logical]} toggle local tests.} \item{verbose}{\code{[integer]} verbosity level. 0: be quiet, 1: print status per file, 2: print status and increase counter after each test expression.} \item{color}{\code{[logical]} toggle colorize counts in verbose mode (see Note)} \item{remove_side_effects}{\code{[logical]} toggle remove user-defined side effects? See section on side effects.} \item{side_effects}{\code{[logical|list]} Either a logical, or a list of arguments to pass to \code{\link{report_side_effects}}.} \item{set_env}{\code{[named list]}. Key=value pairs of environment variables that will be set before the test file is run and reset afterwards. These are not counted as side effects of the code under scrutiny.} \item{...}{Currently unused} } \value{ A \code{list} of class \code{tinytests}, which is a list of \code{\link{tinytest}} objects. } \description{ Run an R file containing tests; gather results } \details{ In \pkg{tinytest}, a test file is just an R script where some or all of the statements express an \code{\link[=expect_equal]{expectation}}. \code{run_test_file} runs the file while gathering results of the expectations in a \code{\link{tinytests}} object. The graphics device is set to \code{pdf(file=tempfile())} for the run of the test file. } \note{ Not all terminals support ansi escape characters, so colorized output can be switched off. This can also be done globally by setting \code{options(tt.pr.color=FALSE)}. Some terminals that do support ansi escape characters may contain bugs. An example is the RStudio terminal (RStudio 1.1) running on Ubuntu 16.04 (and possibly other OSs). } \section{Side-effects caused by test code}{ All calls to \code{\link{Sys.setenv}} and \code{\link{options}} defined in a test file are captured and undone once the test file has run, if \code{remove_side_effects} is set to \code{TRUE}. } \section{Tracking side effects}{ Certain side effects can be tracked, even when they are not explicitly evoked in the test file. See \code{\link{report_side_effects}} for side effects tracked by \pkg{tinytest}. Calls to \code{report_side_effects} within the test file overrule settings provided with this function. } \examples{ # create a test file, in temp directory tests <- " addOne <- function(x) x + 2 Sys.setenv(lolz=2) expect_true(addOne(0) > 0) expect_equal(2, addOne(1)) Sys.unsetenv('lolz') " testfile <- tempfile(pattern="test_", fileext=".R") write(tests, testfile) # run test file out <- run_test_file(testfile,color=FALSE) out # print everything in short format, include passes in print. print(out, nlong=0, passes=TRUE) # run test file, track supported side-effects run_test_file(testfile, side_effects=TRUE) # run test file, track only changes in working directory run_test_file(testfile, side_effects=list(pwd=TRUE, envvar=FALSE)) } \seealso{ \code{\link{ignore}} Other test-files: \code{\link{build_install_test}()}, \code{\link{exit_file}()}, \code{\link{run_test_dir}()}, \code{\link{summary.tinytests}()}, \code{\link{test_package}()} } \concept{test-files} tinytest/man/run_test_dir.Rd0000644000176200001440000000765013672467564015734 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{run_test_dir} \alias{run_test_dir} \alias{test_all} \title{Run all tests in a directory} \usage{ run_test_dir( dir = "inst/tinytest", pattern = "^test.*\\\\.[rR]$", at_home = TRUE, verbose = getOption("tt.verbose", 2), color = getOption("tt.pr.color", TRUE), remove_side_effects = TRUE, cluster = NULL, lc_collate = getOption("tt.collate", NA), ... ) test_all(pkgdir = "./", testdir = "inst/tinytest", ...) } \arguments{ \item{dir}{\code{[character]} path to directory} \item{pattern}{\code{[character]} A regular expression that is used to find scripts in \code{dir} containing tests (by default \code{.R} or \code{.r} files starting with \code{test}).} \item{at_home}{\code{[logical]} toggle local tests.} \item{verbose}{\code{[logical]} toggle verbosity during execution} \item{color}{\code{[logical]} toggle colorize output} \item{remove_side_effects}{\code{[logical]} toggle remove user-defined side effects. Environment variables (\code{Sys.setenv()}) and options (\code{options()}) defined in a test file are reset before running the next test file (see details).} \item{cluster}{A \code{\link{makeCluster}} object.} \item{lc_collate}{\code{[character]} Locale setting used to sort the test files into the order of execution. The default \code{NA} ensures current locale is used. Set this e.g. to \code{"C"} to ensure bytewise and more platform-independent sorting (see details).} \item{...}{Arguments passed to \code{run_test_file}} \item{pkgdir}{\code{[character]} scalar. Root directory of the package (i.e. direcory where \code{DESCRIPTION} and \code{NAMESPACE} reside).} \item{testdir}{\code{[character]} scalar. Subdirectory where test files are stored.} } \value{ A \code{tinytests} object } \description{ \code{run\_test\_dir} runs all test files in a directory. \code{test_all} is a convenience function for package development, that wraps \code{run_test_dir}. By default, it runs all files starting with \code{test} in \code{./inst/tinytest/}. It is assumed that all functions to be tested are loaded. } \section{Details}{ We cannot guarantee that files will be run in any particular order accross all platforms, as it depends on the available collation charts (a chart that determines how alphabets are sorted). For this reason it is a good idea to create test files that run independent of each other so their order of execution does not matter. In tinytest, test files cannot share variables. The default behavior of test runners further discourages interdependence by resetting environment variables and options that are set in a test file after the file is executed. If an environment variable needs to survive a single file, use \code{base::Sys.setenv()} explicitly. Similarly, if an option setting needs to survive, use \code{base::options()} } \section{Parallel tests}{ If \code{inherits(cluster, "cluster")} the tests are paralellized over a cluster of worker nodes. \pkg{tinytest} will be loaded onto each cluster node. All other preparation, including loading code from the tested package, must be done by the user. It is also up to the user to clean up the cluster after running tests. See the 'using tinytest' vignette for examples: \code{vignette("using_tinytest")}. } \examples{ # create a test file in tempdir tests <- " addOne <- function(x) x + 2 expect_true(addOne(0) > 0) expect_equal(2, addOne(1)) " testfile <- tempfile(pattern="test_", fileext=".R") write(tests, testfile) # extract testdir testdir <- dirname(testfile) # run all files starting with 'test' in testdir out <- run_test_dir(testdir) print(out) dat <- as.data.frame(out) } \seealso{ \code{\link{makeCluster}}, \code{\link{clusterEvalQ}}, \code{\link{clusterExport}} Other test-files: \code{\link{build_install_test}()}, \code{\link{exit_file}()}, \code{\link{run_test_file}()}, \code{\link{summary.tinytests}()}, \code{\link{test_package}()} } \concept{test-files} tinytest/man/puppy.Rd0000644000176200001440000000113213523342312014347 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/setup.R \name{puppy} \alias{puppy} \title{The puppy for a pkgKitten} \usage{ puppy(pkgdir, force = FALSE, verbose = TRUE) } \arguments{ \item{pkgdir}{\code{[character]} Package source directory} \item{force}{\code{[logical]} Toggle overwrite existing files? (not folders)} \item{verbose}{\code{[logical]} Toggle print progress} } \description{ Does exactly the same as \code{\link{setup_tinytest}}, but prints a loving message aferwards (and who doesn't want that!?). Just think about those puppies. } \keyword{internal} tinytest/man/build_install_test.Rd0000644000176200001440000000463613747061527017110 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tinytest.R \name{build_install_test} \alias{build_install_test} \title{build, install and test} \usage{ build_install_test( pkgdir = "./", testdir = "tinytest", pattern = "^test.*\\\\.[rR]$", at_home = TRUE, verbose = getOption("tt.verbose", 2), ncpu = 1, remove_side_effects = TRUE, side_effects = FALSE, lc_collate = getOption("tt.collate", NA), keep_tempdir = FALSE ) } \arguments{ \item{pkgdir}{\code{[character]} Package directory} \item{testdir}{\code{[character]} Name of directory under \code{pkgdir/inst} containing test files.} \item{pattern}{\code{[character]} A regular expression that is used to find scripts in \code{dir} containing tests (by default \code{.R} or \code{.r} files starting with \code{test}).} \item{at_home}{\code{[logical]} toggle local tests.} \item{verbose}{\code{[logical]} toggle verbosity during execution} \item{ncpu}{\code{[numeric]} number of CPUs to use during the testing phase.} \item{remove_side_effects}{\code{[logical]} toggle remove user-defined side effects? See section on side effects.} \item{side_effects}{\code{[logical|list]} Either a logical, or a list of arguments to pass to \code{\link{report_side_effects}}.} \item{lc_collate}{\code{[character]} Locale setting used to sort the test files into the order of execution. The default \code{NA} ensures current locale is used. Set this e.g. to \code{"C"} to ensure bytewise and more platform-independent sorting (see details in \code{\link{run_test_dir}}.} \item{keep_tempdir}{\code{[logical]} keep directory where the pkg is installed and where tests are run? If \code{TRUE}, the directory is not deleted and it's location is printed.} } \value{ A \code{tinytests} object. } \description{ Builds and installs the package in \code{pkgdir} under a temporary directory. Next, loads the package in a fresh R session and runs all the tests. For this function to work the following system requirements are necessary. \itemize{ \item{\code{R CMD build} is available on your system} \item{\code{Rscript} is available on your system} } } \examples{ \dontrun{ ## If your package source directory is "./pkg" you can run build_install_test("pkg") } } \seealso{ Other test-files: \code{\link{exit_file}()}, \code{\link{run_test_dir}()}, \code{\link{run_test_file}()}, \code{\link{summary.tinytests}()}, \code{\link{test_package}()} } \concept{test-files} tinytest/man/print.tinytest.Rd0000644000176200001440000000123413530043747016223 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/expectations.R \name{format.tinytest} \alias{format.tinytest} \alias{print.tinytest} \title{Print a tinytest object} \usage{ \method{format}{tinytest}(x, type = c("long", "short"), ...) \method{print}{tinytest}(x, ...) } \arguments{ \item{x}{A \code{tinytest} object} \item{type}{\code{[logical]} Toggle format type} \item{...}{passed to \code{\link{format.tinytest}}} } \value{ A character string } \description{ Print a tinytest object } \examples{ tt <- expect_equal(1+1, 3) format(tt,"long") format(tt,"short") print(expect_equal(1+1, 2)) print(expect_equal(1+1, 3), type="long") } tinytest/DESCRIPTION0000644000176200001440000000260314071100132013631 0ustar liggesusersPackage: tinytest Maintainer: Mark van der Loo License: GPL-3 Title: Lightweight and Feature Complete Unit Testing Framework Type: Package LazyLoad: yes Authors@R: c( person("Mark", "van der Loo" , role = c("aut","cre") , email = "mark.vanderloo@gmail.com" , comment = c(ORCID="0000-0002-9807-4686")) ) Description: Provides a lightweight (zero-dependency) and easy to use unit testing framework. Main features: install tests with the package. Test results are treated as data that can be stored and manipulated. Test files are R scripts interspersed with test commands, that can be programmed over. Fully automated build-install-test sequence for packages. Skip tests when not run locally (e.g. on CRAN). Flexible and configurable output printing. Compare computed output with output stored with the package. Run tests in parallel. Extensible by other packages. Report side effects. Version: 1.3.1 URL: https://github.com/markvanderloo/tinytest BugReports: https://github.com/markvanderloo/tinytest/issues Depends: R (>= 3.0.0) Imports: parallel, utils RoxygenNote: 7.1.1 Encoding: UTF-8 NeedsCompilation: no Packaged: 2021-07-06 12:29:03 UTC; mark Author: Mark van der Loo [aut, cre] () Repository: CRAN Date/Publication: 2021-07-06 16:10:02 UTC tinytest/build/0000755000176200001440000000000014071046217013236 5ustar liggesuserstinytest/build/vignette.rds0000644000176200001440000000034214071046217015574 0ustar liggesusersOM @]S+BExQM\CM /fsVR;{{r !bM(Mā$2f/zoL{^"z_c=esGy*fD6Rд'O-xS(>j25naihG O-G}_)eB4FvA7i'tinytest/tests/0000755000176200001440000000000013546553641013313 5ustar liggesuserstinytest/tests/tinytest.R0000644000176200001440000000013213546553531015313 0ustar liggesusersif ( requireNamespace("tinytest", quietly=TRUE) ){ tinytest::test_package("tinytest") } tinytest/vignettes/0000755000176200001440000000000014071046217014147 5ustar liggesuserstinytest/vignettes/test_addOne.R0000644000176200001440000000020013473037503016516 0ustar liggesusers# contents of test_addOne.R addOne <- function(x) x + 2 expect_true(addOne(0) > 0) hihi <- 1 expect_equal(addOne(hihi), 2) tinytest/vignettes/using_tinytest.Rnw0000644000176200001440000011103214063211027017717 0ustar liggesusers%\VignetteIndexEntry{Using tinytest} \documentclass[11pt]{article} \usepackage{enumitem} \usepackage{xcolor} % for color definitions \usepackage{sectsty} % to modify heading colors \usepackage{fancyhdr} \setlist{nosep} % simpler, but issue with your margin notes \usepackage[left=1cm,right=3cm, bottom=2cm, top=1cm]{geometry} %\usepackage{vmargin} %\setpapersize{USletter} % or a4 for you % Left Top Right Bottom headheight?? headsep footheight footsep %\setmarginsrb{0.75in}{0.25in}{1.1in}{0.25in}{15pt}{0pt}{10pt}{20pt} \usepackage{hyperref} \definecolor{bluetext}{RGB}{0,101,165} \definecolor{graytext}{RGB}{80,80,80} \hypersetup{ pdfborder={0 0 0} , colorlinks=true , urlcolor=blue , linkcolor=bluetext , linktoc=all , citecolor=blue } \sectionfont{\color{bluetext}} \subsectionfont{\color{bluetext}} \subsubsectionfont{\color{bluetext}} % no serif=better reading from screen. \renewcommand{\familydefault}{\sfdefault} % header and footers \pagestyle{fancy} \fancyhf{} % empty header and footer \renewcommand{\headrulewidth}{0pt} % remove line on top \rfoot{\color{bluetext} tinytest \Sexpr{packageVersion("tinytest")}} \lfoot{\color{black}\thepage} % side-effect of \color{}: lowers the printed text a little(?) \usepackage{fancyvrb} % custom commands make life easier. \newcommand{\code}[1]{\texttt{#1}} \newcommand{\pkg}[1]{\textbf{#1}} \let\oldmarginpar\marginpar \renewcommand{\marginpar}[1]{\oldmarginpar{\color{bluetext}\raggedleft\scriptsize #1}} % skip line at start of new paragraph \setlength{\parindent}{0pt} \setlength{\parskip}{1ex} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \title{Using tinytest} \author{Mark van der Loo} \date{\today{} | Package version \Sexpr{packageVersion("tinytest")}} \begin{document} \DefineVerbatimEnvironment{Sinput}{Verbatim}{fontshape=n,formatcom=\color{graytext}} \DefineVerbatimEnvironment{Soutput}{Verbatim}{fontshape=sl,formatcom=\color{graytext}} \newlength{\fancyvrbtopsep} \newlength{\fancyvrbpartopsep} \makeatletter \FV@AddToHook{\FV@ListParameterHook}{\topsep=\fancyvrbtopsep\partopsep=\fancyvrbpartopsep} \makeatother \setlength{\fancyvrbtopsep}{0pt} \setlength{\fancyvrbpartopsep}{0pt} \maketitle{} \thispagestyle{empty} \tableofcontents{} <>= options(prompt="R> ", continue = " ", width=75, tt.pr.color=FALSE) @ \subsection*{Reading guide} Readers of this document are expected to know how to write R functions and have a basic understanding of a package source directory structure. \newpage{} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Purpose of this package: unit testing} The purpose of \emph{unit testing} is to check whether a function gives the output you expect, when it is provided with certain input. So unit testing is all about comparing \emph{desired} outputs with \emph{realized} outputs. The purpose of this package is to facilitate writing, executing and analyzing unit tests. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Expressing tests} Suppose we define a function translating pounds (lbs) to kilograms inaccurately. <<>>= lbs2kg <- function(x){ if ( x < 0 ){ stop(sprintf("Expected nonnegative weight, got %g",x)) } x/2.20 } @ We like to check a few things before we trust it. <<>>= library(tinytest) expect_equal(lbs2kg(1), 1/2.2046) expect_error(lbs2kg(-3)) @ The value of an \code{expect\_*} function is a \code{logical}, with some attributes that record differences, if there are any. These attributes are used to pretty-print the results. <<>>= isTRUE( expect_true(2 == 1 + 1) ) @ \subsection{Test functions} Currently, the following expectations are implemented. \begin{center} \begin{tabular}{ll} \textbf{Function} & \textbf{what it expects}\\ \code{expect\_equal(current, target)} & equality (using \code{all.equal})\\ \code{expect\_equivalent(current, target)} & equality, ignoring attributes\\ \code{expect\_identical(current, target)} & equality, (using, \code{identical})\\ \code{expect\_true(current)} & \code{current} evaluates to \code{TRUE}\\ \code{expect\_false(current)} & \code{current} evaluates to \code{FALSE}\\ \code{expect\_inherits(current, class)} & \code{current} inherits from \code{class}\\ \code{expect\_null(current)} & \code{current} evaluates to \code{NULL}\\ \code{expect\_error(current, pattern)} & error message matching \code{pattern}\\ \code{expect\_warning(current, pattern)} & warning message matching \code{pattern}\\ \code{expect\_message(current, pattern)} & message matching \code{pattern}\\ \code{expect\_silent(current, pattern)} & expect no warnings or errors (just run)\\ \end{tabular} \end{center} Here, \code{target} is the intended outcome and \code{current} is the observed outcome. Also, \code{pattern} is interpreted as a regular expression. <<>>= expect_error(lbs2kg(-3), pattern="nonnegative") expect_error(lbs2kg(-3), pattern="foo") @ \subsection{Alternative syntax} The syntax of the test functions should be familiar to users of the \code{testthat} package\cite{wickham2016testthat}. In test files only, you can use equivalent functions in the style of \code{RUnit}\cite{burger2016RUnit}. To be precise, for each function of the form \code{expect\_lol} there is a function of the form \code{checkLol}. \subsection{Interpreting the output and print options} Let's have a look at an example again. <<>>= expect_false( 1 + 1 == 2, info="My personal message to the tester" ) @ The output of these functions is pretty self-explanatory, nevertheless we see that the output of these expect-functions consist of \begin{itemize} \item The result: \code{FAILED}, \code{PASSED} or \code{SIDEFX}. The latter only occurs when side effects are monitored (see \S\ref{sect:side}) \item The type of failure (if any) between square brackets. Current options are as follows. \begin{itemize} \item \code{[data]} there are differences between observed and expected values. \item \code{[attr]} there are differences between observed and expected attributes, such as column names. \item \code{[xcpt]} an exception (warning, error) was expected but not observed. \end{itemize} When side effects are monitored, and the result is \code{SIDEFX}, a side effect was observed. The type of side effect is reported between square brackets. \begin{itemize} \item \code{[envv]} An environmental variable was created, changed, or deleted. \item \code{[wdir]} The working directory has changed. \item \code{[file]} A file operation occurred in the test directory or one of its subdirectories. \end{itemize} \item When relevant (see \S\ref{sect:testfiles}), the location of the test file and the relevant line numbers. \item The test call. \item When relevant, a summary of the differences between observed and expected values or attributes, or a summary of the observed side effect. \item When present, a user-defined information message. \end{itemize} The result of an \code{expect\_} function is a \code{tinytest} object. You can print them in long format (default) or in short, one-line format like so. <<>>= print(expect_equal(1+1, 3), type="short") @ \marginpar{\code{print} method} Functions that run multiple tests return an object of class \code{tinytests} (notice the plural). Since there may be a lot of test results, \pkg{tinytest} tries to be smart about printing them. The user has ultimate control over this behaviour. See \code{?print.tinytests} for a full specification of the options. \section{Test files} \label{sect:testfiles} In \pkg{tinytest}, tests are scripts, interspersed with statements that perform checks. An example test file in tinytest can look like this. \begin{verbatim} # contents of test_addOne.R addOne <- function(x) x + 2 expect_true(addOne(0) > 0) hihi <- 1 expect_equal(addOne(hihi), 2) \end{verbatim} A particular file can be run using\marginpar{\code{run\_test\_file}} <>= run_test_file("test_addOne.R", verbose=0) @ We use \code{verbose=0} to avoid cluttering the output in this vignette. By default, verbosity is turned on, and a counter is shown while tests are run. The counter is colorized on terminals supporting ANSI color extensions. If you are uncomfortable reading these colors or prefer colorless output, use \code{color=FALSE} or set \code{options(tt.pr.color=FALSE)}. The numbers between \code{<-->} indicate at what lines in the file the failing test can be found. By default only failing tests are printed. You can store the output and print all of them. <<>>= test_results <- run_test_file("test_addOne.R", verbose=0) print(test_results, passes=TRUE) @ Or you can set <>= options(tt.pr.passes=TRUE) @ to print all results during the active R session. To run all test files in a certain directory, we can use\marginpar{\code{run\_test\_dir}} <>= run_test_dir("/path/to/your/test/directory") @ By default, this will run all files of which the name starts with \code{test\_}, but this is customizable. \subsection{Summarizing test results, getting the data} To create some results, run the tests in this package. <<>>= out <- run_test_dir(system.file("tinytest", package="tinytest") , verbose=0) @ The results can be turned into data using \code{as.data.frame}. \marginpar{\code{as.data.frame}} <<>>= head(as.data.frame(out), 3) @ The last two columns indicate the line numbers where the test was defined. A `summary` of the output gives a table with passes and fails per file. \marginpar{\code{summary}} <<>>= summary(out) @ \subsection{Programming over tests, ignoring test results, exiting early} Test scripts are just R scripts interspersed with tests. The test runners make sure that all test results are caught, unless you tell them not to. For example, since the result of a test is a \code{logical} you can use them as a condition. <>= if ( expect_equal(1 + 1, 2) ){ expect_true( 2 > 0) } @ Here, the second test (\code{expect\_true(2 > 0)}) is only executed if the first test results in \code{TRUE}. In any case the result of the first test will be caught in the test output, when this is run with \code{run\_test\_file} \code{run\_test\_dir}, \code{test\_all}, \code{build\_install\_test} or through \code{R CMD check} using \code{test\_package}. If you want to perform the test, but not record the test result you can do the following \marginpar{\code{ignore}} (note the placement of the brackets). <>= if ( ignore(expect_equal)(1+1, 2) ){ expect_true(2>0) } @ Other cases where this may be useful is to perform tests in a loop, e.g. when there is a systematic set of cases to test. It is possible to exit a test file prematurely. For example when there are a number of tests that are not relevant or possible on some OS, you can do the following. \marginpar{\code{exit\_file}} <<>>= if ( Sys.info()[['sysname']] == "Windows"){ exit_file("Cannot test this on Windows") } @ This will cause \code{run\_test\_file} to stop file execution, print the message, and report the information gathered up to where \code{exit} was called. A function like \code{test\_all} will then continue with the next file, so testing is not aborted completely. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Running order and side effects} \label{sect:running} It is a generally a good idea to write test files that are independent from each other. This means that the order of running them is unimportant for the test results and test files can be maintained independently. The function \code{run\_test\_file} and by extension \code{run\_test\_dir}, \code{test\_all}, and \code{test\_package} encourage this by resetting \begin{itemize} \item options, set with \code{options()}; \item environment variables, set with \code{Sys.setenv()} \end{itemize} after a test file is executed. To escape this behavior, use \code{base::Sys.setenv()} respectively \code{base::options()}. Alternatively use <>= run_test_dir("/path/to/my/testdir" , remove_side_effects = FALSE) test_all("/path/to/my/testdir" , remove_side_effects = FALSE) # Only in tests/tinytest.R: test_package("PACKAGENAME", remove_side_effects=FALSE) @ Test files are sorted and run based on the current locale. This means that the order of execution is in general not platform-independent. You can control the sorting behavior interactively or by setting \code{options(tt.collate)}. To be precise, adding <>= options(tt.collate="C") @ to \code{/tests/tinytest.R} before running \code{test\_package} will ensure bytewise sorting on most systems. See also \code{help("run\_test\_dir")}. \subsection{Monitoring side effects} \label{sect:side} The term 'side effect' is the technical expression for the situation where a function or expression changes something outside of its scope. Examples include creating, removing, or changing variables in R's global work space, R options, or environment variables of your operating system. We will call such variables or options \emph{external variables}. To test for side-effects once, use the \code{side\_effects} argument to any of the test runners. For example <>= test_package("pkg", side_effects=TRUE) @ There is control over which side-effects to track. For example to prevent tracking changes in the working directory, do the following. <>= test_package("pkg", side_effects=list(pwd=FALSE)) @ If you add \code{report\_side\_effects()} anywhere in a test file, certain external variables are monitored from that point on, and for that file only. It can be switched off again by calling \code{report\_side\_effects(FALSE)} anywhere in the file. The reporting functionality will compare the external state before and after every expression in the test file is run and report any changes. At the moment, effects that can be monitored include environment variables, locale settings, the present working directory, and file operations in the test directory. Below is an example of a test file where side effects are recorded. The third line creates an explicit side effect by creating a new environment variable called \code{hihi} with the value \code{"lol"}. \begin{verbatim} # contents of test_se.R report_side_effects() expect_equal(1+1, 2) Sys.setenv(hihi="lol") expect_equal(1+1, 3) Sys.setenv(hihi="lulz ftw") \end{verbatim} Running the test file yields an object of class \code{tinytests} as usual, only now changes in environment variables are reported. <<>>= run_test_file("test_se.R", verbose=1) @ Note that as discussed in Section~\S\ref{sect:running}, \pkg{tinytest} will unset the environment variable \code{hihi} automatically after running the file because it was set directly by the author of the test file using \code{Sys.setenv}. The real value of the reporting functionality is that it also reports on external variables that are touched by other functions than those you call explicitly in the file. Reading and comparing versions of external variables takes some time. Especially when it requires a call to the operating service such as a request for values of environment variables. We therefore recommend this to be used only when you suspect a side effect. Or, for example to execute \code{report\_side\_effects()} conditional on \code{at\_home()}. It is not possible to catch all types of side effects, even in principle, using the \pkg{tinytest} reporting functionality. Examples include: packages that keep a global variable or environment within their namespace to store some state, and packages that rely on compiled code where there are global objects within the shared object. Side effects are to be avoided as a general and strong principle, but sometimes there is little or no choice. In Section~\ref{sect:devil} we give some tips on how to properly handle such situations. \section{Testing packages} Using \pkg{tinytest} for your package is pretty easy. \begin{enumerate} \item Testfiles are placed in \code{/inst/tinytest}. The testfiles all have names starting with \code{test} (for example \code{test\_haha.R}). \item In the file \code{/tests/tinytest.R} you place the code \begin{verbatim} if ( requireNamespace("tinytest", quietly=TRUE) ){ tinytest::test_package("PACKAGENAME") } \end{verbatim} \item In your \code{DESCRIPTION} file, add \pkg{tinytest} to \code{Suggests:}. \end{enumerate} You can automatically create a minimal running test infrastructure with the \code{setup\_tinytest} function. \marginpar{\code{setup\_tinytest}} <>= setup_tinytest("/path/to/your/package") @ In a terminal, you can now do \begin{verbatim} R CMD build /path/to/your/package R CMD check PACKAGENAME_X.Y.Z.tar.gz \end{verbatim} and all tests will run. To run all the tests interactively, make sure that all functions of your new package are loaded. After that, run\marginpar{\code{test\_all}} <>= test_all("/path/to/your/package") @ where the default package directory is the current working directory. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Build--install--test interactively} The most realistic way to unit-test your package is to build it, install it and then run all the tests. The function <>= build_install_test("/path/to/your/package") @ does exactly that. It builds and installs the package in a temporary directory, starts a fresh R session, loads the newly installed package and runs all tests. The return value is a \code{tinytests} object. The package is built without manual or vignettes to speed up the whole process. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Testing functions that are not exported: use `:::`} In Section~\ref{sect:spherical} it is argued that unit tests should as a rule of thumb focus on the functions that are visible to the user. However, there are cases where it may be preferred to test an internal function. For example when there are two user-visible functions that call the same underlying, unexported function with different arguments. Or when one of the internal functions implements a numerical algorithm that requires thorough testing. To test functions in your package that are not visible to users that load your package, use the triple-colon operator like so. <>= output = pkg:::some_internal_function(1) expect_equal(output, 2) @ This is perfectly ok, and is also accepted by \code{R CMD check --as-cran}. \pkg{Tinytest} does not make those internal functions directly callable like some other unit testing packages do. Making internal functions callable means that \begin{enumerate} \item \pkg{tinytest} needs to simulate loading a package, except for the namespace restrictions; \item there may be significant differences between the environment in which you test the functions, and the environment which a user sees when loading the package; \item the way the package is loaded during testing may differ from the way it is loaded when a user loads it; \item exported functions are not clearly distinguished from internal functions in the test code. \end{enumerate} accurately simulating how R loads a package is no small matter. It would require a significant epansion of \code{tinytest}'s code base that would have to be kept synchronised with the way R loads packages (possibly with backport options when R would change in that area). So in short: let's keep things simple and let R do what it knows how to do. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Using data stored in files} \label{sect:comparefiles} When your package is tested with \code{test\_package}, \pkg{tinytest} ensures that your working directory is the testing directory (by default \code{tinytest}). This means you can read files that are stored in your folder directly. Suppose that your package directory structure looks like this (default): \begin{itemize} \item[]\code{/inst} \begin{itemize} \item[]\code{/tinytest} \begin{itemize} \item[]\code{/test.R} \item[]\code{/women.csv} \end{itemize} \end{itemize} \end{itemize} Then, to check whether the contents of \code{women.csv} is equal to the built-in \code{women} dataset, the content of \code{test.R} looks as follows. <>= dat <- read.csv("women.csv") expect_equal(dat, women) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Skipping tests on \code{CRAN}} It is not possible to detect whether a test is running on CRAN. This means we are forced to detect that we are running tests in our own environment. In the following example we use the host name to detect if we are running on our own machine and explicitly pass this information to \code{test\_package}. <>= options(prompt=" ", continue=" ") @ <>= # contents of pkgdir/tests/tinytest.R if ( requireNamespace("tinytest", quietly=TRUE) ){ home <- identical( Sys.info()["nodename"], "YOURHOSTNAME" ) tinytest::test_package("PKGNAME", at_home = home) } @ Other ways to detect whether you are running `at home' include \begin{itemize} \item Set a custom environment variable (from your OS) and detect it with \code{Sys.getenv}. <<>>= home <- identical( Sys.getenv("HONEYIMHOME"), "TRUE" ) @ \item Use 4-number package versioning while developing and 3-number versioning for CRAN releases\footnote{As \href{https://stackoverflow.com/questions/36166288/skip-tests-on-cran-but-run-locally}{recommended here} by Dirk Eddelbuettel.}. <>= home <- length(unclass(packageVersion("PKGNAME"))[[1]]) == 4 @ \end{itemize} <>= options(prompt="R> ", continue=" ") @ \subsubsection*{When tests are run interactively} All the interactive test runners have \code{at\_home=TRUE} by default, so while you are developing all tests are run, unless you exclude them explicitly. <<>>= run_test_file("test_hehe.R", verbose=0) run_test_file("test_hehe.R", verbose=0, at_home=FALSE) @ Here is an overview of test runners and their default setting for \code{at\_home}. \begin{center} \begin{tabular}{rll} Function & Default \code{at\_home} & Intended use \\ \hline \code{run\_test\_file} & \code{TRUE} & Interactive by developer\\ \code{run\_test\_dir} & \code{TRUE} & Interactive by developer\\ \code{test\_all} & \code{TRUE} & Interactive by developer\\ \code{build\_install\_test} & \code{TRUE} & Interactive by developer\\ \code{test\_package} & \code{FALSE} & \code{R CMD check}, or after installation by user. \end{tabular} \end{center} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Testing your package after installation} Supposing your package is called \pkg{hehe} and the \pkg{tinytest} infrastructure is used. If the package is installed, the following command runs \pkg{hehe}'s tests. <>= tinytest::test_package("hehe") @ This can come in handy when a user of \pkg{hehe} reports a bug and you want to make sure all tested functionality works on the user's system. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Using extension packages} It is possible for other packages to add custom assertions (\code{expect}-functions). To use such a package: \begin{enumerate} \item Add the extension package to the \code{Suggests:} field in the \code{DESCRIPTION} file. \item Add \code{using(pkg)} to \emph{each} test file that use the extensions (see \code{?using}). \marginpar{\code{using}} \end{enumerate} When multiple extension packages are loaded, and when there are name collisions, the packages loaded later takes precedence over the ones loaded earlier (as usual in R). This includes assertions exported by \pkg{tinytest}. \textbf{Note.} Other than in regular R, it is not possible to disambiguate functions using namespace resolution as in \code{pkg::expect\_something}, because in that case the test result will not be caught by \pkg{tinytest}. The API for building extension packages is described in \code{?register\_tinytest\_extension}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Mocking databases} The \pkg{dittodb} package\cite{keane2020dittodb} is capable of mocking pre-recorded database requests. To use extensions like \code{dittodb}, put the package in \code{Suggests:} in the \code{DESCRIPTION} and load it in the test file. The package captures responses from SQL connections and saves them to R files. Therefore, capture the requested response prior to testing by wrapping your request in \code{start\_db\_capturing()} and \code{stop\_db\_capturing()}. Optionally, specify the file path you want your mocks to be saved. For examples using \code{dittodb}, see \href{https://dittodb.jonkeane.com/}{here}. In the testing scripts, load the package and wrap your tests in \code{with\_mock\_path(path, with\_mock\_db())} like <>= require(dittodb) with_mock_path( system.file("", package = "myPackage"), with_mock_db({ # }) ) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Testing with environmental variables} \label{sect:envvar} In \pkg{tinytest} you can run tests with custom environment variable settings easily. Just add the \code{set\_env} argument to any of \code{run\_test\_file()}, \code{run\_test\_dir()} or \code{test\_package()}. Here is an example. <>= test_package("tinytest", set_env = list(WA_BABALOOBA="BA_LA_BAMBOO")) @ With this option, the environment variable will be set during testing and unset afterwards. Setting and unsetting environment variables like this will not be recorded as a side effect. If there is code in the test file that changes this variable, then it is recorded. Do note that R uses some environment variables that are read during startup, such as \code{\_R\_OPTIONS\_STRINGS\_AS\_FACTORS\_}. Setting these at runtime has no effect. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Running tests in parallel} In \pkg{tinytest}, a file should be considered a closed unit: no information created in one test file should be used in another. Under this condition, tests can automatically run in parallel by running different files in different R sessions. Running code in parallel takes some careful consideration around setting up a cluster, running the tests, and closing the cluster of preparing it for the next run. Depending on the test runner used, there are different levels of control and responsibility for the user to prepare the program for parallelization. Below we describe them from less easier to more control. \subsubsection*{\code{build\_install\_test}} This function creates and installs a package in a temporary location. By setting the \code{ncpu} parameter, the number of cores used at the testing phase can be increased. <>= build_install_test("/path/to/your/package", ncpu=2) @ We already mentioned that the order in which files are run is in principle system-dependent and it is a good practice not to rely on it. Under parallel situations, all bets on file order are off. \subsubsection*{\code{test\_package}.} This function assumes that a package is installed. It can gather any information necessary to parallelize a test run. The simplest way to parallelize is to specify the number of CPUs used. <>= test_package("PACKAGENAME", ncpu=2) @ Here, \code{test\_package} will \begin{enumerate} \item Set up a local cluster using \code{parallel::makeCluster}. \item Load the package on each R instance of the cluster. \item Run test files in parallel over the cluster. \item Collect the results and close the cluster. \end{enumerate} In stead of just passing the number of CPUs it is possible to pass a \code{cluster} object. In that case \code{test\_package} will still load the package on each node. However, note that if the package gets updated and reinstalled, it should also be reloaded. It is in general hard to completely unload a package in R (see \code{?detach} and \code{?unloadNamespace} for some details on artifacts that will not be removed). So our advice is to restart a cluster for each test run. \subsubsection*{\code{run\_test\_dir}, \code{test\_all}} These function assumes that all functionality needed to run the tests is loaded. They accept an object of type \code{cluster}. The user is responsible for setting up the nodes. <>= cl <- parallel::makeCluster(4, outfile="") parallel::clusterCall(cl, source, "R/myfunctions.R") run_test_dir("inst/tinytest", cluster=cl) @ where the argument \code{outfile=""} ensures that messages from each node are forwarded to the master node. It is possible to keep the cluster `alive', so modifications can be made to \code{"R/myfunctions.R"} and then run for example the following. <>= parallel::clusterCall(cl, source, "R/myfunctions.R") test_all(cluster=cl) stopCluster(cl) @ For heavy test routines it is thus possible to keep a test cluster up to offload computations. For more complex situations, including packages that use \code{S4} classes, or compiled code, (re)loading takes more effort than sourcing a few R files. In this cases it is often easier to restart a clean cluster for each test round. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{A few tips on packages and unit testing} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Make your package spherical} \label{sect:spherical} Larger packages typically consist of functions that are visible to the users (exported functions) and a number of functions that are only used by the exported functions. For example: <<>>= # exported, user-visible function inch2cm <- function(x){ x*conversion_factor("inch") } # not exported function, package-internal conversion_factor <- function(unit){ confac <- c(inch=2.54, pound=1/2.2056) confac[unit] } @ We can think of the exported functions as the \emph{surface} of the package and all the other functions as the \emph{volume}. The surface is what a user sees, the volume is what the developer sees. The surface is how a user interacts with a package. If the surface is small (few functions exported), users are limited in the ways they can interact with your package and that means there is less to test. So as a rule of thumb, it is a good idea to keep the surface small. Since a sphere has the smallest surface-to-volume ratio possible, I refer to this rule as \emph{keep your package spherical}. By the way, the technical term for the surface of a package is API (application program interface). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Test the surface, not the volume} Unexpected behavior (a bug) is often discovered when someone who is not the developer starts using code. Bugfixing implies altering code and it may even require you to refactor large chunks of code that is internal to a package. If you defined extensive tests on non-exported functions, this means you need to rewrite the tests as well. As a rule of thumb, it is a good idea to test only the behaviour at the surface, so as a developer you have more freedom to change the internals. This includes rewriting and renaming internal functions completely. By the way, it is bad practice to change the surface, since that means you are going to break other people's code. Nobody likes to program against an API that changes frequently, and everybody hates to program against an API that changes unexpectedly. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{How many tests do I need?} When you call a function, you can think of its arguments flowing through a certain path from input to output. As an example, let's take a look again at a new, slightly safer unit conversion function. <<>>= pound2kg <- function(x){ stopifnot( is.numeric(x) ) if ( any(x < 0) ){ warning("Found negative input, converting to positive") x <- abs(x) } x/2.2046 } @ If we call \code{lbs2kg} with argument \code{2}, we can write: \begin{verbatim} 2 -> /2.2046 -> output \end{verbatim} If we call \code{lbs2kg} with argument \code{-3} we can write \begin{verbatim} -3 -> abs() -> /2.2046 -> output \end{verbatim} Finally, if we call \code{pound2kg} with \code{"foo"} we can write \begin{verbatim} "foo" -> stop() -> Exception \end{verbatim} So we have three possible paths. In fact, we see that every nonnegative number will follow the first path, every negative number will follow the second path and anything nonnumeric follows the third path. So the following test suite fully tests the behaviour of our function. <>= expect_equal(pound2kg(1), 1/2.2046 ) # test for expected warning, store output expect_warning( out <- pound2kg(-1) ) # test the output expect_equal( out, 1/2.2046) expect_error(pound2kg("foo")) @ The number of paths of a function is called its \emph{cyclomatic complexity}. For larger functions, with multiple arguments, the number of paths typically grows extremely fast, and it quickly becomes impossible to define a test for each and every one of them. If you want to get an impression of how many tests one of your functions in needs in principle, you can have a look at the \pkg{cyclocomp} package of G\'abor Cs\'ardi\cite{csardi2016cyclocomp}. Since full path coverage is out of range in most cases, developers often strive for something simpler, namely \emph{full code coverage}. This simply means that each line of code is run in at least one test. Full code coverage is no guarantee for bugfree code. Besides code coverage it is therefore a good idea to think about the various ways a user might use your code and include tests for that. To measure code coverage, I recommend using the \pkg{covr} package by Jim Hester\cite{hester2018covr}. Since \pkg{covr} is independent of the tools or packages used for testing, it also works fine with \pkg{tinytest}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{It's not a bug, it's a test!} If users of your code are friendly enough to submit a bug report when they find one, it is a good idea to start by writing a small test that reproduces the error and add that to your test suite. That way, whenever you work on your code, you can be sure to be alarmed when a bug reappears. Tests that represent earlier bugs are sometimes called \emph{regression tests}. If a bug reappears during development, software engineers sometimes refer to this as a \emph{regression}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Side effects are the Devil} \label{sect:devil} Since side-effects manipulate variables outside of the scope of a function, or even outside of R, they can cause bugs that are hard to reproduce. R offers a mechanism to ensure that a function leaves the outside world as it was, once your code stops running. Suppose you need to change working directory within a function, \code{source} a file and return to the working directory. A naive way to do this is like so. \marginpar{\code{on.exit}} <<>>= bad_function <- function(file){ oldwd <- getwd() setwd(dirname(file)) source(basename(file)) setwd(oldwd) } @ This all works fine, untill \code{file} contains faulty code and throws an error. As result, the execution of \code{bad\_function} will stop and leave the user in a changed working directory. With \code{on.exit} you can define code that will be carried out before the function exits, either normally or with an error. <<>>= good_function <- function(file){ oldwd <- getwd() on.exit(setwd(oldwd)) setwd(dirname(file)) source(basename(file)) } @ \newpage \begin{thebibliography}{5} \bibitem{wickham2016testthat} \href{https://cran.r-project.org/package=testthat}{Unit Testing for R} Hadley Wickham (2016). testthat: Get Started with Testing. The R Journal, vol. 3, no. 1, pp. 5--10, 2011 \bibitem{burger2016RUnit} Matthias Burger, Klaus Juenemann and Thomas Koenig (2018). \href{https://CRAN.R-project.org/package=RUnit}{RUnit: R Unit Test Framework} R package version 0.4.32. \bibitem{csardi2016cyclocomp} \href{https://cran.r-project.org/package=cyclocomp}{cyclocomp: cyclomatic complexity of R code} G\'abor Cs\'ardi (2016). R package version 1.1.0 \bibitem{hester2018covr} \href{https://CRAN.R-project.org/package=covr}{covr: Test Coverage for Packages} Jim Hester (2018). R package version 3.2.1 \bibitem{keane2020dittodb} \href{https://CRAN.R-project.org/package=dittodb}{dittodb: A Test Environment for Database Requests} Jonathan Keane and Mauricio Vargas (2020). R package version 0.1.1 \end{thebibliography} \end{document} tinytest/vignettes/test_se.R0000644000176200001440000000016413524613204015737 0ustar liggesusersreport_side_effects() expect_equal(1+1, 2) Sys.setenv(hihi="lol") expect_equal(1+1, 3) Sys.setenv(hihi="lulz ftw") tinytest/vignettes/test_hehe.R0000644000176200001440000000006113500004617016231 0ustar liggesusers if ( at_home() ){ expect_equal(1 + 1, 2) } tinytest/vignettes/tinytest_examples.Rnw0000644000176200001440000004042213722246232020423 0ustar liggesusers%\VignetteIndexEntry{tinytest by example} \documentclass[11pt]{article} \usepackage{enumitem} \usepackage{xcolor} % for color definitions \usepackage{sectsty} % to modify heading colors \usepackage{fancyhdr} \setlist{nosep} % simpler, but issue with your margin notes \usepackage[left=1cm,right=3cm, bottom=2cm, top=1cm]{geometry} \usepackage{hyperref} \definecolor{bluetext}{RGB}{0,101,165} \definecolor{graytext}{RGB}{80,80,80} \hypersetup{ pdfborder={0 0 0} , colorlinks=true , urlcolor=blue , linkcolor=bluetext , linktoc=all , citecolor=blue } \sectionfont{\color{bluetext}} \subsectionfont{\color{bluetext}} \subsubsectionfont{\color{bluetext}} % no serif=better reading from screen. \renewcommand{\familydefault}{\sfdefault} % header and footers \pagestyle{fancy} \fancyhf{} % empty header and footer \renewcommand{\headrulewidth}{0pt} % remove line on top \rfoot{\color{bluetext} tinytest \Sexpr{packageVersion("tinytest")}} \lfoot{\color{black}\thepage} % side-effect of \color{}: lowers the printed text a little(?) \usepackage{fancyvrb} % custom commands make life easier. \newcommand{\code}[1]{\texttt{#1}} \newcommand{\pkg}[1]{\textbf{#1}} \let\oldmarginpar\marginpar \renewcommand{\marginpar}[1]{\oldmarginpar{\color{bluetext}\raggedleft\scriptsize #1}} % skip line at start of new paragraph \setlength{\parindent}{0pt} \setlength{\parskip}{1ex} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \title{Tinytest by example} \author{Mark van der Loo} \date{\today{} | Package version \Sexpr{packageVersion("tinytest")}} \begin{document} \DefineVerbatimEnvironment{Sinput}{Verbatim}{fontshape=n,formatcom=\color{graytext}} \DefineVerbatimEnvironment{Soutput}{Verbatim}{fontshape=sl,formatcom=\color{graytext}} \newlength{\fancyvrbtopsep} \newlength{\fancyvrbpartopsep} \makeatletter \FV@AddToHook{\FV@ListParameterHook}{\topsep=\fancyvrbtopsep\partopsep=\fancyvrbpartopsep} \makeatother \setlength{\fancyvrbtopsep}{0pt} \setlength{\fancyvrbpartopsep}{0pt} \maketitle{} \thispagestyle{empty} \tableofcontents{} <>= options(prompt=" ", continue = " ", width=75, tt.pr.color=FALSE) library(tinytest) @ \subsection*{Introduction} This document provides a number of real-life examples on how \pkg{tinytest} is used by other packages. The examples aim to illustrate the purpose of testing functions and serve as a complement to the technical documentation and the `using \code{tinytest}' vignette. There is a section for each function. Each section starts with a short example that demonstrates the core purpose of the function. Next, one or more examples from packages that are published on CRAN are shown and explained. Sometimes a few lines of code were modified or deleted for brevity. This is indicated with comment between square brackets, e.g. <>= ## [this is an extra comment, only for this vignette] @ This document is probably not interesting to read front-to-back. It is more aimed to browse once in a while to get an idea on how \pkg{tinytest} can be used in practice. Package authors are invited to contribute new use cases so new users can learn from them. Please contact the author of this package either by email or via the \href{http://github.com/markvanderloo/tinytest}{github repository}. \newpage{} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{\code{expect\_equal}} R objects are described by the data they contain and the attributes attached to them. For example, in the vector \code{c(x=1,y=2)}, the data consist of the numbers \code{1} and \code{2} (in that order) and there is a single attribute called \code{names}, consisting of the two strings \code{"x"} and \code{"y"} (in that order). The \code{expect\_equal} function tests whether both the data and the attributes of two objects are the same. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_equal(1,1) expect_equal(1, c(x=1)) @ Numbers do not have to be exactly the same to be equal (by default). <<>>= 0.9-0.7-0.2 expect_equal(0.9-0.7-0.2,0) expect_equal(0.9-0.7-0.2,0, tolerance=0) @ <>= options(prompt=" ", continue = " ", width=75) @ Here is an example from the \pkg{stringdist} package. This package implements various methods to determine how different two strings are. In this test, we check one aspect of the `optimal string alignment' algorithm. In particular, we test if it correctly counts the switch of two adjacent characters as a single operation. <>= expect_equal(stringdist("ab", "ba", method="osa"), 1) @ The \pkg{benchr} package is a package to time R code, and it uses \code{expect\_equal} to extensively check the outputs. Here are a few examples. <>= b <- benchr::benchmark(1 + 1, 2 + 2) m <- mean(b) expect_equal(class(m), c("summaryBenchmark", "data.frame")) expect_equal(dim(m), c(2L, 7L)) expect_equal(names(m), c("expr", "n.eval", "mean", "trimmed", "lw.ci", "up.ci", "relative")) expect_equal(class(m$expr), "factor") expect_equal(levels(m$expr), c("1 + 1", "2 + 2")) expect_true(all(sapply(m[-1], is.numeric))) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_equivalent}} This function ignores the attributes when comparing two R objects. Two objects are equivalent when their data are the same. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_equivalent(1,1) expect_equivalent(1, c(x=1)) @ <>= options(prompt=" ", continue = " ", width=75) @ The \pkg{validate} package offers functions to define restrictions on data, and then confront the data with them. The function \code{values} extracts the boolean results in the form of a matrix with specific row- and column names. In the example below we are only interested in testing whether the \emph{contents} of the matrix is computed correctly. <>= v <- validator(x > 0) d <- data.frame(x=c(1,-1,NA)) expect_equivalent(values(confront(d,v)), matrix(c(TRUE,FALSE,NA)) ) @ The \pkg{anytime} package translates text data into data/time format (\code{Date} or \code{POSIXct}). Here, a test is performed to equivalence, to ignore the timezone label that is attached by anytime but not by \code{as.Date}. <>= refD <- as.Date("2016-01-01")+0:2 expect_equivalent(refD, anydate(20160101L + 0:2)) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_identical}} This is the most strict test for equality. The best way to think about this is that two objects must be byte-by-byte indistinguishable in order to be identical. The differences can be subtle, as shown below. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= La <- list(x=1); Lb <- list(x=1) expect_identical(La, Lb) a <- new.env() a$x <- 1 b <- new.env() b$x <- 1 expect_identical(a,b) @ Here, \code{La} and \code{Lb} are indistinguishable from R's point of view. They only differ in their location in memory. The environments \code{a} and \code{b} \emph{are} distinguishable since they contain an explicit identifier which make them unique. <<>>= print(a) print(b) @ Another difference with \code{expect\_equal} and \code{expect\_equivalent} is that \code{expect\_identical} does not allow any tolerance for numerical differences. <>= options(prompt=" ", continue = " ", width=75) @ The \code{stringdistmatrix} function of \pkg{stringdist} computes a matrix of string dissimilarity measures between all elements of a character vector. Below, it is tested whether the argument \code{useNames="none"} and the legacy (deprecated) argument \code{useName=FALSE}. <>= a <- c(k1 = "aap",k2="noot") expect_identical(stringdistmatrix(a,useNames="none") , stringdistmatrix(a,useNames=FALSE)) @ The \pkg{wand} package can retrieve MIME types for files and directories. This means there are many cases to test. In this particular package this is done by creating two lists, one with input and one with expected results. The tests are then performed as follows: <>= list( ## [long list of results removed for brevity] ) -> results fils <- list.files(system.file("extdat", package="wand"), full.names=TRUE) tst <- lapply(fils, get_content_type) names(tst) <- basename(fils) for(n in names(tst)) expect_identical(results[[n]], tst[[n]]) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_null}} The result of an operation should be \code{NULL}. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_null(iris$hihi) expect_null(iris$Species) @ <>= options(prompt=" ", continue = " ", width=75) @ This function is new in version 0.9.7 and not used in any depending packages yet. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_true}, \code{expect\_false}} The result of an operation should be precisely \code{TRUE} or \code{FALSE}. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_true(1 == 1) expect_false(1 == 2) @ <>= options(prompt=" ", continue = " ", width=75) @ The \pkg{anytime} package converts many types of strings to date/time objects (\code{POSIXct} or \code{Date}). Here is a part of it's \pkg{tinytest} test suite. <>== ## Datetime: factor and ordered (#44) refD <- as.Date("2016-09-01") expect_true(refD == anydate(as.factor("2016-09-01"))) expect_true(refD == anydate(as.ordered("2016-09-01"))) expect_true(refD == utcdate(as.factor("2016-09-01"))) expect_true(refD == utcdate(as.ordered("2016-09-01"))) @ Note that \code{==} used here has subtly different behavior from \code{all.equal} used by \code{expect\_equal}. In the above case, \code{==} does not compare time zone data, which is not added by \code{as.Date} but is added by \code{anytime}. This means that for example <>= expect_equal(anydate(as.factor("2016-09-01")), refD) @ would fail. The \pkg{ulid} package uses \code{expect\_true} to verify the type of a result. <>= x <- ULIDgenerate(20) expect_true(is.character(x)) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_message}} Expect that a message is emitted. Optionally you can specify a regular expression that the message must match. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_message(message("hihi")) expect_message(message("hihi"), pattern = "hi") expect_message(message("hihi"), pattern= "ha") expect_message(print("hihi")) @ <>= options(prompt=" ", continue = " ", width=75) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_warning}} Expect that a warning is emitted. Optionally you can specify a regular expression that the warning must match. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_warning(warning("hihi")) expect_warning(warning("hihi"), pattern = "hi") expect_warning(warning("hihi"), pattern= "ha") expect_warning(1+1) @ <>= options(prompt=" ", continue = " ", width=75) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_error}} Expect that an error is emitted. Optionally you can specify a regular expression that the error must match. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_error(stop("hihi")) expect_error(stop("hihi"), pattern = "hi") expect_error(stop("hihi"), pattern= "ha") expect_error(print("hoho")) @ <>= options(prompt=" ", continue = " ", width=75) @ The \pkg{ChemoSpec2D} package implements exploratory methods for 2D-spectrometry data. Scaled data has negative values, so one cannot take the logarithm. The function \code{centscaleSpectra2D} must eject an error in such cases and this is tested as follows. <>= # Check that log and centering cannot be combined expect_error( centscaleSpectra2D(tiny, center = TRUE, scale = "log"), "Cannot take log of centered data") @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_silent}} Sometimes a test is only run to check that the code does not crash. This function tests that no warnings or errors are emitted when evaluating it's argument. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_silent(print(10)) expect_silent(stop("haha")) @ <>= options(prompt=" ", continue = " ", width=75) @ The \pkg{validate} package defines an object called a \code{validation}, which is the result of confronting a dataset with one or more data quality restrictions in the form of rules. A \pkg{validation} object can be plotted, but this would crash with an error in a certain edge case. Here is a test that was added in response to a reported issue. <>= data <- data.frame(A = 1) rule <- validator(A > 0) cf <- confront(data, rule) expect_silent(plot(rule)) expect_silent(plot(cf)) @ The \pkg{lumberjack} package creats log files that track changes in data. In one test it is first tested whether a file has been generated, next it is tested whether it can be read properly. This is also an example of programming over test results, since the file is deleted if it exists. <>= run("runs/multiple_loggers.R") simple_ok <- expect_true(file.exists("runs/simple_log.csv")) expect_silent(read.csv("runs/simple_log.csv")) if (simple_ok) unlink("runs/simple_log.csv") @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{ignore}} Ignore allows you to not record the result of a test. It is not used very often. Its use is probably almost exclusive to \pkg{tinytest} where it is used while testing the expectation functions. The following result is not recorded (note placement of brackets!) <>= ignore(expect_equal)(1+1, 2) @ The \pkg{digest} package computes hashes of R objects. It uses \code{ignore} in one of it's files. <>= mantissa <- gsub(" [0-9]*$", "", x.hex) ignore(expect_true)(all( sapply( head(seq_along(mantissa), -1), function(i){ all( grepl( paste0("^", mantissa[i], ".*"), tail(mantissa, -i) ) ) } ) )) @ \newpage \begin{thebibliography}{5} \bibitem{anytime} \href{https://cran.r-project.org/package=anytime}{anytime} D. Eddelbuettel (2019) \emph{Anything to `POSIXct' or `Date' Converter}. R package version 0.3.3.5 \bibitem{benchr} \href{https://CRAN.R-project.org/package=benchr}{benchr} Arttem Klevtsov (2019) \emph{High Precise Measurement of R Expressions Execution Time}. R package version 0.2.3-1. \bibitem{ChemoSpec2D} \href{https://cran.r-project.org/package=cyclocomp}{ChemoSpec2D} B.A. Hanson (2019) \emph{Exploratory Chemometrics for 2D Spectroscopy} R package version 0.3.166 \bibitem{digest} \href{https://cran.r-project.org/package=digest}{digest} D. Eddelbuettel (2019) \emph{Create Compact Hash Digests of R Objects} R package version 0.6.20 \bibitem{stringdist} \href{https://cran.r-project.org/package=stringr}{stringdist} M. van der Loo (2014). \emph{The stringdist package for approximate string matching}. The R Journal 6(1) 111-122 \bibitem{ulid} \href{https://cran.r-project.org/package=ulid}{ulid} B. Rudis (2019) \emph{Generate Universally Unique Lexicographically Sortable Identifiers}. R package version 0.3.0 \bibitem{validate} \href{https://cran.r-project.org/package=validate}{validate} M. van der Loo, E. de Jonge and P. Hsieh (2019) \emph{Data Validation Infrastructure for R}. R package version 0.2.7 \bibitem{wand} \href{https://cran.r-project.org/package=wand}{wand} B. Rudis (2019) \emph{Retrieve `Magic' Attributes from Files and Directories} R package version 0.5.0 \end{thebibliography} \end{document} tinytest/NEWS0000644000176200001440000002361514071036176012650 0ustar liggesusersversion 1.3.1 - expect_error, _warning, _message, _stdout now accept ... arguments that are passed as extra arguments to 'grepl', when a pattern is provided (Thanks to Bart-Jan van Rossum for the GH issue). - Package now also resets locale settings after each file run, and 'report_side_effects' also tracks changes in locale settings. - Run test directories via RStudio addin. (Thanks to Matthijs Berends for PR #83) - tinytest used to implicitly depend on R >= 3.2.0 because of using 'trimws' and 'dir.exists'. (Thanks to Sebastian Meyer for figuring this out and thanks to Matthijs Berends for PR #84 solving this) - 'tinytest' objects now store the stack trace as a list of calls. When needed, the relevant part of the trace is printed, but only when printing the long form. (thanks to Jonas Kristoffer Lindeloef for suggesting) - Improved printing while running tests - Graphics produced in tests are now written to nullfile() (Thanks to Henrik Bengtsson for the suggestion). (nullfile() is defined in-package when built on R < 3.6.0). - Fixed time measurement when directory (or pkg) is tested and tests take more than an minute. - Fix: set_env variables would not be unset in all cases (Thanks to Henrik Bengtsson for the PR) - Fix in 'expect_equal': reporting in case of differing attributes (Thanks to Ott Toomet for reporting #80). - Fix in build_install_test: better reading of DESCRIPTION file (Thanks to Christof Stepper for PR #87) version 1.2.4 - 'test_package' gains 'lib.loc' argument. - New function 'expect_inherits' to check the class of an object (thanks to Sebastian Meyer for suggesting). - Printing of file exit message is now shorter and on same line as test report (thanks for Dirk Eddelbuettel for suggesting). - Duration per file is now reported, total duration is stored (thanks to Dirk Eddelbuettel for suggesting) - Small improvements in difference reporting. - Fix: avoid truncated printing in case of long diff reporting (thanks to Sebastian Meyer for the PR) - Fix: default pattern for test files was "^test_", is now "^test" as documented. (Thanks to Sebastian Meyer for the PR). - Fix: it is now easier to put tests that you do not want to install under /tests/somedir. - Internal: updated tinytest.format method to conform to new R CMD check demand. (Thanks to Brian Ripley for the warning). version 1.2.3 - Added example to using tinytest vignette on mocking databases (Thanks to Thomas Fuchs for working this out). - 'expect_stdout' now handles multi-line output. (Thanks to John Blischak for reporting). - A message is issued when a test file uses 'tinytest::expect': such tests are not registered (thanks to James Thompson for issuing a ticket on this). version 1.2.2 - Improved diff reporting in expect_true, and expect_false. - 'build_install_test' gains arguments 'lc_collate' and 'pattern', which are now passed to 'run_test_dir'. (Thanks to Patrick Breheny for reporting). version 1.2.1 - expect_message() now also detects occurrence of any message, and not just the first. Detecting only the first was introduced in 1.2.0 and reported at GH issue #51 (Thanks to Jozef Hajnala and Ralf Herold). - Better 'diff' information for expect_warning() and expect_message(). version 1.2.0 - 'run_test_file' gains argument 'set_env'. Set environment variables during a test run. - New functions 'expect_equal_to_reference' and 'expect_equivalent_to_reference' comparing/storing an object with/to an RDS file (Thanks to Jon Clayden for suggesting). - New function 'expect_stdout' compares output of e.g. 'print' and 'cat' statements to a user-defined pattern. (Thanks to Michel Lang for suggesting). - New function get_call_wd() returns working directory, active when tests were invoked. (Thanks to Maja Zaloznik for the idea) - Functions 'expect_error', 'expect_warning', and 'expect_message' gain 'class' argument, that can be used to check whether a signaling condition inherits from a certain class. (thanks to Michel Lang for suggesting) - Argument 'tol' now renamed 'tolerance'. Also removed internal reliance on partial argument matching (Thanks to Michel Lang). - Updated documentation on how to extend tinytest with new recommendation. - Using tinytest vignette gains section on testing internal functions. - Breaking: function 'expect_message' no longer intercepts messages sent to stdout (e.g. via 'print' or 'cat'), but only messages send as a 'message' condition (Thanks to Michel Lang for pointing this out). - Fix: 'test_package' would return NULL when called interactively and the package contained a failing test. version 1.1.0 - Tests are now run with 'pdf(file=tempfile())' as graphics device. This avoids writing 'Rplots.pdf' under the library directory when R CMD check is run. - Side-effects tracking now includes file operations in the test directory or subdirectories thereof. - build_install_test now accepts arguments 'side_effects', 'remove_side_effects' - expect_* functions gain argument 'info': a user-defined message that is printed in long output format. - 'run_test_dir' now selects files with "^test.*\\.[rR]$", not "^test.*\\.[rR]" (thanks to Dirk Eddelbuettel). - Fixed 'diff' message for scalar comparisons in expect_equivalent, expect_equal, expect_identical: 'target' and 'current' were switched in message (thanks to GH user Billy34). - 'setup_tinytest' now keeps formatting for DESCRIPTION files (thanks to Bart-Jan van Rossum). - Fixed crash of 'build_install_test' when pkg was developed under directory with spaces (thanks to Bart-Jan van Rossum). - Fixed issue where R CMD check would hang when packages use paralellization (GH #36) or certain Rcpp features (GH #37). Thanks to George G Vega Yon and Dirk Eddelbuettel. version 1.0.0 - New argument 'side_effects' for test runners: monitor side effects while running tests. - New function 'report_side_effects': toggle monitoring side-effects within test files. - Run test files in parallel, for example: test_package("pkg", ncpu=2). (Thanks to Dirk Eddelbuettel for the suggestion). - New function 'exit_file': stop running a test file with an optional message. - Other packages can now extend the package, see '?register_tinytest_extension' for the API (Thanks to Michel Lang for the suggestion) - New function 'using' loads tinytest extension package. - New function 'expect_null' - Improved reporting for 'expect_true', 'expect_false' - Improved reporting for expect_identical, expect_true, expect_equivalent in the case of scalar values. - Added second vignette with real-life examples of tinytest tests. - build_install_test gains argument 'color' and 'verbose' - test_package returns result visibly in interactive sessions. - Fixed path issue for build_install_test on Windows (thanks to Jan Wijffels) version 0.9.6 - Fixed error caught on CRAN version 0.9.5 - New function 'expect_message'. - New functions 'all_pass', 'all_fail', 'any_pass', 'any_fail' for investigating 'tinytests' objects. - When `interactive()`, 'test_package' will not throw an error upon a failing test. Still throws an error when not `interactive()`. This makes it more usefull for interactive testing of installed packages. - 'test_package()' now returns a 'tinytests' object. - Better 'verbosity' while running test files (thanks to Dirk Eddelbuettel for discussions and feedback) - Added 'test_silent'. Check for lack of errors or warnings (Thanks to Bryan Hanson for the suggestion, #14) - Colored output is turned off automatically when the pkg is loaded in a "dumb" terminal. (Thanks to Dirk Eddelbeutel for the suggestion, #12). - Improved documentation, including on how to skip tests on CRAN. - Improved vignette layout (thanks to Dirk Eddelbuettel's suggestions and initial PR #15 for geometry settings) - Improved behavior of 'setup_tinytest()' when a 'Suggests' field is already present in DESCRIPTION - Fix: toleracence was not passed to 'all.equal' by 'expect_equal' and 'expect_equivalent' (Thanks to An Chu #17) - Fix 'expect_warning' and 'expect_error' would crash when run on the CLI. (Thanks to Bryan Hanson #13) - Fix: method dispatch in packages defining S4 methods now works. - Fix: 'setup' now adds 'tinytest' to 'Suggests:' rather then adding an extra 'Suggests' field in DESCRIPTION. - Corrections in README and vignette, thanks to GH user 'salim-b' PR #18, #19 - Internal: simplified code for 'expect_warning' and 'expect_error' (thanks to Lionel Henry for suggestions). version 0.9.4 - New function 'setup_tinytest' that adds 'tinytest' infrastructure to a package source directory. - Global settings set by 'options' and 'Sys.setenv' in a test file are automatically removed after running a test file. This improves independence between test files. This default can be overwritten, see Chapter 3 of the 'Using tinytest' vignette. - Test file sorting order can be controlled with 'lc_collate' option in test runners or by setting options(tt.collate) globally. - More control over testing packages in 'R CMD check' with extra arguments to 'test_packages'. - Improved call reporting in case of multiline test expressions (for example when many test expressions depend on an 'if' condition). - Fix in 'expect_warning' and 'expect_error'. In some circumstances evaluation argument 'current' evaluated in the wrong scope, resulting in unfound variables. version 0.9.3 - New function 'expect_identical' - All functions of the form 'expect_lol' can now also be called as 'checkLol' (similar to, but not the same as 'RUnit' syntax) - Expect_* functions now have first argument 'current' and second argument 'target' - Added 'summary' method for objects of class 'tinytests' - Default test directory is now 'inst/tinytest' - Fix: 'as.data.frame' crashed on tinytests object with all tests passing. version 0.9.2 - Processed review by CRAN team member. - Output coloring is now optional. - Fixed issue so package works correctly with r-oldrel. version 0.9.1 - Fixed a few CRAN warnings. version 0.9.0 - Initial release. tinytest/R/0000755000176200001440000000000014065364562012351 5ustar liggesuserstinytest/R/methods.R0000644000176200001440000001361013766407507014144 0ustar liggesusers #' @rdname tinytests #' @param object a \code{tinytests} object #' @return For \code{summary} a \code{\link{table}} object #' @export summary.tinytests <- function(object, ...){ if (length(object)==0){ return( as.table(array(0, dim=c(1,3), dimnames=list(File="Total", c("Results", "fails","passes")))) ) } result <- factor(sapply(object, c) , levels=c(FALSE, TRUE, NA) , labels=c("fails","passes","sidefx") , exclude=character(0)) file <- sapply(object, function(x) attr(x,"file")) if (length(object) > 0) file <- basename(file) tab <- table(File = file, result) tab <- cbind(tab, Results = rowSums(tab)) tab <- rbind(tab, Total = colSums(tab)) tab <- as.table(tab[,c(4,1:3),drop=FALSE]) # remove side-effect column if it has only zeros if ( sum(tab[,4]) == 0 ) tab <- tab[,-4] n <- dimnames(tab) names(n) <- c("File","") tab <- as.table(tab) dimnames(tab) <- n tab } #' @rdname tinytests #' @return For \code{all_pass}, \code{any_pass}, \code{all_fail}, \code{any_fail}: #' a single \code{logical} #' @export all_pass <- function(x){ stopifnot(inherits(x,'tinytests')) all( sapply(x, function(d) isTRUE(d) || is.na(d)) ) } #' @rdname tinytests #' @export any_pass <- function(x){ stopifnot(inherits(x,'tinytests')) any( sapply(x, function(d) isTRUE(d) || is.na(d)) ) } #' @rdname tinytests #' @export all_fail <- function(x){ stopifnot(inherits(x,'tinytests')) all( sapply(x, function(d) isFALSE(d) || is.na(d)) ) } #' @rdname tinytests #' @export any_fail <- function(x){ stopifnot(inherits(x,'tinytests')) any( sapply(x, function(d) isFALSE(d) || is.na(d)) ) } #' Tinytests object #' #' An object of class \code{tinytests} (note: plural) results #' from running multiple tests from script. E.g. by running #' \code{\link{run_test_file}}. #' #' #' @aliases tinytests #' #' @param i an index #' @param x a \code{tinytests} object #' #' @return For \code{`[.tinytests`} a \code{tinytests} object. #' #' @export #' @rdname tinytests `[.tinytests` <- function(x,i){ structure(unclass(x)[i], class="tinytests", duration=NULL) } #' @param passes \code{[logical]} Toggle: print passing tests? #' @param sidefx \code{[logical]} Toggle: print side effects? #' @param limit \code{[numeric]} Max number of results to print #' @param nlong \code{[numeric]} First \code{nlong} results are printed in long format. #' @param ... passed to \code{\link{format.tinytest}} #' #' @section Details: #' #' By default, the first 3 failing test results are printed in long form, #' the next 7 failing test results are printed in short form and all other #' failing tests are not printed. These defaults can be changed by passing options #' to \code{print.tinytest}, or by setting one or more of the following global #' options: #' \itemize{ #' \item{\code{tt.pr.passes} Set to \code{TRUE} to print output of non-failing tests.} #' \item{\code{tt.pr.limit} Max number of results to print (e.g. \code{Inf})} #' \item{\code{tt.pr.nlong} The number of results to print in long format (e.g. \code{Inf}).} #' } #' #' For example, set \code{options(tt.pr.limit=Inf)} to print all test results. #' Furthermore, there is the option #' \itemize{ #' \item{\code{tt.pr.color},} #' } #' which determines whether colored output is printed. #' If R is running in a dumb terminal (detected by comparing #' environment variable \code{"TERM"} to \code{"dumb"}), then #' this option is set to \code{FALSE} when the package is loaded. #' #' @rdname tinytests #' @export print.tinytests <- function(x , passes=getOption("tt.pr.passes", FALSE) , sidefx=getOption("tt.pr.sidefx", TRUE) , limit =getOption("tt.pr.limit", 7) , nlong =getOption("tt.pr.nlong", 3),...){ nrslt <- length(x) ifail <- if (nrslt > 0) sapply(x, isFALSE) else logical(0) iside <- if (nrslt > 0) sapply(x, is.na) else logical(0) ipass <- if (nrslt > 0) sapply(x, isTRUE) else logical(0) duration <- attr(x,"duration") duration_str <- if( is.null(duration) ) "fubar!" else sprintf("(%s)", humanize(duration, color=FALSE)) iprn <- ifail if (passes) iprn <- iprn | ipass if (sidefx) iprn <- iprn | iside x <- x[iprn] if (sum(iprn)==0){ cat(sprintf("All ok, %d results %s\n",nrslt, duration_str)) return(invisible(NULL)) } limit <- min(length(x), limit) nlong <- min(nlong, limit) nshort <- max(limit - nlong,0) x <- x[seq_len(limit)] type <- c( rep("long",nlong) , rep("short",nshort) ) str <- sapply(seq_along(x), function(i) format.tinytest(x[[i]], type=type[i])) cat(paste0(str,"\n"), "\n") if (nrslt > length(str)){ pr1 <- sprintf("Showing %d out of %d results: ", length(x), nrslt) pr2 <- sprintf("%d fails, %d passes", sum(ifail), sum(ipass)) pr3 <- if( any(iside) ) sprintf(", %s side effects", sum(iside)) else "" cat(pr1, pr2, pr3," ", duration_str, "\n",sep="") } } #' @return For \code{as.data.frame.} a data frame. #' @family test-files #' #' @examples #' # create a test file in tempdir #' tests <- " #' addOne <- function(x) x + 2 #' #' expect_true(addOne(0) > 0) #' expect_equal(2, addOne(1)) #' " #' testfile <- tempfile(pattern="test_", fileext=".R") #' write(tests, testfile) #' #' # extract testdir #' testdir <- dirname(testfile) #' # run all files starting with 'test' in testdir #' out <- run_test_dir(testdir) #' # #' # print results #' print(out) #' summary(out) #' dat <- as.data.frame(out) #' out[1] #' #' @rdname tinytests #' @export as.data.frame.tinytests <- function(x, ...){ L <- lapply(x, attributes) data.frame( result = sapply(x, c) , call = sapply(L, function(y) gsub(" +"," ",paste0(capture.output(print(y$call)),collapse=" ")) ) , diff = sapply(L, `[[`, "diff") , short = sapply(L, `[[`, "short") , file = sapply(L, `[[`, "file") , first = sapply(L, `[[`, "fst") , last = sapply(L, `[[`, "lst") , stringsAsFactors=FALSE ) } tinytest/R/tinytest.R0000644000176200001440000011457014065364562014367 0ustar liggesusers#' @importFrom utils install.packages file_test capture.output getFromNamespace #' @importFrom parallel makeCluster parLapply stopCluster {} if (!exists("nullfile", mode = "function", envir = baseenv())) { nullfile <- function() if (.Platform$OS.type == "windows") "nul:" else "/dev/null" } # directory from which run_test_file() was called (i.e. before it temporarily # changes directory call_wd <- (function(){ CALLDIR <- "" function(dir=NULL){ if (is.null(dir)){ return(CALLDIR) } else { # only set when not set previously if (CALLDIR == "" || dir == "") CALLDIR <<- dir } CALLDIR } })() set_call_wd <- function(dir){ call_wd(dir) } #' Get workding dir from where a test was initiated #' #' A test runner, like \code{\link{run_test_file}} changes #' R's working directory to the location of the test file temporarily #' while the tests run. This function can be used from within the #' test file to get R's working directory at the time \code{run_test_file} #' (or one of it's siblings) #' was called. #' #' #' @return \code{[character]} A path. #' @examples #' get_call_wd() #' @export get_call_wd <- function(){ call_wd() } # reference object to store or ignore output # of 'expect' functions output <- function(){ e <- new.env() r <- 0 # number of results n <- 0 # number of tests m <- 0 # number of passes s <- 0 # number of side-effects re <- "^T[0-9]+" e$add <- function(x){ r <<- r + 1 e[[sprintf("T%04d",r)]] <- x if ( isTRUE(x) || isFALSE(x) ){ n <<- n + 1 m <<- m + as.integer(x) } else if (is.na(x)){ s <<- s + 1 } } e$gimme <- function(){ vr <- ls(e,pattern = re) lapply(vr, function(i) e[[i]]) } e$rm_last <- function(){ x <- ls(e,pattern = re) i <- x[length(x)] if ( isTRUE(e[[i]]) ) m <<- m - 1 # note: we never ignore a call to envdiff, # so no need to check for is.na(e[i]). rm(list=i, envir=e) n <<- n-1 r <<- r-1 } e$ntest <- function() n e$npass <- function() m e$nfail <- function() n - m e$nside <- function() s # metadata will be provided by run_test_file e$fst <- 0 e$lst <- 0 e$call <- "" e$file # will be set by exit_file() e$exit <- FALSE e$exitmsg <- "" e$exit_msg <- function() sprintf("[Exited at #%d: %s]", e$fst, e$exitmsg) e } capture <- function(fun, env){ # avoid lazy eval when looping over functions as a variable # e.g. when loading extensions. force(fun) function(...){ out <- fun(...) if ( inherits(out, "tinytest") ){ attr(out,"file") <- env$file attr(out,"fst") <- env$fst attr(out,"lst") <- env$lst attr(out,"call") <- env$call attr(out,"trace")<- sys.calls() # if not NA, the result is from an expect_ function # if NA, it is a side-effect, and we do not attempt to # improve the call's format if (!is.na(out) && env$lst - env$fst >=3) attr(out,"call") <- match.call(fun) env$add(out) attr(out,"env") <- env } out } } # RUnit style checking functions expect_xfoo -> checkXfoo add_RUnit_style <- function(e){ fns <- ls(e, pattern="^expect_") # snake to camelCase fns_RUnit <- sub("_(.)", "\\U\\1", fns, perl=TRUE) fns_RUnit <- sub("expect","check",fns_RUnit) # add checkHaha for each expect_hihi (lol no for each expect_haha) for (i in seq_along(fns)) assign(fns_RUnit[i], e[[fns[i]]], envir=e) } #' Ignore the output of an expectation #' #' Ignored expectations are not reported in the test results. #' Ignoring is only useful for test files, and not for use directly #' at the command-line. See also the package vignette: \code{vignette("using_tinytest")}. #' #' @param fun \code{[function]} An \code{expect_} function #' #' @return An ignored \code{function} #' @family test-functions #' #' #' @section Details: #' #' \code{ignore} is a higher-order function: a function that returns another function. #' In particular, it accepts a function and returns a function that is almost identical #' to the input function. The only difference is that the return value of the function #' returned by \code{ignore} is not caught by \code{\link{run_test_file}} and friends. #' For example, \code{ignore(expect_true)} is a function, and we can use it as #' \code{ignore(expect_true)( 1 == 1)}. The return value of \code{ignore(expect_true)(1==1)} #' is exactly the same as that for \code{expect_true(1==1)}. #' #' #' @examples #' \donttest{ #' ## The result of 'expect_warning' is not stored in the test result when #' ## this is run from a file. #' expect_true( ignore(expect_warning)(warning("foo!")) ) #' ## Note the placement of the brackets in ignore(expect_warning)(...). #' } #' #' #' @export ignore <- function(fun){ function(...){ out <- fun(...) if ( !is.null(attr(out, "env")) ){ attr(out,"env")$rm_last() attr(out,"env") <- NULL } out } } #' Stop testing #' #' Call this function to exit a test file. #' #' @param msg \code{[character]} An optional message to print after exiting. #' #' #' @return The exit message #' #' @examples #' exit_file("I'm too tired to test") #' #' @family test-files #' @export exit_file <- function(msg="") msg # masking function to to call within run_test_file capture_exit <- function(fun, env){ function(...){ env$exit <- TRUE env$exitmsg <- fun(...) } } # we need a special capture function for # Sys.setenv because it's return value does # not inlcude argument names (it is an unnamed # logical vector). We need the names to be able to # unset the env vars later on. capture_envvar <- function(fun, env){ function(...){ for ( x in names(list(...)) ){ # record the first occurrence so we capture the # original value if ( !x %in% ls(envir=env) ) env[[x]] <- Sys.getenv(x) } out <- fun(...) invisible(out) } } unset_envvar <- function(env){ L <- as.list(env) # Sys.setenv crashes with empty list if ( length(L)>0 ) do.call(Sys.setenv, L) } # locale: old locale settings, recorded before running the # file. (character scalar). reset_locale <- function(locale){ if ( identical(locale, Sys.getlocale()) ) return() lcs <- strsplit(locale,";")[[1]] vals <- sub("^.*=","",lcs) names(vals) <- sub("=.*","", lcs) for ( x in names(vals) ){ # we use tryCatch as Sys.getlocale() may retrieve locale # settings that can not be set by Sys.setlocale() tryCatch(Sys.setlocale(category = x, locale = vals[x]) , error = function(e) NULL, warning = function(w) NULL) } invisible(NULL) } capture_options <- function(fun, env){ function(...){ out <- fun(...) for ( x in names(out) ){ # record only the first occurrence so we capture # the original value if (!x %in% ls(envir=env)) env[[x]] <- out[[x]] } invisible(out) } } reset_options <- function(env){ options(as.list(env)) } # envir : an environment where test files are evaluated # output: an environment where test results are captured add_locally_masked_functions <- function(envir, output){ # Local masking of native functions. 'manually' because # it is faster then loading via getFromNamespace() envir$expect_equal <- capture(expect_equal, output) envir$expect_equivalent <- capture(expect_equivalent, output) envir$expect_true <- capture(expect_true, output) envir$expect_false <- capture(expect_false, output) envir$expect_inherits <- capture(expect_inherits, output) envir$expect_null <- capture(expect_null, output) envir$expect_message <- capture(expect_message, output) envir$expect_warning <- capture(expect_warning, output) envir$expect_error <- capture(expect_error, output) envir$expect_stdout <- capture(expect_stdout, output) envir$expect_identical <- capture(expect_identical, output) envir$expect_silent <- capture(expect_silent, output) envir$expect_equal_to_reference <- capture(expect_equal_to_reference, output) envir$expect_equivalent_to_reference <- capture(expect_equivalent_to_reference, output) envir$exit_file <- capture_exit(exit_file, output) envir$ignore <- ignore envir$at_home <- tinytest::at_home ## add 'checkFoo' equivalents of 'expect_foo' (native functions only) if ( getOption("tt.RUnitStyle", TRUE) ) add_RUnit_style(envir) envir$using <- capture_using(using, envir, output) } #' Use an extension package. #' #' Loads and attaches a package to the search path, and picks up the #' \pkg{tinytest} extension functions registered by the package. Package #' authors \emph{must} call this function in \emph{every} test file where an #' extension is used, or otherwise results from the extension package are not #' recorded (without a warning). Calling \code{using} in every file #' where an extension is used also ensures that tests can be run in parallel. #' #' #' @param package the name of the extension package, given as name or character string. #' @param quietly Passed to \code{\link{require}}. #' #' @return A named \code{list}, with the package name and the names of the #' functions registered by \code{package} to extend \pkg{tinytest}. A message #' is emitted when the package registers no extension functions. #' #' @examples #' \dontrun{ #' # In interactive session: see which functions are exported #' # by checkmate.tinytest #' out <- using(checkmate.tinytest) #' print(out) #' } #' #' @family extensions #' @export using <- function(package, quietly=TRUE){ pkg <- as.character(substitute(package)) if ( !require(pkg, quietly=quietly, character.only=TRUE) ){ stopf("Package %s could not be loaded",pkg) } ext <- getOption("tt.extensions", FALSE) out <- if ( isFALSE(ext) ){ msgf("Package '%s' registered no tinytest extensions.") list(character(0)) } else { ext } names(out) <- pkg invisible(out) } capture_using <- function(fun, envir, output){ function(...){ # call user-facing function ext <- fun(...) # get package name pkg <- names(ext) functions <- ext[[pkg]] for ( func in functions ){ # get funcy! # get function object from namespace f <- tryCatch(getFromNamespace(func, pkg) , error = function(e){ msg <- sprintf("Loading '%s' extensions failed with message:\n'%s'" , pkg, e$message) warning(msg, call.=FALSE) }) # mask'm like there's no tomorrow envir[[func]] <- capture(f, output) } invisible(ext) } } #' Register or unregister extension functions #' #' Functions to use in \code{.onLoad} and \code{.onUnload} by packages that #' extend \pkg{tinytest}. #' #' @param pkg \code{[character]} scalar. Name of the package providing extensions. #' @param functions \code{[character]} vector. Name of the functions in the package that must be added. #' #' #' @section The tinytest API: #' #' Packages can extend \pkg{tinytest} with expectation functions \emph{if and only #' if} the following requirements are satisfied. #' #' \enumerate{ #' \item{The extending functions return a \code{\link{tinytest}} object. This #' can be created by calling \code{tinytest()} with the arguments (defaults, #' if any, are in brackets): #' \itemize{ #' \item{\code{result}: A \code{logical} scalar: \code{TRUE} or \code{FALSE} (not #' \code{NA}) } #' \item{\code{call}: The \code{call} to the expectation function. Usually the #' result of \code{sys.call(sys.parent(1))} } #' \item{\code{diff} (\code{NA_character_}): A \code{character} scalar, with a long description of the #' difference. Sentences may be separated by \code{"\\n"}.} #' \item{\code{short} (\code{NA_character_}): Either \code{"data"}, if the difference is in the #' data. \code{"attr"} when attributes differ or \code{"xcpt"} when #' an expectation about an exception is not met. If there are #' differences in data and in attributes, the attributes take #' precedence.} #' \item{\code{info}} (\code{NA_character_}): A user-defined message. #' } #' Observe that this requires the extending package to add \pkg{tinytest} to #' the \code{Imports} field in the package's \code{DESCRIPTION} file (this #' also holds for the following requirement). #' } #' \item{Functions are registered in \code{.onLoad()} using #' \code{register_tinytest_extension()}. Functions that are already #' registered, including \pkg{tinytest} functions will be overwritten.} #' } #' It is \emph{recommended} to: #' \enumerate{ #' \item{Follow the syntax conventions of \pkg{tinytest} so expectation #' functions start with \code{expect_}.} #' \item{Explain to users of the extension package how to use the extension #' (see \code{\link{using}}).} #' \item{include an \code{info} argument to \code{expect_} functions that #' is passed to \code{tinytest()}}. #' } #' #' #' @section Minimal example packages: #' #' \itemize{ #' \item{Extending \pkg{tinytest}: #' \href{https://github.com/markvanderloo/tinytest.extension}{tinytest.extension}.} #' \item{Using a \pkg{tinytest} extension: #' \href{https://github.com/markvanderloo/uses.tinytest.extension}{using.tinytest.extension}.} #' } #' @family extensions #' @export register_tinytest_extension <- function(pkg, functions){ ext <- getOption("tt.extensions",FALSE) if (isFALSE(ext)){ L <-list(functions) names(L) <- pkg options(tt.extensions = L) } else { ext[[pkg]] <- functions options(tt.extensions = ext) } } #' Run an R file containing tests; gather results #' #' @param file \code{[character]} File location of a .R file. #' @param at_home \code{[logical]} toggle local tests. #' @param verbose \code{[integer]} verbosity level. 0: be quiet, 1: print #' status per file, 2: print status and increase counter after each test expression. #' @param color \code{[logical]} toggle colorize counts in verbose mode (see Note) #' @param remove_side_effects \code{[logical]} toggle remove user-defined side #' effects? See section on side effects. #' @param side_effects \code{[logical|list]} Either a logical, #' or a list of arguments to pass to \code{\link{report_side_effects}}. #' @param set_env \code{[named list]}. Key=value pairs of environment variables #' that will be set before the test file is run and reset afterwards. These are not #' counted as side effects of the code under scrutiny. #' @param ... Currently unused #' #' @details #' #' In \pkg{tinytest}, a test file is just an R script where some or all #' of the statements express an \code{\link[=expect_equal]{expectation}}. #' \code{run_test_file} runs the file while gathering results of the #' expectations in a \code{\link{tinytests}} object. #' #' The graphics device is set to \code{pdf(file=tempfile())} for the run of the #' test file. #' #' @section Side-effects caused by test code: #' #' All calls to \code{\link{Sys.setenv}} and \code{\link{options}} #' defined in a test file are captured and undone once the test file has run, #' if \code{remove_side_effects} is set to \code{TRUE}. #' #' @section Tracking side effects: #' #' Certain side effects can be tracked, even when they are not explicitly #' evoked in the test file. See \code{\link{report_side_effects}} for side #' effects tracked by \pkg{tinytest}. Calls to \code{report_side_effects} #' within the test file overrule settings provided with this function. #' #' #' #' @note #' Not all terminals support ansi escape characters, so colorized output can be #' switched off. This can also be done globally by setting #' \code{options(tt.pr.color=FALSE)}. Some terminals that do support ansi #' escape characters may contain bugs. An example is the RStudio terminal #' (RStudio 1.1) running on Ubuntu 16.04 (and possibly other OSs). #' #' @return A \code{list} of class \code{tinytests}, which is a list of #' \code{\link{tinytest}} objects. #' #' @examples #' # create a test file, in temp directory #' tests <- " #' addOne <- function(x) x + 2 #' #' Sys.setenv(lolz=2) #' #' expect_true(addOne(0) > 0) #' expect_equal(2, addOne(1)) #' #' Sys.unsetenv('lolz') #' " #' testfile <- tempfile(pattern="test_", fileext=".R") #' write(tests, testfile) #' #' # run test file #' out <- run_test_file(testfile,color=FALSE) #' out #' # print everything in short format, include passes in print. #' print(out, nlong=0, passes=TRUE) #' #' # run test file, track supported side-effects #' run_test_file(testfile, side_effects=TRUE) #' #' # run test file, track only changes in working directory #' run_test_file(testfile, side_effects=list(pwd=TRUE, envvar=FALSE)) #' #' #' @family test-files #' @seealso \code{\link{ignore}} #' @export run_test_file <- function( file , at_home=TRUE , verbose = getOption("tt.verbose", 2) , color = getOption("tt.pr.color", TRUE) , remove_side_effects = TRUE , side_effects = FALSE , set_env = list() , ...){ if (!file_test("-f", file)){ stop(sprintf("'%s' does not exist or is a directory",file),call.=FALSE) } t0 <- Sys.time() # set environment variables (if any) to control the R environment during testing. if (length(set_env) > 0){ # first, record current settings old_env_var <- sapply(names(set_env), Sys.getenv, unset=NA_character_, USE.NAMES=TRUE) # new settings do.call(Sys.setenv, set_env) } ## where to come back after running the file oldwd <- getwd() set_call_wd(oldwd) # make sure that plots get redirected to oblivion grDevices::pdf(file=nullfile()) ## this will store the names of all environment ## variables created while running the file. envvar <- new.env() ## this will store option values that are overwritten by ## the user when running the file. oldop <- new.env() ## Store locale settings that may be overwritten ## by the user when running the file locale <- Sys.getlocale() ## clean up side effects on.exit({ ## Clean up tinytest side effects # go back to the original working directory setwd(oldwd) set_call_wd("") # unset 'at_home' marker Sys.unsetenv("TT_AT_HOME") if ( remove_side_effects ){ ## Clean up user side effects # unset env vars set by the user in 'file' unset_envvar(envvar) # reset options to the state before running 'file' reset_options(oldop) # reset locale settings to starting values reset_locale(locale) } grDevices::dev.off() # return env var to values before running run_test_file if (exists("old_env_var")){ unset <- is.na(old_env_var) Sys.unsetenv(names(old_env_var)[unset]) if (any(!unset)) do.call(Sys.setenv, as.list(old_env_var)[!unset]) } }) setwd(dirname(file)) file <- basename(file) if (at_home) Sys.setenv(TT_AT_HOME=TRUE) # An environment to capture the output in. o <- output() # An environment to run the test scripts in e <- new.env(parent=globalenv()) # We locally mask expectation functions in the evaluation # environment 'e' so their output will be captured in 'o' add_locally_masked_functions(envir = e, output=o) ## Reduce user side effects by making sure that any env var set ## in a test file is unset after running it. e$Sys.setenv <- capture_envvar(Sys.setenv, envvar) ## Reduce user side effects by capturing options that will be reset ## on exit e$options <- capture_options(options, oldop) ## Set useFancyQuotes, which is usually done by startup.Rs, the location ## of which is defined by envvar R_TESTS, which we set to empty now. ## See GH issues 36,37 options(useFancyQuotes=FALSE) Sys.setenv(R_TESTS="") ## Make sure that we catch side-effects if the user asks for it. # an environment to store side-effects, and wheter we report them. sidefx <- new.env() e$report_side_effects <- capture_se(report_side_effects, sidefx) do.call(e$report_side_effects, as.list(side_effects)) # internal side-effect tracker: make sure results are exported to user. local_report_envvar <- capture(report_envvar, o) local_report_cwd <- capture(report_cwd, o) local_report_files <- capture(report_files, o) local_report_locale <- capture(report_locale, o) # parse file, store source reference. check_double_colon(filename=file) parsed <- parse(file=file, keep.source=TRUE) src <- attr(parsed, "srcref") o$file <- file # format file name for printing while running. prfile <- basename(file) if (nchar(prfile) > 30 ){ prfile <- paste0("..",substr(prfile, nchar(prfile)-27,nchar(prfile))) } prfile <- gsub(" ",".",sprintf("%-30s",basename(file))) for ( i in seq_along(parsed) ){ expr <- parsed[[i]] o$fst <- src[[i]][1] o$lst <- src[[i]][3] o$call <- expr if ( !o$exit ) eval(expr, envir=e) else break local_report_envvar(sidefx) local_report_cwd(sidefx) local_report_files(sidefx) local_report_locale(sidefx) if (verbose == 2) print_status(prfile, o, color, print=TRUE) } td <- abs(Sys.time() - t0) tx <- humanize(td, color=color) if (verbose == 1){ # always when run in parallel. And we can only print once in that case str <- print_status(prfile, o, color, print=FALSE) if (o$exit) catf("%s %s %s\n", str, tx, o$exit_msg()) else catf("%s %s\n", str, tx) } if (verbose >= 2){ str <- if (o$exit) catf("%s %s\n", tx, o$exit_msg()) else catf("%s\n", tx) } # returns a 'list' of 'tinytest' objects test_output <- o$gimme() structure(test_output, class="tinytests", duration=td) } # readable output from a number of seconds. humanize <- function(x, color=TRUE){ x <- as.numeric(x) # ms units str <- if (x < 0.1){ trimws(sprintf("%4.0fms",1000*x)) } else if (x < 60 ){ trimws(sprintf("%3.1fs",x)) } else if (x < 3600){ m <- x %/% 60 s <- x - m*60 trimws(sprintf("%2.0fm %3.1fs", m, s)) } else { # fall-through: hours, minutes, seconds. h <- x %/% 3600 m <- (x - 3600 * h)%/% 60 s <- x - 3600 * h - 60*m sprintf("%dh %dm %3.1fs", h,m,s) } col <- if (x<0.1) "cyan" else "blue" if (color) color_str(str, col) else str } color_str <- function(x, color){ cmap <- c(cyan=36, red=31, green=32, blue = 34) sprintf("\033[0;%dm%s\033[0m", cmap[color], x) } check_double_colon <- function(filename){ txt <- readLines(filename, warn=FALSE) i <- grepl("tinytest::expect", txt) & !grepl("#.*tinytest::expect", txt) if (!any(i)) return(NULL) line_numbers <- which(i) occurrences <- sub("^.*tinytest::expect","tinytest::expect",txt[i]) occurrences <- sub("\\(.*","",occurrences) prefix <- " You are using 'tinytest::' to express test expectations. The results from these tests are not collected. Found the following occurrences: " issues <- sprintf("> %s#%03d: %s",basename(filename),line_numbers,occurrences) issues <- paste(issues, collapse="\n ") postfix <- "\n Remove the 'tinytest::' prefix to register the test results." message(paste(prefix, issues, postfix), call.=FALSE) } print_status <- function(filename, env, color, print=TRUE){ prefix <- sprintf("\r%s %4d tests", filename, env$ntest()) # print status after counter fails <- if ( env$ntest() == 0 ) " " # print nothing if nothing was tested else if ( env$nfail() == 0 ) sprintf(if(color) "\033[0;32mOK\033[0m" else "OK") else sprintf(if (color) "\033[0;31m%d fails\033[0m" else "%d fails", env$nfail()) side <- if (env$nside() == 0) "" else sprintf(if (color) "\033[0;93m%d side-effects\033[0m " else "%d side-effects ", env$nside()) if(print) cat(prefix, fails, side, sep=" ") else paste(prefix, fails, side, sep=" ") } #' Run all tests in a directory #' #' \code{run\_test\_dir} runs all test files in a directory. #' #' #' @param dir \code{[character]} path to directory #' @param pattern \code{[character]} A regular expression that is used to find #' scripts in \code{dir} containing tests (by default \code{.R} or \code{.r} #' files starting with \code{test}). #' @param at_home \code{[logical]} toggle local tests. #' @param verbose \code{[logical]} toggle verbosity during execution #' @param color \code{[logical]} toggle colorize output #' @param remove_side_effects \code{[logical]} toggle remove user-defined side #' effects. Environment variables (\code{Sys.setenv()}) and options (\code{options()}) #' defined in a test file are reset before running the next test file (see details). #' @param cluster A \code{\link{makeCluster}} object. #' @param lc_collate \code{[character]} Locale setting used to sort the #' test files into the order of execution. The default \code{NA} ensures #' current locale is used. Set this e.g. to \code{"C"} to ensure bytewise #' and more platform-independent sorting (see details). #' @param ... Arguments passed to \code{run_test_file} #' #' @section Details: #' #' We cannot guarantee that files will be run in any particular order accross #' all platforms, as it depends on the available collation charts (a chart that #' determines how alphabets are sorted). For this reason it is a good idea to #' create test files that run independent of each other so their order of #' execution does not matter. In tinytest, test files cannot share variables. #' The default behavior of test runners further discourages interdependence by #' resetting environment variables and options that are set in a test file #' after the file is executed. If an environment variable needs to survive a #' single file, use \code{base::Sys.setenv()} explicitly. Similarly, if an #' option setting needs to survive, use \code{base::options()} #' #' @section Parallel tests: #' #' If \code{inherits(cluster, "cluster")} the tests are paralellized over a #' cluster of worker nodes. \pkg{tinytest} will be loaded onto each cluster #' node. All other preparation, including loading code from the tested package, #' must be done by the user. It is also up to the user to clean up the cluster #' after running tests. See the 'using tinytest' vignette for examples: #' \code{vignette("using_tinytest")}. #' #' #' @return A \code{tinytests} object #' #' #' @examples #' # create a test file in tempdir #' tests <- " #' addOne <- function(x) x + 2 #' #' expect_true(addOne(0) > 0) #' expect_equal(2, addOne(1)) #' " #' testfile <- tempfile(pattern="test_", fileext=".R") #' write(tests, testfile) #' #' # extract testdir #' testdir <- dirname(testfile) #' # run all files starting with 'test' in testdir #' out <- run_test_dir(testdir) #' print(out) #' dat <- as.data.frame(out) #' #' @family test-files #' @seealso \code{\link{makeCluster}}, #' \code{\link{clusterEvalQ}}, \code{\link{clusterExport}} #' #' @export run_test_dir <- function(dir="inst/tinytest", pattern="^test.*\\.[rR]$" , at_home = TRUE , verbose = getOption("tt.verbose", 2) , color = getOption("tt.pr.color",TRUE) , remove_side_effects = TRUE , cluster = NULL , lc_collate = getOption("tt.collate",NA) , ... ){ t0 <- Sys.time() testfiles <- dir(dir, pattern=pattern, full.names=TRUE) testfiles <- locale_sort(testfiles, lc_collate=lc_collate) if ( !inherits(cluster, "cluster") ){ # set pwd here, to save time in run_test_file. oldwd <- getwd() set_call_wd(oldwd) on.exit({setwd(oldwd); set_call_wd("")}) setwd(dir) test_output <- lapply(basename(testfiles), run_test_file , at_home = at_home , verbose = verbose , color = color , remove_side_effects = remove_side_effects , ...) } else { parallel::clusterEvalQ(cluster, library(tinytest)) test_output <- parallel::parLapply(cluster, testfiles , run_test_file, at_home = at_home, verbose = min(verbose,1) , color = color, remove_side_effects = TRUE, ...) } td <- abs(as.numeric(Sys.time()) - as.numeric(t0)) # by using '(parL)|(l)apply' we get a list of tinytests objects. We need to unwind # one level to a list of 'tinytest' objects and class it 'tinytests'. structure(unlist(test_output,recursive=FALSE), class="tinytests", duration=td) } # Sort according to LC_COLLATE locale_sort <- function(x, lc_collate=NA, ...){ if (is.na(lc_collate)) return(sort(x,...)) # catch current locale old_collate <- Sys.getlocale("LC_COLLATE") # set to user-defined locale if possible, otherwise sort using current locale colset <- tryCatch({ Sys.setlocale("LC_COLLATE", lc_collate) TRUE }, warning=function(e){ msg <- sprintf("Could not sort test files in 'C' locale, using %s\n" , old_collate) message(paste(msg, e$message,"\n")) FALSE }, error=warning) out <- sort(x) # reset to old locale if (colset) Sys.setlocale("LC_COLLATE", old_collate) out } #' Test a package during development #' #' \code{test_all} is a convenience function for package development, that #' wraps \code{run_test_dir}. By default, it runs all files starting with #' \code{test} in \code{./inst/tinytest/}. It is assumed that all functions to #' be tested are loaded. #' #' #' @param pkgdir \code{[character]} scalar. Root directory of the package (i.e. #' direcory where \code{DESCRIPTION} and \code{NAMESPACE} reside). #' @param testdir \code{[character]} scalar. Subdirectory where test files are #' stored. #' #' @rdname run_test_dir #' #' @export test_all <- function(pkgdir="./", testdir="inst/tinytest", ...){ run_test_dir( file.path(pkgdir,testdir), ...) } #' Detect not on CRANity #' #' Detect whether we are running at home (i.e. not on CRAN, BioConductor, ...) #' #' #' @examples #' # test will run locally, but not on CRAN #' if ( at_home() ){ #' expect_equal(2, 1+1) #' } #' @export #' @family test-functions test-file at_home <- function(){ identical(Sys.getenv("TT_AT_HOME"),"TRUE") } #' Test a package during R CMD check or after installation #' #' Run all tests in an installed package. Throw an error and print all failed test #' results when one or more tests fail if not in interactive mode (e.g. when #' R CMD check tests a package). This function is intended to be #' used by \code{R CMD check} or by a user that installed a package that #' uses the \pkg{tinytest} test infrastructure. #' #' @param pkgname \code{[character]} scalar. Name of the package, as in the \code{DESCRIPTION} file. #' @param testdir \code{[character]} scalar. Path to installed directory. By default #' tinytest assumes that test files are in \code{inst/tinytest/}, which means #' that after installation and thus during \code{R CMD check} they are in #' \code{tinytest/}. See details for using alternate paths. #' @param lib.loc \code{[character]} scalar. location where the package is installed. #' @param at_home \code{[logical]} scalar. Are we at home? (see Details) #' @param ncpu A positive integer, or a \code{\link{makeCluster}} object. #' @param ... extra arguments passed to \code{\link{run_test_dir}} (e.g. \code{ncpu}). #' #' #' @section Details: #' We set \code{at_home=FALSE} by default so \code{R CMD check} will run the #' same as at CRAN. See the package vignette (Section 4) for tips on how to set #' up the package structure. #' \code{vignette("using_tinytest",package="tinytest")}. #' #' Package authors who want to avoid installing tests with the package can #' create a directory under \code{tests}. If the test directoy is called #' \code{"tests/foo"}, use \code{test_package("pkgname", testdir="foo")} in #' \code{tests/tinytest.R}. #' #' #' #' @return If \code{interactive()}, a \code{tinytests} object. If not #' \code{interactive()}, an error is thrown when at least one test fails. #' #' @family test-files #' @seealso \code{\link{setup_tinytest}} #' @examples #' \dontrun{ #' # Create a file with the following content, to use #' # tinytest as your unit testing framework: #' if (requireNamespace("tinytest", quietly=TRUE)) #' tinytest::test_package("your package name") #' } #' @export test_package <- function(pkgname, testdir = "tinytest", lib.loc=NULL , at_home=FALSE, ncpu=NULL, ...){ oldlibpaths <- .libPaths() if (!is.null(lib.loc)){ e <- new.env() e$libs <- c(lib.loc, oldlibpaths) if (!dir.exists(lib.loc)) warnf("lib.loc '%s' not found.", lib.loc) .libPaths(c(lib.loc, oldlibpaths)) } on.exit({ if ( is.numeric(ncpu) ) parallel::stopCluster(cluster) .libPaths(oldlibpaths) }) if (!dir.exists(testdir)){ # if not customized test dir # find the installed test dir new_testdir <- system.file(testdir, package=pkgname, lib.loc=lib.loc) if (new_testdir == ""){ stopf("testdir '%s' not found for package '%s'",testdir, pkgname) } else { testdir <- new_testdir } } # set up cluster if required cluster <- if (is.null(ncpu)) NULL else if (is.numeric(ncpu)) parallel::makeCluster(ncpu, outfile="") else if (inherits(ncpu, "cluster")) ncpu else stop("ncpu must be NULL, 'numeric', or 'cluster'") # By now we have a cluster, or NULL. Load the pkg under scrutiny. if ( is.null(cluster) ){ library(pkgname, character.only=TRUE, lib.loc=lib.loc) } else { if (!is.null(lib.loc)){ # to prevent a R CMD check warning we must create a dummy libs here # as well libs <- NULL parallel::clusterExport(cluster, "libs", envir = e) parallel::clusterEvalQ(cluster, .libPaths(libs)) } parallel::clusterCall(cluster, library, pkgname, character.only=TRUE, lib.loc=lib.loc) } out <- run_test_dir(testdir, at_home=at_home, cluster=cluster,...) i_fail <- sapply(out, isFALSE) if ( any(i_fail) && !interactive() ){ writeLines(vapply(out[i_fail], format.tinytest, "", type="long")) stop(sum(i_fail), " out of ", length(out), " tests failed", call.=FALSE) } out } #' build, install and test #' #' Builds and installs the package in \code{pkgdir} under a temporary #' directory. Next, loads the package in a fresh R session and runs all the #' tests. For this function to work the following system requirements are #' necessary. #' \itemize{ #' \item{\code{R CMD build} is available on your system} #' \item{\code{Rscript} is available on your system} #' } #' #' @param pkgdir \code{[character]} Package directory #' @param testdir \code{[character]} Name of directory under \code{pkgdir/inst} #' containing test files. #' @param pattern \code{[character]} A regular expression that is used to find #' scripts in \code{dir} containing tests (by default \code{.R} or \code{.r} #' files starting with \code{test}). #' @param at_home \code{[logical]} toggle local tests. #' @param ncpu \code{[numeric]} number of CPUs to use during the testing phase. #' @param verbose \code{[logical]} toggle verbosity during execution #' @param remove_side_effects \code{[logical]} toggle remove user-defined side #' effects? See section on side effects. #' @param side_effects \code{[logical|list]} Either a logical, #' or a list of arguments to pass to \code{\link{report_side_effects}}. #' @param lc_collate \code{[character]} Locale setting used to sort the #' test files into the order of execution. The default \code{NA} ensures #' current locale is used. Set this e.g. to \code{"C"} to ensure bytewise #' and more platform-independent sorting (see details in \code{\link{run_test_dir}}. #' @param keep_tempdir \code{[logical]} keep directory where the pkg is #' installed and where tests are run? If \code{TRUE}, the directory is not #' deleted and it's location is printed. #' #' #' @return A \code{tinytests} object. #' #' @examples #' \dontrun{ #' ## If your package source directory is "./pkg" you can run #' build_install_test("pkg") #' } #' @family test-files #' @export build_install_test <- function(pkgdir="./", testdir="tinytest" , pattern="^test.*\\.[rR]$" , at_home=TRUE , verbose=getOption("tt.verbose",2) , ncpu = 1 , remove_side_effects=TRUE , side_effects=FALSE , lc_collate = getOption("tt.collate",NA) , keep_tempdir=FALSE){ oldwd <- getwd() tdir <- tempfile() on.exit({setwd(oldwd) if (keep_tempdir){ cat(sprintf("tempdir: %s\n",tdir)) } else { unlink(tdir, recursive=TRUE) } }) pkg <- normalizePath(pkgdir, winslash="/") pkgname <- read.dcf(file.path(pkg, "DESCRIPTION"), fields = "Package") pattern <- gsub("\\", "\\\\", pattern, fixed=TRUE) dir.create(tdir) setwd(tdir) ## build package system2("R", args=c("CMD", "build", "--no-build-vignettes", "--no-manual", shQuote(pkg))) ## find tar.gz and install in temporary folder. pkgfile <- dir("./", pattern=paste0(pkgname, ".*\\.tar\\.gz"), full.names = TRUE) install.packages(pkgfile,lib=tdir, repos=NULL, type="source") ## In a fresh R session, load package and run tests script <- " suppressPackageStartupMessages({ pkgname <- '%s' pattern <- '%s' tdir <- '%s' testdir <- '%s' at_home <- %s verbose <- %d remove_side_effects <- %s side_effects <- %s ncpu <- %d lc_collate <- %s # pkgname tdir library(pkgname, lib.loc=tdir,character.only=TRUE) library('tinytest') }) if (ncpu > 1){ cluster <- parallel::makeCluster(ncpu, outfile='') parallel::clusterCall(cluster, library, pkgname, character.only=TRUE) } else { cluster <- NULL } # testdir pkgname tdir out <- run_test_dir(system.file(testdir, package=pkgname, lib.loc=tdir) , at_home=at_home , pattern=pattern , verbose=verbose , remove_side_effects=remove_side_effects , side_effects=side_effects , cluster=cluster , lc_collate=lc_collate) saveRDS(out, file='output.RDS') if (!is.null(cluster)) parallel::stopCluster(cluster) " scr <- sprintf(script , pkgname , pattern , normalizePath(tdir, winslash="/", mustWork=FALSE) , testdir , at_home , verbose , remove_side_effects , side_effects , ncpu , lc_collate) write(scr, file="test.R") system("Rscript test.R") readRDS(file.path(tdir, "output.RDS")) } tinytest/R/utils.R0000644000176200001440000000202114052012452013607 0ustar liggesusers # standard convenience functions catf <- function(fmt,...) cat(sprintf(fmt,...)) stopf <- function(fmt,...) stop(sprintf(fmt,...), call.=FALSE) warnf <- function(fmt,...) warning(sprintf(fmt,...), call.=FALSE) msgf <- function(fmt, ...) message(sprintf(fmt,...)) # support R versions < 3.2, that lack trimws() and dir.exists() if (getRversion() < "3.2.0"){ trimws <- function (x, which = c("both", "left", "right"), whitespace = "[ \t\r\n]") { which <- match.arg(which) mysub <- function(re, x) sub(re, "", x, perl = TRUE) switch(which, left = mysub(paste0("^", whitespace, "+"), x), right = mysub(paste0(whitespace, "+$"), x), both = mysub(paste0(whitespace, "+$"), mysub(paste0("^", whitespace, "+"), x))) } dir.exists <- function (paths) { x = base::file.info(paths)$isdir !is.na(x) & x } } # support for R versions < 3.6 that lack nullfile() if (getRversion() < "3.6.0"){ nullfile <- function () { if (.Platform$OS.type == "windows") "nul:" else "/dev/null" } } tinytest/R/expectations.R0000644000176200001440000007136014065357345015212 0ustar liggesusers # define this internally, since the desired behavior was introduced at R 3.5.0 isTRUE <- function(x){ is.logical(x) && length(x) == 1L && !is.na(x) && x } if (!exists("isFALSE", mode = "function", envir = baseenv())) { # define this internally, since it was introduced at R 3.5.0 isFALSE <- function(x){ is.logical(x) && length(x) == 1L && !is.na(x) && !x } } #' Tinytest constructor #' #' #' Each individual test in the package generates a \code{tinytest} object. A #' \code{tinytest} object is a \code{logical} scalar, with metadata #' (attributes) about the test. #' #' @param result \code{[logical]} scalar. #' @param call \code{[call]} The call that created \code{result}. #' @param diff \code{[character]} difference between current and target value #' (if any). #' @param short \code{[character]} short description of the difference #' @param info \code{[character]} other information, to be printed in the long message #' @param file \code{[character]} File location of the test. #' @param fst \code{[integer]} First line number in the test file. #' @param lst \code{[integer]} Last line number in the test file (differs #' from \code{fst} if the call spans multiple lines). #' #' @section Details: #' The \pkg{result} can take three values. #' \itemize{ #' \item{\code{TRUE}: test was passed.} #' \item{\code{FALSE}: test was failed.} #' \item{\code{NA}: A side effect was detected.} #' } #' Authors of extension packages should not use \code{NA} as a result value as #' this part of the interface may change in the future. #' #' #' @return A \code{tinytest} object. #' @family extensions #' #' @examples #' tt <- expect_equal(1+1, 2) #' if (isTRUE(tt)){ #' print("w00p w00p!") #' } else { #' print("Oh no!") #' } #' #' #' #' @keywords internal #' @export tinytest <- function(result, call , trace= NULL , diff = NA_character_ , short= c(NA_character_,"data","attr","xcpt", "envv","wdir","file","lcle") , info = NA_character_ , file = NA_character_ , fst = NA_integer_ , lst = NA_integer_ ,...){ short <- match.arg(short) structure(result # logical TRUE/FALSE , class = "tinytest" , call = call # call creating or motivating the object , trace = trace # list containing stack trace , diff = diff # diff if isFALSE(result) , short = short # short diff (4 char) , info = info # user-defined info , file = file # test file location , fst = fst # first line of call , lst = lst # last line of call , ...) } na_str <- function(x) if ( is.na(x) ) "" else as.character(x) oneline <- function(x) sub("\\n.+","...",x) indent <- function(x, with=" "){ if (is.na(x)) "" else gsub("\\n *",paste0("\n",with),paste0(with,sub("^ +","",x))) } lineformat <- function(x){ if ( is.na(x) ) "" else sprintf("%d",x) } # check if 'call' is a subcall of 'x'. # call and x are both objects of class call. has_call <- function(call, x){ # we do this to ignore possible srcref. attributes(x) <- NULL attributes(call) <- NULL identical(x,call) || length(x) > 1 && any(sapply(x, has_call, call)) } #' @param type \code{[logical]} Toggle format type #' #' @return A character string #' #' #' @rdname print.tinytest #' @export #' #' @examples #' tt <- expect_equal(1+1, 3) #' format(tt,"long") #' format(tt,"short") format.tinytest <- function(x,type=c("long","short"), ...){ type <- match.arg(type) d <- attributes(x) # trycatch to make absolutely sure that we always return to the default # print, should something go wrong. i <- tryCatch(sapply(d$trace, has_call, d$call), error=function(e) NULL) need_trace <- any(i) && all(i < length(d$trace)) call <- if( !need_trace ){ paste0(deparse(d$call, control=NULL), collapse="\n") } else { i1 <- which(i)[length(which(i))] j <- seq(i1,length(d$trace)) paste0(sapply(d$trace[j], deparse, control=NULL), collapse="\n-->") } fst <- lineformat(d$fst, ...) lst <- lineformat(d$lst, ...) file <- na_str(d$file) short <- na_str(d$short) diff <- d$diff info <- na_str(d$info) result <- if (isTRUE(x)) "PASSED " else if (isFALSE(x)) sprintf("FAILED[%s]",short) else if (is.na(x) ) sprintf("SIDEFX[%s]",short) longfmt <- "----- %s: %s<%s--%s>\n%s" if (type == "short"){ sprintf("%s: %s<%s--%s> %s", result, basename(file), fst, lst, oneline(call)) } else { str <- sprintf(longfmt, result, file, fst, lst , indent(call, with=" call| ")) if (isFALSE(x)||is.na(x)) str <- paste0(str, "\n", indent(diff, with=" diff| ")) if (!is.na(d$info)) str <- paste0(str, "\n", indent(info, with=" info| ")) str } } #' Print a tinytest object #' #' @param x A \code{tinytest} object #' @param ... passed to \code{\link{format.tinytest}} #' #' @examples #' print(expect_equal(1+1, 2)) #' print(expect_equal(1+1, 3), type="long") #' #' @export print.tinytest <- function(x,...){ cat(format.tinytest(x,...),"\n") } is_atomic <- function(x){ inherits(x,"POSIXct") || ( length(class(x)) == 1 && class(x) %in% c( "character" , "logical" , "factor" , "ordered" , "integer" , "numeric" , "complex") ) } is_scalar <- function(x){ length(x) == 1 && is_atomic(x) } # alt: alternative output longdiff <- function(current, target, alt){ equivalent_data <- all.equal(target, current , check.attributes=FALSE , use.names=FALSE) if ( identical(class(current), class(target)) && is_scalar(current) && is_scalar(target) ){ if (!isTRUE(equivalent_data)){ sprintf("Expected '%s', got '%s'", target, current) } else { "Attributes differ" } } else if (isTRUE(alt) && is.environment(current)){ "Equal environment objects, but with different memory location" } else { paste0(" ", alt, collapse="\n") } } # are there differences in data and/or attributes, or just in the attributes? shortdiff <- function(current, target, ...){ equivalent_data <- all.equal(target, current , check.attributes=FALSE , use.names=FALSE,...) if (isTRUE(equivalent_data)) "attr" else "data" } #' Express expectations #' #' @param current \code{[R object or expression]} Outcome or expression under scrutiny. #' @param target \code{[R object or expression]} Expected outcome #' @param tolerance \code{[numeric]} Test equality to machine rounding. Passed #' to \code{\link[base]{all.equal} (tolerance)} #' @param info \code{[character]} scalar. Optional user-defined message. Must #' be a single character string. Multiline comments may be separated by #' \code{"\\n"}. #' @param ... Passed to \code{all.equal} #' #' @return A \code{\link{tinytest}} object. A tinytest object is a #' \code{logical} with attributes holding information about the #' test that was run #' #' @note #' Each \code{expect_haha} function can also be called as \code{checkHaha}. #' Although the interface is not entirely the same, it is expected that #' this makes migration from the \code{RUnit} framework a little easier, for those #' who wish to do so. #' #' @section More information and examples: #' #' \itemize{ #' \item{An overview of tinytest can be found in \code{vignette("using_tinytest")}}. #' \item{Examples of how tinytest is used in practice can be found in #' \code{vignette("tinytest_examples")}} #' } #' @family test-functions #' #' @examples #' expect_equal(1 + 1, 2) # TRUE #' expect_equal(1 - 1, 2) # FALSE #' expect_equivalent(2, c(x=2)) # TRUE #' expect_equal(2, c(x=2)) # FALSE #' #' @export expect_equal <- function(current, target, tolerance = sqrt(.Machine$double.eps), info=NA_character_, ...){ check <- all.equal(target, current, tolerance=tolerance, ...) equal <- isTRUE(check) diff <- if (equal) NA_character_ else longdiff( current, target, check) short <- if (equal) NA_character_ else shortdiff(current, target, tolerance=tolerance) tinytest(result = equal, call = sys.call(sys.parent(1)), diff=diff, short=short, info=info) } #' @rdname expect_equal #' @export expect_identical <- function(current, target, info=NA_character_){ result <- identical(current, target) diff <- if (result) NA_character_ else longdiff(current, target, all.equal(target, current, check.attributes=TRUE)) short <- if (result) NA_character_ else shortdiff(current, target, tolerance=0) tinytest(result=result, call=sys.call(sys.parent(1)), diff=diff , short=short, info=info) } #' @details #' \code{expect_equivalent} calls \code{expect_equal} with the extra #' arguments \code{check.attributes=FALSE} and \code{use.names=FALSE} #' #' #' @rdname expect_equal #' @export expect_equivalent <- function(current, target, tolerance = sqrt(.Machine$double.eps) , info=NA_character_, ...){ out <- expect_equal(current, target , check.attributes=FALSE,use.names=FALSE , tolerance=tolerance, info=info, ...) attr(out, 'call') <- sys.call(sys.parent(1)) out } #' @rdname expect_equal #' @export expect_true <- function(current, info=NA_character_){ result <- isTRUE(current) call <- sys.call(sys.parent(1)) if (!result){ this <- if ( isFALSE(current) ) "FALSE" else if ( length(current) == 1 && is.na(current)) "NA" else if ( is.logical(current)) sprintf("'logical' of length %d",length(current)) else sprintf("object of class '%s'",class(current)) diff <- sprintf("Expected TRUE, got %s", this) short <- shortdiff(TRUE, FALSE) tinytest(result, call=call,diff=diff, short=short, info=info) } else { tinytest(result, call = sys.call(sys.parent(1)), info=info) } } #' @rdname expect_equal #' @export expect_false <- function(current, info=NA_character_){ result <- isFALSE(current) call <- sys.call(sys.parent(1)) if (!result){ this <- if ( isTRUE(current) ) "TRUE" else if (length(current) == 1 && is.na(current)) "NA" else if (is.logical(current)) sprintf("'logical' of length %d",length(current)) else sprintf("object of class '%s'",class(current)) diff <- sprintf("Expected FALSE, got %s", this) short <- shortdiff(TRUE, FALSE) tinytest(result, call=call,diff=diff, short=short, info=info) } else { tinytest(result, call = sys.call(sys.parent(1)), info=info) } } #' @rdname expect_equal #' #' @param quiet \code{[logical]} suppress output printed by the \code{current} #' expression (see examples) #' #' @details #' #' \code{expect_silent} fails when an error or warning is thrown. #' #' @examples #' #' expect_silent(1+1) # TRUE #' expect_silent(1+"a") # FALSE #' expect_silent(print("hihi")) # TRUE, nothing goes to screen #' expect_silent(print("hihi"), quiet=FALSE) # TRUE, and printed #' #' @export expect_silent <- function(current, quiet=TRUE, info=NA_character_){ ## Make sure that printed output does not go to screen. # nullfile() was introduced at 3.6.0 and we want to be usable # on older releases as well. has_nullfile <- exists("nullfile") if (quiet){ # we need to use 'do.call' to avoid a NOTE on r-oldrel dumpfile <- if(has_nullfile) do.call("nullfile", list()) else tempfile() sink(dumpfile) } # clean up on.exit({ if (quiet){ sink(NULL) if (!has_nullfile) unlink(dumpfile) } }) # try to evaluate 'current' if that doesn't work properly, store # error or warning message. result <- TRUE msg <- "" type <- "none" tryCatch(current , error = function(e){ result <<- FALSE msg <<- e$message type <<- "An error" } , warning = function(w){ result <<- FALSE msg <<- w$message type <<- "A warning" } ) call <- sys.call(sys.parent(1)) diff <- if (msg != ""){ sprintf("Execution was not silent. %s was thrown with message\n '%s'",type,msg) } else { NA_character_ } tinytest(result , call = sys.call(sys.parent(1)) , short = if (result) NA_character_ else "xcpt" , diff = diff , info = info ) } #' @rdname expect_equal #' @export expect_null <- function(current, info=NA_character_){ call <- sys.call(sys.parent(1)) if (is.null(current)){ tinytest(TRUE, call=call, info=info) } else { tinytest(FALSE, call=call, short="data" , diff = sprintf("Expected NULL, got '%s'", paste(class(current), collapse=", ")) , info = info ) } } #' @rdname expect_equal #' #' @param class \code{[character]} A class string. #' @details #' \code{expect_inherits} fails when \code{\link{inherits}(current,class)} returns \code{FALSE} #' @export expect_inherits <- function(current, class, info=NA_character_){ call <- sys.call(sys.parent(1)) res <- inherits(current, class) if (isTRUE(res)){ tinytest(TRUE, call=call, info=info) } else { tinytest(FALSE, call=call, short="attr" , diff = sprintf("Expected object of class %s, got %s" , paste0("<", paste(class,collapse=", "),">") , paste0("<", paste(class(current), collapse=", "),">")) , info=info) } } #' @rdname expect_equal #' @param pattern \code{[character]} A regular expression to match the message. #' @param class \code{[character]} For condition signals (error, warning, message) #' the class from which the condition should inherit. #' @param ... passed on to \code{\link{grepl}} (useful for e.g. \code{fixed=TRUE}). #' @export expect_error <- function(current, pattern=".*", class="error", info=NA_character_, ...){ result <- FALSE diff <- "No error" tryCatch(current, error=function(e){ matches <- grepl(pattern, e$message, ...) isclass <- inherits(e, class) if (matches && isclass){ result <<- TRUE } else if (!isclass){ diff <<- sprintf("Error of class '%s', does not inherit from '%s'" , paste(class(e), collapse=", "), class) } else if (!matches){ diff <<- sprintf("The error message:\n '%s'\n does not match pattern '%s'" , e$message, pattern) } }) tinytest(result, call = sys.call(sys.parent(1)) , short= if(result) NA_character_ else "xcpt" , diff = if(result) NA_character_ else diff , info = info) } # helper: format 1st three elements of a list of condition objects first_n <- function(L, n=3){ i <- seq_len(min(length(L),n)) msgcls <- sapply(L[i], function(m) paste(class(m), collapse=", ")) maintype <- sapply(L[i], function(m){ if ( inherits(m, "message") ) "Message" else if ( inherits(m, "warning") ) "Warning" else if ( inherits(m, "error") ) "Error" else "Condition" }) msgtxt <- sub("\\n$","", sapply(L[i], function(m) m$message)) out <- sprintf("%s %d of class <%s>:\n '%s'",maintype, i, msgcls, msgtxt) paste(out, collapse="\n") } #' @rdname expect_equal #' @export expect_warning <- function(current, pattern=".*", class="warning", info=NA_character_,...){ messages <- list() warnings <- list() errors <- list() tryCatch(withCallingHandlers(current , warning = function(w){ warnings <<- append(warnings, list(w)) invokeRestart("muffleWarning") } , message = function(m) { messages <<- append(messages, list(m)) invokeRestart("muffleMessage") } ) , error = function(e) errors <<- append(errors, list(e)) ) nmsg <- length(messages) nwrn <- length(warnings) nerr <- length(errors) results <- sapply(warnings, function(w) { inherits(w, class) && grepl(pattern, w$message, ...) }) if (any(results)){ ## happy flow result <- TRUE short <- diff <- NA_character_ } else { ## construct diff message result <- FALSE short <- "xcpt" diff <- if ( nwrn == 0 ){ "No warning was emitted" } else { n_right_class <- sum(sapply(warnings, function(w) inherits(w, class))) if (n_right_class == 0){ head <- sprintf("Found %d warning(s), but not of class '%s'.", nwrn, class) head <- paste(head, "Showing up to three warnings:\n") body <- first_n(warnings) paste(head, body) } else { wrns <- Filter(function(w) inherits(w,class), warnings) head <- sprintf("Found %d warnings(s) of class '%s', but not matching '%s'." , nwrn, class, pattern) head <- paste(head,"\nShowing up to three warnings:\n") body <- first_n(wrns) paste(head, body) } } } if (!result && (nmsg > 0 || nerr > 0)) diff <- paste0(diff,sprintf("\nAlso found %d message(s) and %d error(s)" , nmsg, nerr)) tinytest(result, call=sys.call(sys.parent(1)) , short=short, diff=diff, info=info) } #' @rdname expect_equal #' @export expect_message <- function(current, pattern=".*", class="message", info=NA_character_, ...){ messages <- list() warnings <- list() errors <- list() tryCatch(withCallingHandlers(current , warning = function(w){ warnings <<- append(warnings, list(w)) invokeRestart("muffleWarning") } , message = function(m) { messages <<- append(messages, list(m)) invokeRestart("muffleMessage") } ) , error = function(e) errors <<- append(errors, list(e)) ) nmsg <- length(messages) nwrn <- length(warnings) nerr <- length(errors) results <- sapply(messages, function(m) { inherits(m, class) && grepl(pattern, m$message, ...) }) if (any(results)){ ## happy flow result <- TRUE short <- diff <- NA_character_ } else { ## construct diff message result <- FALSE short <- "xcpt" diff <- if (length(messages) == 0){ "No message was emitted" } else { n_right_class <- sum(sapply(messages, function(m) inherits(m, class))) if (n_right_class == 0){ head <- sprintf("Found %d message(s), but not of class '%s'.", nmsg, class) head <- paste(head, "Showing up to three messages:\n") body <- first_n(messages) paste(head, body) } else { msgs <- Filter(function(m) inherits(m,class), messages) head <- sprintf("Found %d message(s) of class '%s', but not matching '%s'." , nmsg, class, pattern) head <- paste(head,"\nShowing up to three messages:\n") body <- first_n(msgs) paste(head, body) } } } if (!result && (nwrn > 0 || nerr > 0)) diff <- paste0(diff,sprintf("\nAlso found %d warning(s) and %d error(s)" , nwrn, nerr)) tinytest(result, call=sys.call(sys.parent(1)) , short=short, diff=diff, info=info) } #' @rdname expect_equal #' #' @details #' #' \code{expect_stdout} Expects that output is written to \code{stdout}, #' for example using \code{cat} or \code{print}. Use \code{pattern} to #' specify a regular expression matching the output. #' #' #' @export expect_stdout <- function(current, pattern=".*", info=NA_character_, ...){ value <- "" msg <- NA_character_ tc <- textConnection("value", open="w", local=TRUE) sink(file=tc, type="output", split=FALSE) tryCatch(current , error=function(e){sink(file=NULL, type="output"); stop(e)} ) sink(file = NULL, type="output") close(tc) value <- paste(value, collapse="\n") result <- grepl(pattern, value, ...) if (!result) msg <- sprintf("output '%s'\n does not match pattern '%s'", value, pattern) tinytest(result, call = sys.call(sys.parent(1)) , short= if(result) NA_character_ else "xcpt" , diff = msg , info = info) } #' Compare object with object stored in a file #' #' Compares the current value with a value stored to file with #' \code{\link{saveRDS}}. If the file does not exist, the current value is #' stored into file, and the test returns \code{expect_null(NULL)}. #' #' @param current \code{[R object or expression]} Outcome or expression under #' scrutiny. #' @param file \code{[character]} File where the \code{target} is stored. If #' \code{file} does not exist, \code{current} will be stored there. #' @param ... passed to \code{\link{expect_equal}}, respectively \code{\link{expect_equivalent}}. #' #' @note #' Be aware that on CRAN it is not allowed to write data to user space. So make #' sure that the file is either stored with your tests, or generated with #' \code{\link{tempfile}}, or the test is skipped on CRAN, using #' \code{\link{at_home}}. #' #' \code{\link{build_install_test}} clones the package and #' builds and tests it in a separate R session in the background. This means #' that if you create a file located at \code{tempfile()} during the run, this #' file is destroyed when the separate R session is closed. #' #' \code{expect_error}, \code{expect_warning} and \code{expect_message} will #' concatenate all messages when multiple exceptions are thrown, before #' matching the message to \code{pattern}. #' #' #' #' @family test-functions #' #' #' @examples #' filename <- tempfile() #' # this gives TRUE: the file does not exist, but is created now. #' expect_equal_to_reference(1, file=filename) #' # this gives TRUE: the file now exists, and its contents is equal #' # to the current value #' expect_equal_to_reference(1, file=filename) #' # this gives FALSE: the file exists, but is contents is not equal #' # to the current value #' expect_equal_to_reference(2, file=filename) #' #' @export expect_equal_to_reference <- function(current, file, ...){ eetr(current=current, file=file, type="equal", ...) } #' @rdname expect_equal_to_reference #' @export expect_equivalent_to_reference <- function(current, file, ...){ eetr(current=current, file=file, type="equivalent", ...) } eetr <- function (current, file, type=c("equal","equivalent"), ...){ if (file.exists(file)){ out <- if (type=="equal") tinytest::expect_equal(current, readRDS(file), ...) else tinytest::expect_equivalent(current, readRDS(file), ...) if (!out){ diff <- attr(out, "diff") diff <- paste( sprintf("current does not match target read from %s\n", file) , diff) attr(out,"diff") <- diff } out } else { tinytest::expect_null(saveRDS(current, file) , info=sprintf("Stored value in %s", file)) } } #' Report side effects for expressions in test files #' #' Call this function from within a test file to report side effects. #' #' @param report \code{[logical]} report all side-effects #' @param envvar \code{[logical]} changes in environment variables #' @param pwd \code{[logical]} changes in working directory #' @param files \code{[logical]} changes in files in the directory where the #' test file lives. Also watches subdirectories. #' @param locale \code{[logical]} Changes in locale settings as detected by #' \code{link[base]{Sys.getlocale}} are reported. #' #' @section Details: #' A side effect causes a change in an external variable outside of the scope #' of a function, or test file. This includes environment variables, global #' options, global R variables, creating files or directories, and so on. #' #' If this function is called in a test file, side effects are monitored from #' that point in the file and only for that file. The state of the environment #' before and after running every expression in the file are compared. #' #' There is some performance penalty in tracking external variables, especially #' for those that require a system call. #' #' @section Note: #' There could be side-effects that are untrackable by \pkg{tinytest}. This includes #' packages that use a global internal state within their namespace or packages #' that use a global state within compiled code. #' #' @family sidefx #' #' @return A named \code{logical}, indicating which aspects of the environment #' are tracked, invisibly. #' #' @examples #' # switch on #' report_side_effects() #' # switch off #' report_side_effects(FALSE) #' #' # only report changes in environment variables #' report_side_effects(report=FALSE, envvar=TRUE) #' #' @export report_side_effects <- function(report=TRUE, envvar=report, pwd=report, files=report, locale=report){ stopifnot(is.logical(envvar)) list(envvar=envvar, pwd=pwd, files=files, locale=locale) } # generate user-facing function that captures 'report_side_effects' capture_se <- function(fun, env){ function(...){ out <- fun(...) env$sidefx <- out if (out[['envvar']]) env$envvar <- Sys.getenv() if (out[['pwd']]) env$pwd <- getwd() if (out[['files']]){ env$filesdir <- getwd() env$files <- file.info(dir(env$filesdir, recursive=TRUE, full.names=TRUE)) } if (out[['locale']]){ env$locale <- Sys.getlocale() } out } } # internal function, to be called by run_test_file after local capture. report_envvar <- function(env){ if ( !isTRUE(env$sidefx[['envvar']]) ) return(NULL) old <- env$envvar current <- Sys.getenv() if (identical(old, current)) return(NULL) out <- dlist_diff(env$envvar, current,"envvar") env$envvar <- current out } locale_vector <- function(x){ x <- strsplit(x,";")[[1]] values <- sub("^.*=","",x) names(values) <- sub("=.*","",x) # make sure order is normalized values <- values[order(names(values))] values } report_locale <- function(env){ if ( !isTRUE(env$sidefx[['locale']]) ) return(NULL) current <- Sys.getlocale() if (identical(env$locale, current)) return(NULL) # report all locale settings that are different. out <- character(0) cur <- locale_vector(current) old <- locale_vector(env$locale) i <- cur != old cur <- cur[i] old <- old[i] diff <- sprintf("%s changed from '%s' to '%s'", names(cur), old, cur) diff <- paste(diff, collapse="\n") env$locale <- current tinytest(NA , call = sys.call(sys.parent(1)) , diff = diff , short = "lcle" , info = "Locale setting changed" ) } # old and new are Dlist variables, resulting from # calls to Sys.getenv(). The output is a string reporting # added, removed, changed environment variables. Each report # separated by a newline \n dlist_diff <- function(old, new, type){ if (identical(old,new)) return() old.vars <- names(old) new.vars <- names(new) removed <- setdiff(old.vars, new.vars) added <- setdiff(new.vars, old.vars) survived <- intersect(old.vars, new.vars) changed <- survived[ old[survived] != new[survived] ] rem <- if (length(removed) == 0 ) NULL else sprintf("Removed %s '%s' with value '%s'", type, removed, old[removed]) if(!is.null(rem)) rem <- paste(rem, collapse="\n") add <- if (length(added) == 0) NULL else sprintf("Added %s '%s' with value '%s'", type, added, new[added]) if (!is.null(add)) add <- paste(add, collapse="\n") cng <- if ( length(changed) == 0 ) NULL else sprintf("Changed %s '%s' from '%s' to '%s'" , type, changed, old[changed], new[changed]) if (!is.null(cng)) cng <- paste(cng, collapse="\n") long <- paste(c(rem, add, cng),collapse="\n") if (long == "") return() tinytest(NA , call = sys.call(sys.parent(1)) , diff = long , short = "envv" ) } # internal function, to be called by run_test_file after local capture. report_cwd <- function(env){ if ( !isTRUE(env$sidefx[['pwd']]) ) return(NULL) old <- env$pwd current <- getwd() if ( identical(old, current) ) return(NULL) msg <- sprintf("Working directory changed from \n '%s'\nto\n '%s'", old, current) out <- tinytest(NA , call = sys.call(sys.parent(1)) , short = "wdir" , diff = msg ) env$pwd <- current out } report_files <- function(env){ if (!isTRUE(env$sidefx[['files']])) return(NULL) old <- env$files new <- file.info(dir(env$filesdir, recursive=TRUE, full.names=TRUE)) if ( identical(old, new) ) return(NULL) on.exit(env$files <- new) oldfiles <- rownames(old) newfiles <- rownames(new) created <- setdiff(newfiles, oldfiles) removed <- setdiff(oldfiles, newfiles) remain <- intersect(oldfiles, newfiles) touched <- remain[old[remain,'mtime'] != new[remain, 'mtime']] cre <- sprintf("Created: %s", if (length(created)>0) paste(created, collapse=", ") else character(0)) rem <- sprintf("Removed: %s", if (length(removed)>0) paste(removed, collapse=", ") else character(0)) alt <- sprintf("Touched: %s", if (length(touched)>0) paste(touched, collapse=", ") else character(0)) diff <- paste(c(cre, rem, alt), collapse="\n") # we do not record status changes, as they may mean different things # on different OSs. if (nchar(diff) == 0) return(NULL) tinytest(NA , call = sys.call(sys.parent(1)) , diff = diff , short = "file" , info = "CRAN policy forbids writing in the package installation folder." ) } tinytest/R/setup.R0000644000176200001440000001031513540176247013632 0ustar liggesusers #' Add tinytest to package source directory #' #' Creates \code{inst/tinytest}, and an example test file in that #' directory. Creates \code{tests/tinytest.R} so the package is #' tested with \code{R CMD check}. Adds \code{tinytests} as a suggested #' package to the \code{DESCRIPTION}. #' #' @param pkgdir \code{[character]} Package source directory #' @param force \code{[logical]} Toggle overwrite existing files? (not folders) #' @param verbose \code{[logical]} Toggle print progress #' #' @section Note on \code{DESCRIPTION}: #' #' Fails when it does not exist. It is assumed that the #' package is named in the \code{DESCRIPTION}. #' #' #' @examples #' \dontrun{ #' # an easy way to set up a package 'haha' that passes #' # R CMD check #' pkgKitten::kitten("haha") #' tinytest::setup_tinytest("haha") #'} #' #' @return \code{NULL}, invisibly. #' #' @export setup_tinytest <- function(pkgdir, force=FALSE, verbose=TRUE){ # local, verbosity-aware catf catf <- function(fmt, ...) if (verbose) cat(sprintf(fmt,...)) if (!dir.exists(pkgdir)){ stopf("%s does not exist or is not a directory", pkgdir) } # fields in DESCRIPTION that escape reformatting kw <- c("Title" , "Maintainer" , "Authors", "Authors@R" , "Description" , "Depends" , "Imports" , "Suggests" , "Enhances") ## Get pkg name form DESCRIPTION dfile <- file.path(pkgdir,"DESCRIPTION") if (file.exists(dfile)){ dcf <- read.dcf(dfile, keep.white=kw) pkgname <- dcf[, "Package"] } else { stopf("No DESCRIPTION file in %s",pkgdir) } ## Create pkgdir/tests testdir <- file.path(pkgdir,'tests') if ( !dir.exists(testdir) ){ catf("Creating %s\n", testdir) dir.create(testdir) } ## Write pkgdir/tests/tinytest.R testfile <- file.path(testdir,"tinytest.R") test_statement <- sprintf(' if ( requireNamespace("tinytest", quietly=TRUE) ){ tinytest::test_package("%s") } ', pkgname) if ( !file.exists(testfile) || force ){ catf("Creating %s\n", testfile ) write(test_statement, file = testfile) } ## Create inst/tinytest # (dir.create with recursive=TRUE does not always work # on the OS that we shall not name) instdir <- file.path(pkgdir, "inst") if (!dir.exists(instdir)){ catf("Creating %s\n", instdir) dir.create(instdir) } ttdir <- file.path(instdir,"tinytest") if (!dir.exists(ttdir)){ catf("Creating %s\n",ttdir) dir.create(ttdir) } ## Write example test file example_test <- ' # Placeholder with simple test expect_equal(1 + 1, 2) ' ttfile <- file.path(ttdir, sprintf("test_%s.R",pkgname)) if ( !file.exists(ttfile) || force ){ catf("Creating %s\n", ttfile) write(example_test, file=ttfile) } ## Add tinytest to DESCRIPTION file suggests <- if ("Suggests" %in% colnames(dcf)) dcf[1,"Suggests"] else NA if (!is.na(suggests) && !grepl("tinytest",suggests)){ catf("Adding 'tinytest' to DESCRIPTION/Suggests\n") dcf[1,"Suggests"] <- sprintf("%s, tinytest",suggests) write.dcf(dcf, dfile, keep.white=kw) } else if ( is.na(suggests) ) { catf("Adding 'Suggests: tinytest' to DESCRIPTION\n") dcf <- cbind(dcf, Suggests = "tinytest") write.dcf(dcf, dfile, keep.white=kw) } # If another test package is already present, perhaps the user # wants to take it out. other_test_package <- c("RUnit","testthat","unity","testit") suggested <- trimws(strsplit(dcf[1,"Suggests"], ",")[[1]]) if (any(other_test_package %in% suggested)){ pkgs <- paste(other_test_package[other_test_package %in% suggested], collapse=", ") catf("You may want to remove the following packages from DESCRIPTION/Suggests: %s\n", pkgs) } invisible(NULL) } #' The puppy for a pkgKitten #' #' Does exactly the same as \code{\link{setup_tinytest}}, but prints #' a loving message aferwards (and who doesn't want that!?). Just #' think about those puppies. #' #' @inheritParams setup_tinytest #' #' #' @keywords internal #' @export puppy <- function(pkgdir, force=FALSE, verbose=TRUE){ setup_tinytest(pkgdir=pkgdir, force=force, verbose=verbose) catf("\nThank you %s, for showing us some PUPPY LOVE <3\n",Sys.info()["user"]) catf(doggy) } doggy <- " ,-.___,-. \\_/_ _\\_/ )O_O( { (_) } W00F! `-^-' " tinytest/R/init.R0000644000176200001440000000032213500000356013411 0ustar liggesusers .onLoad <- function(libname, pkgname){ # turn off color printing for dumb terminals term <- tolower(trimws(Sys.getenv("TERM"))) if ( identical( term, "dumb" ) ){ options(tt.pr.color=FALSE) } } tinytest/MD50000644000176200001440000000743414071100132012442 0ustar liggesusers69699ce17a032e70333d1a39e88f6d2a *DESCRIPTION 1ba7de726b7f276798c8a7fe6b3ac9b0 *NAMESPACE 1e59f963cd019616460b4f23d0fecf96 *NEWS 017fdd0e07cde50f980e1714eaed3369 *R/expectations.R 50e7d02026bee5ac8906b17c7deda44d *R/init.R 388c3eb16d0dd6823c4d17b499133662 *R/methods.R 8021b11aa26ca42b3059c28f1718d99c *R/setup.R ef3ea59361ee5fed0e625f94a2782244 *R/tinytest.R 3c74905c2a2ea4cc3ba210203a22c316 *R/utils.R 422cee805e3d7fbe694b0dc3c1002dc9 *README.md 7d617179292dac3381fa2e5160dcfa07 *build/vignette.rds 4336e67b1348964ee4da8c198edf3cf9 *inst/CITATION c81f16a47de2fd228b008c5ed2a04e5b *inst/doc/tinytest_examples.R 37c710118bf1057c92d7c18f0fe75376 *inst/doc/tinytest_examples.Rnw c8eaf81c2471a74732fb2e12d59e8353 *inst/doc/tinytest_examples.pdf ce49245d8e5c5725de52fae68f685e5a *inst/doc/using_tinytest.R ae09471ef3996d7d37c9961380a99174 *inst/doc/using_tinytest.Rnw 948b27646000d570fc3175549febbbb2 *inst/doc/using_tinytest.pdf dcda01de96d8ce3554cada85cb637981 *inst/rstudio/addins.dcf 804b7fef290add675b038cbfc14c71b8 *inst/tinytest/programming.R abcdbcac0c19d4ef571b1957f218d53b *inst/tinytest/runs/test_cwd.R 0fa0632726cbcf95aa903bb51c897299 *inst/tinytest/runs/test_cwd2.R c9bca2dad4076d86402f4ea3058f2fd1 *inst/tinytest/runs/test_double_colon.R de279aea5113e0adfa6ce18d028f4de5 *inst/tinytest/runs/test_envvar.R 54bb53210a247363436e18d9ec743f41 *inst/tinytest/runs/test_envvar2.R ca0ed1ec5984d8cfbd791f6f4f2103e5 *inst/tinytest/runs/test_exit.R 814b73d0ec38e86bf7daee9887d57589 *inst/tinytest/runs/test_locale.R 9a4af4fc5fddafe6eb0f2169d36f0ed0 *inst/tinytest/runs/test_set_env.R 45cbd2a36488b882408595be6a15b70f *inst/tinytest/test_RUnit_style.R 1e22b0dd6f0b25bd4c4ef09646c3dc3b *inst/tinytest/test_call_wd.R 48ea9ab33a50742606afcb916581158b *inst/tinytest/test_env_A.R 48482c2682b5825157b37b63d7f6f02b *inst/tinytest/test_env_B.R 24d27c406f6097f3da278bb8c8237529 *inst/tinytest/test_extensibility.R 2ecbd9c221d07db4d4b5c3f2360bb1b2 *inst/tinytest/test_file.R f0e0979f3190f1d20507af7625c69dc0 *inst/tinytest/test_gh_issue_17.R f6a223d1b1d4892ffb11411d40bf7f0c *inst/tinytest/test_gh_issue_32.R 8a23aac6ae943a6a4cd6d6e3e37a2462 *inst/tinytest/test_gh_issue_51.R ed04b381b0985f59daf9eca76e3c83e7 *inst/tinytest/test_gh_issue_58.R 17354d0acfa181c3ddac84dac9eea780 *inst/tinytest/test_gh_issue_86.R b2050f6a8a6c1c222abd5b0878263084 *inst/tinytest/test_init.R 5ba4552d893a1bb24cb27b168b249e59 *inst/tinytest/test_tiny.R 87e5022004e0f244d4ac7ced675e692f *inst/tinytest/test_utils.R 3d582c8039e89e68e63786c986ac065f *inst/tinytest/women.csv 1f168031ef73faa1f161eb17ff126eb8 *man/at_home.Rd 22b3fdbf682a24f694d472372c992911 *man/build_install_test.Rd 185b8deaf43c68eb82ea05bcd6c9f161 *man/exit_file.Rd 065099361481f48cd3e095b4e5871b96 *man/expect_equal.Rd 138c34e2750feb4cc2e760c44c56acdb *man/expect_equal_to_reference.Rd 0b188c36cca4d3ad7b989c6895114b11 *man/get_call_wd.Rd 4b93c361110ca33cd77e28edb9d64274 *man/ignore.Rd 13dad6d18051767155068fd25820f278 *man/print.tinytest.Rd 5bdc486bde1eb9ee58d791a1b699857e *man/puppy.Rd 7871b9c93503fa632bef706b38d7e8b3 *man/register_tinytest_extension.Rd 3f49fd46c573f92b9f068f324cb24b2a *man/report_side_effects.Rd e62912b584b41fddf6cad8144066a106 *man/run_test_dir.Rd 3e2e38bbc8d4944c43f654f7e9d1f55a *man/run_test_file.Rd 1fb123baf551b15ce40b97c3172016d0 *man/setup_tinytest.Rd fd5d8dc500aff1f3bbabd0e4e4a3a06b *man/test_package.Rd 0e1c69d1248ea628ec1d2d1f75c5da6e *man/tinytest.Rd 6c6c066e3b6579af5c89c9b417540b3f *man/tinytests.Rd 961d39e10fba57618861db20671fef6c *man/using.Rd 49c954001384bac33f4dbcfaf35aa650 *tests/tinytest.R fe66ea1ee46d6e95c07c6de8f4961f63 *vignettes/test_addOne.R 283331cc9329d1e36976470087adfa25 *vignettes/test_hehe.R 1398bc6679832399a5e2a8b0666b590f *vignettes/test_se.R 37c710118bf1057c92d7c18f0fe75376 *vignettes/tinytest_examples.Rnw ae09471ef3996d7d37c9961380a99174 *vignettes/using_tinytest.Rnw tinytest/inst/0000755000176200001440000000000014071046217013114 5ustar liggesuserstinytest/inst/rstudio/0000755000176200001440000000000014050141245014577 5ustar liggesuserstinytest/inst/rstudio/addins.dcf0000644000176200001440000000016314050141046016516 0ustar liggesusersName: Run all package tests Description: Runs all tests in ./inst/description Binding: test_all Interactive: false tinytest/inst/doc/0000755000176200001440000000000014071046217013661 5ustar liggesuserstinytest/inst/doc/using_tinytest.Rnw0000644000176200001440000011103214063211027017431 0ustar liggesusers%\VignetteIndexEntry{Using tinytest} \documentclass[11pt]{article} \usepackage{enumitem} \usepackage{xcolor} % for color definitions \usepackage{sectsty} % to modify heading colors \usepackage{fancyhdr} \setlist{nosep} % simpler, but issue with your margin notes \usepackage[left=1cm,right=3cm, bottom=2cm, top=1cm]{geometry} %\usepackage{vmargin} %\setpapersize{USletter} % or a4 for you % Left Top Right Bottom headheight?? headsep footheight footsep %\setmarginsrb{0.75in}{0.25in}{1.1in}{0.25in}{15pt}{0pt}{10pt}{20pt} \usepackage{hyperref} \definecolor{bluetext}{RGB}{0,101,165} \definecolor{graytext}{RGB}{80,80,80} \hypersetup{ pdfborder={0 0 0} , colorlinks=true , urlcolor=blue , linkcolor=bluetext , linktoc=all , citecolor=blue } \sectionfont{\color{bluetext}} \subsectionfont{\color{bluetext}} \subsubsectionfont{\color{bluetext}} % no serif=better reading from screen. \renewcommand{\familydefault}{\sfdefault} % header and footers \pagestyle{fancy} \fancyhf{} % empty header and footer \renewcommand{\headrulewidth}{0pt} % remove line on top \rfoot{\color{bluetext} tinytest \Sexpr{packageVersion("tinytest")}} \lfoot{\color{black}\thepage} % side-effect of \color{}: lowers the printed text a little(?) \usepackage{fancyvrb} % custom commands make life easier. \newcommand{\code}[1]{\texttt{#1}} \newcommand{\pkg}[1]{\textbf{#1}} \let\oldmarginpar\marginpar \renewcommand{\marginpar}[1]{\oldmarginpar{\color{bluetext}\raggedleft\scriptsize #1}} % skip line at start of new paragraph \setlength{\parindent}{0pt} \setlength{\parskip}{1ex} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \title{Using tinytest} \author{Mark van der Loo} \date{\today{} | Package version \Sexpr{packageVersion("tinytest")}} \begin{document} \DefineVerbatimEnvironment{Sinput}{Verbatim}{fontshape=n,formatcom=\color{graytext}} \DefineVerbatimEnvironment{Soutput}{Verbatim}{fontshape=sl,formatcom=\color{graytext}} \newlength{\fancyvrbtopsep} \newlength{\fancyvrbpartopsep} \makeatletter \FV@AddToHook{\FV@ListParameterHook}{\topsep=\fancyvrbtopsep\partopsep=\fancyvrbpartopsep} \makeatother \setlength{\fancyvrbtopsep}{0pt} \setlength{\fancyvrbpartopsep}{0pt} \maketitle{} \thispagestyle{empty} \tableofcontents{} <>= options(prompt="R> ", continue = " ", width=75, tt.pr.color=FALSE) @ \subsection*{Reading guide} Readers of this document are expected to know how to write R functions and have a basic understanding of a package source directory structure. \newpage{} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Purpose of this package: unit testing} The purpose of \emph{unit testing} is to check whether a function gives the output you expect, when it is provided with certain input. So unit testing is all about comparing \emph{desired} outputs with \emph{realized} outputs. The purpose of this package is to facilitate writing, executing and analyzing unit tests. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Expressing tests} Suppose we define a function translating pounds (lbs) to kilograms inaccurately. <<>>= lbs2kg <- function(x){ if ( x < 0 ){ stop(sprintf("Expected nonnegative weight, got %g",x)) } x/2.20 } @ We like to check a few things before we trust it. <<>>= library(tinytest) expect_equal(lbs2kg(1), 1/2.2046) expect_error(lbs2kg(-3)) @ The value of an \code{expect\_*} function is a \code{logical}, with some attributes that record differences, if there are any. These attributes are used to pretty-print the results. <<>>= isTRUE( expect_true(2 == 1 + 1) ) @ \subsection{Test functions} Currently, the following expectations are implemented. \begin{center} \begin{tabular}{ll} \textbf{Function} & \textbf{what it expects}\\ \code{expect\_equal(current, target)} & equality (using \code{all.equal})\\ \code{expect\_equivalent(current, target)} & equality, ignoring attributes\\ \code{expect\_identical(current, target)} & equality, (using, \code{identical})\\ \code{expect\_true(current)} & \code{current} evaluates to \code{TRUE}\\ \code{expect\_false(current)} & \code{current} evaluates to \code{FALSE}\\ \code{expect\_inherits(current, class)} & \code{current} inherits from \code{class}\\ \code{expect\_null(current)} & \code{current} evaluates to \code{NULL}\\ \code{expect\_error(current, pattern)} & error message matching \code{pattern}\\ \code{expect\_warning(current, pattern)} & warning message matching \code{pattern}\\ \code{expect\_message(current, pattern)} & message matching \code{pattern}\\ \code{expect\_silent(current, pattern)} & expect no warnings or errors (just run)\\ \end{tabular} \end{center} Here, \code{target} is the intended outcome and \code{current} is the observed outcome. Also, \code{pattern} is interpreted as a regular expression. <<>>= expect_error(lbs2kg(-3), pattern="nonnegative") expect_error(lbs2kg(-3), pattern="foo") @ \subsection{Alternative syntax} The syntax of the test functions should be familiar to users of the \code{testthat} package\cite{wickham2016testthat}. In test files only, you can use equivalent functions in the style of \code{RUnit}\cite{burger2016RUnit}. To be precise, for each function of the form \code{expect\_lol} there is a function of the form \code{checkLol}. \subsection{Interpreting the output and print options} Let's have a look at an example again. <<>>= expect_false( 1 + 1 == 2, info="My personal message to the tester" ) @ The output of these functions is pretty self-explanatory, nevertheless we see that the output of these expect-functions consist of \begin{itemize} \item The result: \code{FAILED}, \code{PASSED} or \code{SIDEFX}. The latter only occurs when side effects are monitored (see \S\ref{sect:side}) \item The type of failure (if any) between square brackets. Current options are as follows. \begin{itemize} \item \code{[data]} there are differences between observed and expected values. \item \code{[attr]} there are differences between observed and expected attributes, such as column names. \item \code{[xcpt]} an exception (warning, error) was expected but not observed. \end{itemize} When side effects are monitored, and the result is \code{SIDEFX}, a side effect was observed. The type of side effect is reported between square brackets. \begin{itemize} \item \code{[envv]} An environmental variable was created, changed, or deleted. \item \code{[wdir]} The working directory has changed. \item \code{[file]} A file operation occurred in the test directory or one of its subdirectories. \end{itemize} \item When relevant (see \S\ref{sect:testfiles}), the location of the test file and the relevant line numbers. \item The test call. \item When relevant, a summary of the differences between observed and expected values or attributes, or a summary of the observed side effect. \item When present, a user-defined information message. \end{itemize} The result of an \code{expect\_} function is a \code{tinytest} object. You can print them in long format (default) or in short, one-line format like so. <<>>= print(expect_equal(1+1, 3), type="short") @ \marginpar{\code{print} method} Functions that run multiple tests return an object of class \code{tinytests} (notice the plural). Since there may be a lot of test results, \pkg{tinytest} tries to be smart about printing them. The user has ultimate control over this behaviour. See \code{?print.tinytests} for a full specification of the options. \section{Test files} \label{sect:testfiles} In \pkg{tinytest}, tests are scripts, interspersed with statements that perform checks. An example test file in tinytest can look like this. \begin{verbatim} # contents of test_addOne.R addOne <- function(x) x + 2 expect_true(addOne(0) > 0) hihi <- 1 expect_equal(addOne(hihi), 2) \end{verbatim} A particular file can be run using\marginpar{\code{run\_test\_file}} <>= run_test_file("test_addOne.R", verbose=0) @ We use \code{verbose=0} to avoid cluttering the output in this vignette. By default, verbosity is turned on, and a counter is shown while tests are run. The counter is colorized on terminals supporting ANSI color extensions. If you are uncomfortable reading these colors or prefer colorless output, use \code{color=FALSE} or set \code{options(tt.pr.color=FALSE)}. The numbers between \code{<-->} indicate at what lines in the file the failing test can be found. By default only failing tests are printed. You can store the output and print all of them. <<>>= test_results <- run_test_file("test_addOne.R", verbose=0) print(test_results, passes=TRUE) @ Or you can set <>= options(tt.pr.passes=TRUE) @ to print all results during the active R session. To run all test files in a certain directory, we can use\marginpar{\code{run\_test\_dir}} <>= run_test_dir("/path/to/your/test/directory") @ By default, this will run all files of which the name starts with \code{test\_}, but this is customizable. \subsection{Summarizing test results, getting the data} To create some results, run the tests in this package. <<>>= out <- run_test_dir(system.file("tinytest", package="tinytest") , verbose=0) @ The results can be turned into data using \code{as.data.frame}. \marginpar{\code{as.data.frame}} <<>>= head(as.data.frame(out), 3) @ The last two columns indicate the line numbers where the test was defined. A `summary` of the output gives a table with passes and fails per file. \marginpar{\code{summary}} <<>>= summary(out) @ \subsection{Programming over tests, ignoring test results, exiting early} Test scripts are just R scripts interspersed with tests. The test runners make sure that all test results are caught, unless you tell them not to. For example, since the result of a test is a \code{logical} you can use them as a condition. <>= if ( expect_equal(1 + 1, 2) ){ expect_true( 2 > 0) } @ Here, the second test (\code{expect\_true(2 > 0)}) is only executed if the first test results in \code{TRUE}. In any case the result of the first test will be caught in the test output, when this is run with \code{run\_test\_file} \code{run\_test\_dir}, \code{test\_all}, \code{build\_install\_test} or through \code{R CMD check} using \code{test\_package}. If you want to perform the test, but not record the test result you can do the following \marginpar{\code{ignore}} (note the placement of the brackets). <>= if ( ignore(expect_equal)(1+1, 2) ){ expect_true(2>0) } @ Other cases where this may be useful is to perform tests in a loop, e.g. when there is a systematic set of cases to test. It is possible to exit a test file prematurely. For example when there are a number of tests that are not relevant or possible on some OS, you can do the following. \marginpar{\code{exit\_file}} <<>>= if ( Sys.info()[['sysname']] == "Windows"){ exit_file("Cannot test this on Windows") } @ This will cause \code{run\_test\_file} to stop file execution, print the message, and report the information gathered up to where \code{exit} was called. A function like \code{test\_all} will then continue with the next file, so testing is not aborted completely. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Running order and side effects} \label{sect:running} It is a generally a good idea to write test files that are independent from each other. This means that the order of running them is unimportant for the test results and test files can be maintained independently. The function \code{run\_test\_file} and by extension \code{run\_test\_dir}, \code{test\_all}, and \code{test\_package} encourage this by resetting \begin{itemize} \item options, set with \code{options()}; \item environment variables, set with \code{Sys.setenv()} \end{itemize} after a test file is executed. To escape this behavior, use \code{base::Sys.setenv()} respectively \code{base::options()}. Alternatively use <>= run_test_dir("/path/to/my/testdir" , remove_side_effects = FALSE) test_all("/path/to/my/testdir" , remove_side_effects = FALSE) # Only in tests/tinytest.R: test_package("PACKAGENAME", remove_side_effects=FALSE) @ Test files are sorted and run based on the current locale. This means that the order of execution is in general not platform-independent. You can control the sorting behavior interactively or by setting \code{options(tt.collate)}. To be precise, adding <>= options(tt.collate="C") @ to \code{/tests/tinytest.R} before running \code{test\_package} will ensure bytewise sorting on most systems. See also \code{help("run\_test\_dir")}. \subsection{Monitoring side effects} \label{sect:side} The term 'side effect' is the technical expression for the situation where a function or expression changes something outside of its scope. Examples include creating, removing, or changing variables in R's global work space, R options, or environment variables of your operating system. We will call such variables or options \emph{external variables}. To test for side-effects once, use the \code{side\_effects} argument to any of the test runners. For example <>= test_package("pkg", side_effects=TRUE) @ There is control over which side-effects to track. For example to prevent tracking changes in the working directory, do the following. <>= test_package("pkg", side_effects=list(pwd=FALSE)) @ If you add \code{report\_side\_effects()} anywhere in a test file, certain external variables are monitored from that point on, and for that file only. It can be switched off again by calling \code{report\_side\_effects(FALSE)} anywhere in the file. The reporting functionality will compare the external state before and after every expression in the test file is run and report any changes. At the moment, effects that can be monitored include environment variables, locale settings, the present working directory, and file operations in the test directory. Below is an example of a test file where side effects are recorded. The third line creates an explicit side effect by creating a new environment variable called \code{hihi} with the value \code{"lol"}. \begin{verbatim} # contents of test_se.R report_side_effects() expect_equal(1+1, 2) Sys.setenv(hihi="lol") expect_equal(1+1, 3) Sys.setenv(hihi="lulz ftw") \end{verbatim} Running the test file yields an object of class \code{tinytests} as usual, only now changes in environment variables are reported. <<>>= run_test_file("test_se.R", verbose=1) @ Note that as discussed in Section~\S\ref{sect:running}, \pkg{tinytest} will unset the environment variable \code{hihi} automatically after running the file because it was set directly by the author of the test file using \code{Sys.setenv}. The real value of the reporting functionality is that it also reports on external variables that are touched by other functions than those you call explicitly in the file. Reading and comparing versions of external variables takes some time. Especially when it requires a call to the operating service such as a request for values of environment variables. We therefore recommend this to be used only when you suspect a side effect. Or, for example to execute \code{report\_side\_effects()} conditional on \code{at\_home()}. It is not possible to catch all types of side effects, even in principle, using the \pkg{tinytest} reporting functionality. Examples include: packages that keep a global variable or environment within their namespace to store some state, and packages that rely on compiled code where there are global objects within the shared object. Side effects are to be avoided as a general and strong principle, but sometimes there is little or no choice. In Section~\ref{sect:devil} we give some tips on how to properly handle such situations. \section{Testing packages} Using \pkg{tinytest} for your package is pretty easy. \begin{enumerate} \item Testfiles are placed in \code{/inst/tinytest}. The testfiles all have names starting with \code{test} (for example \code{test\_haha.R}). \item In the file \code{/tests/tinytest.R} you place the code \begin{verbatim} if ( requireNamespace("tinytest", quietly=TRUE) ){ tinytest::test_package("PACKAGENAME") } \end{verbatim} \item In your \code{DESCRIPTION} file, add \pkg{tinytest} to \code{Suggests:}. \end{enumerate} You can automatically create a minimal running test infrastructure with the \code{setup\_tinytest} function. \marginpar{\code{setup\_tinytest}} <>= setup_tinytest("/path/to/your/package") @ In a terminal, you can now do \begin{verbatim} R CMD build /path/to/your/package R CMD check PACKAGENAME_X.Y.Z.tar.gz \end{verbatim} and all tests will run. To run all the tests interactively, make sure that all functions of your new package are loaded. After that, run\marginpar{\code{test\_all}} <>= test_all("/path/to/your/package") @ where the default package directory is the current working directory. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Build--install--test interactively} The most realistic way to unit-test your package is to build it, install it and then run all the tests. The function <>= build_install_test("/path/to/your/package") @ does exactly that. It builds and installs the package in a temporary directory, starts a fresh R session, loads the newly installed package and runs all tests. The return value is a \code{tinytests} object. The package is built without manual or vignettes to speed up the whole process. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Testing functions that are not exported: use `:::`} In Section~\ref{sect:spherical} it is argued that unit tests should as a rule of thumb focus on the functions that are visible to the user. However, there are cases where it may be preferred to test an internal function. For example when there are two user-visible functions that call the same underlying, unexported function with different arguments. Or when one of the internal functions implements a numerical algorithm that requires thorough testing. To test functions in your package that are not visible to users that load your package, use the triple-colon operator like so. <>= output = pkg:::some_internal_function(1) expect_equal(output, 2) @ This is perfectly ok, and is also accepted by \code{R CMD check --as-cran}. \pkg{Tinytest} does not make those internal functions directly callable like some other unit testing packages do. Making internal functions callable means that \begin{enumerate} \item \pkg{tinytest} needs to simulate loading a package, except for the namespace restrictions; \item there may be significant differences between the environment in which you test the functions, and the environment which a user sees when loading the package; \item the way the package is loaded during testing may differ from the way it is loaded when a user loads it; \item exported functions are not clearly distinguished from internal functions in the test code. \end{enumerate} accurately simulating how R loads a package is no small matter. It would require a significant epansion of \code{tinytest}'s code base that would have to be kept synchronised with the way R loads packages (possibly with backport options when R would change in that area). So in short: let's keep things simple and let R do what it knows how to do. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Using data stored in files} \label{sect:comparefiles} When your package is tested with \code{test\_package}, \pkg{tinytest} ensures that your working directory is the testing directory (by default \code{tinytest}). This means you can read files that are stored in your folder directly. Suppose that your package directory structure looks like this (default): \begin{itemize} \item[]\code{/inst} \begin{itemize} \item[]\code{/tinytest} \begin{itemize} \item[]\code{/test.R} \item[]\code{/women.csv} \end{itemize} \end{itemize} \end{itemize} Then, to check whether the contents of \code{women.csv} is equal to the built-in \code{women} dataset, the content of \code{test.R} looks as follows. <>= dat <- read.csv("women.csv") expect_equal(dat, women) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Skipping tests on \code{CRAN}} It is not possible to detect whether a test is running on CRAN. This means we are forced to detect that we are running tests in our own environment. In the following example we use the host name to detect if we are running on our own machine and explicitly pass this information to \code{test\_package}. <>= options(prompt=" ", continue=" ") @ <>= # contents of pkgdir/tests/tinytest.R if ( requireNamespace("tinytest", quietly=TRUE) ){ home <- identical( Sys.info()["nodename"], "YOURHOSTNAME" ) tinytest::test_package("PKGNAME", at_home = home) } @ Other ways to detect whether you are running `at home' include \begin{itemize} \item Set a custom environment variable (from your OS) and detect it with \code{Sys.getenv}. <<>>= home <- identical( Sys.getenv("HONEYIMHOME"), "TRUE" ) @ \item Use 4-number package versioning while developing and 3-number versioning for CRAN releases\footnote{As \href{https://stackoverflow.com/questions/36166288/skip-tests-on-cran-but-run-locally}{recommended here} by Dirk Eddelbuettel.}. <>= home <- length(unclass(packageVersion("PKGNAME"))[[1]]) == 4 @ \end{itemize} <>= options(prompt="R> ", continue=" ") @ \subsubsection*{When tests are run interactively} All the interactive test runners have \code{at\_home=TRUE} by default, so while you are developing all tests are run, unless you exclude them explicitly. <<>>= run_test_file("test_hehe.R", verbose=0) run_test_file("test_hehe.R", verbose=0, at_home=FALSE) @ Here is an overview of test runners and their default setting for \code{at\_home}. \begin{center} \begin{tabular}{rll} Function & Default \code{at\_home} & Intended use \\ \hline \code{run\_test\_file} & \code{TRUE} & Interactive by developer\\ \code{run\_test\_dir} & \code{TRUE} & Interactive by developer\\ \code{test\_all} & \code{TRUE} & Interactive by developer\\ \code{build\_install\_test} & \code{TRUE} & Interactive by developer\\ \code{test\_package} & \code{FALSE} & \code{R CMD check}, or after installation by user. \end{tabular} \end{center} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Testing your package after installation} Supposing your package is called \pkg{hehe} and the \pkg{tinytest} infrastructure is used. If the package is installed, the following command runs \pkg{hehe}'s tests. <>= tinytest::test_package("hehe") @ This can come in handy when a user of \pkg{hehe} reports a bug and you want to make sure all tested functionality works on the user's system. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Using extension packages} It is possible for other packages to add custom assertions (\code{expect}-functions). To use such a package: \begin{enumerate} \item Add the extension package to the \code{Suggests:} field in the \code{DESCRIPTION} file. \item Add \code{using(pkg)} to \emph{each} test file that use the extensions (see \code{?using}). \marginpar{\code{using}} \end{enumerate} When multiple extension packages are loaded, and when there are name collisions, the packages loaded later takes precedence over the ones loaded earlier (as usual in R). This includes assertions exported by \pkg{tinytest}. \textbf{Note.} Other than in regular R, it is not possible to disambiguate functions using namespace resolution as in \code{pkg::expect\_something}, because in that case the test result will not be caught by \pkg{tinytest}. The API for building extension packages is described in \code{?register\_tinytest\_extension}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Mocking databases} The \pkg{dittodb} package\cite{keane2020dittodb} is capable of mocking pre-recorded database requests. To use extensions like \code{dittodb}, put the package in \code{Suggests:} in the \code{DESCRIPTION} and load it in the test file. The package captures responses from SQL connections and saves them to R files. Therefore, capture the requested response prior to testing by wrapping your request in \code{start\_db\_capturing()} and \code{stop\_db\_capturing()}. Optionally, specify the file path you want your mocks to be saved. For examples using \code{dittodb}, see \href{https://dittodb.jonkeane.com/}{here}. In the testing scripts, load the package and wrap your tests in \code{with\_mock\_path(path, with\_mock\_db())} like <>= require(dittodb) with_mock_path( system.file("", package = "myPackage"), with_mock_db({ # }) ) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Testing with environmental variables} \label{sect:envvar} In \pkg{tinytest} you can run tests with custom environment variable settings easily. Just add the \code{set\_env} argument to any of \code{run\_test\_file()}, \code{run\_test\_dir()} or \code{test\_package()}. Here is an example. <>= test_package("tinytest", set_env = list(WA_BABALOOBA="BA_LA_BAMBOO")) @ With this option, the environment variable will be set during testing and unset afterwards. Setting and unsetting environment variables like this will not be recorded as a side effect. If there is code in the test file that changes this variable, then it is recorded. Do note that R uses some environment variables that are read during startup, such as \code{\_R\_OPTIONS\_STRINGS\_AS\_FACTORS\_}. Setting these at runtime has no effect. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Running tests in parallel} In \pkg{tinytest}, a file should be considered a closed unit: no information created in one test file should be used in another. Under this condition, tests can automatically run in parallel by running different files in different R sessions. Running code in parallel takes some careful consideration around setting up a cluster, running the tests, and closing the cluster of preparing it for the next run. Depending on the test runner used, there are different levels of control and responsibility for the user to prepare the program for parallelization. Below we describe them from less easier to more control. \subsubsection*{\code{build\_install\_test}} This function creates and installs a package in a temporary location. By setting the \code{ncpu} parameter, the number of cores used at the testing phase can be increased. <>= build_install_test("/path/to/your/package", ncpu=2) @ We already mentioned that the order in which files are run is in principle system-dependent and it is a good practice not to rely on it. Under parallel situations, all bets on file order are off. \subsubsection*{\code{test\_package}.} This function assumes that a package is installed. It can gather any information necessary to parallelize a test run. The simplest way to parallelize is to specify the number of CPUs used. <>= test_package("PACKAGENAME", ncpu=2) @ Here, \code{test\_package} will \begin{enumerate} \item Set up a local cluster using \code{parallel::makeCluster}. \item Load the package on each R instance of the cluster. \item Run test files in parallel over the cluster. \item Collect the results and close the cluster. \end{enumerate} In stead of just passing the number of CPUs it is possible to pass a \code{cluster} object. In that case \code{test\_package} will still load the package on each node. However, note that if the package gets updated and reinstalled, it should also be reloaded. It is in general hard to completely unload a package in R (see \code{?detach} and \code{?unloadNamespace} for some details on artifacts that will not be removed). So our advice is to restart a cluster for each test run. \subsubsection*{\code{run\_test\_dir}, \code{test\_all}} These function assumes that all functionality needed to run the tests is loaded. They accept an object of type \code{cluster}. The user is responsible for setting up the nodes. <>= cl <- parallel::makeCluster(4, outfile="") parallel::clusterCall(cl, source, "R/myfunctions.R") run_test_dir("inst/tinytest", cluster=cl) @ where the argument \code{outfile=""} ensures that messages from each node are forwarded to the master node. It is possible to keep the cluster `alive', so modifications can be made to \code{"R/myfunctions.R"} and then run for example the following. <>= parallel::clusterCall(cl, source, "R/myfunctions.R") test_all(cluster=cl) stopCluster(cl) @ For heavy test routines it is thus possible to keep a test cluster up to offload computations. For more complex situations, including packages that use \code{S4} classes, or compiled code, (re)loading takes more effort than sourcing a few R files. In this cases it is often easier to restart a clean cluster for each test round. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{A few tips on packages and unit testing} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Make your package spherical} \label{sect:spherical} Larger packages typically consist of functions that are visible to the users (exported functions) and a number of functions that are only used by the exported functions. For example: <<>>= # exported, user-visible function inch2cm <- function(x){ x*conversion_factor("inch") } # not exported function, package-internal conversion_factor <- function(unit){ confac <- c(inch=2.54, pound=1/2.2056) confac[unit] } @ We can think of the exported functions as the \emph{surface} of the package and all the other functions as the \emph{volume}. The surface is what a user sees, the volume is what the developer sees. The surface is how a user interacts with a package. If the surface is small (few functions exported), users are limited in the ways they can interact with your package and that means there is less to test. So as a rule of thumb, it is a good idea to keep the surface small. Since a sphere has the smallest surface-to-volume ratio possible, I refer to this rule as \emph{keep your package spherical}. By the way, the technical term for the surface of a package is API (application program interface). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Test the surface, not the volume} Unexpected behavior (a bug) is often discovered when someone who is not the developer starts using code. Bugfixing implies altering code and it may even require you to refactor large chunks of code that is internal to a package. If you defined extensive tests on non-exported functions, this means you need to rewrite the tests as well. As a rule of thumb, it is a good idea to test only the behaviour at the surface, so as a developer you have more freedom to change the internals. This includes rewriting and renaming internal functions completely. By the way, it is bad practice to change the surface, since that means you are going to break other people's code. Nobody likes to program against an API that changes frequently, and everybody hates to program against an API that changes unexpectedly. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{How many tests do I need?} When you call a function, you can think of its arguments flowing through a certain path from input to output. As an example, let's take a look again at a new, slightly safer unit conversion function. <<>>= pound2kg <- function(x){ stopifnot( is.numeric(x) ) if ( any(x < 0) ){ warning("Found negative input, converting to positive") x <- abs(x) } x/2.2046 } @ If we call \code{lbs2kg} with argument \code{2}, we can write: \begin{verbatim} 2 -> /2.2046 -> output \end{verbatim} If we call \code{lbs2kg} with argument \code{-3} we can write \begin{verbatim} -3 -> abs() -> /2.2046 -> output \end{verbatim} Finally, if we call \code{pound2kg} with \code{"foo"} we can write \begin{verbatim} "foo" -> stop() -> Exception \end{verbatim} So we have three possible paths. In fact, we see that every nonnegative number will follow the first path, every negative number will follow the second path and anything nonnumeric follows the third path. So the following test suite fully tests the behaviour of our function. <>= expect_equal(pound2kg(1), 1/2.2046 ) # test for expected warning, store output expect_warning( out <- pound2kg(-1) ) # test the output expect_equal( out, 1/2.2046) expect_error(pound2kg("foo")) @ The number of paths of a function is called its \emph{cyclomatic complexity}. For larger functions, with multiple arguments, the number of paths typically grows extremely fast, and it quickly becomes impossible to define a test for each and every one of them. If you want to get an impression of how many tests one of your functions in needs in principle, you can have a look at the \pkg{cyclocomp} package of G\'abor Cs\'ardi\cite{csardi2016cyclocomp}. Since full path coverage is out of range in most cases, developers often strive for something simpler, namely \emph{full code coverage}. This simply means that each line of code is run in at least one test. Full code coverage is no guarantee for bugfree code. Besides code coverage it is therefore a good idea to think about the various ways a user might use your code and include tests for that. To measure code coverage, I recommend using the \pkg{covr} package by Jim Hester\cite{hester2018covr}. Since \pkg{covr} is independent of the tools or packages used for testing, it also works fine with \pkg{tinytest}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{It's not a bug, it's a test!} If users of your code are friendly enough to submit a bug report when they find one, it is a good idea to start by writing a small test that reproduces the error and add that to your test suite. That way, whenever you work on your code, you can be sure to be alarmed when a bug reappears. Tests that represent earlier bugs are sometimes called \emph{regression tests}. If a bug reappears during development, software engineers sometimes refer to this as a \emph{regression}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Side effects are the Devil} \label{sect:devil} Since side-effects manipulate variables outside of the scope of a function, or even outside of R, they can cause bugs that are hard to reproduce. R offers a mechanism to ensure that a function leaves the outside world as it was, once your code stops running. Suppose you need to change working directory within a function, \code{source} a file and return to the working directory. A naive way to do this is like so. \marginpar{\code{on.exit}} <<>>= bad_function <- function(file){ oldwd <- getwd() setwd(dirname(file)) source(basename(file)) setwd(oldwd) } @ This all works fine, untill \code{file} contains faulty code and throws an error. As result, the execution of \code{bad\_function} will stop and leave the user in a changed working directory. With \code{on.exit} you can define code that will be carried out before the function exits, either normally or with an error. <<>>= good_function <- function(file){ oldwd <- getwd() on.exit(setwd(oldwd)) setwd(dirname(file)) source(basename(file)) } @ \newpage \begin{thebibliography}{5} \bibitem{wickham2016testthat} \href{https://cran.r-project.org/package=testthat}{Unit Testing for R} Hadley Wickham (2016). testthat: Get Started with Testing. The R Journal, vol. 3, no. 1, pp. 5--10, 2011 \bibitem{burger2016RUnit} Matthias Burger, Klaus Juenemann and Thomas Koenig (2018). \href{https://CRAN.R-project.org/package=RUnit}{RUnit: R Unit Test Framework} R package version 0.4.32. \bibitem{csardi2016cyclocomp} \href{https://cran.r-project.org/package=cyclocomp}{cyclocomp: cyclomatic complexity of R code} G\'abor Cs\'ardi (2016). R package version 1.1.0 \bibitem{hester2018covr} \href{https://CRAN.R-project.org/package=covr}{covr: Test Coverage for Packages} Jim Hester (2018). R package version 3.2.1 \bibitem{keane2020dittodb} \href{https://CRAN.R-project.org/package=dittodb}{dittodb: A Test Environment for Database Requests} Jonathan Keane and Mauricio Vargas (2020). R package version 0.1.1 \end{thebibliography} \end{document} tinytest/inst/doc/tinytest_examples.R0000644000176200001440000002775414071046215017602 0ustar liggesusers### R code from vignette source 'tinytest_examples.Rnw' ################################################### ### code chunk number 1: tinytest_examples.Rnw:76-81 ################################################### options(prompt=" ", continue = " ", width=75, tt.pr.color=FALSE) library(tinytest) ################################################### ### code chunk number 2: tinytest_examples.Rnw:96-97 (eval = FALSE) ################################################### ## ## [this is an extra comment, only for this vignette] ################################################### ### code chunk number 3: tinytest_examples.Rnw:119-122 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 4: tinytest_examples.Rnw:124-126 ################################################### expect_equal(1,1) expect_equal(1, c(x=1)) ################################################### ### code chunk number 5: tinytest_examples.Rnw:129-132 ################################################### 0.9-0.7-0.2 expect_equal(0.9-0.7-0.2,0) expect_equal(0.9-0.7-0.2,0, tolerance=0) ################################################### ### code chunk number 6: tinytest_examples.Rnw:134-137 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 7: tinytest_examples.Rnw:146-147 (eval = FALSE) ################################################### ## expect_equal(stringdist("ab", "ba", method="osa"), 1) ################################################### ### code chunk number 8: tinytest_examples.Rnw:152-162 (eval = FALSE) ################################################### ## b <- benchr::benchmark(1 + 1, 2 + 2) ## m <- mean(b) ## ## expect_equal(class(m), c("summaryBenchmark", "data.frame")) ## expect_equal(dim(m), c(2L, 7L)) ## expect_equal(names(m), c("expr", "n.eval", "mean", "trimmed", "lw.ci", "up.ci", "relative")) ## ## expect_equal(class(m$expr), "factor") ## expect_equal(levels(m$expr), c("1 + 1", "2 + 2")) ## expect_true(all(sapply(m[-1], is.numeric))) ################################################### ### code chunk number 9: tinytest_examples.Rnw:171-174 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 10: tinytest_examples.Rnw:176-178 ################################################### expect_equivalent(1,1) expect_equivalent(1, c(x=1)) ################################################### ### code chunk number 11: tinytest_examples.Rnw:180-183 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 12: tinytest_examples.Rnw:192-195 (eval = FALSE) ################################################### ## v <- validator(x > 0) ## d <- data.frame(x=c(1,-1,NA)) ## expect_equivalent(values(confront(d,v)), matrix(c(TRUE,FALSE,NA)) ) ################################################### ### code chunk number 13: tinytest_examples.Rnw:202-205 (eval = FALSE) ################################################### ## refD <- as.Date("2016-01-01")+0:2 ## ## expect_equivalent(refD, anydate(20160101L + 0:2)) ################################################### ### code chunk number 14: tinytest_examples.Rnw:216-219 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 15: tinytest_examples.Rnw:221-229 ################################################### La <- list(x=1); Lb <- list(x=1) expect_identical(La, Lb) a <- new.env() a$x <- 1 b <- new.env() b$x <- 1 expect_identical(a,b) ################################################### ### code chunk number 16: tinytest_examples.Rnw:235-237 ################################################### print(a) print(b) ################################################### ### code chunk number 17: tinytest_examples.Rnw:242-245 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 18: tinytest_examples.Rnw:253-256 (eval = FALSE) ################################################### ## a <- c(k1 = "aap",k2="noot") ## expect_identical(stringdistmatrix(a,useNames="none") ## , stringdistmatrix(a,useNames=FALSE)) ################################################### ### code chunk number 19: tinytest_examples.Rnw:263-271 (eval = FALSE) ################################################### ## list( ## ## [long list of results removed for brevity] ## ) -> results ## fils <- list.files(system.file("extdat", package="wand"), full.names=TRUE) ## tst <- lapply(fils, get_content_type) ## names(tst) <- basename(fils) ## ## for(n in names(tst)) expect_identical(results[[n]], tst[[n]]) ################################################### ### code chunk number 20: tinytest_examples.Rnw:280-283 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 21: tinytest_examples.Rnw:285-287 ################################################### expect_null(iris$hihi) expect_null(iris$Species) ################################################### ### code chunk number 22: tinytest_examples.Rnw:289-292 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 23: tinytest_examples.Rnw:302-305 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 24: tinytest_examples.Rnw:307-309 ################################################### expect_true(1 == 1) expect_false(1 == 2) ################################################### ### code chunk number 25: tinytest_examples.Rnw:311-314 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 26: tinytest_examples.Rnw:320-326 (eval = FALSE) ################################################### ## ## Datetime: factor and ordered (#44) ## refD <- as.Date("2016-09-01") ## expect_true(refD == anydate(as.factor("2016-09-01"))) ## expect_true(refD == anydate(as.ordered("2016-09-01"))) ## expect_true(refD == utcdate(as.factor("2016-09-01"))) ## expect_true(refD == utcdate(as.ordered("2016-09-01"))) ################################################### ### code chunk number 27: tinytest_examples.Rnw:332-333 (eval = FALSE) ################################################### ## expect_equal(anydate(as.factor("2016-09-01")), refD) ################################################### ### code chunk number 28: tinytest_examples.Rnw:340-342 (eval = FALSE) ################################################### ## x <- ULIDgenerate(20) ## expect_true(is.character(x)) ################################################### ### code chunk number 29: tinytest_examples.Rnw:352-355 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 30: tinytest_examples.Rnw:357-361 ################################################### expect_message(message("hihi")) expect_message(message("hihi"), pattern = "hi") expect_message(message("hihi"), pattern= "ha") expect_message(print("hihi")) ################################################### ### code chunk number 31: tinytest_examples.Rnw:363-366 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 32: tinytest_examples.Rnw:375-378 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 33: tinytest_examples.Rnw:380-384 ################################################### expect_warning(warning("hihi")) expect_warning(warning("hihi"), pattern = "hi") expect_warning(warning("hihi"), pattern= "ha") expect_warning(1+1) ################################################### ### code chunk number 34: tinytest_examples.Rnw:386-389 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 35: tinytest_examples.Rnw:399-402 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 36: tinytest_examples.Rnw:404-408 ################################################### expect_error(stop("hihi")) expect_error(stop("hihi"), pattern = "hi") expect_error(stop("hihi"), pattern= "ha") expect_error(print("hoho")) ################################################### ### code chunk number 37: tinytest_examples.Rnw:410-413 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 38: tinytest_examples.Rnw:421-425 (eval = FALSE) ################################################### ## # Check that log and centering cannot be combined ## expect_error( ## centscaleSpectra2D(tiny, center = TRUE, scale = "log"), ## "Cannot take log of centered data") ################################################### ### code chunk number 39: tinytest_examples.Rnw:436-439 ################################################### options(prompt="R> ", continue = " ", width=75) ################################################### ### code chunk number 40: tinytest_examples.Rnw:441-443 ################################################### expect_silent(print(10)) expect_silent(stop("haha")) ################################################### ### code chunk number 41: tinytest_examples.Rnw:445-448 ################################################### options(prompt=" ", continue = " ", width=75) ################################################### ### code chunk number 42: tinytest_examples.Rnw:456-461 (eval = FALSE) ################################################### ## data <- data.frame(A = 1) ## rule <- validator(A > 0) ## cf <- confront(data, rule) ## expect_silent(plot(rule)) ## expect_silent(plot(cf)) ################################################### ### code chunk number 43: tinytest_examples.Rnw:469-473 (eval = FALSE) ################################################### ## run("runs/multiple_loggers.R") ## simple_ok <- expect_true(file.exists("runs/simple_log.csv")) ## expect_silent(read.csv("runs/simple_log.csv")) ## if (simple_ok) unlink("runs/simple_log.csv") ################################################### ### code chunk number 44: tinytest_examples.Rnw:488-489 (eval = FALSE) ################################################### ## ignore(expect_equal)(1+1, 2) ################################################### ### code chunk number 45: tinytest_examples.Rnw:495-509 (eval = FALSE) ################################################### ## mantissa <- gsub(" [0-9]*$", "", x.hex) ## ignore(expect_true)(all( ## sapply( ## head(seq_along(mantissa), -1), ## function(i){ ## all( ## grepl( ## paste0("^", mantissa[i], ".*"), ## tail(mantissa, -i) ## ) ## ) ## } ## ) ## )) tinytest/inst/doc/using_tinytest.pdf0000644000176200001440000064733714071046216017467 0ustar liggesusers%PDF-1.5 % 161 0 obj << /Length 1551 /Filter /FlateDecode >> stream xZ[sF~P"f"eI:IL$)LeK l7 o Ǝ̓jYힳs>[oG (dT ;'a0/"qg#vN_BsWpFW%(%9yH.CB06ȟB fd_>\D?qΩif U^ D(9TJ3'4_W462)FB^ D6@!J0Bb/؏ M,/5f IBq)v I}X#~1+e`Hҟ}0{ D| ϴhnIB6y Ȫ1; rO53K%{m7 N֠)R- ^@6  (avbBE찒8IRIB;=MD5QFg7NOC|Y@9B^M"z.:DKcK?q67+/O>vX`5Ef4n;ܠExT.p˝nꀪ+a͘r6En'TgP1vܫu)Nkެ8jAGc7wo0+o:}tEK6`Ƈ9IlYYBsZ 68?wAŨXzf=}AD\{{.xI]^irfDnLD䠪},ng%J}tcY`!NxH˃8K5#fFb˔Ŕ@[HĦ>Dy 9[ ӌcc,=Y `-A&IJL]Zsܳy92Lp(+w9Me% e3?ZGVIy@|ĝmO̴ʏ9)kZ&=edK||=O!r:Z1D!Q%ʣ9?VoN8Cm)swA (Ä "o5W tƩgtGfk}uV򕪟Ne0FWw=P|*1كְ{2,3[%[`}꟤KۀnBCuX+N11Sb4-rT<āE8<=-5nPR[Mf._-mLFG ܵBvұfe=+Ic%21 Clb(F3߹Z}u?m%V߉kDEqM osEv O]awgf;oHE(kXx7 lxcQm<[ϠXm]g. ThnRZji~ tכ+6}-jm/b|ԉ=Z- HWd㗵DCa+u" endstream endobj 2 0 obj << /Type /ObjStm /N 100 /First 812 /Length 2067 /Filter /FlateDecode >> stream xڽZ]o}ׯ>^ c+n䶽n NhL =C--i^={fvvfMYEDZ)ָƿCFyґ&td`x${19GH.9bMު;07E!jr:'r`x)8lJso"ypU^xA&e Jpm m0т&Y4s,ů<ءysIsA+zf` ' D_NK"BgEr^ڐ B2ę֢aJZ9~dem!L[+#:D{lϴabf+> H8$jX2mXɝCDpfZBL_=y" w@#ί^o?4~TW]6~ezn*j\ohU^ݖ7K6uG]_Vj}谽|3#-jݔ]}_ʇ~vm~ ?cnlZMG{42}κ^Vaۻr]{vm^M=Y]9xnoݝlNT4A*Ǒ ?lG_// mEE550޵ι'ՕNƃƯr/\lr^Z^^̞}O puEMAUV/_c7~yiemZ=0jz#}szlAZ* eJ0Ozf?W:EK}msWa/ \_NZX1j]W_Wde1#+oY}Ux}lUwJyp.wJ/tW6YEKo_FgW|yӏ[GO#٦bBdV2\HVwWUEPp6b7B"o 4=zW-u@# |b}ؿw`bV*:&6[[ېی3x&㙌g2x&㙌g2x&ٌg3x6ٌg3x6ٌg3gLQT*TdT.39~p:=5RɢVHڎS9jbH}t.*I t='C>S"^STU HF3q'e}..\U/|3~ Ȃ*ʞdaR.,:MөF`~ ov/ h'Q4jOP ?Q-a_C4(3> ZXliȡCkSAQ`IN<bḇлС5.?<.6IdY4, fF[0$F?&A?kg endstream endobj 175 0 obj << /Length 456 /Filter /FlateDecode >> stream xT=S0 +<:C+厱dЦ$\rWXZْ,==Gj"wV hɤc-y19:@ /=bɌ*ełL$mT߬q_Zk4$hnerގI㸾<'jvqJc;9sLjԁ1=-* EbH K:Ϣf4rAW8S_GI_,Re؇!1tH,$pYFDY]*s\/tƿ.沝p_}bQՂwM> G>&6FY4K*cW˷j@>J3*n7YYoێ=~JbX-W&~ 4g8f`3m?4bQyQ ΡhV 26:&7 8]jvXH 8Ze1 endstream endobj 180 0 obj << /Length 2337 /Filter /FlateDecode >> stream xZBPDjN}KM- (dYE{gfgK;q>o;-Gr3R2%cGoLzt#i|z&S?Qr25r᥂jw lVkh%_r1edj 7>lqD+\r秹))JY*$Jp3½_Mdi v V|~TB֣us !OGDn0=03wT QS554`5֙,(ssK|+:4o Yo'AnGys%A{Ѹ5@t1FwWzT% UbEd_l+x^f?kE[Drj;;{M,pwT[twhW6,0k (΍pZQ#@$!\Hi -IaqGЎ(t&o!\nz/j+᪼I)c%qs]qX0\"} kK\ۘ(";_]uCÓ5Rݐˣ3w}DU۰5Y}(2m _x['QGɳ^*]"s'EM N$I@ 3kzwO2<-»b=a_08d.ڂb\W0c08;?Q>t@:]0UR=?_u-P4yf;'N}u~5BȜU=+tYt -4j7aKz.t)F$}b |S|?0} QMxr2ȊWچïyȚx؉^jFtУ?_un#~Bϒ%5֢87x-h< 1c, ƃyE 6Nz 75K=kԫ&QDFnSݣp@QǒcjQ j[(yBk:?`S2i!N CyW(  (_>˗ L8:?O_?{_EY\쇽:]"6 <# yFHˠ~PӮ{(xxρ5y2S}f9\sWާ{.;~Six${ x05ʚ@'82_>$Ke(g>Q i*CLq0B*Loi:(B  w Z+-lI ;֔V- M⿷k1u·3Ά}OĸܯH L0hOio{͡iL͕(,VH|\Ж>??Iqq&SLd #&$$ 6 K&w˷qMa3 zLK^D*(=4KY76@Yp>0_x}сe([ʖIzb'ƜyK\$?Y4 U˜T8 ŠGovڋޡz/V 28t@hcؖPߋtAOB7dOxkFt f\;3oр9J9ۿ/udPI3at|~Zj;QYaCÆ%1yL -E:_ҏdg|H[ʼn,:9Z~cܮn{T ؜C|@ 3u,-;GOr楗{_{Gȅ+%V'_UVy?u?>xD:v+枉ם`闙}ͰsHT:oUE\H;ʘqN"6Y{Rϻt"-n6m t ҳSay;Y$$5S?#;$@ v8*EYh:[?*33>˭S4cyAC{U|ը9=8QB"^6ƿ6V‚;ٳv endstream endobj 191 0 obj << /Length 2859 /Filter /FlateDecode >> stream xZoBȗqwCs@K9\E[$AAKFt&eh̾$Jvga%<3d$%_htG<}݋\Md"*NI)Wt1y5DN8$- \fh+nkd A)ho-} [ ONf;hO=5wƛ: |c||$jC ޖzA'u*TUL t!.oD-𐖇]0’W$5$"dRʂ_L,v6xM͑;fl {'-Hrt {d~) dZR@[t>A I2}^u}hF {]rQʹrm[yXP50LAj.vZPGb^4v;֩#Fgoy!!Mº۸^fgﻥw6vvkAm",Xr,wc!_9?b9IN4H[ ^]@ϖ?b-*!w98 %ʤ  EtXA\|3Pծ?F]2{8 P, S 9I֛ ;>v|{.|}$=sW} \/.6\*c`8Z^^SL2 )WdAAX=Ct\ hPa *ŷ1Z ]S|"ۜ#m#hm')mbpUnv{ZFGi[kXeQ**h!GlNʱZW)r_:\;KE} 7" #4@r6(GMG-ՏYU(k'͖+a:S b 좮=(1dTϕ-o:U0nZ`lWP8u _s K~~`;/oOB?!Xdgm?S>wP^]|ֶ~T@EByC_ N}ZбÕWD)Сrt c{i#R)TTK7'\f[ETf"O,u_'"RFamW<ʟow~=Y uikUgZH4!>\^y*C֕n"a!滷k0B|j7pRcW%~"(IO(Φ endstream endobj 200 0 obj << /Length 2304 /Filter /FlateDecode >> stream xZ[~_a4G["A fE-{VK3HQywvgӴEdQG:'g_̟]≎Uz2_MBM4QYNwj,ngzzhO+kkl庁b]Jp2U:6@n-dm2^i8?͢$\YRIB]EtG׋@7{Y׸E5-8ESq6ιÆVF-89í4IM [`A" #-x^p>Z3FbV&֎nGJ[)y{&9; MԉC*d&ws,dcmc0z$?Ǡ&PX2Kxg6 5}Xy3Iʞ 2N"S"3 3|Vj,+$t:A OTySG,Bk-E<&%ib_Np>e /> stream xڽZ[o6~ϯ0$cFR%[vk0d:#;^},9 ;<<(IKXs9Fxo$n1ɏGG+1,.XGHq"(KEt4=yr{H8G)Y8]*)\[`91sxXy,H ϫ$IDCd֣ *WňD2/= 8hUmYl6vBۏ^$1hiLN.n="fGLд$ckXJfLaHO3۸Ș d)\~TARg(\L{oIl% $ˆ;$Qp?ܒni7)HBX=n X&I7\wq j7۰Ϧ9^8N;\wq'ɫ(M '\o LPk.H"ZqIaq^H(!%ޮm W^ttǴk-X~!=GA͏I_Y|侌ZAWwkEx#e:qЭHlUٺ+/v $BۚzC 츝sC!E$).-R◆%O?ugag$aMGאhE]?TgEzuʸ͟3,ohhcZصddv# 5hN)vzǸt#v^J+["r,rx·X.ZZˊ(FtKEt=7 kZn%N޾)R8CT$3]'䉱NmM>,2%CtBWIpsx~p n+4EX}fsaϕjWqBϔ+dSM:g0B(bMf6?1P9ƒ 81`_2itGpq}¯{UzkﰶK:~Y/=DY22Kf⾩׌H|Ap=1h\Xw'}KzT0%%}vס X 53c({ DDDs((w편D ױ'{Anx1Xy'^ KL5.3b*0rإBgv<떪TTuG`"PH%3kva k4OD wNz$ sIX[hFD]0cWayPQIV0d" +oB"f bZފڷW?#4 je; ~ 565yc 2Rk 4n^IbAEmC[0ȍT):Bv}h!"YSI2-LYn;{+ڷ {lq7`ö1]1{@}+ x%KcT5tbmMuЬm *܀8`wad 5˃;)^ )Fa#TOwq'm"ڸe I=")9w(d񩍱GtnWgQ!-/-Ι 'Jor6>W(꬝>7}ཱིSv,nehrC^Xiv.GQF9:jm*d,ƣI~Jbܤ$=#p4ζ(;jSƽ%2Iv n*KHؼΠx8ns.۶X) @#5!z}A-þЅWο/BNd@8fCi$8Xn#nV2;8=Aw nX™ޕVb:GtG03։ _<{BdF6'`2DCsg,7_)E73IIy4*7vK) q4h0ߛ(!Pn 5f[B\2b*ܟ ({yP0V$ $%Ut,N]%~_=-cmbOm9hԄ*>lM|y|ာ ΅ȟ̙exw P 1 <ȍk[†qoG endstream endobj 208 0 obj << /Length 2993 /Filter /FlateDecode >> stream xZYo~ϯ҇Ȁpf6mEqDB$)/h{dʉpx,3YFh9JGoeltx-G^{^zҤNk5:_tN:G''ڌ^pdbTf?_xϣ<)U9(B}p7@+z>}^}Hno)P8˳" ŝX~D@4ϐg鰣\ʬGU鉮wH*ϑJoVE3fmRLnb}6^!E2ɈͲLJLP 8jtE[r{5-9B`ת. ^2.hʺvzg|Q:z6x%x38"8`hWIkxhWI S>I^5>GaE/ֺHBx?T$vh]l pvQԔՐ3k^  6nn{V7-$NK]o{W^}ɼ cNuoޕS첆 ?W|9A{M :Cݞ`kEi \ _s L sntby 3žJEW:̀)ߖ(t¤hĞ;羕f!e])@wdJ\L ZS`Ys jjŎ R]2SG^EV0Kh7,c&XlFC#fSztNn*Ho߄@LE`]Ĕv({hh|MeC<@kE꧶[o2L < Y:eJ'֕SfDi'U\ VTI^ЌERc?f>4J&uc,~g?<xuYdoi\@imObCZqAA؅fUV;NXMYӍ .C:" koZWqB1")J QJ#&]CqeW0+He uvu{S_DұE@PXu 0#fN H1(վ+j$^q)Ԑ ș;]jA'RJ$[ 0[%^怌"qCeuo㇭K5,d\=U+8/e/HKiވ_>UL+"=z'n<z.F|6Τ>נ>_CUYu&VVdص%C@ hہK;TDGX2߈&?sͧjCVx(_΁b{ \ ҳ2BgE}ű,xB w"-%SL`g /|'fv/5*4\Bv+9{VV~K4Uiך&ap8Evy.S[,h]Nhhwno)=oO]:`9tuu|]`Nf= H#>3bvJ _! 2*K>&VvH7? ĕ}*UPl)\w.oCAI3~[R2*)'*23TE,trHM:Xr77;ˬ=4}::sE+ݠ&:7XخE|l,ݪ({ K~=5zQ]e ˧Rdx [xiKy栞).Etve2(t?"Qt=6`TYϢ!!ɕ/еT[uWM5|$51)W` mv֊L_w3g-k: =2wq̋ W JBvIT(b-t/)jpu ϡV% ZJ J5rSpaefQAla\pWlp_FUz ;h:0X_O l#7@UCW,Y< 5RZpĪZM}Hт밴~L8LR%{4F2#6tׁOjmu 樬 (e"#`!.'cmwR9 >ȮO]љJx8Yꕼ^YdTYfy:q&u˨߶טabڂN5gږ endstream endobj 216 0 obj << /Length 3075 /Filter /FlateDecode >> stream xi~X+h $(Z^ %ꨵFޢ?R]$N?$xyބ$|"wDIn&~59޾wY<  hrElqv1y7}sMt3BO;i็g^ɸ42t˛Y'iBf$<@dկBVoːThO6'YLjS"nK*~dEH[: cD+2LElY@zy}©?2uW)CUe.`>۵JLR,q7ljc|2?>vgAgЋ0|`qM*=2v^ш) n_5~l0qFXO+md 0፼]")A/ʸ!I`b!#ṬM[pcx+mz_p*ATy:?R< < M&x3glxQt^N-7[=|j'-K~eRqϘs<ת5vZ#Tb?4LrۛoA\Db q(Q/6|Iq|(rjӞG냈O c]-EJd[/p0-wr.K<fqX)Gw;"*zl.Qsը;L`Ag#<_]E?“x+&Y g0: zZpfx7TW2^TAb>8,$A`HL_\Q\ 2E`81A}Q`͂L+Qw"J譞[XRg)4KuW*gtj)틧4Nƀ}ga5J[tc A# :?Yd )יz+OԼ_m^^ֈƮq&-]RߴAi'gy&*|Dpe`dx k--Joi9O p8gn&fwV| &=E鵭&d8z\0nKQ̌9RIѦrTMI6RDqazK=ϵj[sU j?VJ|jP6gP\|hp!zqѯ֑xW3ad2mt7̥=Rn)۴ (%﬍PԿWIz~t FfK̕%TKMoܑJDJ:6,k(즣-)h]6,CO8 n\5 BGf?JD2b* z埮,zld<3YI^QHyιԥ_rQOmt7[]Or#0c= 7H4䑒 `ڹ}mdUa cxiv46`ȍܸ@=SW#A<iUAJ1#MLsf"5n/_r GZН+2%1 y]\X=ov{).ͲaPԹXhx y؎a-KȭGd /]ǫI޾Jvk}Pm%|0@WUWty+y[֟%e}yu߆M:zJ&+ķ nJEa~ W!׊[j9p5xglkD ݑ4+|u7Cqq-ݽHOxxdғD\~fWNB Lkj?ػa  L\1TO9;(L W,`wL0,W%D\͈0e>ё.i 赪wZs損+)wpHX][&rc\̈́OFN _jO8Oޑ󀒏9+"0j!+ D_m_׽E K8† S$x9Ywu^ AY*LIRٻQN$ʫȹ endstream endobj 221 0 obj << /Length 2819 /Filter /FlateDecode >> stream xk #d ֊hE-{k[%^΋d`pT4¸̢<$q3N_" x>wŗ/^z0N2=;_tY0+j&H (8*۹ kqjiz7XƼ,.ghJBa\gư2B6S"LwBOT_i\=ތɸ7ilj ;2 !az4p@lB6DthқERf9nJla 켬W0)igk,T8yܯ 6QRE\9LM-Zml궦Hy!@❴;ƶ"YGqĖ'IYXDNNU;x%2Ě34)W3-aQx)<< IAOs2nLىlizHcW (itpx/ܕY?kyt*^h{[4 GA1Ϟ9V-/Α*]d=(Q}*aSw)1?Wgn˘`.Nx0/IOIo'd=)՟$`wL9DҦ73hiaWx1Yyf \g2'w6&5g< G>y(1֖ͮD'-Q=kyE/v"D[zq^flđqޭ,mP ܱcC/U0˴vgޝM$y_xP¯1 Lj8Of'8 q{_FI3bud?5ܾvf +盫[_<2=;8{ Gs|EZp_=٢qONOc3 ;~cu>`)\ e<3mՂhk=0XH 9O3SkD ^ԬjnC"1mM6$ m m&ti #!vfH4kz-MDwF;]fi$eI$1yXPQ Z=D^YPx4{ן:L;E󂁻(F z2Dt#qsjȂn1kéR!A(.\cw76V$\Ҥ?Uic>mM@ik({,. zq݆GӞq8X/k#<46ad7t2H҈DH߼NU;߳Af)+]͎Lpsd#7X]6Rqm^PKwWǣϛ5{g1e8NܪEL!:zz~\!]K̸+P*ۍhDoa=a$Me_H95%S! $Q #t*}+ ]3"0c$6dCDžm@B/plRlK,Iչ8H8 %#,`NgJQɇo6$r{yJ٥mv鮬@/oL J DqΈVUдd` {d 7MCdc%5};_~˞%I~ńο4В]x}$΅V=: O:/ر^=gc{*la*' ogSI\I NOA.y~s? ! endstream endobj 231 0 obj << /Length 2986 /Filter /FlateDecode >> stream xڽZ[o~ ҇@~IiME(`KKEDc= E;lls=s|gFwޫ`  лϊԻXzf{{7Or֯NY8fxN|vt5t ^odQ }uParQÿ[٬Obv5 A+,8H_ښܮDŊ p #ҫw /F@]*XHН~hA9x$\n道w Pƺ,#/*mE0kn%RQ6X|Du[wIɂ jpԻlGfbQghF}( qQVj.5%RgYMwC`ہ#a?$Xvࣾx4HdRDrfz90Qַ6ZYbM l=>Vd墉+Ͱ<2lrk|֖Xja{l}ݩؚ I?8YQj<r-Jo6"ɺRIĚŊn VbUUd(`Xhlm7(zuݓey%lV@, '\"L)Y!&H\BB12^Tw-fK7Ƥ~pb\r)`7B%NM ͍vju0AC˖;reȴ_Rt&UY;~Vai_;R@F\Z3dFg/.`ok(P,4t+nRcm,eAMEQ? ,9{3 &# ?L? 9o$, 8 *7;YH.\- ZxЫ<ceXk hq%<$2o>.<{4- 2'`qyΎ0\=ٞ:KYPf2aQas0Ұ'uI0خXaZcmgMNpA&H.B(RBOL{MRgLHO\4E5:ߢ=+N-VᆭnqwX"a F+ti*lyUiܴ(0m A:xFF9@TTd@:,wg( Re;l*]+YܻTF (_3%2`NeLQh XºxL-w8@u8׆]nܨ+i[]Ԕ&Gv@9v~ve]ƢF O4̉al`Bd> 8}K: ٔE_ GyIх H}7gˆΉuDXb2 t.eWrdP*4'2&/VZl+%L-8u6)},mi3a3b$f|+LxٴFfiG|WzeHۦr{^dOWJi i%7a]n-ޥ7iyu'+QiS暈V8]3K‰lJ:DZƚރڍ|_j^K u?K U*9F:g+'ҍy#E-߉\DDƺeYNָZ{[à , }}<1_ډguNl/jR:ybgc5b'f]( {kRѕJw|&n&2J#7JoP{torŗﱕ/K5kNx-K] zʁ"/9( ')Jj|!;Pu;\"b+W{ x6ir;|,]mZ'ch!.^n1e{G֖u_+7?U yKc7_P<'*D/m)| ˢ\, oeJRVzo2w7I~=͋94$7XXnڡQVr:i}/ s`v?E"43%Е+skb_d/t~,U#]:s-  5Q:  םfH?kR݌RL\FLi+{eN6+)ӧ/b 7.Ŷ:˶c8~8tǧ`m^T1,+/݇[BwK 7^XB;?;>3TlߕWe%D>a^?q76JmYRA[z!)0䷗K/e+k󋔵?S`yXRA3Abg{2E Bg endstream endobj 244 0 obj << /Length 3055 /Filter /FlateDecode >> stream xZs6_=<?@^'m:m:CP-kbH~.Rc'%$.G(;_2-h[F.wϋ/Ί$JFQR4SQ'BUytL580o+Sۜ2JaʛySxnN^*6+&"+YOMġ m%fTFj&:mw~!0qY'ʊXa2>4 !HJ df !Ǡ ^1=vRxwl[q@1sa .Qrs\JC~*&jv~L* )emi;3d: Mn@e @Us;) ;äi)#;,Ej__XC0:ٞQ̐]c6 4iF 'HMwR56XU mJr[t[Ij4ZznoVHUGU1;| s!$;Z3^, /x\lGc_Yyn`2L^\jmY55p8:R U". }Atٺ(G'zԞژ0 E, 5ft U kːߡ.FћL MMϩ@_ภ+8~G ZHJt:U".mݓyfKgc@SȁR'6 e,aUٷH"lǦz`0b:Uj-LAOm>9 >cCK< li6 Gv&kۍZYy1{.bp#\1,Z`M%k>E%9Q%f㕧 e$'K"1XbEצ5ؤ'H7'lo8 !-r #%ζNqpF|1Is)&_x[lj1@,|r wB9BdI2Y? wT6rX!1\qWnj^ 79߻!Zpۛ1Ώ3끱A$`Ӯ563|q0{^kx^CpMM 6rH+.PȔAh'Z+XY6P͜Ӝ() G إа]s+,MsK V/\: $wHtK ~Dѳ_xNhyo[pw@c x~tжMCopO'=n6\^T>u\< T\U\?aj~z% ,ˬ{S&O VBfUQ$I,08׸U'HvV[0ٙt-KT;uu=f+2U}KSm}$}ケe=JoaKsQ7@g.'޶"VOkj-l@Rdr}O)TwF}oh`ǣpq,a8,7(<{tA)T}, O5ό H\13vr\T;;mۉlƦ i.2yQ$(_4c͆3/uۡ1]Gb8(psEY;1sp nTx8^C84[禤[x8ƻa( !RAn+vcE5IjQ'IQ%Rb1bp0-ӤURzJh`ܜ{*udt8ZQ׭'+r5 !y8>z옾)Wqh)Y;Wamر7>I+zir~1o\{[SZ4@o >) }k#tl+򉳠!~Q_o^]Zr|1&+g3\x"_^ڏEZb3[kF7i>Ï3G [S1AI5В4R_ Z^ T)}qq_.G5 endstream endobj 251 0 obj << /Length 3141 /Filter /FlateDecode >> stream xڵZ[o~ϯDCr%Yh$M>-QID*m/48i s;;;of̶dQbIj2ѳt/=xECk*C-nLEΌ,0` aR `~̠=[_yxȳ$v+\80L$.TX8/2a{Y\Ֆ{t-i"/"Uq|;[*w)s3;m.x?R ʎ~O>9*~^Hб.!a$@OS4OԦGӵDP:7rSř1EHԧ .u;q01rN`W}rY'IWSo5eܭSZJ|ՙ;?B!-YFٺoLhv4R: k~Z+M2{,JJf5lm#2a?M7#릎W'B`ҍI@Ozz dFNYtxaҚX Zncz^5_'Xd|+#ɣE~Lq! vo&╜eƘ[7ha9Ɩ0/`ɒ 4[յXN} 2$Z0%|ȕx٦wJfԉZffVƘ[fMMjpf V QCx4'EYYů[%]+h~o -^zx\s~y?2s!L)*.XQH^zvDDlط!wRA@h?AUEᚡ錈]Dj% ١n yJ?2Dc e>i#3Y1Hbה*h .ipiq TL#l,'&tKwְI*cyd.OT`=x/*+cmS}wvcK u6=KfB7-Z~drvUXe>Zݺ% Z6'}TroZEzepC*r~a^+BeJmڞ^:\ KJ /hU.Et#uuE B A8 vh!/yuXq(/ظLA ]{7Թ%3!yk[9tP-nCrp7 T62;/0'8[?vZsc/-W>y0qfgq8WWqQ p,xM%BȞ]3{|iOPyLǏoi-c+,OcP6ԂNܯ9ɲ )Ƽ$ Ox c}D2Dk;Hb3#?uP.W|jHۡ1/({ldq^x8Z8 #s&ua~w?p|9'O MTVŧM23e}gJǺBBО~[uVEF dc|' w,xĸq^=arƓ/LqDT6K$W6O I:p$^w @7bnY?)Jsڗ!$k؆N!dd&#Hbr#g.# 'PF>Ln>I3 U@ ,NR}8[8:Pj2?Nʑ!]ܳr619.zL2ߡFkAy-{*ωe^?'X6sMzWCi]O A6҆vh+eh%$g]Dl6^hI::/%Dniτ%}``:JlkLBzSVI=yi #}atױIu)K .p|:k?OVIoC)c"n{YlP9Qn4?vW.1!g$Zӿ׸vj;̕o #Ȕ7 ^&-+#9C,p_pU>ɂ"ș(~./ endstream endobj 261 0 obj << /Length 2979 /Filter /FlateDecode >> stream xZY~_!l"#.7l@ p(.IfF0uu7)jvv!}TWuW}u o~v wtKg|sͻ$X(\- (Yq%y,~\v+<Swh砟nݟ޼GD̋Tnhx U.#(=Ȫ.u{J b;=m:Tr+6zj^8%HgXUn[ld~q? +R?jl%*Kh8!?;-ZTs/*(=.YslՇ)EXN:8 IU=pv+'}#=ܴ(NvMkgF/֕]֚^ "X =5YWz"jw?OZ0j˪+k~L[2=G2\>ී̰La{]8/\htᷧ!WP7p2&1/!0hc|8|vp^_F>4>Ʃ/v!ƍAc:2Y0U(7Ҭ]#>^r/ڲqPr9 vrCϟ/r! StҮ0=qH [Y4yzL3ϫZ8#OkںJV1yz:HSSꉳ 깂@ "jJ앃)mqc:NmB>^{0"@]5xG7՜|*0dlܩkpHwRJ]V Ә bF+H%t/S=xJ7 %AU%P%n'Thf".q9Bϰw2QcoјutX5x=Չ15(I`|x]&$(Y>̏1XP,me5łP}/,t2 ̗1:.ts6* ZƤ8wo 26%ɍ+耱aK-*GK^^c#V 2 N)ǭ :ơnŋCc3(e׮wjףeoslb˸ B]@It\qF$hdӚburO*!k)E8ݵqzQ* / <FhͅܔxWYq)S+qab-p'q hQ9фN_׿VB{őcĤ\ y]gvJR[ Wg\8v˨jF^%N"*w܂Uf&qү2,c!d UA}`D0ҝ$[aU~0,siwr)L> stream x]o=BH,]bQHK.PW} Ze#鈔|.iZq^>\.ggfkgf,vdU"ϷW$4J׋l[,<*_\n4z\LQD ?,2>Mtn``K(!>n>v~[b=K h?Ovu#f1Bيvu?cϘ,z#ñ ͌ '|k;b w##@Il"Y2)Glq `lloY4IIR=u6سm?k4? [SYiDN2JlImPx{{T< gt=.M&Y4&yn>\wOJyzc"nM恋v7#n7du}cyOj$"Gi3^FIhzlK} [q6"12 w~P;V4=3C0ZA tz ;Ԅ8*Au-ѵ콽|dc"En2ؤp,Og>\uY<3$N@FILAHV"s$yja ~ 8ފ[QdrefZLp$D(B1&G’5fLA"Qzs_EVӸ.VѴ"ē\M@ZÈeUlw6dv"d+A6U__seߋyDt~Wp=Wi:lA`̱PN}1]{wU#+((7b8ibpQAbV<.B!$QxY#1T?^ l,E8+`bkFY! Xh]`ްgʲx]B9WJi'EH@}/S]^;V31Uʌ/@Y4T;a.ɂҲKyhVANlaNf ֔ ZN~g,m2I5VSj9= /U$}'l-%ϽPLդLpT^跚U բ* -tp4; I95Rjs-5%xʭdFQR4dk(0ι{@a*ۣlko8@r|9)Š< oY ʔ=@} 64Rhs:(Ύ[#uP!sGHfpj& #cLշ"N9J%bq1CP(mʩXH鷒ʳ܋,}}$3Ss}Н/Q +׺; :֤aȅs- 6֯ rz t=eE\+ͅ6Xo(0!Ý,q6N8qiM u9dv-]#2=vpsidғr6e+I<_Y<#ٓ%dos*DVd*kwM2)F `_2CAmQ@rdʅ -[|&ίaQ}];&+ƃ▦$5MDžC ֤;k@3հQm/ Ij6`F0VěQTg jN^lJgz!ţrPZNǹh#B /!9Mq9Θy+Ѱ.s| B.]nný:;y@2eЫٺ.!n1FC5JR 4Gd"2Z ɝ.bَ62Ёr]}kY|+ ]ufʽΌNz;,sJwu[_o猩 004"(I.(H|"ovd _G1^@J}7o5uD39m}QL_bFTX)n)ڒd9_ Q:qFf/x&%ml>l_N$c4-?ňƹ)?@?Q쁴s犝?Wo2VqUy3 u)¶@~rIVlY;"%B,QЦgWD !x'e$]+|L i>j;)Qܟ%Ӯ1%pN%HZĩ_a:k;¾YJ!._F endstream endobj 172 0 obj << /Type /ObjStm /N 100 /First 863 /Length 1943 /Filter /FlateDecode >> stream xڽYߏ5~_Gx8glmtB*TH UmriْAf&ڵw78C1 O10bC&F|ob x&#>d|Odg̦pP,0!v:WX<E׀ƃe23>Rn0&eI}cE)x!A%)@gMp*!) :^rQ@ec'8 7l@  `3G v$Z^I~bB9La|=_ Yi ;pQHgxB^!/'%"Ca@F9s0/ `#aD8D0;C O:@t)ǔDoSX`ABD 8%n0I0X$). &MNG0ԦIBk,UO'MlhI^/n&|euM'|֙7SQ%h,' 9?7+|վnM|ZauOOw u `IWI6D0-IuMmIiA!…H<7oęǟdGr/mixlhv@{.^ P>)"o&ᩛzOylg4/_Cg.Zx1e>i EҐU{W}_}7{=`z)1]b-hM+v]5A,yyRKDrA9XHTgs#˽1&q)`,j5)[vc=6vtdm-y<Ήq̇NLb>ʌ{{42Ɔۋgy=y7 tU׽vP*-V }ԗ#k$~̗* #LAjf-K\( /iCs7-ْ#*ݢ8W4֡FՈW'Jڌ{VxێX[Fڲ#ֲpҒm~lBz0?VÀ!-t6B5:ým !mk(:h58h@zv  <"e%)W^rf<?ui|k/9j~`cu"ЫS:hJ pprN|h9$+p|7k}^2]sͳ}wUo'곦YuM|m5ߡC5g]l9]]ug˻m;uIѢ TVᵇV{W"%4p)aQU)ozegIi._ٰ]6>g0# /+rM"[뾛Os4u׵o^/V=g׈mteoD^އcф&$xJQh;JZ$1jh=!wr2o5,ݝ ʡTmh6b6x1S9Ѣ.EuNCs_*ls#EhLGJlN4I&#LGvX 5]wDHCB}-I.֪n`-HoKo:~ endstream endobj 276 0 obj << /Length 1833 /Filter /FlateDecode >> stream xX[o6~*1K.0֡{laEXr`﹑Kb{%JE xIqҏ&BpWo\2FU+*-^ejmd1FFzcS݀/iT괻T/LVm_IhU zr&foa[Pns0:΢tQ\DUK\?-qHc,fgW7w`׮`, =ҨeZf6 .b \ ~&F{ydǰs]nrI.UP(.$yO*P#07xsfrgD苞p#ڀAң8JQa= Dq^=VbKv<$6.ᕸ YFm%;;d‡%w 4aTB.f48DR+ʷжSy+S8*+ uS󴷈iyn1DlǷW\ŀ&d:O &qx0ɊNy+k +xg#+rMވ.c$cb՛L!(NU@ݑ G I39Ҷ\(֫&LpCg2x4ᤊZ [|wgkxen kSD2Ixw׌ı$TӓOQ2]q'!cI|U&RN y&>϶;' [L\xBઆlJ5- ys65j3y}d3i/ܓa,Y?QK$q |k3yjbKV.R*uïz0~ zlLLRP9GK9<EHav/qhZ7 laUD0bzbL#jIy?1Uq HeˣB[^?xE"‡:M3g7RF32c }(j`>uKfwx~)yĀ8>]aq+l@` ev}qRnʔ1|Y\X5hޣ-kȊ~tŬCB幙JLenx|CTq>eݝ]S& s灜rywk8uڵN-U9tAAʍO}Gv}x NNBSY~MuZ/3ɪb$gƝ1yn p$O< &1AZTD} endstream endobj 285 0 obj << /Length 855 /Filter /FlateDecode >> stream xڍVKo0 W([HQ`:N3MϏqR lS$Eds&}0Y,3a"cV]]ί"T(Lk61"ɒH8x(PuQH΁=%SRd2ShK΄I2co'lp.Ae@\Ҿw3-!|Hჰ hDO nq̲jFž?]F]^`QU}};nOŻ-aAGbyooBm-;#:\ b&.>\jΰu 1@u 3:)> stream xڌsxZjm۶Lll ma }CA#`dj%Dq݀@ _eLDŽ@ аqJ ḿn 'G +.Pv:X 7"qPR`rvg:Zehj7475+yS*T*t3wqvwcrJh@twrp:!+y濏q gfMG@"?2+;t@ܚ >)YsvrXJXAL=wW`?XY63#vho Wo> hY,4gN>uJںZL`dcgprxKbjD*hwF7g \JNh̻ '9kb17'){Yea`c{A T-VZx8_)h-D@o7B_co_kgoTqr0h@׉h:VJ:;Ysl\SWWSPqrXAi<A.PK'W,߈ ,q  `q vT@Sq*x@*YEP8"?Ao ntf#V65+A9O P#N=`+K'N9DKZ?.XqfiNpXazÀ4%j'mPC}lA}'E.P=Q b r?rv5?j3s3`O ΠpllA>ߐ'w?N$GYAz s7UwPPnn'Q n tr,bew3wrg@Gj?VDjAL@g?w+z>@~@s%'spۆ:Q/)A.wx{}&%\ ;< k}HEH$xn/;^n&X  sF%>_p43W,qO}Fd.Iu)'1#~(WW>^y,c0#b(2D-rdqiH 3h!Rn7_d/۳uHxC7 %c )ܪ8iRKlwʆ^B t|KHl-BJe~&sWs\~Ƙ뵙nB~Y)V'P"J|EՉLh{9}Wqb7` 9@DhWHeyySb c%@I) }.vS}E>9OC_G5>ӊ:MAĞ송_:)SD gV-?_aG7 j]\" ˀW~A4ϬF>zX2_2|9A 1f!XcYDZr>c"NX/,ƩCFif…=D9'6i"c+!m&\o:DEGASCLqaaފɉy!LkU$;, +snkb\6͋y#M|Rvhڳ1uT#kx$fާyl80ʄGd`MSOO6 %7)qm]i$#Ꭿ;e3f `}Maf L$'.|M p:Ƴ"JLnf rN.MaRM ](>1"c6)1W$[Ȳ&YyA"f i[vd}J(_m*K!)#uk.}KfTT{J~j*!HLs~38ҴIy3/+xnbVW "(iy\8ICK3aSC2~GVj8^ŊDx|oZN㼡I %,S=OI/g7,C_^E"# !1 _]z{Q˒8^Q5êWzo1(?1BH0 M"T_$+" e4cYތQ/F&Z͛zztQ~ jɞ7%-FU0Bºp>vXs LG5 =@ 8}7pR͑٩bN{ ?W!fi6Џnu4h1BwP  %y/OY|C.:d|Aɇm\"|$Cr"vOt8"fĔٿH5I۳oQplEגrtI+˜҇CL/?eKkL-eͣ͜w9Sbי}6}n~U{bGe1Dk=pg ^|pD"nu$c,53\_6.~EP+uM |fueIi[M~fҒV Fu)[38S :?}St׏}؍reEw!1,_ך\T t}g}s Wgp? O%mHְj2H)W]n`v-Ҏ]uHlg0G ǬJ頗OZn4B~*Nd%6v.$ac0P-!M]‰{k"~Mv/do2~M 0)2A$C;aY4=CԗN^B eQd2[O긟˯WqFz|cTz{OGQ{*xWE`>Ԟ2|ln9BJTu !Z:\O+4ɡV/0,xMDv?Ɲ5qg<洲w_D2}̅jr>3ƙ?CJ4nN4>ܬVVhʿ g̳fsV#jf܏1Jwu:DOu2emn̢r)= ODlc;9++|5-Am 5ï_ J¯/ٚtrJJeݷ?6ך`L+Y `JL%czޑ?/y{ۡeşX`3㗡k*՚;;ȳTiqh~K)j#0nB 3c`D/J}^F!+9"yu%¢x`GP*ߴY`M1^[Px< ?^%kGZ( qfo EE-mņ|v+zwPr^(5BVy}X7;KpA@ (xՖW+c}; w@^(Os^O{17|6omuvڱo8n̅}&OxO5l|gOdbEFNOxOw>00xMmETnNI>v/2 |dC꧌S9VFbP55IppeN}yY3b2 2 B&rz޻'̺8\Ň,dLrO:IQ9'0Lb2ܽu a l#L> 뗤S1ƈ鏸U`}e!du9O6V vDzzI; eӁTlqC V$/_KKGXe#DɐZ^U 0:):!=1Gk_xx 77)XC9bubm n,v=?#&qժ]Y.*U^WF }&)vh笹)Ux;K_Ǔ٠Cqθ~u"6?km]znotwIׯ0E%k^;:,^R 4l y E,|v"PxH)aBy-辖ӯ/{oό8 n6OBqA*AILq?Ӝ{~WM;+ŲչvI#_'0MV"^[}G&;V*|~qIި1geVXt 62BzzRi]oTۃZ͋WMZΤϘѶڮ$35>~@0/N3 Sg=8j"6]̚\V3ڲ26o&T_; d}O,lC)k,o`;u!72-SClTcH*ue3Q[faCW7y'%n;Rf5-vȪ1r: |>eInV~b^LKp3c+Y}IǿZ -9P-̧>n328Vռڷu6@}O考mQS/ u]Đʣ Q(&ϓ| g1aqW~b tVW z)yp_̈qΏ ٠-KјBvƕ+fg0V~Hm'MV?nɝvщ9E$#xA_p@h"](Qh3Rx-9ӾmOʷHKK4[iTaFڨ?,1Z@|M.:/XJPN`W}>sz)<["ǐ37,dq8z /]S\i*L) u|EHtDP3QBRo*z}ߵvc;+fBӯsY .6s˲pVf>+ 7,yBDq\ ڻP@EtMRvxlu(WzϪ"xܨ)\i3?]x[v(cLLw;R.e(AAK_"yhMdhug΄mޓ *iQZ[YCVbh@1E UfdžvMԻu*N%ުcg2"QsJG %2D W!3R&' eqYd߲Lz6x-h\ט3p*MnLIk??~4r9LnJ\V'JjXj}Fs9pߩWt*lm1_,b`ɉ_.#nZةqj~{NG}gz UCojr~R$ROqwhdC:m2-6gtF˕ \WCT :ox|W(;>nj^ aPgtOJ$?{p7chfJ8l]O4|?nB蓍moSog=?v[c5DAf3]Ki}$Fjz_XmRwu^9e Lay3FMZ-QUvܞ#'E7EUY9["M`=; p>՘paΒX̠v>"'bO"_\Uh+"YI%F_q^KHPp }piewqƭYd-S6>GA=,ly7Wӟv}"J]HD=Xč8~Ƙ;c(zEE4Hו!c+aޗ5{<,EgM@տ*˨vC]RENύФ: ȧ liFm?qjI~JR-1ɶkNjx1Zp}9<=Z&a~MH9_a@4U0QLeveBvxEv9[g~r,gKv\Ö"i$H+3ΜY]fR>d%//;}uϦSߔbD@hq wgWR6U*n|An|_g1`;Q*Z~&6\+DlmBZuD Us qfhv;ShͰ;k97uHCaX<}^U pϴYꪅX0DŽTE)9ZwG|xm&ʀ`3V@ECRŠo}sQyݧQOZ޷gw_, Y_݂=Ȗ|XWXFJ/Dc̳u&RӰ m` * I]&pzK*$^#YEyÃjh[DsvѠAk3T.Гu ɢ4[w22u3*.`unz#0Sm~j_l~~9mM#:M"W='>ɍZ޿.6|it+O$o@0jN~`酴$͆^U祇;Cy-|8 ܘPd]S7 `{2.MEi"jCW?K FV2Bbɏ؀FnȈY)9[$o^Ic $Tkc}iN Z|s#Y@//>*.jH$ t[gĊΘ l) QY|ճ笯utL Q1sm_4€b8)VA$DBx- pxRtzE^bG[8 _K%ܬU:X~i}ݑǝt8"[M;rmgq`wx`NG@}!=ʟQyb͗ljVn`5A#"gy~4|ibu'n3R *;]!|g3μeC%zoMz*_}3b ')'})?Ӝe eP=6~v:\DX;`DaSk0%-(&!vSE*C%X U9 6\\pTW =+%.aUH aӐMǵgm5s!X4fLGx&`bVxƒb1̟"WkU2.X#ҚeI*T ܧZ_.f+h)37BݱUx=}WglIGc Mv`:n5\Ilp]QݨY lMϯ+ƤyiD3uGr9#^e[ĨVwbYo^>o n^Wj SFK_cő]kMۛ߱44(?)˴&3RxB*#`Xt+6mtIz^/_pn gIi*-}W'=0rմtj21=z&Z+wo F*_Oz˒t5\ E~cu~Y3J$ή%RTv/ v{4U^5Q@ 4wO6ӴcUHc7LRiָ^^{&Z4_@@)s./ޫ*jڹ'>Rc7] G{@AY!b|ס*6ϝy+q{lJ@m*(s#0É8nY]Z"ښ O uɕvFJ/E aڨ%MlLjn+*8Yji(ٸE vΦ?wKӁeW\k|RF6ʿ=;Djտ٭zJBHe¸)uF LtvKh.b!`JFHy4D.кGodw 7k 118RCC66E3rl[ehNL ;o6-~ xct W^  *cV9\QrOb@1əzWX/ƞ P]?*)=mQRL.)B0m^saԄ;`}_l΋h U>76תNe=}÷8f%e~fmVI4h|QlG u2h$-㥇b\ќA7!1*?s<D7F19#N>'Z=_ u):|hL1gq/Y|4R¨xQ \(v-TAe|JyP_"BgsFiWG):3t}wDY+Fu%!Ef5rRq#/ޙh[S{Uk$3^zGP/Z獇D8 R[J I^YZnu7'`虞+9RCbwk V?My~&o9MO bg{۰X"<v-y$VBʟřg $h]p%tKo M 72͐3s SH(좐( V.&X1׻\Ҁ"r_`I%-," QI4=Ύ;ى:Veq鸳PdUP``Q+EU|́♑ ԯB)# 5c`U{ { գ *G'E`w&LAh>@#rAJJT~>tN4-9S ;E;b*&(QQĈz{6kt'pg{5=]2'Vsjjm|b` DQ*I$]s9e(7pFS|mK%JFgD93|5 >~pJ8cV4'۳@V_0]ը7xQ:Eܽ|~ΫG!FD ҁݲx~V}pLcBj =v(|Ĵ%c"A+)}WդݒCPKODcB^Ko7 7_!'MgV%B4wH1v em>/SHށLR/x59\ȖWEO* 40C8B }~/6]-tq ch;U|xu,DV28LU`3M,M5KEԷbVJep0BU}˓sCoe9B xtw|Bx!6͸-~M |SQKS{g ֎-W9BQUI֠l@Su|y%GȻM BOzU/֪4*Hr2&@7|Ё{`3־[_\P˿IcZ$鴉6IW6*{;$"&aX8'Lm$?_i5a#9:}_u;j!"kW02ޒbkwE&Z](p֊,+W?" M'E CHm)So"A`I@B3}2s߰/9˲gا2}O`ߙ#($ArS:"aw@P,)H#{1Pl94JK\s\ʛ|^ *KPo$V^Lϱ>9Olo|kЀ!VFHNӅ!8o(dGk]4T7(| u)KҩZKtxNGbtwhN+wL;%\rX)Pf$NBZk~rW9K-BՔ; 7(wOGE/#7z kvC.ynfyM]KcfVtU,D xFф- ew [%d*~(mpkXǓ8%A8`k{C9\Wra(R 3F1q%V4An l2Tp'׎[ѵy_Neq HS9O@ _MP{^tLw#fgH#{6J(UI!j>ҠTQ< Hjzہu]1i{j)ށ͇i| 6S~68o2lDWCwpΏ]B)| W+$ǺJ{FD`8XԌXp~K<>S,]]D6/6K5)w!Al5ѨQS|uy[oJz *' !OOIxL(H)<!T}VT"C3BX96 w6#m#~d pO2)A<]<'?CY3->4w:/:6/0==`ll,2 \^-)v2uTq`ytJ,dV GS gUJC cHGZ0:R/M [?lͿQ[m?lu6J>cltS4F+PDO-a$,髾([* 79YWQihkۗx`p#:`;͙ŏ`uԥp+azzGC*0 ] d(c3UxI=/4NI*[ id2KR͍:7@korr=4Waǻb71 p&;2/t &~w#??IGE}$d,3W'eRY|>!QLiD^}ʌU&>4 JJc~řab /иi~.sFD's!/[͖Ml\ ~h_3A47ސa(uND9Ż$jWhSy:uOs!JBtR<5&Xڢ#MM6wp_J79␍8ug í{dߜ`^e7J.m`/aQkI\]*ީxŶWcJ{%^D:%!2,V h>K?\+J찱"ߑ/,MI+мIp*@6XTy̔4䱡c7d!~1x%"Qi}SaO7EƉ|WEi7V835#Jp,"_ĵf>â26|US1SMضpW"D\#w3nq15S! s8qBq׆Z/귆Z p$0|}"ў?h>|4$PGa)Ń]_>xql:tP+p.zO}\}AQ^¦&aGBab5X["p2 endstream endobj 306 0 obj << /Length1 2623 /Length2 13892 /Length3 0 /Length 15373 /Filter /FlateDecode >> stream xڍTi wRܝ=@p.)w-VmqbEtfiYY+ɵ~ZJU q H.T`ss"jڸك'Gڀt 2)P wpp8x936%V<L+ vvvW9#C@/wPY ́ ?!ܜ<==Y`+QFf5@ rY~ P:)imBl t {s+dh)T@+m 9V+_@ss `ic(y1 `?hc4EW!S+~YBrtsEOd7?ktqU;HN aggg /sk_ 4@)9~!582@~6 +psq/BXؘ@V6ȿC ˿1]lz2a`G{1JW)!rX8y>èm񇫜% 7[H`g? .{ y!o/C+_F26l d Up:7W darn@2;ZHW/\צ8T6 ;ArB/=M)hf< 2K<<>Zc#  :S^/߈&$#~o$`d~#߈&q~#noA(F.Jo/pQ \~#E7p \~#E7d @tD4o=oag4A@n\ޠ"H0s=dv'%qpd,@f ܐ1Z+%2:J m~g=~%mG6RozܐlN ?, 2? N? >Ar9Q)6eY?@QCqs֊'%L->ΕO0݌yve-AIK ݞwY-V|iQXljjdpI:3a|\U[ŏ@&e*[xn>L%͚W=CxltwM9GkX[uKK}i\g =ռqpS K̖or๒,O:0ygs\x_1B91 XSČ KT!9rv23lة~ex}"lvp8M*t{ (C7ik#to0}lq6:nn F,Nu,") N V 6&dA J 5GۤwV!H[ Av ~H%wH$q$_j/f`l-c;Xu#H]L zJsTQuŭͳ$ZFd}xy*B(*<}XVnc7ְqYZI!腲F(I<Ltݤak=t拢|~` Y="cl~+]7MWC 0W')yWf D:J+wZ',Q˕*b]UǭxnNROyc3a'=iKdսWV O4Rх]qiNaV 0'S4 S+ēMJqX)+9U:ߵ#TOzڕ8Jꉽ5nز!Of_re$3 M,]JWp&OJ:zz!*=5x겖+MlݑSiפȓU1)9vqƲ-I؄o\&plG]zFGqaՓD%2g `ĹpoVpo.s c?xS Ak<_ QN#fυ6E؛e.?rGzNg_S3A0^jys#d"#LDJ.؋;e%s5ޮC*&(Ӛ7+?xl,):^%/zeٱvEv|IG/$0d8xSR1Ynw1C^?%b){W="1 Awp5srػx.CڣhS$#>|`XF򑌯o< \ h x\5T_i80dXC<Ӎ?ގLp%їҫ@.^;Lk\ Zr?@g%c tB%rBFa$?S=]U!D|*]uҾsgbƸا;a6(z݋yH^_g\wN`ץ^.#sl' %'8!7t߷?RbB31*`9Y%}Wnǔ-|GNrMiQ5#}5>ͪ N9LZ|`L|h٤IV|'3P ?!^\zdB_6D}g.e80$ͨ7;$nⓗA. vYxOhl?I^6~Ǻ<]]0(.$,yrژ^;(u7k 4{pjKO}tD|%8y]/eޮG?!d> >\__2F)5 !Z AM*'DSq^T B&"\z&F!t0u|ej+k:Þk+* |nqÅ,© /> }!@MEXgy*1'V ZMINԩIHi^O9aqdK)I97߲FwB3`%M0yX蜲_C\?ȺXGTҜz Fj)kg */njvu{}O4 8EĞ×x<¸5٪mx T8伆tA2O4klxibı":ClSuG7.L?:U:j&tLQ9B7lk-OkOŽ?*tsmi_g$vL yzwhv(< ķd-[\(Y~r Y,m[IU걙q;  @cS蓤v@V.m3o yEC*)m XgWn 6{xf#ܡdfXWҀoa:KQ)՗vRRC̀_`)JKј4MR 2@gMK%Qy`wC@qtylEK֘wLc SDŽs{V7N-=e3cTD-Udň|XSgܾS'&tX/Vik#O5qf_SPdzoZ&Œ0}Ph1kcinn xd 8Ԛ$O8h^WrY#WDYəx|)7-uHN7~7\$i8V|-lbTZevev9Zن }{BTfR}!iQ i#}`eE4xl5p!~"CQł8#=LGѡe2)!I,vR/ &7_{Ie/:E2lԲhG1.ޡZ6+~,񉟊=K/՟zb ecFA~_q=|Z^Q^HY?3jkh1!1nmC#lSdϺUj 2 fV9La 6&?'Weps ؎2o0 6CRZ[Xp'̭0M\5#0Y=o`ݛA reĶ_.gUqј?z0/vWTANQP}N6},} &ޱ~ĚƃҴD?t.?W 6+7MPIH8 _4íuߍuk]KY-7a`VU4_8!P H-!aR*C^|Tbi+]q)9nJEݼkR錜XS8a\%p11'BNCѭf|_NNi.h,1INmP${t\Jv@`1rFq8tOΞ*n !׎f^PPxi^'?R]U ~Q>YҼכOfk {9c`gE iN j⛘Mkgf*OX2JR*gF^ AtvI!+4$F=,rjq~=0ٸ-4} #ʮFX|ovM!]78$9N]b;׎dDЅg*aaa0\.G* ËA-G%Zw(Ff.7kk^\W,⁄$0eE!2= ^/D4FBk3L_-6_9]Xo} _H} IƓQ5~Ν|vvk4fn vzۺnr``9gXS~q>(ḻZ]ZQnv/ V3JzŒS894QHߌ9|ltv5Riu`4)G3~Wi߆zSi$"<I ^獯t0 s5%~KY0^~ xdhd]^.3@xꞇ'vax2yd(&vk $f⒎YdN'iQw\E~3qcCv-*+' lI9 Pzv&hGQUl;Ha)bҰ =ؐ@&@ AM{:1=R;nҳ!(nZ &')h@i\r-ءSRgBRx~ rFP"6t 5 fN,f(8 FFHkbeJr v@.͖b!pD5!sgGZL)o@VDe/}u2˛=͠Q/gyό y:h!Wn_bQ혠Y#gwDbm }vF+>Xڿk ^ƶ:/ӕW&a1^J`0UUv-^ 0UzS\޷2zcIJ'Bp5AŅwP"K\mfW׶Nֿ*G(@ГV_Ŧ3 =3)2*b3Z .[mb}/h<w.QnܕSZ)t f']2 <2&'IēBӜͮE44'[1h%)ݽ%@3:}EPk% ,,Bb;e3ܸ֌uj⟺sbb-,W?;I#YDޱ Et /ӏF}(wt9z궅B׉0f ajqT?CL-wN 7 r+zX@ivEF~ڊ ֭~ ow&lF|6|X̔ YrRw(?m[_>ZO ۑ){iWkޅEQ&x0$!G s,f>UY[ֿ3u6:&  d2fM>5)A3l.M!֎o*t<_hşxk3I($Q8plkWz;KKd*8ׯ?4jAJs NB5Bun͕]ռas/uAL.sߢ|ExЫo/T~ԂxB>KL:mE)F1߼lN 2حIHŴK~_w533c|>-㊙#NB847C.ɉ ÜSko(VP88Eg. t8edC't#U+EaqwґfS_ĖA+tvE44>-7ݝvьh[sJ+YDFYfnl%W#bҷ۝ru/Z/Gfg `&)n9귷NzK^{c/rIL0Q#j9< rL|'؟;k2W94ONuR'>84!-%~מAg7]Ύ#x}nc-smܕqAG'C!1gY:JaZw鴝L8w4P`ll ,fކ"ͲIq^AJՆaٔNE*&3.-&Xmn[dr}ָ$$tHȵ߁h?, !Eu5}U;Iq(9@sB? 2 u>?'xŵuƢuZ)#M*#w8O+puUWm2 ئ-ܫ>|p壒IXCX]R7gX:>^? #_G_x0U-gy](M7cuh--!ˁƛne9]Ft IXGhy7҄ΏIes~88r\xO"EtMZVfLBZGg=΋EbgVNސ,[l!%ŮMeeIqSHW(I҆К8GRU]=Fhlឫ~TŗUDu-E>戒}e 3~N1DC>6rfMƞwD:դroAW{/HPPa8;kKsn#PŸPp<0|HqmQB!?F"M%kg{l7UfsꙹNan>ᘍ8A"c6ZlcÝ:<*&e7 K>ǥ$q{]F?H9:L=JSQ|'5̶B};ACVyoo]iz#I/6#^W1O{Bj$#GS3:= lG}*׺QY tb@_~xbv D#ڮm'["40gՆ^DOvcotՆhse>?Aeb+m1OD._N0x$oӭ`~6.22zB`;c,Fe1{u0 *WK]Ð&L-}DžV\ ZogNwe"it<H/LP⬕Z3VW {~mk"z8*O9[U6`Q'|98aV+Ë^}oJ8 *AYJ-Zda=éuK(Qzeˎ(XvQ(O4{ЫxݵOS׷EHj^bh/W!V[ .|&1o\|<Ǜܒ-05tnOk96JgLΨށ◄0B{?-LoTSz tp ="-W+~eW'C}'ٮGb*Qpv 9-SPїlJ@7!8BbNV0;+ Jŵ[DH0Ho}TZIFÒʧKQPd56;5%ln#*lk8無;}N˓'aX_MXt}h}{8>uɑSBMݞ[(صa疖_+vo[-l9|h .k?ffziлv'kegd| _&cfGЌV~o>p%TYrFp}BU (pr7 $vocoZ i7yYK& RZuZsc rѬrFMۚkm%TpN_ش+(/fמĸݻO=h|K Q +B98zW0rxJ_'FRLB}Ol6P6 'WXx xU%Hn_e7fCkOZbCx[___DU3rC$3wBڏ]vPow؁t^|6ĭ"8v˂ǕڊBCq0<I wzNƒzzQ=ўsXBJg] G4 z+-W 6޻:ElFj(ix&n/N*.2F'T?s7uBdulOJ:'R Ԗw27]I.TFA!VɔD;[F5'1D남96 {LZz);ñǣM#۟ __\d; 81C0:ɳ7(${JMx7uQ0-6_J,SFRSaHhp\̤[P;0nemJB6 veZW]m~)lʽ9)lL'ݷZիel;Z&gF@?1M~ '3e$&uZ ϟ2AaUEVa[4YWPfEbEf L&'kR9a: !7MJGV|SY3]n(*k0/ a r ({=t\6[LQa/p'B>vt-M(5HD |pCWӧ `EI6<k}ǯIא˜hw#ڨY\A}pB 10etk[jAntC-'A+ړ>Tndxt}G!h|frƚC$A4Ο;% }Y4yZx#=<4~8I)en46#>R9Z_-^E$8(|,dG~Zˀ~ό8zށ%X6ixEGwQcQ1i;VFi)KvNQWxg[;Ahsބ3*uLBFSVŒ-Eb;p\bXP )oi;cVRMLoU-*~,JȜ؁ EI›<( s_&b1ws$q;tamFk 4X eb) GR=TK]aT9.Ak&~- 'z%<$~*I]5n^G)0mlm_u#o͝WiW Ғ&^,f0}Ǥx4NS1w#z>WpAm^Vl:4ndfV&x)N\ρSolZT=Dw1`ݫoJП|{)uD~G7Qˊ=},\VY+_R0z"„бJ$gRrOtz+Qeu!v*TEzGJ12S;h^sDpK&7_Sߞr~; -vl\Br~ϼP|I-עd4ܳEu~MC:e/\ S/BҰd'[MXD}+zmȏUMay\D=h]ism DPuu/BNm1v$䪒@S *\ 2)'&vO$ XC8;Z*[dƾ}XGPsU^jԸ uR;뻫4%;BҪoXjqYɡB6ջjGEv](tVzXr ĽF[ C>)f4n&l*03\`.`X?}[:<"{7MeŎ?۟dTw,TǷmF& Nk#޶!$yHUM}V >fBVݶQ4?No ep\Z|L5ThƎVb5['bCSKz7zwB*Zeo jqg򢻉͓Ʒkz`H1Oz'0Ɵ #$fi);Bl{{P[i~bmW;e W.j%܆F@X>@|taewB9ڭPе$OIq0j}Rl:|u/9?S* |x7jJZY먇fvMi#rU%.Yf{ڏntI 25MZnM&9Cf * zͷzՌ%%.\Pa d%vJ1T\s} *HJel{'QW/X>/dKkk)R,4o ̾Lp}rͲ9}:!Mmw񻚭h IR+MW#|pFWU8aǣht8;bn=_IRwM"#yzoύ3jb9OR?>A endstream endobj 308 0 obj << /Length1 1755 /Length2 8795 /Length3 0 /Length 9916 /Filter /FlateDecode >> stream xڍT[6"H 5tH  0-)ҥtI҈t(x}}k֚=;9>3 -c(\|<@1HW x,,zP?v<+B#Q6y0Tݝ|>!1>a1 'ȃ=6@A\P;{$j<ح9|\d!p5#!ΨN]5/ v {$Uӓ8P=@= 6-4ΐZcC:taHO0@*V誨4]!.ks|<|G2 vlN: ;!`|BQ:( :?5D N{Mf93]<F7_tnݕWQ+efA@)xY^@Ճ+`j>|` w`F vPQfupJa60'8b^Ec %%οZ)+ r "aAh'UZ6b$|pK.M@kHo"Ew'?`g_(#QCFCC\^$5 2.vNo$hAOIs@`5^֎.jz{Ik1p7%~AA/jm ^CR083~ Um xA^H|(oʼn;Bv^ j)A?/bS .F犒_𡪅 G QNj(z=%`xuDGxS_j?fuܥoaf-PvU+Cɽ5h%dWT&kiSE9Jk,bj֏ոiwv4H4 cgv60j W$GItD&MRބ lW-Ձ68J.h2{bאF/H&z*B_\ʶQ0y1#c..lٴv%@Sw0ڶZ=l "rO5cl{o{pYC2,~5YyZMtz.%{\Mȓ|)T.Јʙmw4c+e*Z!Fϭq/ulD=zփucw߻T(DG<ɹ2JsGŚVO]Ir.nӬxB\ӊv>s _dopGlgJP 3/GіKՍx{Qm =z1Ǔ=9ܢzj˂zCTnԸ}@_a_H?"Eﮝ{enx*S`ϼG!7rk_xxiqǣh*i͝gЃfW%lYz3%)1z7-Bm_Qaп)ԋ'xG`r4y ZWOUEgco^K13eg#y(Ⱦ%z)UIPeʼnn-`>|(N1m.\prcKL0h{J23zWFr)(G7!E+w71S.qڼ . .#ӗ@<|t #m*3.ZUlQ'!"bQpu Qgs D];4d,SU\u\oU=\a6b5B]zb*h }ٙ6,a]7j|dgmP? XBu~GAjxUs|۔q4&RiD=c[gSs&>A čĘ9D.%t_TT|F̳&.ږ?/xM:)}vu(}pLآFAʇ4}.T"?PqG6'{~X4[]h#jfqF!q1Ƴ-Zw'~y><;/ʮ6t];.t,n"?=#AxaSCw|Ocgr MخxPF/ޅ !78r*皮<Ҕfω/CA$}H7|qniIp/=#ؗBu e?yJhn=9>6Mb,y$!_~vyFYĺЧck׌k}e'gV|XJ@:rvG1{VOV$1>:\[Nƿ)=-l"aePA{X&B|jz:fH+ dpv˘a,epThE+nbV:YEOUĸa2tjIA(Jb̨ ʼ6](o/~,5T;>kW2 ^&[,#D\}byWE\gbj)6tnOo51֒%^BЀuH?4sMulO5QF""d!Bqa|v~&M:sM'Bf|cR{X:ѿb>*SoSa`oJ2RAF %{Z#pt|R ԩE3քF.RWTGǕ9gz([bs7'!jSB"]xg%g6*5[ScQ//M]V'8UQN]iF Sax/)6&R+T!!QQD"1|^S<Ĕb^`db+TߊvZB_ %TUfx`?ȋj:\|L/R۸3l՛ͫx1H^n}8v.q+f-ӌ?BWrb~c^)b 4PU\:֩a= LQ;cR+X,Zėjn剓 }"Bgn-I&I.ӇH. hgopu][}i!vizb@lo$4puս΍!q.%~zY﹵M(j&@GVXX߮H^ν6E(b2O+&ks]f/PyYj4+*>P!Iٷ-Bځ~x&ƘHVaaz^pq W=sd$V{Q!UkIA8e\:A?~Pi\m#k2;{:_ZXy>Y[IkGLD<5V?wS$ o6dUop%GսNa#crgG犸$k|:>3I/سݞ玑:WH^VbX^hZ mo`c mR9E4Ϊ&mɄg5w?YX5ǖx.w6)QuCƚXW]9 B[L^ WT`Á,AEn2>M`VRW Ʒ%yh&79 3eꗼf rd 8;|:.mi$(aٻW;D{4n"/.9XC}sqMVm8IX5)~rL[V YZ)H#5pd>lBg;x$O>B~ED9+^h$Xu44sФ} x@LLh0]؅k6Ӎ*eEܲo`sY;DfEr Blᴼ8 |εGЄaݖmTzL9:E1Mı g6r/M+)* ZeÅ'{@ë[埖#>dؐz +H3U.Z2gvBuYcUI; s[śOku9ù0,L0=(Lvd!׹4l@>kfq&(i2gbnKJr#4 @ܡI=cCls'K쳇NˌV $U 6* >-䖾:!ac h4n>;?y>p,SXa# Uي!LoBxf?Z%BHt.G!`1ݼM+D[I2'q,TJZrv(R蘨ۮ})FaԨa?(&{ʕp?7-|8ݾpjsӮq)wZa$p5go6`ۅ , ]=T]tޛm+/jn5~o/%a"㪜<]m~=yj͓2.(6vӫwY@SfTd׏GAh^}Zz9Ov>ǦG^A^ !؇My=3EZWa'P)t|aW' _aY\QȉSm,pscia2+ӆЈ4y^y(:ف,p BsIN c}p nݹHa$Y~\,FD2B~+ܚʍVk+5 پlğh}z38c}:sgZ5=O.m*ˊm N )9}>TiK1RH;p{O(s_FVa,#MS:5sǀ+M#Q?#[rOZ};q`v~™`Ĺqaxacޅ. (cȾD|0,7jǙLB;n>圿oG6ggmZ/lq6fo]xY :3J8>6 kr\nHJxmcVͲ'WaʭC#F[ΥmqiW~l08Wh#NJK~BO,Vf()Um Iĥ}M󘤁<dz5Mm>~n nB %h4 R~0WpuS%їUb ە],k'fzo|*9vY67XYJָ9}+zofͪY%e֓qU"]\l^G/Rfpͷ1W ĦxP2!PGsZҝA MȎ<ߜ>[}QSƨ0w 6&35Ӹ "`$T^Ł2M4V@D/'}Qo/Lӝȡ-Q)s3ʥ'~ 0Q<Ge."╳l(8=RE~}BFq)~#ItL28GVb{~^e︁mJE%(px[} 8c. \UfqlT/kMxkNٺn`~795i;*pP$.W^?mླྀb/j'SkAӧǠQsN+uo`3܆0J!&"K3)fg Gc[E݁̽{rxFiIcc;y:'c66ii:{f^)SrHwV8 %*zC8}\Ep}K)ef.#'pi{?4Wy+$22ijsW쯩(l6/qTjgevؽk"Lwcs~P&)2{lJ4~;u@UUe=F' >WIȒ'eLTlaEeUC?]p[7_M Uh/_un< uRD瑚~U K;ŰʔG+Yc/3{\+ծ0AkqJP_#wUYO ᴧשּׂA|g.RqL% >&0:W7#EZCD\Cj~udk2Eg-P}`uq[CEIhqJŗ[ggR'M[-RVsb[^6Kx*q[4>:o¥/똜 77˜]lS>GOI/;X * x Iƾ笅;WQwBd!v8~)[\y*̐/7f5'[92z*_fy406IXnyZ^XB.Mn (? r Df)7&`Q ׸*ؤ0C1otvE4d3AAgmzJ-TJer,Aimf>Z|sҩMM+u|*!Tsk44{xI]1Wj?؟ZXP3*; = z"Iqs_DEf"v&z=@K_M13ZsMg{sQui9Hs*TF),/8QZn?XzN^VQ ;=ѫX,W0TD߯q-qeH^)QLLF'th*C(uZdQDl^nܙ܂LR[t).Hc5f0fXץ.]Ljܢ-17,F `UV|>+1|9N3}ۥ9Βt{9UUf_Cw`4 {gyE_\)E)[As ˲Doꢮ[6}4ಉu>E,OFKmKqI+;Z$xt7udMMa9'XOtjÆM냹1PEuQ\.xB|t|>?%N=0w OxAXfanґA{>18ߪ5~ϯV0ȲL$>8qIbXrQet, dZV endstream endobj 310 0 obj << /Length1 1475 /Length2 7054 /Length3 0 /Length 8047 /Filter /FlateDecode >> stream xڍtTk6 tKH ) 1ҝҍt C " ҍ4RJt#ćssk}zz}1i J8 k遄@/ȏƦC;A="Q0\ y$)7D  @Bb a1 ED `w @CQxl/$}_'$**; E `8@:߬;h/ŋ@Iqr<`h{.ECmZhƋзp!l`$pcpApMܬSQh@ ptFJC g0 œ-%u^'"Px;!. Po:? sAxQ0_=Js͊py3FOBn݋u#<>![W6n.|pTEO΍ o "@AZ@ ei||P`w(tol`4j ߜ? 0zg~0o#S1{gq!<><"~A @T@ |F ߡ*p[@jo鯊ǟ w.MčpunBn^D+H鷟;Üd }3Q7j@m`nUAoAn`Pmb^4'@~-_8QvAoK*!_c/(#`/<>yz1@߄n"xTDgnDސaC|!Mf_Cܐț1¿ ( B@ğ:Wl\>E$L1Ym;XWf$dHT7}Qh35ؗ }=)yj]Bi(Uzi(3TPrfv >Go01{3eYi(`Z3qz_>VRZK_vw E?v0N5OxD߹N9<Щr89}l,E$xV+g\ AVۼ?L.D6dq]cj kښMʭV`b8MЦAЯr1udO?DQ~̌B@{ژtRO%k*#q#^tGZC?pA+/q_(fAqg}9 n?1B>tY MS5HV>*+|Rڛ[O4)y^ێtJ~ !$EF)w[P> D^v=FK8UE3_~Φ;Btk0W Vu'&՝$$)y3ѺGq4өk>6MޒW0V:aVS' NhjΪ?T6OwG'0s.My:0FBN-t+?~lr\)%R0:xHΰ<=y$N,}ε/~Yx4z?d&~_,+k?fÛeg~I'WcjғtE:)Rtz͛$b.?V.%CIS LSSF׬T=_>XTtt2ҙ;⚻\ YH~sF^]KL?1֣0* ?/_3ŵϵIK[k#w׎ fykCZ\M[*~b=% G3d iBA>b] 4>BGgrKTtא 5 ה̶"X9N6ws s5^:ŖO,yf.!g$6\#5]jDj^T?WL_hxaNz3w0hw4sicB[wvp /;_7֘ ,u(4d>x_E0j;*)n9C/m1 i˅DE-J)`~7]3Vuͪvڈ?Q8 Q֍pJ;k?v؋}0{ӣ1,,XaSɲQslYԼWSLC] 3$egn:)'ڊniTakQiҝQoHP}/$tTngv<ASaq;a~OvMH*w"Jaf"O>")_pɪ;Z()tEL1DhԅM{gL|myGM4V#ЧV`[!kODkOm"iI3f"rM0 w)p!S,>04KeM4/*|G<)L~1EHBL-I_'ib&݈SD '̜pUs-ğ zG(py?.S}>PŒ5:y4+Z%-> AD#3]ToQ^z8n8J/OV~GQөע ug'm:Fs wq8=ق6>vɨouMrh,[&H2C|YU9Ҥx *.WRT.%Մ=?ݟ9CA|!˖?ĝZ%!eXEy&50´X'{U0D6դ/DtfYj5Ȟ)OC߃v/C͏l+gq«o(P֗Yώ51ܸZ/Gya0{G+H{UGmyAjXΟ_{._M>rg*\őx@`ja[F(۾d2کIr @OHkZsA; :`Geݗz'4"jdJ!+Dz%oz~l̸oy]Dj(6ޓ ~Cd*L&_.3G¸cClyy_Fn[Nnەf-q"VԽJMMc6QX3 oD'&xd 4uHǠCގKoҘ,NRs15Ÿk]xsi %-;+6D`㌕QGq+wsr8M@0-cSlzIY3֨`=YسyG1x./+,–w[+5%$v_mi*2&,ane9ӗv) #DOB/CeO6[d]EBmU1d_djs$tCSȧ:3{L#L|+ofFp7x >ktԺ 7lt#"&qg*?AKoyrӱX,*G%eI4aX"HfMa轉vay"Ep]fPÝٞAU[ !ٻ«ևKarE%֟m tMo NS8*4Hޞ6a=;m{j5j_e2ܭe_K&~iS2j9*3- #i1ϸ~Ю5SH<~Z74$wn͚K7@3j6Tc @Z :w9beaʡM7+,\Wbi}G=íHܤ{f*/Co "$=`ТfGѥ/t n4W)#)ci;~tI.Zh&+(5 (P*[3ֿg R"!WX?p> ;5=Kd[^ᘒ!_? zћ䇴mA؉u쳩Dz4gX![,u,wqoGQj\eޟW0'5YaO+]EH~)t!=>wcҿKʦ,HhCPNC>%1wcziY|R0-cmt>vz<bA[-J{y67>ʘ60 VR /Wy,{htߛrS/,"]Su:[kk'W׼u@ JnP"B&X;0*ũxRN6=+D.YHx1|P~9",+g6fA#!U8 (,RUvh>S4[ ]XqFCWoP<|q(Υ54<%WCaG|=xOfDIuIXs9t>xy)MTݸIax4]>'Ieb [N]m܆Wֶ\Nڱc]*h3-)njzQӤ!(zu%;0΅@M̿Rޟ۷ʼn;Nw6"y>5$O7 ֖m{tbSN&J,y|zIG{b]Nf_Lz]Eeur4YgVKGlfL~jqY`b(y-5s^*niGF/ΈlY-"t@s%lqq%?_|T=ד ӷ%>y^e"Cӝ(nd=_^f6(4ǘZ_Q/n!Z4[wu!~*V ǽ' !,a>7O_.0T\DK _tqZ[:5ݺU~Ѱ^#>4Δpd(]Q% %hjB>3doetb\ ~>6DXLhiI6d8@?9CW\.Z~EPΚOXL슦3|a`>b05# 3.gS.ˋUpw˜Cf_WE.?lܿ; [RQFQ3_[|Fj*<5/3 `š8CEL?Yp/{lV; FrOw ~BPr|S/:Ҥb8N *ЍR(<4Ix\2uGzL=msH=ઍzЯoQz |q{ö7oL[Wvz&_Fʕ2u:[X U _eFi5͚6EϤ9ޯ[fk^ǺZIL#o_$$Yw!5gZwX_m!Ƕ4JقATsGrgs63ykhmCj&"QPHϡ`.W5`:=iEQooyr'L:iwlsϦLcESRfs軗 c+&MżݳV/e(n;}jUIDjVU cA^z`J$]| "kbQn!iKzE1SHTp;xSw:`inI:4:h\զ}>}xZY]K[-"B}Y>>Dt'gy.Ap.'%f݊@摧Ɩ-t1P| G!m5Wd rRݷҋ32M\FUD M.|+ϲuz| endstream endobj 312 0 obj << /Length1 1455 /Length2 6559 /Length3 0 /Length 7535 /Filter /FlateDecode >> stream xڍwTT_>tHsi Nf`KJEiD ABRJ?g}>gb30T jHFPDHXP56  qq1.?f".3( G"d᠌160O<\1@DRFDJFX#%=@WB"h".e \.Oמ(BQp{0c`P.1+ qh!$QOc` EyB!φ=+wgBD\ m7F:`((pipC.5u}7(ⷳo"B"3+lotu#|Gt0t`O0lwr0h/wàp-~eUD E`D?S}p# ?xLpwK6G(),@= 3|A plw^`O(Ay@I=:Dg4C~GKK?] D|Af&ww%%AQ a@DXR S߱$ mOŞg8C^ -r+a a3&.Hh+åh=0C.poVE_G8[-? (",_h;_^KI%UH(؇/'r9/ !s\8 QD?O O(( \B!=Za~lB]N/Q]늀BD&aNUa?*W>-4?L茔pO>\IWqWL=gc۪53cFKsIiW0[#dp|/pr!3%%OiTZf]π+ -'ڂsgaEG|ai 'wCd77דՙ*5OdكkԾZl42XJE o'J'Q0m+ hbi e,XM x''FѽquPeEVl%6NF=o3bF5=*6 0vR萟*^S5:gK;(O^oA֐~9>]3ĩJ%=ZKEi1qE%h)of|\i0]8qef%o-s5SiHo2' wȲ41R Z -RՇ?b)5gh%@?!, _d6۳/?ǒW:0|&@.zbT0A:_JɁOa٦җaESC;TӾ-ӿ;#5}K"S+HpmJi+JK[sqI\wBmꄡhDBM˖Y>bLѯf}48q6;f ? S2mPL7_OOE"褊V>&h?7y^w T<3z]-gwB j!~ 'wuPI>8 c:F>l.t?{^07P?3 )f>IPO$JvIy.įJPY|ߧЊ!z57f) q\^;P,lœ;RNQc!$n{yNFg۝9MsxY+ӷj͋RM9~oyf~¸SydqOj5J:=@&'/`Zli 7ۼq%zLnݜ.M`bF@D03^xB%Dy4}kAOֆ~>q\u\͚%B`shB*\)sI݈ͣϰ0v:!eu^i -!Budkt]L?X֞jIMg@> #rI `%1Ȑ_Y?kcvkϹuf !̖idHꑡR<4iqjk:D\9 OvSILL+CִK7yǪκxyV4&^adtLZdvvSOJYx|Zmz\vU: +^Ĥ͊'y.,-v߱$(8F+"CwEީd+!\Yi%vhWOaU'mo̤h^GLs"V"KJ_QIk4ᦝ}J TB?Yқ&kh@zhpfTsnɬ@Vmv&q["B6njs)~ipX ZwHr+l9F_46ZC)$w?ƒpѨMV%V@98!;xYy)i[ƹ/q2Gqq %quN5gM6Cl:ĺώ]\pw{Lҧ[W@4Vml|+ in[F^t \xp؆ vUWEo bO /J*ǾY#T]$,c$ZGpQ>ds^FcM,5fU\ETQrzQwH!ջE;7xtMRyYN+Ϣ~ܶMF!W:ë$^lK9~כ?A(.pX?R$wA̐k&7:b+{pNXFZr0@# |q;J]KkdYT75H&HK LI KQ8 {GVISA f T5L 'mO5-=9[6ɷmDjC yrWqRt6As;S0£#j-klTT5W>ׁ$6,\ۃ'5csz m1lE[k.ڟt>:&/GPiߨܮ^M$9rWwεA%͆Fx+-S(?}I P8;gʓPpC\@ɄR i1> zmN~O`Y=r;?xn4e '{"jĩ'ƟvԲbtNYhQ} ɖu^1zi,-sVA#}6N%߼?i˷#C9(;TKgo;U>gI8W]6H\6ıvmљQ_s߽T*8jHBQf|5 =Ӻ.~Eu_HSYLۓɧ[ߟ)WeD &MP];G;f^9e81uP IiƱ  *iQ2|AlD(6X߮GauԳ7OW/[e_MOr"/Qh'wљ#VRNg~Ų4 H<~^wMI&ż΁^0IơOTb FY'0"nIܣpp+C{Jrg}xq)'KW%pZYU.>jv KSqW6N ϼu%9|5dn1mVo6לRVϭ]pVM6^wT YD004"dIdyfGӬ F{7*C|кpH>P;6hʌ ^5?*1.OiP/MGŹM4w)t2mgS(De)cXcMVS *E3(V52x4N6O 9*5ZD95#oQj%-N\]fG6d'i}j/‹ 5;NmSǺOռ/t zO>5-3O֞JnVԑT†܃Zip5۔3FCކ Hv~PXў!1bJ|j\P>~OZWI+Ìa<6~!"f9.DY`y!5ޱ0<=cߓ!%HJzҬG{[Vm8Q7ZXϲHr#H|ea3+-=zztP~vשpp|-jfa S?Bn\ O?w{АVg^H."mw*Hoe֩U#Cg.c-? ~œ3~tV7S!Po[a'MetTQ԰4 >UDž9|4W,e_/S-GS%Ӱ潲J7 #+SVN19auEC8r F]-ʽx+7QTQUz^)5Fxm&NQ.FCA?m,ãɹȺ.q+aƇzyg\_%{~/r%JFv"ڕpR0d1lE-u~0D;Qo:i#!`&12*N˔YrMфsT{3~V,~|ib]@i.gm0 a)1S`H294q41`<ײtZCmʯz)i\)L 1AM,b}j<܂SˏN3d (P^7- #)e=7+-6Od1~2WSS08{MqпbxaZIS?.3KA}C@UҵZ.\2|tC|6 I9՟V{aE=z[ľP<آitW>~L5 $y+f~K촦 )ιbrߐ}']xjo&\@_vGZ ;[WK$cLUT5g->)[c^!vܞяO:{ޣ6[1OGP|4mb~Cu(y9eT?N2훫t}'!>aM=ؕfLr\E"U7GL%aYbЎ +ۏCN,H,]‰JeפYte0}Y93V' Z_}{wNd!݄[Uq8&~i${$-B `^uh. 4wkGr5LFf2xE,=M*(v"" *yD>_}I +^ rMB'{2DLd/+vDA+ոY1؞REUQCXTy=@=wɧ<Α@f1iP.z`FUeQ}Å={\O8SCX h(o۠=Ү+ь^;>ʎKN-Med Fm6j=VB!3Y`knڕ]\P.+E.Jy| }}'OU+C0r ^H?+vStn>?BS% Oe~zr^\HYĵuvK(~IPr>4,GPZ|w3mU qRǣ wat7:`k.o*T:FL1D]mr>Fjf.<.pTr~ endstream endobj 314 0 obj << /Length1 1648 /Length2 7941 /Length3 0 /Length 8986 /Filter /FlateDecode >> stream xڍT.L# %:$fHAiPb`FRNV;C;so{׬53?vfM>itp$Y5q u.vńpg 񇁬3ₖA\vjH౫`1 ct nA b hBW U-zLop}RGY %S?SE Rp!;Ͽ ФuuA}kjh0j] CvgD@b_r_Wfpk"Q_ G>--@)[G_w"aNLPD8;C< KF#E7~ @ X" mTT% E ?HuЯ`@0  @tNP щ`t"? :(, 890:D{ѥ =9 BqpJe l6Rޝoeon)"ި3\ąc򕷵* 'sXM_Tq zݳA)J.K2{|9=燭VpFjH,5b{R'8O8 &.CڄWPk[=PpBƜB#մe̶k,4W]e+(DLC4ғޙOBU+DB)ho{=fK좥L|!#r"[$}U_/M_#ڪjR]O]swXrTob*Լg2zEc8cwoBHX uvoһfżÿ£)K|X|sm_ƪp7EzpUVF"ATut*X#9 -*&y^ A1!%uʘZEQs D~)#!E>##21KT}F$( {GWo*Kn<`-D t֦mkQ!<8X0C%Ea ZqX}²0SyomOs6~5QDZc9a=)OiN0 ( x:[ׂ`$L!m94=eW8M6u ^>'&&˻]38Qut9f7G2Exwn}6.o/gH=M\;;z,}'髕3?`5FS6%|x?d9I>Q@QVHgdï3MLw[N:'d{H ;>&j-o[%#8gӹFm()tY ܆Ոsǩo`'׽Yw%y"P\D V<-4%irk~[t4z[:`b6\1sŚacAl;5a26pFdbPN!vnuoeJ=BuFgcVoc_lL5,rFPa{`Q>gl:,z/Ȧ%㪲lo1\ڒ>oF?Μu CZ)js#nB(OEi^5@&0˛XEoWVW>/ol9\-ef,ԥ tOXuaWKQ5ùjGSv[tQvs&d*SzDBrifGg۳٦peDOeS'EQ -k50)͗X||\F'Upw1ըhcYcژ_5It.~"RrZǭoG݅w+vZ J},8, (T8IyF$M0渹N 7pHhjRRHӰ1:Fñ+]k0_ )'Ϡ5uCYӉA4#v{ H*ɒcF]Fݘ$^' *(a1Ppe_W4ex(1OQb o~8YSP8F:MĴ4 hFc{s2|nW>oi3slg $s엖 f7t8%E=^@ϩ9g;O.Oۢ*eK'F"G[(DC. ܃M@ uv/5=#Ϙyri@j(k#Jym>+'*asUpڶGX%'1/\?$HkrZ2d?oHƦe3{ݴnxXjT误KϷuh(CHiQ0No~цQDai\#R DzwZv}d3-. m8{A2rh\]׋3 !0jeeI8qNzeڽ3:}7hGUxqL뱗r?^;}I,Q~w׳-.'αj:C}q}JY FɃB "cBlI*3B/ITkc:q5KM3-\ffLrٝR=tG~SGh#3@aD*@-[g .p<qKGJ}dB&ElͰCkD+-maHK++o pT8n Kۋ U2h1q5o|+)q|(NӖ"i()Z`I>Ҳ.H _SgBnSy8NJ_k²L>{ SW.Qϴxi s`KVØBY["poXR:+ tn%9H~EJE4 /dfʞqԃXI)"F]0l)tb{ :?):x/d Ro6B}y buL_YNo6CtW45j=1OjiRq`41x~Bm1SmMy>Q[/hξl5?]&qYjR^xD).rsv3á L&ic,wU}v廙/ujtZ|ɐT(zF ah#0QSF>l䰿.O~ܘv~'Pޭkv@8Cĺ8dz=،S-ǐZ?']vqdUj4}MԫYUoRKO00mwlS^7P0q5>2:$p" $\/͹+9P>otHz "ՙXPޔ2 sⱠ :|8==7]h)}23T.Rɸ쑳 S[ps oQM7*(5RA(rɰ`1c/R Bf/PTH\3QbNFr.{{+7cT^#k4+Uß\@wt5&9-~ 'ΪnEa^u^GEU'ⵠ8%`xF/:ݪv 3ViM: v%xe5daTK~%MQ}4b}RuxQabeFdXM/L>S_[%wWadc7L{F 9l^?JR1vQܕ 5>d84TcOZ SJ|7M2Hr4T {797 (.r~.' ? )$@bqINWA>bjz6WCb ~ ޘ&%Tӄql3g%1E0=bocԠW+S.X)I ۢVн!EG]Ϻo?-zo J~/N/}= l)e"3["*|udyi=3}UԢY eUѾm'aaΕ|vtz N-rf)/0t&;(&n5flZtPVꨈhj7C](Zc#zMdJo67xQk֭E30״ؾ/_2G~)2@VNˍRi8w( LwʒyHӅP>jxV[ qWnvg9|/rUlޠ2؞Ę6[%.FрLjS{ 䢳(FT,9/!ٻUUM&;&g BiDUfa)_{[>~B1RП}lJ>̈,D:8֟pjo].],Tcoܥdַ0u N+ Jŵ?k^x^aQo= p$Z` z337<@`p,SeHls|ȁ${Z=44>73D _{>^++pP<^8xq>Q|˸MFg|6SI. )6bz$`Yk(t tz'bO\ݓZ:xDՎ[G2;MqbDyD)pAiMn8{LmR::;g۫TTM{b"o79#K؊u*vVDT2.۸ЁyTTT0y|x#S@f"j#KATAG)YwhTݠ'\zLk4+ han8Mrxu G8[k-nY{!I:*գfTPEeuqFw䍯gXGE I#Dtes_A:;FTaX˯-]} Cz&G{qiQ Q!lG643'_o3M#w/(+}S6 h܄P+g=yQ\-"Nʠ*[ciSMe9 !C3,oO\QST8ƎsNMB>%5CX+RxkowΚ4.N_`Iy'9}{?o7W/ӔY4d/ gV EUtJ;>YeۮH,c!!f-ŧQx"܆gQZtnϧɑFeFww9@Fj&#͜ղ*m?2{{*?љۈ-. 9%W(iSG1n<&is endstream endobj 316 0 obj << /Length1 2277 /Length2 14026 /Length3 0 /Length 15355 /Filter /FlateDecode >> stream xڍct1Olhl &ƶ&iԠ1;il۶sӽf=;CFL'dlkqcg|UV`b02322Ñ;YpԀ64pz8L,&vn&nFF3##m".Yz 쓭ӻ~PQ8hRY̍ lNf@wFVe[#s5srf`puu7vu0姢;@1w9k?ÑTf)ۚ88+s#㻒1,)-,-?03c?ڿ l`ddkmg`nnc 01dܜh6ƿ m \ ̭  &0x?:999;[ΒᷙB89Oh^ywZlicjNَA()w) nFf b2&igk0yOmn|t4pޞf/cb9 6p&)p0wh3!osflkcGF3hKkj?laa[7'dx%D/]I[WAg((T%g>@埁adc4zc=Mo+?$leE_Vyag}} +{m/O}+lL)X{m 8O_,KQ#[0pp0p{o;bx2/1Y0:ޓ:*;A7o`8 N`q0 f`mA6Aɠ[Qޭ(A?=?݃?A Al<#[FbmGw ?=8CcMr32{f& L?Q.:;ܻ{f1|{VW龍O9Umއ_m]aϖ{+쿉N@+ӟ:rM=Ղ=[?F@rxw|޽1~*{ \m^ ^kyxgqi/y fmx-[Bk0Ӎ8No+ge.J-0Y g]ynֺξ݂N'x"5|UO&.rQ@06U&COp%G/ jkQ)ǥvd8v"g:&`+R',E.X.޵yh`3O4 EgORm99(PMȸ{s&O<[RM%LgXLmri X$N'!F֜(xgSqpu>ї;À_/?Wl^ ʳDwsd#;08ck*s e[8ZctKʍ)X<؝s",m(KMo`]k ]ˬ`=o >;-<>v(%KSYY }ZOl憭d<Gi6o# uYD;jϓހ,WM񊴃a]vs_}4(~k򗟖zp55LExG=D[]Wd91"6Nj UFfAT}sц.b L[؆Taf6l+1@6)܎V!+E~TeA0M3Uo:|13tYfwfv^7yMHBEӷZ^L:" tA1n3 U{q(ٝeFL+[5wۉ灮a{,m"=S.@֙Zۗ'ЕsH8du.fuT5v~T 6F4YcW-ZQT"/ŔUTms6yɘW[ֽPFg)hn0Đf9U*=qݨ&-xݳ+Y8Ɛ`Ä5p`䱅P@wAcQVv?P2& :MNpy'Ғ?wEDr:rÞ7ҡ>jAk\fG8< ?4XD~HK c3^i{p Y ~$eJۜ:ȍ23ߑ(H74d;דaZ: )1L衞֡׻݊mFCe$ dr~?CPkMYѣj ʓ^4T'5 YB@K@1%:pxwL_a fZکx_Ȩ)R^׳>,R&#nłbV][#_\ XC('K껈Ԏex>z YO}LgFkbpVB 覢IR "0)ͫ y2dW)g(ؗ EQqx"B r-,!c:24¤WC/t\ oۑIM&z 1O fT Tga KBݠ O$-4vc//:QNH(C]v"NzFC=PsصyZG v+",b]h /}y4f^AJjiUE=uPCUyi7zaG :{jeV8]Y㳓7ii_k?p)1_q=r1 o#)?dNMMWN<|ld9tG"}e&*sJBlM;C_&rTBŻ5E )MPA$mۏcҳzC,WmUW‚QݰC V*q`bglbYN´uR8]DwHd.Cu8%ztӵ=P~(N ^Ruz>8x=jט734j3:SFf]AWSv|:bPs#A}0%qD;#3qW' !/lv}<{pש 1s39v疣y!@cXdN9&9x\໳Edw#TXHt|N4 Fx[tv#?eӌ^f 2.&h^/jISBVՀ$A?NH;RF~{^%vC`iݞ츭4k7W*XAH|upL|>%6`ą 5Tu5+|p3% Ha}ZᯫbXv=BӜl(yEr5r7 YEQƢ`q]#AC:ԋ_c(W.ltԐ$Q%:rr(u0i''և7HʴJD MowV/Vc ~7;ǍJ7džLdfCQ2:X *HG5|O;6݀Ӆ{D7դ9nEgq=S7&9E72 ܐOi2 E[)b0jI zD>uϔ<7@rצ U98ooj Iqsf!C\4rVQ^4 LI6쇯X ;M ת;Ipp:aӣ4eޤҸAIa'糩!/ŶejWF:H؜S+Bowh=7Up[8i!< O]S3֬X!)Boqh57ł$[?٠epϊv#.m;8<;{+ Gp.lkF6|ў], gv\9x<-6c'Y*Y5Gg.eKѥꏵ77:wPZ! W6ű`uwq4FKKM1Ke ݊܂a*+VB؄Rɮ+q -Iɳy κRQ]!IJIo/pvB~~'ϪXuk!)uj]!VSI~b/l6E>ǣ{3d=0fF.u~xG,b擘2 ] QGۣS"Tϼ\%*8A<_,,kfRyv5@qqdYF|4\MXmIFz|~sUVY-:a5fYmܓ[m$MXJ7nT7t79΃Vz ,g"$Iֲ1;*1l #\'pjIڪ_?S.MYҁsu/]ia ~6\[*AS rER#-Em5>OVqP&^2H| G74T( &mG?~|"G!o5W^x`t7+9?!vWSwWUL&;)DA&1k-%;E+TI,|=3'#[lY_ -?GJJV 3}!wȍt Or\n/ P zyu!K Ugd;V'$v|҃/3sWĔŠG ZqO1^ށ`GDYIHXS7׳j;Ulu-!Js _EߓOㄡ /Z|3\):l:" mt-{1Pg^͠n2H0_0WY,ۆ4}Xm,*ѪrmT"l)q0a׮>Ŝ 0] >(?J։p>g|HŒPøB;@&W"r$ 3PM6 Nj%F!@-E82q4RvV'/Cs58zi˫6ocx_IȯTEKp'j4_w6Ǻ9D\~z7'Q3tIJI=H}|h7{)]h|TQ/ԝMKl]>Ω|F+F+EY1":,=O||D]8(W qt 4ro]ExF|ldW"!1RՂas8FQ̡cqC :wΧBGB2DHܽ4~W)R!*'ĶPڂZcL(ˏ 6 B"SC%2dMV|PJJ`rSof$[9NBeUP-,er۩lKfk,ELYJ9]c6%͌R[+myA63 U{Yg˻QEݪ(oyЈ1 ,w }ۓ d m3ay[~\],٥ EE6qd4eAqؓ+jV=|9. sr]|=*уSD=M 3qu pv#ϛ'Td6Y,tLgij :d& (VJcgWs弩q)~fb'd@g^Ͽ ]p?>-%:J@@;[K7nZ+ȉ(3Y<ݥ(- &QO{B$߆۾&dlC*$K=:y3Ow3NGO'ntwB8!k$[XKDۘ?qɤR1⌆5m)Gm![â6i֛bSŏ=hĪhrwVF@NH (!G5]$zJUEo/3hLpK6 =,<Ѵ_(D`'Q*KikSxo+<]Ojt<"bʎg{>쾵5AZhnBX?3|qm\^~u"PzRֶi%G*c@4C>X'0jnaGc$`c&iU=\8/KTeɗج׿ǨɄš{=syFA+n+'k3>AKVyʚ@=^cHFv]*DvIBRA dWPV)f[EL #-lOi!w(%%%汍vQVN4!Ƨ#bm .41]/K#^=-4*ԟjid!lb& @glnEt=xm*vD KQg\:҂T,^NP1c eRw F/)nR~Ѿ=WnD9Y*F*_M92Ǟ@B\04 1StШ1'/%NJO44q/9 {ฟ8bX$^.?ᱠ nb,v6@~c,cZvz, fKSYe>pIkf;"D 2n"kk?V3=*a O_Z|R"2ao]KكvsQH2P2T5s_~kB@J5/Q?F/h9'jfKEle28L}5TdQpT[H TY{2|v;{b!EJ:n-3ZQt8nA nuz%˨7=ie#nʗnj, վko6ǐab^I ]wI֚;'2Zd|1%GʒWalcLRd =Xo3}*` St&A 2P 8Jwpх\r"I5Z̶9|^8 6p=K YQK9 ZMWQw}sTCs@pK[V6uBTw9Q`zm\H_&aQgt*,48Ѽas>>Y7=^ \ytifbvTԊUoV,XуE]b+uCLlvG`^m;ϱaT2rw!&ȨcToj]bGKTh GBdl5*j,"GJ5%rC~(! qpɤ1IAp_( 8ua*=KMK/`&(^1~G"#YYI/}"=M>{UJCPq- P12\*-^= 8p~T||(%RM T2d9{p1z_\\6AvoZ;k(^J^{ʡ[on >$Jzq42 j7seNl96)RCS׷Wu/0[ٲ-}sg<٭ZکCaClu/1}WB_$am,- v]/U|@t gRњE5qqNёnrkr}T 2j^ӥj4tVT(X#͝+5\^@lZL#Uiƺk%z.%tծc|Å@Y"*lhc0ƹg ja `QZ$ 'ETjULZ/dcGX=%kE1ZE>8vdX_gXYy.Sb;c?U|l[ xSo\}[e=xT;yEWq ϽDϤ+I')yg 0#k'1);2üx >,j$gNfY$-(2;/aJ)CDoKb&`[l/1WQsBdTpļ:(vvR8>]4 ],<i9{P>%)o4n;nٶHb 4hHkιd #,qdfatMqP<[xXYAy4Q' Qӭ| _XrKƟƠ@H= VƜە rj@1@Ϯb44>!T~ p#%cMQѶ$i!`Tro"ອ5|KЀйR^V~z0y_J{D2&WX5z짋50k/kSe'UiLzςE`C:q#ϥT*p5nRky=R!ʥ0Ł!"{^llԩ$KD"ōe'WpѼt3̷4 6 EΒ$6o@y@ VЏ2K]BfN% y/$=@r%,qJr\J^xiLEBw*zk)3ʪ[r#Yrby~Lp/ʉl؀$]/[K*4W'oS_چlOhѯ*)ӕwu?dŻ l=qtD}, ֒,F\v1@V nnEOxyboTN~.'Tfe+qrp$ mguIQjQSRa>c+Ce\D, FuL=Cqgc1<;kW% D,]oe aѼe>z9gYla+~9XmΔQ @v2VoI39OC&3d4`TlNo0@GN]R>ÈE'I ޙ"4.gAƿĕÍ_#HFm32ϮZVĢI.$Mޣ"~2Z hM($r==sNYtܠ,ފGzG}rWN |pwLRn1Wl|"o @,骊,;Ų6R̥ ) l}A+F:Fua0nHFW}IKK yT p:&ko'yh~/eծ (@>@ح{FLujᕥx,%cŷmKD7( yeZ(Uԕ=1g-D?.u%i?b"mnGD$çC b~k}^@ϩi/b8,B>&(&֌@Huʪ<}3Js5/?C}v~ABffyNTwq?tw] NV{/>яL~]b؃N@-T#:5*lT@ʿr`9BAGL͂B=[~ƕSR5A /q1p2֚i"x:TT )2>CDZwe6 u9+ +0gm>\D`D-j'69aWDc> stream xڍP.\ݡ@ ^S w/Rŭ8[qwwsLgz׳/&nno:qʚ@rkAmV P?L$03R Pppr89@(/C{GA+P%<!VΰD `p ;B@Pdj дAIA͍ΉR%+ l;]MTLeq[iofqMy%Npxl 9B= PK PQbwvwfB:M]M!f0j7ȈLa-Ӡ$0sKفN듂8A|mnPC w#.P;?F0d`g/|AVShy8RrúrwX@,t/'SW0:''r-!PDc!7@9߯3j̡ #wx ظxN ??W߁L!< wWͮЀ%y X*0L!!q?S/?'$bk?S;?&08VA5`sjMa+!њo!I6W8frg / ?:ؚl`-Pu::zF C/N^"3j sX;+EW wu@Nr8,<? /aUaac8++u82\2\`y@X^ϿZ#`w0}v$l]t]!Nƶ1,tr6k-=f+N3=mNXfSغUEuz5u%̶%Y%Ds)gٺIT(#p-5q:g e7U)?_pn0='e\Z4 OVIQnAs+]߻EqL'-\\g }f^*,=ُ͟J8@x:<eBޗ}gAQzSclp (aP1*d-O68Sóm!zPŬi 5/<4aYԋu_Ѧhj)26l3=OH1y g\} ZƁIpO|Y7{ƼCR<ލg#6~>^%%`VNb}VpCz4cC[[a8J*x%h_x"2 <.$g@N;DkFۡb"c <609N;!%i'M-`2=kYiuM|!3Y$q_rl|%V+M"a 8-+vst /ovP9i$D;p*:&I71&~T1\7:ID!D%\(*z[  vB[A.έƃц lakAImA [UOehU!?*mҙPԪޢ2pYKY2>5˭9 .XOۢ`7H[+4Tx{ydH*9 X_W\?NGޟLBOfb,FHՍ0&,8IM&ߢ_}0K nsjEnؤU{KPHҫ2&0;@vvLTwLB]J6[D$FxzRDaBUL:w;d}Dk/r8Կ<-"'" "R-fcJhDZ! >n["u?icݳJs^wirTS_Wj>Z\_g`I kW|9~?uo?j$[5%9 7F_2CQg2*}Vi_9΅oL_%ĮGΤK|QWp9 @xn_ښֱGNbn)~%~mce"#i;X %ضՕRfuko858ur>@71KoO4Y-) 问 MwM &"V뷟ik[r1j%Gc C0HKdH!ZaXe=۳(_a:I '(n!~[݄(1LFSCS>2k~^TxT1W@! rW@焾#rݪIRo̬l]M|5m*oa`ΩNF};,)ɥM$p7i!d{vCn'St$hz+  f|zw*#L9{BvsdUhMUj -lgxojՉR1 F67fK({$!hC Yǚ%J<EZTx)zzUwbVnYs C>Es˓rfEn>42ٲfe#k6J\, 뗭̨O]_#d'-[ޯSvYE`lBMcA/^L#ݖ+j04!ýUXi};A Tca]`:4k+Ūy>%Hqwڇ1vds~9 ,Ɩ]Qsy-At krmokК Zۭ/SPU'~ &bˮ3}%րe,*~8񍢶Bm=cgEA J:Y%u A;$ڶhх+E8$@'] I`ulBWNFɭ5Yfjl(ݠTOF3&TƸ^i|"{G#iԛL_5uE3{TTB_}n,mNv=,tLIߗ5 h0 mŮgH Ҟ;1rα&+,[~WI"&:<M.K%@B]e7cGi.lh&DT0&]|+#$zD/_׉IuQM|4S_[ %df8GI95n*"BqמuO2eq,ǨT}ԏG]Oqw^`=j"7A0-{?y=+;!Y|i# z31y¯PU.܎= 9BV0]\v"pp/]K9sj;.ũܠP#}[~]kfx[LY>@ V%ZV])[=:Er_HxK.R؜|@ڷj2rul&֣-j-=OSDxEÎU~uϮϟ5BjI؃UOXl x$K-Fr&)Kvb<>)Pwc:)ӻ=n& Ym`xGFS[9 @*rysVΗyZ38ҢNS~Zp 6b,O>\MoΌ% 8I5ZXpUr\ R N" H릝y19+Xt7_i#$$tUgs')R$̓8_a><%cPLL`^RpHci1_-5:^)DL$֖4QN^$ *9 Nb&XTU~vs1`5ABG^( 䄌u@ӘV +[PG2HvЂʊ4˪d532=ɩgeܲ[ ~̬Tȷ!cIbjʜ|Tx eioUywl#,ekg֊aoZy-"7VQp)7ty׆[(F,c{vås¹O5h 2]io{h>ӛDf HkBADDU瓹̎a"Z݇? u8 ٮ7,#MYqz"ZK4TX.gFHq$ Ox}%?C]T8 ?\:y z< 9EdT!*[Lqx !ӻ"j&˗fVg>41vCkz$p4'%mPڥ5Q@P]j=L.\Zb+!> sxyű[swv8~ϝ 7 q->u[+$y$&Y_q;uR J(!; O7\Ci<`Mt]@N~)2;p/@kR6h_K⧳H-S4/yi!!1/&ßw(Q\(ߊڦۨ|9UӞz,P-꫟SyC3Gׯ*l鐍 t/K4ȗw::r_J'K-ɔ(d21^ PY 9+ėnyI[T>bs2=bi*<:->'v|iy"B$պzC>vDގ ޏ3\EꯒDhA};z#SIUgȩ ]:^t3xP%Tktajе\\7}y@gD\.[+Q 幓%8jtĿ{Q gJHUdamX/R0QJUY,~S)CqfagE7ғ C}=L:ӿ9'h45\S˿ډ*꺹Yz9+ϴW'o+p"-S[ŎkB9u3u)yP]5\ n JumiȜ  =ǡF"$bI,Iβ|ZwGpgN[ u촇+ĺ$ŋS_ x @%Uu[2$!Zl.ؔm$&8e}jLTKA v~#~:^WA4jP[K䫕DqfS#Gopt;(g5gWS>XA꣛N/u_R"Oi%4<5C\P<3E;LUɱm#Ǹ_^oRE}@g[GiLܔ#sS̙BFf}6VQ7A 9y'/dPKuYR~LQ nIJ+aqa2`/HZOAm_&sǐہXh(vZ{|\fHn7E>7C=7iJn\Sm?1lt<=٨ vqn3@3C_tVFp8FvXmfa,GT܆Yx{GT0(etOI]ͷ鈆~]YTOlt9k8QV8]J83ͶC(`d\JHw.ZTƣ) P_Z|~hQq4ݧzrS֟x^mftrc1|,ǂ$[cCQM.e44NR|Hd=<`l8"UzCGt/X%5j(XT(cѕSH'+C,~~DD &8Ng U9/ jWÁL[N<-^ǻ& Wq֝QKUhH-mE<5ћ]^/R#4I\cr"2Z}w1>"?;9.XHo^93I+)qAakq2Sv%VVX(#Ir|1(S=:ḵ2zV?abda2zwyi{$i _ӴS]ee ]Wz0%qm 7c{DQ'71CH8#pcժ7ӲV?.MKD_ÉRF`m fЍWW{ks% ͆@>> stream xڍxTT6 ] H HKtH 3 )]J4JJ! ~9=Yk~ykϬg(`"( P mh@A PAr@ܑP\?J[ڦlBp$Iĥ@0(7.PpC utB} @ w(жE9A\0! | &%$%hD;(' q~ бu5 ! 0D8l!CHt@Wjht ?`?~_ _`0;0@WUKmaH: C~n PUآ'k>$B "_3 JfG!  u#^pWPï1=܄ 0hm@()&& @ `'_| nE致PtsE`;; qF;`D0{o#R7k9?aI⢀ѳj?=_3\DC,%ws?~[W(/( h-j #]m=([hF CPovÚ?v_zA= ˇAAkuU`/ lm}g^@hUC $G!;ᯃBv @_-2߼@!op~s~vQ%ywe#:Ѽ7J9I /Km򱢍}3ރQGrQ*GO9BcϢHm,-RLgŻD6_}yy݁|( p3:v1S[dxi]c>Ɵ+}Y\ZzLPZ*kPr?uR΃7H NE3D6"e,S}I" :Ju 7z!Dgd򺖏7 6%G;kC`.4EFBrPjRlLHLD-e]q܏4;:ٔp=@K6&^m)O,+ZS>y#iaB]CXtjda}o۹6.ϰ(դŠ` -JK}}ZmM]߉@6ݬt~?VTPrͅKWiqJaE* o b894n4h w-؟6Ő]u3.Q2 =cJILW콏cf({+%'M Åfz*GZͷi~;ޓtT;\mt⋳KS&,)0՟41 @UWEˆ.,E]aӈP>1}s#w9*РekhIfd*)cuv >^?iگX,Љ|(<&dshhmY4?D&S7ʳ˺`{N2`ϪvN.]tQξ 9fI"xd7izu@3>(g\ ̲r)WMI#TźU>"7TdM<*w綊*?w247bp|dxu(}O8Ij2&[KCӓi#Eٷ)cm& \9cge(ҁrKfؔܡ=.: oUo)ԥo-vs/LY q1\Z1bGIa{e?PWR$,xP=I|ܓz+YowVꌉf5uɊS9dS *#>ϳl(I|Yýl2u%F&`Jo/C͏P6;6Wc]V0h[}HUc^6(FVGս_kRHm~׎uԎ_X`{#G yC e ^.  q([xa;i&DIA{)Y$k(+.ĵ˳U%ÎJtI'Iso[nI~6RZA=iI͈)A0?sxZp6gT.~J:т1A@GHHX"WKq)-dEI"8;ڵ[Keϫ Wb*(%՞ [Mbk+7P@ g̃xxdLQA,ɑW<_0cك4 ]**c+ @;jVYGIص-3 }y%}aJB{ntEݗLwU>$UV~]x]Ki}0(ROݩlrHR&Yb+ ښTTHq^bFBijcGC,+o'50GL?s@Py?c؛/vކeE;OODm ?vzg+ (h=DZՑTpMmVR m;;Xxq6p{ $Zfu85\-5c}:(? !16iu+t}bT^CLN}QOL5Ye$ F/? !q ӜVLI\W`y 5+7WF{_>;`߯3_Η & j_ćuy?x;XҍպDmWNZ*N, fo[gz _y [vpb8w̹e%xv \ b.0>N׻֚/,_3$R?SI,~s|갩XW9Pe5ਫ {y0H9}:44v:c7}vQ wvu*9=Yg8ɄJW:_Szƃ]s{c͒E^S;|]p[[.q'gcgF:/#=$d[Xͅ^S>ܪjU_豵jFDUMw@)igP{,ziGmJVe3nb}ԟ ұ NULEn/z#XΖ{]. < B Uz{$Sg z5Sy'(?VIJqLt=!Y,MBCg %9g;ոڞWJBW_ _-G-{1Miu\Ic_WXt@w+d TE9GV:(v`.x[jP+wN0fc$vYˮMRJT4Gf9)$'A=3%v`@N>9(NY8@CuSJ :kh; #qVr(T+.bzRTX֎9dV&qYE -"rD,R(. Y=$x.~}_qksP$dyEtO1#pnXnۙ*=zrڱg=Rɵ*w)1zҊV e)";yoԅz[߬h̷Ņ| d`b I/SY'7`g3ٞeLQ'vN>>l=cWyXNm݃2]I!Ĩ9OЈGBv&ԃ@џ|.M%e/zOEP?[ #J꿜$XPOi8cƛSP!D]WnRr'??EV#joD;n~(6Nʿ1G(Oi){$>TR_mӍU{:^3{D֩ɬG.!Qҡ$sb'n,H6g%And5c-O"BFM˗C%*gٷ,}}ƒٱ!Eig {6ܽ~28>GQI՛>_z6^~Э.K?h'^cNh9‘/p/`_k [F9?5`}ܝVӯRiy3&4\ vՓzg?RtOZk6nf-eJ7fhL?Ry7^MFW HZ{0ex)xgK͠bym9pa $YgY[ƧM(G50|3@"/'rAJMG:T1aW4*;xIr;J*~O/&'d@QSH;M[^HǫEt N0Z877F#+NjNYvh_A>&#w5Jli|mRL9('cvaj(%\ &0, )^"&c: ؚ{ʤ & KR "n<{K}r' 78ǡTN8 FaUԒ`hj=KRbw]ҍM8fITԥKOAt>VKu{|ŏ:7xS/fFgteɲ+"M+:y.`$ endstream endobj 322 0 obj << /Length1 2613 /Length2 18652 /Length3 0 /Length 20156 /Filter /FlateDecode >> stream xڌt ilkǶmncj154٨Ʒ{?gι\kS3;YX,v&6xJJ k7;Z@WkGYM@2 7@`ccaх aamPd9:])]-@< 1r3X8Mܬ F3;5BX913{zz2ػ29X 2<ݬj@Wd=Ҙ)V֮Q;Zy `t e'c9V&;_&ffN& k; @YJˍ``oabmgb 2+u*TX;2ZwP%nv:8z:YX;[.݉Y(+ HGf tpo o'_JbP NN P@k <++ ` v$Z ?V&_G̬*%Mw(F6vv';7 QTL_$ {h^ZRr-@g X8Y@/3;ߌGobomhn@;k *ͭV v4U hbfflG{*ֿo#+ сt{F/;(`h{8&..&,Ib@.Pu GG `-0Af?,%A,f?,e vb@ ]+A v]A >?5 b@:] b@~& P.&avc fbeehbbZK?3Ķ@eG@ɚ8Afvi'E{?ICfA%FMc( k?Վ.rX[~56e'IP3ɬA'E.PEv?t_2 no%fv(iT5(uus`[.W_\ɬqNv O;n@s+H~Jt?s3q(?nvf7+NT@1%ﯟf.!<A=Rz XA3(?ϝi:5Ѕ_ׯ hhjSVqoRprO;w٥6&;dV4eumGFd6=I(Qmf~i{pDaQCdO+ K2ٝY޳_ګa|uZN!_▂{~*>7u,d'Hbq`kp)>) G1Hөto3s#ݽΡ1ٯ[Q"~̊ F% ɻ(+Ӝ5XvcP!Tv:o['X9q5 w創[60-LĘ3شsڴQ]BkTlˏ V/RՄS?|-v}MAjyw֝\%RmPjs\~w!e(`msf 01~=4W0<;ǻ/n|b1l8:i)IDڷ,~~ ٳU}_E#v16C65ٽO|'U$P8۞xCOO\7ꔄ0dpe>,)~م*yZG}~~eMk^>W v2NM-\H} / ȧzm}pWrDbh|7q(p>^Fbċrj4\tgٱ3޺`ԊM/4UjF1*TD-4,cȠwi:%TԾK )qA!^[Pc1Z|MM"igh:wCmQadݠj `ٳ:xQml)1xLk:'jl+Q`aJꪉc]E0Ž@~5* 3b9tц|. ɐL6,9)vT,wjr;ڪT_2[0fl5^$:0y|X[hm{n}”m3w)Q]@}\Gmڣ)" wʡ\7> "ଽSt"!˞àů FbU?V7ӥјf;tϮ4C{5ti)j [,K$wL4|=ۿ* b!( ʯ+x14af蒵Xv?^&&2?l@"+h\IA @@XfZ%cܑY)0ۚE͛WGPw Mh6gqÿ@sok\>V%D`n`;bLO>1||6|֏UP"Ր}xNpu wǛ>VIS5Q{ODG^0%9ˌ<*zo6]i+@i2hԫMr ],n֞sr9Vla aa ,Ӓqd_,Zr];k,b%rO ]ËDr.ȸn<ݬa 3_nGjp߷ L~ɏ^Oa2'Pe,ƍ5Ǜe ɂ s[ncf>\]\|_Ӧ93[w`K,u}>;sUHZ)CJO O$axz2<5O9ҖV7u7K׊t- }ֽ5ŧEtJmػzW>“d0mC[h D3b*hHvDO]X?t|T&CՅ槭Gz<-7">me4Y;:$f 'փLQ`8 t gF]byKejjR|0j~׏؀@j+Aa\-YiƬH**AӤMV҂]?M4tbg)_W=@T$Fpivd\F.Zʫa,`RrCʭ>lX2gxu*͜ i _ZT $wוy`6w'KMeVZN[|/0i8mR9`x/1^Rb04Kr٫ycNcD̞I0WI "xzTnp.S*p)Ƞ;ɜiE!v)(㢒N]wvG~ symX~Pr>H~{}gSQX}k2E 7qJ?Z']Èlmz@?\{^oaF)ғwY*}nm:+ o܃ M!>?Clxa~ ?>hԖ.ǭ6:hVIm&ev~p?VgFېda= jY:Q׮l1xrn:qX1jYΜ~'؞LE?f)+s<1RCL_tB4v5&c]XȂSH3+{Kg Ӛ}Z^J|ޒG 팳aZCp!nZpo U`('z>QbkW2N P}fK=)M{6XoK^F2b֣3*dl3+ڈFK¼%2)Nί^ lqGQ5! k90b#޸thXfz.\E]cuEK5 7'.~]Y|2KI5.^!;=?4^S!y] MQ{!Y'k9y6O xȏu˸f314p*5)m0E)5] SȖ*3a#KE[N=~ f- p6Τ{柗N&"rQ*w,mG !$y0Z!sM"`JP&ج!KhEvŋ7 M\vtnB[ GG)cFFl[;_a 0]N tw)(U:2J.VK35uϒw ul%\$dԳ뗨͙H e\qQvQˠvzQr8:VhC&k2gaM)'b%,$G |(Y(d\$b/l)]8{ĻA`_edB zD}Y1q:%ǧbASܦ׵G\jm9>3r 6xOM| ʨd ¼]&p}1:o?qjo3}4=.pH`=fjY1yݭ8[kYxDHwf),4ĭ9c4h|1_`ۻ8;/ dM Jاm:"=(c xv=:ɔ;FȐwˌ=Yt8maQz x.ʉ4ѹvH0ZpnI(ՓS{*"!1ېi{4x+NZxd>R1,V}@i*b*L۫J730Ze,-Ї/DIq(5cؘ kQrl3O 3?cfVěZss9yn-k95 }[K>7m(=U<ҎwmUmPKpĉf2oTɬmxZ>nš|tHr&b.L6/~} yx' ?gOSLh!!&M~Ҡ ɿ5x?pJIeƋ"u6ֳw)4oaKuq'Q2|,:B-WDxX\Vm$^S8QB QeU5c,Z{&MXlWoR ˙{e>y&1YY3;srU+p+xuI]C<ŗhKH{v֐  wU314?GP̻l+ vnǸ(.A=^GiUS4%sou.PZU)_GIHZ+2V!$/RݓM+)t8p RgUsƴH;fp 0?nf2 X &}xVSBODak%qm3O z[l !=0Fxw~[O4iHzPJ3̰8UU= TCQ)EʩK$dvNl&vZʂӡ "D^R壣Qa2;TxT98}֬-@ .8Z{o7=QM攻OK 7`SQD9?m4}Stф]&gOkZ5.֝$¼;?ӗZb)VOhA\]RB~, <c UPgY(r$Ip}WDli^gy n]%#葠V+^'W}hr4^OᲧfK@Akڠ !z)bX[]--|5O4ZGaa]t0sO% FU+oBDG?%_7n&yGWF%14/ ~KDYX/.]4)!'x2%vfi ?nEuRFQ9:`c.JP^3>i!{90m_kC䝙_`Sțkbb^֗c/+yIKԖO7:K3˜n {ʋ01'7jghQ-ǵɻ4h|ٗ*{R.+vT={ZHխJZpvnϺufxmRO yzt 0UY07wI!g4V&0߻Q޳Tȅ 1gs0?~rCe »Yדo3`$G:K(R j_W%Ljs<<)z7 Tdµ_&Xa/UYm-/1>+yaP5ys4 &{񄨾mvNN-bN %)V=.pPHhKafŞDGa;J5&Qk uNo>]GF\N!h滭iQpcyTh#+n}`@735(ZWڋ*E/ =Fih}`ڎ_WgOxŲ̱3PjX}S_-vc\2>ˍsv X lX_G}~9(~˩9u]R!s,p! !BC4nuJ,Q.~Ywo]fjn1RIz|sV!F*0߁EA)@뭤,B8'NtzмC _?U$O I\s$[s\$'|r1 CXٟ 6 x3^a!Ƕ0G78Ip3Z5 G^ѳ*mBZxy CjxRIӶOW)E,ou4cFИR_Y֪43u.f-^ˌ9SCJ@Ba#᪾؄C]ݠ/`bIMBH=1|)T*Bt?f4t$eTPQW'ʫGA Y+C:TR^ʮ48軜ew~BL-{&}n"sAptl^z4>O|* }ΧR>?龻 Ouq٩_ȶwdȼM<#cT +Z-#h~%)6{`-6=ysiDgьf'V -{&u1#c o Wtܡwz@\;6EFw!S4E4s[BS?O{Q98ZC)ɴPhbsKPfI=9A^~8W:x`x%:@zTeFc?#:e? IvWkt?$m]ؽ@KX'2pFJR"Z=9ؖ@҇J,I׊`"4@⤼OHaR\WjWΰ5 X /:iJ ֦IK(~z׊k ?KQjI&W>6DHQ͌0h0GygUhr7XS԰W@?PQQJÃFa\&ӊꗭr97._f6FdEm EIEMǣwF̆)+- |UM[l'^#k0(*a'5]ޤhc7l~Q_+c*B#P?TxEd*beqHp PhHC>҈jQ][m02bҎ>{ZXAnJرPSO۲D6P&Q%qaa|ݟyH FiG+ςGnG`BC7).72F 5GLgI,Y>٦NPE"ޑc; Q+.%֓fiAC2rO- ކY&Kƚ&I͸Rg/C6~:5+|bP OQ \}<21ܮ[bkF9#R0ZE18.?ZGp_~f(V< Wz9ۚUޠ[w)i*=c @ b=ɀL/M Ra-V0_Cv:66T f?֤y&,dK3iAR˨Q>N^yڦ p׏3xh8i㾿T p(dtz6vmRҌ? o,H{-SA-Ͻ'߹M?_~s9RP{܆l݂k 'dF7$n ْH42깣4с.1.ưeˡt7_uI{I\Tt>c#ݬsǂ& QNϒ6Hڗo9ىW/Oqy03x?'p/FS`#E~t|eQذ/g&ܹ'TdY>i@-gR71w-k* w}lpw؆UǐqVҰNSD(ws(i `059$,(m +?(R r Ph+Btl1f/ exf*,g [ TMDq(od=}JLOpQzLqzXtF'~MЩJ% XQE{v"5sm 0LMyu< SgN 7Ai$ RAyL G|ߖ97i&(b>KEE d%8tEAV8MdrH\ݱel.Vv\ɺ vNBC._5DX{SbnO)!SZf}C?iMy7+w1)tv=M#cބ] xXPk鯹PI;ޓ+ш хs m-`z&9:Iɏp2uO з. i?gR¬tComE,E{AX ͳr9XYm,1>V׹sYh$%n;`n|%#neAO|H^jg@?Ɯf2&K΍5ٗIaR c\K͘15w.Wɼ$~`r1mGƷ~T ]#!>Du"!j˦2,{ b94P *G Qdr}cwV/9RCjKdJᾌAdz x<0lz \f*L-VaӥxwymޣJJҖLwgD|o%Nܞj$.Z6:'S!vWDU='!l^=!zv)IPﱦ^ϔ( Fڳ&#|3džWQ<s̄umU d`SC4w")bz^?fL|%q+ *X-u'vl'c<qѻz}:d\[f_^&Jo7K`P qT+QjE[ZyLwxtgVTH*e}d=!Xiʩ:w$'4o0nJ%HN gct2< _U3H2_Dd8F[x;[fOoq[qÝV7`\{MrvBwg:pQ:D9K LmA;DN~`)86RAh6,7λ`\ 'p^7fށ4&!+ j)8? @BI>oA.'GtB"dU]'h٦4戧A#A@HSz~$ЭiT$WSz913oV -0dΑ*Zic\5D*pFJLv(mkzvV$t7O=0#nѣ):H/.X$d٨#=-PۆS۩-t,ڗ*s[|$ޥ3Nb#1pq>r{-JEU=:h'99&BdӂU{(Xrmiad+JO$"6)nWJܩh~K1w^xL+qJITP & 3%][o7sNk؜9kkd3,'аSo}`'6!~y4_٤V_| 7{ɹJR_DTo`ب`N[tz3mTgq8Zߌֲuj΄C}pQ n'_0­ij3w ݮ?8,oK6Zgzn@CA9kc*Ž.n' oR )!}<)wpWّF%e b9:ƹ߻}ԐTJ6 )^csetu_ F \9^V&M>><ӌ':*dq 5f䩛>);-c)K:Oؕ,[2ȇ8hYcP:I'L^aAN;}.f=b Upq(j>&? u&LwG;aonD4 S$eJ{nH~mvܤ:VEEIoWX3dF/Ga|&A$%TE*.;泭c)7't`C\ݴ⇆s(Z(xo*^jt],IN52*qƮ_3Rn?%oZ~"}P!H2(q5>~3QTcʆ#Hod>b!ʴ94A=1d k(B2ltsgkєVtsO=UvU'rgPHr/KpĶVr- ʯ#1u' .1=c# .fNnHhj$񁐤0̴"v!tAJ0ZuYpw#cji*}}*t)uVgxȓ 7Bd<oj4*CD>-"VL_DP7U~2ׄFR1ݕ/oBGfY٥z95󳦳R^={N4BB/(l[ю"nf_݂*Qpb)JĎ Luh]~sMCn(xlR:n"j{- 3Y#Si0%+N,tsLu45gѣ|n2s8`v@lYN(r6w\rvb"7 ѡd5N8xk]?dA?ϖ~|fj=x?o]{rb3k l'}nlPEWJdNcL.;~Q^7lWC%| $m/K"S/ #wZBlj.ayNB: J)t)I*όKl|NqFe%+pwuoz [Jkp>ɭUyQfِ 6PHߔ֠a*Txw2AQJ,>jp N+CDN-WwYYq}qIU;]8F[pYV1IL%n=G%9ld>| h#k"Z^ VyK#@ln#!N)nKzڍXM-"[?{hxu+qghx;c`ع`^nLr=`Hc[o߱9Y6{"lpSL`? S_C68U$Za,vH&Kx•jI}|i wNoG;<.pIK(ѬdpW2I!9_Gy\^DrvzhWry&]jX]i,jr%t||rme]'?! u]J3yKqgAҾ=*ƒ" $f즕8/.n INag+F:߫)wmVoW?bvŢ'#yVp]`VfޤeF*Efx=Cŗ o!9ޙ/B`.O_vp*]m&kb0>(L_2Mߝ-/ClR|jw$lF;Xj_ |:aiiknlF0+ucMpk*srP9bc PQD5εoAmd/ )D̉+3w[hڠB.ȵҋF%mFmG 1ZcN8Dj*kii-VkPlnG"hՈ1yr\q|(Ɩ\fCi$Ɏ<Ԁm&9#LY^^ o"Ն^M&\TS36==O+]'ٙ'ir<7 Ip~=.~1gP[{I}G 1,Iѧfm6jKD@[ES[U4žEgvG/uOBςB( V!dgzx9ZF_C0߽̆^$լ i61h~abd/G PO5;^2}tKJ,bFzg3F:Y_ݧ8׵U cp"EIQP)!Z {bEcA#Y]AF}sMAFָ%dBs+yY}!`ׇ>d9 ynk;a x VxxlbNEt>K:AҦdـ*2d9daLaGnLne]7wG@!57o]-_a{=(z XC[ %I]1NV!3~x+Yަahs o'Dk{ eFӭO^hDB^<B_2q&.ЭvtLejaUr=tqR,OQi v mulO,җ]ĺd N Y Nv+' G5=-xJVD"YwVZ( O(?y;$n`8HÏȤt' §6]ᳺtB@ <ޯ-W + MNPQÞmǂX~ڀ06:;a4%>_ZQ4h1 r؃9`9b$"O CѕKa\݀#q4t@a{Nb endstream endobj 324 0 obj << /Length1 1410 /Length2 1837 /Length3 0 /Length 2751 /Filter /FlateDecode >> stream xڍT 8T{/IE(?c0ތ3g89bƵܢMEn,M5-B%FAuy3}߻]kBga L(D2 8zQL6%T"_G0/#f pS=0rP,hK d ƣ'fDCa>A!QϗW`x:sa1QD\#_ B`Aw% l$L." D` <\xr5"AE!/<(O,0b`t>0(DrB:̄ D4 .DX` (K drόc"f \ހo8!1>p;ecvFY >A:ƒ!Ib"4FP[KCGX!4]oHXd)c,H~1x"u;$%`1'!l#$q0pR¿[ H"H%|a?P2N? K_~PN7\]<&Wtp Lfj,AUon(eIL|_y o4#!A>8.?iD.Bg yW8}CıC1rW]xV3wWNeӃwL!=ʔg-#ag⒡%ﱡٮ'ܓ+26 餄WdݯtKN.Nlf 9t[dc$DV+z$ e+^(tFc6~ ҁ"Ga >QUaO12zTbÞ$P;bY~O:+)^SGyCk{cg4A|YK yXÜz豲r⏒c]™CSI 6.oIpI(տ6$o[Aڧ/]XJs؈cV+zA{ik[ lhUaZ7wV^[{n~btͷTqqMwݖC[M)fV#(;d~f_֬ӯM[0=)0PCROo]L%udv=sWB 2-L4 ^2Z.]C0xL0]}AJc bOb~=~>gb5#tӟ4lʞMye+B,TɾW_錱g;Gw/>@ikx4uxyn}ۊS=,B" Xit?u%<ڤxl)kí1}β;&y9wd \ߜ A}Y_И(q1얊¼sԊu-þuzC-GgS IkŸ֩)@1ƌ mssWXڟG.4{\OԝzZR3IlPߵ7 /cWF̎E۾@#fc?ȦBu6MStp}SEN]YV}ܔ$m*v < WkNȉ+߬yw}O}%M{n&ij߼*7&mz}]Ϣÿ:W +Xژe3]i;ޓ8U'\dg\9!jv^xMM`>Y´KBW-m;WNfϫ\m5u'{* ;:ڬ]P- 5/-C^VWuyjdҵ:W(^ FyA*?0c[R;bGsr?E%2#f\&q1fô@GtRﺚ"9n4WmȒT^on{cJw&$82FR,`Ò뙲I*FakJ]*kRdjgu.C-xnϩFz>Qtלs.n3>H{{=P#shgqmh%4&g.m1S8;-HkcOl nfxnB=Ӫ8Ld endstream endobj 326 0 obj << /Length1 1642 /Length2 3920 /Length3 0 /Length 4944 /Filter /FlateDecode >> stream xڍt 4{ܷ-1,I)d23{.)13fƞ%iȒP5nQ,eW5P^9s朙|XA]QxI*QmlTDQQ۠Io1H"@Dq{ (8.Ӈvx`TY _x:'<`ǡ q=7v BHP55]w@"p`'y88GQ$ʿBHizH`<Kܵe_4FQ2 4 X_Ƀ4<ލ 'FpD"yc3el@=*;o@hܮ3c8 1(LG8!Cd8wV upz#"hoQ CbQ8S>BNڬ 8NHoX ]G"0PކUBwހ T Ed@"*@P(D#H+.F~#=(:x~fge~uNW9EԀ~J__iHesHCq' ADu{'Fd쪥vEc( !o_'kB}3c#hC!$.1~/CY7B E7t>PwFܹ/2'N# e0It]'v;]SB0jTn{"va`@(=n'DC%e:32&=AM 趻'N^H/ 5p@pv/B~,2_@UU)N+-}`,F\~9DP,#zOMW?i~||ԥzf /ZqEʕiȴq\%:,b–jZHFbZ5S:zZ.u78X6^@cg+YQkyy<(PAcUaT8ǻ ^.+j,b#SOdtd 씘nl(1کDu'8AyR|cB[B0mjڗ0oRraNgBqq%Vhmc)C& l3LR0)1Vtwth7$n;?,4؉Y}vzR8:8pO+- tQ?65fzEr8o=Iav[kSn9f {f^dfܴ~b6Hya)֧?Y͑R]1|AP_ -.<(鳶rd<+\sjg/Eg7+s..5\ 9f=u{/V&l^ffj`+z2Q^Y-f_wNUgK=2NcqYK2q ݴwe;7O6voL%eW*[Rj. {ׯ@tWQ,Vcb7/3DuQf۸P 0^)[%>-X{V|K X.:Oӻ*vƼ{lz9lb7:[:̭6=QYw܇/% bDRo9zԢU_2;1++W̹sEGH4V Fo[hlL*f.n3@],;08zpjHװJp4 ܦe[f{Aϛ.A"愾}m&( vR1ՕR\$OZXr#eOr~5,%Sί6uhz F "uNC,t+!| ԉkFHQl]>0-.%aW b ,Kb$o>)/461l%E-. (OM`W:VD#QGoMH9, 6?'{=]|$Z!G!|j!=IWk ;_EH/H ꋐkyh+4]6Nߣ[/9IIP7o%2Fı׀MS9=:u%[ͳ"F٥]CyjT{hj3"O4k5̚ i ]s4lY˒'8hZ͆UӑF֜nC%3}/P!RSoBr=O2Z}9qZgZ;_v2(HU=~m1\>N65/d¦sU1̰' 4Cbh¹!r.﫼B6Z݊uMHJU>}5Y쮒{6F`s'$K J?5XM1?g5*Ue9/++dc(E(զB._<_=q/vwһhgf8K ~. ?`55v6! އ 2ޚwe1N):t;qv\ԢJ+kVT]sb* |u*T(y۩_2`cfgNsIώFe4CZeH Cv@]F ?}&*39D&rIC--g ǃ%D;FyMQ'{|`+0|/?|Xe3Ǹ4gۼϰdQۨts@K4eRףUdKz*Xc{+MO\M/J >ecX *J<(*yQ"9jOzLgQ7+3JO' evPLOlUaFjmv=6zM;8VlR6d122 {QElT(4J})2zc2p5rG \Q}ǟOXkXi*=:Rn!!,q84$.z*&eŕE݃G5I]W 2$%28=SwD0HR^SBykmc/깒b=/gIz . dwX"toV\*y#b=X$(2X${Wm=@`iS 9NxxcbVּ\ bI%瀇u|DNNNt;?~z>28" Pf8ueZH(TL$˺:);P·?V7v9(!Y6$辧rK*ܣOBnDj JKzDqI%}ϑIŮe_hnh|Zݾx6 ңTO٘l oLfɦc)Jftʹ$/Ι:n!xkCG^ <.J;_+&ή~/zu֓yGn|` wU S3uƺ3[y;?OfEGo2.A hޢOrղT6}fr4[ So'So~% s^g/ɶ9V9y^b81}|}De\x_kBuIƇ-C7\~ר",v^]ZISZ_\HfUg%2ԄSM޷(ͫԖq}̉r.gCHaOMu ?l^oeg ? |rqdG|y!v䑥`61p3_Ea~E+b]#+E4b>@ຟ1~R5}GiNIsK#37 Ssx 6+̎8%%30ne |^o|s=:'19j)54=8jsGZߣcjd|LSW5 Z*Z endstream endobj 328 0 obj << /Length1 721 /Length2 6909 /Length3 0 /Length 7498 /Filter /FlateDecode >> stream xmwuTk5R5t 30 2tJ ]t4 HJIy}}׻ַ~>g?g=k8X5'TD@DPX`a/",##PCAAh,  " !!=Q0W4ۉo`A`0 G>9>}G( ]g P3Zih5 P>`8 s"<g$ '8!_x EsF!=*u5!S5iiGeN("U? sBPH/ߴH?0Ax@}pACQ}$B Ŀ, C s@0o X oϽC!0s$?G􍍁f|-euCL  ? >lZ῾#e"Dbqq hBA 3˿ߋAfNrn!E|㣎f|"s#G6^WS|_0I(Jy85nᲘ%jڨ6Ϝ(ݭ*Us,k'_y5?u̴M{G>tFrAZX5TIfuYx*h6h'gg~ʧd(MK~ 2@4KZ*,bfIvjA:7"I쮿eW3}ݔ0`o~ϔiRm.*2ua-ɗ!FYicD'jz>+dDBKx|'V6_x_w'ȽiB&Jw'M* {b#"߼p7)T)M¹hkXw6=Y,* ׷]ٌq or>+'~\"&3P"><_{3z `<,G/oM >+f4h,h3Ʈ V=6dEMo1dnhe>/ȍrf SN`f]ȃ)%IFڪڕEi,n]t!T>sffVx]ͭ](pxu8^\Efa }0iOO nMl: 9]%iL #ǥdOxԓ4Vu|K* eOtn>ʿ1ډ6fWqiڄ︯OBٛn0?tZUc7$GdXP*=kDɠyBe/r-r8wlt9*[ /{#NI53~rݡ0&xͮ >،}*6qDg%ҿG@j3KC 'eԩ 6짹3 '0wτ-}0|KH)'QAɸ nGCK=vrȐ޷?6j `#i9Iݝ“0u ^iV)g=qAp-`j*ǔAoS5ѝۆ>F:!jkTOTwq7OS7KD]a =Hh"xS#%o~+#+R:иa T<.l3_|V{{4.9jV Q^C)}RWG͖ P$a6]mM_42TUjj͆m~KNT]16RR q->hlsFcs~ ~OAɳ<z*}oLsGKa[@h;U1o9Uxqeb~gf/^$@:W=CZ J";K 8 EAgzE.M/1!ݑmН=<2+gեrPɛQh4c|& Ͼ'|aׇeޤ/ZEԌYk>!wn?Zʡ9l e/2@g;?z2$铵ЦO4~C.iJؔrIkRDP4*PWw+TO8!CՓ$S&O,o]ULUh2v͐N9Ռs&вĭMhc&WwڌRlu'~p晻 1g2p˒>(+4v$ pie`"!\3okWɥUT|NS?j K&?Rf ߠIeS[b[}{\w_SG'!Q31~XWΪwqjV cOtg[}i*`Aw9nd!.b :pr3oX!S1Qyez1H1;ۗ3>NN+ᭆld 6Ufi YB3VMZⷀga%ڵwL^O88 xP̷w-7;kKj},cv&ub:qD{qӦ95"  \YH${#)s`AXKn6Kݝ;c804rdYA74MAѡQ]$AJ'ݸ!􄕝M[KXeI͉tE"Tr}~is :u<1x=CmVyn25:A7|%55@x=dǍH>`ϱvBA}csoTur>KmY0s0G\ K-o9evVb*>䢻pKrZAf,LF ݄IՖ4;S)!Q޼񣮍@X=ah>c`"](umX^A"1Y2%L@ z߯wMK'ԎP&+b QLK /pb1Kk^1aaO145gZS瞍Q:Lc7slT6 Ҁ,1k3;KY6PvŷJY,L] D^\}K*̍bWQp [GCYgm9U2sd% FO;P/w wo"6{^Bgʨ$e%XP<֦mx4;5 ɱJռHg?:S0k.O=Œ7&I} +1{]o}yHwwK: wlyzMtg؏jx6[݆)Qƾ5-JzVansf8Gfϥaos/Q=e}ւc1T1˨ ߏ1`hWg@FLuyn %T]|,J9? -fZY0$atӫMG7<MNX2 +t0jАUU@5%)r`%6.tY29=E/wlaE ӤY&(Zuj>Y"l_я 1b}Tϓ)Ks,И nUoDnJTl~H 7z2UaӬm'a^kn~Yz?#4n.E/zMGR^Od,JJZΊ؉C-ا H5wk?\sutVrlm ;gפj 8߅}@9 (]jG2Ucًq|*1YݾfdE5läkFZ{1mDɝWjs3Ud4f5rv_JJi ď/<7ewt$|x >n{Ł#٥ 2?Z_iy\q^(P'6Х{+a8sY|:0Lx@ p}l^4)dh>`6A<3]oVŊ}%+ӟ=y[0 ." 3M-IY)^߫G{|+q"IbYLpp @Z-^: %4d L߉mcדm*}r<KwZ*_{f=uF\e&G'WfE ;R(nkK=$J0}]BuU~ ἅuֵiU;r .COvIM=*GE+ xOW-n"~_{z ?7 :Oԍ>~ZMMف9H~+yo* ƒ0n;)o.B춬u^# 8P˶8':wDO*3~6U'gs)>hN.{4|~Nc0FVhՎh&NB MٻȚl.cg+U1C,44#'`Lk)u*T/MFeIu:i8HQV$ 'ށOI@eBEwK2G?Z}N!V5W{ٟrf(Cm%ɧ Q v o%5akeO(kR![{Ma`s4s~L鲲>YQmyq3F6˒>v?eoJ]kfdU5  `7&b]rBYOm_Kv_Y}~7fŖ'‘Y S69v2~hu"^nRSm]7ٔ|޵ *Օ?ڱyg&mb|u_&> ӣfDt6rW\{t9Iܐt̺u_Uo nbVsnG թ9 C0]_ !<=ۼ a:q1aa7 T{Ү(kF3 2J,B*Kn> 3䑆Z-ZSGFJS endstream endobj 330 0 obj << /Length1 737 /Length2 13137 /Length3 0 /Length 13718 /Filter /FlateDecode >> stream xmysgn6~i۶mL۶m۶mO۶m[ӸssukuLBeچK4De&r<&'&]c3<.{0]0gBYRNJd?m"NH?E8,'fM?J%3iya7[֠J5& %m, kjPBC)d-]?w_-aw1aZ-qG fϝ"qظRSc6uf)A $Ya{pmsa3𕱲(^|m\C~-rݛ&e D.Om@ҼO[>2WGHQp%/=F$P_Yc GIf}xl}Q;=\[!z?ߒUrCwZ뗻St14<~;*S,> \'H:;ND1OL1ЎRvJOon69IP>h!M0@śd #SPb|1)b#0qP?݌JZz{&2ʐ"-rjg#&tQAf!Kh@ȚjbOpgNh;?°y~Z{vJ1nIȡzrKte]'@_Am~0{Ƅ̪++; ೎ْFh g߻g\ue4q0-l2H^QjSڊ@d0Z4@ce$Nh'T #OvqG*hFD%OU)f?u%U,@="l_=UtUP-tU)[땴=.3wB@:t:ufgpBpC.5 Y”D!ݓ Vǃy7iaԥZd~S}rBȼDh@|J)vn$Z˙h![,NvQ~&!dQqWNr1nBf?eDAqB EFj|^mٶvԥy^јA\R"CprgDfg&ZuqYqn 5r&n2rCͫVyL)U:{}|Kô8?^ħ DQ*(61dzo!q_=tXR'|bL -;dQhd0SʪiZO6.F]>sc&wBCciZI<iLu E|I IFy QidCKy25?צ:7|KR 5Mx{~HFɶs༧S9zz^y⣽Ժҽ8ela6ʃZnJl'/ 1'@IKǩ@LL-IWx6!uOp"ǝF䊯A3ʓt%9Y3 N.Ѳ+?;F<%1};XAGZa2' O"s1v~oN9Mѯ+S &.^|AkBn "K'%1d{a3(s͑cqhb,'Xl@2k?Xz %ΰ\c|4ś'/4ZDL40*DFyifoÒƌ9~%<`1)|mˀa֒N~JhѽHY#B -xǔXeT@\3^ {K/ X{b(yK8Iۗ5k@"6U gJVS]fA%(R$cR$!ѐULB#i۝|-6 [xL}y;uzK8Rlt{T_5ethv557K.Rɳ@P6#'QpJxhJ G_LjEZQv ;gsGF'U߸*UTƁPcHy1>zNH(׬oڵzn)u&QG@ T>\`[as{|BJq/bBK'ƥ*`_j/blvY2%}35K[joR% l CmNe2&^PkEQc!#5%(S?# #@$EV;gE`\lYԥCmhR~`K!ĄE b){Y4(k^C\ѡ b&t߱m"i )Zz ^k옐W+㪥iHE7& o~Gg?)D) %ȊgF ɘ zlTц&8Fj1qfVzhoiPgdQ3TI z1H3kȏs*o),PxBbwv_8`@!1mR4g)U6w VfIvnՓOxFeab^zޚv".&}#_Q|0<2*1!z-k#nG睨`b6CAqz9qfiJb5>R`_$clS#%F%u.S~v_GS,=4GA=a8/Yɵ#e9a.OXNpJ|Y9hnV=&2=ejcE?*KE9l7ȟ qJ:#ĦDrmF6%wwDkY5&oZb ]3PYmXztQT oZPn [cE0;pY`0[d ?aXۮ"jtI'TL@I nFP(˹^Y=^CªӶivGE >ur5;M#)ʽ=SZ3[<RhyctoXl X-!-ᥢzіcIB; 8cNnNk>rT +SfX@{6ͭTtC,OhP=$=߷_OI̩A\*=YurHFV`84{lI{)^:`ږ,:ʳ>x=\v bMpPGJtjLK`I='[vU>2W:5Lip^1d/UJN Ab_H\hCm> i^)K!O5 TkFKjr'i?}&}}2-*ztQNq V$<6+{gnA>꼫XY2藜qa* L[bj@b$O_diEZl7lꔎ?|;e _]ggJ*_po񒝯1^esVh:FQzzR8z) *waCm/ʗpBګ)'qt2>y-HJ%uEHX|픫Om%5ܗ| 5sdkMz wĥr`2l҈,jK @.Ïu4Og'Y*jnmvɫӶJp  @ϼիU0Uvof] dlFrEm&_1Yٔ`Wyks\(:TItt|O7}tu@ \p=+w2ݯ xp]pDAxs?x7]tr>"FfF՞\$ 'M4jѕz&G4KZ ҖqY;Eb1W٪{fqKVn7`O?KӢUwjFft _e[f?Doxxv1b.0{oICmpt@D஧Bc U ~8FT-Ϣ DJ{w0g]~JՊjR,O JH Ė]&:6'y=χ,AcHjA_b1hFiFLO6 /p^E1ZLrڐwR&obt„ʮի_)Ha۸hT=5X6٩{h90DH vgrI()^AOXǔSWRUt:vHK^u߻DGiL\6Gy3x'K7N4֝ pf2l* `x0k^J8)E >U#:C+6NXƞoZAGTD{qH LPnjȄw9Ÿ57$|h |%S/c}ΞW[%OǝX6#.-w{FRdHISۆ ~׭Qx&RD+p[Sd3_ˣٽ\(~Z5Uq"\ ԙuM_u?r[$IH뤮Z K1+M|;n!` ~Y\K󹐷}SXq㵞ݷʍtF}ȋāmOsnO7 T[inGq,yk}} %jG2b>.yoA}X nE7ۨP0UN%f%3+O8|N RgM?D "<"F7 lH~`P"is7ʭ:IZ3{>C eȇ{Ch/fs]]QDtX_D)`Z?Rb`j}EYNVdVf[f4Uy㼓\nVDx5zb\F HETY7%B-=63Ǚ[ay<ڿE&b1`U10᥺+J@*CeUᏢPK:"|\)tj'ӑ º ~rҵ5o~ xw2{Z_q aj%υ)uAf+rDNjx7ӫQD9bWB=\4i}PnOf];-^P/?[y5 B,53fq3!d|64D˲-`QOiJ2|WsI'N)mq΁:|#2f>&g6ʦv(ߢ\[h! 2_i5[yy Ne뫽y/b8P #m,+ g!uhw&azz~rej_mODK.V OxL_qāBozCqpOT' E r&kB<$# ƣQ+;#QLV_cx6Aq^ @RR ߱lUWD/uctsnPt#kkzTgX) ryZȠI{OU@,C k]c+UJD>B9edEG$ puTmB;vđ8OGZ/vG¡{KԖ렶GRA-6i-xՈY{oqre3H5zbOT~ ] !Gf=€ИBG*eօٴ+ OGl{ly󎇝2BP/)W 0iEb5-Ŏ:yϝe2O25/54KWp9M&!7 jP~p ow6~T Au6y#17x:SU^P HOO/EO4/i[_2flHB_b $2P{S^(p[3FG8nkz~<Ϋ^pnbru1HE;`60&zO 3#`I83]ͫѻXa1]=OFerF.zb{dNierWzX2$ Tjwgτ3"M w̃KSޠ!=ST[MʐދY+Ӑ կ[ MQs!>ѯ,kÉ .fWh28av-Ԡ.[QO* ;oCW7iF eOy .G15jG1fbڸ5"KW^{{N-) zj! !o}7ƥƽ;$>`7ۅ5/Gy.ʡbtwX(^dAÌp1,:d/2MQk(bXOCtȥs2XLDT.V1sM~(9'`mb u3aU߹$asZF_lqXtTkx0ӱrr&.hkh1ؓ@-+r-#%y_UN| \mZNrr6nA-֧Pa_ɧՄ;gkVY<*zZ~Mk G6 #Nz㲅'2gU~Rf?`W34Ak,$f0v8=kpv *rH?֑(XaV>(254fQuZn=Wu]"*1_]mͱ:dR: G_MЅ/X{ _u-niC"Ѱ}[Ҥ>€iy裆Ke: ZaN:Q*Qy_+OufNrI,[@<v;יNudC #zo#Pq|D6[giޖ]':&c9 2o0B01at3&O3(t*Y~ @z9:R'}83ݩYj\f]3ƁS%r RŻO!F9 FׯF+hՏOHlr"!JSO$>p~(?)ȽbXIy^ DdS[AI;0Y{ yY06פ>Cd􀅴lJluE(m&qO,)ҷ}`bՂ_K[b!4m?cיb}yS2hBEȧlv1eE0+7xI?-V[8^1³:(RjjW9rjdBMAUw!]ylBzQ_IlYtP~.%6/UQ?yw!BlCˁEqQRIDCQ+DBFu*Ontߛf Kٵ#2tC穚m7qJCdse2CZE^L, rtm,k'&&pē"4 }"1Ӫ?u"G >  ʓ.w3rQH.s۽x$M 6M'!UYC湃I`dm.fC ЛSd8#ϥيNd;PD_ s E"Ḅ\P+gxq'V.&B\V^GL{D̈́$H;fJ JZo.vgFubeY5cF7sBp Sx4]2I"If9 Pɻ ppX:K ]?Y/OfE P8eC/G: ԩ;*!zKF5EZLğlErl.ߚ(5 q"% {:҅R@uqN3T[f۵$Ө!ۅ{ľSʃ9DQqs'~WƀH&j}.Ie9JU+<E/Xh]y-b1-:,HeG A7("},Dw$3s(L~ ?ЊkP~Uf)6~MPiH #mL<,sRQS`msTTUAM=Bhb⌰EBN=EQXtH6m*LZ`Hɯ;p& i/`UF,,itiV* y*մMCdxy`"iwze[٠ŵR r$~^di|, ܥ`~k2]f)O48#ֿ*OhgShMAiQP% Kn9d\ V:ZvJT͞G;亅IV {. {J)gP_&sFT1%%xa+bM 7# VBdUWyO3 %hO[Mu\ (dMˊy׏9Aٯ6 Jn7ZvV.,1(>Q~w1-]h!Ok:cQwIvҧ4@&a3YS-&9o]Xgrf;{"YnvAxQw˹ԍȨ=xXG|L.r\<ם?Da^VeɟfA{Q)˻ki<vz<2|V:'++UǢyj3*ئ3]f9qtK>w U.@&G9&'C~Æg)\3DGBj/e9]a1X~PxwPkJ)[Ҕ[h.o"$[l^~VBvgY0kX);Ga zN19Z8JQjTw^ZTڥ`#YͰ5<8H0]1, sg}8 Gq|4rzbc Aף3l-g81t- SIq]^.ΈXϣCGKKhҭG͹5\vo C`vt s-+5^$3 LˑQq:*1klyZf5!LwbgUڀ:$V-tyT"Іf%w)9{LxDAbƣjʷ֐7z=LI/ex7Gf de@YP)*O ( _Z},i3+خDlM`҆(Bݪ;.Acu&ryd_ƊEuPJ{ '&S۩xG(sй.>wm$ߣm吗W? (v>IP5I-Klb%d)r)RhJE7Hʑ}X?<~ ,k8TTgpOk/Vnt=HMs-09|7} s^$n58T$QL.?v_ d2MΪ DІTM9J';"ޯMW#hu/pU߷6QU%R6ǁ^8h]:ѸpiDQ:VNr_XH(V?",0}݆pK$wQ6>:iW`'TuNl+ݻh((b'Ӽ a\E\0O(HXܐ E.Gڢ,>:ˊ) CMԤcrJɅ+XSOycҐ5/D8 d{끧2kvHɤwaaoldݹj7Hv+LӋoshS%d<dbUd5ݷ#\ G#F0zC\z^u,jYe(qQJqѽ@mEM(ߎvR17lԲ;K; pu]gR ڲ,?Ղmc!#9\QnIY rĒ~, zK4'g,p+k d^w4A ljpьw<i[ endstream endobj 332 0 obj << /Length1 725 /Length2 16161 /Length3 0 /Length 16663 /Filter /FlateDecode >> stream xlc.]-\ze۶m۶mۮe۶˶97g2XcȘ3V&)#-#@YLE@ CF&djbio'bbP75(:L 0da{O'Ks 1տ5CK[K'Mٕɕ$eSS) ,))'Sڙ:\l,2ƦvΦT3{'09?̜m*br*Qaza @Ft;gM]\_l W2_N`&.#SsK;&igf`\r&fDښ(m\]L&NvqdB m-m<O?dڙCLb& .jF?pϩxʿףQ"[N&k[??XW5tqh3D=iXlFfVF35vur2s71ƪ1wUzkXh|8**#L6尅5΃ N;\ɇbxSUR*s; z7`jضr`.A ,yyc *:v֗ĩt)P~Lhj-Bn7@ nɰ-*µ 5%0Evwݪㆷ!2Wt G!oywe syTwyY|#^fu(\f)twEa`l6W\d'9&Q+-O1ۣo΋>ym )e@l]ځmڝAK%U2=1['",ݚκpv8R [2g5 y &\5_Ү#K\TEzW<2ҷJ5< UxKʠzS!O,>8c;Oz^W/MrBFN*A81u_oݭ2̽췸ڪDP0 !e 3-GK^eGqsGx^䀍^R\D K$}u󾃬?FDsuVw(BVŏbqz6+?1w~*eM^n@wתJ.ޖD:cqtzgz -U<8#)-{íAi\y-!wY}ɖX7nkK Fvg(KI N94ġBFhvvyRC8EWW2?c}aagQxb]c~E990RFD4>:+=(s qwtUm[<8"\cX`FyCrPܪsmgSiTB'vk?q';-4^ܑ&l dr1CwDwPڋ.hutJ9Ro,eE Em\9͕Z%W OIo=2=Qg9'>cn G `1L7~&96zv3CCHl ȊFg-N"}РQDU*eԢB~Jmp!%+NIiAnWO%iwI0[9^<91N/ʏ,[<,gScjEj=Z9]= Ͳcsg呇Vz 9ۋoضUK(j0p0%$9uyV |ė֙2P)M:bswmc=N̩@^t{#2FF,8$Y;(>.A>I#ūN9_L}T(qGMhѧYu۷k^م|:u,RNoXXgQdt8|cAt${ A]c -(*n&@rwaP[O+o2\7:^uaBߘR2ͭt ܪ 5ߚ#S?j7L$IK3;SAsaɃ!fES%p3iid6aKu0U˙Yg*.MR?g&O'2sʻ!A]icԸ!Ʊ${r:\i_@torڏ&cf"쑫~5']>oF(G #C+_o&װ-9n ]LͫJ^]:$4{+]^$ +ug!guCK6I3(hցAzk~jp{G*TvJ@olR'תyN&x41q@L8 4\ڠ}C$`agY$ p{lr>֫-ҩbPL;&,^Up$cu K0JMȓig4ÚoR W?hY/[Ь&UOxOkh!=P7GeûQt.>ԕgd!P\ -@?' OP_v@HH:eY,P+{P?aM|}P+jo e[ BW3f!83Ecs^ʊ,RMr?%ˠiQw'X7zwMStBufNH6G[.(fVAng*~afɦ !ƨ;EuKoUH BCp,eZoy DODeAcCCf&T= @L>`';ͩ^7n45߹&.gt@[O ق&(DSDIP*:LB}eJܕdƯ*Hehq՚[pPe(=hejP'/ [XR@0'd}>,-BΉ{p3_tc.L[=ڣx!q :U >mx&܂EC)tk2U[-zaZ(k2nT 4^w%3K3̉{4!kjJ"nۦp2qo`k/?zH.T"*=2c4q&x2SOCb^Bq$t&ʃAZ̻N_,V/ty4~>2L+/{sRJ&/MK%/۳GBfKq)*XϪkGK8][LY/W~M>T^1gޟ!ø s$Ï22g"v|˔H 瘡܂YB$\ZXAs× pec(D g"Rmg۵J3 8+{KԒ~ O^FǓ::%*{bJw܂!.)O2~k{14f܋qy\'Zj*N:jnNelZ&VdC)tRޚh{fNLjܷ/B&a68={UXY q@F\ys\qa]sޞWihvP?9r@8K#=s?U3a3uA4<+dډB>'c8XTOPŀ14"c캱o kG@,K/t[*, W b͏KkvL-%DHqRe[]&sQr> thO&)U޸Fnsm4#GT.Ljkܑ/w%&"]#:F~$ o1 Uٓ_'`- AJl}~V|x.8슴vh/@Lq{E\V|HA[tsMf%0e65VxW P ^]g3!3źt r;NNjNFV[`Q Z,o1n0b>a?PtRձ%H坫}] ϫH.(9&o@K Sj<_$q_g!sI8nⅣRcf2+DT @*O"ѿFo!p6ST^”J:Ϙ4M88 ~M9|<1A F'h&r\S#K #޸jz^cY9ҝ,|=OB^0T!eq_"S4]ίSNdk8 !EBth㯎 ۪?0Gד-1t`,x្d;<$?65l\k<ۂ.c,L¿_?˱eӼSk/Rzs@Ҥ*H{u^2Е=m\Noµ--$R}ǒYxNHdRrlЃ]uaе!8&MQ,[ߜ3/}3)M65H"RvE$71IΟ6;7u][H} z!Mփ;H]_WQ@+OrjPU 1Re\Èe]qTдϟ*8WkaoM|DsDE8,{SPq=+:ÅmĚ~ö'ttMh,@_~ud[p *Ga3wP887;S޿FR`> LF헣正e!=.e_ yVRdxoqV}7P4P^vmt!ƥsMQL.6rYb[9^=xǪmeAqJP@CcXI`VqMv1΁;KZ52a$U[9G׆qN`F^䎥Th?:;n<9Ļ a& j$!d2jԇaZ,G)EL c kpIb(&{2":$<Ņ/ `r&_Q-l|tu{hf۪=.|pԶ*|U.# 0u臜bI>9G@'2;xˢd2z|*QabSUgM^Ò{Tp]1@AުiuXpٟ'?M-lwK!+gB1?LcJ,hƙ+B#^^.Iv]LO֟|Wa]}_H 7㖲5܏XV^P^ C{xt'ܳYb] m-Zrn7c]{Dj`O/X/~[-m'.s Gl]z(SriЮA̚Ź21\,fg~ⶤxb~6N*PY0'uU%|O QpϘ`=3h'Gj9ރ#6&H^Rݘ]t> æb`6  'nYL^55ӈQ:]ҍ֢L=r2,mf\ҷOړ,Ncyb"CHnpԛpqnaoUrsK+,-R Z-gohG=Bv!-ߔ/FZ>yo:ird,mO]Q&ri?1I bRI\Iłx5Ʒ)n.6j}%&4s6Bf'~UoCyLtR9lՠQ 12^˸,߈g SbJcv/)w 7pmA÷f&A.Ye#.'0&MBа,ƑkIne_Bˠy%W^q7 |L%*{meu RERxIfLsû块e[VxޗTOtk RtuY ATBj18O^S"9L__[)jYbM}V˹`W}X-f{aϺ\jͶbْnjϬTӚ|6o|cO%x!|ǹR$[tH*_~@e*"`;I KT>B`5IwlRz7dRDM8ږ17]fA!AĄ#NEH C#F/f`t ^>?ɓ\N"v x."r]U6vG;ԘmbaMY0(Nks9iE;^I(y)[ % q줦 e\yT]{xҊz]ن=_yB~܄e%Wj#$;"ߋs-jӽ@lLbl挵8h e?{_I |s^x/4rf;vEO_|_P]MH'3ZT@0K3';KyBNWtwC<;HXih/A)yc: gBT_&/#jxJMEw/F(h Rf#yYIrZvV^*+PivLǣIx y= ,r[Co3M#&F-}T*KM^45QjRЌE<;O'r[FpO{؄qfIHPDV&ErwQ<s#3cBuz9=s-7D~Q!V%m%s=N]4h52zxOĔ)S jK_8rFqZ_t[-%F݉dy˝>1 лUƷav$zjoĺn$"1h}95 #R]<32"%c#׵P~>4+k^-WY(gjNB%^oZ+?'鳯AB@t`cz.4;,>TT=x|;nl g$lY/1e{=xr_İ%9<}&%{lre1<7i4ʎUďs]Y.6\zD8̄ yn:'!͖EGѻX5:El.'KJ1j"Kc.a[uMk,G Yb^b7Gm8Ub f 9Ԏ|; w<~$ [V%ȑ~hnQ.A $yݱjeMkM?/xۻH~8кH,V808~>:A]R)78WNWBh4r7X }AM?:Ug-3vb@zv5XDPT'|K{kZIlGr&v1K⅞%!pVq3(xT[gu~G! <̨ys6uF2$ ȗk!3fpjUE_vTPԊ>~AW> ā;돉c[ǹr>1%lc:k dN@B8NpT@eq'x%sfw-G#P'q!ZfA  :d9w)K_s!-++,2{s3 Ԇ8lm=+}B>{ZoV`DKA#L9&%[V/5muC@-&]%%bgc1Yfc?ي+,)3(e7}.ʳqQN{kr}j.6GլҏGݟuDŽr!'S ٯqx,q͂=)ioyA<اxۂC]aU+˖}HJ&Ø\4u_w߿\v0uiwZ0zm85u\l2mَiđ58ȩ9R{ySTm+Z^9Ow򴥉2f+һb]obͦ>%] 2R5X3%z󙮴0)^\M]@S3=,Cro3tá٘ߐA3t<ȁh")gxB0~Or:,R*bD{srF͵ڍ&[I ,P\HWե֝]x/G} Zm|j r"'rQbⲄTA̜hq1OeYr^5Vط#Gd.tk׸tw">,Z,9'#d, cddGVOYJ˅Ey٣ptK 5m3}C-#Mi)EK³{ L,PӶI =D- ``Xx6>!LF]YQ23<`l ga:e`}3+o"}/FtR6vZ 8WGY:S6-07,%Ke2au?,V؞:i\K{Np&awN}sG$][8*8#yif\ji>WN/_g?ҁ3<aio?XMİDrc)@ zl}Ob؎ [؂SцͷN)=%h$]m=a,M]DK*E:! [yMKԸFd$F\ 1 0aYu6߁"W+zs &ۃUᴍ&5zٯKcuq+AuͩdDJ#A<:6'ZW 8705gnHN>4x[ yN-_d Gk&Q.|[K$l${"*5!qSNKOeKk׭1>cll!2 d398)-e-9x[Yz5(@_ɜL} 7Q`syl-wJw 6"/hGA/@Òάo=4Wt c?~;}ت뤍=3EAlq%~ ˡ2hA:S=$9d\`>\IUf}X(ŵA13eA0%Kcu5]Q}\{ث6ș1 WkXKjm__ޡ$fkD?m 7e. >`.}U8Fai!apww8h’ާK sRyXlu%fr~!.U-qIr] ro񻮊 #MX,1^  ʺg45WcFQ-JXܐ7z Fᚢ ƁzlV=x҄X/[!Skrw~N]8UDCcg\kr"z)[Ml{M]%iTxFL@r괛j5 W֫{y c[=g#m %;ۥWsF-T(t\Ae/A<s$QO IGQQ'H+Ri8aM]>):wvVE#GKڎ&&dH@V{"qù@Cw ;N"1= Dm֮{kavzY ~JDlCiK* ?ـ" }%Yto=$ ^o]7U9|2oZƒ >˚_X))ˠ h0$P}:/7w-!i/IbTV!)?@DLlrنb@G<CSU v(FbQ tmPGE^'?/fރy+?^+Q*zw]4h-~t+9ݮ[ zpn3j"5Y(S,kvmu9#X ä9À \#HYd5HDbԿԣhL`y"*iH34e)<Δ Zn(}?E;7_U{w]>[-Μ~c~)Lz>3> 6?/P}pMv\ hu,'%Be_$nJ' 'mer 5:FH@fOIhYHy)lM\-$LCi0:=s`+4ӈCz%v΀oJLMn:rpkP,}~͸eeWPv5c{D&[7硼fs刀~q~c}}*y-7-jv8⢜LyOvUKF+h>wyShQPeP}m?ҟ\AIAv[B$=#Cfׅ\gH{=:&Fӄ?X[_L8RU."`kF#'Da&[|U 4ץkdM}AM 4+"%[j;c;5 jQXlS(nfwZցgw aYL6ZU̢Upܱ/Ęc}b&Dqy{ 粖?m7?ඹe^ҿ9D(.j竼T9o6-,}H2SL((eMU+qQ6TGp4CPEp MA!YAEW#:PMg ] :OCnV:W=L ~9DnSt4hVU/& pE?˝i4#[K j=4> stream x[[s۶~ׯc3ۙ8:A[,&gwQ M)VCg,{vXP|)OPP(+pF"+0ર&.W=uw(P0y@.,: q1FF=£ P+h!Q=!t`D ~2`@Ͱ#Qu P€ e졭ʢI :Z@A 4 \ 4P+n)zFQ<NJ¡J#GkJPx/evT%-. XKK0H)k@O:[z;ǂk؂߅ F/pLޓ û2`0Zh ^!T0&%8\=o!5R{f3=( q`x@uQ A=A/A=CBOqc@@' Me*UA@!Gy! (S`3cn `CGBC0@\X-4RkC.-nI$ w$ƕq {ǎ]W';9s=LϫGKg9{.z̋Z5KcpݺbR v vXI?  F^/=^+d>J-h͂kfj Z4WqQxqP'` ֦47JZOqߍ֕ xNjڸh.˂îHF<"l:VO:/N/{|VZ&7A5KCakAA)̥tm^j4qph#d)l7 B1g w2w2<}8\&օXVX%d,t^i΃$ ɘ|~=ci\N_O'r2`g@sXIފ5}e izUo͕}tWt*{vł$SB#@'PȕPORB Uto\ e;H-=4CU-- <395?P"'tQjp2[ $K~fF WP&P CK1Vlج\Z( `y:h-@`EbAEg4'i>Pѧ҇ u[p}0G0S 'jh -#S,Dc >YXxElQcj$2"a~>iv#iWBĸa,Hwi\)^qmX4͸~͍}S.UM$ q\ӓbxT'hdFSqfM[D04״.&ikGk&hr,R" iN1ITR'N@:[ȅx Ax aZkeQTG'POF %H t3#$GhI4B41Yhݢ8|H"$ ?pT'd= oޗO#qF:=* K!b*q>0|j dß>|ut$8/f4}ɋRS |2lpO=_?`S=GbTCΫwާI,OOl=dc{دlCv޲YΦj>>)rd6&chU$}b_*ir3ebZՔ]oח՘ g6bj6cWlq&l5G$zQu5Nf~gLوMٌͪ/q6frZUlDŽݰy5 &ӊ}aYM'"CLu燣ÃgUδ\{c)sf geLW3—gz/e7W߱cvnwcߎ`ԿB8{=Z8C6Nma=Unn/cNW`O- eFirwͻ7E_ Wf4Kp0:pW}X*Ua(K~kYKY*el~^vԳXV?^p9@U]IU#TËwk!V/ު%<}œ4|87k<}4g]32gŖ;9WCښf!#yiY>jmOGQ^~}pχa3+\顎S!\HO|i;_a++Ѩj8rL .|1rxcLǍmYC+s6 FIWEGk,pn?dƠd<)~TA6>sA$#{Ia<*z%AJ@zNgs4P٫~鐵 m-B&^eGѶ֋Z<%^xoVWo-C.] 0ujKxڮ`cO3mz+po˿{5 ~v1/oyf\~mWI<&3.o͗?wZUR[{X+bK Tӫڋexhn^BC[#uw]4g 84~7B6 ,F|NDL2dLÅm{߆O^)&^vfߪ]Km&z3 8|U$=UO'XX-|ɚżi *wg,Sꜛ&/'(w > \|j>i7|Z5I x&Δ3k4}LϲL-Z%F5\@Ո; qD\[{Av\ׁr ԦlL԰MȶUSj֠86qku7qfFUc̓ZAV5̭UnpnW9]ni;ępn6mvmL\[jE+g> endobj 336 0 obj << /Type /ObjStm /N 18 /First 150 /Length 798 /Filter /FlateDecode >> stream x}n0~ "Ci&"Ef!eLۼ}gcY%jƙRIcPLi2ւ[2'pULpSƒfB* L SZP,Al]2z9.p&w#TlŤ|vy9[7a⚉GƭXm⦭ >̙z`~mضsL٩DA**pP @"Q<_Mԣcm( @90D@ I14 `-@ATc(dљ+A4CvB@3^+\wv}zf$&bnqWe.0)%j%|ل.$Y6sAsZw*Ή($p,ԑc UXȴLϤn'rXKVLRRc(OQ p,%(4Y"=m&@_ݾ8TĞTĶXщ 40<@c DtkNB9<ňZwsx"7gU X+8/] fal2ܹk~p1:CHh endstream endobj 355 0 obj << /Type /XRef /Index [0 356] /Size 356 /W [1 3 1] /Root 353 0 R /Info 354 0 R /ID [<5C3A828F4D0C76BC386CD04529DC4AA2> <5C3A828F4D0C76BC386CD04529DC4AA2>] /Length 837 /Filter /FlateDecode >> stream x;OTQFϾ8D`FA^_-34B; LlhveacKcԘt,;sYB`!}'x}T,_riSl+-M MBvԐݠ 2ddikANU- KmԓV Dv l##L 'D ZȎ]mmd3dhA `'Is] 2mELv&;GDɮEP Av$;M Ҧ)'i`F(8` 48#(,8X@__5JCt5v6J;՞KY"}䕄RT"{lwَ㎬.Y-MjLTl: ,<k,dEyY >q4Lq.ƺZfNi;rHzhE8PZP.v[+rW>5h he qM ######OYQWk8GrDr BEbc[X fdo-!G _h]Wr〓2gSo[X,U li /jP  cMs t@g6:U]6t> >7+0[z`vUU{PU4}QU[Ր%ʖR5lɫ}|jĒ_Uh:Ȉc8"V͋jIFȈc0`# 62بhyKnK- endstream endobj startxref 215706 %%EOF tinytest/inst/doc/tinytest_examples.pdf0000644000176200001440000042654014071046215020146 0ustar liggesusers%PDF-1.5 % 66 0 obj << /Length 1785 /Filter /FlateDecode >> stream xڵYI6Wȡ23HmǶHmsKV奱eעǝ߷–OpVǷxZ.#~2֪̤42>-,:m4sf@;̗ɇ|Fւj耿b4ˊwr='A 7(MAf[t K,-eU-<:q.߿Se:V+ԤJe44_0O?9״$ٿTH*!՚Rh5:W&H8Jt<' oĥ-r~*UxJ'4B6s-}E*u'BKNs,I/"V(rY'w &JzWo>8N~5. YF—O,G$BWzНXig2\'.ywt>ˢo{`kzXΉP Xʉ^9@D#3>$i>TQpI41j)n$W6S?Rqal\  m%e(nƀA0*!r^W?0%=3Et &Y]\&ٿt endstream endobj 80 0 obj << /Length 1969 /Filter /FlateDecode >> stream xko6{~ UHY,Z6 Ð[Zl8nm+{dMd&yݓ u+v`HāX$3R* aDL~P h#EOӛ1`R5e 6l`L3?kEƴ$sv$5{b=$9T:d_ aOlu .mla\}&|8HlK^ 2qQE)vMC Ҙ R_ԗ:#SP)H(ǥ&$$җ23|8֬/4 ƚMXErJxf&*cؠҭ1ìk+N5S( F{}JM* GOH;N ueJމA|lD6X8$]'\雺ËtzC}ǟ<񓖨zFHz!ƿtăTB=>CH<㽊?foVb|f gxБ'j4 ~΃zlzD>{#>}CaЊ3 >:Mcs'(S|6Ki=8"/mOr`:b4, ng *1<{{2oxgDl&ϓGcG˚Xb shU.2_M톙앩`8 ൣ`jɝ7ϛ`~wVY?1Afr0W9SXhΙ*9-bƶm ϸC2P&Җp9&97^b*! K 8(WFiJuç5^E٬OkX-Z:V"5wP$YSM[ Bm!y K!ԂTwkK35!,Wȡ"B\Nqaj̏tb>`DY_chxQ^p N)AK^[|o"۶+=v}+rlA2 T#%s19XmcVv0llyih6kBw[ bNiG ??Dtr `[t}Wm17vUSp]B!Yc1L;GC^ʻ˦pbG V?[wH;Ȥcf(FD;Fz2 9|D 6mI+Wd.4y3f%{)lN %vkSЍ<+6okkoGv¯Sy\o1blffbv ِo ӫ-G'-wX vkE7WpE[ܦ;;; Aw>qۿ]SmɯYv[eoO+| o҂@= t g,<|kyW?l۹sg(gG'KGF`_LrϤ^O;+acƶCPQ7Ƽo;yL)7n endstream endobj 86 0 obj << /Length 1412 /Filter /FlateDecode >> stream xX[o6~ϯ$"k m 6d0`EoZm؏߹d-6cO{I:{s> uE%YGŅ囓'T$ҋ zyYz~{zG#Bfy |)~~zhԇ;YA=NmhZyO/SQXFB΁2Ea rf.K CdpJᯮvxixP(\ x KIȼ]^,.xry'.|꾃pdIL:c2Ί${lD'DI@-(IF\"}:8~ǬjěU.ѫizE)bD@EIaBH? 1 '忋:0FOXgjhbvnR{V?ݓ|I5mEUMr$J]w'5SqC]jGvB俟n1nk12D5OBցC> J֟QeG2e))wboeRۉ}ӎbK{5b,y: WC1d=|Х{"m3cFC'ó/)4 4b6JF,F rc,@ϠL$Iowl>e;Y;=zhl%OsteƦ˜x&>&L"r1?<6)&>M(tKz~7u endstream endobj 91 0 obj << /Length 2289 /Filter /FlateDecode >> stream xZ[~6*!!41Рv w%Q^źyE@|u8v69o7}EE7 2JlekwٷϞJ0Y<\<ìLGѻq2y31 <}iJ=1;SO%c{5 7s>ّ2* ȶA\I旰 7O[$cW }Í=zMA< >5ZhGo'0N̘$qo8jOzt.o:MϮx#_4qo9r,IԖ)LҽZWN]aΔ`7J7^tQ<^nW q;C採I}1( k/ \0LR_ 6(zz%t[x%O4J5]rDu$/Zhi[ʦcC[$0oA'㯡puԆ("|peH>kYSMUg!Urү—cHחLRzGJЀ4_SK(u16 bU&˿.?z("olQn+;K_ I8嶍[K5N7'jͮ#0RhZwgYHf>),ݦ 6};hAr]c#%3C l .[cv+o> :9J4Dbnޞdܼ1+ ̐@Nۍw]ISpl}p !ȳWqMn?$, "m? x!+8 M]5l4zRq'lOoByyDOO|S S(+DfR⿀GxGgq)J%dq߼{!Dl%dSZVm,j0@=I3> (!,>Y[]L*TC1zj I\ Ú+HK0đaPH8I}A ,/ScFyS$I `ʈ⶛ʃO]֜\5J'Բp1tMBMnAi{~8y6K=ځH%i `H".LIqkq6iPl2E-#l[E(<:.Ѵ(7]Рcf\\'t4OE#EBڬ\] 3Z"A3V%6Kr=O~М1*2JD*,͓%0Uc*T%;ZzPϑ|_ú0\L ߵC vnMY{폢{kQN*]47#EP 1ŧ8Ggj&eѕD@r4;AbM=׸(),A57Yo\i[ΐ2:4ͬ )R7s!đ_Hg݊߹8Hy O)WbmUBy=?9>'zaf"q$ |·=.(r1`/|^9dv#S~xKOsZlS`s]Ϟq%W:юbBK!ҭV.W NAB[f]ez-OH@7fd 2Qk?Va֦ۛT(gt}8Pl#BɁ[d:d_v*XͳkeP{Uĕ6Tkc^% Bk ^dNمI5=n };Gw#\p>{ kFs^Kz/yKYaq$ǥQr̪of endstream endobj 95 0 obj << /Length 641 /Filter /FlateDecode >> stream xU_k1 ϧ`>91 K#=ued% +I]'پQ>Ȳϒ,駓8YN|9S%pd'w`86͔Ts" g$!Ռ\R]UcAŬ`׋ԙwװ@WloaEwñDpVR<eTႹ n,WJ nO*ȭ\:h.a]YP |t{%gt}@ lsz玃Hx^<< $RʍT l]DЄVuy:CGaN80e0t{ʎJ i 2/bbP ^?9F~| 1)Iy4rHѣ P$R t&sZ¾:(dU{yLXڇe,Sçg( fHkN߮1u{BxYa8J\\%n4HZaEU YěYEGئ,tQ> stream xXmo4_qJ]7~M@w=!@.&k?[o qy33ɒ$K^e]1Yި<3 '6?xP+&<&̔:9&?zy,ҒوחMB5~s 8~~섒a8`?SQ XI9U'cQ2Y1;Rt49ч2綯Ү/Gc;+<]v<|L:ˑ(h#{q:*\ .DA{]J) Se#s d~!7n%]0]pYe3em&df,eJ &h3eu} sף:nLgX >IC%Hᤰ;5ӗ\3hYs /V%Wða>=%mً#"7% ?UCngl6;P(jyCxǀ_U(o)"&W3t!=jII٭$*Mrx Ziѹ),1w}ɚ&F0i.H Tg^QX88wHe[O ~~&>jMTQ\߹?zo:- Dě{>^܏jd\ߑl[=^jÄ,,fƮ?ɡB/*TXĢk)G%sxO2& endstream endobj 104 0 obj << /Length 843 /Filter /FlateDecode >> stream xX[O0~﯈xY"cǎLҦQil􍡩kCA-jR1$~αOҤ%I>_ZM<xUr&SS(2\{ X<>bR{QUi ީፉiv l[9|Pl!Ne^_+OpT `Ha2{aKPE_\ 5X̍\ZնSlV ,M3Dʭ[x|t}yi.=_- sfDa ם7\ T1v۹Oe($U"6( 룍7B字H Cǘ8g$,jX-+rbR1N}cm:tY+wܰ(:~e`fCԔ0 @NH@JݔU6g1346"K޹O'M>e0jڗN`~銝6|ʙBg7YGl'.y_qY}9gY@O19'[w u Wtp%/Usn# Zސq"$Hg4i36[Ԃ8B=n5gb2+Zf֒6y´plCLI>PRx7':|/ΪDʃVmynh#V[W 9"->JMNSAeٿHŢ41>N(bnn};z&t-OIV6?̝xN|XJGMu8TNk۵pMr *3ƬAtsS2a*6fQFܯPJ8% z'>_ endstream endobj 109 0 obj << /Length 826 /Filter /FlateDecode >> stream xXmO0_ae Ǝ i(4mTCSֆD)jS$~싛vm(Ә|8ߝ `-kU X'"fcXxtmvdRsuY83gA>~$bP8ךm@Q(A~ΠP\\b1@n'L L"`Haq&@(`AMcvzvKv9q*::vYPG2}+(Pܨ;M\ܢ[}Rs|>)2v=F vsAȗ".pjC#>WR%-H5]GV&`LyDtP | bb)Y᝗af&[--N >fZD*{cPI=g6je'2xō|Pލ}9]SX๒jJƒwZtL^5#ʼ4:ͧk N)=)rjZ6`49<#)ywbTA#;U{&4u"DBp1W!X t R?V_5)M֖W[ĵ^HXn[U@k6O@I(aqL˵&vOΰjizAKR&{SObH>4T!m+rߝCڙO<ə{o (ͣ,}ͪ=46 SnăswchlWw90kޤ3n-@*{R.AlC "+ d\Auӝ endstream endobj 113 0 obj << /Length 1210 /Filter /FlateDecode >> stream xko6{~} THYl< 6ާT[V` x+riТ(^<1^< (t9( oˣG'd$t(oz ^+7{lfEHRt H[3hv ;H"<<% XrԒ<zJ@k% `WT _øf OshbDQU$ mOTW;;@. ޮp###bԈcI95!oOp,–_|۪Cm[ԌlI:]^ 3+.@$aZyqog|f8$X1%-X_׍W<S+n@2sMg dԋE*S ES}? _}VTS< /GSͺP7оZtln,ؔ}Yhzp=pq:*\5!NF_c#YK0HA4)*gF|FX b=Bd8nyx1=)UIIw<' _Lj^nto6rRD+⼉zmEFi&dtkb3V2wb17U))NT"[u^B`~n_u*L3t4TQ6js [v:nFNb )LM&B}Kn,\+Epʷs3d~]Q fi;jS;c R'Û`3:45W: n_̕zL呈U~0c{9'Tr2<_` MbucM';yacav8s9pMf A?|!bw7'z d &6W7`o|» SPw@aݴ ܠ^([/wQã#2Ew>ЧR/ҙ&O4LYovR@_qNϧGR endstream endobj 117 0 obj << /Length 1652 /Filter /FlateDecode >> stream xXIoFWD1õhhptK%HۆҔj$nmy zM^ym)F^ u^4pb8xe':P&|5lNWQNɛizne>Х%~:'_vCPi"^\FD{*RfBm$1| }l#|fxaMnx\Y@~~ueCu2E 7(Ll 1ƕɏKY#yǴ(n}QPG9WмA1R8Qšh6#`ؼ;~2 Z+6Xz TΨB.GJ1H!Yl@ixdAطX,p%0TiLνVA8H+ #B{Os.(l6]JwaRi%PШv? 1ln:7=Ȑ"噔!(y$Q4#lmq AǂfƮ3did;S@`xզA{D#X vtqx ~q(0vpZEa>9yRYq~ y}]Y|OCG6jANa;Qu ѶϹ|nk{C_& c`'55٨HH9U yǤ$""FHT\qF"RvG9&l~knchٟ{%dcO/{e}FuXf,X;D \YDM^ۑNĪ.SPtc{ck钼0Hr W5i[7)y]S( >qLSjząmKznp f͵7 p*Hپ>w |Fٍ/^Fy endstream endobj 121 0 obj << /Length 928 /Filter /FlateDecode >> stream xڭVn@ +Ԡ@嶚&z*1-M/&kYPKr8N0,$|$y R,$Awy;=U06pN4"`8 B%ïglJ%bI'pGG Cg jFLA \Yt[㠃>غEU; WE669OjKS[I^nn唋TY5C%߇^6Z+sYޣ>^͓ET!d8n\kc4f䈗wEN#w=%)#bPC(]V@BBSYJ4 cHS.Kv8nA K:B(H]N%\#R:0$Z܌&'(Ql"1N^[mu6d馞[L !OXbk&' Cvc׃0Gbor5'2 ol.B%Ċ* cèƚ7%km~~kj/t󔳺St I#LIZ#4,[ Ȓ*w\MxK,ĒQ4քUPJFb層Ǫ̀ڊ]YmpybkȁTà'"]#pY1y\}1rM|tN`*IXka%f'#hOҞ>X|SiT[k-Id5}*%m%mD-lMÉ4h8u'uniٸ/^>@oͼKfx%;3jީ{M|LՓgVe˲Ihir4nϫ4Sh=_F^{X3v`DXbtb_U endstream endobj 133 0 obj << /Length 1330 /Filter /FlateDecode >> stream xڕWms8_♳" 1i4n:vw%E&+igJ:#3|b#c?)X89_Q|60ā3[8Ottf-xѷ]gA_QR2VK:c / YLTߎfJGY&9Oc?dߪ5_h,y{%Fd#IXUO@@KCSTeԓ0|JVhh*w=3{݂WǑW J?#s$/ؤC8טd7;V{xQkkT Wp̱g`iKh+40p/ Ƅq&"+8߳x0ZQ[l:dhX]<NP+lKV0z^#y)WZhJܙ%V0*.E#t6# FD猕'?jPkz* uΦ3?{˰1޾K!0`D*V : EU׭&(L8ј3p8f٪ S` $Rx+T81PxfKRy $9H"l86ymʘ8qD\g/+pwFq:;-fujDV8,tYR&ȕHIn-RĥC:EKx3eGH*>` .%[s, ${DHd KjA!M7, fZǾla[eɉ화)$nh  a#pi{%AZaʞiʢ|g#qP03X L7ENPJ?f g *r/q/Zl8e "i7MݍAןUb `ȿ;Wb5YA`cԲHcUbՇUyC'W"֟@]A˔{R-Y:fq?ˁ\D}7H8%tW6 nwr7DtFq.dI`s[tţWpq^27vI&Awd:8q`6.Ap«>~uwԯU9{X+(*w֏u 5$Ru> stream x՚ao7rHΐ,|+p- 'ں:GG[iήjERʒ,9Z wr9! M1)rL48L2L69*h<_`xbBeo!B4 Mp1^ ፦)ÛMQ2F*ha .'p nlTq0GϐY >>2GÈG||*b+> N\%K ш"V2l46J`H`c|F|B|5BؒFD?#RY04)8p"+}"@T< M` )=PANs*e6\+y < 4rѩ!oA%a,*"1YL1"<1 J  R/YS2[0f)x=l0$IH RJπp8`.1HurbWg1t4믫g784 dvh| p@qz;et:"}NƓ[5vm۴Ux3gr}դiץcMr/dz u<9ןnFVf2S:VhܶTW9w8!Tp\TEKs;Ls7қԛܛv^^^E^E^Z Zկn̺OU7M{9j E9u_pf}XrmE&yCש|DSqxK&CUtlsZLbs Q!XPWn9;#=powYDn^p뵜mI'$ELlKcqdsHtWqb[F,X0ݱ89 $$M݋E#d],/+g2[E( 6Hg2E^YƜErwK qzG`٦ȋ#˘ ՄuB( )dzXXWF[ @᠉`#mG`Kyb?_%Ev" J?̐H@ӓ~Ut={;|UW7vؼw 4l<4MgB?+~W?YF}sM,m\)E۸L֡]r}ڥy"1Z$HCRc~S_]Ƕa P }ԯGgJjC*ElM~u_t{qY(s܍K歹,>2G> 1*1*nkFKO2*aGFq{Fؔ#rkPq*/B"WmȐ4y[6˾O!:3PihWP@uP3(y(yZ<*o}Ԯ@%v-^G6բ/zs+s6mWWl;.F˨dؕv.ؘVMQ Ķi{۴W`1@7~?zs9*%YJbK{EoFvBIf9,uf>Zl'2N6pP*~GunBl}NU: "6`} 6zXug= LVcćcKerG%`rI;7> o$Ϟ6 endstream endobj 156 0 obj << /Length1 2404 /Length2 15081 /Length3 0 /Length 16497 /Filter /FlateDecode >> stream xڍcti85ƶmLضmq6iؘm]Yk2~!'VP45813reeTT,*NV?r5 dN HT l`b0s3qp32+h 1t0lm¶vfN O 40qqq:Xd ́ ƆVe[c ddJohHo`GE pp2(.@rG [8R5ur5t@+ c#H(K6 -@ OyLLoC6)Zڸ[ؘL-y1z'7'ZoAC+G[H bCPɑw ̀ -jc"lkm qrDTywZ6113PR?R  j,t36gD7 h898=_00v,lXc8XAC`7]МX ZR4$[H Icfa3qعkH?KUwB7f  lA PwF6FcKk[17&1g+$(+0r h@ k Z +{e&+dZ A3hqѳMpp(X85&U P112h׌?Ή#h:bA^EmmM~3;4TllO&rgH`jlߤ'A08 b Ab0A?dS (A?!? 2cdƟoinlkj))"9 &VV ?FAqo ڄ?*?*pM-\e7>@"f,fOb7 v@IhH-A";(m:H lfAY l?lP v cvǗ?m"h@O[XA屳rW:jcl41W@ T?~#˿w]? @: N@Ur3 =m]P#\As+fW?uzv?TH?8݀K ;5@}@⌸VOxM9ӗ^k< ayA%y*.ǀSy +EnÝ"R]RZwhsB|#8՞i~b~4-BE[4|$CJpM*?gn׵sA=f oTd.qh؏}V,mbf̀t*W.VH$YzĤفD`K1U{~=\Ҡ2Z> R^g@Pċf2c<6g;S[6RqyO^{Hݤ4MW z_BIJ62AݩE}RT.bdL_a [}*LG'gHg%[z(ۡS+hfL~R|4^5Y} ?@L4d8zj~DOEH,or=64 ;C/on:DtE ?üwQPzp]0QBR3e6S8F~l*Khg{y˳B\:#[uM+SjY U(ݪ7 ?qȱM~X9ʐuvf"3qRСnx;$&9^VxBaW5?1OҢPyTeF Z%agW8 Gي YJYNISo[ڝn9;L>Am!ǒ&궞3|| S*fʑ_2{_%y݃^0p.^Ԥ<:\å'(l,T++(ʬ%]Gg`*fүqN\ dY -,6U8ؙDo[hyRJ?񵬇 8,ʑ4́!\Ǵz<A{|duXUm] ct-I0ݙor>6#-Aѭb-x1mS@M%XC&qڈz9I Ĩ1WL$2sՂ_RTlELSc18HL$.Y7$Wv }ue,.ä~M_{DzCnrY;@fJ[zNz]2+ 5Do㢋Ư45՘IzB Iuʂh3"ҭUs)1z5({oTl9&̅'7iק]V;.Y|{=4O0)/4ln'սkOP5HW^Vva r?S|z2f0z\N[DLh Y<#6qcv|$:j!?ڣO홣ib~= '賺gɃw2ruES Ǽ2D[TFӧfQ1NbQԹ(=ŀWEiڱa {vE"upd#+6]v=.]1Y\F7ұFgE6 \a`t2nQ1BK6SiQE yj7nЖ%X3u ɷt)lZn">૜ԯHjEd+ pfh"EF'{*#1Jo+ .u4<=姰ӈSwfe\$KV1w2R]'/QZ>G5T{I*Kq&!đ&|Pt}j(a~jxDVb:{&ˬ^itމl?6+Dp462.2 ί2.I>zz +5g^-g#<MB|& דg*ň/:/wgD)Hŷ )ԦpŁ1-:wVmUv5\[q$r23^7Qߖ9 (Qdykԡm3I qb`ù3InvE I(~CՊ%u_KL녈ߊqzz\W(U] u`!2M>xy`-"6ި![=QyaWJX*^$k8e; F_ox{iYr"VQB-ll=,*j:!~7c`@PgHc4/9~wn2+_.4;性iDs%;wqDF{Gz5q6v*UuȻN&.T9c)+^W0ݏïF%N-Hk4qeXb+ ;\ Qt"2֋Czps!YPz7oΒ|0Q$͢&k!`Vp3tGb jxWSH K&Fb$oz; 2xF;J Qi/yQs\l1숄clAM q'O_&'umXÌ]k]s?}R$0$xz:1;CVh$J+EeE˺ͯRR7:z7鹪JpS P2Kq{I?A=KF^uN~9EѠ֒#1S4Y?ǭCr13qȯ/QUk:GN!H+;_6ٶ;[,On`\(cz,lԡָಿ UzHj/@>i]F=Owԣ Mlc3$qx3GӁvgpL8XԱ?1 LU "OE:ޚJ9l=!RVlÛ 4JM'#YjtUr孚n!0Ndqz}V^|7]jq*̈́.Sfλ.ͨڽW|&i",h ;=N<~AS}> vl9S}N#AJj#̼ԹϗTnRcZ,zއ*Cus--ATdz c6-e@ұ7q ugҒpqTFGt9b31V|йK8 Dc7=>!2J-'E&'Wqb\cXV7g}+CUn(,*m!)֯w y4yS%3 a|UuV}>{Sd" }+g,AO/͚TSmJ0n%:9\ OsAI|Ysrm.:> )YoE 8`{P sڌ^9h/ߐ9!)".C|4Mz2ָތ,œ| "s?|`yޢjdkNE;cNAXVc2 Qz :,/ `9̙6و$$&XY ?| qWDc㺡,&AIgxLc;߄T..ҡJu _:^c(s#)i#en%N; 1jYc\ij#O |HoY۞tő/ib7_`/M 1=ͭRAL8aSL`13-WDpzTGz*|W|9 )Sd{PuNl_nК*)s\L4|=*3=iz,}'vv⨓ TI]Aչج7hJ]R1Dؠ죇wekw}wd#QUxs B0!L1sF`@R%]愋*(D8գi aGpkakyqDH5O"8*]&Dڎ;b8ܱMAc[=-s@B+4.LvX' (%~bZG%OSSh<dz_G׊a'ywbO{1Vhw {CB|O5+=x%A0N 4s_v=d<Y3#B.b,+& jir5Oy@'8".3IMIw"4BY`UګXWU`IѤX3olU]/mpdD0U(coDJdC3?2$}{cšgj"f\P} u{nr[@th=C[J5=lhf6- DD:l̚<(h֣|ْ]N4|k6. n~p.FVr|BIgL܂+n]gTiz-ߤQTL g'2+_w?xiYk"|B`cV srMvy僳3ӑ!sQ2INn6¼JW<_7ׂj@*G]#$řء_0u%'G^򅦴s03])$d\JGY">&3G܉7DWg r'wI>G'KD3 Ax7Y/$Kޏi?@䤃H1֚"]2I9No).J1K N,ܚ{ t.6Z6.R:f-}:lWf$"ea[)f۟E^oN^Unl_m=gt$ڭ:&5e'4ϧA{ #轤'HpA eS<-c\jr#uO8AT6Un,Wb`8w&UnDl| + { @ԱzS1@M '_BkÜ6sHӌ9]0e-j<,E y{S_t, //3)'n#"|8Lr눎br.$ 7H Hɖl߽dxwGC|2m(D*gwn0(T"#웂.Q9L)+*)hc) .= _W\NC"? ƙw,2[ْ<2S7Q <^'b?#vi@uP](kycI>+) MՍ{ݛ^OLG@",Ӵx˜ӂc'%%^I4lkL$nL NIוTs`e O{W:#&+}1", }C2f6&ٿ<056cΧ(aw&&u/ ~Ǣ 6+Ľ51! 4=fjCd?m}L;.$ԘjD>l#* 6Q; ^sdlo-WY,mjXT6֓XĄ%vPU}`HQ/Wj/)8^8xɔ/X +x} d9@z4[rxT}7yy'Wwa6Q$y1.mpNK_b f gf-\]bcfO0?ۉXtz#iW>k vprmzpiRClt8I*XoEYrW8NUӞL kR>}f](:X"t܁`Go7w?2VNikm٧&Y4 X\Ҥ-6=axyV+ףN>ٝ #{e"m:y0`˻rð%3{CN'd^A>w=d>6 c{%xz8I.{'Ю&rY(RjkAygL?TD;6@ 2MV$]=u]Ub994(6~I큽ܜ }<~A;t%eB2BAdaBpTcK5+ hFT| t4iw17s?O")J|h:O*ƍQ*A0|&zKvnAM۬+Psdt{}R=nBQ}`$3{lQWQ@ʖ @ߠya_{y1ԁxgA]m!<S\뫄G15~v$C@Og|lxt>tI&0D4 jKWZ#0eV[_Tk~!&V*}ns. 9ôSٞm}q~8J9[l7@@{eQ'i)O(H9=6>:m8HMŹ~=Ij*n{t r}S]VjBӥm\{?m <%T&I'BH0C|J6X^n쥲b^tTJ֦RxnC[Di%%0i5{(WFJ&Y}SI:Wy  _̹#qGKPϽR.X1+2;mghPX0kMQtxCߡeEVr-EےakqfO@N[ nnF惻T,R="- 0BߠD`))bÎiFг.%@}a:PYoLsP p*9RztT=eN4Eaށ=eaW*jtohavH%d2"Ửǒ mø0>0Q;8YS" -aEuZa<>48d2g`vo!=@PgBiL h>~yrlG؊S-&MX)W֚%֣QQY.Edݓ6(Ǽ8t> OuwATi'_(/?M1~1EyU~Do}Fϖe_oGΞ[σ@L隤~Gbyg/C#78bDܤo(%5+fN}j+( m[a.j8rիr}ԐKd<}ö4Q@*/Rp% quhRBUsp0L.t~@M9}X t;NF9Y!Hg i\lVuGe{ZEa!u+j i@39!*^>&K1ͤ0l&N[F2mPro\0"DlKssC\-L+3bD[Ԝ 3j-ȇf8*>Ziu ;7mhXA6Ě׷%`tkf'j' QR{3O~ DspQK2/bϋĬb)DŽnR-L% V(#ڡ߹8UMK+lC=;MNY¯lȞm%yd*uPf~oBF76B\Nv0'-?6x-~# ێ(..-n68ծCJ,ՠG娕yN~w8(f\3`G ]=tS-C/ᖫrcNc,5"Yr <$BV$Q ,HS5Z+[쳽uxķB~3a qȒ@%ey=OHV7avW_^u}Q&=Ba HzǒQm%zng&Fu~ +Oy2( (>F Y۞ʛ:-)p ^yS@`.Q#*CnU7>$\Q8L) ד͠$So!>Nמ%¬M2Z%*eD5,/f'66;Gc-T Pa:˷sCڠ89 ~ա6}'sFo& B#A0ͲhTQ䩒K.+l7:XȔ6馔p ojVAXaC23TٜCQ烠2i hsr,Xl*^!\m|Sv@k˺eDpIC-~GX&~ &ޡ`Yb20g  "Wu:.x򶇤rvՎ|*$AvA^SB ǧ)Qal۳Eh#cȞ*aDJDuS1WkS.?fv!EBQg W=|PI'Q^}\@+vO78wL>+wuv[PȈ@qN$Ae;\|hVcv&Y5*(<:`OYghr@8V$?̃uɼv$IDpBJXtp;x5g^{G;cƟw4E+`vH^y͝\4RI$/J4`n/8C'TT;KS0gcirvy-s_~^G7*9dJѩS3vEu^vэ^4oev6i΄$CҰY{6Pbo BA)<- [$d/x24"wNJ'=lumO+ fwq+Z5i"N3dcvǮ"jѮ!gߝV`D\WR /;z˞TA QWn./4)yg=qa 6BeUZ&aݴkƃA; ےP~w0JgC-_焾\: rmXQ@l~vTs3eoim=ݴfŊGpowѭ~ID TTtp {t'.CH;qOZ-$"JQ!(qZc*5z% tno4GQEzUrN '/ak_vjrx(R yg6f(UlS /6/ ґFq]p0+iJw ۥWI*s.SRŲ Mݎ-^QPzQrIgr8nSLZCl5 EDE{{_ ޠ^_0>,Le-g @?H/m4ϓזq­fT*ۨUP۫+A\nNhjoyx;wAC~bg]wd 阄 은(lE JQ &V'.<ͮ89LQB|b }>f^Ƶ?RϽx 5h[uMW  iuE-]fz}HMxz6Hjj< !4`X'Xaq'k4z%=]g2_h%nJJC ǿ.y)R3bq^YsQaZr6] l[\$X,RmcR_?cd,. ^aD$Q=Y[3*qɐIc`c_g֩ϭX;;׏+*FHkm9s?e[iӑ:7u3؃{%A__*?VϨA) \ j.h M~?V?~D|BY>t@*Οdq90d36F="kLW(sf8F,}?)X2"8p|ˊP0kMOGƙ2e\}}\!d(bJ&&|lڑK`)Igf7UQDH8%Uch( ɯvYNX&F ΆJZt$S"䗰 tA_DmPܧӭ:gJ o8^~\+s< %TE:.JyE3_2.*dQ:OyXxԣ|' !p8:ݷ~@X ~9]Aax%ƭGFN(f.yP~yYpO\1;Q5t˝qlRʀ%#ܵn/o=a݆IEJfVv*b:M9'=7,#Y{w*;yr%\QlƩxǏOL8n0VrVFT"ޭ1K^}A7'wsC /2N {7|(lZ)Rbވ e#o@E'N0~z& 4N ԫkX(ߎynx{PV 4F9Za2m^$_5UE^=|)"?rvg)^zjUؚ&FPhF9K=G_! /`t+֤vv{at`.+O7̘܌o,l2S!f`4ġ[@FOEm$9i*\0N\3?Na]]Qd~>/pDBNߜ̐g۾@[o*{M k؂|;ZM.dK\@>찆߶I˭e1|Ԭ8Bely@}+\=F9qcQen Go1do]Zz<'4jAer84 g`D(RS?& 2*%?xS2uU4={ v5*\LŎ(tk#box&gC* 0F1p 8hפ^c Gҹ꼇Ǡ~ QZ\G6wBʩ:i(9q):pGe--?Z>V\8p'6z!\u\xsJM}tKP(|4³~Y݁\5Luoo=&qz~>=oB".kv 37*"PRÁj0dY&D/NW!ѐ@4go WmǙ\pJN!ATH>6K|VtVQp+{C.K^+si|P-,}2K!OXYWX;rQ^o>}dP-泰a`: 4f|=㐪\=P M^''i`Iymhߪr_Ϡo ~>lC8/z E,x8(iM*lI'+sފHf dONl_9>3K8\Cf5H~soА-S{biWEkCnϾu *#ڛw+\WW:R*S_N,`Zq#|Acʼnh# 0/J/S endstream endobj 158 0 obj << /Length1 2445 /Length2 12764 /Length3 0 /Length 14165 /Filter /FlateDecode >> stream xڍT Ӎt!%H*11twt tI4 ysZY筽RYE(`f`eH(qٹX9QhiA`[(@g@0D&i*8d]m\^A>Avv'; &n s+@B+ Cx`c ftLV@;-@ {'(jbl) p@dXQhV j`wg "] .@g&#Prm,38X9 @ MM=A -$- 3Lغ8@ML@&R7HL S3U#ۯ06KٛK8.(9 }dg6C {s_e:i؃\2@D(e@0 t=̬~{:RrCjvtpX@,/o7 S_0@K=1o 3Y?Ͽ f`ojKho^ ';@@@#oe?i*co;[H?+`7dq{nyr俢H/=?z;?uC(_S-'WhrZ 0[H4h Y/5~4[=Pnpx@RBN)3N^' ;d8yxhkl` R/LyylbD#^o`lM_`8lo#.o ` r]7+Fv®/⇰+Fv®AE`q@ 6X˹* )x l!S7q! B >b-~CH?/%wD__zW?AL,~W $m4iұ?J\Zl#@\!'=8@[ y3Ctt3ߕ6 %uC`Hn@l9ƗuBaG5r姮>P|IJm߇֡Yca;륻h=Dbn=72q!z/1[NJ,x*haTB%' 6Lx_!B)_A){W+\i\K\M*ְw,9}ZQ)OTu2.L!608-ޓR7i>kt^|8 [?$R%F: iC.+aJXhT&,mHȂyPvAma?:Ўp_[ ]sդHtxLK\aQ\}8Znx. pFzW6xl4g|Nnj6n ئZH XbNeG'niu .9|[~cɋt%z#'M˽<{wf,m(W<Ͱw5"}Ēi_81~[$i5[?TzlDRdvlwJ;!ӗo4˸=b2ͩ`Y bZstX[aY./j">f:{n#0Q,/w\kQ+&%$ ACl-۱9<9mpAnWsQ:]pLD)ȩ"%wŋ[ggA4/*T l< AJ K")ٿ226,MT)"ɠ;PT !#}|QoLC`˾"?z>X !R[d쵏ˀi qf[ޕi=Vʩ۹? a÷J]mUx`UGɧe9CSaG=) dսװr< ]vJN?? UҿE ֿY`пzH41?+!76P9 j S:'m^zR0˻8 Ӗ6j--סą_>~Z}.dV+Mm|Hq%ZSkLYXFvϯ"(Z\ϴ"C10-|ȓ=AiѲ%:E8ɼNT)>::eçM o˟Vld0'S6ާK6*btJh#ȩia讝R5~hSZBG4ް~D-Hdc&@X2'/D~!`yS$";4 6.a)иkTl*ېႜIH0 $lķ~.D 7^M`O4f) JkQ5AN|H&cͪeN<@*' i ?-"&iK˚aYNp[ E A~˵"N=u|C^_q.殎 .nF |m:)e㤮P6!,/R3$T:xPvG,gE@۵KRfZf. &Eat_lj%r[h1&ON~Qvt~/0aO]BXw oJ2ZuvA3X~ÇPD,EϪ3,B-=prDP^˛kw5GQv".I4ՆH?j #^߸8%y&)/gǤzbO~- 36"DWR#zݤXךF~+OIF;W譥:,02߯'r W~*QJt36_$r*wJ4x)qU 鬼g/*Y]Ҟa1v08(. ?;zڤ /ːslGq%'X!/mOg!ԧXL̼r_-D'r1Łғ(tIW NM>mPY$sÁ5'=LoFQOCz!>YiB!.<MݜgsU>۱* l{W̶ b-6Uv햜fOm먎 Kq;\ 4ohld=*U+b\Ua$ * Tް/^uX{_'UBW5@neY@;J{l"Z/2jܦ"#ebM{ 0P\;SNJ+8`A}N©KoָqwkǬkhMՍULDK> )Ztme !Z_وF+/и`ɜ`u9)Jca-D:L"ϩC!긶o:{iV,2*r64y[3j䠚5IE`u*[yv=Υq8'NTx$'ҌWnsL%{~sswY_3 0&ȄeHOCL7ЪHO)h? @ZmUܜ/6H7{L~n]z6g MMo(V ggG&񣲕'q\cC*4]r^}@'5^4QWCiݵ|0av^.+n:hj'Ď?*zwsmik_g$vFO~BZ[=m0_ R;9OAPLT Yv[i6clw. l+eAfʷ~`.M)wx,EC*)up?81.(lp3& dZ aUi%f#YzO GojbqK9*oSR,Ƹ8pp̬ދ9VJ3 Tq6!9s'Bv• HV6l`$(LrBAv=H`t- !T q KS-"aoM)|e23w~q%VHS&=qmvt-o3e`> EWʞVNbOxFbJhbDR#NBgm3qYw aIR5![(8d97jɒ08K֐wLmSK¸,saV\CwGVx=e3cTDme|Xc@3UשSGv_cR+|I:~-I3H~?L^[rY#ՑsgfE7m2J^a:?Nñk`S5ȴ~Zf>_ik՘UIQT{kk@U} *F]k"J6 \M3A_ c'Ҍ=/=HRyxH :P${%c_/$N<;%R VY֒`iv1KS+/ ,.¯WL\]+GvŹ,q5Eng6I~%OYyh؍1sesUl Drݸ.2v,0u.<}%I1V*ĚUrD ;Tx(͑X:(oE7CxW5,;Y[Eʳ-sdSb٘49(uV$6i=dgqݞRc_m*^wXsd]tzOϬll˥`*??^tG D#kd=-xzӕb芛؁i\NnB#gW;o@AD% 70G< dϝFˢզBզ fP(9+b|:1>FO*3#j6^XDxhw[xr83vVE=>l᧱Dq|54b;b >?2:CȕP@p N^\;2(3{ݷΨ9ApwxJ?97r|'lMJX+J9 X J1e601D^0Sֿ{P5_N))!}s]={°"ةm?+7āns@}b *YҿsB<#j DY*@FڴX$N"mV hhsmՕ2҅IRml;ps(A1 w_Yv8wת]FL>^^VHE?3/th 7o#7$-kt@4H̬p!9ML ą0yՉ58:i)̠*"zح(Im,6‹ƒg qja )묞+Xn&r\R&g\4FnLbȠ~LQXPVd Oq m;E2Җ!wšF-G?Kx),QѰ](c]jT$\.بTʮX/3rG%.n'#|t Uw7Rۭ!7{YH'ac_Do pJY- RUGq4@FB\B0Q,E59dՌ>Z"]sܘuW_!r[I3bVmM]0üE׍KH͙b |]; MFy}Qv; MD$Y:YZBuHrU:hz 3jeO$|Xkm9,cKrmHo k/3KG*oO^@6%K3=X#/ۓujq 7MfVjV)|}8cqʬ2fOe`4 8ߏ1 BN?w>TC#Vnl] Kp pDvT\b= 9JpʹI䳜Vw.Gɦ]%b!ʕGCWg[)7~wQ=R h^jp=* ވtoݡt:Klݬ{p\߳>G<ږKs4+z<|,_[ |0J_kۯ`}}w NF'jOU|]S;wC2t/,&1w#llϣhL6:?{2DAf`-O$l զ+ǁB|uET]2S,r^ qFqCCa 'Z(?]uMNӨuKL<ܥ·Jo#K9au_A%Oao7 \`f}$?C{ c"j6{w"NJܱG)r矟J`bxKG_1Χ~Q{Luo g5$+<' lq PFf&pGwQUg~y~׼*GV$=&A!mK؆<ӼHgkCd.LY%V;x"pSql+T51YbɀvA!-".'y@R fCG0x//p,5!+_̜ڸ+t2" *LH'>fD%;ZP[u tq}38S@(=~bd/ UPy+N4Lpy;ħ_w9П"1ndie=/C2|CuL~`;Aا( cĸ65×]f_wo }ІΖ3`:,Z(OT~TZB+~*Rү(fBń46E"E1u?o8Ei0! § g ;wBkO,ײ/ _IL$[,eOT+g2_F5Iޥ enoR.O6H R -hxMNc4]2^l"J%'q^rw|- "7@ c& [~0{n 7_OfV>W٫M?үiLk ~ 3AW)=NR`Ќ|@dMnEںՇW.&eaHdA4 j, B}Gn6G4 ^$`3SЊ` ,i lepl|J\dl4WW6q&kЭ51:rljaV1PJ<= Zټ% Cw2"أ+3*01Ȱ ^ow$["DDE7{7OL(2W+L]mx)y_i{+_V;۠R*8Oy}0j@+MUzgK7oƏ5 ty;-x;Ș/|nѐ ږ&FTb09K6JƵ@% u.33>Z MO/ /*:UH>Tdzf:rSuvr J:i㩼<`?4/D:E] uK5|JAr9dYV0F!]|^ޮ#!Mm fIBW.BgޑGSj 𨥕kMm8_v{fn>>9-b}LyDiSjץ$h]Z)tG`^~?/5e_w7)J͎_"=bYˣ96A#kE[/Wkg|A%FF7ȒzZƭ%.`6=mG4|%Rq^9HNPcJBYoiu6*NN@/Gz)TkjQ,):whؑRC'/.Ŕ.'hJY$^}Q+,} 횋gN[VXTlֳz0ٴ=0];ش-v5%sT&}D7g:aೂ^vIF"J[8kџ=zT]B-}òzZz fp$gW 0Z6q`KU[T%c ;Қ;> ܢ>+{w?31C=zV %97V͏MśYZs^!T.ʆA WJz&F [*4g P=I%lf;&2y7_Tp(I:zM}y5N2:!)dLj;A3:٨scQr+Q#;NwBV:k֒F'&ݧ6vFv\{ W9oB&\"k7O{wy0 v<څ6&I:MN2]Oj^kw"HAn ~9Wf[5Z`N H,smTO$FJ~gW`g(.H*Ԝxh6c}mi(2&ԑ,stF:!8g6 m"+ap*㾈T7`'AIA'rI7Q_7: W}wM[L^V9`炙7KizUzMCɖ' :<ӫQ59;uiD0/zy8Q5[\fTFu#k Cď5Nk3@eڤ] 6D,oZTYUbPmbZ'Dꈰ9%3>m%էu 'Cz`QVm6K"a]4fj ֒%Mo69՘hzT$doʳ/ ~H'o31].S!amǢO6ӇN m\L{_ x[]sc}B(~jdR PciX[Gv4 P \NtLonu]֔>CĪޕxn4i֍5KtY9ԜZ40sK+VvmXNiIjM Xȸ Jɐ( oiAOtOe|KMd;)rz|qϗfgUF\zedIcC*QҘkW? YMr*!H~0zD,#Dea8q8jD&С^sp>O6_Sem!e%'.Y;`e87#\6 Kߞz~ ب R@yQQ_N]Bυ.أ^~n-Ory%m0& _7F>TA" b`P4j OLPB`TgCU*FZoB& cTeAo/ىt$仅Air:VT(G z\s_z1иF6GP趥 &ˇifv\-=1+YwBU5zUE; OWM$ @en*qTg )5kQRattP Sk%E{;ׇd~4ا[*Y fIu̗/Jls5rݫcnTD^M9*Ek'AU!Ru UxZ݇<`~t9/0Twmԗ)uR{6{*s:e07.lU=Av ^C7ӢZۉDIo5Wdpaaܓbl~LrЫ%ٕO 4 !fk߫s`\za#Ȏbck?wv[ŴKdxZ#a]IҞo1[!du2¹ o7?LH!]Z$9 wXv]WGԽ<35ZݯN>mF.tf>?3Be ,ޅFtޅqhj-J=E^{ 9"I 2}j x*́™8Pcja^/솔%oN ;ZQ~|%e&|E>h˷宦߉|xlKk{ĐڥBj`…d pbq=HASO7>>T3=MI]kݢT^iKKv̌n!VBSZ-BK-iw5|w&q*sO%O7+_t14b31hO-ཤh?vxΣ!htpq#l"\n<;"g!dfJG)gޅgS[s8[~ HPe3lj0w0-2^Ӑ4 !\/8-O -MenVs*EjQ,?pD/_o;T D mvNvۮ D}9rc{j?&N_3 !;ea_[G\a n]5ֿ:jQX?li۶l' `ӧդi3(֢ ˚j[o7;*@~{j/q^@2WL_J<\5U>?}bla>pϓkNOaLh{ŸP‰i|s > 8w22qU{U7v3cTm7ǘpTnxc&tF1Q_LDèaJj ).qB!sʌyZ&HSp5{vt^aj+H0j!#Uc:O It(?E r8;w'ĞAcX!0\`wxsVw&9>YfsWfcߦM s/t"gʦ7}h؉*M]e`aAfT 0!6#?ٰDP?7ی ה+9kDKw I<~~ OMO2ٙ1-Mn}Uz9-T:CnQKbcOBVII4 S{ 1o`%A"WZ[ucy 5S®Ja{죈waxZ-}=o0v;}n#w=:eG'ߨ&l{ T)aiKg앿lZʹpԋ tb7.V`yY l{? RFbqS4TTjKa-?U<(Iadd.kug6 #n^fF'7.,CD7PDcVг. b3Ya tiI~^v۪#7}C_d.xjB~H1q~m_|®w fI2gm#5j\rIƒDŕrfq |OTe+3&4[W7 "cIX!lE"jIE|>^+KT*%BoKjtVSm[5B-:Xcp'tU]wt$SZz_+ P\ɪFW,x?U&! N0=Z3F5u 8}P37b5៓t{|#m{*w_ѸϞQB{KG6#VL0H?:vOڹVA}jDxqzs\`1O|̻ZO |^tj 0Ukg"0fq{'{F*r!-h=k\bB GQФ&n \u ~7BC}3HUT9nWǎ:)O t(pEIf>|9Զ~ s:KI-BÇ{ i33XQ?_V6sN6"`9Zz`t9Q.fe};[*R<{SxTȱSɷ 0fIC֜Amtr̟QAlM*->I'T\0T@ Q6} P xbH:*ԬRUOEr 04w]#E.AUJN endstream endobj 160 0 obj << /Length1 1755 /Length2 8795 /Length3 0 /Length 9916 /Filter /FlateDecode >> stream xڍT[6"H 5tH  0-)ҥtI҈t(x}}k֚=;9>3 -c(\|<@1HW x,,zP?v<+B#Q6y0Tݝ|>!1>a1 'ȃ=6@A\P;{$j<ح9|\d!p5#!ΨN]5/ v {$Uӓ8P=@= 6-4ΐZcC:taHO0@*V誨4]!.ks|<|G2 vlN: ;!`|BQ:( :?5D N{Mf93]<F7_tnݕWQ+efA@)xY^@Ճ+`j>|` w`F vPQfupJa60'8b^Ec %%οZ)+ r "aAh'UZ6b$|pK.M@kHo"Ew'?`g_(#QCFCC\^$5 2.vNo$hAOIs@`5^֎.jz{Ik1p7%~AA/jm ^CR083~ Um xA^H|(oʼn;Bv^ j)A?/bS .F犒_𡪅 G QNj(z=%`xuDGxS_j?fuܥoaf-PvU+Cɽ5h%dWT&kiSE9Jk,bj֏ոiwv4H4 cgv60j W$GItD&MRބ lW-Ձ68J.h2{bאF/H&z*B_\ʶQ0y1#c..lٴv%@Sw0ڶZ=l "rO5cl{o{pYC2,~5YyZMtz.%{\Mȓ|)T.Јʙmw4c+e*Z!Fϭq/ulD=zփucw߻T(DG<ɹ2JsGŚVO]Ir.nӬxB\ӊv>s _dopGlgJP 3/GіKՍx{Qm =z1Ǔ=9ܢzj˂zCTnԸ}@_a_H?"Eﮝ{enx*S`ϼG!7rk_xxiqǣh*i͝gЃfW%lYz3%)1z7-Bm_Qaп)ԋ'xG`r4y ZWOUEgco^K13eg#y(Ⱦ%z)UIPeʼnn-`>|(N1m.\prcKL0h{J23zWFr)(G7!E+w71S.qڼ . .#ӗ@<|t #m*3.ZUlQ'!"bQpu Qgs D];4d,SU\u\oU=\a6b5B]zb*h }ٙ6,a]7j|dgmP? XBu~GAjxUs|۔q4&RiD=c[gSs&>A čĘ9D.%t_TT|F̳&.ږ?/xM:)}vu(}pLآFAʇ4}.T"?PqG6'{~X4[]h#jfqF!q1Ƴ-Zw'~y><;/ʮ6t];.t,n"?=#AxaSCw|Ocgr MخxPF/ޅ !78r*皮<Ҕfω/CA$}H7|qniIp/=#ؗBu e?yJhn=9>6Mb,y$!_~vyFYĺЧck׌k}e'gV|XJ@:rvG1{VOV$1>:\[Nƿ)=-l"aePA{X&B|jz:fH+ dpv˘a,epThE+nbV:YEOUĸa2tjIA(Jb̨ ʼ6](o/~,5T;>kW2 ^&[,#D\}byWE\gbj)6tnOo51֒%^BЀuH?4sMulO5QF""d!Bqa|v~&M:sM'Bf|cR{X:ѿb>*SoSa`oJ2RAF %{Z#pt|R ԩE3քF.RWTGǕ9gz([bs7'!jSB"]xg%g6*5[ScQ//M]V'8UQN]iF Sax/)6&R+T!!QQD"1|^S<Ĕb^`db+TߊvZB_ %TUfx`?ȋj:\|L/R۸3l՛ͫx1H^n}8v.q+f-ӌ?BWrb~c^)b 4PU\:֩a= LQ;cR+X,Zėjn剓 }"Bgn-I&I.ӇH. hgopu][}i!vizb@lo$4puս΍!q.%~zY﹵M(j&@GVXX߮H^ν6E(b2O+&ks]f/PyYj4+*>P!Iٷ-Bځ~x&ƘHVaaz^pq W=sd$V{Q!UkIA8e\:A?~Pi\m#k2;{:_ZXy>Y[IkGLD<5V?wS$ o6dUop%GսNa#crgG犸$k|:>3I/سݞ玑:WH^VbX^hZ mo`c mR9E4Ϊ&mɄg5w?YX5ǖx.w6)QuCƚXW]9 B[L^ WT`Á,AEn2>M`VRW Ʒ%yh&79 3eꗼf rd 8;|:.mi$(aٻW;D{4n"/.9XC}sqMVm8IX5)~rL[V YZ)H#5pd>lBg;x$O>B~ED9+^h$Xu44sФ} x@LLh0]؅k6Ӎ*eEܲo`sY;DfEr Blᴼ8 |εGЄaݖmTzL9:E1Mı g6r/M+)* ZeÅ'{@ë[埖#>dؐz +H3U.Z2gvBuYcUI; s[śOku9ù0,L0=(Lvd!׹4l@>kfq&(i2gbnKJr#4 @ܡI=cCls'K쳇NˌV $U 6* >-䖾:!ac h4n>;?y>p,SXa# Uي!LoBxf?Z%BHt.G!`1ݼM+D[I2'q,TJZrv(R蘨ۮ})FaԨa?(&{ʕp?7-|8ݾpjsӮq)wZa$p5go6`ۅ , ]=T]tޛm+/jn5~o/%a"㪜<]m~=yj͓2.(6vӫwY@SfTd׏GAh^}Zz9Ov>ǦG^A^ !؇My=3EZWa'P)t|aW' _aY\QȉSm,pscia2+ӆЈ4y^y(:ف,p BsIN c}p nݹHa$Y~\,FD2B~+ܚʍVk+5 پlğh}z38c}:sgZ5=O.m*ˊm N )9}>TiK1RH;p{O(s_FVa,#MS:5sǀ+M#Q?#[rOZ};q`v~™`Ĺqaxacޅ. (cȾD|0,7jǙLB;n>圿oG6ggmZ/lq6fo]xY :3J8>6 kr\nHJxmcVͲ'WaʭC#F[ΥmqiW~l08Wh#NJK~BO,Vf()Um Iĥ}M󘤁<dz5Mm>~n nB %h4 R~0WpuS%їUb ە],k'fzo|*9vY67XYJָ9}+zofͪY%e֓qU"]\l^G/Rfpͷ1W ĦxP2!PGsZҝA MȎ<ߜ>[}QSƨ0w 6&35Ӹ "`$T^Ł2M4V@D/'}Qo/Lӝȡ-Q)s3ʥ'~ 0Q<Ge."╳l(8=RE~}BFq)~#ItL28GVb{~^e︁mJE%(px[} 8c. \UfqlT/kMxkNٺn`~795i;*pP$.W^?mླྀb/j'SkAӧǠQsN+uo`3܆0J!&"K3)fg Gc[E݁̽{rxFiIcc;y:'c66ii:{f^)SrHwV8 %*zC8}\Ep}K)ef.#'pi{?4Wy+$22ijsW쯩(l6/qTjgevؽk"Lwcs~P&)2{lJ4~;u@UUe=F' >WIȒ'eLTlaEeUC?]p[7_M Uh/_un< uRD瑚~U K;ŰʔG+Yc/3{\+ծ0AkqJP_#wUYO ᴧשּׂA|g.RqL% >&0:W7#EZCD\Cj~udk2Eg-P}`uq[CEIhqJŗ[ggR'M[-RVsb[^6Kx*q[4>:o¥/똜 77˜]lS>GOI/;X * x Iƾ笅;WQwBd!v8~)[\y*̐/7f5'[92z*_fy406IXnyZ^XB.Mn (? r Df)7&`Q ׸*ؤ0C1otvE4d3AAgmzJ-TJer,Aimf>Z|sҩMM+u|*!Tsk44{xI]1Wj?؟ZXP3*; = z"Iqs_DEf"v&z=@K_M13ZsMg{sQui9Hs*TF),/8QZn?XzN^VQ ;=ѫX,W0TD߯q-qeH^)QLLF'th*C(uZdQDl^nܙ܂LR[t).Hc5f0fXץ.]Ljܢ-17,F `UV|>+1|9N3}ۥ9Βt{9UUf_Cw`4 {gyE_\)E)[As ˲Doꢮ[6}4ಉu>E,OFKmKqI+;Z$xt7udMMa9'XOtjÆM냹1PEuQ\.xB|t|>?%N=0w OxAXfanґA{>18ߪ5~ϯV0ȲL$>8qIbXrQet, dZV endstream endobj 162 0 obj << /Length1 1548 /Length2 7434 /Length3 0 /Length 8454 /Filter /FlateDecode >> stream xڍtTӾ(tג HwDz,R-J# ttwJHI|w|svyfޙyyhTP#;(TTW@n Ah ˎΠwmmR`{"@B@ HWp[:$jt?篿&3$(; nAE%DJ$ltvv8p-D0GKjj2@ l5t%:la}p:@]NAVs8?f; m`[Wf ((p88f`k}< f 6'. W;?= #4c5@m'B徶;ۺaf0sBpj유rRrM,^ (@P%4\N/}8`~fAww흠t@3` ٢ 5߿=?{m]bNi]i M?[SBpgs܂{4*`؟e5~LUO 0̀R  `[@^ Ve_u8Y[3A`럌{:9/"~l csf0'9A?9\f*0GzîkӬaP`_/^]Ҷٯ5{-qAhu-c'->pߞ'nNxL ?7p pN!p"S98\.e=-)?>P >3 <kU\ܙ}cJsݮ@^GƩ wKԤv&f%$_U ߍLz\ .ɦ;>Z8bf2Y FRb#?֞o*̹ivphӚwЯ8'8ii< K4^'/,twsdw'%p{E%02UF 3">zO|?I&2UEŎPU:ph(oJE|+$1V:h?H*R }iޟ2p+}S{m8] `D$tufᴣ{t4\PIpHFHM(GuB QE{zVNڮLHy>z`%IQ=Nq,\8a<]j:{ƍ(2SCH!ʹc /+˂OЂF9vdʸCc[HmGF4712NӎU|'Zf4 pG4eO7zҷ=r0(dfŘ=Za!.$;_ޏ=#Z0 oox3 vTAJwĽLpIGo9Th( w,ҕkQiYhnن(ͅ]6 ik0r !rZ~R֎Ȩf^*MnILZ[Z54nҾ`5* _ڕPBk;qZqV,".WrjyrmQz.u&;iʫy"-r9?O{J}I%?v6.-j:G58s`nKTHcm0>;'ڼ@s,3_Z7Ϛi複?Mzߌ,̫Dn#2\>h_Qw3{FK%x|@FK,]HINд ywӎR[axF*׿ ?>Q%PsտV=F<-oڽt\j!:_'$ /}'[KQvw;kRZe:50閽M$N(&Da5|6#1'6?8Jlo;͒­mX2 qGNŋn%ɾ+yUAWFӅx6]zRoG߱%4E U> njHxR6Rdzh5Mب76/V^ qlUʶAz.G/pP=jL-x6蓤5:%)Iu=%hlFyƍe(Xvg"sƥ+92eɰ^K![1=eE%wqf b2ph%YF!iqN1T'6Q^׽Xu&㑕\!z6fG A9f}C(eYQ7Yۆφi_xNV<켜N, 50+I0%HLY(ȸ|bi3+ʙ_5FO(ř4qL޽ک:Xx)j.FeT'Zc gE w2MbX'\SvlͶjƹ!͍%[҈&4 &CLkJu- %1yn#b 藥 }GH?oW P=n-\,PF -)b墇dY⊊[+i xwT2UYY^AΫ <>0Φ<ޙ\B{N5 [C,rza$w3 vlҟIҒѡ) DƐA;R/!I]\SBa spXO(}&NļP)>LbިWM)=MqUAKQ];t–7٘H #>ƬG߆8H>8yFLt,bQ [:Nn ɏLڝd@E+NNy*i~"M6*AŗZ?= <15d^:>~ [=S+}/Jt6lv`8eZUE5]x4^e$B像ty)C\Ƥ%VD,ȷ W5Cp\8.rsmPSxwCm2B![O—zHbYMҚv}/Nh$RDS"xV2Q! 1gYX{ӉJaz\qoY-dҤ;/g4V j&M,GsU\"fK_]iȑRݽM~~.HOojuç'<՗`ZMsTij\k@+ JjQf+@D<»_wCab孇^2]қu3>!|UR~yOEz]!ҦoZxZ~0ؕ@*//\v^enzQlۢbf?Dc'hLAwS*Z4]vTS5ȭA%+?pW A<+Θ6Qb^H<Gr3C.BHB*U2A_DhZfYzsk=eQqΉD*;rAkR1+;X|LP}MVV:hQf<( [E*|wWY [*^<9/g^#kU+@Rr?CnҵЂH0]] ]ӜRlܔŷqAwxT:so)j^uތYuX0 EGUޫIpkO䫂ٻE^X,b\Dg}K򰟃G'?n{;.d^Ma迺#{m+14>vĦ,WThm6 ri' Vag7l_ojepV}af~ O~\ ֿQr _2vY:/q!ʌ} K,YA@h1* >P ]MAz4&Y t0z #"$HgHvWDޣJ,m-Cll,\oSLEڝNA uH#ձMs_2utB{+o27bg^^`a+ԣ1P(ȏ==G{s~cnn^u%zxt:@ɕHe)5 mP,"߿Α6bh&| 0hXIO:)yu7VB5rԳoJEٟ]\i(OJv ~(|ۀ;^?Za((F$qTGaKn(+c?e>L[ONdsZ1Ǥ~AF8թNSrs:FO]U@(s q W[[W;敲"g"5{z V0iECXwCk98ܔAϒpJ^>RVX@riQD )gୃm_>d~+۲^'oQt scPtڕzYDu{-P2P4>VUw>ndf·~O&W/)otdT0'D~)w (Vpib\J+|gx91HX>`(${8q# nH+ju0Xۙ覯З}dh"{1lU”эrgw5Ea>Z3m#nڤZw#GK(֢5pk )TW24n/V>O4Hs<_$9Qw;o}a%+u{N49G@25gs$[0:U؀ m2$?Yw>UG\߳2>QPƚ+J0P{mIy(%/1;lZ, Dg|%80!ԽU7 '67wY#!|;c)氤a3=gٺ8͎Dlks? <=8q Mx; f@D7q_W @ٷѶekxsi,O㊷jk -ٻ5AZ(DxExG_P::!}S<0]C%|gFjF=sg#[vN+,NU <ۿ@;hj#!w?Y>vU im.+bҴz Ɨ0'=J:aU^櫼i ~7@^p3;I=DŲwREؙ- ~9׵1նiqUwxSvӱʯlް忓\i-d\8f[^S0 U+GUA`aaW~ 0v``!umjiE ڧ7VHg>=lƸ[|"F%6Zf20 nJ E6^wTB!)%J)ly=^VPpЧ=- d& @yp=e(pMH8q)S}fi3i)mыօ' ;$9Z0Ns#;a.i6D|'Y NVQ=|~ȨjK8}"ƳO_ ! (iwsG.;Eo&EeͨaߘC|VHj&Ԥ2+ 5hTvDJ$egh6jRM;3gUྣ4g/> ;?M\it ;fkѢt_; j%%eTER&3h'g[uC޳vzhӪ3CJ5tYɋюq~#KyGξmJ#尰rMǗamXGד`S8 N(B^hBuÙWZE[I-Ղ5(L()DV\ϱFf G{`őR.{on; =!x86oimyfn_-VPVJ.kS@QZ׾E > endstream endobj 164 0 obj << /Length1 1971 /Length2 11442 /Length3 0 /Length 12643 /Filter /FlateDecode >> stream xڍwuX[--R-XI X)^\P][)~}{O'=kfϚ=Ri2[BA2P 3; @RYSSB ƁBKv``(D$ tyZ݀`{quI:[.,`TKIW~R`KX:jCN yP~Y\lll|l<aa&Z-?v:|Vogszio[A`O SC&d,{UM]KME_%$of^v37Hj@2WbS'it߱TO nx#6n67Eh _ ?vuyeT@7UK@-M4kK v{,.64?=R:fll4gvOWSgm=o) Z5o< Dy:' fLK߽ `e@]\O|VP_ `k`rXE|Vɦ/FVzTZ( oS"V`?<2C]a8~87ae sJَil "Ɵ겆 K?T\F48=T̅,< /P\WdJ:U=(Et |5d"ۢj}Rh{[pWaao@@6Tf{x?-:g6JhuDhhcJZ#xЪIs{^XF%jYVm'Z@ G8 -f09'Y?S+hRef:סwq ?¶(6xS^?f%Ho80h"yCx2u!r;қ8Fc5ILrYD_Z_h(taVWMN=HJ+U QruUp9$/Wv|y[ZLYC|m"(E@,ħ8Jei~d׵}Lv9ֈ.&u皧FDguBcm'P{eSz Bw~gW^pĖc4I(wj}4舷'\KP1VkpJZܸ ԊH8 , ˵"YMيl;&)4>IwUHck!MED̨n>W~jARMr b3DjTFI_?߲ Ef#ݫJd/׎[5۝*Sm× bnE3|ŀ*Y?so|r38ҌDCɞX)S+7΃$<ۛPGdpl~D$ЯR& D ,ܫTڟ&ze f^u"=y_t1q8ŒueF2D9fLD [':v$wqz;xIeY=},Kvet5jΧMΞ5@mC:#nd1"(߉18-+}9u kDթ3XԪfXerC"TGk)iQQ8#}rmtrx X.9tG?Pи}ʏ1'(v@ H_isLKKiш))htVp> Kd~T׌nOg{w\8Gbc)qQsth$dBIwJ@wp,x`@z`WsUNDuư*"sO]7Gy8K0'~!{vUU75g:W7p멦~/@ZTigޗ<2P-ߐk>Alڿ42iQPC ʾAt)'2|eJ[U82ڎ +O&v0鶈R`˂7vFBL!WF>#[Ms<ԂEz_̥<#m_uG\ˆz )iM#Y4QK=ZaBf ң$(2? ]g,Yʁԩ&wlmǗnW I*i}0* O|1`W`*w%RuK5 Ce9~%jyht|*Gg-`Ӟ袌R!u9-@*>;7/X,YTCFRܸĞޔ7 6uVCvǺY8[l2ХK R Yb(cM]3;[ 5[{ֲ {"tE,Ue{o6rUFQv^3hi~XlSQmA\Bsg0pᵙ>!Չ NG9WLVyx[eH~UcBjpBAtw5"(#>UG)Q%*+6v 1fv~&3]qo܃-=kݤJ۫YLw6fV_}\BOGaᅵFe5щp̽mx[Tqb?\F.]LKt%17b0VM=q +a P :<_הo`*y\~ xVQ!_^'t}[(y+F3LE`ؾLcy:ײU'^I Abui퓈`Àoy=M"xIdK$'枋|h%}WٝUG"K $(\JM1  FX;K1 toYmpBW$Q o&m闦qE;~WjЎey B_rF}(G=%N9#~.O T!cc V:o]4ER5VY(s;gf o䶗zӤ?iWdY9 Aޏ,C@cHj WŚQߦŪŤH ܂ gXx*ݬ*O8ѡ,gPK /c1BÌM:yG`(MzOK{I'r0Q7OTZ],SKw5mP^ 7s2MW ]{Nݭ3ǰĠaR-QCnl`EVtܸwd\Ƙ"T9{ k1}]}w_t֯On"[J5Z|/$9* GERF?\NBVtXnxډ]gVi0"HE. gAdžԅ̓kYj$ JN!l)}Zz[NhmvJ @үq]Dy +U%D$Tp'~Bgǩi.W!q⏱p>˷A=@fJyЩ{b7taJ.b.]}4yf,ےLSo1 a3 ˇ2GtCC_iky; IjϴWΏnw_=᱘ݹz>gI%kB<#:s;?V{} ~~+ۥ. ʦ5<߲e/NDq«n;ќ4"C=o|̚uF&Rt·x=+*EzI0 F{:R 5hc$Nlyߔc0\eBsj%ch[%O-ɚul. SUkc/|lB Z`'Ni5LϢ)}FD ݻ梥a位 o9u/kI65&1N~MAۨ}*=\f:ks m~_fD-xhugG֕}+jvFny$_sw +[dqwY iUP+jTǝNj NX2)ZD >R5m/#mJT"ofBBIJ1jTuv !W}d~Vqؖ cTavPbӭPTA"R!8BA.Gxq#k ]Hn_/b7kn:kVw{ui 8dY`;FW"! ʖT]W_hd,H?tTavaӍ) k L}|) "<([w7PP0s,Vsy94)Ǩ]{+zڅ!bg]g(xz|^lb8 ?X_ōN౓?6lZT9$W U]PԢ~)K-Zb` ҅J:!Y?ޔU6#xj-ˑ̅pXg;l0o׋xJyseא-Wp)}2ԯոXfVpm1ᐻO7u+bR[֣΀Ozct7-8r̆gbvN+O<&׀A<#E NmןƸ I1l`]9*~cn|3ۡGj! ۺ}+4ooVɔN<C\Zb/QsEvdwT==Rm} *rS@nbҘ]jrqow!Rsپf!}kShg=s׽ kZ' etcFof }Ԕ#fNz%hz2ʅ*|sr [4Mo; @_&z݅"}#8pthx|_T@{~R3츝m6CP\|'@8uq[y.EJ2ȇq@'jgC2.w;C x,7k|akso^3%Ĕ3IѠ~|'8HN _GYPS 3',_0ka)yՉ Mgg {,%SHٮ_+2goJnfxJAƂE'T&cÅa 8Y 59g/Z`J)؜k3Z=`VD=*{!KAq4/%Q{-[0,A_Te%mg zb`z4}@Jzm'l$sBoU3]SH+X;bF;rZG+ѯ} U/;.g-7~iqQ a J~$u&gTܙ.FިVfLJ)ѓQ֊itEѨ wCVGp]4'"Tslvg1 *⭳{5i|k?|d ykZ3)-PV}dbMEd6"W鍻agR#?.i8'2S-{?Yy{q}~VSQ|2Pl43G6u|%))QϘ(]8xWEC(f tt"(̢X]u\ԡfE޸Hr"1(kwn?u߉t]R>N_D+tu8܌O)Ö#m?, +) Wx$S!] _ֻ?sdRw})Xpyx7t-/[ $aSjga%M nJ_7,Q%G#D # m5>X2"gzKMv3̗vRNr.~h7We}ykQ[p k;OK,}gQayQG0{"[+S1HvhU/N71rS՟(GОfE6Vwf,׳IZAi<8+2]?[󹄒*NV iu5VM%]e@B | [Y2AC;Q T(vd=GzHk4>>Kv E9W]zSՌt襎6-o~ՏU4~90F;DNWhn΃Q7h?$G,4"υ]19. f6"l,$Z )N"X^Qؚ~3 ~tUr_T55*!uz{(z^XEthp¶q)`Y ]c5(⎈ulf[Edbj{1s>F}C9BW;JAW_@|p> j(d+/Ӣ=j還DW;~Td)ͯkp>]gzᒧvo3Q&k.':R(3FF?;:3{uu˿S'O%;sf^F'A zs`B(r= TYސ).9 {j p+ʋ_cSqz_>|@U549uZĆىǽCX6(/_ڪ:9}/i$ +VF 󡎚F+_]5>L]]X\4Q4fEρ'^?f ߺW uOgJ|v\MunT'>ߛi[fB!`;=YvrY*a%591JZ7# 3$q.@q*f^hCTZ8wG٘J܊JU?=:;V6dmLJYo=d0z6緙H1|l?~_lw2vLјud喦ԟsK)rY0Ul~ʈ%xߙj5*f^phD?7שm{2i}ǀǁQaRA4<%JH trGOPKn  ޻GΥ4'3lRDgV +o@X@u:՜biJQHyƜQ1Y1N$=&wt5SK4hr|4D=Izꀃ^;8hմL7bȤH禿wԝ#?Cm/8ʈͬo4mݘH+?B՗nUj" W(w&YQF/ܙNk6VdM 죓"kf86/pu3Yjŝ=?^o.I"HMyᵛĨwQӫ[ŇZ!;RAn˃;>fh ǤW5IC| ؞b4+<a^68 s #>@^+#Vy}'qGE;JN^HߗH59wغu,_g}q#܅&4W~Z.uK\> stream xڍTT{Mw#!9tݝJ3CHH7҈tJIHItHJ7Ҽ<߷Y?{áfr9A8فBiUmmEN fthtz W7DiW;T&cTu<N>!N~!  U c  74:i'gW;4~-$A`Ks@hiv}++ n \=AV_MAc v[de @`K ڊ*ugoc Xpx lnilCl`@]N۝`eh74;[@  ' 0On`gw7v7ï&9~,JqwCU d =x\_{_C5b̡ xe1~l@^ (@ޖR8RrCsvrXCA/4?7sO `tXlѡb W7:ϿCfqmUP6TQ`Oj~l6.^ w s?v]/S?cϒ0;tzA \&WoIr0c10w;c_w*:AXdp_9t%$!6бf[v{4Or_4n2P t5Hܠ ݢN* tn\|sWWs4襆/V ᅥqr \~]W>^/M$ M\ PM|ͧ%h1C7A;M|z ڑ] Y&h-ү+aB/kBi hBktE:Fs@@hN ,?Zvi]N~OZP.3PrY=۬baCi v:'7&|T) b^ (NTq+^m:W sI^J|Vk)R!$;Xro f]w{22M-ZY-ѫgh` +*X3d6WVxLZo=}n~<ľUo|}q[N3'*nEkO|XK!_|C*kHҎirD1 w@xlfPp%q}B:S \i~\+\WDӖ3hMqEX6b),O(.+=49t{/~1pRKs5Tv 65N<ky'j@&Ltq 8[L}3.ΝÉ,1;zx]-[0S:Jڰa=(1f(\v׾[|?-v !e2%MʢݨzA{{S#X*i ߀}C*as015W mcu/һt7ڴ7 ś<R~mxV{m)(5S83)GSUGgp)l%D/ kl3Yj&TaQBh! !nE/` _IJK,ks PLO_6 p&wef>:C*L5>xz4idP0~/zB(ж_G_u5 KɿkgY41H+Mw^Ňɗzy '㕩zc|>!e~451Fwn[)F&!tԗB%ЗU2[y}Uc bO-CB"S*}f ԟ>Y*pu5ё j5 >T0M@s 1{!.}y@W,y9t9#Y&O%wMIE5mf6o4@GlؔΏHQ/;&KR)nw.1]r~n߾701^#$%ᅵ&eM \)v9NTLn ?n6Yˎ׍[ ;S?VC\vs,=-o5:)%cs?v^W. jgQcL,tU|:_*w*d>=A,4bC V'mJ\g$x蘿m[`1҉ʸ#]> NQ#!߽k{_-Y6CH8VMᬰ8Y}GQ/S=oD]4tkp_g- Zw?2GՊsukR?<1SKn |f0L3~hX2$HaRnkɗF^S ,H%/օ?iNUx́4oS0CM;5MZ.MG9^y ɣ"W! _@WѽGHUMG$V|$'yWr|wrci#益b&W|3cfrٳǦ4z"#`c%$e`IUK5Kx[kV]khDV$W=3wqj|JKۃ_m}zsWYo?Oϼ}X힍Mv g6!)1Y8J+l>eEۼQm4[GRF&*EMrg!S~żF3M@.XT e]m{}mB߁x޵6睓W!pFOK3?yG`-]=XҕM‚2ppۂФPv,O 4 |a69fLk~Dc8kwR;0ayQ!]OYFkMHwݭzu} )N* K۶c4? e OSVq!H*3o9b,27\ZcfbVXVLާ&Þ/D)z;h8癅~˯"?}͐͘:O+bW9ZMu$e|seZwWb#({1TBӾFLaw H %R48|%v^KBɽ9/~t `9Dh[^diu.򦮾^ 5Wf˭n] z5^GgŌ=ÓkN+|~蟧FN(2'8fڂx.9dj%>/E.<-.3(U\-yH q) tF~xg),iVx"OϕןJQ3) Lj>ҖsB'BS ֮4h4(.^CGz=PvXafZ}U:1ѧtR[[ j\U~G D\;HDŦIhl[V/XY["%{P4Fh&j>.w쑏M$1SIEf[;. Rַߗj s\M[;fy/#RLY Ȩ Nr,jrut_^>!$EJ4wSnmo0hb.eX,M ?*5!}+ME.Ps\ } fM~v20ͳQckkv1DO:XԅGښObS+Ywuٙg;,r#o|S|رX dfaG QLP xۂm2SkLuH)%VoGڡ\rZW]Tɣ_~TLVe9O\o@wcÑt.aoUfA$=aYv* цQ]; lЬ:j.i,3Xã!|(}MŐ }JFE3O*?0U=ndw(<\wGe=o%#@r&UI.Sg*Š]Wk+o>k3/[;2@ M=Cw>8瓄 q^O/ҤʗHגX1~/- +I v.+%}RWg+=ƞ>I-p<羪kC!P;f6@=I`k?"KQ޺ 45b 6|H4nٌkCy Uz|.<,Y=tya›8,Rƴg8ֱ= |j6V$n5@$A) rl!7G8̮7K} m[&{ile<Ҷ%b ?-` cXpXJ @Ȋ8BjjAH)|%yZ qB|[l{L3>5A݅Qf^1UrnlN#Mq}擐[mpkv= r)NZ}sAƷQtn_ @nX-Z>pn\f5ž Dh?sco@5C3Ku#nr_ע50j1ҕjI2Ek%B&oyy4+Y[qr-T)$NN' [^+D8ھ}I}~͑CAG/xZVjS\z=)+ˤ5!1>m4Nm D Ul,cן$2aduX>zLjKAjeу1<Eh?0<5g=,ۀC++&V}5θOvJBBsqޥQH=,?x ~{2D{T$c-L9ҁ+).Ja:+l(j'a,G}bxi.gXT>ueqYL&2z9 {srI.LU`_іD4\p/wV{U#\ҢQ_Mn7JiQ]t+[#eJ'C w Sᅅ":-ik0Yw(?$^jT||eCV t oUЈBDG3r jWtJ;10B6Ÿ]z  Vb}W ⽌*,u:>OHƨyH-c-(`?jJ~yXmFH'4-e$CX,Ͼ|PԃgCii'iR7p$`@kP 6d2 h. W>j\XrbAwV$|V(2#Tġk;F;3@Οxﰗ /ڦXF7? [`#eA+OWL(1 ~ZKUdݺ\} KO. +ĮxM3,7/HY4.΋3IΈ(i`کy|^nQ۾"rsE&KFy߻"+rbo ފ,NEKWS$xKtgx ߥn)79Va (M*sDUx,\7b`"w2oay5zK;Yf laJ]$=) ,:W63;2_N=v#%Ѭ3,DZbFH%R0P-:)`Kv+i,TF"e QRS<J_UiR Ҕ]#^Y`s8yPmXP!=:M@PZ#KtCzw%ZFHZfǰbbg8#F˜pV%sDb_w;v2[<Os` `O!mZ|)VŠL_1sFKb~5(囼MyAVxW}v1z}oov薛Q l3#QyÛ,Q3ԝA,i䴨r'oPH +$S7,%h.LA]v1@4ѷ!M]玸OK6 ebRd0&~𡨉nQ`=DVwHHET&_"3f |O@{}wt>^:CMG\i\5zJ^+Jo-6ɟ "©&0W(\L48/YT+ ^Ŭ Mzo ZAR+ hg7 fa̙6h?)h7&E*?E>`Qغp98'hq˷ LfJ3Q|~k8a9k%F83&:\ aaH7vKOrVKdjQ?? Qio_|e^_q(&Ճ/)]n= S"F&9Ɍa]S^/}i>01E¹x(D``=8% Wt.Lc'G$Z4ٝu!R HC('V^X$3TZǤ+\_ϖ UsyUB'}XiZُ8\a-ʀ#\#|s::no:l䉅He-⣩gh]3#3T29uteM*G/@3ɖ > 75:\Tѵi>t77`bJNrSOcm/3WOp| &sp?y4c7-n!O4Ⱦar%,fE:v vU@/6'mKi *>IRFEm"#ǡ@;JP}xKفH']lҶR%( W%'zꇄigϸ (TS8Vqd2~$^]'ٸ\wk\&l|ʺ&ϯ\5֭r:njƼ|CO%!p"V]f15F"Zr>)g2EQGq^:sGN5-4#n<†[e*8k8Ǫ=|C8, L;9Kï5XΞ?X' Au:ULEp(gbMpkGSl z͆ul#)B(~Y>ʒżl=Tdys h@0`s f\}L0eNP.\:\4m߃l\Ccw.o#nrEc/xLBV$ee'-3>3?C=Bg}3C>~#̦_<2|!LUqJNBEj]YWyւDu Aaꔪ4&{¨h]hbҳr3#|I lBIXn&k_I5HbhF6ا?j𨠐7Du5랷h #@_YP,BO9N.Qj˔X'TI {4x^Tyo7Q1dmDVDi*BFwTUR+xPR~lѮ㧱i.`;)(U-O|a{t0@Xi|@ jQ/#i4ktu8& |'8?Vq7,忀1Z ~sV T*P Ǩݣ[>d7$cIX`l ŧ0V>RЊ2+);8kUx}2kaqr~;`O sQS4_a0nx(\?ٮz1NY60p]b.6#=^:_liT}Zsk?0,}@}k91]]7TBf{ NŞf'lH?klt :|/e6] (C>pF 3q0֣,`G^oI@lmiM%#_4#qGR!1eG`PMq۾RNq2QXxr[ZR IX@df9nUYE=u>oU"mכ X+Ju^ qxN IzS8fIȸeNJ> stream xڍxTSۺ5"wkҥޛH !@HRIo)Qt Ҥ+"Ͻ㽑1o~m9#FN(G e ňo;1 G!e c650 G!:^HI1 Po S;E:($M̭;`uA )ew'F;" p()`0>>>"`wY_ǸLh7 kdg4bn =@hl Vj =ȿz6+ݟ_`Fj`|1B0@`o0v~h( ̇x=0h4kF_i۬tREC4PvD {#`p5G{A`&ۜPFRR@}!. y@;3x<0 8 !@4(?\@'8p:ÑΎ5Ca >2 DEt,5T/  ,&@ $@JJ#V CdjO|g.P߿~(`@gWݑ?,s0X裰Z@7tNp/jcX5(#Gk}NFp /e77 5B0( |XAܰw4]PYW A9$ #ƞ5v%aUMf3`(O_ JD 7e^4 /̢ r#:딙}7G VVb%0{UnrVG]Pp+g(y\q:dcVr|TˇR=[>_u̘x=G\a{ T6^fW g*qm߿;XHV&_#$e69WGET"==gAjt@{WjMoP=G,9fDIZRb\sp:g~Gӱ~OϬp}`8wN٦#?Z~+A핲"ę7EVFzWbb7kZ+PZ z)F]/v酥;IzQAVk4wLO{z&.gl_Jkƃ綀Ғ>o *\6ΐwtPd,Gs~ 6h[A^[i84}3[ _M(c-岺|RUxSF=}In@ׯA`Mr^zױ/~53a&ERfMcbyj0Mw[~bOC\Zn;{Uߚӳ'M{ϗ3M+ ҍwQ;[Fv Y> ԥNCZn|3pIlCF ݱQ4COֵޯm [ϳq*eiGRY6My:<#4b^xҰ!0!M6G/dBk*Hǘm6H"L~ƥQᭆ@SdoDh_?>>DMQ;Q>;tr`!-U=[^|6L&Zdnm7CyUJ怔wF(Gpf IdzY ?x16=}f7'nȖRJ4im͎?l/LzZiaa.ӔP.q;4XY(KREreNTX?pV`T88( zr`H`:CK6U<ZZo0PIjPمr0:jPLYm16 ȶ=V"=8r"R3]($F{ؗiI2p{x|)yJnv<e]V: Օ;!{ha[>_SU;,ćc>cT2~hVٟo:oo8Vnv3 6epIZfGO>%S_˓xq8f:6GOm|̯ ~%9׈]9+5,yOr'N+0CNh­X6Y<ИԐְKbr83n'6MUwhlj#A_Wf4l(ˣLM\f, /M ^ޜh)+f!ޥ4;rHqdI>'‚|&P2}˸4:V/+0]%gwM#VRnόzFƨ]c-Lm~tXyp5(*|FZN ~L,=_(F ZX +q[b`;K'9'z?qY6VZ(»bK~eLJ6Y~|x썳0cqmdoE.a%gF^/C <~Tr5?=u-l!D0z.}W;.xY0uq{gºs*='+ P)K1*!#VR gh6뮱o ֠u}{ DfczI,byyNJrp:$W?3~g fxyڟ6LHN Oc}#KYNHȭM""f$ПR*{ KniVLE=m?`_S[16Yn K*Zpi )YL9 e޷ڵ0ؑ-+0 I жnO,*8 Z.Y4/=~̺tfÌYGeoVҒ ;5xM,N޻/4Q98IXD&cw]NƾHle%ml  :{( tT :C9< EȎF93P(c+%qab&3 yݘu8jT_i<,^fLӭfqPF.U"j;/z=Y'y*Y9UT\DU4 FЬizu'e6_y;| hO{9dw\$u更ƻ4F ua 5`F:+ED|bLeBZ:z x`=eXi AGfsBUk ]`\͢ᢵXraqqޫ?Z /j)U-3F>`G>I5ZN+q| Y.Ii|3ʔ)=j㷄?uxtތ5>+@wu*sy&HHB̝?dvZ{Vtwoց{1Ø-Wȃ^m=mT Vx>3lo< Ӈ\]'%q?x%S ~K/SNICu ׫ J&hC4+&@lM^F?~ )\{l"֋mM ykTW6 p|̌muFj* =j~nٮ;)X1eVVe^QYk '!}$0CQyHKٓUk-VGPjZ`@Od6#.Zj GLG~[i5zTNN 5u\4ڴkF/Onlݸ3wz6Z}dr ] ՟^WK?&k(GC͞"՚1eVԉ"Frf/txS'bܼJh|Q𓯍XuTz`GډgV/D6__wr6_0Ģ^h#zi:^iٲ-G&o`z"ɐDi4BĪHa|rC[cKV6D`jn8 sSu)Pe(fyVRTщ;bUk)].9msKz('%8EI-f/^t'ZAuC ,8~ۣ8<(ƛ_(hKYFXg`AyPdqdIi(\e֎j8s^9ō/}J8S3N4H-rS! َbE{lg![bL+aA2Ƣ}% А|WCSuM(]6lcKo\Vݙ<ħǰTٌG30T*O[!Ŕ6{Gcju\=ҷРJtQeUPL5c~VFr5~<4K\08dC;R`ME?r]z;P+PX {PGCr}#jKֻ q܅']L-R%0;/xcFTpJmDOĨc9tc&F86'9!ypADr43#{7 }s&*H hY)זWнRs!z7lwIU:LJYj,\!}0,OO}2K`9 ˩؜5~ /;#dLuϞ湙gjSGWZ>GVU)t _Er|laY#=6@vȕŽo xOղ4]&!4kY.qus&owng> %('陈>nmQ兗 bM^ęs:t#^&"ֲ3֙" \OILތ8!kEuFl]ѩ C-Ħ(Q4¡:AŲSL b- CשƖmO N&0&i9vUY\NߕߓJfj<=}+,^шMU|Jj F4l=7kkL$mV7cb`vij齁/oEHm9/Ǿgۛx j΂" I.K3O>*<^7oiᷓj:$T~IYD ^e~:;tLXqS:6@ \$1\{`a1zz N I]w5B|RIaJq4TC-vt4HpX.Z" V}K!^P0$6B6!;ED mi klE endstream endobj 170 0 obj << /Length1 2510 /Length2 17279 /Length3 0 /Length 18759 /Filter /FlateDecode >> stream xڌP\b%CNpw.iqw\wwwww'hd}v߫jz|2s QRa65J8102DUUYl̬ '+@G ,DNo21C7Cy[ `ef/@dg)Dm@fNo+ژE;@26:N46NCAgdhhh`&@Cp9@ wCkߩ1STAQؚ::o+1x;"-PX?􀿋`adoD m mA6fS(!D01mhhhob24z3+tCG[hsrdtYΑ7[mLDm6N9ws-ml]mX?_.f7߈$SGoh rmnv@ml?+49[_.ۘYSH hr26k6#VgV #``af?2|=F/mwDqc[K 0tp0tg~$V'6bӛ -;orr8L"I0A<&3Ib0IAorƢ~cQX&zTL[j  7?CGc`l G?oDF ַ V@S9gqf?O5Ir«#'l#9FZًb`G[cwUB2GOow(.āD% Fsws FFfRcDt ~ sQdM9Cz+"ڳbnmM}T&/>=z^JpM5:'JV#0O4:øj+/'cIܲVqFh1'ژea\y %ռdB2+&Bӭ{w%ُ&dft5GGaPs5n0¤8kcuF'("zn"~B'Y6(蔧Q`< \VJ)'ꆎG*7uDߔl.V$ivk/ UFWiPR !h T;mx`<<*E\fwx?5<+8Kf4U1&p调^mg,)TyAkrPYpuխ*ƪ/vڠ'QcDY)az]b'|0[4VP>*r㇯kR#t 530v-4oĿ;r ڭDHlf>G3K%ͳ;#j&d줳8M $,Fԥ _'5m.B› SCyޓg]|-R>(>0ņU.Vt~$0,5c3~CީW7H5k }|:dT l>OmuJ.%I|q-Gi:7FBOY0!=>+F/1n+ +StSNJ4 Mh#F_UP!Gg~֭ychWȧ}6_Liരnޯc?LWcw/ L͎):@ĊDTQ5Ȯ/**T*n׋iZɉ]o;r_"Ȗt=/݉GKTAIC~8򔬅Կx~K5"1`SETrī>,ѽSVN-ҫx!>٤9"[9V[HAK "/YmL5;(~K  BoYGD8iEC괞Xu|,u!,,@&j=g $2 Ay=,H*ߕXI3/ K\ǖ* ˯RycȘl%"9āuAjؓ~ dCDPlA FIH~6䠶zכev{} @UDdLUҼ{[9]VtN€3GQ4.˓&π%4AGQheAe4jfO uP߄SP|c_E6sfȫfŗEnx${1'5C&q^$e^=:B'#6F h ǵY)gNmsrdKqmޓ7@Pgv+xIœdŦs ;ϲ-}8ަVBJW)2. F!7SI#'FWɶ]p"r0#{G\`D,/llW1͸(`^la: = ejpNUviw-0*gjHrʓyR%a_URSR(A]f!7>#"V9à>0N( Asn1L&_nbI[Cϝ7Cj2{sc6z?iX쀶8;ثUubzlUͼkބuE*#̌_Y~ \ь " IG:$wm$991kuEr2٧-R6Ƭ!nĚY,$ ;ﱜȈz7)T::)q@oȺ9M1"C l@߱bˍs^~8[ C!4Znr*ʡɻk0-@>°N;Qut$+T-S-̌2bM`yỷJ# );K> x/{}7uHⴭh oQ$*<a~O9{SH'iWC9@ߛ /pd'TQp֔GIϋ=M@!k8I)xضUΤp]I;3D: CyT&8wf#MԤS5Jl댷qSfN[mv#u47Nf:ԵW"YD%phUN\ pK9n?ެb2]nY9pޛ*~*nGWez3n$g;8)~v}ٮ:mL<|O#YcK%#X?Exqmr$/M%DELY"`y[~IݵɃOpjwi쉋}i`-fZࢮ]|LmcO ~j w?])D L1ݖuk Go\qFUzmfT}0S,:|v(BMddb įݔ^Կv'L{swG5ƒ c.q+W˱3x)`\hIƠoY(Hz4i>)6O9"9nJ3E*n᧡ l< /,XTW=ٲ[_F3B \&,R1c5Yj?PM+Gᛤ>!م~m8OgHgUJIwqPW\ 6N/1nƔ֚]0}20*xi&zѯ[KFVW}pҙ1%NC|'6/i iB1@Xԭ''fUq"!,,wqv'/ruquH?~*@9eQRɁQ qؒ3Uk4WA:hpMY[|/;M*Wn utߏUuX!J ]p"c =vwŠhO^RV5L,0L3,h{9,Чscf^@\/?6*+ChqwZ=+(#tb vgBHu|N^~H/kMr/rZ-J.iGe4d NN9lsRJMʼn,X;rAE|)LR֧MZWv.vփVh,}S9i#Qrʺ7j7џ!{lOiT[t@/L{o #x g869<*.@VgO_WxcmnwC:D/3w`5ؼc<$6o5`UFd6 3h|^1( t:Q{/?`f*7s fhx~<ӄd/ћ50+̡WLs2j=G7..\#a@d|99EνGTēsuu1ED-f7cޒi#4VJHyڔE ij≯a{C4U?/"8άWBv)a+pħbbEh#Aೖlvt7c(#%b9(8T(n@TqDVmsD[g)ZX{?M;nK=| MvKr Y`2(%SUc!nc f^V0ϐ:;NVB̸ >=0WqGEfx>c 6!KC9L $L}]o0mA+*\а1 TrN6Rl/wI37ж]IPYe4+{DG`ih<>}VYҶ.87z dR梁at!++iIOhh: RF=Dhx"mP-=} "9tnDÌ}rweg{Fq򲸉Fһ6jMflߺ#b; QKQvUl?T͆#u׵-*r_8*]QC(`y]"FH fKΊ?oa}BjPkxW=1<;+&5r iیDƨm 0ʿ_3_VUfRAn[+|e~ą3P \Y!Oְ[Jo]8+:ct9FEN, O?KR]Vֱ<.rx;5$$L,dt5pzfr׶!3Po>+|kԠ%H S8T;b=7nXjQ 昈c ZbNndjKZ SC- gƹMX7B{V!&rABߡG ?pu02uImE@I?$StCiI&elR[S4NJQ^e41k߃_B~ 5['mRtd1C1ô9q>OB& 1gz |V %c9]Ss.Mͺ5e9m9XZ[FW11 !]mϴ[(U#\7x֩I%$TPln}|:ӟPYG6kzɎ-üz<ck"N|dG+pl߼{N*?O}zh|cep|D(ۘ՜aVHCIw|x|b8(zMVth'J9Z/ԦW2Stj$KT])ߵg,wA^(!$~6Ε}Hl80 YXoVta*/0IDШ9I -k b3j @Jhqn~QWE(, (8E#CysKYv30fʒ rHpgpߋvj4f\2lHndeW\,>B۰q `eOknW+>2<ȁ!^3(fά 9|Lr4gQAVG®~&Z~BM曯ZtxD` /‰l#ڔaA'2D'`H;Ę ZE9zfVP3,@qz >yXn%ܥ.D65!'\rkBڬƴ1E1'ЊϡfвlPe-Ft=]Ys!m{}n[ݞ#B8]锆=Þ UT9xʆ)b  ,)ǹ%bUŚ^694/M'l&$83)4jEBy,5q 3?kAlު)}|jw +C. ~hۏkA{r6Gw%Հy]Kfo= jxdbU1יF͌mUnx4hz v@&B$Uc-\`w6UZ.4&c:hLvEͪ|Xq{rc肃[qtT9M܏wļjd_MYqQLP=pǶ+aӎ2`iKxAŎ ?rꂸ=@Pu 5s+kN~7̢3I3IUr%Qf+<9;.65{çtpFDHobJԞtϵ/x*<…NM {ø0&upG^ސZ+\Wbm$?, {e`ohv3p^e㗼D3q.ŢN+0E4~30 oG8h83(ܢQ!5דL{:O@eb̷%H.UiH &CՌH%-W CN)'nFG*JhG—T\-aG׽r\ ȉ_ϺIϮCߗm9B<9~FO0h&`4fѷۉ=_Pw^7ͮMS$;9 +X# ;K!l:NmQYMpp2ѹførw1NkR~g0lwSypv@]sEڨ'*K$Zpb_ sFpksVzTat6߁x*Bxcoiq6ǻR7)"JzvZ=M?D/ܱ y:0˖+F[W!UJT^ Xv/f3u> <ٛZ9p~f z/*/5a !=K Sğ%N/ E?$cGxt*yNbyk!1)NE:Wb́]Vfc:X'4C(٬,|e񜞴XS!]˸ل9TɰkEDb\<{|c8kB+<ͳ7QO} 4 ]ܖۯB pnmK|~*[_V2V7.O~1E9{iEj~u3bѻIǝ~ 7CWhPw1+*]mGP`f~”EgTS`Y~mDkQjš Y~(|wNO,1ܾgcǤV jmSx-?9 F,@83iD11-ԍe';UnɆ:T!ۣ/R5QZY345q6ޭ~g##$Xĭ2D`5z}ʕ%;Ö@/<[3;Tr~̯,TanutzlUFUhęO9s.f r-S-Vv*Y@&9"¢P(L4m)\2Q5%K90iZiǰ0*09g5&pw60K82_w-t$(p!ÜN9%* E{F&϶e9ķP4z&b0kگlH0{:eBtcIvrZ !f͵8qPciIRF0FnPg?W-2\[$~6k[-#F<1H2m^cb%WP)!\4>5xFSu%4W}uB9mP}bP{`WkW8=Nۤ 4-&Xg9Vi!yXE>Y~:^ђG%j S 2J{U;aUMQvI量9DRhىV譻"[Ɣ9qezTG#cwTFS+IzzdX6+)GXWu=B) {$[|3Btd#J& I˲|3W#2Γצ` P( &H*:mqx)%F0/R.]tQ: : ÆI;VԳCx}2J͂c~Ͷ"l֍.V-[5g*Mm~^.CeA,u)@mb;tʟX1v%ǜ˸Y&|~K{ -w ϥo8ϻxxm톜|Iܶ+c+1雷t=:HiWj "du nI߇ }?op?l,'X60:5tii֏dpZZ*{5b%" K”w类,duiɒӋU~?kL'(w)' ^(Cڡg>eR-P٘hI$J~7lq6P%U;%ЪB+M!ʬg"F9n {))s%J](֪>H"jEspmg G:rз>e xVd]L֖&dA-Ƌi}>d[!FAA}c.n>e~krR6S W9~ k"78&[  ;lж N@D+dYDRȤȬ1Mq6>r\`}׿f;P'AߛHᘃZVYF!舧o}&y(;9\iϗrJ {/tQRZ;-a,ΘKGWT79f f4j"]ccvha$,v7,!KVtTPjh :`S8Y(1,5ޑ h5g2fgdL5JY,>n.K C]H(wtYfO_(2AA'}l"&3Hp$(UWD$1kzA< g0t*c4ehіT ^Dۚd= wvOݑQ:N2{sX\hKIM$XK,$VM~9^BQ¡|9$P/>(Ja:~",sEKaʤRdHl7KW'9z0¡V~/~e}F5gtOc9y( X9DzY:UCRXRH7/BlclDZavz^+gךڒM뒭?aC찃O "x!W$2Wk1z8fL/U jk!?9-N+F(eNN7+_.,n۪)')){<S߈y lo5gBo~":Sw2H4,4i}V;D?7T]vNMVb%M!c9\{2+:${ouQZKhz&iՁcu{\f@iSkEW8*MA~lW NݽE>M`… 7Xɑ`7)2fzp:fq崸w ^=IkIq5‰XQ(lLz[bzIEIyY`80teGmo&d[V[E<A _2RzORYǭ;>P5'SfQ@gl~t/ A%*vCeJo {9ɱk~:q$~jeH#f_B74_[ٞ(fN`wO pESntO9X-ƩZp~Ό`Z6PB1<7vg3\}u$'(2x0#F 0;7r+?[6hy0WQU~ "t[θZl /bj,;k9u208oDaX'KG/s 0N~0D[yҎyп3if‡7Q3)5ؚTBg}kn/9 AQ06|I*uƑ3|$ؕZ9 1a%-{( (mXI!Zlq||~k&]99AV0nM|X?A͋kJ`Q;^| f*!eIi2 9Xւ}q\tJ}B(w$W>Q]P#*.GO/6\ۓiA}4@H=BDtԻ*WHXg6b0 e;ϨhΥG[Tͪjyϓl-εhAB{ŷ_?tO,s+#~KA=82Or:V)~wD@^a0(l g0sB[u{.,V[{ (eҐuƳHI+yz7fP'{*ȥ& DD]l( iLFd\ w$xkgȟs_UA qe칷SSOgㅇcN1 <»Gk/QP"U>-ИFAbB3A#ebA,ryZ&2Zi߮sb{9y 6i뷉 $*A-6w^nѦ ͰAR}'Xۥ}nGDO.bJ[oRdTQv{U *Mȁf:זVe% FԞ_C4"X8TeFQ=R*DN5Xhpog33tk LvTMz~ic כG\bCS*: 34)^c-θ~5)> , ΰNFlW HΘ']s>}-0- F^A2MAhz|7E_.y[tP(YOmzI7^r5՞tOyϔu4XPs_2q(WMS#Hڑ# ZxDZ&g2Q(u@CyDX9E'+-+X4dzDĢElmT D `7,bE-v:6d:eo$iS DQ:ST`(Y?a+]撌,[RbJAd"L+R 0m_bg%M<-/%s ΣZsM6lN+iXK Gґd㕿7;:bҙs`QU/jdoKl_?C>yA+gp&9ksgLu2tx􋑏",mRgvd{H܅078(`JG $r裏\zМ fy8i6O)( tTc;;nenyygzTB52w IYπtԊ/$g/6#@dFcf?>]~qFm]^<j1uAK[8Z(hmeT'rEc/gq Q2jf҈5yfsOn˂{as-vJ y팜樫@ޖjN"gA{=}v)Bm^?OԩmcC` gHl1c e'KX.ʂmx.䦨ޅzcEWR Ae@Y`7+S46!pNKUw~)[Gc(Z1'-wY5dI Laʯ*R/Jۭ\* ,|ewqn'`7ވ2픋qi|T'L55VxTO\ødݬo_% ,|^uu tw'\(02ߗ=gVfg~ @Mƍw6 9"ԶW9W^V5WKBf:E=G' j0n3B^ETJ RsO:~4d M,{\b+\j}CʽH#V7jp}) r͝Ia0S<^8V!_#qswP+1EmJ58M-_5j;:SO} TWcϱBkogT6Tn9Qo~F%j{ @\U6nTu}۱+޽(cu_3޻O-܎菻~ M\B:)ޝ6f=fA"ɣrRQ74\ǛTtyDc`Эm$ok֦賕M0ا]-Ij:m&LWQԢv4o w혴Ononlw6h(^$r3BVK$=1y_y`NIPJw}f>:[`'&..8i C߇WD"jn,eUn9.(%~Dƌn>( )>Y͓ 5 "7źdu$yo oNOjL)ǻU!&R:e5O`V=B]Sf$?*Qn(Uʧ&SLz\kB%URGu%YՎ]>6pl9ZE>;0` 7gYA<&j>v5&1!qr'U0GsM5%Y7>6Toh#޹ ݴ5)Vbhu x$!B 12h[G!ct8vK_Vܩ#Dh:ys-Hnl`ZKp#TvKHyABZMuT KҙU /!D~eeyV=Mvee۸%>)i^htj⊯ LHGT%zThU;LBj+s~zϿ]2D', K|w6 R5+ߒncf͖ALzPK †Hltpjwoˢr̪ a\R$;ڲEJA'U!"dLAs\~UABI*R;lA(m)˛J{Ɗ zqV޾3t{ lƟAN*!iD}a xEW!dpI5[Jeպ?cǯDvG/PWf-tmw:5yɍҼ$EN'/exAbރ;wv?ʍ endstream endobj 172 0 obj << /Length1 1675 /Length2 4226 /Length3 0 /Length 5258 /Filter /FlateDecode >> stream xڍw 4{?"!\KegDXf][D5%]d"ew{s9gy< ?9Hq@jaT "ZX@DB @ܐi/#qx-BH2 dhAznTJCe!@4`^(`(aH<-:C99Hy~;ġ04`#8#I07G! !L ``ooo1;^ sR( x΀y!aˀ51Z~08a8$@H4F q);`kc? D_@Š}>rw, B;(7$`e F!04ǐa^0́dpT: R5`PX^ r;|t̚h:&i@pҹ]+96X%eC9!  >L`E)bRX p$ D9"I?x <FP(@  $F:Ĥǡ|5o+_Vbn jj@$ -'; oO]#Y,.~E߱0$"4HA/G.?F_iߊ<܎B? =˂[Oi 1I@ sp iTNn$ A"LP7~- F`tႻD#4;ΨcC&.% p8/-$q))JFX !\Rw#G{xr2v(0tXq B0Ǡv:0n )o%%BDc{ yw AiL8Iznd{p XgPR^ XPTM=q8::5v郄Ӿ"\"꿖rz{%ond`!֨j­)g!viϾrt cV7_ol@#ήUuxo ^<'eM撶@us5j=qde%NSD4f6ïSki7K\j}(9j:sy"͡o ϵh}K ۬r6I(pX'~,4U>+ia"!t5q^q@TQ NTPfL_SQv%+x֍_zG,.JBCY\W/Ġ)*&vz}o4`/]^5qַy^M^hgJ*qm라w`ᒕL7}شzί <4p<©JoB2jW1! 1HFSzd76q]' KqxQ:_}<(33j9PfmSb++ 'xO&=|BvG"Ek4Lv d+DF7,9̹)}a>U*`9ȲkLIMqNjRrfJmK_|i%iϳm_r+8[~,a"8rG^.姝i<ʠsbJLL[ԣiOurX{,9bZwM3"krYŗ FTdMgT4*D,'VFWvoޅH 'h(9ݵgÐn0;S.u>Yr.;>ʊz#֨JU*[PߊH͉^!K+ ;bMEB^%W0ܻ"BԢoa9m2f|vXE_ڽMC#B kcdttTZ,L0--ۻPzJ(]^/;O_ܨ=/ƽoWUPm{?ߩL"^PT[~tbZj5g ۼ;O4^BaJSqF=+.Eb#cXSVOHd%\` n,f;=&i.H3U#Vf2Lӕso_*:Mֱo 6=˥j X>qj_[7ZbD鶡{jBĤW]'guȓ[J75n#yCr>TqW|͉CDzu>;+s$6ǃBJ⏜!znAfq́ l^ŕԫa& SgSJ$eD9n pP^?#FE|W:p])ߝ,W:o#ؓjLN'pq2\$Wdgөv0 iT{9Q@lY51?cV8[{-Yvߔ2\9%Ҕ0+G,E' ֆk^w^^!'iZwՎ) 0j+zSPD5ȴ2(eYv~^vSQQs:z#Ղ߫[{.Ⱥ|#&U#藛N  ֮TaI~?zxq, :<`ggnΘipT=+qL{eTQ؝O|}M8VQ (+ Ҥ,OJlgPl K*No`kkEM nX>͗͸xHs=RnG+Yr{>]1$[M6vڣ}݄>B'Mm$/r:CJ;#ti E۰Kp]U_f~dҷ^gE2=]䌰7^qٴBwC߫](l(RVDD:$\ek ŗu,m?ߓe!gbӛ+6ދV!I` fv|ȇМS /?XˬOU+aL)PiB#wݨO|# ¸Md,gGD/YUw˞4&_nNkz\4VzBRbf~Bp Y.;jL۩[ JjfWTZ.VLuZXf!=m:S6s2l*A&I ):}c-TDNBRуs%\@׹xʴ.N5o÷sBcxs`uZMt7.)r汛¦ݾa#pGq/Xͱ.$+RrG1,Hd1kV?>`=<>NpM0],N<5m#4SLvÍ|eJ֨YGCuiO:: ɿ)J.12N|MY*GO?]"9lEhϫ6 ^sg *]UV=|$eZO\NVqT6X%HP Sq*WaIȜ%> & 9_gI [MwH2(cU7l[R*yٵ+dͿrEU6 (Ώ,}Ə%B ڼK"Ä^X듚۝I"3SPc\0hh;o,W^:PiUE;'f n[km8IpO;r.pq&{~ S̼jhk05xK*?dQܾܖ]Pfn]~I3`.a`>Ͻ/ɦDj=dg7 siށVC]e;,Lbg4`Jf U\ $iP Q0BJrͣdl顠njf/ueF)3,:SՇiVCͬJAA\*2wdHUDP;͝"h֗1n@_ʐm)~MS4K$6h~#zn[Wϡ.8BF?n8Qc-AnNMxL`ZP#/4P(DK zmNAO_c#_0S<# 겗l/QL~.cOj'oH%CHF~!EA'y"@%fP}7( 0`#r^eP+PfwBi {3ԹPeE|ҭ?> stream xmysgn6~i۶mL۶m۶mO۶m[ӸssukuLBeچK4De&r<&'&]c3<.{0]0gBYRNJd?m"NH?E8,'fM?J%3iya7[֠J5& %m, kjPBC)d-]?w_-aw1aZ-qG fϝ"qظRSc6uf)A $Ya{pmsa3𕱲(^|m\C~-rݛ&e D.Om@ҼO[>2WGHQp%/=F$P_Yc GIf}xl}Q;=\[!z?ߒUrCwZ뗻St14<~;*S,> \'H:;ND1OL1ЎRvJOon69IP>h!M0@śd #SPb|1)b#0qP?݌JZz{&2ʐ"-rjg#&tQAf!Kh@ȚjbOpgNh;?°y~Z{vJ1nIȡzrKte]'@_Am~0{Ƅ̪++; ೎ْFh g߻g\ue4q0-l2H^QjSڊ@d0Z4@ce$Nh'T #OvqG*hFD%OU)f?u%U,@="l_=UtUP-tU)[땴=.3wB@:t:ufgpBpC.5 Y”D!ݓ Vǃy7iaԥZd~S}rBȼDh@|J)vn$Z˙h![,NvQ~&!dQqWNr1nBf?eDAqB EFj|^mٶvԥy^јA\R"CprgDfg&ZuqYqn 5r&n2rCͫVyL)U:{}|Kô8?^ħ DQ*(61dzo!q_=tXR'|bL -;dQhd0SʪiZO6.F]>sc&wBCciZI<iLu E|I IFy QidCKy25?צ:7|KR 5Mx{~HFɶs༧S9zz^y⣽Ժҽ8ela6ʃZnJl'/ 1'@IKǩ@LL-IWx6!uOp"ǝF䊯A3ʓt%9Y3 N.Ѳ+?;F<%1};XAGZa2' O"s1v~oN9Mѯ+S &.^|AkBn "K'%1d{a3(s͑cqhb,'Xl@2k?Xz %ΰ\c|4ś'/4ZDL40*DFyifoÒƌ9~%<`1)|mˀa֒N~JhѽHY#B -xǔXeT@\3^ {K/ X{b(yK8Iۗ5k@"6U gJVS]fA%(R$cR$!ѐULB#i۝|-6 [xL}y;uzK8Rlt{T_5ethv557K.Rɳ@P6#'QpJxhJ G_LjEZQv ;gsGF'U߸*UTƁPcHy1>zNH(׬oڵzn)u&QG@ T>\`[as{|BJq/bBK'ƥ*`_j/blvY2%}35K[joR% l CmNe2&^PkEQc!#5%(S?# #@$EV;gE`\lYԥCmhR~`K!ĄE b){Y4(k^C\ѡ b&t߱m"i )Zz ^k옐W+㪥iHE7& o~Gg?)D) %ȊgF ɘ zlTц&8Fj1qfVzhoiPgdQ3TI z1H3kȏs*o),PxBbwv_8`@!1mR4g)U6w VfIvnՓOxFeab^zޚv".&}#_Q|0<2*1!z-k#nG睨`b6CAqz9qfiJb5>R`_$clS#%F%u.S~v_GS,=4GA=a8/Yɵ#e9a.OXNpJ|Y9hnV=&2=ejcE?*KE9l7ȟ qJ:#ĦDrmF6%wwDkY5&oZb ]3PYmXztQT oZPn [cE0;pY`0[d ?aXۮ"jtI'TL@I nFP(˹^Y=^CªӶivGE >ur5;M#)ʽ=SZ3[<RhyctoXl X-!-ᥢzіcIB; 8cNnNk>rT +SfX@{6ͭTtC,OhP=$=߷_OI̩A\*=YurHFV`84{lI{)^:`ږ,:ʳ>x=\v bMpPGJtjLK`I='[vU>2W:5Lip^1d/UJN Ab_H\hCm> i^)K!O5 TkFKjr'i?}&}}2-*ztQNq V$<6+{gnA>꼫XY2藜qa* L[bj@b$O_diEZl7lꔎ?|;e _]ggJ*_po񒝯1^esVh:FQzzR8z) *waCm/ʗpBګ)'qt2>y-HJ%uEHX|픫Om%5ܗ| 5sdkMz wĥr`2l҈,jK @.Ïu4Og'Y*jnmvɫӶJp  @ϼիU0Uvof] dlFrEm&_1Yٔ`Wyks\(:TItt|O7}tu@ \p=+w2ݯ xp]pDAxs?x7]tr>"FfF՞\$ 'M4jѕz&G4KZ ҖqY;Eb1W٪{fqKVn7`O?KӢUwjFft _e[f?Doxxv1b.0{oICmpt@D஧Bc U ~8FT-Ϣ DJ{w0g]~JՊjR,O JH Ė]&:6'y=χ,AcHjA_b1hFiFLO6 /p^E1ZLrڐwR&obt„ʮի_)Ha۸hT=5X6٩{h90DH vgrI()^AOXǔSWRUt:vHK^u߻DGiL\6Gy3x'K7N4֝ pf2l* `x0k^J8)E >U#:C+6NXƞoZAGTD{qH LPnjȄw9Ÿ57$|h |%S/c}ΞW[%OǝX6#.-w{FRdHISۆ ~׭Qx&RD+p[Sd3_ˣٽ\(~Z5Uq"\ ԙuM_u?r[$IH뤮Z K1+M|;n!` ~Y\K󹐷}SXq㵞ݷʍtF}ȋāmOsnO7 T[inGq,yk}} %jG2b>.yoA}X nE7ۨP0UN%f%3+O8|N RgM?D "<"F7 lH~`P"is7ʭ:IZ3{>C eȇ{Ch/fs]]QDtX_D)`Z?Rb`j}EYNVdVf[f4Uy㼓\nVDx5zb\F HETY7%B-=63Ǚ[ay<ڿE&b1`U10᥺+J@*CeUᏢPK:"|\)tj'ӑ º ~rҵ5o~ xw2{Z_q aj%υ)uAf+rDNjx7ӫQD9bWB=\4i}PnOf];-^P/?[y5 B,53fq3!d|64D˲-`QOiJ2|WsI'N)mq΁:|#2f>&g6ʦv(ߢ\[h! 2_i5[yy Ne뫽y/b8P #m,+ g!uhw&azz~rej_mODK.V OxL_qāBozCqpOT' E r&kB<$# ƣQ+;#QLV_cx6Aq^ @RR ߱lUWD/uctsnPt#kkzTgX) ryZȠI{OU@,C k]c+UJD>B9edEG$ puTmB;vđ8OGZ/vG¡{KԖ렶GRA-6i-xՈY{oqre3H5zbOT~ ] !Gf=€ИBG*eօٴ+ OGl{ly󎇝2BP/)W 0iEb5-Ŏ:yϝe2O25/54KWp9M&!7 jP~p ow6~T Au6y#17x:SU^P HOO/EO4/i[_2flHB_b $2P{S^(p[3FG8nkz~<Ϋ^pnbru1HE;`60&zO 3#`I83]ͫѻXa1]=OFerF.zb{dNierWzX2$ Tjwgτ3"M w̃KSޠ!=ST[MʐދY+Ӑ կ[ MQs!>ѯ,kÉ .fWh28av-Ԡ.[QO* ;oCW7iF eOy .G15jG1fbڸ5"KW^{{N-) zj! !o}7ƥƽ;$>`7ۅ5/Gy.ʡbtwX(^dAÌp1,:d/2MQk(bXOCtȥs2XLDT.V1sM~(9'`mb u3aU߹$asZF_lqXtTkx0ӱrr&.hkh1ؓ@-+r-#%y_UN| \mZNrr6nA-֧Pa_ɧՄ;gkVY<*zZ~Mk G6 #Nz㲅'2gU~Rf?`W34Ak,$f0v8=kpv *rH?֑(XaV>(254fQuZn=Wu]"*1_]mͱ:dR: G_MЅ/X{ _u-niC"Ѱ}[Ҥ>€iy裆Ke: ZaN:Q*Qy_+OufNrI,[@<v;יNudC #zo#Pq|D6[giޖ]':&c9 2o0B01at3&O3(t*Y~ @z9:R'}83ݩYj\f]3ƁS%r RŻO!F9 FׯF+hՏOHlr"!JSO$>p~(?)ȽbXIy^ DdS[AI;0Y{ yY06פ>Cd􀅴lJluE(m&qO,)ҷ}`bՂ_K[b!4m?cיb}yS2hBEȧlv1eE0+7xI?-V[8^1³:(RjjW9rjdBMAUw!]ylBzQ_IlYtP~.%6/UQ?yw!BlCˁEqQRIDCQ+DBFu*Ontߛf Kٵ#2tC穚m7qJCdse2CZE^L, rtm,k'&&pē"4 }"1Ӫ?u"G >  ʓ.w3rQH.s۽x$M 6M'!UYC湃I`dm.fC ЛSd8#ϥيNd;PD_ s E"Ḅ\P+gxq'V.&B\V^GL{D̈́$H;fJ JZo.vgFubeY5cF7sBp Sx4]2I"If9 Pɻ ppX:K ]?Y/OfE P8eC/G: ԩ;*!zKF5EZLğlErl.ߚ(5 q"% {:҅R@uqN3T[f۵$Ө!ۅ{ľSʃ9DQqs'~WƀH&j}.Ie9JU+<E/Xh]y-b1-:,HeG A7("},Dw$3s(L~ ?ЊkP~Uf)6~MPiH #mL<,sRQS`msTTUAM=Bhb⌰EBN=EQXtH6m*LZ`Hɯ;p& i/`UF,,itiV* y*մMCdxy`"iwze[٠ŵR r$~^di|, ܥ`~k2]f)O48#ֿ*OhgShMAiQP% Kn9d\ V:ZvJT͞G;亅IV {. {J)gP_&sFT1%%xa+bM 7# VBdUWyO3 %hO[Mu\ (dMˊy׏9Aٯ6 Jn7ZvV.,1(>Q~w1-]h!Ok:cQwIvҧ4@&a3YS-&9o]Xgrf;{"YnvAxQw˹ԍȨ=xXG|L.r\<ם?Da^VeɟfA{Q)˻ki<vz<2|V:'++UǢyj3*ئ3]f9qtK>w U.@&G9&'C~Æg)\3DGBj/e9]a1X~PxwPkJ)[Ҕ[h.o"$[l^~VBvgY0kX);Ga zN19Z8JQjTw^ZTڥ`#YͰ5<8H0]1, sg}8 Gq|4rzbc Aף3l-g81t- SIq]^.ΈXϣCGKKhҭG͹5\vo C`vt s-+5^$3 LˑQq:*1klyZf5!LwbgUڀ:$V-tyT"Іf%w)9{LxDAbƣjʷ֐7z=LI/ex7Gf de@YP)*O ( _Z},i3+خDlM`҆(Bݪ;.Acu&ryd_ƊEuPJ{ '&S۩xG(sй.>wm$ߣm吗W? (v>IP5I-Klb%d)r)RhJE7Hʑ}X?<~ ,k8TTgpOk/Vnt=HMs-09|7} s^$n58T$QL.?v_ d2MΪ DІTM9J';"ޯMW#hu/pU߷6QU%R6ǁ^8h]:ѸpiDQ:VNr_XH(V?",0}݆pK$wQ6>:iW`'TuNl+ݻh((b'Ӽ a\E\0O(HXܐ E.Gڢ,>:ˊ) CMԤcrJɅ+XSOycҐ5/D8 d{끧2kvHɤwaaoldݹj7Hv+LӋoshS%d<dbUd5ݷ#\ G#F0zC\z^u,jYe(qQJqѽ@mEM(ߎvR17lԲ;K; pu]gR ڲ,?Ղmc!#9\QnIY rĒ~, zK4'g,p+k d^w4A ljpьw<i[ endstream endobj 187 0 obj << /Producer (pdfTeX-1.40.20) /Author()/Title()/Subject()/Creator(LaTeX with hyperref)/Keywords() /CreationDate (D:20210706142900+02'00') /ModDate (D:20210706142900+02'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.20 (TeX Live 2019/Debian) kpathsea version 6.3.1) >> endobj 144 0 obj << /Type /ObjStm /N 62 /First 524 /Length 2838 /Filter /FlateDecode >> stream xZYo9~ׯcqNƙ Զ{#KIoUբZX10؇GER{&ԁȂg`'bJFxj1-tTiB`k\,t[ ,SVaE Ҭ12m?5T"3"t J  VPgV3" %~^3 #P1P `^*JB<ӽjP,- ,maDA1a`(T@ܠCA&r0` ;L(EG]yEfLyhc!}dQ{C# ɢ!bF 8GHe:2P L5#;w$A31~Ю^fίnuAu{uJv!Ekp?uU]aG*m7Q?fb&;W!h@~:ϊ}b[ň' (Ԯ 1LeR逥*~w-,d5JDTBn $~ћxjaQhY[)HJ81VT΃H\ JG/uFùiԋ5X#a4& O׼RX.Ʊ1M|#!D\Cܑt"p~*Xw(o&}_Ta?] ;*n?'7[#~S~_ ?w7ᗓ^KQ j^ 'p<|0$zC~ůʯ&_O^UL ~wSxˇ|XL|PޤX%wŤp6?g\O 7^fRkg|6i<)Wƿx/a~\Tt~'/{~&: |g6f>fɇl|(P]CBc]xȟn||_| |G%gב[t!zqɑK.{hjitىaozre5 x'Ϟ% TA<=6z%Ϝp "9 ;{l>t~W8{_{R `엓z3,u懜/+$G2ifA|88x:}ÖSíM&ڇn{YsZ,[GAnv8Bk\~Ag;  Ŧ7F i-ZΟꪀSIǜU@eEM{ӂ쬹,Fo+Q~\N4W7mT7S׺aw[|l`īx+v>8ă[Ͳ2ov>Y]~+琶C/o{y[j Vcom-^ⳙx~-3"C?.~5X ?3Og&I+) 'g*QMz$"=Lz.Eb/Ezm.:6[3IeZR" j E3s3u0r¦zeNG5MվjMMYF>b 2>QqGm|LGc>jQa Z Zr>flpp9j βYn9YAy9r r=)`<:Ϫޤc˪>)ngwE_a{UK8^Lg_G-zp]JIA mYZܶfrG-|#{ڢވhSVyt +|PȐTj&[Q1 (Dd,#Ȭ{E챨n?jkWdq h:"vN~UGAj,o[}ku9݊zK\+8-I!)c[LtFQCpjƩ>nЮqfMl;޶s7+'=;i3]}2K4 v7AOŦ|fd?K endstream endobj 188 0 obj << /Type /XRef /Index [0 189] /Size 189 /W [1 3 1] /Root 186 0 R /Info 187 0 R /ID [ ] /Length 466 /Filter /FlateDecode >> stream x%[SazFfR(mS BlB1co;3Ɓ#ffuk]ϻy(B2H)fgqqQ u4"q^hefc}+FIVJp2;EjVXgvP.8 8 `8 )Mӻxuޅ2|g`JQ4 ~s0(xaa^ѷ{U./y.2ݮUE-xvmgwWl 6!<)F8ߜN0hs*z'3T endstream endobj startxref 141966 %%EOF tinytest/inst/doc/tinytest_examples.Rnw0000644000176200001440000004042213722246232020135 0ustar liggesusers%\VignetteIndexEntry{tinytest by example} \documentclass[11pt]{article} \usepackage{enumitem} \usepackage{xcolor} % for color definitions \usepackage{sectsty} % to modify heading colors \usepackage{fancyhdr} \setlist{nosep} % simpler, but issue with your margin notes \usepackage[left=1cm,right=3cm, bottom=2cm, top=1cm]{geometry} \usepackage{hyperref} \definecolor{bluetext}{RGB}{0,101,165} \definecolor{graytext}{RGB}{80,80,80} \hypersetup{ pdfborder={0 0 0} , colorlinks=true , urlcolor=blue , linkcolor=bluetext , linktoc=all , citecolor=blue } \sectionfont{\color{bluetext}} \subsectionfont{\color{bluetext}} \subsubsectionfont{\color{bluetext}} % no serif=better reading from screen. \renewcommand{\familydefault}{\sfdefault} % header and footers \pagestyle{fancy} \fancyhf{} % empty header and footer \renewcommand{\headrulewidth}{0pt} % remove line on top \rfoot{\color{bluetext} tinytest \Sexpr{packageVersion("tinytest")}} \lfoot{\color{black}\thepage} % side-effect of \color{}: lowers the printed text a little(?) \usepackage{fancyvrb} % custom commands make life easier. \newcommand{\code}[1]{\texttt{#1}} \newcommand{\pkg}[1]{\textbf{#1}} \let\oldmarginpar\marginpar \renewcommand{\marginpar}[1]{\oldmarginpar{\color{bluetext}\raggedleft\scriptsize #1}} % skip line at start of new paragraph \setlength{\parindent}{0pt} \setlength{\parskip}{1ex} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \title{Tinytest by example} \author{Mark van der Loo} \date{\today{} | Package version \Sexpr{packageVersion("tinytest")}} \begin{document} \DefineVerbatimEnvironment{Sinput}{Verbatim}{fontshape=n,formatcom=\color{graytext}} \DefineVerbatimEnvironment{Soutput}{Verbatim}{fontshape=sl,formatcom=\color{graytext}} \newlength{\fancyvrbtopsep} \newlength{\fancyvrbpartopsep} \makeatletter \FV@AddToHook{\FV@ListParameterHook}{\topsep=\fancyvrbtopsep\partopsep=\fancyvrbpartopsep} \makeatother \setlength{\fancyvrbtopsep}{0pt} \setlength{\fancyvrbpartopsep}{0pt} \maketitle{} \thispagestyle{empty} \tableofcontents{} <>= options(prompt=" ", continue = " ", width=75, tt.pr.color=FALSE) library(tinytest) @ \subsection*{Introduction} This document provides a number of real-life examples on how \pkg{tinytest} is used by other packages. The examples aim to illustrate the purpose of testing functions and serve as a complement to the technical documentation and the `using \code{tinytest}' vignette. There is a section for each function. Each section starts with a short example that demonstrates the core purpose of the function. Next, one or more examples from packages that are published on CRAN are shown and explained. Sometimes a few lines of code were modified or deleted for brevity. This is indicated with comment between square brackets, e.g. <>= ## [this is an extra comment, only for this vignette] @ This document is probably not interesting to read front-to-back. It is more aimed to browse once in a while to get an idea on how \pkg{tinytest} can be used in practice. Package authors are invited to contribute new use cases so new users can learn from them. Please contact the author of this package either by email or via the \href{http://github.com/markvanderloo/tinytest}{github repository}. \newpage{} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{\code{expect\_equal}} R objects are described by the data they contain and the attributes attached to them. For example, in the vector \code{c(x=1,y=2)}, the data consist of the numbers \code{1} and \code{2} (in that order) and there is a single attribute called \code{names}, consisting of the two strings \code{"x"} and \code{"y"} (in that order). The \code{expect\_equal} function tests whether both the data and the attributes of two objects are the same. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_equal(1,1) expect_equal(1, c(x=1)) @ Numbers do not have to be exactly the same to be equal (by default). <<>>= 0.9-0.7-0.2 expect_equal(0.9-0.7-0.2,0) expect_equal(0.9-0.7-0.2,0, tolerance=0) @ <>= options(prompt=" ", continue = " ", width=75) @ Here is an example from the \pkg{stringdist} package. This package implements various methods to determine how different two strings are. In this test, we check one aspect of the `optimal string alignment' algorithm. In particular, we test if it correctly counts the switch of two adjacent characters as a single operation. <>= expect_equal(stringdist("ab", "ba", method="osa"), 1) @ The \pkg{benchr} package is a package to time R code, and it uses \code{expect\_equal} to extensively check the outputs. Here are a few examples. <>= b <- benchr::benchmark(1 + 1, 2 + 2) m <- mean(b) expect_equal(class(m), c("summaryBenchmark", "data.frame")) expect_equal(dim(m), c(2L, 7L)) expect_equal(names(m), c("expr", "n.eval", "mean", "trimmed", "lw.ci", "up.ci", "relative")) expect_equal(class(m$expr), "factor") expect_equal(levels(m$expr), c("1 + 1", "2 + 2")) expect_true(all(sapply(m[-1], is.numeric))) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_equivalent}} This function ignores the attributes when comparing two R objects. Two objects are equivalent when their data are the same. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_equivalent(1,1) expect_equivalent(1, c(x=1)) @ <>= options(prompt=" ", continue = " ", width=75) @ The \pkg{validate} package offers functions to define restrictions on data, and then confront the data with them. The function \code{values} extracts the boolean results in the form of a matrix with specific row- and column names. In the example below we are only interested in testing whether the \emph{contents} of the matrix is computed correctly. <>= v <- validator(x > 0) d <- data.frame(x=c(1,-1,NA)) expect_equivalent(values(confront(d,v)), matrix(c(TRUE,FALSE,NA)) ) @ The \pkg{anytime} package translates text data into data/time format (\code{Date} or \code{POSIXct}). Here, a test is performed to equivalence, to ignore the timezone label that is attached by anytime but not by \code{as.Date}. <>= refD <- as.Date("2016-01-01")+0:2 expect_equivalent(refD, anydate(20160101L + 0:2)) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_identical}} This is the most strict test for equality. The best way to think about this is that two objects must be byte-by-byte indistinguishable in order to be identical. The differences can be subtle, as shown below. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= La <- list(x=1); Lb <- list(x=1) expect_identical(La, Lb) a <- new.env() a$x <- 1 b <- new.env() b$x <- 1 expect_identical(a,b) @ Here, \code{La} and \code{Lb} are indistinguishable from R's point of view. They only differ in their location in memory. The environments \code{a} and \code{b} \emph{are} distinguishable since they contain an explicit identifier which make them unique. <<>>= print(a) print(b) @ Another difference with \code{expect\_equal} and \code{expect\_equivalent} is that \code{expect\_identical} does not allow any tolerance for numerical differences. <>= options(prompt=" ", continue = " ", width=75) @ The \code{stringdistmatrix} function of \pkg{stringdist} computes a matrix of string dissimilarity measures between all elements of a character vector. Below, it is tested whether the argument \code{useNames="none"} and the legacy (deprecated) argument \code{useName=FALSE}. <>= a <- c(k1 = "aap",k2="noot") expect_identical(stringdistmatrix(a,useNames="none") , stringdistmatrix(a,useNames=FALSE)) @ The \pkg{wand} package can retrieve MIME types for files and directories. This means there are many cases to test. In this particular package this is done by creating two lists, one with input and one with expected results. The tests are then performed as follows: <>= list( ## [long list of results removed for brevity] ) -> results fils <- list.files(system.file("extdat", package="wand"), full.names=TRUE) tst <- lapply(fils, get_content_type) names(tst) <- basename(fils) for(n in names(tst)) expect_identical(results[[n]], tst[[n]]) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_null}} The result of an operation should be \code{NULL}. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_null(iris$hihi) expect_null(iris$Species) @ <>= options(prompt=" ", continue = " ", width=75) @ This function is new in version 0.9.7 and not used in any depending packages yet. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_true}, \code{expect\_false}} The result of an operation should be precisely \code{TRUE} or \code{FALSE}. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_true(1 == 1) expect_false(1 == 2) @ <>= options(prompt=" ", continue = " ", width=75) @ The \pkg{anytime} package converts many types of strings to date/time objects (\code{POSIXct} or \code{Date}). Here is a part of it's \pkg{tinytest} test suite. <>== ## Datetime: factor and ordered (#44) refD <- as.Date("2016-09-01") expect_true(refD == anydate(as.factor("2016-09-01"))) expect_true(refD == anydate(as.ordered("2016-09-01"))) expect_true(refD == utcdate(as.factor("2016-09-01"))) expect_true(refD == utcdate(as.ordered("2016-09-01"))) @ Note that \code{==} used here has subtly different behavior from \code{all.equal} used by \code{expect\_equal}. In the above case, \code{==} does not compare time zone data, which is not added by \code{as.Date} but is added by \code{anytime}. This means that for example <>= expect_equal(anydate(as.factor("2016-09-01")), refD) @ would fail. The \pkg{ulid} package uses \code{expect\_true} to verify the type of a result. <>= x <- ULIDgenerate(20) expect_true(is.character(x)) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_message}} Expect that a message is emitted. Optionally you can specify a regular expression that the message must match. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_message(message("hihi")) expect_message(message("hihi"), pattern = "hi") expect_message(message("hihi"), pattern= "ha") expect_message(print("hihi")) @ <>= options(prompt=" ", continue = " ", width=75) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_warning}} Expect that a warning is emitted. Optionally you can specify a regular expression that the warning must match. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_warning(warning("hihi")) expect_warning(warning("hihi"), pattern = "hi") expect_warning(warning("hihi"), pattern= "ha") expect_warning(1+1) @ <>= options(prompt=" ", continue = " ", width=75) @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_error}} Expect that an error is emitted. Optionally you can specify a regular expression that the error must match. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_error(stop("hihi")) expect_error(stop("hihi"), pattern = "hi") expect_error(stop("hihi"), pattern= "ha") expect_error(print("hoho")) @ <>= options(prompt=" ", continue = " ", width=75) @ The \pkg{ChemoSpec2D} package implements exploratory methods for 2D-spectrometry data. Scaled data has negative values, so one cannot take the logarithm. The function \code{centscaleSpectra2D} must eject an error in such cases and this is tested as follows. <>= # Check that log and centering cannot be combined expect_error( centscaleSpectra2D(tiny, center = TRUE, scale = "log"), "Cannot take log of centered data") @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{expect\_silent}} Sometimes a test is only run to check that the code does not crash. This function tests that no warnings or errors are emitted when evaluating it's argument. <>= options(prompt="R> ", continue = " ", width=75) @ <<>>= expect_silent(print(10)) expect_silent(stop("haha")) @ <>= options(prompt=" ", continue = " ", width=75) @ The \pkg{validate} package defines an object called a \code{validation}, which is the result of confronting a dataset with one or more data quality restrictions in the form of rules. A \pkg{validation} object can be plotted, but this would crash with an error in a certain edge case. Here is a test that was added in response to a reported issue. <>= data <- data.frame(A = 1) rule <- validator(A > 0) cf <- confront(data, rule) expect_silent(plot(rule)) expect_silent(plot(cf)) @ The \pkg{lumberjack} package creats log files that track changes in data. In one test it is first tested whether a file has been generated, next it is tested whether it can be read properly. This is also an example of programming over test results, since the file is deleted if it exists. <>= run("runs/multiple_loggers.R") simple_ok <- expect_true(file.exists("runs/simple_log.csv")) expect_silent(read.csv("runs/simple_log.csv")) if (simple_ok) unlink("runs/simple_log.csv") @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newpage{} \section{\code{ignore}} Ignore allows you to not record the result of a test. It is not used very often. Its use is probably almost exclusive to \pkg{tinytest} where it is used while testing the expectation functions. The following result is not recorded (note placement of brackets!) <>= ignore(expect_equal)(1+1, 2) @ The \pkg{digest} package computes hashes of R objects. It uses \code{ignore} in one of it's files. <>= mantissa <- gsub(" [0-9]*$", "", x.hex) ignore(expect_true)(all( sapply( head(seq_along(mantissa), -1), function(i){ all( grepl( paste0("^", mantissa[i], ".*"), tail(mantissa, -i) ) ) } ) )) @ \newpage \begin{thebibliography}{5} \bibitem{anytime} \href{https://cran.r-project.org/package=anytime}{anytime} D. Eddelbuettel (2019) \emph{Anything to `POSIXct' or `Date' Converter}. R package version 0.3.3.5 \bibitem{benchr} \href{https://CRAN.R-project.org/package=benchr}{benchr} Arttem Klevtsov (2019) \emph{High Precise Measurement of R Expressions Execution Time}. R package version 0.2.3-1. \bibitem{ChemoSpec2D} \href{https://cran.r-project.org/package=cyclocomp}{ChemoSpec2D} B.A. Hanson (2019) \emph{Exploratory Chemometrics for 2D Spectroscopy} R package version 0.3.166 \bibitem{digest} \href{https://cran.r-project.org/package=digest}{digest} D. Eddelbuettel (2019) \emph{Create Compact Hash Digests of R Objects} R package version 0.6.20 \bibitem{stringdist} \href{https://cran.r-project.org/package=stringr}{stringdist} M. van der Loo (2014). \emph{The stringdist package for approximate string matching}. The R Journal 6(1) 111-122 \bibitem{ulid} \href{https://cran.r-project.org/package=ulid}{ulid} B. Rudis (2019) \emph{Generate Universally Unique Lexicographically Sortable Identifiers}. R package version 0.3.0 \bibitem{validate} \href{https://cran.r-project.org/package=validate}{validate} M. van der Loo, E. de Jonge and P. Hsieh (2019) \emph{Data Validation Infrastructure for R}. R package version 0.2.7 \bibitem{wand} \href{https://cran.r-project.org/package=wand}{wand} B. Rudis (2019) \emph{Retrieve `Magic' Attributes from Files and Directories} R package version 0.5.0 \end{thebibliography} \end{document} tinytest/inst/doc/using_tinytest.R0000644000176200001440000002601314071046216017075 0ustar liggesusers### R code from vignette source 'using_tinytest.Rnw' ################################################### ### code chunk number 1: using_tinytest.Rnw:79-83 ################################################### options(prompt="R> ", continue = " ", width=75, tt.pr.color=FALSE) ################################################### ### code chunk number 2: using_tinytest.Rnw:108-114 ################################################### lbs2kg <- function(x){ if ( x < 0 ){ stop(sprintf("Expected nonnegative weight, got %g",x)) } x/2.20 } ################################################### ### code chunk number 3: using_tinytest.Rnw:117-120 ################################################### library(tinytest) expect_equal(lbs2kg(1), 1/2.2046) expect_error(lbs2kg(-3)) ################################################### ### code chunk number 4: using_tinytest.Rnw:126-127 ################################################### isTRUE( expect_true(2 == 1 + 1) ) ################################################### ### code chunk number 5: using_tinytest.Rnw:152-154 ################################################### expect_error(lbs2kg(-3), pattern="nonnegative") expect_error(lbs2kg(-3), pattern="foo") ################################################### ### code chunk number 6: using_tinytest.Rnw:168-169 ################################################### expect_false( 1 + 1 == 2, info="My personal message to the tester" ) ################################################### ### code chunk number 7: using_tinytest.Rnw:200-201 ################################################### print(expect_equal(1+1, 3), type="short") ################################################### ### code chunk number 8: eval ################################################### run_test_file("test_addOne.R", verbose=0) ################################################### ### code chunk number 9: using_tinytest.Rnw:247-249 ################################################### test_results <- run_test_file("test_addOne.R", verbose=0) print(test_results, passes=TRUE) ################################################### ### code chunk number 10: using_tinytest.Rnw:252-253 (eval = FALSE) ################################################### ## options(tt.pr.passes=TRUE) ################################################### ### code chunk number 11: using_tinytest.Rnw:259-260 (eval = FALSE) ################################################### ## run_test_dir("/path/to/your/test/directory") ################################################### ### code chunk number 12: using_tinytest.Rnw:267-269 ################################################### out <- run_test_dir(system.file("tinytest", package="tinytest") , verbose=0) ################################################### ### code chunk number 13: using_tinytest.Rnw:273-274 ################################################### head(as.data.frame(out), 3) ################################################### ### code chunk number 14: using_tinytest.Rnw:280-281 ################################################### summary(out) ################################################### ### code chunk number 15: using_tinytest.Rnw:291-294 (eval = FALSE) ################################################### ## if ( expect_equal(1 + 1, 2) ){ ## expect_true( 2 > 0) ## } ################################################### ### code chunk number 16: using_tinytest.Rnw:304-307 ################################################### if ( ignore(expect_equal)(1+1, 2) ){ expect_true(2>0) } ################################################### ### code chunk number 17: using_tinytest.Rnw:315-318 ################################################### if ( Sys.info()[['sysname']] == "Windows"){ exit_file("Cannot test this on Windows") } ################################################### ### code chunk number 18: using_tinytest.Rnw:344-350 (eval = FALSE) ################################################### ## run_test_dir("/path/to/my/testdir" ## , remove_side_effects = FALSE) ## test_all("/path/to/my/testdir" ## , remove_side_effects = FALSE) ## # Only in tests/tinytest.R: ## test_package("PACKAGENAME", remove_side_effects=FALSE) ################################################### ### code chunk number 19: using_tinytest.Rnw:357-358 (eval = FALSE) ################################################### ## options(tt.collate="C") ################################################### ### code chunk number 20: using_tinytest.Rnw:374-375 (eval = FALSE) ################################################### ## test_package("pkg", side_effects=TRUE) ################################################### ### code chunk number 21: using_tinytest.Rnw:379-380 (eval = FALSE) ################################################### ## test_package("pkg", side_effects=list(pwd=FALSE)) ################################################### ### code chunk number 22: using_tinytest.Rnw:409-410 ################################################### run_test_file("test_se.R", verbose=1) ################################################### ### code chunk number 23: using_tinytest.Rnw:456-457 (eval = FALSE) ################################################### ## setup_tinytest("/path/to/your/package") ################################################### ### code chunk number 24: using_tinytest.Rnw:470-471 (eval = FALSE) ################################################### ## test_all("/path/to/your/package") ################################################### ### code chunk number 25: using_tinytest.Rnw:482-483 (eval = FALSE) ################################################### ## build_install_test("/path/to/your/package") ################################################### ### code chunk number 26: using_tinytest.Rnw:503-505 (eval = FALSE) ################################################### ## output = pkg:::some_internal_function(1) ## expect_equal(output, 2) ################################################### ### code chunk number 27: using_tinytest.Rnw:555-557 (eval = FALSE) ################################################### ## dat <- read.csv("women.csv") ## expect_equal(dat, women) ################################################### ### code chunk number 28: using_tinytest.Rnw:569-570 ################################################### options(prompt=" ", continue=" ") ################################################### ### code chunk number 29: using_tinytest.Rnw:572-577 (eval = FALSE) ################################################### ## # contents of pkgdir/tests/tinytest.R ## if ( requireNamespace("tinytest", quietly=TRUE) ){ ## home <- identical( Sys.info()["nodename"], "YOURHOSTNAME" ) ## tinytest::test_package("PKGNAME", at_home = home) ## } ################################################### ### code chunk number 30: using_tinytest.Rnw:582-583 ################################################### home <- identical( Sys.getenv("HONEYIMHOME"), "TRUE" ) ################################################### ### code chunk number 31: using_tinytest.Rnw:587-588 (eval = FALSE) ################################################### ## home <- length(unclass(packageVersion("PKGNAME"))[[1]]) == 4 ################################################### ### code chunk number 32: using_tinytest.Rnw:592-593 ################################################### options(prompt="R> ", continue=" ") ################################################### ### code chunk number 33: using_tinytest.Rnw:600-602 ################################################### run_test_file("test_hehe.R", verbose=0) run_test_file("test_hehe.R", verbose=0, at_home=FALSE) ################################################### ### code chunk number 34: using_tinytest.Rnw:626-627 (eval = FALSE) ################################################### ## tinytest::test_package("hehe") ################################################### ### code chunk number 35: using_tinytest.Rnw:669-676 (eval = FALSE) ################################################### ## require(dittodb) ## with_mock_path( ## system.file("", package = "myPackage"), ## with_mock_db({ ## # ## }) ## ) ################################################### ### code chunk number 36: using_tinytest.Rnw:687-688 (eval = FALSE) ################################################### ## test_package("tinytest", set_env = list(WA_BABALOOBA="BA_LA_BAMBOO")) ################################################### ### code chunk number 37: using_tinytest.Rnw:718-719 (eval = FALSE) ################################################### ## build_install_test("/path/to/your/package", ncpu=2) ################################################### ### code chunk number 38: using_tinytest.Rnw:730-731 (eval = FALSE) ################################################### ## test_package("PACKAGENAME", ncpu=2) ################################################### ### code chunk number 39: using_tinytest.Rnw:753-756 (eval = FALSE) ################################################### ## cl <- parallel::makeCluster(4, outfile="") ## parallel::clusterCall(cl, source, "R/myfunctions.R") ## run_test_dir("inst/tinytest", cluster=cl) ################################################### ### code chunk number 40: using_tinytest.Rnw:763-766 (eval = FALSE) ################################################### ## parallel::clusterCall(cl, source, "R/myfunctions.R") ## test_all(cluster=cl) ## stopCluster(cl) ################################################### ### code chunk number 41: using_tinytest.Rnw:801-810 ################################################### # exported, user-visible function inch2cm <- function(x){ x*conversion_factor("inch") } # not exported function, package-internal conversion_factor <- function(unit){ confac <- c(inch=2.54, pound=1/2.2056) confac[unit] } ################################################### ### code chunk number 42: using_tinytest.Rnw:853-861 ################################################### pound2kg <- function(x){ stopifnot( is.numeric(x) ) if ( any(x < 0) ){ warning("Found negative input, converting to positive") x <- abs(x) } x/2.2046 } ################################################### ### code chunk number 43: using_tinytest.Rnw:880-886 (eval = FALSE) ################################################### ## expect_equal(pound2kg(1), 1/2.2046 ) ## # test for expected warning, store output ## expect_warning( out <- pound2kg(-1) ) ## # test the output ## expect_equal( out, 1/2.2046) ## expect_error(pound2kg("foo")) ################################################### ### code chunk number 44: using_tinytest.Rnw:931-937 ################################################### bad_function <- function(file){ oldwd <- getwd() setwd(dirname(file)) source(basename(file)) setwd(oldwd) } ################################################### ### code chunk number 45: using_tinytest.Rnw:944-950 ################################################### good_function <- function(file){ oldwd <- getwd() on.exit(setwd(oldwd)) setwd(dirname(file)) source(basename(file)) } tinytest/inst/CITATION0000644000176200001440000000045113630550357014256 0ustar liggesusersbibentry(bibtype = "article", title = "A method for deriving information from running R code" , author = c(person("MPJ", "van der Loo")) , year = 2020 , journal = "The R Journal" , volume = "" , issue = "" , url = "https://arxiv.org/abs/2002.07472" , pages = "Accepted for publication" ) tinytest/inst/tinytest/0000755000176200001440000000000014071046217014777 5ustar liggesuserstinytest/inst/tinytest/test_gh_issue_32.R0000644000176200001440000000055413532276246020307 0ustar liggesusers out <- ignore(expect_equal)("foo","bar") # Message should read 'Expected "target" got "current"', # not the other way around expect_true(grepl("bar.+foo",attr(out,"diff"))) out <- ignore(expect_identical)("foo","bar") expect_true(grepl("bar.+foo",attr(out,"diff"))) out <- ignore(expect_equivalent)("foo","bar") expect_true(grepl("bar.+foo",attr(out,"diff"))) tinytest/inst/tinytest/test_init.R0000644000176200001440000000031613500000356017112 0ustar liggesusers oldterm <- Sys.getenv("TERM") Sys.setenv(TERM = "dumb") # will be unset by tinytest tinytest:::.onLoad() expect_false(getOption("tt.pr.color")) # reset option set by .onLoad() options(tt.pr.color=NULL) tinytest/inst/tinytest/test_gh_issue_86.R0000644000176200001440000000047314065357604020320 0ustar liggesusers# test the ... argument expect_error(stop("chocolate foo(bar)"), pattern="foo(", fixed=TRUE) expect_warning(warning("chocolate foo(bar)"), pattern="foo(", fixed=TRUE) expect_message(message("chocolate foo(bar)"), pattern="foo(", fixed=TRUE) expect_stdout(cat("chocolate foo(bar)"), pattern="foo(", fixed=TRUE) tinytest/inst/tinytest/programming.R0000644000176200001440000000035213462146043017445 0ustar liggesusers # some test to check that we can program over tests. # this should yield 10 testresults for ( i in 1:10 ){ expect_equal(1+1,2) } # this should yield a single testresult if ( ignore(expect_equal)(1+1,2) ){ expect_true(TRUE) } tinytest/inst/tinytest/test_call_wd.R0000644000176200001440000000025713755031203017567 0ustar liggesusers# Only test at home, because R CMD check uses different working directories and # who knows what happens at CRAN. if( at_home() ){ expect_false(getwd() == get_call_wd()) } tinytest/inst/tinytest/runs/0000755000176200001440000000000014063210547015766 5ustar liggesuserstinytest/inst/tinytest/runs/test_envvar.R0000644000176200001440000000025013524610553020450 0ustar liggesusersreport_side_effects() expect_equal(1+1,2) Sys.setenv("foo"="bar") expect_equal("a","b") report_side_effects(FALSE) Sys.setenv("foo"="baz") expect_equal(TRUE, TRUE) tinytest/inst/tinytest/runs/test_exit.R0000644000176200001440000000014613525326355020131 0ustar liggesusers expect_true(TRUE) expect_true(TRUE) exit_file(msg="snafubar") expect_true(TRUE) expect_true(TRUE) tinytest/inst/tinytest/runs/test_cwd2.R0000644000176200001440000000005513525316246020014 0ustar liggesusers old <- getwd() setwd(tempdir()) setwd(old) tinytest/inst/tinytest/runs/test_envvar2.R0000644000176200001440000000022313525316246020535 0ustar liggesusers expect_equal(1+1,2) Sys.setenv("foo"="bar") expect_equal("a","b") report_side_effects(FALSE) Sys.setenv("foo"="baz") expect_equal(TRUE, TRUE) tinytest/inst/tinytest/runs/test_set_env.R0000644000176200001440000000017013630256301020606 0ustar liggesusers # this file must be tested with run_test_file(...,set_env) expect_equal(Sys.getenv("wa_babalooba"), "ba_la_bamboo") tinytest/inst/tinytest/runs/test_cwd.R0000644000176200001440000000010313524623313017717 0ustar liggesusersreport_side_effects() old <- getwd() setwd(tempdir()) setwd(old) tinytest/inst/tinytest/runs/test_locale.R0000644000176200001440000000014214063202375020404 0ustar liggesusersreport_side_effects() expect_equal(1+1,2) Sys.setlocale("LC_COLLATE","C") expect_equal("a","b") tinytest/inst/tinytest/runs/test_double_colon.R0000644000176200001440000000034013737353043021617 0ustar liggesusers # there should be a warning when running this test file, # explaning that tests that are prefixed with ::: are # not registered (GH issue #60) tinytest::expect_equal(1,1) expect_equal(1,1) tinytest::expect_equal(2,2) tinytest/inst/tinytest/test_tiny.R0000644000176200001440000001046613757312265017164 0ustar liggesusers # check behavior expect_true(TRUE) expect_false(FALSE) expect_equal(1,1) expect_identical(1L,1L) # check output value (wow, this is pretty meta---man) expect_true( ignore(expect_true)(TRUE)) expect_false(ignore(expect_true)(FALSE)) expect_true( ignore(expect_false)(FALSE)) expect_false(ignore(expect_false)(TRUE)) expect_false( ignore(expect_identical)(1L,2L) ) # check behavior expect_equal(1+1,2) # check output value expect_false(ignore(expect_equal)(1+1, 3)) expect_true(ignore(expect_equal)(1+1, 2)) # check behavior expect_equivalent(2,c(x=2)) # check output value expect_true(ignore(expect_equivalent)(2,c(x=2))) expect_false(ignore(expect_equivalent)(2,c(x=3))) expect_true(ignore(expect_inherits)(1, "numeric")) expect_false(ignore(expect_inherits)(1, c("matrix", "array"))) # check NULL expect_true(ignore(expect_null)(NULL)) expect_false(ignore(expect_null)(1)) fl <- tempfile() expect_true(ignore(expect_equal_to_reference)(1, file=fl)) expect_true(ignore(expect_equal_to_reference)(1, file=fl)) expect_false(ignore(expect_equal_to_reference)(2, file=fl)) xx <- c(fu=1) expect_true(ignore(expect_equivalent_to_reference)(xx, file=fl)) expect_false(ignore(expect_equal_to_reference)(xx, file=fl)) # reading from file dat <- read.csv("women.csv") expect_equal(women, dat) # check behavior expect_warning(warning("foo")) expect_error(stop("bar")) # class of error condition ec <- errorCondition(message="wa babalooba", class="foo") expect_false(ignore(expect_error)( stop(ec), class="bar" )) expect_true (ignore(expect_error)( stop(ec), class="foo" )) # class of warning condition wc <- warningCondition(message="ba la bamboo", class="foo") expect_false(ignore(expect_warning)( warning(wc), class="bar")) expect_true (ignore(expect_warning)( warning(wc), class="foo")) # messages to stdout expect_stdout(print("hihi")) expect_stdout(cat("hihi")) expect_stdout(cat("hoho"), pattern="ho") expect_false(ignore(expect_stdout)(cat("hihi"),pattern="ho")) expect_true(ignore(expect_error)(stop("foo"))) expect_false(ignore(expect_error)(stop("foo"),pattern="bar")) # single warning expect_true(ignore(expect_warning)(warning("fu!"))) expect_false(ignore(expect_warning)(warning("fu!"), pattern="bar")) # filtering from multiple warnings multiwrn <- function(){ warning("hihi") warning("haha") m <- tryCatch(warning("huhu"), warning = function(m) m) class(m) <- c("lulz", class(m)) warning(m) } expect_warning(multiwrn(), pattern="haha") expect_warning(multiwrn(), pattern="huhu") expect_warning(multiwrn(), pattern="huhu", class="lulz") # single messages expect_true(ignore(expect_silent)(1 + 1)) expect_false(ignore(expect_silent)(1 + "a")) expect_false(ignore(expect_silent)(1:3 + 1:2)) expect_false(ignore(expect_message)(message("hihi"),"lol")) expect_false(ignore(expect_message)(stop("hihi"),"lol")) expect_false(ignore(expect_message)(warning("hihi"),"lol")) expect_message(message("hihi, I lol"),"lol") # filtering from multiple messages multimsg <- function(){ message("hihi") message("haha") m <- tryCatch(message("huhu"), message = function(m) m) class(m) <- c("lulz", class(m)) message(m) } expect_message(multimsg(), pattern="haha") expect_message(multimsg(), pattern="huhu") expect_message(multimsg(), pattern="huhu", class="lulz") expect_stdout(print("hihi"),pattern="hi") expect_stdout(cat("hihi"),pattern="hi") expect_false(ignore(expect_stdout)(print("hihi"),pattern="ho")) # check that info fields are filled. msg <- "whoO0Oop" L <- list( ignore(expect_true)(TRUE, info=msg) , ignore(expect_true)(FALSE, info=msg) , ignore(expect_false)(TRUE, info=msg) , ignore(expect_false)(FALSE, info=msg) , ignore(expect_equal)(1,1, info=msg) , ignore(expect_equal)(1,2, info=msg) , ignore(expect_equivalent)(1,1, info=msg) , ignore(expect_equivalent)(1,2, info=msg) , ignore(expect_identical)(1,1, info=msg) , ignore(expect_identical)(1,2, info=msg) , ignore(expect_null)(NULL,info=msg) , ignore(expect_null)(NA,info=msg) , ignore(expect_message)(message("hihi"),info=msg) , ignore(expect_message)(1+1, info=msg) , ignore(expect_warning)(warning("hihi"), info=msg) , ignore(expect_warning)(1+1, info=msg) , ignore(expect_error)(stop("hihi"),info=msg) , ignore(expect_silent)(1+1, info=msg) , ignore(expect_silent)(stop("hihi"), info=msg) ) for ( x in L ) expect_equal(attr(x,"info"), msg) tinytest/inst/tinytest/test_file.R0000644000176200001440000000440014063205610017071 0ustar liggesusers## note: the system.file call is just a convenience for my local test.r script # run all tests in test_tiny again, this time # with the file runner. results <- run_test_file(system.file("tinytest/test_tiny.R",package="tinytest"),verbose=FALSE) bools <- sapply(results, as.logical) expect_true(all(bools)) # more complicated tests, using ignore() to skip nested expectations results <- run_test_file(system.file("tinytest/programming.R",package="tinytest"), verbose=FALSE) expect_equal(11, length(results)) expect_true(all_pass(results)) expect_false(any_fail(results)) expect_false(all_fail(results)) expect_true(any_pass(results)) expect_error(all_pass("hihi")) expect_error(any_pass("hihi")) expect_error(all_fail("hihi")) expect_error(any_fail("hihi")) # collect side effects using the call in the test file out <- run_test_file("runs/test_envvar.R",verbose=0) expect_true(is.na(out[[2]])) expect_equal(sum(is.na(sapply(out, c))),1) # only perform this test when LC_COLLATE is not "C", because # setting this is what we try to detect. if (Sys.getlocale("LC_COLLATE") != "C"){ out <- run_test_file("runs/test_locale.R", verbose=0) expect_true(is.na(out[[2]])) } out <- run_test_file("runs/test_cwd.R", verbose=0) expect_true(is.na(out[[1]])) expect_true(is.na(out[[2]])) # controll collecting side-effects from file runner. out <- run_test_file("runs/test_envvar2.R", verbose=0, side_effects=TRUE) expect_true(is.na(out[[2]])) expect_equal(sum(is.na(sapply(out, c))),1) # detailed control out <- run_test_file("runs/test_cwd2.R", side_effects=list(pwd=FALSE), verbose=0) expect_equal(length(out),0) # premature exit testing out <- run_test_file("runs/test_exit.R", verbose=0) expect_equal(length(out), 2) # plots should not cause an 'Rplots.pdf' file being created plot(1:10, 1:10) expect_false(exists("Rplots.pdf")) # test that files are run with environment variables set out <- run_test_file("runs/test_set_env.R", set_env=list("wa_babalooba"="ba_la_bamboo"), verbose=0) expect_true(all_pass(out)) expect_message(run_test_file("runs/test_double_colon.R", verbose=0)) expect_message(tinytest:::check_double_colon("runs/test_double_colon.R")) # uncomment to see the warning with 'make test' # this also tests that commented lines are not counted. # tinytest:::expect_equal(2,5) tinytest/inst/tinytest/test_env_B.R0000644000176200001440000000072713476015321017220 0ustar liggesusers # The "hihihaha" environment variable was set in test_env_A and should be unset # now. expect_equal(Sys.getenv("hihihaha"), "") # The "hihihaha" option was set in test_env_A and should be unset now expect_true(is.null(getOption("hihihaha"))) expect_equal(Sys.getenv("hoho"), "") expect_true( is.null(getOption("hoho")) ) # Surviving envvars and options expect_equal(Sys.getenv("hehe"), "3") Sys.unsetenv("hehe") expect_equal(getOption("hehe"), 3) options(hehe=NULL) tinytest/inst/tinytest/test_extensibility.R0000644000176200001440000000026213524455575021072 0ustar liggesusers register_tinytest_extension(pkg="lulz" , functions=c("fee","fi","fu","bar")) ext <- getOption("tt.extensions") expect_equal(ext, list(lulz = c("fee","fi","fu","bar") )) tinytest/inst/tinytest/test_gh_issue_51.R0000644000176200001440000000022213660042025020264 0ustar liggesusers # small reproducible provided by Ralf Herold expect_message( { message("catch_one") message("catch_two") }, pattern = "two" ) tinytest/inst/tinytest/test_gh_issue_17.R0000644000176200001440000000011713546554224020305 0ustar liggesusers expect_equal(1, 1.1, tolerance=0.2) expect_equivalent(1, 1.1, tolerance=0.2) tinytest/inst/tinytest/test_utils.R0000644000176200001440000000036513761203377017334 0ustar liggesusers expect_error(tinytest:::stopf("foo %s","bar"),"foo bar") expect_equal(tinytest:::humanize(0.3,color=FALSE),"0.3s") expect_equal(tinytest:::humanize(61,color=FALSE),"1m 1.0s") expect_equal(tinytest:::humanize(3601,color=FALSE),"1h 0m 1.0s") tinytest/inst/tinytest/women.csv0000644000176200001440000000017313462146043016643 0ustar liggesusers"height","weight" 58,115 59,117 60,120 61,123 62,126 63,129 64,132 65,135 66,139 67,142 68,146 69,150 70,154 71,159 72,164 tinytest/inst/tinytest/test_gh_issue_58.R0000644000176200001440000000013313732440320020274 0ustar liggesusers expect_stdout( str(mtcars), "carb") expect_false(ignore(expect_stdout)(str(cars),"carb")) tinytest/inst/tinytest/test_env_A.R0000644000176200001440000000153413476015321017214 0ustar liggesusers # We set an environment variable in this file. It must be unset by tinytest # before running the next file (test_env_B.R) Sys.setenv(hihihaha=8) expect_equal(Sys.getenv("hihihaha"), "8") # We set an option in this file. It must be unset by tinytest before running # the next file (test_env_B.R) options(hihihaha=8) expect_equal(getOption("hihihaha"), 8) # We set another envvar and unset it as well. We don't want to bother # users already following good practice k <- Sys.getenv("hoho") Sys.setenv(hoho=2) Sys.setenv(hoho=k) # We set another option and unset it as well. We don't want to bother # users already following good practice oldopt <- options("hoho") options(hoho=2) options(hoho=oldopt) # Here's an envvar that must survive the slaughter after this file was run base::Sys.setenv(hehe=3) # And one for options as well base::options(hehe=3) tinytest/inst/tinytest/test_RUnit_style.R0000644000176200001440000000037713463134023020446 0ustar liggesusers # check if RUnit style functions also work. expect_true(ignore(checkTrue)(TRUE)) expect_true(ignore(checkFalse)(FALSE)) expect_true(ignore(checkEqual)(1+1,2)) expect_true(ignore(checkIdentical)(1L,1L)) expect_true(ignore(checkEquivalent)(c(a=1),1))