RUnit/0000755000176200001440000000000014565704512011324 5ustar liggesusersRUnit/NAMESPACE0000644000176200001440000000202614565677274012561 0ustar liggesusers###################################################################### ## ## RUnit ## ===================================== ## ## inst/NAMESPACE ## ===================================== ## initialization of classes, namespace, ... ## ## ## ## ## Version ## ===================================== ## $Id$ ## ## ###################################################################### import(utils) import(methods) importFrom("graphics", "arrows", "legend", "lines", "plot", "text") export(".setUp", ".tearDown", "checkTrue", "checkEquals", "checkEqualsNumeric", "checkException", "checkIdentical", "DEACTIVATED", "defineTestSuite", "getErrors", "inspect", "isValidTestSuite", "printTextProtocol", "printHTMLProtocol", "printJUnitProtocol", "printHTML", "runTestSuite", "runTestFile", "tracker") S3method(print, RUnitTestData) S3method(summary, RUnitTestData) S3method(printHTML, trackInfo) S3method(printHTML, default) RUnit/ChangeLog0000644000176200001440000012100514563457515013103 0ustar liggesusers2024-02-15 12:15 zenkar * cleaned up minor CRAN check notes 2009-04-22 15:52 burgerm * tests/runitInspect.r: added test cases for more complex functions to track 2009-04-22 15:50 burgerm * R/inspector.r: includeTracker: fix `<- if` call handling which caused the inspect mechanism to fail 2009-04-22 15:48 burgerm * R/runit.r: isValidTestSuite: check for empty name string as this will cause subsequent failure which is harder to understand 2009-04-22 15:46 burgerm * NAMESPACE: declare S3 print and summary methods for RUnitTestData 2009-04-22 11:37 burgerm * inst/doc/RUnit.pdf: removed: use R CMD build to generate, no need to store in repository 2009-04-16 11:54 burgerm * DESCRIPTION: patch level 0.4.22: clarified GPL version 2009-04-16 11:49 burgerm * R/textProtocol.r: specified license to be version 2 of the GPL 2009-04-16 11:48 burgerm * R/testLogger.r: specified license to be version 2 of the GPL; extended function descriptions 2009-04-16 11:44 burgerm * R/: checkFuncs.r, exportHTML.r, html.r, htmlProtocol.r, inspector.r, runit.r: specified license to be version 2 of the GPL 2009-04-16 11:39 burgerm * R/00Init.r: specified license to be version 2 of the GPL 2009-04-16 11:23 burgerm * inst/: examples/correctTestCase.r, examples/runitVirtualClassTest.r, share/R/checkCode.r, share/R/compareRUnitTestData.r: specified license to be version 2 of the GPL 2009-04-16 11:18 burgerm * tests/: runitHTMLProtocol.r, runitInspect.r, runitPlotConnection.r, runitRUnit.r, runitS4.r, runitSetUp.r, runitTearDown.r, runitTextProtocol.r: specified license to be version 2 of the GPL 2009-04-16 11:13 burgerm * man/: RUnit-internal.Rd, RUnit-intro.Rd, checkFuncs.Rd, inspect.Rd, printHTML.trackinfo.Rd, runit.Rd, textProtocol.Rd, tracker.Rd: specified license to be version 2 of the GPL 2009-03-16 17:38 burgerm * R/inspector.r: always coninue if-else on the same line removed semicolon at line ends 2009-03-16 17:36 burgerm * R/runit.r: defineTestSuite: gained some argument checks isValidTestSuite: warning messages modified to handle multiple names some typos in inline documentation fixed 2009-03-16 17:29 burgerm * R/checkFuncs.r: always coninue if else on the same line checkIdentical: added precondition check 2009-03-16 17:26 burgerm * R/exportHTML.r: always coninue if-else on the same line removed semicolon at line ends 2009-03-16 17:24 burgerm * R/html.r: always coninue if else on the same line html.r removed semicolon at line ends 2009-03-16 17:21 burgerm * inst/doc/RUnit.Rnw: minute corrections 2009-03-05 16:58 burgerm * DESCRIPTION: License field updated according to R-ext for R 2.8.1 2009-01-23 19:13 burgerm * man/: checkFuncs.Rd, inspect.Rd, printHTML.trackinfo.Rd, runit.Rd, textProtocol.Rd, tracker.Rd: corrected typos, changed formating of code chunks to allow for proper display in pdf reference manual (suggested by Terry Therneau) 2009-01-23 17:23 burgerm * NEWS: typos corrected 2009-01-15 14:47 burgerm * DESCRIPTION: patch level 0.4.21 2009-01-15 14:46 burgerm * ChangeLog: update for 0.4.21 submission 2009-01-15 14:46 burgerm * NEWS: updated for 0.4.21 submission 2009-01-15 14:26 burgerm * man/tracker.Rd: enclosed \item statements within \describe environment: identified by new R 2.9.0 Rd parser 2009-01-15 14:23 burgerm * man/runit.Rd: defineTestSuite default argument value: use \ escape to display \\ in resulting help page after parsing 2008-11-07 14:49 burgerm * R/htmlProtocol.r: compiler detection failed on MacOS, fixed, thanks to report by Steffen Neumann 2008-11-07 12:20 burgerm * R/inspector.r: use seq_along 2008-11-07 12:19 burgerm * R/checkFuncs.r: check for missing arguments 2008-11-07 12:15 burgerm * DESCRIPTION: patch level 0.4.20 2008-06-20 17:51 burgerm * R/textProtocol.r: printTextProtocol rewritten by Klaus using toText; getErrors moved here, TODO replace with S4 method 2008-06-20 17:49 burgerm * NAMESPACE: adopt S4 classes and methods; removed some outdated functions 2008-06-20 17:36 burgerm * R/runit.r: rewritten by Klaus, using S4 design and condition mechanism 2008-06-20 17:29 burgerm * R/00Init.r: added generics definition used for S4 methods 2008-06-20 17:27 burgerm * R/testLogger.r: removed; will eventually be replaced 2008-06-20 17:26 burgerm * R/: TestFileResult.r, TestFunctionResult.r, TestResult.r, TestSuite.r, TestSuiteResult.r: S4 class based design of RUnit 2008-06-20 17:26 burgerm * R/checkFuncs.r: rewritten by Klaus using conditions 2008-06-20 17:24 burgerm * R/utility.r: utility functions not attributed to one class; miscellaneous 2008-06-19 15:57 burgerm * ChangeLog: updated 2008-06-19 13:49 burgerm * tests/runitInspect.r: added 2nd example function 2008-06-19 13:46 burgerm * man/textProtocol.Rd: details section updated; example on RUnit test suite execution added in a dontrun clause: works only on source package as tests/ folder is not copied to installed package 2008-06-19 13:42 burgerm * man/runit.Rd: runTestFile example call added 2008-06-19 10:07 burgerm * DESCRIPTION: patch version 0.4.19 2008-06-18 19:18 burgerm * R/textProtocol.r: details section: output check number per test case 2008-06-18 19:17 burgerm * R/htmlProtocol.r: details section: output check number per test case; improved compiler detection 2008-06-18 19:16 burgerm * R/runit.r: isValidTestSuite: changed check for object class to is() to allow derived class objects to pass (suggested by Philippe Grosjean); use addCheckNum to set number of checks performed within test function 2008-06-18 19:13 burgerm * R/testLogger.r: added addCheckNum, getCheckNum functions to allow check number output in summary 2008-06-18 19:12 burgerm * R/checkFuncs.r: checkEquals: argument checkNames checked for correct type; not required paste calls around error message removed (suggested by Philippe Grosjean) 2008-06-18 19:09 burgerm * R/00Init.r: removed start up message 2008-04-29 10:06 burgerm * inst/share/R/compareRUnitTestData.r: check for empty test case results and exclude them from comparison; replaced seq with seq_along 2007-11-30 15:13 burgerm * tests/runitRUnit.r: error introduced in last commit corrected; some more check conditions added 2007-11-30 14:57 burgerm * R/htmlProtocol.r: fixed errors introduced in last commit: replaced seq_len with seq_along 2007-11-27 20:07 burgerm * ChangeLog: updated 2007-11-27 20:05 burgerm * DESCRIPTION: patch level 0.4.18; R dependency set to 2.4.0 were seq_along was introduced 2007-11-27 19:56 burgerm * tests/runitRUnit.r: seq_along introduced; partial argument names expanded 2007-11-27 19:55 burgerm * R/runit.r: seq_along introduced 2007-11-27 19:53 burgerm * R/exportHTML.r: seq_along introduced; partial argument names expanded 2007-11-27 19:52 burgerm * R/: html.r, htmlProtocol.r, testLogger.r, textProtocol.r: seq_along introduced 2007-05-21 13:42 burgerm * NEWS: updated for 0.4.17 CRAN submission 2007-05-21 13:37 burgerm * DESCRIPTION: patch level 0.4.17 2007-05-21 13:33 burgerm * man/: textProtocol.Rd, runit.Rd: changed example code to work with installed package test case example path 2007-05-18 14:58 burgerm * R/runit.r: try harder to ensure previous error handler is reinstantiated after test runner execution 2007-05-18 14:56 burgerm * DESCRIPTION: patch level 0.4.16 2007-05-18 14:55 burgerm * NEWS: updated for 0.4.16 2007-05-16 15:17 burgerm * DESCRIPTION: SaveImage replaced by LazyLoad 2007-05-16 14:27 burgerm * inst/share/R/compareRUnitTestData.r: initial prototype: comare two RUnitTestData objects: intended for test run time performance evaluation 2007-05-16 14:24 burgerm * R/runit.r: .sourceTestFile: updated to observe - after sandbox introduction - test case file defined .setUp and .tearDown functions 2007-05-16 14:21 burgerm * tests/runitS4.r: check S4 setClass + removeClass chain with new sandbox implementation 2007-05-16 14:19 burgerm * tests/: runitSetUp.r, runitTearDown.r: added for focused check of .setUp and .tearDown functioning 2007-05-16 14:16 burgerm * tests/runitRUnit.r: class names changed; clean up operation revised: try harder to reset to previous global state 2007-05-16 14:15 burgerm * tests/runitInspect.r: .tearDown added: cleanup 2007-05-16 10:49 burgerm * DESCRIPTION: dev patch level 0.4.15-2 2007-05-16 00:46 burgerm * man/runit.Rd: added encoding latin1 to display umlaut correctly if available; make use of describe command to format details section 2007-05-16 00:42 burgerm * man/checkFuncs.Rd: added encoding latin1 to display umlaut correctly if available; added paragraph on S4 classes and methods to details section 2007-05-15 23:46 burgerm * man/inspect.Rd: added encoding latin1 to display umlaut correctly if available 2007-05-15 23:45 burgerm * man/textProtocol.Rd: added encoding latin1 to display umlaut correctly if available; added header; added \pkg command around RUnit phrase 2007-05-15 23:43 burgerm * man/: RUnit-intro.Rd, tracker.Rd, printHTML.trackinfo.Rd: added encoding latin1 to display umlaut correctly if available 2007-05-15 17:33 burgerm * R/runit.r: make use of a new environment defined as child of .GlobalEnv to allow setClass calls wo where argument 2007-04-25 08:46 burgerm * R/TestCaseMethods.r: construct method added; verify method checks partially switched off 2007-04-25 08:39 burgerm * R/TestCaseTestResultData.r: slots checkNum, traceBack and warnMessageStack added 2007-04-23 00:14 burgerm * R/testLogger.r: replaced by condition signals 2007-04-23 00:10 burgerm * R/TestCaseTestResultDataMethods.r: .printTextProtocol added: used in recursive call chain 2007-04-23 00:08 burgerm * R/: SourceFileTestResultDataMethods.r, TestSuiteTestResultDataMethods.r: .printTextProtocol, getError and getTestCaseNum added: used in recursive call chain 2007-04-23 00:05 burgerm * R/runit.r: removed .testLogger; added warning handler; switched to condition signals; use testCaseCheckCount for check call counting 2007-04-23 00:03 burgerm * R/checkFuncs.r: removed .testLogger 2007-04-23 00:00 burgerm * DESCRIPTION: removed .testLogger; added experimental condition signaling 2007-04-15 12:50 burgerm * DESCRIPTION: patch level 0.6.0-2 exploration stage; NOT FIT FOR PUBLIC USE 2007-04-15 12:48 burgerm * NAMESPACE: new S4 classes and methods added 2007-04-15 12:47 burgerm * R/runit.r: experimental: enabled new S4 class based result collection IN PARALLEL to exiting logger mechanism for exploration; NOT FIT FOR PUBLIC USE 2007-04-15 12:45 burgerm * R/Logger.r: getSealed added to exported accessors 2007-04-15 12:40 burgerm * R/checkFuncs.r: RUnit specific signals/conditions implemented and enabled in check* functions 2007-04-15 12:38 burgerm * R/zzz.r: log statement added; TestResultData method def enabled 2007-04-15 12:37 burgerm * R/TestSuiteTestResultDataMethods.r: method implementation added, accessor methods added 2007-04-15 12:36 burgerm * R/TestSuiteTestResultData.r: slots error & errorMsg added to cover suite level errors 2007-04-15 12:35 burgerm * R/TestResultData.r: slot name changed 2007-04-15 12:34 burgerm * R/TestCaseTestResultDataMethods.r: method implementation added, accessor methods added 2007-04-15 12:33 burgerm * R/TestCaseTestResultData.r: class definition reworked, slots renamed and added: NOT FINAL DESIGN 2007-04-15 12:31 burgerm * R/SourceFileTestResultDataMethods.r: method implementation added, getTestResultData added 2007-04-15 12:29 burgerm * R/SourceFileTestResultData.r: slot name renamed to sourceFileName, slots error & errorMsg added to cover source file level errors 2007-04-15 12:28 burgerm * R/htmlProtocol.r: regexpr fixed for R 2.5.0 2007-04-12 10:29 burgerm * R/inspector.r: unused variables removed 2007-04-09 19:42 burgerm * tests/runitRUnit.r: all check* test cases reviewed and extended to cover more failure conditions; checkEquals extended to be checked for all basic R types and S4 objects 2007-04-09 19:38 burgerm * R/testLogger.r: errorHandler: rewritten to be more failure robust; docu added 2007-04-09 19:25 burgerm * R/checkFuncs.r: checkEqualsNumeric: one more argument check added (now identical to checkEquals; docu description made more clear 2007-04-08 14:14 burgerm * R/runit.r: runTestSuite docu make more clear 2007-04-08 14:11 burgerm * R/00Init.r: pass lib argument on to packageDescription to load correct DESCRITPION file 2007-04-05 16:28 burgerm * R/htmlProtocol.r: included deactivatedStyle tag to HTML output: non-visible change 2007-04-02 00:08 burgerm * R/zzz.r: class and method init calls added/updated 2007-04-02 00:04 burgerm * R/00Init.r: packageDescription now observes the library path for the currently loaded package version 2007-04-02 00:02 burgerm * R/TestCaseTestResultData.r: sealed argument controlled by .GLOBAL state; slot failed renamed to failure 2007-04-01 23:59 burgerm * R/: TestResultData.r, SourceFileTestResultData.r, TestSuiteTestResultData.r: sealed argument controlled by .GLOBAL state 2007-04-01 23:53 burgerm * R/: TestCase.r, TestCaseMethods.r, TestLogger.r, TestLoggerMethods.r: playground extended 2007-04-01 23:51 burgerm * R/ArrayMethods.r: - code formating changes ArrayMethods.r - setNames: unnecessary precondition check removed - concat: precondition check added method definition also added to Array base class - printObject rewritten to use show - show method added - array class constructor: sealed argument controlled by .GLOBAL state - applyFun method definition added to array class generator 2007-04-01 23:43 burgerm * R/Array.r: allow sealed argument to be controlled by .GLOBAL state 2007-04-01 23:42 burgerm * R/classUtilities.r: in setGeneric avoid assigning to temp variable value as no postprocessing is intended here and this assignment has a small memory penalty as shown by memprof 2007-04-01 23:38 burgerm * DESCRIPTION: patch level 0.6.0-1: more exploratory code chunks added 2007-03-31 23:15 burgerm * inst/examples/runitVirtualClassTest.r: S4 class example 2007-03-29 09:26 burgerm * inst/share/R/checkCode.r: utility wrappers around checkUsage (package codetools) to check no default location code files; experimental 2007-03-23 15:18 burgerm * tests/runitPlotConnection.r: deactivate test case if R is run in non-interactive mode i.e. most likely no X server is present but require by the png device 2007-03-19 21:08 burgerm * tests/runitPlotConnection.r: simple run test 2007-03-19 01:54 burgerm * R/htmlProtocol.r: fixed gcc query under Windows: report NA 2007-03-19 01:52 burgerm * NEWS: printHTMLProtocol fixed (Windows) 2007-03-19 00:55 burgerm * man/runit.Rd: added header 2007-03-19 00:54 burgerm * tests/runitRUnit.r: added exception check for call object: R 2.5.0 devel issue with new try implementation 2007-03-19 00:51 burgerm * tests/: runitTextProtocol.r, runitHTMLProtocol.r: added check for successful execution, taking care of nested test suite calls 2007-03-19 00:49 burgerm * inst/examples/correctTestCase.r: R 1.9.0 compatibility: replace isTRUE by identical 2007-03-19 00:45 burgerm * R/inspector.r: docu updated/extended; tracker closure functions revised: internal object now is always of S3 class trackInfo, renamed to trackInfo for clarity, addFunc simplified, reinit of oldTime was not written to closure variable; several precondition checks added 2007-03-19 00:40 burgerm * R/exportHTML.r: functions now return invisible; HTML head info updated; result page file names changed; HTML footer added 2007-03-19 00:36 burgerm * man/printHTML.trackinfo.Rd: header added; details text description corrected 2007-03-19 00:34 burgerm * man/tracker.Rd: header added; moved closure functions to new section to avoid R CMD check warning; added isValid 2007-03-16 12:25 burgerm * NEWS: updated for 0.4.15 2007-03-16 12:22 burgerm * DESCRIPTION: patch level 0.4.15 2007-03-16 12:08 burgerm * R/checkFuncs.r: compatibility to R 1.9.0 as declared in DESCRIPTION: removed calls to isTRUE as this was introduced only in R 2.1.0, replaced where needed by identical(TRUE, x) 2007-03-16 12:07 burgerm * R/htmlProtocol.r: createTestFuncRef: removed unnecessary escape characters in gsub call 2007-03-16 12:05 burgerm * tests/runitRUnit.r: compatibility to R 1.9.0 as declared in DESCRIPTION: removed calls to isTRUE as this was introduced only in R 2.1.0, replaced where needed by identical(TRUE, x) 2006-08-30 01:13 burgerm * DESCRIPTION: patch level 0.6.0-0: start new minor level 0.6.0.x for development, first public release will be 0.6.0; 0.5.x is reserved for potential S3 implementation updates; class layout as described in dia 2006-08-30 01:11 burgerm * NAMESPACE: initial commit: class layout as described in dia 2006-08-30 00:58 burgerm * R/TestSuiteTestResultDataMethods.r: initial commit: class layout as described in dia 2006-08-30 00:54 burgerm * R/: ArrayMethods.r, Logger.r, SignalHandler.r, SourceFileTestResultDataMethods.r, TestCaseTestResultDataMethods.r, TestSuiteMethods.r, classUtilities.r, zzz.r: initial commit: class layout as described in dia 2006-08-30 00:51 burgerm * R/: Array.r, SourceFileTestResultData.r, TestCaseTestResultData.r, TestResultData.r, TestSuite.r, TestSuiteTestResultData.r: initial commit: class layout as described in dia 2006-08-22 11:21 burgerm * NEWS: 0.4.14 update log added 2006-08-17 09:12 burgerm * DESCRIPTION: patch level 0.4.14: package utils dependency made explicit in DESCRIPTION and NAMESPACE 2006-08-17 01:14 burgerm * NAMESPACE: import of package utils added (required for R 2.4.0) 2006-08-16 17:39 burgerm * inst/doc/RUnit.Rnw: added a note on the new arguments rngKind, and rngNormalKind; replaced path construction via paste by the more protable file.path (Gregor Gorjanc) 2006-08-16 17:36 burgerm * inst/doc/RUnit.pdf: updated 2006-08-15 18:52 burgerm * ChangeLog: updated 2006-08-15 18:51 burgerm * R/textProtocol.r: return type enforced (a bit more) to be logical 2006-08-15 18:50 burgerm * R/runit.r: defineTestSuite, runTestFile: added arguments rngKind, rngNormalKind to allow configuartion of default RNG configuration, docu tag updated 2006-08-15 18:48 burgerm * R/testLogger.r: documentaion tags added 2006-08-15 18:47 burgerm * R/htmlProtocol.r: return type enforced (a bit more) to be logical, documentation updated 2006-08-15 18:41 burgerm * R/textProtocol.r: documentation updated; printTextProtocol return type changed to logical 2006-08-15 18:34 burgerm * R/html.r: documentation tags updated 2006-08-15 18:33 burgerm * R/exportHTML.r: file I/O: replaced paste call by more protable file.path 2006-08-15 18:31 burgerm * tests/runitRUnit.r: defineTestSuite test case added 2006-08-15 18:29 burgerm * man/runit.Rd: runTestFile, defineTestSuite: documented new arguments rngKind, rngNormalKind 2006-08-15 18:27 burgerm * NEWS: updated: added recent releases 2006-08-15 18:26 burgerm * DESCRIPTION: patch level: 0.4.12 2006-08-08 00:58 burger * R/runit.r: defineTestSuite - allow file extension .R 2006-08-08 00:57 burger * DESCRIPTION: patch level 0.4.12 - allow file extension .R (runit.r) 2006-07-17 19:27 burger * R/00Init.r: package version added to startup message 2006-05-22 10:33 burger * R/checkFuncs.r: DEACTIVATED - fixed typo in var name 2006-04-05 13:47 burger * R/checkFuncs.r: checkException: added comment on contributing author 2006-04-04 19:25 burger * R/TestResultClass.r: License header text added 2006-04-04 19:25 burger * R/TestLogger.r: initial commit 2006-04-04 19:24 burger * R/: TestResultMethods.r, TestSuiteResult.r, TestSuiteResultMethods.r: Lincence header text added 2006-04-04 17:38 burger * NEWS, inst/doc/RUnit.pdf: updated 2006-04-04 17:08 burger * man/inspect.Rd: updated inspect call and argument documentation 2006-04-04 17:08 burger * man/printHTML.trackinfo.Rd: updated inspect call 2006-04-04 17:07 burger * man/tracker.Rd: updated inspect calls 2006-04-04 17:07 burger * inst/doc/RUnit.Rnw: updated inspect calls, added some line to recomendations 2006-04-03 18:35 burger * tests/runitRUnit.r: checkException: silent added to checks 2006-04-03 18:34 burger * R/checkFuncs.r: checkException: argument silent added 2006-04-03 18:32 burger * man/checkFuncs.Rd: checkException, checkEquals documentation updated 2006-04-03 18:31 burger * DESCRIPTION: patch level 0.4.11: checkException arg added 2006-04-03 14:40 burger * inst/examples/runitfoo.r: example function 2006-03-21 15:47 burger * tests/runitRUnit.r: unsuccessful attempt to extend runTestSuite test case 2006-03-21 15:37 burger * R/runit.r: isValidTestSuite: error message texts added 2006-03-21 15:34 burger * R/inspector.r: inspect, and includeTracker have new argument track, which defaults to track for consitency; API docu enhanced and extended 2006-03-21 15:32 burger * tests/runitInspect.r: reactivated both test cases after changes to inspect, and includeTracker 2006-03-21 15:31 burger * DESCRIPTION: patch level 0.4.10 2006-03-07 20:41 burger * R/checkFuncs.r: checkEquals: new compatibility argument to allow to workaround stricter all.equal checks; tolerance precondtion added 2006-03-07 20:39 burger * DESCRIPTION: patch level 0.4.9: checkEquals has new compatibility argument 2006-01-20 18:25 burger * tests/runitRUnit.r: checkIdentical added; checkEquals test case extended 2006-01-20 18:24 burger * man/checkFuncs.Rd: checkIdentical added; arguments a,b, renamed consitent with all.equal 2006-01-20 18:23 burger * R/checkFuncs.r: checkIdentical added; msg argument default added; msg added to stop calls; arguments a,b, renamed consitent with all.equal 2006-01-20 18:21 burger * NAMESPACE: checkIdentical added 2006-01-20 18:18 burger * DESCRIPTION: patch version 0.4.8: checkIdentical added 2006-01-05 15:09 burger * R/checkFuncs.r: checkEqualsNumeric: update to be compatible with R 2.3.0 2006-01-05 15:08 burger * DESCRIPTION: patch level 0.4.7: update for checkEqualsNumeric to be compattible with R 2.3.0 2005-12-12 10:35 burger * DESCRIPTION: patch level 0.4.6 2005-12-12 10:32 burger * R/htmlProtocol.r: replaced HOST query by supposedly platform independent Sys.info variant 2005-12-05 14:44 burger * R/htmlProtocol.r: system info table format changed 2005-12-05 14:44 burger * R/html.r: writeHtmlTable added; API tags updated 2005-11-21 15:29 burger * DESCRIPTION: patch level 0.5.0 added - temporary - dependency on EpiR.base (arrayTemplate class) 2005-11-21 15:28 burger * R/zzz.r: class & method init currently required arrayTemplate and thus relies on EpiR.base FIXME: remove dependency on EpiR.base once design has matured 2005-11-21 15:28 burgerm * R/zzz.r: file zzz.r was added on branch S4-devel-branch-2006-08 on 2006-08-29 22:54:46 +0000 2005-11-21 15:26 burger * R/Logger.r: first exploration 2005-11-21 15:26 burgerm * R/Logger.r: file Logger.r was added on branch S4-devel-branch-2006-08 on 2006-08-29 22:54:46 +0000 2005-11-21 15:15 burger * R/textProtocol.r: added execTime S3 method 2005-11-21 15:15 burger * R/: TestFileResult.r, TestFunctionResult.r, TestResultMethods.r, TestSuiteResult.r, TestFileResultMethods.r, TestSuiteResultMethods.r: first prototype 2005-11-21 15:13 burger * R/TestResultClass.r: first prototype: virtual base class: the mother of all test results 2005-11-14 13:40 burger * DESCRIPTION: patch level 0.4.5 improvements to error detection in runTestSuite & new test cases 2005-11-14 13:39 burger * tests/runitRUnit.r: added isValidTestSuite, runTestFile, and runTestSuite test cases added test case description 2005-11-14 13:37 burger * R/runit.r: runTestSuite: added preconditions runTestFile: pass on error handler flag 2005-11-14 13:36 burger * inst/examples/correctTestCase.r: used for unit test cases 2005-11-14 11:37 burger * R/runit.r: added codestatus API tag runTestSuite: modified error msg 2005-11-14 11:36 burger * R/checkFuncs.r: added codestatus API tag, set to testing 2005-10-27 10:44 burger * .cvsignore: initial commit ignaore eclipse project file 2005-09-29 14:19 burger * DESCRIPTION: changed Klaus email address 2005-09-29 14:16 burger * inst/doc/Makefile: initial commit: utility 2005-08-30 16:28 burger * DESCRIPTION: patch level 0.4.4: changed maintainer 2005-04-07 16:17 burger * tests/runitInspect.r: added & deactivated 2 test cases: environment issues to be addressed by Thomas 2005-04-07 16:04 burger * tests/runitRUnit.r: DEACTIVATED test added 2005-04-07 16:03 burger * R/inspector.r: includeTracker: modifed regexp in grep to comply to R 2.1.0; removed semi-colons; added docu tags 2005-04-07 16:02 burger * R/checkFuncs.r: checkTrue: argument renmaed to match docu: R 2.1.0 CMD check issue 2005-04-07 16:00 burger * R/runit.r: isValidTestSuite: added check on folder existance; docu tags added 2005-04-07 15:59 burger * R/testLogger.r: setDeactivated: added handling of msg with string length > 1; docu extended 2005-04-07 15:41 burger * DESCRIPTION: patch level 0.4.2: fixed R 2.1.0 inspect regexp problem 2005-02-02 13:38 kjuen * R/: htmlProtocol.r, textProtocol.r: the protocol now doesn't mention test files that do not contain any test functions 2005-01-17 19:03 kjuen * R/htmlProtocol.r: minor formatting modifications 2004-12-13 15:34 burger * R/checkFuncs.r: checkTrue: had to add explicit eval to ensure the argument gets evaluated before attempting to set the names attribute to NULL 2004-12-13 14:58 burger * man/checkFuncs.Rd: updated help text for checkTrue 2004-12-13 14:49 burger * R/checkFuncs.r, tests/runitRUnit.r: checkTrue: extended: correct handling of named logical arguments 2004-12-13 14:48 burger * DESCRIPTION: patch level 0.4.2: corrected deficient checkTrue 2004-11-29 19:53 burger * DESCRIPTION: patch level: 0.4.1: http URL mapped 2004-11-29 18:12 kjuen * R/htmlProtocol.r, man/textProtocol.Rd: 'testFileToLinkMap' parameter added to the printHTMLProtocol function added 2004-09-30 15:19 kjuen * inst/doc/: RUnit.Rnw, RUnit.pdf: typos fixed 2004-09-29 18:53 burger * DESCRIPTION: added methods dependency, again 2004-09-29 14:18 kjuen * DESCRIPTION: changes for new release 2004-09-29 14:17 kjuen * inst/doc/RUnit.pdf: hopefully readable for everybody 2004-09-28 11:11 kjuen * R/htmlProtocol.r: deactivated table cells are now printed yellow 2004-09-22 15:27 burger * DESCRIPTION: patch level 0.3.8: RC 0.4.0; removed package splines dependency; added SaveImage directive 2004-09-22 15:24 burger * NAMESPACE: removed splines import 2004-09-22 15:23 burger * R/00Init.r: added .onLoad hook for loading methods prior to RUnit attachment: recommended for R 2.0.0 2004-09-22 15:23 burger * R/inspector.r: removed library calls for methods & splines; added 00Init.r file for this 2004-09-22 15:22 burger * NEWS: replaced tabs; corrected typo, added splines dependency removal 2004-09-22 14:40 kjuen * NEWS: news for release 0.4.0 2004-09-15 15:07 kjuen * NAMESPACE: DEACTIVATED added 2004-09-15 15:07 kjuen * man/: RUnit-internal.Rd, RUnit-intro.Rd, checkFuncs.Rd, inspect.Rd, printHTML.trackinfo.Rd, runit.Rd, textProtocol.Rd, tracker.Rd: some very small cleanups to avoid warnings with R-2 2004-09-09 12:21 kjuen * R/: htmlProtocol.r, testLogger.r, textProtocol.r: .getErrors completely removed 2004-09-08 18:05 burger * DESCRIPTION: updated patch level to 0.3.7 2004-09-08 18:03 burger * NAMESPACE: added getErrors to namespace exports 2004-09-08 18:02 burger * R/testLogger.r: copied .getErrors to getErrors, added to namespace exports, .getErrors set deprecated 2004-09-08 14:20 kjuen * R/htmlProtocol.r: deactivated column in testsuite table is included only when there are any deactivated test functions 2004-09-07 17:28 kjuen * R/: checkFuncs.r, htmlProtocol.r, runit.r, testLogger.r, textProtocol.r: several small cleanups, DEACTIVATED function added 2004-09-06 15:32 burger * inst/NAMESPACE: removed, moved to from inst/ folder 2004-09-06 15:32 burger * NAMESPACE: moved here from inst/ folder as promoted by R 2.0.0 docu 2004-09-06 15:21 burger * R/runit.r: .executeTestCase: added log output: test function call 2004-09-06 15:13 burger * R/textProtocol.r: added braces 2004-08-05 13:34 burger * R/htmlProtocol.r: added braces; added col.names=FALSE to write.table for printing out R version info 2004-07-13 18:16 burger * inst/doc/RUnit.pdf: created via R 1.9.1 buildVignettes("RUnit", "~/src/R/Runit", quiet=FALSE) call, package tools 2004-07-13 16:07 burger * inst/doc/: RUnit.Rnw, RUnit.pdf: set VignetteDepends; commented out LaTeX dependencies; added bibliography with URL for SF RUnit site 2004-07-13 16:04 burger * DESCRIPTION: added SF URL; fixed vignette PDF problem; updated patch level to 0.3.6 2004-06-29 17:17 burger * R/htmlProtocol.r: added colnames(ver) <- "" to avoid warning in R >= 1.9.0 2004-06-10 11:01 burger * R/htmlProtocol.r: added writeCR to writeP function: creates better structured HTML code, relevant also for the internal built script 2004-06-09 20:40 burger * R/html.r: corrected writeEndHead & writeEndHtml: both wrote start instead of end tags 2004-06-08 11:01 burger * DESCRIPTION: updated patch level to 0.3.4, corrected R CMD check warning on man page files 2004-06-08 09:27 burger * man/: RUnit-intro.Rd, RUnit.Rd: renamed RUnit.Rd to RUnit-intro.Rd 2004-06-08 09:24 burger * man/: RUnit.Rd, checkFuncs.Rd, inspect.Rd, printHTML.trackinfo.Rd, runit.Rd, textProtocol.Rd, tracker.Rd: replaced keyword{RUnit} by concept{RUnit}, suggested by Kurt Hornik 2004-06-07 11:41 koenig * inst/doc/RUnit.Rnw: some formatting changes and better descriptions 2004-06-04 22:44 burger * man/checkFuncs.Rd: renamed f to fun 2004-06-04 22:44 burger * DESCRIPTION: updated patch level to 0.3.3, fixed last failing R CMD check tests: ready for CRAN submission 2004-06-04 22:33 burger * man/tracker.Rd: only example text format changed 2004-06-04 22:32 burger * man/printHTML.trackinfo.Rd: updated to new argument name 2004-06-04 22:31 burger * R/exportHTML.r: fixed error introduced with the check on the successful directory creation: renamed res input argument to trackInfo 2004-06-04 20:16 burger * R/htmlProtocol.r: added R version output at end of HTML page 2004-06-03 17:41 burger * R/exportHTML.r: added preconditions and handling of the case the directory results exists already 2004-06-03 17:40 burger * man/tracker.Rd: modified useage to be consistent with required default name for tracker object 2004-06-03 17:32 burger * man/inspect.Rd: added seealso section 2004-06-03 17:32 burger * man/tracker.Rd: added sealso section, clarified comments, added one sentence to description 2004-06-03 16:54 burger * R/inspector.r: added methods dependency 2004-06-03 16:50 burger * man/RUnit-internal.Rd: added writeBeginHtml & writeEndHtml 2004-06-03 16:37 burger * INDEX: initial commit 2004-06-03 16:35 burger * inst/doc/00Index.dcf: updated 2004-06-03 16:05 kjuen * inst/examples/runitc2f.r: example extended 2004-06-03 16:05 kjuen * man/runit.Rd: small improvements 2004-06-03 15:30 kjuen * man/runit.Rd: random number generator behaviour documented 2004-06-03 15:15 kjuen * inst/doc/RUnit.Rnw: junk character that made texi2dvi fail removed 2004-06-03 15:13 kjuen * man/: runit.Rd, textProtocol.Rd: print and summary documentation improved 2004-06-03 13:59 burger * tests/runitRUnit.r: added test cases 2004-06-03 13:47 burger * tests/: runitHTMLProtocol.r, runitTextProtocol.r: initial commit, simple test cases, currently only checked if all input arguments are checked correctly 2004-06-02 18:53 burger * R/: htmlProtocol.r, textProtocol.r: added precondition checks 2004-06-02 18:51 burger * DESCRIPTION: updated patch level to 0.3.2: updated Seawve file, added preconditon checks 2004-06-02 18:40 burger * inst/doc/RUnit.Rnw: corrected errors in R code which incurred R CMD check error 2004-06-01 18:11 burger * inst/examples/runitc2f.r: initial commit, simple example test case, required for correct automatic Sweave translation 2004-05-27 11:37 koenig * R/runit.r: random generator set to kind=Marsaglia-Multicarry, normal.kind=Kinderman-Ramage (default before R-1.8.1 2004-05-25 11:07 kjuen * inst/NAMESPACE: exports of generic print and summary functions corrected 2004-05-25 11:07 kjuen * R/textProtocol.r: type fixed in summary function 2004-05-25 11:06 kjuen * R/runit.r: code that attempts to clean up the global environment after the test runs removed, because it did not work with R-1.9 (because of some namespace stuff that I do not understand) 2004-05-25 11:04 kjuen * R/htmlProtocol.r: junk code removed 2004-05-19 21:08 burger * DESCRIPTION: updated patch level to 0.3.1, version presented at useR 2004-05-19 14:42 koenig * inst/doc/RUnit.Rnw: E-Mail adresses changed 2004-05-19 14:39 koenig * inst/doc/RUnit.Rnw: company name and E-Mail added 2004-05-19 14:12 kjuen * inst/doc/RUnit.Rnw: some details improved and section that explains test case execution added 2004-05-19 12:48 kjuen * R/testLogger.r: debug print statement removed 2004-05-19 11:26 burger * inst/doc/RUnit.Rnw: added library(RUnit) before example code 2004-05-19 11:24 burger * man/RUnit-internal.Rd: added newline 2004-05-19 11:24 burger * R/exportHTML.r: added API doc tags, code polish for better readability 2004-05-19 11:12 burger * man/RUnit.Rd: updated, added links, removed unused tags 2004-05-19 11:09 burger * man/: tracker.Rd, printHTML.trackinfo.Rd: added CVS header 2004-05-19 11:08 burger * man/: tracker.Rd, inspect.Rd: minor text changes 2004-05-19 11:07 burger * man/printHTML.trackinfo.Rd: fixed spelling of function name 2004-05-19 11:04 burger * man/RUnit-internal.Rd: initial commit, list all private functions not covered in the docs 2004-05-19 09:46 koenig * man/: inspect.Rd, printHTML.trackinfo.Rd, tracker.Rd: initial release for the documentation of the inspector 2004-05-18 23:17 burger * inst/doc/RUnit.pdf: updated 2004-05-18 23:17 burger * inst/doc/RUnit.Rnw: corrected missing end closure 2004-05-18 21:46 burger * man/: runit.Rd, textProtocol.Rd: typo corrections 2004-05-18 21:45 burger * inst/doc/RUnit.Rnw: removed duplicate abstract, typo corrections 2004-05-18 19:58 burger * inst/NAMESPACE: removed Copyright notice, removed class &method export directive 2004-05-18 19:49 burger * man/: RUnit.Rd, checkFuncs.Rd, runit.Rd: added newline in lats line: suggested by R CMD check 2004-05-18 19:47 burger * DESCRIPTION: removed 2nd maintainer entry: not allowed by R CMD check 2004-05-18 19:02 burger * DESCRIPTION: added splines dependency, added poster, added more Rd files 2004-05-18 16:58 burger * inst/doc/RUnit.Rnw: added some lines to Motivation, added Future Ideas 2004-05-18 16:34 koenig * inst/doc/RUnit.Rnw: enhanced for the code inspector 2004-05-18 13:24 kjuen * R/runit.r: sanity check of looking for a 'runit' call in a test function removed because it is not necessary anymore 2004-05-18 13:22 kjuen * R/testLogger.r: a dot prepended to getError and newTestLogger to mark them as internal functions 2004-05-18 13:21 kjuen * R/runit.r: bugfix in .executeTestCase: setUp and tearDown are now checked for errors. typo fixed in the code that copes with errors occuring while sourcing a test file 2004-05-18 13:18 kjuen * R/: htmlProtocol.r, textProtocol.r: trace back writing improved 2004-05-18 13:16 kjuen * man/textProtocol.Rd: documentation of printHTMLProtocol added 2004-05-18 13:15 kjuen * inst/doc/RUnit.Rnw: my version of the introduction added 2004-05-17 19:54 burger * inst/doc/RUnit.Rnw: added abstract 2004-05-17 19:53 burger * inst/doc/RUnit.pdf: initial commit 2004-05-17 15:40 kjuen * R/htmlProtocol.r: some minor modifications 2004-05-17 15:40 kjuen * R/runit.r: default test file regexp in defineTestSuite improved 2004-05-17 15:16 burger * R/: exportHTML.r, html.r, inspector.r: added GPL preamble 2004-05-14 18:38 kjuen * R/textProtocol.r: useless code deleted 2004-05-14 18:38 kjuen * R/runit.r: small bugfix in isValidTestSuite 2004-05-14 18:37 kjuen * R/htmlProtocol.r: first usable version 2004-05-14 18:29 kjuen * R/html.r: small improvement of writeBeginTable 2004-05-11 15:34 koenig * R/exportHTML.r: moved html helper function to html.r 2004-05-11 15:33 koenig * R/html.r: initial release. helper function for generating html pages 2004-05-10 23:09 burger * R/: checkFuncs.r, runit.r, testLogger.r, textProtocol.r: added CVS tag 2004-05-10 22:58 burger * R/runit.r: renamed argument to isValid to testSuite; ts is a time series object 2004-05-10 22:57 burger * R/textProtocol.r: added API documentation tags; renamed arguments to required defaults for print & summary methods 2004-05-10 22:56 burger * man/runit.Rd: added authors and keyword paragraphs; changed ts to testSuite argument name and documented it 2004-05-10 22:54 burger * man/textProtocol.Rd: added authors and keyword paragraphs; added ... argument description 2004-05-10 22:52 burger * man/checkFuncs.Rd: added authors and keyword paragraphs 2004-05-10 22:22 burger * inst/doc/RUnit.Rnw: fixed broken LaTeX code: missing begin environment 2004-05-10 22:21 burger * inst/doc/00Index.dcf: initial commit, required by R CMD check; needs to be updated with this directories contents 2004-05-10 22:07 burger * DESCRIPTION: updated to minor level 0.2.0: CodeInspector code added, documentation pages added; package passes R CMD check 2004-05-10 22:05 burger * man/: checkFuncs.Rd, runit.Rd, textProtocol.Rd: changed examples such that no Errors are thrown, and packages passes R CMD check 2004-05-10 18:51 kjuen * R/: 00Init.r, initGeneratedRUnit.r, utilities.r: removed 2004-05-10 18:49 koenig * R/inspector.r: initial release for tracking tool 2004-05-10 18:48 koenig * R/exportHTML.r: initial release for exporting results to HTML pages 2004-05-10 16:52 kjuen * man/: checkFuncs.Rd, runit.Rd, textProtocol.Rd: first attempt to cope with Rs documentation tool 2004-05-10 16:51 kjuen * R/runit.r: test suite objects now have a class attribute 2004-05-10 16:50 kjuen * R/testProtocol.r: renamed to textProtocol 2004-05-10 16:50 kjuen * R/textProtocol.r: renamed from testProtocol 2004-05-07 17:51 kjuen * R/checkFuncs.r: ... added to checkEquals so that further args can be passed to all.equal 2004-05-07 15:45 kjuen * R/testProtocol.r: S3 generic methods 'print' and 'summary' added 2004-05-07 15:44 kjuen * R/testLogger.r: getErrors improved 2004-05-06 20:46 burger * DESCRIPTION: added GPL 2 licence text, some code improvements 2004-05-06 20:43 burger * COPYING: GPL 2, downloaded from http://www.gnu.org/copyleft/gpl.html 2004-05-06 19:39 kjuen * R/testProtocol.r: license header added, optional args added to configure printTextProtocol 2004-05-06 19:38 kjuen * R/testLogger.r: license header added, traceback removed for Failures 2004-05-06 19:37 kjuen * R/runit.r: license header added, file regexp in runTestFile improved 2004-05-06 19:37 kjuen * R/checkFuncs.r: license header added 2004-05-05 21:35 burger * R/runit.r: changed to default set in R >= 1.8.0 2004-05-05 20:08 burger * DESCRIPTION: updated version to 0.1.0, runit test framework from EpiR.tools revised and commited here 2004-05-05 19:30 kjuen * R/: checkFuncs.r, runit.r, testLogger.r, testProtocol.r: initial check in of rewritten version 2004-04-06 11:27 burger * DESCRIPTION, R/00Init.r, R/initGeneratedRUnit.r, R/utilities.r, inst/NAMESPACE, inst/doc/RUnit.Rnw, man/RUnit.Rd, tests/runitRUnit.r: Initial revision 2004-04-06 11:27 burger * DESCRIPTION, R/00Init.r, R/initGeneratedRUnit.r, R/utilities.r, inst/NAMESPACE, inst/doc/RUnit.Rnw, man/RUnit.Rd, tests/runitRUnit.r: initial import: preparation for public CRAN package, will take on most of EpiR.tools functionality RUnit/.Rinstignore0000644000176200001440000000002214561532231013613 0ustar liggesusersinst/doc/Makefile RUnit/README.md0000644000176200001440000000433614563457515012617 0ustar liggesusers# RUnit [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/RUnit)](https://CRAN.R-project.org/package=RUnit) [![CRAN Downloads](https://cranlogs.r-pkg.org/badges/RUnit)](https://CRAN.R-project.org/package=RUnit) RUnit is a testing package for R code, inspired by the xUnit family of testing tools. Originally implemented by Thomas Koenig, Klaus Juenemann, and Matthias Burger, this package has served the R community for over a decade. Since RUnit is no longer actively developed, I provide maintenance of this package mostly to support older projects that still rely on RUnit. # Using RUnit To make RUnit work with `R CMD check`, create a following file in the `tests` subdirectory. This would run the actual tests stored in the packages `inst/tests` subdirectory. # Our package. Used for the test suite name pkgname <- "your package name" require(pkgname, quietly=TRUE, character.only=TRUE) || stop("package '", pkgname, "' not found") # How to determine which files to load (have to start with test_ and end with .R) pattern <- "^test_.*\\.R$" # Which functions to run. Have to start with 'test.' testFunctionRegexp = "^test.+" # Path to the unit tests folder in the package (assuming "inst/tests" here) dir <- system.file("tests", package=pkgname) # Define RUnit test suite suite <- defineTestSuite(name=paste(pkgname, "RUnit Tests"), dirs=dir, testFileRegexp=pattern, testFuncRegexp = testFunctionRegexp, rngKind="default", rngNormalKind="default") # Run tests result <- runTestSuite(suite) # Display result tests on the console printTextProtocol(result) # Write results in JUnit-like xml format printJUnitProtocol(result, fileName="junit.xml") A typical unit test would then live for example in `inst/tests/test.operations.R`. Each such file can contain multiple functions like so: test.additionWorks <- function() { checkEquals(2, 1+1, "one plus one must be two") } test.divisionByZeroFails <- function() { checkException(1/0, "division by zero is expected to fail") } RUnit/man/0000755000176200001440000000000014563457515012105 5ustar liggesusersRUnit/man/checkFuncs.Rd0000644000176200001440000001606014563664702014451 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{utf8} \name{checkFuncs} \alias{checkEquals} \alias{checkEqualsNumeric} \alias{checkIdentical} \alias{checkTrue} \alias{checkException} \alias{DEACTIVATED} \title{RUnit check functions} \usage{ checkEquals(target, current, msg, tolerance = .Machine$double.eps^0.5, checkNames = TRUE, ...) checkEqualsNumeric(target, current, msg, tolerance = .Machine$double.eps^0.5, ...) checkIdentical(target, current, msg) checkTrue(expr, msg) checkException(expr, msg, silent = getOption("RUnit")$silent) DEACTIVATED(msg) } \arguments{ \item{current, target}{objects to be compared (\code{checkEqualsNumeric} cannot handle S4 class objects).} \item{msg}{an optional message to document a check and to facilitate the identification of a possible failure. The message only appears as text in the test protocol, it is not further used in any of the check functions.} \item{tolerance}{numeric >= 0. A numeric check does not fail if differences are smaller than `tolerance'.} \item{checkNames}{flag, if \code{FALSE} the names attributes are set to \code{NULL} for both current and target before performing the check.} \item{expr}{syntactically valid R expression which can be evaluated and must return a logical scalar (\code{TRUE}|\code{FALSE}). A named expression is also allowed but the name is disregarded.} \item{silent}{flag passed on to \code{try}, which determines if the error message generated by the checked function is displayed. Queried from global options set for RUnit at package load.} \item{...}{optional arguments passed to \code{all.equal} or \code{all.equal.numeric}} } \description{A set of functions used to check the results of some test calculation. If these functions are called within the RUnit framework, the results of the checks are stored and reported in the test protocol. \code{checkEquals} compares two R objects by invoking \code{all.equal} on the two objects. If the objects are not equal an error is generated and the failure is reported to the test logger such that it appears in the test protocol. \code{checkEqualsNumeric} works just like \code{checkEquals} except that it invokes \code{all.equal.numeric} instead of \code{all.equal} \code{checkIdentical} is a convenience wrapper around identical using the error logging mechanism of RUnit. \code{checkTrue} uses the function \code{identical} to check if the expression provided as first argument evaluates to \code{TRUE}. If not, an error is generated and the failure is reported to the test logger such that it appears in the test protocol. \code{checkException} evaluates the passed expression and uses the \code{try} mechanism to check if the evaluation generates an error. If it does the test is OK. Otherwise an error is generated and the failure is reported to the test logger such that it appears in the test protocol. \code{DEACTIVATED} interrupts the test function and reports the test case as deactivated. In the test protocol deactivated test functions are listed separately. Test case deactivation can be useful in the case of major refactoring. Alternatively, test cases can be commented out completely but then it is easy to forget the test case altogether. } \details{ The check functions are direct equivalents of the various methods of the class junit.framework.Assert of Javas JUnit framework which served as basis for the RUnit package. For functions defined inside a package equipped with a namespace only exported functions can be accessed inside test cases directly. For functions not exported the only way to test them is to use the '\code{\link[base:ns-dblcolon]{:::}}' operator combined with the package name as a prefix. Special care is required if test cases are written for S4 classes and methods. If a new class is defined inside a test case via a \code{\link[methods]{setClass}} call the class is added to the global class cache and thus available outside the test case. It will persist until explicitly removed via a \code{\link[methods:findClass]{removeClass}} call. Same applies for new method and generic definitions. Be sure to remove methods and classes in each test case they are defined after the checks have been performed. This is an advise gained from the cumbersome experience: not doing so leads to difficult to pin down error causes incurred from previously executed test cases. For a simple example see the provided test cases in \Sexpr{file.path(system.file("examples", package="RUnit"), "runitVirtualClassTest.r")}. } \author{ Thomas \enc{König}{Koenig}, Klaus \enc{Jünemann}{Juenemann} \ifelse{html}{\out{&}}{&} Matthias Burger} \seealso{ \code{\link{all.equal}}, \code{\link{all.equal.numeric}} and \code{\link{identical}} are the underlying comparison functions. \code{\link{try}} is used for error catching. \code{\link{.setUp}} for details on test case setup. See \link{RUnit-options} for global options controlling log out. } \examples{ checkTrue(1 < 2, "check1") ## passes fine ## checkTrue(1 > 2, "check2") ## appears as failure in the test protocol v <- 1:3 w <- 1:3 checkEquals(v, w) ## passes fine names(v) <- c("A", "B", "C") ## checkEquals(v, w) ## fails because v and w have different names checkEqualsNumeric(v, w) ## passes fine because names are ignored x <- rep(1:12, 2) y <- rep(0:1, 12) res <- list(a=1:3, b=letters, LM=lm(y ~ x)) res2 <- list(a=seq(1,3,by=1), b=letters, LM=lm(y ~ x)) checkEquals( res, res2) ## passes fine checkIdentical( res, res) checkIdentical( res2, res2) ## checkIdentical( res, res2) ## fails because element 'a' differs in type fun <- function(x) { if(x) { stop("stop conditions signaled") } return() } checkException(fun(TRUE)) ## passes fine ## checkException(fun(FALSE)) ## failure, because fun raises no error checkException(fun(TRUE), silent=TRUE) ## special constants ## same behaviour as for underlying base functions checkEquals(NA, NA) checkEquals(NaN, NaN) checkEquals(Inf, Inf) checkIdentical(NA, NA) checkIdentical(NaN, NaN) checkIdentical(-Inf, -Inf) ## DEACTIVATED("here one can document on the reason for deactivation") } \keyword{programming} \concept{RUnit} RUnit/man/textProtocol.Rd0000644000176200001440000001542214563756551015110 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{utf8} \name{textProtocol} \alias{printTextProtocol} \alias{printHTMLProtocol} \alias{printJUnitProtocol} \alias{print.RUnitTestData} \alias{summary.RUnitTestData} \alias{getErrors} \title{Printing a plain text, HTML or JUnit-like XML version of an RUnit test run protocol.} \usage{ printTextProtocol(testData, fileName = "", separateFailureList = TRUE, showDetails = TRUE, traceBackCutOff = 9) printHTMLProtocol(testData, fileName = "", separateFailureList = TRUE, traceBackCutOff = 9, testFileToLinkMap = function(x) x ) printJUnitProtocol(testData, fileName = "") \method{print}{RUnitTestData}(x, ...) \method{summary}{RUnitTestData}(object, ...) getErrors(testData) } \arguments{ \item{testData, x, object}{objects of class \code{RUnitTestData}, typically obtained as return value of a test run.} \item{fileName}{Connection where to print the text protocol (printing is done by the \code{cat} command).} \item{separateFailureList}{If \code{TRUE} a separate list of failures and errors is produced at the top of the protocol. Otherwise, the failures and errors are only listed in the details section.} \item{showDetails}{If \code{TRUE} the protocol contains a detailed listing of all executed test functions.} \item{traceBackCutOff}{The details section of the test protocol contains the call stack for all errors. The first few entries of the complete stack typically contain the internal RUnit function calls that execute the test cases and are irrelevant for debugging. This argument specifies how many calls are removed from the stack before it is written to the protocol. The default value is chosen such that all uninteresting RUnit calls are removed from the stack if \code{runTestSuite} has been called from the console. This argument takes effect only if \code{showDetails=TRUE}.} \item{testFileToLinkMap}{This function can be used to map the full name of the test file to a corresponding html link to be used in the html protocol. By default, this is the identity map. See example below.} \item{...}{additional arguments to summary are passed on to the printTextProtocol() call.} } \description{ \code{printTextProtocol} prints a plain text protocol of a test run. The resulting test protocol can be configured through the function arguments. \code{printHTMLProtocol} prints an HTML protocol of a test run. For long outputs this version of the test protocol is slightly more readable than the plain text version due to links in the document. The resulting test protocol can be configured through the function arguments. \code{printJUnitProtocol} prints a JUnit-style XML protocol of a test run. This feature is especially useful when running your RUnit tests through a continuous integration server that understands JUnit (like Jenkins). This machine-parseable output allows you to track errors over time, sort by execution time and similar useful tricks. To take advantage of this function, you have to have the XML package installed. \code{print} prints the number of executed test functions and the number of failures and errors. \code{summary} directly delegates the work to \code{printTextProtocol}. \code{getErrors} returns a list containing the number of test functions, the number of deactivated functions (if there are any), the number of errors and the number of failures. } \details{ The text protocol can roughly be divided into three sections with an increasing amount of information. The first section as an overview just reports the number of executed test functions and the number of failures and errors. The second section describes all test suites. Optionally, all errors and failures that occurred in some test suite are listed. In the optional third section details are given about all executed test functions in the order they were processed. For each test file all test functions executed are listed in the order they were executed. After the test function name the number of \code{check<*>} function calls inside the test case and the execution time in seconds are stated. In the case of an error or failure as much debug information as possible is provided. } \author{ Thomas \enc{König}{Koenig}, Klaus \enc{Jünemann}{Juenemann}, Matthias Burger \ifelse{html}{\out{&}}{&} Roman Zenka} \seealso{ \code{\link{runTestSuite}} } \examples{ ## run some test suite myTestSuite <- defineTestSuite("RUnit Example", system.file("examples", package = "RUnit"), testFileRegexp = "correctTestCase.r") testResult <- runTestSuite(myTestSuite) ## prints detailed text protocol ## to standard out: printTextProtocol(testResult, showDetails = TRUE) ## prints detailed html protocol ## to standard out printHTMLProtocol(testResult) ## prints JUnit-style XML protocol ## to standard out. ## You need to have XML package installed for this if(require("XML")) { printJUnitProtocol(testResult) } \dontrun{ ## example function to add links to URL of the code files in a code ## repository, here the SourceForge repository testFileToSFLinkMap <- function(testFileName, testDir = "tests") { ## get unit test file name bname <- basename(testFileName) ## figure out package name regExp <- paste("^.*/([\\.a-zA-Z0-9]*)/", testDir,"/.*$", sep = "") pack <- sub(regExp, "\\1", testFileName) return(paste("http://runit.cvs.sourceforge.net/runit/", pack, testDir, bname, sep = "/")) } ## example call for a test suite run on the RUnit package testSuite <- defineTestSuite("RUnit", "/RUnit/tests", testFileRegexp = "^test.+") testResult <- runTestSuite(testSuite) printHTMLProtocol(testResult, fileName = "RUnit-unit-test-log.html", testFileToLinkMap = testFileToSFLinkMap ) } } \keyword{programming} \concept{RUnit} RUnit/man/inspect.Rd0000644000176200001440000000512514563756530014043 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{utf8} \name{inspect} \alias{inspect} \title{Track the executed code lines of a function or method.} \usage{ inspect(expr, track = track) } \arguments{ \item{expr}{Any R function or method call.} \item{track}{list object, as returned by a call to \code{tracker}.} } \description{ \code{inspect} examines and modifies the source code of a function or method. After the modification of the source code, the modified function will be executed and the result of the tracking process will be stored. To store the information a \code{tracker} environment with the name track must exist. Note, that not all R code constructs can be handled at the current state. In some cases it is not possible to track a specific code line. Therefore, clearly structured code with consequent use of opening and closing braces to indicate conditional expressions can prevent these parser problems. } \details{The return value of \code{inspect} is the result returned by the function executed. If the function has no return value nothing is returned either.} \author{ Thomas \enc{König}{Koenig}, Klaus \enc{Jünemann}{Juenemann} \ifelse{html}{\out{&}}{&} Matthias Burger} \seealso{ \code{\link{tracker}} for the call tracking object, and \code{\link{printHTML}} for displaying results. } \examples{ ## example function foo <- function(x){ y <- 0 for(i in 1:100) { y <- y + i } return(y) } ## the name track is necessary track <- tracker() ## initialize the tracker track$init() ## inspect the function ## res will collect the result of calling foo res <- inspect(foo(10), track = track) ## get the tracked function call info resTrack <- track$getTrackInfo() ## create HTML sites printHTML(resTrack, baseDir=tempdir()) } \keyword{programming} \concept{RUnit} RUnit/man/printHTML.Rd0000644000176200001440000000521514563756537014226 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{utf8} \name{printHTML} \alias{printHTML} \title{Write HTML pages of the tracking result.} \usage{ printHTML(object, baseDir = ".") } \arguments{ \item{object}{'trackInfo' S3 class object (list), containing the result of the function \code{tracker}.} \item{baseDir}{A character string, specifying the base directory for the HTML pages to be written to. Defaults to the current working directory.} } \description{ \code{printHTML} creates a subdirectory named "result" in the base directory specified via \code{baseDir}. All HTML pages and images will be put in that directory. } \details{An "index.html" page will be created in the directory "results" which is the root entry page of the HTML pages. The displayed result for every tracked function consists of two HTML pages. The first page is an overview on how often every line of code was executed. Code lines not executed are highlighted red, executed lines are shown in green. The second page is a graph representation of the execution flow of the function. Each code line has a edge pointing to the next code line that is executed subsequently. Thus loops and jumps become clearly visible. } \author{ Thomas \enc{König}{Koenig}, Klaus \enc{Jünemann}{Juenemann} \ifelse{html}{\out{&}}{&} Matthias Burger} \seealso{ \code{\link{tracker}} for the call tracking object definition. } \examples{ ## example function foo <- function(x){ y <- 0 for(i in 1:100) { y <- y + i } return(y) } ## the name track is necessary track <- tracker() ## initialize the tracker track$init() ## inspect the function ## res is the result of foo res <- inspect(foo(10), track = track) ## get the tracking info resTrack <- track$getTrackInfo() ## create HTML pages printHTML(resTrack, baseDir=tempdir()) } \keyword{programming} \concept{RUnit} RUnit/man/testCaseSetUp.Rd0000644000176200001440000000275314563756534015142 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{utf8} \name{.setUp} \alias{.setUp} \alias{.tearDown} \title{Definition of RUnit Test Case code files.} \usage{ .setUp() .tearDown() } \description{ Either one or both functions have to be provided by the test case author, take precedence over the dummy definitions provided by the RUnit package and are called once for every test case identified. } \details{ To be written ... } \value{ Functions do not return a value; called for their side effects. } \author{ Thomas \enc{König}{Koenig}, Klaus \enc{Jünemann}{Juenemann} \ifelse{html}{\out{&}}{&} Matthias Burger} \seealso{ \code{\link{runTestFile}}. } \keyword{programming} \concept{RUnit} RUnit/man/RUnit-intro.Rd0000644000176200001440000000362414563665057014575 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{utf8} \name{RUnit} \alias{RUnit} \title{RUnit - Package Description} \description{ This package models the common Unit Test framework for R and provides functionality to track results of test case execution and generate a summary report. It also provides tools for code inspection and thus for test case coverage analysis. The design is inspired by the popular JUnit unit test framework. This package comes with a set of unit tests, serving as a test battery to check correct functioning against new R versions released as well as practical examples for writing test cases (see the \file{inst/unitTests} subdirectory of the source package, or \file{unitTests} contained in the binary package version). } \references{RUnit - A Unit Test Framework for R. useR! 2004 Vienna} \author{ Thomas \enc{König}{Koenig}, Klaus \enc{Jünemann}{Juenemann} \ifelse{html}{\out{&}}{&} Matthias Burger} \seealso{ See \code{\link{defineTestSuite}}, \code{\link{runTestSuite}} for unit testing or \code{\link{inspect}} and \code{\link{tracker}} for code inspection. } \keyword{programming} \concept{RUnit} RUnit/man/tracker.Rd0000644000176200001440000000735314563756550014040 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{utf8} \name{tracker} \alias{tracker} \title{Tracking the results of the inspect process.} \usage{tracker() } \description{ The current implementation uses the 'closure trick' to hide all details from the user and only allows to retrieve the results of the code inspection. \code{tracker} is used to create a new environment to manage and store the results of the tracking process. The \code{inspect} function requires such an environment with the name "track" (currently mandatory). The tracker records how often each and every function was called by \code{inspect} and summarizes the results of all calls. \code{tracker$init} initializes the tracker environment. \code{tracker$getTrackInfo} returns a list with the tracked results of the inspection process. } \section{Methods}{ \tabular{rll}{ \tab \code{init} \tab initializes the tracker environment\cr \tab \code{addFunc} \tab add function to the inspect tracking list (internal use)\cr \tab \code{getSource} \tab return the modified source code used for during inspection the specified index (internal use)\cr \tab \code{bp} \tab update tracking info for specified function index (internal use)\cr \tab \code{getTrackInfo} \tab return 'trackInfo' object\cr \tab \code{isValid} \tab check 'trackInfo' object for conformance to class contract \cr } } \details{The 'trackInfo' S3 class object (list) has one entry for each function on the inspect list with the following elements: \describe{ \item{src}{The source code of the function.} \item{run}{The number of executions for each line of code.} \item{graph}{A matrix. Each element in the matrix counts how often a code line was called from the previous code line in the execution flow.} \item{nrRuns}{Counts how often the function was called.} \item{funcCall}{The declaration of the function.} } } \author{ Thomas \enc{König}{Koenig}, Klaus \enc{Jünemann}{Juenemann} \ifelse{html}{\out{&}}{&} Matthias Burger} \seealso{ \code{\link{inspect}} for the registration of functions & methods to be on the tracking list, and \code{\link{printHTML}} for displaying results } \examples{ ## example functions foo <- function(x){ y <- 0 for(i in 1:100) { y <- y + i } return(y) } bar <- function(x){ y <- 0 for(i in 1:100) { y <- y - i } return(y) } ## the object name track is 'fixed' (current implementation) track <- tracker() ## initialize the tracker track$init() ## inspect the function ## resFoo1 will contain the result of calling foo(50) resFoo1 <- inspect(foo(50), track = track) resFoo2 <- inspect(foo(20), track = track) resBar1 <- inspect(bar(30), track = track) ## get the tracked function call info for all inspect calls resTrack <- track$getTrackInfo() ## create HTML sites in folder /results for all inspect calls printHTML(resTrack, baseDir=tempdir()) } \keyword{programming} \concept{RUnit} RUnit/man/RUnit-internal.Rd0000644000176200001440000000277513267374743015263 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \name{RUnit-internal} \alias{includeTracker} \alias{plotConnection.trackInfo} \alias{writeBeginBody} \alias{writeBeginHead} \alias{writeBeginHtml} \alias{writeBeginTag} \alias{writeBeginTable} \alias{writeCR} \alias{writeEndBody} \alias{writeEndHead} \alias{writeEndHtml} \alias{writeEndTable} \alias{writeEndTag} \alias{writeHtmlEnd} \alias{writeHtmlHeader} \alias{writeHtmlSection} \alias{writeHtmlSep} \alias{writeImage} \alias{writeLi} \alias{writeLinkRef} \alias{writeLink} \alias{writeP} \alias{writeRaw} \alias{writeRawCR} \alias{writeTableRow} \alias{writeTitle} \title{Internal functions} \description{ Helper functions and private methods not intended for direct use. } \keyword{internal} RUnit/man/runit.Rd0000644000176200001440000002336314563756525013547 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{utf8} \name{runTestSuite} \alias{runTestSuite} \concept{test suite} \alias{runTestFile} \concept{test runner} \alias{defineTestSuite} \alias{isValidTestSuite} \title{Definition and execution of RUnit test suites.} \usage{ defineTestSuite(name, dirs, testFileRegexp = "^runit.+\\\\.[rR]$", testFuncRegexp = "^test.+", rngKind = "Marsaglia-Multicarry", rngNormalKind = "Kinderman-Ramage") isValidTestSuite(testSuite) runTestSuite(testSuites, useOwnErrorHandler = TRUE, verbose = getOption("RUnit")$verbose, gcBeforeTest = FALSE) runTestFile(absFileName, useOwnErrorHandler = TRUE, testFuncRegexp = "^test.+", rngKind = "Marsaglia-Multicarry", rngNormalKind = "Kinderman-Ramage", verbose = getOption("RUnit")$verbose, gcBeforeTest = FALSE) } \arguments{ \item{name}{The name of the test suite.} \item{dirs}{Vector of absolute directory names where to look for test files.} \item{testFileRegexp}{Regular expression for matching test files.} \item{testFuncRegexp}{Regular expression for matching test functions.} \item{rngKind}{name of an available RNG (see \code{\link[base:Random]{RNGkind}} for possible options).} \item{rngNormalKind}{name of a valid rnorm RNG version (see \code{\link[base:Random]{RNGkind}} for possible options).} \item{testSuite}{A single object of class test suite.} \item{testSuites}{A single object of class test suite or a list of test suite objects.} \item{useOwnErrorHandler}{If \code{TRUE} the RUnit framework installs its own error handler during test case execution (but reinstalls the original handler before it returns). If \code{FALSE} the error handler is not touched by RUnit but then the test protocol does not contain any call stacks in the case of errors.} \item{verbose}{level of verbosity of output log messages, 0: omits begin/end comments for each test function. Queried from global options set for RUnit at package load.} \item{absFileName}{Absolute file name of a test function.} \item{gcBeforeTest}{Run garbage collector before executing a test for more precise test timing. Enabling this option makes the tests run longer, especially when testing many small tests. By default GC is disabled (since 0.4.32).} } \description{ \code{runTestSuite} is the central function of the RUnit package. Given one or more test suites it identifies and sources specified test code files one after another and executes all specified test functions defined therein. This is done sequentially for suites, test code files and test functions. During the execution information about the test function calls including the possible occurrence of failures or errors is recorded and returned at the end of the test run. The return object can then be used to create a test protocol of various formats. \code{runTestFile} is just a convenience function for executing the tests in a single test file. \code{defineTestSuite} is a helper function to define a test suite. See below for a precise definition of a test suite. \code{isValidTestSuite} checks if an object defines a valid test suite. } \details{ The basic idea of the RUnit test framework is to declare a certain set of functions to be test functions and report the results of their execution. The test functions must not take any parameter nor return anything such that their execution can be automatised. The specification which functions are taken as test functions is contained in an object of class \code{RUnitTestSuite} which is a list with the following elements. \describe{ \item{name}{A simple character string. The name of a test suite is mainly used to create a well structure test protocol.} \item{dirs}{A character vector containing the absolute names of all directories where to look for test files.} \item{testFileRegexp}{A regular expression specifying the test files. All files in the test directories whose names match this regular expression are taken as test files. Order of file names will be alphabetical but depending on the used locale.} \item{testFuncRegexp}{A regular expression specifying the test functions. All functions defined in the test files whose names match this regular expression are used as test functions. Order of test functions will be alphabetical.} } After the RUnit framework has sequentially executed all test suites it returns all data collected during the test run as an object of class \code{RUnitTestData}. This is a (deeply nested) list with one list element for each executed test suite. Each of these executed test suite lists contains the following elements: \describe{ \item{nTestFunc}{The number of test functions executed in the test suite.} \item{nErr}{The number of errors that occurred during the execution.} \item{nFail}{The number of failures that occurred during the execution.} \item{dirs}{The test directories of the test suite.} \item{testFileRegexp}{The regular expression for identifying the test files of the test suite.} \item{testFuncRegexp}{The regular expression for identifying the test functions of the test suite.} \item{sourceFileResults}{A list containing the results for each separate test file of the test suite.} } The \code{sourceFileResults} list just mentioned contains one element for each specified test function in the source file. This element is a list with the following entries: \describe{ \item{kind}{Character string with one of \code{success}, \code{error} or \code{failure} describing the outcome of the test function.} \item{msg}{the error message in case of an error or failure and \code{NULL} for a successfully executed test function.} \item{time}{The duration (measured in seconds) of the successful execution of a test function and \code{NULL} in the case of an error or failure. When running with \code{gcBeforeTest} option set to \code{FALSE} (default since 0.4.32), the timing of the tests might be misleading when garbage collector has to reclaim memory allocated by a previous test.} \item{traceBack}{The full trace back as a character vector in the case of an error and \code{NULL} otherwise.} } To further control test case execution it is possible to define two parameterless function \code{.setUp} and \code{\link{.tearDown}} in each test file. \code{.setUp()} is executed directly before and \code{.tearDown()} directly after each test function execution. Quite often, it is useful to base test cases on random numbers. To make this procedure reproducible, the function \code{runTestSuite} sets the random number generator to the default setting \code{RNGkind(kind="Marsaglia-Multicarry", normal.kind="Kinderman-Ramage")} before sourcing each test file (note that this default has been chosen due to historical reasons and differs from the current R default). This default can be overwritten by configuring the random number generator at the beginning of a test file. This setting, however, is valid only inside its own source file and gets overwritten when the next test file is sourced. } \value{ \code{runTestSuite} and \code{runTestFile} both return an object of class RUnitTestData. \code{defineTestSuite} returns an object of class \code{RUnitTestSuite}. } \author{ Thomas \enc{König}{Koenig}, Klaus \enc{Jünemann}{Juenemann} \ifelse{html}{\out{&}}{&} Matthias Burger} \seealso{ \code{\link{checkTrue}} and friends for writing test cases. \code{\link{printTextProtocol}} and \code{\link{printHTMLProtocol}} for printing the test protocol. See \link{RUnit-options} for global options controlling log out. } \examples{ ## run some test suite myTestSuite <- defineTestSuite("RUnit Example", system.file("examples", package = "RUnit"), testFileRegexp = "correctTestCase.r") testResult <- runTestSuite(myTestSuite) ## same but without the logger being involved ## source(file.path(system.file("examples", package = "RUnit"), ## "correctTestCase.r")) ## test.correctTestCase() ## prints detailed text protocol ## to standard out: printTextProtocol(testResult, showDetails = TRUE) ## use current default RNGs myTestSuite1 <- defineTestSuite("RUnit Example", system.file("examples", package = "RUnit"), testFileRegexp = "correctTestCase.r", rngKind = "Mersenne-Twister", rngNormalKind = "Inversion") testResult1 <- runTestSuite(myTestSuite) ## for single test files, e.g. outside a package context testResult2 <- runTestFile(file.path(system.file("examples", package = "RUnit"), "correctTestCase.r")) printTextProtocol(testResult2, showDetails = TRUE) } \keyword{programming} \concept{RUnit} RUnit/man/RUnit-options.Rd0000644000176200001440000000441513267374743015133 0ustar liggesusers%% RUnit : A unit test framework for the R programming language %% Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation; version 2 of the License. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA %% $Id$ \encoding{latin1} \name{options} \alias{RUnit options} \alias{RUnit-options} \title{RUnit options} \description{ RUnit uses three options available via the global R options list } \details{ RUnit specif options are added to R's global options list on package loading and removed again on pachage unloading. } \section{Options used in RUnit}{ \describe{ \item{\code{silent}:}{logical flag, default \code{FALSE}, sets the 'silent' argument for \code{checkException}. Allows to globally silence output from exception checks for all test suites excuted in one run.} \item{\code{verbose}:}{non-negative integer, default \code{1}, \code{0}: surpresses enclosing begin/end messages for each test case, \code{1}: output enclosing begin/end messages for each test case} \item{\code{outfile}:}{\code{NULL}, connection or character, default \code{NULL}. If non-null has to be an open connection or a file name. Will be used to redirect all output to specified file/connection using \code{sink}. Connection is close after test suite execution call (via \code{runTestSuite} or \code{runTestFile}) has completed. If the file exists it is overwriten.} } } \author{Matthias Burger} \seealso{\code{\link{options}}, \code{\link{getOption}}, \code{\link{sink}}.} \examples{ \dontrun{ ## quiet log output ro <- getOption("RUnit") ro$silent <- TRUE ro$verbose <- 0L options("RUnit"=ro) } } \keyword{programming} \keyword{environment} \concept{RUnit} RUnit/DESCRIPTION0000644000176200001440000000127114565704512013033 0ustar liggesusersPackage: RUnit Version: 0.4.33 Date: 2024-02-09 Title: R Unit Test Framework Author: Matthias Burger , Klaus Juenemann , Thomas Koenig Maintainer: Roman Zenka LazyLoad: yes Depends: R (>= 2.5.0), utils (>= 2.5.0), methods (>= 2.5.0), graphics (>= 2.5.0) Suggests: XML (>= 3.1.0) Description: R functions implementing a standard Unit Testing framework, with additional code inspection and report generation tools. License: GPL-2 NeedsCompilation: no Packaged: 2024-02-22 17:11:58 UTC; m044910 Repository: CRAN Date/Publication: 2024-02-22 17:50:02 UTC RUnit/build/0000755000176200001440000000000014565700136012421 5ustar liggesusersRUnit/build/vignette.rds0000644000176200001440000000037314565700136014763 0ustar liggesusersmPj1fW?OTY,5Q I`_I $39sf>J*J -1,xLqp"5uoZgr 6 O2bհz4Mܥ3Pz%2QLp` u˫yS "[}Lʐ9T/YwMG9ӘSDAGbXyGVMs;|Lǎ|Ls> stream xXo6~_Y(Cdݖvm2; G% &wi%v7?$;G`~d|==4(I(We 0FL"$x"󹜩0O |KM, 9Pʳ b18UM'ͭӧ[iܯ04#JI"1w2mu6K~0c[yd%SYZQ&ҥ~C[ y%g9iHIitvPpO0`ZvjdB'_z_ze?Lgي3Gq::u..e|xݙ~_3e\b#ϝk[J=#Ntd^(bBE{שZU7xGѿ=U7!JH^Lu}Dm08AU_AՔ3U[x1y.Ÿ$gKv;Ssy2I%o5REW̤b53-m"jq«iUVq_|Ba ΖvXb50b$(X ;{8Z#Md{EgY5 UږH7Vօ4c%w5C@8_WRЗ^{+EQnȅ{u˲n5%kQ6v0WA+#T֋=׳2Wuv8YDwJT\]f t־<4` E1F4K$/Iլnd+tQ7l zc~l(!0<b!LQ)" ZF&!ĞZSk 3<7VKYbYrnSP)*Ң1|lfgf_}Zkoclye$cTB /V endstream endobj 76 0 obj << /Length 1692 /Filter /FlateDecode >> stream xڵXKs6Wԡf"$4v'鴙:r/q4I%!ȸ.(R$pv֓ޯgWg//Ex"R&RQG?\|Z{yQD.vFE_I|u 'I/8nưuw wo<"Uއ?N[_1@D^*3_]M6dY(lP獾2j2l\(_}yk_FSlÍ{=<*to̽%I& rjcF}-0[Fϫn[TljT'7M#nO04+.%]K4wm_^ӌh%dRdR}/HvHUz뒿 MawhsqLjc=vC :cJ-h4D[u/eqViznuQIZX!.+<AoDӢ8̸O絓Ub}0b2.?ϓ L#_´|P(AgoѰ [kJaPNMt'xbr$#ܲxX,Uo7^FBLn?l{HU=āc/16틓 fj ' 4]Ӻ@Ds״? dˮ)=G QX$.MϿSKTP]6;@hSrw.}\R|ƈHlٷUDy1@R)Sn  U'B 1;AGEqoMm$} P ,j[L( mqy Ak21J5F᥿o :Fyw9?ʙ[#9 bsoʔ|x*s,:6i Fp\Œ C-3 G8*GB&KUm6[I*T-y -FYICH &)ݺ=ICj`Q=h1\0Ǽjg'l<=>`cjح6'3d pAv-ؔOXpd]P;}ؚvIF o"MChMHNt.&h3ah90lDc:;i^3$s=8r]2Vq_a"}Hd1)}3-Ćؚ h0_NbkM=r_ 7>gdLZh=Q3> stream xڕko8{EX2zRR= M)]\.p1m%,;j @4$Ù= #u(ύlUɷ&|yJɎSnG!UMp4% I73cf@;cmy+3mSk+u]5>-׭aHV A|3 +Y\GdH&q隼Ps3T ЋF Z^0P`GNk~bY-2HЃ3˅M[[X5a)$,}۬ILO$T,Nj,h#A@j=hlYo0 S~$p#(Y <#fqf35!0"aD2̔-n9Mno 1/`nco0$t55vmSSp&?zc<{BB~4^W_^u'{kAg2ڵW$jIk*cu?:ߖhʸfū9#opBbuRp]XcLdy-󧊔EN"=dq(x] e7RIo{5η1f<'P[V㦟n>e3~Iƍ~dCGm9I,O=RI9! >hn?BKa%J(|@-xd7JgY-BnA5>a4#ueQ$*Hjtz0ʤP=1$:OO*l,톽'|ۍsF:%po+7_0uPBx|x(''lY{4y9bvM!?9EbAtb |K\׏ER=F-Zv;9N-U %kVao򒢺eʩ rI%Yg:1;[vy)˨MZ(KTNk9Gw=?f0Ha&0 dGR~>TiJ5~#1(tBUR9%H\ L׈[a{La:ݚ| Ț#z|'/cYVz0V>@?$PεLݕ$@`6eRŷ2/12М5Ý3#Wk!5/*T%n}zJi(``0.t0I50+gz0b[9ǚўrp8Hf?-F\ ZneqiElt_(k}-W˛n7 .UF'E,Zl9]Z(\Ҧx0 ~yPE_^ endstream endobj 117 0 obj << /Length 1242 /Filter /FlateDecode >> stream xڭWIoFW J8Q9p19Rftlo)YgWah,tCkVBxJoc۫mz"K']Eck&ۍĝbO8-w08-Ye;1)1-!6].ӝEcjޡP mf/k:qSex Ahc6/EQmoA>Cȍ 1Jt&8!u,ǚT EcLUɖ xNSH3"#R;%CS< (~fKE Nԋ[VvA~U$?GFO5^vXJ qۭ">ť17u)[){"FbC4S<3^(5=鯟҆=o!|[v<&4~>vєɿR,9I&e S`rj0}('t4Y BolPF@E{d?g˹k9?.Ұ> stream xڕWێ6}WHQhP7J*ڇ 44OR/Q$J^}H]zgWs iޯͿ(aq4%q2~\H,p^lI rRz&\/~eUBrf?^hl[숱n/'$ % Qe,!q/*[q26i nmA)D\-.1*w84t0 Tӷ[h 3iD4/ogݥe1k/ 8$AQ.76e^ogvxfd~Ukq".b\U1Qj\>Mɕ-sZ:Ꚗk0g@цfbo! }J hiol"15*Yvd4UO:Rt#  y` - .;(L`N!5 ھ씝Wa5} 8޷- cWF5;Z(n/V@(T+!@(%++5P'L`:g" E2zwtȥ=C5#@xVg@V:Հ!5xwzqCW/4'. ki@˪Ql+]_!w SϢrEZ`ڸT"_LadLZ)Tl@׊X* FWŐآ*|>P^b #ԅU`Bxlt7zp^kje͚uI <3VUdvĨe$$>"M,h̟mrt<-nb#bL[d[6 Rw 5*MzCRv ̧4'Y2{BX᪡ &af pͻ삩ԱvI)P"78A.}^A:˕KG2tlj7WcR,TEow.4.u{-:?:daJ5w#<7|`ae]@+}7[6y+O32#;NUqDXn1a&S_“TqVF`%'F8ɐ97SȐܹ࡟6Ú41bADd*z e&+ bk}q~vmܿKak΃%/ꊫfP=b6~yɎyMm%RQ̠Q6[i݀ a~L[Q4 endstream endobj 148 0 obj << /Length 1596 /Filter /FlateDecode >> stream xڥXmo8 _al*Y~n[{C aQcNGcN_p(P E>Rr|rzN’ 3NrHϙ/n8>"2%"նͫA 7O/|İ"qL^2"TC _Z54ˋFYU*k]^pWji$Ӣ, a, 2mFϼl8,_u Jt=~!`L- ]a;0c{ tҿ}1hYրsFMުfPw&m~۪vם|>9;Ty, m&_sg?Q&عӢE>p'T:|j/,‡XIkǒXtd!{OtH!{du[ny%hԏ]Q/ 4/қi̬&-h|5ț BwKͳG.q禵ta[4܆^>htI-$A2z*]躣 ZTM5DuvC[ںD qi6aJ=%[Í&bc 7yʱqBoʘyII*02Lotr]T)k{X2_sjhY£Nꄬ4mpq}{M3i=m]=‹!ڑUW=@&,}t{V]CZ:foxZK1g]2d64RH( Q-d4FIPͮ=UUdá4!0[UTaY=P}H˪ζ6RL-j!'8+apW Zv_G43C؀z .Un_EPWnȑveL߇蝞_T=e=FF؇%=u0m64QF֭*yAJ!ZC?W(*Ӎ¢?nQй# ~tuO]tS+ 4Y y6ԏc R ]ʮVЮ ,C7+u '%@q+0 O# Q+`Xzg.9GN)Ʉ3~ @>EI_ AEQa)6Byè|Xjzt]onX}WhO%:}v];N_pٱ~Lvϻ5~JkwgESNrn.zndyaU#Si1X5]$W#a_?U;dϛ7 Ő?wyMf TЇ / 쵯Lo_}9?"up> stream xYKo6W\ ]ɿ7Zd+]9LZhqf͐KB '^+ "AJPЂ B;<#"$j~/B{a% +ᕮU[Xˈ]yųz^c 7<nj`# m@Ld <|GPW0Pd,@8¤X d9.Yx1T+h#19_ʳRL^''R.㍤'<'fMMGnnvj~ɧǫb6K~昛7Eb=ìXӪKUP#ٝWT[ZV}[bVTo޿X+[bu挛ܼ}uK*YbUt5.U]WF\`O[Y;wVߪ*kU}(۱uc߼g:_NT+ z]}*&]Mo[\m[R19U(}<**/%8I,Wid GW˗7q8%'5H#Z1]dг0I#(J;3[tBŦKuTQk_d;ktgU?e5EȂ  ۱eQkwE}U8wibUڤqb|>A>0Rc19CO{9.qa$EWHBܪ؛ J<ߋG¶h]d`lYIzBԫYr$N;D5I 4%(k@.4 8;#Vꄸ^jڢ#L!tP R)RS)SS=QYK+CuWvѽٳ6(EB:|) n/e^_.!I7E$@`jd ,-APߑp|w7H!HSX'};g{&w blcD|_7>BޡC|Cbo\2{u9/3BJCb5_̦#c-$C.=4g1c| š ՇT# A1hw|\DoQX'rsmJ a(¨8;ǑODF!@#@ݔ##p |ܶRW=q}:މ%bpv:<TߏVCãu[vszB>i&*5`-2(?P>a( ͏+VEֵ{;/){ԊZ Zq9~& = l6YB>D$۲ezWZ endstream endobj 163 0 obj << /Length 1487 /Filter /FlateDecode >> stream xڕWߓ6~_9:3%owڇ@[(-AqXvwW+9v0diw+wx^ Å{/7DROeNw/=dF7]S/LL7:im )jIq6񌉿?ñ %d NǣG(MF8~i"@ِЏ0_B/SEb>ˆ$`c‘ -1΋{4aNcZv@rPYvg<hьO¤̦(R #y!+GI#HHd?RLy79>3; endstream endobj 178 0 obj << /Length 1964 /Filter /FlateDecode >> stream xڵXm6Bw2+z^C暻/M2mE gf\r8pzn<2 ,μы0H˳0I/~umO&Y8m3M (qi (Mnj*~0ϒֽ-n ҠBȎVn!_GRMfm(B! u~GpQPFI.hT{rj·W@ZٯaO{EQY_CԵ:Vt_j{\; l}ɗ|S swM;^4DSW_n~5IYxҋFQ Ѝ1 fI0,,, ۪~y2\`mз2X7EW&w&g?I_ʮ6ũ$b%[Ea_ Ż3&Oc hrHr-&%qB@.r Hwֆ;ZDչԯdG.P!E6& ;lMx{ b(,A$[ilX6 pT#Mj(YAty, &, 0/QTmC :+#Dp jYtJ%:bDDXc:8B:B蔑t(X䪯CS1ל6p`ѭA*gN3T&fE,1 AXX:^I2ޖ^\Ru-ȢHx+Y%Vcbv+E`מ-[NhxotYG# = fιB,[pWk#y!Տ@a R蝔, 2 Н럣 vh|Z=GYɸ pe5>p7 =Df\Bw ['ANc4(xmγ(0=B8lc9&~k3`Ӓz B{?k{vuIJ_DIM5o-BD5(,]i$̵y:"8J\ƓcI 'w{ei>+{pr&ۀӝeq󿶣 x:|1x= ?2A6lnjP}]b(H?wc܈^a1&;. jB540+dӵS\ ݲ0^a(;it;C]ϖ/=oD漞 n$ey:s$ pMsk0Y蚷#l,v1lܭ?iHbc 77N1' =uJ llS5fr['2('+GČ`8N^EP]{FCO>4 gsqp fz]nk7?J5+ΐh+=6ȅeiGvO# _,>, g( ~@!J w0@~]6y"C@/4+竖%u31+ȇ]/[\nUVDE6LEƺÕE?E8 i뵈=$tpe]i`q#VX;O@g>3-[%*YϲR)f,ɡu5b&XZ£,lQ a:9W"ed?/{ F3T}#fqGws٧ m, endstream endobj 192 0 obj << /Length 1922 /Filter /FlateDecode >> stream xXY6~ϯ0>h> !iw3A/M 2m+K)eCPd֛) 93HoyO^\:{f4s($CFeF|N"a7de) |A5Nj$`ˬti"8"w wDX#Y#јAVhU7H%*հTH$õ]!"2=i%fSG;V)xp0 6^ Ff\4A#y_n.NTV6'p!iZic K鴵8r˘1MR NA7VOZ)\t8U@SŞopt}3Ým4ÞW]ɥa(.7"0N%NQW $J,% +n(D5dt2j4^Y^ H-~'T6GcI_C^ӷf} 6-0.bCTϸz&{Y29 A[.B5ձ]YY9(t᛾GMdJD>Lj C>Lx]ˑdkr{B`)䲺,-Sv`b q/inYaj!9Q6bCsU-!.|h5`7:se8u`-іuc>#Qf[`sR&vEE1R)({RnzpMTyFnti|#sE~'Fo_xCg͔(+6aSIEw0ړTn8I:Ve(h4'͘fIXK͑8fPa)0 㬐84 LiILġʼn*Kދ'7#VlL ʪV?]'ٛӵOAfH;ߊoY> 1,Y\'cDŽN~Cqzv/3H'9|=n@LѰclQj_P endstream endobj 205 0 obj << /Length 2753 /Filter /FlateDecode >> stream xڽْܶ]_1j&ʃK%Q8$f܀t;ZIjhmNo͋_MꦑmǍ<~.ͻx9>z_:60pCVyn<7fnn@]H:8w9gtlʴs٧}Xx`b .m XYYpv1Y<M SC>$aзQ. afH>);tEO쨯/Q,]l~#)6¦v$EJGG (PdIwX}By5NK1 +dn1$%L ֦im sMl )A1(yj%Dp606c`lK.cxꊄ=ǫcWT"RϹ.;5d#>F]g[^]UgZ"QˌaGif NuG1?fCK z3MWYے7k(=\5h=nq={(ߌ~>zW(Dbj@ ZJEʻ3_+lIվtE$EUiTе҃F[hANqơ(o"mE[MehX B1懚]9f7v\A8zo]m_橆l/5k-8Z.x}{3]3]/~zB>u[K`t5ykmڔ9 /!"i[vVV_%grDz?!a3 io2#Lfdf4yZ4e<|u~ ]Q;&aubY`Ȱ:'c3J?x8LR\۾nfz!l =볯yҌz.5Hzt=>I$ReN\?AY\a~A<`# =1JiK<0CobV2I1n$q bVx!<^bm*Q; rN)Bd`/'SxL]ӳ$?uq%3KQr H~: -0bayEB \k*7߭ @ D4PAhw- \!H!j"@5R^6a)ﻼ؉PH@YRo/")CAb3CpSa ANL)P_o+kûwk# .>lZǛJ;ϒuW^MaK7N,P᫇&LqQYk*Y^ NWR F³5_%Gsᵕęd蹞AM; }ze3qb@"dZ.iZXxꃟΪ5()rV |"q_+0gSXfQ E oUGv͚ 0fU:A3'u{òl v)O玠eAmb8h+-`>peOBd +1 gP\ԥџm_CuU=Pj_ Z &l̳)]1 }whgC~d@hxv2{Ja\Ne汾ib.CНO!H1g} lBD(uE&GbK@HuP>K1q jB@88nYx筣nU'#N ?%Rxs}1f 0##[e"'l](#>b_ j.4&}f+0AP9>%"n=xNeMc-p1k pǶ㓪$/ OQjspq* (,Bвo/gЪv[gZ~y=ˏOdK >@H{IHAU_je9cIi\t|[ZҳZ<qA)[O293r9Pћ>$N}T:0WmvQi^9jco;17G=_7Ҍnm:_'@n[_c nuu&S!/D#L}xb8E%GTO H㌷S Z6٪3U0߁.HXjUbʟjS۲P> stream xWˎ6+d##R]M& RG $YpdF]R}/E?B|\^^{H 7˫x*Rza84DIDr};6lj߽o_Wi:e980V&N6Ty@}ÇrO).8nq \ՆUq@N9 ;m …5kV0]JdgJc_2< 5(݉J|)w3-y/*hQo5e^Fw񸁁h n:."(2wr`bGaM}7Ns YgdsNNP%GW:&`Y.>&?ziPJۍ4\{#ϥBC۫g]v[.ݱomYPkw\lis\y~fzNcfkE6}srRH:Ck42VKkLDIjga*l{).PeG1$ eu8MIە} %t(*roM^ %j?.^Z(: #Y (߲LPPP]Cr'!o@$JТDѵVE Hvr?;0Y`[Wv:;ɨFNAib> stream xXK6WC* $9;){eU[Ɂ!PݠH5gs/t:/^m_|FU.r |_D\I?^4d$LS >w2{]# 3&˻ S$֛(˽)a^\ՅUMwXo04/w:L@dW$;U-Q}*ݼ\o0tOS}ZƼ&ޅO-o@IB]U!L|jڮkU l܃}TMG&^Eyow$ݪ?9Z#*;U2j;9_,/ =Nj)ciܰdm@v\{#=[YGjkq 6wL /$xs<'BDZl`,XxTkq*,q2zcK! { 9]5Us2c cK$8xQfia'>JįCD Ǥ& F/?%@ R9c:._Ym1P$ƍLdZw@ɬi31 +D7U؟ۼ1b")G)ЗJMU y:p M?ɞ2Rw=w@mKrDWjbA7U3gH}"!mlǏiCмܻSLa$H[cpv0gc[T c=\>Lf"ZG=aM i ~IÂHaYmA!b˦PfN+^ؕ^񎢣9}!+נ r{Aj} n0G{jfbKE𣥴~fGh j}cC>#qp:SG ;u@r!UL_yG{]$ ɒ|}ؒrEV3\>4S:Ӱ.6ہD'âXn0j-|?4s(ttØW>]jeO-,3$4p^i&g.ɟBvcY74N<3ܲpCBV13U 00)7rT=qk_K@`͒``]ԾXEpo}l)i #KIăg@FG$N L}˽7Uש> rT,79%<¾F2qҭliEQo0Qr1qHɞYP; ')F9?O i4H4>Up-)B86 DS 9lgZȏ|8 ueS#)R(ÍL{nc<Q۲+p0?~IoSD[v $Dﲖ`oE ^9f`]7q5ZMPLڋnJ<=ug4Cy"ƪUIeT G23Gg_̽C' %dP1+E,Aq.aj.b2%( \ԏA;> stream xڭWmo6_a gwEW ]] lmZ@iK,$&#) Iw7h=rGM4sf櫑Nƣ$v(G%ċ;'洚|qK/t='IQ r1#;Hض4PlMO,jVVV #Vb+z0Fw&vּ z 9%ǒ m&śR~?*'qdH06Jl$؞̢HP0!E&1e91Laݪ,%MW5(FY.H-ʉgUg|]NȺC0F ڀ!':N_5:O -UqMڹ__#ze4'+aX EQdyxfd]_.${(OZ]%':zbz`_Ґy`J5D'QF@Kf]^6*+MQ&Y~؟C F%YmW5@E&!`]v%w$aZpAy!Vu,?qA1nB6 yM>R߼_^*L廳6!Ѯy yJ(yTY5|nJMU鯜tUG7+}/oA/z-zzUuo@@gQ D/­r4VKh^4ْy #~-eXJzqu 9e&{ٜ^.m 'X[6;ٷ%v@X~=(P[ Qk[\)  \l_ }rUɒteUUAUmOa B'9߻1<O"'+f|.,tM67^!t*>NDoU 3ū9JcW3龡n'C4 uC&zGuz\6vqv_ibr@}[DŽjl־WƞOTםy سl:?u+mXʃW& endstream endobj 242 0 obj << /Length 1706 /Filter /FlateDecode >> stream xڭX[s6~_8 DB 3}HRmgxӗ8^jVlq}n,xɃ.GG-rxZIYOB U|tqz2$ }GѴ"LH2W )STX҇woAjYӾgMWϗ^.+.2=Yeu~BNo؂  1rƝ~?4Q |u৘~`2\đ%[zA0=K]^7;z0r56A꓈~mtLG31Oc&-k>㼐!T;!ovW-L3Wt kg{Wu áe~wZmfU feeXE0{9 =yeE^ TL|NgfwH?v0#q+(6I0 :*''IOW{Ab,u.X֚3J=a|"K)j.s9)\8f<ؘV{DYNJ52r&=HI3٤άmN-ݺ =%HtjbTOS#A&Cnd3XVUb9:S鉬}&'GF,2٣H7 X^ѓMm''ԐeGzeh]!H6r =!d0;0~؏ibTHMf aǍ _Vf.u0sIP E{X*u%H 4(~UVP[t'ؙɬS)Xp`OA)7dZlZ`s^Bm `c$Ot@V{'!>4da!ƌe1&Kf ;1[-%6Y)sU"ws0.E>QbMfɸ 1"3Q׵0k.ItR/@#xue0YI4d*dPl!OOLQ-Dng#kP1 9w[]r0Y/wR))Sv8OH2Nd1 :R6+ EĽ,IBv%!=k@0#3KXJAn&=|(rtfk\r'4@uL#coڒObAz!b{ 2t;B죴 6֓Wz&lRee⚛J%DXva* ©0Qk7u̶~(S5 ~{:' PI:ډ= (.AѓݜewnhnZg>< t{`;-5 ׺[ !uQB8VbTNL\/yah;v+ lZ<D8u7/ƿ!uֱql,0ڧQIVh˥8T 6J[[}!V77$9 ~ԟm7=Sw$X-2H endstream endobj 251 0 obj << /Length 786 /Filter /FlateDecode >> stream xVn0}߯ vEN HT\/7!8[lrر&m,TBR}̙3sBoA4KA*A0`/+Os)rŗy \B@(m26%[8{# 8Q|}3?xB/ᐺE ʚvC y͐yyŅf"ʌն)nk̰vOr3.-#(YZ;R4Mi> stream xYKo7W^(΃/HvFi@Vy]ӖЃp^r)(ꈳ+=r$Fv/8Y?:!''}MN(!9J%..`ʮ⨺NRZ?Z1:!obP2D\$c2 B A6fj?3^pZ84K&˜*V0Ka[ 5W`"A7,h+$* 7)R7lr8.b Pm=UD{U Sy5VH%Q pF*NX$,$Bbɐ8lKb Ks \ X o-@HfMZ33!:-dDd5ZjS):T".2!uĠFڎ :j2 + O"dYAtXm)'90LO`$1vp$bԜ4"%?O( LjoA0:8_S˜_/ƍOGϞ+6IxL-hO0z.3¡ E=Vdp&C$|lJtqv2Zjlz~ݩЍO/s^э/1cϯĆGzv]-rۂ~q&RL(RcX OiDژy~4>}违좛-fo?_Ңc2c1Dًe$Xq-7WjBwnzz2uPwGz?_vM3kFְ2J ,^qp{k#)g$69xr!p0F8- ,5{ۇ|͍ڐ< s_z/P7Ѱ]­dX>w@X "|N'uνt}Iw5?̻HN ޶@!f^P JG43N j^ ^ d3]v̴p,k#Va̭P<!0V_278߃)NaRNa|[2*L퀣1ޘ=夿e+Bqh%U y\R{XǮ*.cQ-60>i}laC;v9kKլj|JxHM^wljgvou+!'B6wnmLtu[/ųngaq}Fcw~y2em*nEʾu|M OϧvXeMW"ᇚDzsEbaEDA.t\QLEX~2GgW|26ÖB{خc`^̈O#T*/~P= 6Vih-Q e0G lճo]eƑir[ՆI*o' endstream endobj 335 0 obj << /Length 1329 /Filter /FlateDecode >> stream xZr6+f*o%:Ӧ&B#!.T}! %'d!OsqޭX}]ra  xB@(`S_su4WͳK=[3P'LJG܌<}i -KCPFPfσDs0r'JH/ysu}cOqC8jvwfE?/ L.FɽY\|u(qW~Qko ^>g~fQCAϹL:^GJҿa,0΂\aA?U.,@ 9i<ݩE!Z_cЧfИF~ hFvܸvtO0mQxZ#8.O˟<}T OV,ڙa5ZD}K mdfͷjE;gcO5;e3TMտQ/mhKQ(SF} Z2 @L(d-J8+IE sOjAoX'tt1XBPRbˁm>*דq%ŘXZX 4m4c"@b)dN+#bcrR}'J!铇ڶC5X-#m_v{pCZYݜ{w92bMpU0C/lD?W_HuB43쾫qcP!CMz.=1LčDi&/QѭJ'q'G<> k (X"-9)fq:^\`[>cj5H"[m"H%p^wh}ܥxeq~r-xwK.xvϺAbNm.D0Mp99zi"Ҥ"V5[^ܲ. kN4i,1i,m޿ݜko^)f0,%̠>?Vf Lbjh~Ҿ1ywuF_ӪJD1lv 쒠e!vam!`d`ǒw;8.Åpg&zbUY9apvKtz׫Q1h}%4+&Vv`Fw޴9 %rkdJuDQ2Z겂 +4^!g~3X$ILo}$AUc@> stream x[]T}_r$"v%<$x $BY͠a6>An}۷|\kB.9vgrn!jA[D;-HV(OVvFk Rs Js>0MՐS R@W<`iq';z@bf߈ Q܀H p ܂=Ɇ); ("FDQʼW){o $N)`=~&>[l)Dg ]u#'AR̔L/"9]]rq2Aqӫ?)MxiEbNK R&$V 6z^/&Cx'2~7/p|I8&Ӝ?.1{Vdo.O~~ s;(?积A"P19-)!i ו}V5oN:kN9kN9״uם^wzuם^wzuםpz 7pz 7pzDԊ[ּ-Vo['NO8=qz'NO^vz鹈ƝZ^vze:=uzz{ tS@VTX'-;hQwMY .1gBA;ZЇ&'V#gDP}75NjLM =\hg` kL\7 2k<~F 'PtL1D 8ŐZHϘ(2ߣ"_A$|OYcp"A"p#6ƙn((Ln6G[B(MHSHB Co}4鱙rRf^G)kmH#E%HZ =G&lnu(ٱl AƢ&΂Np:{ [T#Gikġc㡒lGz\ȶCL 2+Vf3"t5 *Va`-< Ła 3'ʢ2|ĵl}-jR|6Af $K*$,JrLH'5Xզ,&4Katfj 0ɴa5TEhPSbZ2XʹH(hu>2ilœ$1Pb@mC,T Q!D%-Kݝ `cC(̭LmCS3b|75er5]X FXk^Mb!f`Kbvfey2FlZ>(kŎ4և1 !u!0jJ4d7mf\ "weds`r Ӛ~_y^0Q B<3B QXUv4ln u, $v"v&LĦ .]=}Qd`5r\Tf2b6kc {B!}M`Z#[q N\+k 醒Ў_y5.ǒX0c0AΪ^3}/ /b\3U<皩HF,U[B ɢi rmVyg 5oOt0n;VB:n!bajqHwf/,"YJDds{\h&ߦS*)bW8>E68wKmK"qgMؔ=7κ4!x v~KcpK ~~Ͻap s\Ubgi1c Ňl?UdYC?wnXMM,ͫ{:R쿪g'^ی}T<\ݡ?)?'ْ&홇il]Ӆ5;&>m9~߾q{_)zk9oGc Fm5ŭ? fڻV'vqx W`?$ endstream endobj 349 0 obj << /Length1 2887 /Length2 19854 /Length3 0 /Length 21292 /Filter /FlateDecode >> stream xڜP.ww %]A#;9߽ݚ*}z<ݫ{w JRe5FsGS#+ @ǚQhngrSR['-xJMb.@7D d(xX9|l\|6V+:ͬLv5+' <G-ȐdP5q=a@ca`bd3烰=-搕37HL._rxg*{<3iCM 4;]j2 о))@P)ހ6MDW|nnv3_v-1Z09ݘiA%p0s:³̭@Kkx\{8́ &n.^=&V_ @0wt*.YZW^MR袿EE,FVnn' /NME巡#ݝ?@ƣ+EG7P 4wM h(/?On?ѿ[/LAqo}U,?>̒vvB??B'Gj6 "kWIk/O_Y;]:(A#_kVf@WWE@0qq1g 3''` zb3398LNn 5E\fDD\f߈,#^߈,~#6e~#o"F . o7qQ@U#Pt]7E@5#P<Oo xF&ff.fYٸfmg[ bj9q7YٺDM]L̀v@ ?ĜsvO-?yf#N#3G;P _{ߕ`eoRpAgoPYX{XI,;i[n32qadtC$Ake (E:N:b4俋 no׍f%VP)tÊeP '$뿤@{g8@Ets# V7IPAo"l u22'gMt2Y':<0p 6Y{Az&tg8Z]@s 7@ ~eь?̦!Nqõ4FA|jB!q؜mII#df)Go]#VurǢ/,yg?O!hI5T<"qJy#*TaBU u c2N}y UMs;1Uj'JڳbLſn.3S+-(~2@2K.y!q~f^v%K/43bqRFLx.NA4i;QA_ ﻢ^i/ 2tH܎ۗpAm^'qI(jhL%8r>->rҊJ];c q䬸$*AǸDݗ6:PhF7's}pNG{ ԐQU-7`oĈnp=3> r+qHוAh&" d@UIn2#.㉿n%p?h^'ٙՇY|([O熅2 RC0z񌈐kW |]~?xJ'q (]8Qb >C'YPx?YM:w>}8@2{ : j8S*8?TpUY!% :oEd-6 uDu<"#i#U/xn3ֈ^ x=Evc3S-.o^iLfğH *Ln𤎷"}Fxx@a79!Ǵ_b{8@=$OdkcONWm%}Ƨi\=$S~ydw{Y[ PǴ]lGu@4ȲvEsi9 hdž9e[+? 86H\*"iDF6Rh]LHP }Rؿn*oY?H@g. u>@(=2LuKzj%y+'CυX3as(k2DH "[ =VKD'oϺz~A4Pڽ?oD &˺Koj_n|Grxcϖ'BC% Fٓ7VڍIe5g.mTcIT9kEd q [܎2\-7.F}pU!M c:CaJv_x*!84M'biM_(<<ҍ4KHx-0CZkϢ*SƚWLlpڲS*j8BPq,Ǎ\zL';zeS@&kYkFI l K߅XBMM;JN [S2x("d-ʼ>;$Mh _Q{Tm'oO+DEIj^i7V04i{-d:PF|:n= 븭粨y:a%8M'G~i6XOC50@WY. =$D`nq\X lLW@uRN&'Ӈ!x˱=c&';A Ä \S1m9Hg8>8OFCsTx|,Uig-j͵>GJtf4LKY^ Of؂ kP:oاr! ь-WgKnm!'}Nh72&%\՗(( 24|'M(W,(EeT}t*ȷ7 Q6|'a}BP1*j]Pks^6_+ F[m} `e fG j([{rY``5;9v*TuE33"%*N\D,1GB(tXxS(8.L&JkaBp=d~OM4:3sۇ}<ʐ;'}nLtPe.@j0~,znb Y&+Ĝ9cU}6ЏnE$AG"')!܃>Cy>!#05&C1P('iDiG,Z-V8M+8sgNOI5U@'S::_ޣO{鞼QtݣJ#.8PL=t# d֢iv{̭M_S*oԲf===}D#Eg佄"6 z`01U=:y}l G' 3 h_.wAIܣ֝.mӱeNs!|:)>zj.SY#DaJȿ`=V>_*Y Dx7I0ܽm p}}l`zpݯHhz_ث?Ei|bNRdyLVԴ+CƗ'Hd[R`kBɩ JnDh0"]K]M -H:YTdWe [WӬoZ_2#MўY^՘' SSXl0(w_q*_x*L1l؂wؙFK?[9sю5ڋ-?ae*b8 3o~|Β> 5kԵA+t66~,gXj,=r%{Ye[Jy޽t4^1v# ki>]VNZtBqq\U02taU^g$5}Q/dU!"?iuk"5wZg0q0@us`ciOXA<'Wͷ 34gފ,by';']xA56uV_ ڗ1:ҦAxi~`Tk<1x P #PƌB:5eF ooW$B#_7 R} >m73DHHИw.%󚡩u,ɭO/;(h.3D1$ϐuw^NAPi9pq48m-^OGﰣLKt+&,\Փ70Dd)#bP7g ߎfՏId*B'S|$XBRBnI}5{q>)gY2N<X B#9ǟ³v-vjiq7G9Zݢբ+'RfMv0!$-)Υ8nIGc/= 8*2%[#!XADp7ܨX*]GBoP܋胢> VOy-$c>fz[Ū݁d{-BZ<0 {[B1z2K=O=4@G'eZr x:I9Ķ $/k?Ln,~VKK~҆イ|ۆש} F4C'G|DfQo;5!cjEӞ>Ep#Wģur8VI=V"H8쯞 M4؎sӎKi)Ӄz!L"CZkvt.._t%tY4p5 47u0W=<ٞ?%Nu/l5P%{}pe3W^Z'in$_dB[@9heOS媘S#͛y"j]̒r]~AHt|k +V&a;/51n$$Ռ?2#ʳMv j!K5jA^omcv8W~u)S9X44\ga m[q_bkoKvkKD&wf\&XMr'E18dmhIz5' C.v!0w4 Ȏ"A? R˺0mrM }Յ)ke<2ꪅ-1͓vʏ2r7X 4Z9Wj3/YvV)!DaiC8Ϛ_ }()F`8I(L H&7[ KË́0Z}cĹc)_W"Sc\^;z`@`BIoVi453me.ZĒͺk3|Ь}@p/Le6 |$ -&xR}^40OrUάu}HVcc _MgGAX - xUU-E=*8TO{'2F G=zr?pHW~MDdK%\Hci&7k`a uzU"␪5gt霘 S6'=%$و;5B4;m=`* "x7&O*ץӚ L3[DBssh v-pؿ׎(,?<\~ssz͗ɑgr͆f%UaŨ|] V,ҎvNQ+tFz-Lw)],d"_nxɏ6 iͶW6#*hu3)M6B:&G/Ƭ"QkZhavc bC0o{[v;W⮏ο}E/ڨtR'Kts T^B]^BcYfccSi"ϸ,x?Zĺe3%VGq:Gu/| ͲJa Jr* =SwCpwj|):Tb)`?1N3]a ąp+}0j2v%FPjXs (?%AzFTS*Z rTtf|^u: twDhvKT=nLIr&8ɻKcHjtavBеѢta>Qcrxժ?ەgEKhp*q/@|822 gEmqV;bEoؗ˓f𐸩5OEن&?| V[Wj}Pћ!MLRf6L`eʹ 2_PI8ڙ+g>k낿N u'^16 t,M7F&xS yS~nêt'E/$ߡ\AL""$iQqT֠#]ihc\-vȼQA$D@mpc$78alЛϩ#]gZ%L kC>p-Ic-e'%-WYJ;Nmva閊:i ? K@ T!҇g>X+"g<) [((!#ww6vD&=]e-8gFik}L %4`E {|=%"ľU3;M!)ۥWAu"tJ:݋Z gW;"h;uzQ5Zٓ2Ͳ5Ox4Wc VԍW+I<_8hFȂl=wnMȫ = G ,$ⅺ &PmLVs Y *UmnYB %64z! Mߥ$z҈DD/ T݉b3c5#Wdb0U|Xý>|-8D[C yaM}RWj~183a+PZ[*a}݀B{j1]^pj\dWY~1O>.$ȯ>gx ki$$Tj[92!C?Dˆ̕ȰmX+tRVcD.UorǺ2r:\3!T]!&(Kl ܨ0DiAkC{ݍm/kMwOLϾV90q۵$5ށC>J )ZvkH5jTGaMXJ]l5̆kL07 sJ{<1/FA:*zOa40؞6UG)XCGFw2F4nՆU݂$\h׈ #LV4֮ ?"'xr{|<&zӶ2q 7ʸf.@gZ|^;\" >^C:Pf@YWRD11<z`my%ZNݦNWPgz4PJ!e}Xjpv8] IqfS_ )3È-G*˜W67aMk: +ۭ=҉A+NኵD:iT]7.+xrojy-He?YǑ#~Jl-"`?|f™H N89@A v{Aޗ=%fnSgRT:__xgro>lHq?y/D!lJ2ۋ?uU}D0&+dA˫;gDEqK3AeI\YIN}*)M-9mcM4.v^:bq0`z! bC%Ѯws0t^YmB1Vhk[L,ja:7\ZQOG\#,~$˲Fނ(X:eõȦ8`cTE\g,H}ք )R)gڰ(zq<]F͊D>-cswyF2YYz_!g59&컚R7:=[ƭiRym|UT CԳS!xP_ ®#>W+o{cVa?Ϝf1{͂P. fxg$uoPwVoy46F>ʹh6yf!YUwi۴cN>0q-WYBi;fP܇˧2zaߡqO,J,LȊcύ_s?2W)p{}! >LͥBkk?Q56@?&5jx\:;X}}e*''g{o4RqVR˝$yʂ.m!;Ȱҋ.eڝH ;iXP=Sg>aZ50 ;>?}$ŪfCu0l,UpC'Nud)~o:!$IcCq5!6j2~_ݣc!cDt+ż8ɳw,3aqC>kf!=OkQ]^גe6NsZT/7^2ȿT;)EӼlo&絷LqÅf)Okq%^Y<Ցr =2!dhM#PX^iMP9@6dŰ6槚 GLP8W# xq^j>桢;}Fym7BPnذN#p_@7:y0W;^TĕM)n3F=}V ez)3c7;buS~4ޚwk K ʚ,*E9*o?E:ѯ=_}R+"^0]޹ew?TM.C8)"*Ȼ}lV, (ɩ~1;"/na62,؛?Q"JqN(~H YTAahPC,,aoWI/cf^ p^oBVadښMO$$SEcb-M6~\69la-$eH!g፦.J5?YWdMLMΔM4 lU$>*U֊sPWpM ٕZ=骁LaӞm$¶N0hkbƖ4#>r!uҸ@?01WW2VCPPFvpr 5 {d;&Ʈ^à~Y:կ!P#cB-5B^eJ->tq׆tz=0%'}NKП$()KKmgebsv0;GbC6~Z@ɩwcPW^kZΒY{zyA9ޓ<*]UP9z!$_:l2}ŇZ CAK'rMCU:0.sV))I2W/=+c"gf́ N#-Z>5TD!؜}`*& q9BY@[E¹+,)7mS6diX{L9 L|oAy?W1]e2:w &vr @fJvQE3dj=I*;!ШjR,T9g|() lve`-9|:@Av=9m;!q.{URr|k rBz XӒ+}3 >cQ;vn-3xm H]>iI^s*([AYwA°vG.܆·p zTD"'sO<~(w_hHFrjF#Q l qtS Hj"^ۆ]`[ev:9SSous2H>H$n;U^~}ynz;0rBD޷[!S9n^{&5У"f:ZAIvL"=/Scdz˝=)^VE%L:NVw>5d[4ߔ,kŤ@ÇysTDMW-G52[_mJ5+~FC5' p(QX:fՈ9tKmfP{?Sj}q$TYĽB:=H )Q<е7NubWeJ OotLx-l aT;2[^67|B!]t!gCҪQ웦|E \Hn J=|=qɤA@9,Bxx y|5mSi9cSrUcWB5B~k vCʒK=Ϻ tVtZUNmA#uD$7ȹ,ߋ3Lx3Y;]+GwDHskr8f媛aZIr1:}r6dXϟ 9q5CfzP~eEHUm;1NpjSDʩB|)>w[Q_oelٞLg;_M أi$ekbC>S@l/ПX BeG{%5z#kَx0'VDm<')7X.Lc]w YoHw? .[,p\H$\3v$*JTz? %wOw)')Bl~ ]J-ѰtsJz"^zpL7ƴYn#;&LxIۨܚ#V3:N?I!j; +h[̕j%GAeދېkćVXxr-8Y#a(vmMm#&{4պ%o7߻swzR 2דw^T"̗8K5v»aJ^f!DD_#s϶5`yXPu}MvV!r{SsOxd[G%UwobhH?"TQQE*q0Xz (b&~-W $Dy =# 'zc5\9 vV<J "K X4@mҺ4g)Am Mq&~c[>erjv:|pmn{)X6mcuȤʒbm4n¾NFsbN"wW0ĔGI_{c!YIM"%VD`rT8q_%ʂa4跙"4U~Q-PqZ8ҟuM؟|Yx\lQ>@hd:֫xqyۿQ\w2E]&J;axh<HGg|8k̈́Z0F {1+~M5Fl9zا#vq`1] hk(=5E3'⻔;tRҡu b;vNZ%K.Yd1}&8̔ r*w϶s:BwɄv9&eK,=zx,kHU>K,r:;7BB ~h_Ȩ bp wZ[܍ه>*%<48>u~Ib]Ӂsy4vN?g62v/<J*HaXΕ$@)hZc:JE3MD/}3 Y"Ј2˧ ANpJG/6I4\5E}"aX8#c[ 4޽^R/D4Q;w190oNHݪ;hvw>,|a7ߏݰ-bde) v^d)nc4*U|3ղ1&e0DMă,ZֳfKك6O*7k}G-1ٌt}VZKH1 s"Z"'0z9Ѣ,8-E15FJ?,η)J3LM@ ryz¼A-j8o܊V%>w}S[ {i9`ZpRf,(1A]>S׻SJn狔' !OpٻJ_ECl{+p%R']`CcX !]@^Cx^l͖θ<3*ޤ3_㚳5::?wwzGX cWQpS` ":xlYCy|mZpK ٿ=L,M*5" .H 2T^]S?\7 H(;}KW}ýf_s8@(jeuWlAe@~1*0qeʣ3&Ä }{) {$&5hzLXB$f]}" N=.+l׍/06A$y%; Ӛ$vO\us6'esCb>y :})OH1UqF_*D oj~aZ& TfaD'L"ߣ5XΖ"Uic~@#KLBg(Wj; ts!i7cVn^yB*7`QA9 UP,~u} ^G A7E~bTl'J p2Wh(-L4{m2y$K^)P9zJhEʓr_dO[&CTɻ 7% . .1*c~)G` : ed$w];c*-*PqR??t=ޝU|+l$.fV^vOHYc^,y¾Uz2K7K&V^Uh5w>uos!!QꅘH$GR̤4  lj|~^PX1"ebTD+2 7`%RܔrCMwe =],ޝ.GsIۂr&tO}F\USX[>g& V0(?-_דkBlPe\N^rZ6{1L0]MS*WC's`9zXe$r[`Ԭ}#^u&Am(k]?3|ZXp CGjjtDBDIp{z{F1&8e7NJGҘʠF\m,*Ƽb'@>0% z5 jG 2ٍ Qv["BV R߰ISgb! aYUh rs9W,ƣ! -M@hq {We0AKTh%v*BQu-!6CH?Z/oa6.yB{JsHnk )L;˖nD xo5\*=-jO^mڗ=%}ЂZ/⥥+ jlȍȏ$ٿxv]#C NZ*dvh&WU]&𛸽02Ȕۃq&Kiꄎq\X\IC! ܲ:|XnYI%@5z}I#Ľ$x#jЕȰK1~ETx5"|wSTztIFe): Q~]il8X Fk9))4)^lykƶ Zb҅Ļ2Y,e[י`۫hDNFvЭxAK&pWIF路, S=r|J^O4) !oh`&  ER1p.|n6p}:/`HH3K;rVdv-~aRG |Ob :pޛG},d-~ -Q.[RnM2vJ2ߐ0,%WE,f7ޖ@Bjk}~%Ժ|뫸#vzNtlqv.isG8}vVڮxo`SZc| {4uk ؄Mü-WM0 Hiy)^V?_zڀ9)$/'P׈bX=!pM%Z-y$utTn<$_xnv':㯘$ɲv uk<WaJܱ:,NET~+T endstream endobj 351 0 obj << /Length1 1386 /Length2 6039 /Length3 0 /Length 6990 /Filter /FlateDecode >> stream xڍxTSۺ5Ҥ#H7 & wAjHB$t^7J](]zQA^x}kk͹d30T`($F$[ PT!4c$f04 T,PD@4HBRQU C!ahN% III(<0 8ܰ!`0H#K {{{ B('y^7 0a^0(=hB$g8/1 C!H(0Ðul$W?ѿ#p #H/ Fa^`8n PW2 cBh8׌¿`Y UA4ɯT0v}+{GBt6Ex´T`&ۜ`PJ\\ =| ¿ ~;31`pG 0@tsEps#Ik9ƞ`0( 7 iݷ43(@PJ@ 1@?X-# W}e?#^?s顰̅xMtk;_YWwGo?_v#| `UjPs_ՅAn€jPB:a-+Vp /e77 3@0( |XA\w4]0YW AAMDL`|~ ,Da!쌁GɯؙhW98rLV{[0 B2?Ȅ8UbP欁gՈ" zX]tQeg: MqDmLПg'Dl* XG.d44Zxzl.˞#wN+-n"7Z^w D8N$Ytfom%7k2SiCu&'NwiW`O4(4zgGl)ð {x1)QMmX㸅ȣc7RՙݵwۍF=UsRպ\RfAd'dPYcBA{hۊQK,Uw ^4mu gxš? D?|p{jn+Aݥң"ę7Ej:"v"7[Q$[>S 7;<Qdnef&NJ[DVҡ5r=gUw8(BJ3{9Πsuwo!!|_mTEQkWM%i݈{1:O;̴LVAOE;747LE?!һ$}MaR4͕zWd'~ 3C?~ՖSv[&-Nn䃼@jie5{左[F׽Ts UIȧFr):]JZY4%P!M?WșhϏ$ءaSzGQ4cQ˚]WV?X[t8 4"Se =y<#0lZp\7.E{:pU"U^hzzIǶHaITX>oxYPb'yq)F~Oi7&lT?ˮge(l~90qV9]\|>\*Zdxv]W}[?+gM)e Pjo}q}G.Aj`{ƴ5=G3WC*IDzZ3+W- u˳m7fHqw0LgJ+hR7RI[<]6C3WILggdgltyͱJR%5j0[0r'm>8i(s>{meǏlp|in|;ԙvgn]I0S? !0j)n-R}E:/!#G㨛U9:o۴?5f>b?^\sNMܥb=!ڌ8wnc\6΂'2,Uϼr`}Ʀk^%]q[9NJ [x;N&"- 5z.6B<{5B޾K~'\}BЄeG4lz}]g$-!JXo*T2.?`gl`)V !d~oѣnW?wݑH ]@ O7}oz]y)1X R|[727r4UE]zaEi-U'U7yYhc-b0kx'8tx.Dѳkx%{@! f njuɁby蕋Iv|Ho J8 3$%ͽl˾&wIbpa[rfR cG(]S6!bs~P^Ξ}<ѐ&A$㰓[v²s&>'+Su oR!Oωm") gK[A!ţըC~moC| [P輱:Rǯ.n"cd67wK6Ù_'Sp|,F|a.2))9 \++ĺ| ,"bBnUhME3ƢQ/~;XT悔 MqwQ,;[П!%7QM9J0XHtvdK.8JpS\dYiہQļ J)N|[!=͚QbY%F~=Q?cґF՛^gl᦭*Ҫd_-Ei;·'Mc]L]ecgz z 6R kSHXܕj^TQ J̐e4>c V/cbje`rbqؙaΌ O`kn_EkV2BDKW i7Y͎rK%ȑ/ɷkhԵW{|Czn,)v_-vwı{ e yѼ5OR d;, ]kA\8]vn>&אY8Ca"r7q֚啢s;<5 Ll@.Or%Ռǣ==+䂓6sS/n2~ }URڈV0fo0pj22fm˨@.g^pdt,Pb쎆DY0g+*mռ?sngS~)nFXN`fLe鳨N}t2m `^uyu'cS]0 `%O)Ĕ J(RK0)a䫌  "MO-5Y@+횃-aF $O8fh1*N>niȩ.38Ep:Z=g\P_kn+:Xh߄oqʑxXv:#-"]SY 4{r#}1E(BuY0ՊcyOB4/rky8H»rCo 27n'EPf^X|;8Ԃ&Q`YKFY4@F3nfyXܤE)b /c=u1r5|!*x]m:1LJukgsC:!a\ ݅xVfO^z3z:G/NT+t kNQg7ʯ62OWNm7w|PlU((?=$F_d2R^_EU\UE"||wp_*IA؅ӊ)AĨq\ݱD?jTI?"+!r S ;/B،1ПKfv#{POlduk"'r OP5KֺAyY9XbiD*NQz)hrM3Sv{COEW=U#sSc/$.gK!Aj Cb%\cV 1B&m.T 2@"fUR_B>kqQy'E w؋,%t=/齗AA]ޣߑRFɓfab<Șp[Ci$q6qnyQ 7(%CYFXfr9bR3ȓPW@яPHVrJU͋7p,lk_*Oh}'yIk|N-LKR}şua sjR8Ė8w_noUmNf S`{*js,W|ƩI)i"flvX=5S]j}1w,oPN5b* ]*"KzKM%)։u.MCI.LDb#P3pAk˪kSE]u.z_|>M`qX>u"9=zڳaz s}%p^5`,hoN~Jxd~;B jwgTFCVclSd,iRоTsIXa-s*:EG-t>ğJX"[ss=d_SK hǧ'y~{j2K` ÍexlTI&yʞZԁ~᪸ nUmV}BWQ9MD`Ͼqn /ο`i$TעKr3ݬk-=mxA] Hb`#b\ ^y)Dgw06|bNmP`f&2E%{ E{S0d3)Fy!Pש݆mO/O&h@*-.>͍$lmKPYg5PCk-Ǧ *\Z&_&FLX?o-X=8~8 .+"=`Yδߜ7W@Ce+37q㼮Tw;?Fz0| /|;ܘ:o) Ds =K-a鴨\gWE > stream xuSyQa"AXHx\dDg"B+1+|&WY#]AĆ#t rt&TA>Z4s:¢gBvP#X4L,SB ]3i̜!>@͝[q?,fδ6Ptw'alPXp+c62@gH4Lx`Ѹp;џb B;E`B !@5|SGa5 V ku^(o>H0fn_T06x)"o1WB;Blľ  îWALd3Ep?5wO-47˝dq\xӽsiiWsYw! 10uL 2)5,fμ87 `px.1"`P @7C0sN0aB0 Q̯4xf.=eςAp+P/AIg'ϐc0nYXm,Zn+t^fD6r)m`9o9L{c" j湥i0=gCT~Ф5EkcϝWFWO;T&#񺓛Qz|%1͏(u#%[҅S.x^Ѡ[ꨂJvU}E*&6޼d(۴dzt̬]ӣ뫻5S^ّX}Dkm60dx0t~zli^Kɚv󶞆{k'֩#%ILf=?x$6wjVurhu(237k<]iu4Mтָ'" ^&?S^PZo#fn=q-ޞ'IS 6Ɖg'v5+:+E-%F#/7삯O$1w_H\W8PAݓҨ@BT9>2hZJ?U7[qf*L&\꺪#oXl-Aih\Fѹw)}ʭDءx5{b 2+: M%w:~uxe[ؤ=j*/ާ z:V]q[e"Y)sa@&YDtd[~Lwp[:eMY1uX|ƹڪ~9qluL,a$+o[{$mr>[4|x~p7>Qi\XZT< 0\8e@<2}llDUޭ\Q=D-)p#1ve9k|U\3)J)}AؾގWuЉ<گ4kli3[}!FW7=81&A[%E R9etI犓%?Hd)g֍{}:drވ>~s@ҞhReQ? {#nq69WxKKԇn7r겜p=*VmI.xu$ #c|?M>ՙe:Y`{Yt2C eͺiۍ{6i8U捞5 K֭^]%+ ڍ#VE\~E"Pk~%lLs+ęyoj UVHF`iͶ8QO 6kKZ$M sSC] ąhv~B1Ja:`:>LcKRa-4&w([nR(UK}5*a㧬'R4>o R:`4V̷(2語rnxjo \s͓T҅ اPPhy`#qRãvEjA fR[SiNuC%eNy՝թsG9޷h{cdE>!Gm,)hi|-M7Q21dՈDZêhEm 쩒\h endstream endobj 355 0 obj << /Length1 1626 /Length2 11920 /Length3 0 /Length 12756 /Filter /FlateDecode >> stream xڭveTۖ-E RU{@.q~5Ʒ5ךkQ4T,bs4.P9:C!,J@K΃JC#4sAf.@A pr8Pi@O_!sf:7=B5@ `$TTe2Z dfPu5YA@3`q-A% 08C4:9_ gu.lajWoBP'këLl^JJ_An5bWK^a^.f 3W-s  7| uM'f t'uo{ ;8\V5-\^k[l `>7kg^IYBK*2$2O-K{Z^u^30(zh\3+_u`+WH֯ry-UA.6+3ym[A`૮/>M/x-TgVUc׿U_E?ZQX/qqz^ %Y 0x훝ϓѿH- uU/ky{@BmJzKIhaf^N@?%|C2nL!i[qg= xMKЙJǴf\[Ɍ:>/戚q#X3 C[Nk(Eru08Uiozz;I}A2#K IR(.aP7|\p ]wA%[OT1^Zu|01,L"43AJf^#$;*4"Mwv|'d%P̰v0<(qp5<]E[zU"]H 'T3"ڱ6\֕"gDhNI1NzdPWcS'^&d={bm-%IYKLL ;Ys޸f:6[tG>,΀֯ac.}2b+׭d7J$z?֤E0<[Aˆ .kV9Y4Hn~B-}H?Q-"׾ugr"m;,<騈=ޖT#c{Ѧvq%oB6+ Ok dXx5#HO$Ā~.WQ1zoŘv2;KXCǛSĶ"E2J1;mj"!19lqv +@,Y-&O#_koⷱ]yE>adD[\4ܬڃLa]wBD:%bi-aD%P0 :&N8׾/FCõ3.w++U2Ue"ݿ]Im[ݸٿlP b=scBw |E-QL,>"ac4,=venSMB]>tVeI&I),qc3wkC暴"C*{qNNgEJ++pWSݖL`[>$(}7D bK5 O1QOY^Jca'N?ӉjQ[((Q^NKĖE|Co$P=,:liS3>C%s~ΐcɺ_!G2 |su!120GE(kYi!.dQL ﴭ,&C0E`,9{&GP4-x[DQ^47#gEq`Lv,u*Pnb25혾k^/Y5Z_è vgÀ v]ᾴ2, >Е'f@=M ѷc]$ڄuLP͹#޳|cfe/VQLSbc榅 D|obTX <=rٵcG`шF>jؤ)2-dS "DnDGV^tZ]f1ۤ|~`آ gc/"_>p灛b/+,R˟bJѪKK~P^! |߆rdfr7?}q6#0 iAXQ׍(_k c d (5GHb*5"߽0ʗ9Əg]6`D^=[UHۤ5IƙMׂE^u.1VZI0Nu03>O +.ݞ_t MRz9#Qb])5uGX#N3mĈ:`'t{ޯ&8()!i Jc}!B:&E@ct~X>fHv)rk#:L l/EogYרK,0QۻցMj\leW~! ?> ^7?* 6ŢcTNH֓9C>Zī~+SN;~K>I6MXZ,.R3;?u?Ri'<ј7a6Do_;! )әai(҅i\ U}F;(3&?s*0s*/&nҖvOuh@m~1<,aOR/z_-Oj;O̸Ss{7kTm% lZ"4;uUx% ό G0陕2P%c6քDyh"ӷ!M)Pd*@/眰,Y/fxoEAp ,~.al8/A"™Me]pm >S 'rz20E#sF~1챓zyf@ Mh4b@1#md=P,:R= k5}%Yx%{ nkq3M@HIvF=aGY*VR/3R~r[LL9\,?)]W_נfMnqb6nItwڸ~ů޽E0wKvޕdւ!wr'X|WOR7G7/1=, kyīL!~;ٿ $i,LxgBoIN!e1)Km T< I3hGJGvIoӦPx?9Tw\]S{i%O>j ;+ 5*HJLB@t*ǽ/jBc$lO|* s*XWbCfb^0TKA@.IEdum̒ٮq"GC F4S) U&Z>dʆN:t+6A N`| k@fiB“Nn9{ϸl0;Ihb~V׷_bVh* ,544_S/P|i` m*/@F8{CEݑ tws?F' oa%iwVi ޼e/> ^$'&MyUn&'D0&WȦ0|u05L)>FJv,c리?\</"׊S@!+C{rgM!W :piOkR7]$UھH q`p~v{ kKH!Z;%1z@Oq&X6o6DA9ۋFvET"b1Viȟ.C j*ǩf*L1kXZǝrw!Pʹ"|tV=J~=KL/d.Ij R:Tű1( =<AU_Ua(=F%.gf Pz܊`IVgz @0ži KڜU9\N# zJDGPћ) xnE+JρH6UOpĕcogelE廡p*6~'haÙȁmYNZ'w]]3nӷqz5©R~EPs^3i@uwfѽMqdI-='1k759,٩gF{ H6,B'\񱒴8L9Ñ\b4Wrc`fwPsupo`܈e[\L#=1+3׎hM# 8 aěأD Hq `(1f+ |,hlRCc-nѺά>|4UvKǻa7\I&[$5uB;H[|ep,@ItʶrWf䌽sDl?OCȔPSV@ztq&ENxeP]?jko,W+⅒1Z@"°ʼn)9Koqw:|Y[(9ʆe{(0: ᕋ4UD[XbIG;#q潴U/Z<Ц9:xJ;sB۴/YvKJ^z{uZx=+w.rċd1;oS%8u~!& ?yz:e0){OC"WRl2;Iҡ3PPC> 3>=tK+$A)>oQ~Lb \?4f\08N{3ڢȨ4G lI]ؾcvPs?|nK`Po0^v $ =oM3VzPg.SE?wym&sZr3%Tj7 _J/e+$Zjʞf7fRcXG&xW%l-0ߐ I ME_!R$udh%,HW/D=éviCR2,hJ8e4Sۜ/~#zt0y%\^w%U9Y&0vD8Dew)(ee*WˁHH3)g}1z|bb z[DW录$AeATq5Tɇuڰc mr7]e5ZTV@fh= )%+y ןrMhmC΁$AJݩ1gjj4rtK!RӨ4$F5pӾJB8v&uhQ y05"8ckg0#!}X>zBk\bZmDǚWn)e{C!DFye78vyBu5ot?Z<[^ PUGIc%>L}EN|_?}k҂z)27" 3L(I0qWeO^N𲷂c/ aM.=;dRꓒVjqALq98(G. okG172Э)?eeSɽGmH"i](t{z@vU %p*_!TR~$t ݀b?1Xh;Q!lxmf]@q<_?EVgUxž3~*v%mh>MC7XK@Vۨp̬eW>;Pd N;X-"O#W@;d^WedSjπjk^Gsw8/T~G h סp=_:ɒ Q }E߮_T?HX1ܙT)t(ӔkScsIbڸdsF?u6ޕe%xOCRu|-K YXeo~F۞<96ZjQM% \ Q3MLƹ9 o7GuvǀXc%r;0èE\=zy6$7i#S tĤk|f6Ek)Ҕ* tFrSVlg˃y(?#q"L憗a&Yy 7̦"K盫ä1M"^ttC%`3f!ǡUxF /[jjH!h *O/$y5OaڡjI3] `O6}cs:.ޢ:/ ڠ<}]5 'mR[`5lv~IA͇!֋r6u7FY#'X7i0'%C3;~ +iKz{>iG9IxZDS.U]jNn?#O`F\E;Lr]Os24V`O'<gGSܢ=ʒ0qdpYm!\@ډ -ѝ)((@ 'OPp2SeW#D`9bpg[['QQ13DIU۽&)(%$iC /TR<~xuda;Ic0:"<LɩN`Gf`kOhaZLV~ΐHA<4\u D@Ew5'kOQ HaL. mz<`ȼ)/65/u. b =r;ooZ3\ kNY0GN v޾ E$2f-TLn8V =1[Q L+JumxfieF]3X~]RRSKAA0!jL|<{= HٟI{#HA?+1&3tC|Ԩ~*@OڔF/n?m6*Rv0 S0-h-w\!s" &9I}]1n lJD2u nt]cGKIe#FBXP(vb]HO_`,r;"IWYFkdpzfPEI0J*̂~HMpі᝚kyݯ|7Ñ5HK2N./ &i>MJR)pI0˦G}GpPʒ'yF;5>_E9 -0J&jƞ(}ߙ:ﱉIB0O:qy~[yQT0/ B^"Jh:=ps RNDޕ@ ¨#>SN\f"jά[徳k0'B<YMӍwՒ8i,Y 6"omEryk>/5;cG~HcIġNyv@Bg ^HViEՂ (..i&k|+W^ sоywgINI͏lPuSpK!9QQ$X]|Ϙ寜BI4 1x,>^յt#hdb*liK,8aڿrZ)3, Ӄ/]O]a]Ғ"nMwv2TЖteOuLPA0R}ӇP;XG3 oya$ROkyP!MQ- endstream endobj 357 0 obj << /Length1 1630 /Length2 18363 /Length3 0 /Length 19207 /Filter /FlateDecode >> stream xڬctf]&v$۶b۶]m۶ *m}q?ckkIi Mlir6.NJv6rv2J&f.rVRRaGg ;[g.1@`!{8Z;(T)iS ?4=,ld_\MmLlB_;*M&ay I9q*@bhma02u29}[S_,A'⯛?*woF.$WnjZSsrv2rw "< dW 3kilgOIu6u8;`ldom7_0{GdakM M;Y'_v9X8;X02i7- ?"ikj`d?t&j?3C7 c;[k) ߐ;Hoz/>Wh1kk9d l @Ϣ6pXX{ɿ?I:m_j-pp71Vp62Xٿ䪶&&W[ Ebnade V եee%7 _ YonY;yFHEe`{& / <8;Z'#jkdg(;)Gm-U_sobnblgl\;2%5:b_ڨRT_cYQB4tfy Eu8ևaMޛbrCB_IN}H[ ~u( Ơv;[?uLOZ(! ,|h|td:'7(C ͕ݩj)U9Q}مc*c%3ٷ >aUX(l SQ 𬊔n}ײ EWU gQSxIbfhRZ$,;K=?(B@xEҮ@qnoPJ`}(m4ۯ P'7r=& i˪#i1ȵnWoa6M\QɺZ=i M%P#=@e|1ry,ɚMM4Z';YB)3O,{d)bPƁOj~B7;5#L^ڀ|Lo@ $UmGC{f.Vw,}5{%cjk7o̺BdęZ૧Lu+6Kᖻ纺Xm=d)NڙȐGqK{SVTh(fA - qwRCzruGc|,P`Rp>DA,R&Fll(b' V{ OW*rpbgn%qnoI wXd؟/jW <.s*}v(rvW(N)ї+AQAk;iwjcŠ@Ц'ste@s_1JGL@O8Q9(z&̗v7A# ⭏Rq2ĊrOGx*,*,;j&UFv6O77fi`J>'ft3 PK<9lH:VjV4Uv,~HQ!^. q^Lkߥ6ZfSY5)8ˎ-9ؐ쓖_I_y4"$cmzG#Cx Χr(-4 aM( )O҂E hSG,Πj#^SWe:tp: h yrw>`% EpG<62-) j[\QߋR3 {jy>߇G5WSJfdvgËGR?nl+9Z:rVԨ}Ԛu~(&0 $]rZ+Z-:_;XlN5N齹QM1e"=.`w1JuBN=m|`{zHL$2)>` sb~iQxpN 9 8k;ڳ(O$#_ ,%ٔuUĤREHd Tݱ$UYSTv16>銞eѽ Ͷ#W[|p'5q_aӌaʙ`4b6D"UK JzȖnEjEۚyѭhq` s4n/0zs7Hbp[Nm(2 d+vAx6 #o_X`G|_A!Uy+#PHvXJ>@ om] IP-< ɛ1z2M1\ϗCwu M,9ɈnJk_fnA7V^s_ ^7G!ŵ A0nZ %/#Ob%/fN 9dL0CQRx@BE}6Z M1p;-+) -6Ac*s}ba m/Mҽ!=jwۓg3H\3?2ͅqwx%Ys$@6'c#i9GӒNi6}#]OؿEo4uXQc,(NE̿JȚinєT`P8ϧ0>FAN ۟ LѨ7Gj'30&,8m|S=j#J΍s's8A1gpO UkTFŌNJ7v+5Čd AĤOZUT -xAn~M'$seu:Y#9YZb8oë.E]8.or-=LC{ {`}b y YGy4HVx3LjRΎxMa]]T*~+H % u"ӈ\̨Pm}k /v&àC@aEJw(2.N沇I_q.ӿM byLHO4-1EnKQģI;!Ol8ے'O}@́1mFOņ=g?/Y7'sVۛ(uغՅ̽C Miy QobQ>0|݆r Q?\uvDÕnʭHyJef~#r3T̴Q|OL[;URJVj& Rb q9ucn#VnqP/h~G[r\.SBۘ<놊nX5} DԆ"ŧoLSބ'Uf7[Gq^3ڨxuؔ6.\ê\edA[Ȩ޼/ lЁSZ*s=mI"%a'pM46. FQ V=ڤV.,nh?O[Q) :`}=SxBHԮߴ&m?cȔ^!aGXaC4A R'.1FGκJRT~盹쎺v B/^djFwsD}Ps^S%t֕RF-cu^>yԪul7bW6P@xB@/;\7WpˢB 6KMy(PQTk>!I4 \D)8+{d&DC>>:ʎƓFVРvqA85ko}l6ӡ0ͨmbM:cA!"疛#GqPڳnl A6~6 f=RPZitmXԱvWl]$̑ P.0Uv/cwxF(Wj(ݸ|SFB2M R 0RtR\°(f!{2Gc"Y/8`2?uR4# y'+hsbPRAڿ=!G-6IR<ْ10vXZGޔohC${Wp}:8r~}6.+9_wjO$[7#WѸS1}ԭ;Xo%jIX.So\fmwj h:2S uĄm7JHsh*56FmKaf6uB:P-}-x5 %hbuA$UIB{PZWѯ{P'e72NbXU0LWj~ϛVү}q#ZVB\?2n_3r /SJU{, v{zx]5$#{JCjE9 &MMR)Ha*U|ᾑ^ol׭v-yr"1$߽MSaA %n:=VO ^ Γ)}|Lid~&D?BBȿ D$nO̵xAb6f_B9>$9w0׏Y1DZ@{3}*'5eҝ#^sk?d̲ ,߶~u|eu#P*S?P0LuЏDžEI!Hth)Yc$9X*W9-x|g&89˃t459Z$nq4 Bbқgbud&6ba[~>2C;w^xߛԎ".srzWdh*1%1>8ln9=&YۣڧJ#:bb*_dՎ`,ƴYQJʃz3&~&0Sx򭶺pÄ&}۬h\V%MBO#92d~:𫯂p$b2I<[ =/~y` 6tLk9Last2FQ5V=$AI$6X#ɴя`:я >Q5ݯ];eoڬx?Sǿ1$랚OLnhjSeѳrm_?"1oAI26"M,?{MHI9#sfx=&tޣ>I^?/ӾYBn7f%~ӻ.YH00271\Q M݄Nz-/\pk,IfΔ'^=[e8ωb.ȧD!|N`kR@NpϟˎV\ 4('<0qa_D1J j v}(O]Sj(F}T=H[]* 1(d֜6\ )Yat }s9BZm/Hܱ^8Rq&v  6X `u[mo0miBj ۺ\J:4!YNH2I}`ɕ[ζ}[tLx%ł]e2 mO ?HXوN0B:IE#@7 =rX+z L}%3 .TaT pN1HHGhIʟ* '/URCKO>pk[艃* ?ㆷ򣊩)iKjj| &wjĚ=8':5Tm5k}~Czqc{{lo5ȲMߠ"oO)-x?:y=B Y }?~ Ơ5ԩ1MY?1;cE&P-ٮFyyATخ{rR{a/Auޠc#vȘm:E_aNlR-/.m :T9إ|t!W`k5<%Dz(,>Ӊ~M;ݥc&Λy'M:Ib@soQ>:ݗSW9JM26!:HoĔϢX~NTյcb!':;5K#:Kٶ1"-O*"elr*_ :&Oma'`u5MWjFl>_AsU#yLhCƈLy<8VLE),U@\އH3^jkHg.>,X:uYJA#0c\O ɒp.5ΌT함:[ 1AܣB06}]Z18\n4/ߑ^=EZ0SlISVBaCt3ٔFI^_1^E)~ S[mj$o{(}ͽ +Hh5ѥuư/]S5 0(8ߟ%턓;)x7mH> iicr}nWh2HE3e?R{ڻFW%$MnR;Z/~'4EHJܛjos]:yjMiȾ0=0UdeygZ6)ao3ekhZ{ij ZbHJTo6zQ-4 #6 Av`9};CV(mEP!t}2=`oCvM; KVʛIefɈ"]Q )]u])d!TG]ScKh1qFkY¬=$, X1һԽ^RI;կ}Y|h4rļ ? ⠠fhC UKUk:Ŵ+V%4x^h:Y-G>{Z#Ƚ~>Q{K*KޕLk,L9Uz!zs{0rϗWj^zUmډ2`O˒ ky'KGŵ/bhWl!¼eߗtd*j٤x^ 53vz KTVdk Ch6kOH$?{) r y(L2ܞ%eZh~D"]}"aN#I 1)-D,[wɘ-x W= pҴfyM~!WRDeO.X.더m::el0:(KQl@d,`]+<oR./a:KSBjCª+'񩡦qJ!lwU{K}:d}vK-wB5B$u)֮a'jP%非l~zP($n%ݍyj.R$?̳"$}Ib[ S9{(6Pavŷ -ҫIE/> `8zxhuuz>eNuvLM<y/INv2>czgVkwq\tSoiɘmcrRѻ+M }t%|p5f<-/P#paͪ5jVIq%[]ޤ[ $ƬYqBD;2T`U~W=1u (G$ @Qɹ }*Pi4u əv\I!{u ѭh1)w=Ri"s ~d`tb786>OIwc{J}DVD"ȯи$׷z o,x[mE84M;6|(Wm㐤 (MȤw3X:^"S=Q i8G ͝2t&@1:8M̰ј X/)FJA]k@?@5bgyHaO%[@6EɸhnѧC%| %0Ĕ\ֻ=yy)\Ej1(zK@0ƈV Q;D;a{}28ĥam3aE|B1 Oͳc@qO܈ NBr -c#GDP dÜ(읥LFEJˠm4A>G!9<̍f⚖nm})57CJ*RfO4c9@CvNC ,v:V-rVC~%I/پzЮB8Pτj RL.N6 ,1u5T错Ikɣn7 <ᇢgS"eea/Pޚ|&ؒKB  [wyFz4na}-ysG)BG+(=ONyQn \NNjTnT+*V5r T/j.+n)S%w))yļ6'< 8 lx'7]])C 諆CM=l%(ij˙RmMe3̳s{Ƣ^UE+7cfpEOc'򄅧;DRf l~PS&¹ /am8QƧQk)|MOʟ$oh0J4wQf|L~Ni>LjukU̿}7+ZlH1u`I9)AC|lj`zzN>"qMPkq5ǚI?Oz֪GS`V1*t>)Eo, 5gi\ =go/'hla{S>K̪50Fw4~iۻˉtuM* <i@- W;=,3@R0[FA1hB֠oNASGm$KN|BoՉ.Y S5i~~-s&6|D1Rm;W|XBڛ.*9}`*{hL$r870P.3I;y¿\Yp'N9[7?:txNsDrcX_ڤ܏7r3Zz!A;4lGrbX-ȥe |DMcAqV @tJȐ~RUweW]"U+#JgQ"-0v(C\TmeHbXNR3@'8wTubU4"Ocfr*C va)#Ab(޸2mKFbiİ2*F+e#NMxȩLT1\oYpL6Whst,"-7 V"5\1j֐*ٔ\U\_Pzؙ?LZr{?g}PdkŠ!Z|{hrmoE30-SAqdZcWjVKtsl)dߗWMP{4yhJJ$(KB:Ό8Lz.?0Z:DZE{ohS ]cž7|!U(3zOЀ rU;6ÇFg\cB֜<_ҬKtV=5BPWjZ{<v F1OmU}3&dу Vl)YluJ҇BK.ڃ YqQb T0l7YG5єkĽ~W{>|Yk~9\2[;G,5f,FXJ-tRPf9rxb,2=|NưEh S%n"P%x42t J? @\x< =fm)YYImS@} ?0{E, 5ʼnLT (MxL?! /=^Kw4o*eYpJ-k@b*lϞh_emZb4l06W8ɳ̙#s.eh=Qdw}.e m5pVٞp~?-9A٭P :y$Յ%pTwh;g0%7>*3;Z*ǃ4Ω2{;ޯs=ji1f|E^\G#TkIkj7DݰcM)T"ot-I*¥md=Il`=zg>"B=$JGbDjI%#x4vnV?#nAɌU % ?LjYD+de`k14:8n6NnHP}#JuXz57jB0eTM,uC< V˹@xh,C2]8gY@58!xq<7rWo#4#z!)l@yT]"}?ps[{ \:^8 u뎣sYY$ؖ}iSro@72\Ɇ&-K^1F1+oxqFn ڷHՅ&_,!u-Z/)&CA-1'5k L0bȱv;5d0Rle[d~, :%c+)lVS Zi̋X1VX\Q7TtOJw!o/v  г_F/r_@\,!p?@> #jrbMib+p8cJl6.~#?+2al9(p2 {a3p={гpwP~hji4fp>B9UQdyfѤ;$\/qʙGQڑ=ϐVkFF@"#R^&iFB U %y_`U]yD4?Z̪>?պO-LdERvP򒦕r%<=N>}X"H8:[04j`1 gH #\ٓsD v7ȍCwoFZuO$n>gT!OjL^˸i rU~I.u<2Ήf脣)!Cfai&_FK٠n-Fyz~,[.-l&v]MRXQYw '"&p@.; ̦T;45;uhzO-Ȍx:dvb5WQWp.$ށ5JotoE=}ߛs]+3×5KΥuh}zGíRToHGU)54!e)N5G33\;嵃d+4<5v~*laO?]\/KC`L*v$ ӈҹi0>#I|}v&_)ae-Wue?fz` hztDXNr$OCW;p$-ncv9-p}<:V-mtd╯tcop΀ddZs.uu!=̪F)ce+ʴYy>hSV0].}Pf{d. ]vC H0]CJUj3r}9n4Ba 3zŵA:{AzEz̪ `89:ζlhVZ=`;lϸ;?k`]icɆ8bB踭 +%_W+Oɰ؅(> j7F^TtTƟ&߈[m+r?G=M/ɞWT^ 1mɶz" ׅl oTfgл]VT*y{LzNT 3$=Sm#ϾRS|t8MvCBƷ]6LTF7x$&xl0i6;ױBp}lw %O_Ygy=C^+}%?6 U:>Y:'V& ku[-ԦwX(Z\ >TcO̕gv 5)I2k;P5![ :TMڌ3OKH@Lo~bLL4Igtٽ|SvNzkDS+Zv:vZEk- Yg#ՄJzYYЁۣ@YG_Y1Tonh]P2EWcvЇhU$9,U Elr2P*`f*cϋ5J!kZRs Zd3`a8:q:XDdGZ~+W$~"QVVPqkfXcwO`_ :iU}e#N0,eޠlfU`3pY SH{Q60*a"ZKZ2Wٲn5Pף;e[GZJ@{:ϭ{Ŋm%úwT9&Q HywRQ/lΓwGXUכ_#|AP@ȰWҎX\k;Dix׷8FKg`R~?]6AHQX xS>{rp ;hg8;_wgij϶G֙P[uQ-V E̜Fdңk $j |Я$f*2tۜۋHc9<{zI92ádtRD H8$Oc`E3ǜwV,:*|59im*l9f֠&K (-C߻<2ҬѕXl!0JuU_dTV woS?ޛ >ZjY)ٌb Y{ endstream endobj 359 0 obj << /Length1 1644 /Length2 9025 /Length3 0 /Length 9845 /Filter /FlateDecode >> stream xڭueXݒ-n![nCpww    8|ߝs93gz]ktյ$ 9G+';@`(̦ vy褝A`G+Hd@.. @ q0h1S ?4/.`kduA\_ ׆Z ۃjFyU<r6Y؃-`Krt}X:B?`I.P% a Q g?v6Xڻ"r+  3 x.#w6|_G@GK?){yѺ!.W_ 7|u bX ksg=Ou'?eo{e׭vu[qrt}m ++G'r?t g^0:B=@UG> [#_9OCsW5wxiw eɘC/{ h͝z]_" ~MGCoE]-mV/uK /:m@fB_9ђdKML96Qi WmO(8q(%'`|Ɨx|qb qspprݿB,I|-ݜ_(k翦Y-/8Z ٦f tqCK ;R6+j&Z<J{C] ɠI'AU&gOnJgIOBC^EsƋRHOj+Ե-(-JX JSȻ(7aTN0Ѝ#Ì.Oܒ(dC"S[u6 g}ĥmQ5fO)\zTm2"xjct;N';tw$8  TBQ9t%4uwb̊M4-B0P SU,Z5ח's΢(x,TrFgv0Ep]\'/y##s6Wi5j}VHb#7yp1K[~kp~pr[tTowܜ*v IDE4ڤ;RjrMݐe5^2,XFYGƵrƨ#HN/J7KgU:$:> /ɟz7Ux`X&iP0Wuu[nխ &يYeA;(4xޚΓU!lyO{vYRj 4lzW;a9WXvMouvbuGڷ?gweq+9:^eM<G{S\ WHmU9MzȲ}vc)T׃!H,,w_Wq<j` (-A8<.5v}ܧؗ u-EY[Tsإ\݊0q7c!tHwDQ!w6k1Zq`.ʸ7`"oeҽqSڶ_x(qIEeѥ7! 9)} Y}5G7I~iZiGV r&Hm%D`ho΂HGEAa.!D6hV*! 辤FSkO&WV_x sMqsUPv58­ȍbHE>ak/w+c!Ҍ8XC-}6\OTY=?&_$}dP$34Pp!:i?cVIk.!AMRn3."xiڀ~Q{ƉgfȻq;ÄV.xv?b6{QV1 pyw,5Q7>Un1M^3^gD`uZ#0&c#(6;.o+ .1YOR/a(: ZVܚOaNVn#o w\4D&RnS,5DHC5*LL}mej*ɻ;7 *|°D> "ƿad\`x+}蠞H7$F5b/]cu<4NS)A<6eyvU#@[Fua=pl͖Mc!2f=.Wd_ѹn0PO:FJ۪KO5G!%G 9;iuE*,jGFDRk"4!8@ybQkUsF>׽;)$ ȘGVSC#QCs !QrE{Wj0pK!#?{Ed+D_#|_~J 2r{ -Mޏ,.X{t\ANj4 k5.l(fOz}@ѹз~ӯcAO\.w1Z u#E/y_>D zTX*b6^Bh{?afj9fw3r)U-aYw:/WG c2 -\UtkԌWR$~C yPIKҡϊBhi mh wQR".5n[hMmO\s%$!2Fy*7**< M\qu ݐ%1=~erq31^DBKP wc"27EF`J6H(V?yu$=,3+6X KdızqC'%ub |!GNs*PEt4ʹMKENq];~;S†UE1|<9a*$ބ9Rmv0?ɱA\M)424L]~?[e{0j =7e+tTtaD8VSD o*I>a&LWN3*,_]l\MgNoT~vM&Ƚxq7#^,7VuEma73I}H#?\;H2+mCrg-l`$"bŲ<5¡MvtQ臭|$PSdxa#;%[Ӻ*P:ڑCϩ.=t// $ACg1CRp›?*aZf\fdEM)lza.:ˑ߼=7y]L0F1!#=7IyrK%69w\y)AM#-ۃ*3^NKb պQޤ 탩LWluT ./BAۦ ƕ8d$HLg23!I99BI]sPa:ezh[lz>ileal{ }Z;|K`\Zr|pmUג}L6ӂX%7D'?\aCcЉtX2n5tf $Mg6+kԮ=*X%?PgWdt!9pj:Mc1n", OjJū`\wDN;]S#w¾.x(n֧SED8yuvEDѺ*bcujVj+'Y'oV[?S7owYa_;zzmWUu-R@ d^Hݗƀso `Gv~nb89 ^zl}PcʊP~ȱF*+R*ܭuY: V` kKe)HTow޹^o;9.(֥9W* \:5ZMto. aY$ǁ"mv"_}cy&&OzCi\!,R'>hSvf81!6IFe`^!(uS嬒T[TA}!XnQL钕8a>L/`D M`ۮy &/%plaմ7 ēpg=y(|8;rԼAcbْ2A-w$Hx&ߜtK><6`ئA/ ?THLYB8fx27{kqg{,$X9V=+W$S8}LZ]#5BdW vYW/+]ǞJik$ P䱒?{"Fߞau{o4Gk^5EW 3v)F/΢qs1$$ܞL2N 2?OX>&ij< DV^TVI]@]y:Az(0 Y`nz4Q-8uw…sSS׮3iI]vW4H!_.$n2mXN GtB\R]>鎿o_iD}q5ڕfѥ7x>8;iѸ߉j*љ@-D8Щ`S#Zn>/44hUkKq2c+|h~n{=(QF{U8z<׭ŘpdM"j7W5u`y sLhND&v{198ۻ'w}d 7.5FU1杫zOnӡ u$Y:_a^ [<`5r-O%ږǃ;e\o޽C7NV5IEc9Ԅ}%!VņXѱfڮ"ϐpTɬɉ("}ԈdbȞ1K;GJ1qGΑ=?TޅJ7;;pݘt[LX(Ot&*y2)w-%%5!6$N gr=gZ@' FII(f RU_73[i7QͿTL!${;}h`QNI`"+qjqFPE9ZmqƼ&sa۳ Z>}$}j;']}UݎK~8gW1C c# mْ=L1fac^VGZJF<⩮Yv%@*\&5rsO9RĒg׆sms8D/x%P;mNՌ fi5L6_e~s./D|Щ+.\[nf(t(&^LRa~BɨO~=߂+N:_Y[_Z#F]mE9PGz[)tܣtN$+gt[H^>࣡D6Ûi#&x?j);Wӛ) K:Ю~$or9z*ю٬N]6Эw d# Q]|cw[@d!lYðL!ޏDO.8 И. BI.IԮ%8)ExPԋ؜66=]rm=7w9H2SD5?/cۘ>9f3 }Td=V+de9,G&Ȝ-< N` 9,J7ߜB^[~G[?b :&\ }QSo9Tֲ&WMU$7!|>d57"nST"sO-˹n5yу8d 8Oy+dofm!Bs(.8}yΛ];JɤmŲ(Kwq:"n*Mo90އYAF=tZú̮\)Nl9hoB*`IOo-+{HHB InGL}J_%>؆a &Dwd)o\lgsO{~7oMg ֊y6n7JB#t_Ć670i7ҩԛ(b5w} Uw`%I8ͳ3/xaS*NI򚥞J~nFؾn:5$jN*d~55h+s#STDGۊ,9*[[B~m LWkiύ+$cgr"jA?zZÉ[sK7n]F,*wffGct 7d. n \gM@6Wm#>)1 IbKñ96k<.PhSΣP#$:$c1ΥrIx?D_2nKT2BŢrR+/[kaxUl}WZmIڐҕ.*W16GV鏗yG]Ɓ:8SVUрp=s= IxnU|b +H*؍eP@*zWY2XkX05{[ܪoL9̄/{Yiا`QGvv+<X| >_KQqE'n%o4]0,e/!kۡEUb'Lfԭ^[MZXb%ѱi%Z:>ы)oh7Y<*qxG"}1GqM> stream xڭyeTݒ.ݥ 4ҸK Aww}gΜYgf~twWzj?UMM(bno 02AvΪv@KW[Z hZ@s8 `E;x:,\Zt `Oͻ3 w]!ۆj@ bJ:2RZ)E  tzOBd@: =WjLX" n0:b8@3~.d]llrp{UG.V&.v;\Jo;̻v=\e lM<}98W '-W@.@[ &DVwf.-A`DEla`e:7DWнabn-]]h{,3@ +wKOߡ%]mmM s>hLY5llM ejbn-?WÅ!FVN&AΒ 2 `ab~x5@'[Nno:u+ /68=w΀YIKSRR3mެ^.@xR7_PoFV.^#7{3#/@Z+8<z,L,,~27 _ub6/6surzgi?74C\7NIOudP\^_e%%l:~seG`'-MG4ؗ+cc7n a1J7Yuh].QUâgX6v';:J<l[T?5Gniz:.`vbM\<^aݸ]QR_\))b?iۊEY)_CӸ}$k(B/woXe5~iNBdeO;42j\U]&IGRuUiώaL=ITJ͂YrKQKZ+niqUv\i_^ H &ƪ8(8yUD;>g-Fϒ͔/UfijĿY|R}\c[PSBp&>Gf^5Ie &]o6,\mYm_g1[X\l1FSFPa=eQsi0zo+)5 XɏIws 5~b]&_塳dXsdD4o%d9{ͅifC[ZSo (5v}8ђN;5 Y"i:l9um'ԉU bD 9Q`X­i&j[pV 81먩?o64CHwWz0c`± ׅYJAVg,{DHi, R{J@S`>r )5[s4 ?hj>ɀVH(Oy'Al<6~uˌ 5?TFdȜ.|>. b43}^U\ dYeF[,@GT얟ϲ⡾|DP+Fhnp\jtN޼"kn#bDⓙARXV;"×5h^7SuI),|K$^*X 3yMHL49xrNw2Zڪb;^9r%BaKΧ\qaC>A~Y,59F<&XFH/dѼ)rv4k;3,ϨNnu []: ]02ԓa nHl@*նx&pS9z+ݭmy g6@Ch3-i,| C$N"Ȕ[ȥ0_.=2gݽϓ'ϔHBl\ 7x;Vh!zh_ sn ttbY%Eק0e ~?}&ADY~ąENYN*2.n5 SטJ c$puP. ;"`y}ߟHvq%߱1g7:'}qj1Rcw\CB}h=&QԽ +Xp?ԔieQc?#*];~X1]![g175W,>vƀ n5ktyTOGx5ٮ ͆P0C6 I4?7{)KF kMp:l !1Es:k0x{yMo¯f_( }M2%G*ف+{09>mVў{_cWj> ϲn}0Gp{[tv qcK|P!7B$NM'#Meִ=H.q} :|%Eig72d/_ w~ߩ# (7k𭾱n*VW Zg/hY>s;K{8z1W*wwJ֨O %˲'>) ʍ<,RZt`#'nK Yj 'lA\V{rHgMA,+5ѥ02'jts:1+ A}V z^8VI ( X U-x:u%\vli6-ngpe%ݷnތT,CX<6Km'?{ e˭ԕ~/$S=-'b|=9\7s4a# B=v -8U*+_1XkVk}Y{s·q0t zmI7>thT@:Ü~[f}K-A=ڶBr\>[$+F& vwqAo_͞Cߧ/p׍ƬͯdoUgS)6k_~Qu!|H$w NP[*hVHܓG*aW\UWpRPiF"++g`B)^"X-1p7?ðPQz~kC$&R ^3SGm!npB9_[T*lx ?~Kq#3*.p#DJ5.Q vZ/0U1x F|ki"פB|N8wb;B2D"/pQ$R q֦?ec~oU^r 䀬1Zh#SF% cI{kAzu'B& rKxlȟCؓp}- K`߼T_L&Y(u-tSEĀu_yx TJA!jFDet{D.%eϴKeg'Wӹ17dqa'6+HI[ Lȴm)J܉y#8 [%7^|o)*aW% Ia)2`!m#˾+?A|D xòTd m3ܰ6꬏nLO'N2弿+`^Q KMゐe*oEKLiʫUz:8 S9v4S@LRh6C$btb;szȋTVn_է^1$ך~)(k Rr冷*D\iE]BLwP6F\G:"`H-L䂛Pi2|݊s p{)i\.}rm6\TymLj$]T[=8U}f#eڂ(t0eG;hSW,;;$dfC]\a}Fon86h{W3tUWX^{Z'Ho'<>jofDYqTeɉ_u=( $\WUp~`v6 i&#H΢ų0K1NcI6hy8k| <'FL@BO6{]OӯfxK+h{f>5uH4~l8je:m;`U+L9r(`}ʡ gH 9ɦ z7Tꥧp[QOa,dqVҋ O7/TYz0 _!X* rs ѵшsBFfbWٛL: H{UU7:-X^я\"BuimK84r~\-qLCBP~wxЍbb U3U_?n5gO4:"U7wѯH1j>ǀMGς)k)y/ic%/҇_`Ȱsd0+pŎ7z=`-X垲Ĥ5Z'pB>gS=1$xҦ9;PY/9W}imfe|i%d%Gҡig?if\$-)9;qƀetN9fDGUť'-5iv5,)E41DR+I }O<I-g{,b4ڱդg9Ne>/fE)2bgI]+\:Gm [Ývɂ- OuS!HIφaj" _K&ln ryș t'W,aZU3bP\$R]g3gǪʄ Ə'8B_ U9Hς jr I|9fjd@r`> } \\}DŽh~" s3YhQ#V@93QVOM<'8WxƟ ŽwTq:Q ڡ݋ƃv~B+4_/JE#2 U&B=69 3y Έ!D̈{>chX'g[7NxT0' lVZ&".q_]Y~SD/e$I?[!MMe`mP9>蚤8@8!rY wy>J0Eq { g3q-miCK÷&CsmtF@ ąh[XSHRd,H 8Yٳ~"N LGk #wkw5ٚh3sjmmaQ}dG0$Cq |ߘ@pϽ4ΣKSdV&_8'6) \5Yx1Kn7ksv+ǘµroe] P3: m]uV=݃4'v~LQ`*+z: i77CM(6As< hejVz5ۊtDQ<|P~qm̀FHh%ʗ)B8s^MfGuX7F:acC l5}8;͖;_@dYB\Ȇ\[a!BLDl ye 8)ԼﳃKͤf-"KQP'TWpk+lXΉ$Cሒ'PqIAB)!u_"Dh7u¦)/W g |O7((v]+"ZTk6sguY=ULQlN`_+~H;ҾuidWlvtnzqf˲+>lp',xjFt7ÞT\)XFӲ: {Y!2lqhOCWfX;<HJ$^ΑZ-Y8хJa AXԧxyZe'.9MgddFáaݼ5}`?o`<$2${:5F =^kR9\;ջ).r 3SBү:0e'/mKHݖ#:Ke +C"(>k~C+=iudʹ$"IϻTήڂZkiSgD'$8zZbCd-MQ$ʼn|NjomC9M8$TyX{4 :?t59Jj,o.%(d,ş" Zdo;84!&j|q8jcŲvsZ!E"hHLo~=JR@(J[eH7F`/5`vh:TiKw2?h}Oop`ah0&iE͒*J>\,&϶TC: X*dƴIX\s_p8 8\`k2qhb|P0#޼ԙT}Vj}zW( C呐2ftJ%m} XFD>CLQǺD?N*XpM5~Q^,敄b|C1$M.I!-t;s$1Co+XC7oKU¼L/H~nf 7>ڊg,>dWݫ=ÁH8AqE!xWpP:F'epՅ*>[V_߫E#ְLg!FQ7"5*ņ};ĨOtFZt( Ҩ2;\8D(X cE#W`4ޘ! lM#ҲSPm?[=Ve>b F[m6o6dtR9?VW .ƟHm/0+qʼ1T3M^e6b?1|KTy?U"UobN97QFa&w@}8' ZLhp. 3'vdn*o*mo67Kį$+n9B>=c&(·w^MW6ENbJ b!9r ׈x+?Ȱv2:96uIkH)EoK_fg&&nꟻ-5K{MUdCw\t@[}-Q1K;z۳O:|iD_*y&69ۉ6q+`!/vZڙ+-J/=uM ;vb /b&R;{hO5䷄H"ޮƹGGqcS|A!%M{ YJcD3}wPnmʄ#sMp~Hnr&oh7d+,{ XSvTP%fͺbH^a B\S}m"@\h^0Am -\/w BG<_ ޕO g|Ze 59*8[龱: #cvmKn vbSpy|"JxfTFa^_R!3l+쫢 ߮e7w束{ zz; 3< LxoENdD9%/1:Fc;JWjem# eid1-o&YG/;a=-7H;^;Tbaj6' ı H4TF L79p>?$ :ma^N>Xf `F6r߿cp2?^x}lfk8 F{< f1Ag5h@m~}SQ`zAWGZҌ xML<;<$J_u(_|6LIoK 01GHEt?A|ENwiG7I1,(w3m BP*\)̾ⶣ)?I= ~6X.È"Xq\)tdn5Q'>Bf7XD]&Ugx%s?Yci kZVQnb\76_J44sB?Rr˭]-cMqRAHx@yN$@l 7DFhHQpC[ KIFcjv2T /B6}m1%LO+ 6.xS|Bvq^PzҚ;S9:\6T dhq Q_p1ͶZ;IIo*lxZUQ32L%ܩZݓqDnS=ffyԣ}$yd: p d>#%Kx~C;qG/⋈ԩrEeD*"ה^Svd=S# Cq^HbWU8.cɫ:Mh;";ׄ%0PY(mlBuPJC%q ӌi|*>O"ϼ-@hZ?˂)˰'ވkdg~jg#4?ln%a$1g촁l ̡se_$.&HMTg{zZd7f&LJ2ػ@c"cPW3sSpp2&S~)N!(!m(\蠚|B|FpF٪T}J|I2.Ci5k8?۹6 @WlN;p xb~9hyFM Y9z <6T{$+Nlf R뇬k,__'G0-͠xxWJE$oD~.ߢib p-JYkE,g7 oUBm0ispND<#&h`ZOM~49 cv&{*#@y̤ClҌ˛89$GRdţ0d1kw>t{3 `9HtTnc3TtVq5GV"/+ 7٪Fm;md*"4DW\!]׊Jcfˈ+PeDQQAeb[ endstream endobj 363 0 obj << /Length 900 /Filter /FlateDecode >> stream xmUMo:W5?$R. d9M eCkmCp;;w~>|3E_?O]5߶w]Occ]=~?}Oyh9%?۹׬B|Ɯ>);vw%g43>\ 6 EJ78 1{~`W(-;]%=xe_,b+-O;q\L}UI--=BKE1p[! Mߊyu>.N5K)Wb٬8i[_uʕMzQ)V(Txޢjy!Z2P="Zd0\ÃGR\).2*Шa!U,H`+j.5Nα@VK-x%3%AYӀzΚ>kP#5m0Woþj.ZT$X/)n)#Wo(oRZ $Kp4Z-b\1ܰJ P"GXQi/8k^Zq:Zs9dB )sL-7xJ`aɽ)f$1 dъcCZC<73JgznHȰYɚTa,_-O87}KԴܗLloK+gJ.GZyVc48Wt]:P~`rZq.n1] S/Pu7Ue:?&?!d&1yHn5)yғBx#1ޞ]Go׏M?X endstream endobj 364 0 obj << /Length 700 /Filter /FlateDecode >> stream xmTn0CƆ@LE"jD;oƤjgy_xN{qV'wC&]\]]u>t\qxں7ŦmN7isƬ'k~G]?ߓ` 4;RV_n86]{̭֚u[sfߴ L:?v>4|`0nhWu}QE KU=5Yw߇l?N6jwwv Z/բ,ko{&PaffIq XMJ0LfhrdĥP> stream xmTn0CƆ@LE"j.RC~8iZ)Ayo7?nkNy$냛G׎ծU[7|SlfM[kwʽ5g x=i6;RV׵_n85]֚̽u[OsE͡i P{ LՑ @4=tb/yVvL MnݞArjwf4P׏ީFT]Nrî}sBZ2pmmR?\rs<, X#.KIɌCH'hjmJIQ09da"2rG~\5hגQv]`n @v)(A'b}qHI($ux-JBJ!^I :ggM597F7FN}Y{}&Ff.pdk_ ΜN0VG9ʱwDK4X=CaCɁg2)4X(rb0/s4lƵǮb]ˌ[r> stream xmTn0CƆ@LE"h.RC~8iZ)Ayo7?^$ŝPIs77EW]}==硫nTشxGɛz?{k۝=` 4vN߷u8NM>(s&`ywS0jzQshz+&TuS~Hxqq`P<+ OC톦}SWUn}@`T;P3qtj}w*5UWSܰo\ze \[3. 9ff ؤdF@!i @F\ ` H sn4ȶ` $(Ng 2R0zd9#Cb.k(@.0[Czr aà8SuX$Q:\CAfpGR~m%^!N%$h&՚R #ƿp'XϾ>AI }3Nh25gNE'bkkؿs %|V !3?fc91ӊ9|u 6ZcWCab d1׮eF-9Ag깐3Z=I= 6-7p?)pegT> stream xmTn0CƆ@LE"j.RC~8M])A̼7W?^$PIsWWEW]}~{SCWmݨMi7mv9I+ڴg{ҏÄ~F )P ǦkZn;@1zz5= 7m=x Fgu P}?i]X<;k C톦}UYoO} A`TS7~wpjmS!詺]]ꂅK(ew&97\=̒5⒁yAa>:M1ȈK,x΍t,@F*&" C,zdWXPv-hakH/]d"btv"gg?|2JB^G5kdwt,uVT Jb9;kBX!00a0bw3W M";\88̿9Earʱs ށ?c>+q p~PrL  hi˜c>:q-+01~k2#Ϡ3\OLqRυ>¹M \)s9O \Y!O>\\/Au*[ӺkzT%C0t endstream endobj 368 0 obj << /Length 700 /Filter /FlateDecode >> stream xmTn0CƆ@LE"j.RC~8M])A̼7W?^$PIsWWEW]}~{SCWmݨMi7mv9I+ڴg{ҏÄ~F )P ǦkZn;@1zz5= 7m=x Fgu P}?i]X<;k C톦}UYoO} A`TS7~wpjmS!詺]]ꂅK(ew&97\=̒5⒁yAa>:M1ȈK,x΍t,@F*&" C,zdWXPv-hakH/]d"btv"gg?|2JB^G5kdwt,uVT Jb9;kBX!00a0bw3W M";\88̿9Earʱs ށ?c>+q p~PrL  hi˜c>:q-+01~k2#Ϡ3\OLqRυ>¹M \)s9O \Y!O>\\/Au*[ӺkzT%C2t endstream endobj 369 0 obj << /Length 814 /Filter /FlateDecode >> stream xuUn@Cx ,ei#$JW)R w8`x3f_,Y}..=pF=Lc겺oxķCvYQ_s9;~1_B4-辒O~:p̵:롫9Dsg~&1^`32(WB0(~z?v؎r8ӫh~?u~Wu]t<(V4dqy5jޫ kOGKWj4?L%/۳v _NU4(61ȘH`Zp0aASgAQ@Q LE)58ZP\RC%4k(4mA%MJ$*C6TQ.c3p4ct| 1v9y\;摴.y.i*OYIa%a2A{&cxs4c̲ lcw36av7fgΘ4aʒOg[2O[1[3K?mgS- }3 O3ev/Nz}\-!={3pII)dKdgI$[d$[HI$[$[$[d$[/¿l᳔l1l/%[K~Jɖj%[^JlIo$,v)%sRJxʚ>fT+cVS߰n$7G"=MS8鬧'k?G}&馿r endstream endobj 370 0 obj << /Length 550 /Filter /FlateDecode >> stream xmSˎ0+$1$#8h@Bܰp`~ +8*=SJ]sCM&ESݮ`w z\ħmbo'ޚr028~}uHXz_z.XA_`1o"xR:bct\$7҈٘TmH@ ]W0ywznͩV+1r]oś}X 6g1ͭnm{!^ ' bނP48YhC`୤\UY=0ZĎiơ 7([4r;"A"e"qDgs"2dK$#В%#KDDNs5&]J[/G endstream endobj 401 0 obj << /Producer (pdfTeX-1.40.25) /Author()/Title(\376\377\000R\000U\000n\000i\000t\000:\000\040\000R\000\040\000U\000n\000i\000t\000\040\000T\000e\000s\000t\000\040\000F\000r\000a\000m\000e\000w\000o\000r\000k)/Subject()/Creator(LaTeX with hyperref)/Keywords() /CreationDate (D:20240222111150-06'00') /ModDate (D:20240222111150-06'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/Homebrew) kpathsea version 6.3.5) >> endobj 344 0 obj << /Type /ObjStm /N 65 /First 573 /Length 3405 /Filter /FlateDecode >> stream xZYs6~_() I JmI$q-j+$Ǒ9 Kݗ-Eq}&B !57%!LxP a: \@C.&\X(CD0"tDhȄH₊H CBC*Q %XB`%&)L~aB>sBOW8eW=ӋΖղZͳ:fyv[xtuOH!Nr#ԇ }RMeM }{ve/ *wYy>1='џ//+^KzEз7N3UӢthu1Ь>Ҭ:~YuGli5?u'v1|EFgt94lNoM)3؏ѻ|I /<*K,9]nyYK+:+صm׀k5?7:]ϛFU GoFI+hUsZEg7;/W O 0/&? ysrw={q%.qܡXa^9- m3}8du(p!XUY}Ku^Mu-ϝۏ+Ƞ=X[c]o4d<=vr&vQ[!xR'}+uZ\ء6PHd/0V/mIWbwzlW\s cgopBj.8{ }rc//^<4H.7-\䦦Gb=p"9׀wۼ,Se˙K As<6.γKxMλڔr_x"/!uUzq5l(U DCeMxOo.FnG(fp rWΡl ( f *p9jzM$!@'bn;ZA.v u6"}lB쳇O_p 2Aj;#kHcw!OzՂoć/69\SB2]- 0Np|kL@1ISXFJ,v@01|0WW mbӯöÔas]GBZxU9 +^s_x1iQV5&<۶"] ZY0wTm,Hc\2ex Zd1>@cWgpFejבuX33 y#Cl|7)EB8|s^YYm|sq<:z#v@D <^< &يEo%'O]}rur|@Xuc0C \c.6}`dHdZdnVIԍZq /fPm`l>?ro(CU>ͧn7˧Op1fe;ظ~;-$;,f:&G^1FjF?)OmUA̷Y~*MQC'3j{'e*A W^ivݛ5)V.MUXV|'?Rzl^z8iRV,u㰸1%HSsnn爈G[a#UWgDF? b)RbƔ P挪Yn~ (xK"/;X~DV"+?؜;b={*YG].[%:PPc{.[y-Rc ' T%5yne>$ȒP_JT؍j:$ CB`!7u:vi³ }J++h$!)E('Őa:zp}<&| V7Uara0= obqứ>Q At#9OϠ0n''cƹ/wI6MoOZ'N9"B6C endstream endobj 402 0 obj << /Type /XRef /Index [0 403] /Size 403 /W [1 3 1] /Root 400 0 R /Info 401 0 R /ID [<78B0E799D2EA137B9F23DBD5E3408118> <78B0E799D2EA137B9F23DBD5E3408118>] /Length 934 /Filter /FlateDecode >> stream xkWgsMLqRnj!&lTc+wA\8Bq_FD BE}͹߽sOJ)EU)URXNV <0Fgn&Lmljm` ڨuQ[v!XDulbjv XZC3:. bwq{R"|f?f/KwjG/8 nrH?sqH(|>V8$8Ƣ槜p^WjGQp ~pwi(t\g~g6cĭf,8xVѠUEt+K5 w5ʦ>E wblb(?gIek ܒ7ruuuuu2ĭ/Q/..-d@DD2]$$$DĒ Xc#Ur(ʑKnHHHI9eŜDDĒD< $$$$"G}hhhYg~mA$駇_"ɗ8:OŃhxx |3]&N QF"}9}SJ/d!H"x^>/EG8H_/CAd.f^/ŗ,!d2E"}9}@W+B!n7ĴEbZ>eM}t <ߴlv `. ``L)??%)5?"K endstream endobj startxref 131412 %%EOF RUnit/tests/0000755000176200001440000000000013267374743012475 5ustar liggesusersRUnit/tests/README0000644000176200001440000000012113267374743013347 0ustar liggesusers unit tests have been moved to inst/unitTests in the source package structure RUnit/vignettes/0000755000176200001440000000000014565700136013332 5ustar liggesusersRUnit/vignettes/RUnit.Rnw0000644000176200001440000005042214563457515015076 0ustar liggesusers% -*- mode: noweb; noweb-default-code-mode: R-mode; -*- % % $Id: RUnit.Rnw,v 1.22 2009/11/25 15:12:11 burgerm Exp $ % % %\VignetteIndexEntry{RUnit primer} %\VignetteKeywords{Unit Testing, Code Inspection, Programming} %\VignetteDepends{methods, splines} %\VignettePackage{RUnit} \documentclass[12pt, a4paper]{article} %\usepackage{amsmath,pstricks} \usepackage{hyperref} %\usepackage[authoryear,round]{natbib} %\parskip=.3cm \oddsidemargin=.1in \evensidemargin=.1in \headheight=-.3in \newcommand{\scscst}{\scriptscriptstyle} \newcommand{\scst}{\scriptstyle} \newcommand{\Rfunction}[1]{{\texttt{#1}}} \newcommand{\Robject}[1]{{\texttt{#1}}} \newcommand{\Rpackage}[1]{{\textit{#1}}} %\makeindex % \begin{document} \title{RUnit - A Unit Test Framework for R} \author{Thomas K\"onig, Klaus J\"unemann, and Matthias Burger\\Epigenomics AG} \maketitle \tableofcontents \section*{Abstract} \label{section:abstract} Software development for production systems presents a challenge to the development team as the quality of the coded package(s) has to be constantly monitored and verified. We present a generic approach to software testing for the R language modelled after successful examples such as JUnit, CppUnit, and PerlUnit. The aim of our approach is to facilitate development of reliable software packages and provide a set of tools to analyse and report the software quality status. The presented framework is completely implemented within R and does not rely on external tools or other language systems. The basic principle is that every function or method is accompanied with a test case that queries many calling situations including incorrect invocations. A test case can be executed instantly without reinstalling the whole package - a feature that is necessary for parallel development of functionality and test cases. On a second level one or more packages can be tested in a single test run, the result of which is reported in an well structured test protocol. To verify the coverage of the test framework a code inspector is provided that monitors the code coverage of executed test cases. The result of individual test invocations as well as package wide evaluations can be compiled into a summary report exported to HTML. This report details the executed tests, their failure or success, as well as the code coverage. Taking it one step further and combining the build system with a development and release procedure with defined code status description this approach opens the way for a principled software quality monitoring and risk assessment of the developed application. For our code development we have utilised the described system with great benefit w.r.t.\ code reliability and maintenance efforts in a medium sized development team. \section{Introduction} The importance of software testing can hardly be overrated. This is all the more true for interpreted languages where not even a compiler checks the basic consistency of a program. Nonetheless, testing is often perceived more as a burden than a help by the programmer. Therefore it is necessary to provide tools that make the task of testing as simple and systematic as possible. The key goal of such a testing framework should be to promote the creation and execution of test cases to become an integral part of the software development process. Experience shows that such a permanently repeated code - test - simplify cycle leads to faster and more successful software development than the usually futile attempt to add test cases once the software is largely finished. This line of thought has been pushed furthest by the Extreme Programming \cite{xp} and Test-First paradigms where test cases are viewed as the essential guidelines for the development process. These considerations lead to various requirements that a useful testing framework should satisfy: \begin {itemize} \item {Tests should be easy to execute.} \item {The results should be accessible through a well structured test protocol.} \item{It should be possible to execute only small portions of the test cases during the development process.} \item{It should be possible to estimate the amount of code that is covered by some test case.} \end {itemize} %\paragraph{Background} %\label{paragraph:Background} Testing frameworks that address these aspects have been written in a variety of languages such as Smalltalk, Java, C++ and Python. In particular, the approach described in \cite{beck} has turned out to be very successful, leading -- among others -- to the popular JUnit library for Java \cite{junit}, which has been ported to many other languages (see \cite{xp} for an extensive list of testing frameworks for all kinds of languages). Accordingly, the RUnit package (available at sourceforge \cite{runit-sf}) is our version of porting JUnit to R, supplemented by additional functionality to inspect the test coverage of some function under question. %\paragraph{Motivation} %\label{paragraph:Motivation} One may wonder why R would need yet another testing framework even though the standard method, namely executing {\it R CMD check} on ones complete package at the shell prompt, is widely accepted and applied. We think, however, that the RUnit approach is more in line with the above listed requirements and can be seen as a complement to the existing process in that: \begin{itemize} \item{test cases are called and executed from the R prompt} \item{the programmer decides which result or functionality to put under testing, e.g.\ formating issues of textual output do not need to matter} \item{test and reference data files need not be maintained separately but are combined into one file} \item{test cases need not be limited to testing/using functionality from one package checked at a time} \end{itemize} Moreover, testing frameworks based on JUnit ports seem to have become a quasi standard in many programming languages. Therefore, programmers new to R but familiar with other languages might appreciate a familiar testing environment. And finally, offering more than one alternative in the important field of code testing is certainly not a bad idea and could turn out useful. Before explaining the components of the RUnit package in detail, we would like to list some of the lessons learned in the attempt of writing useful test suites for our software (a more complete collection of tips relating to a Test-First development approach can be found in \cite{tfg}): \begin{itemize} \item {Develop test cases parallel to implementing your functionality. Keep testing all the time (code - test - simplify cycle). Do not wait until the software is complete and attempt to add test cases at the very end. This typically leads to poor quality and incomplete test cases.} \item{Distinguish between unit and integration tests: Unit tests should be as small as possible and check one unit of functionality that cannot be further decomposed. Integration tests, on the other hand, run through a whole analysis workflow and check the interplay of various software components.} \item{Good test coverage enables refactoring, by which a reorganisation of the implementation is meant. Without regular testing the attitude {\it `I better do not touch this code anymore`} once some piece of software appears to be working is frequently encountered. It is very pleasing and time-saving just to run a test suite after some improvement or simplification of the implementation to see that all test cases are still passing (or possibly reveal some newly introduced bug). This refactoring ability is a key benefit of unit testing leading not only to better software quality but also to better design.} \item{Do not test internal functions but just the public interface of a library. Since R does not provide very much language support for this distinction, the first step here is to clarify which functions are meant to be called by a user of a package and which are not (namespaces in R provide a useful directive for making this distinction, if the export list is selected carefully and maintained). If internal functions are directly tested, the ability of refactoring gets lost because this typically involves reorganisation of the internal part of a library.} \item {Once a bug has been found, add a corresponding test case.} \item{We greatly benefitted from an automated test system: A shell script, running nightly, checks out and installs all relevant packages. After that all test suites are run and the resulting test protocol is stored in a central location. This provides an excellent overview over the current status of the system and the collection of nightly test protocols documents the development progress.} \end{itemize} \section{The RUnit package} \label{section:RUnitPackage} This section contains a detailed explanation of the RUnit package and examples how to use it. As has already been mentioned the package contains two independent components: a framework for test case execution and a tool that allows to inspect the flow of execution inside a function in order to analyse which portions of code are covered by some test case. Both components are now discussed in turn. \subsection{Test case execution} \label{subsection:Testcaseexecution} The basic idea of this component is to execute a set of test functions defined through naming conventions, store whether or not the test succeeded in a central logger object and finally write a test protocol that allows to precisely identify the problems. {\bf Note, that RUnit - by default - sets the version for normal, and all other RNGs to 'Kinderman-Ramage', and 'Marsaglia-Multicarry', respectively. If you like to change these defaults please see {\tt ?defineTestSuite} for argument 'rngNormalKind' and 'rngKind'.} As an example consider a function that converts centigrade to Fahrenheit: \begin{Sinput} c2f <- function(c) return(9/5 * c + 32) \end{Sinput} A corresponding test function could look like this: \begin{Sinput} test.c2f <- function() { checkEquals(c2f(0), 32) checkEquals(c2f(10), 50) checkException(c2f("xx")) } \end{Sinput} The default naming convention for test functions in the RUnit package is {\tt test...} as is standard in JUnit. To perform the actual checks that the function to be tested works correctly a set of functions called {\tt check ...} is provided. The purpose of these {\tt check} functions is two-fold: they make sure that a possible failure is reported to the central test logger so that it will appear properly in the final test protocol and they are supposed to make explicit the actual checks in a test case as opposed to other code used to set up the test scenario. Note that {\tt checkException} fails if the passed expression does not generate an error. This kind of test is useful to make sure that a function correctly recognises error situations instead of silently creating inappropriate results. These check functions are direct equivalents to the various {\tt assert} functions of the JUnit framework. More information can be found in the online help. Before running the test function it is necessary to create a test suite which is a collection of test functions and files relating to one topic. One could, for instance, create one test suite for one R package. A test suite is just a list containing a name, an array of absolute directories containing the locations of the test files, a regular expression identifying the test files and a regular expression identifying the test functions. In our example assume that the test function is located in a file {\tt runitc2f.r} located in a directory {\tt /foo/bar/}. To create the corresponding test suite we can use a helper function: \begin{Sinput} testsuite.c2f <- defineTestSuite("c2f", dirs = file.path(.path.package(package="RUnit"), "examples"), testFileRegexp = "^runit.+\\.r", testFuncRegexp = "^test.+", rngKind = "Marsaglia-Multicarry", rngNormalKind = "Kinderman-Ramage") \end{Sinput} All that remains is to run the test suite and print the test protocol: \begin{Sinput} testResult <- runTestSuite(testsuite.c2f) printTextProtocol(testResult) \end{Sinput} The resulting test protocol should be self explanatory and can also be printed as HTML version. See the online help for further information. Note that for executing just one test file there is also a shortcut in order to make test case execution as easy as possible: \begin{Sinput} runTestFile(file.path(.path.package(package="RUnit"), "examples/runitc2f.r")) \end{Sinput} The creation and execution of test suites can be summarised by the following recipe: \begin{enumerate} \item{create as many test functions in as many test files as necessary } \item{create one or more test suites using the helper function {\tt defineTestSuite}} \item{run the test suites with {\tt runTestSuite}} \item{print the test protocol either with {\tt printTextProtocol} or with {\tt printHTMLProtocol} (or with a generic method like {\tt print} or {\tt summary})} \end{enumerate} We conclude this section with some further comments on various aspects of the test execution framework: \begin{itemize} \item{A test file can contain an arbitrary number of test functions. A test directory can contain an arbitrary number of test files, a test suite can contain an arbitrary number of test directories and the test runner can run an arbitrary number of test suites -- all resulting in one test protocol. The test function and file names of a test suite must, however, obey a naming convention expressible through regular expressions. As default test functions start with {\tt test} and files with {\tt runit}.} \item{RUnit makes a distinction between failure and error. A failure occurs if one of the check functions fail (e.g.~{\tt checkTrue(FALSE)} creates a failure). An error is reported if an ordinary R error (usually created by {\tt stop}) occurs.} \item{Since version 0.4.0 there is a function {\tt DEACTIVATED} which can be used to deactivate test cases temporarily. This might be useful in the case of a major refactoring. In particular, the deactivated test cases are reported in the test protocol so that they cannot fall into oblivion.} \item{The test runner tries hard to leave a clean R session behind. Therefore all objects created during test case execution will be deleted after a test file has been processed.} \item{In order to prevent mysterious errors the random number generator is reset to a standard setting before sourcing a test file. If a particular setting is needed to generate reproducible results it is fine to configure the random number generator at the beginning of a test file. This setting applies during the execution of all test functions of that test file but is reset before the next test file is sourced.} \item{In each source file one can define the parameterless functions {\tt .setUp()} and {\tt .tearDown()}. which are then executed directly before and after each test function. This can, for instance, be used to control global settings or create addition log information.} \end{itemize} \subsection{R Code Inspection} \label{subsection:RCodeInspection} The Code Inspector is an additional tool for checking detailed test case coverage and getting profiling information. It records how often a code line will be executed. We utilise this information for improving our test cases, because we can identify code lines not executed by the current test case code. The Code Inspector is able to handle S4 methods. During the development of the Code Inspector, we noticed, that the syntax of R is very flexible. Because our coding philosophy has an emphasis of maintenance and a clear style, we developed style guides for our R coding. Therefore, one goal for the Code Inspector was to handle our coding styles in a correct manner. This leads to the consequence that not all R expression can be handled correctly. In our implementation the Code Inspector has two main functional parts. The first part is responsible for parsing and modifying the code of the test function. The second part, called the Tracker, holds the result of the code tracking. The result of the tracking process allows further analysis of the executed code. \subsubsection{Usage} The usage of the Code Inspector and the Tracker object is very simple. The following code snippet is an example: <>= library(RUnit) ## define sample functions to be tested foo <- function(x) { x <- x*x x <- 2*x return(x) } test.foo <- function() { checkTrue(is.numeric(foo(1:10))) checkEquals(length(foo(1:10)), 10) checkEqualsNumeric(foo(1), 2) } bar <- function(x, y=NULL) { if (is.null(y)) { y <- x } if (all(y > 100)) { ## subtract 100 y <- y - 100 } res <- x^y return(res) } track <- tracker(); ## initialize a tracking "object" track$init(); ## initialize the tracker a <- 1:10 d <- seq(0,1,0.1) resFoo <- inspect(foo(a), track=track); ## execute the test function and track resBar <- inspect(bar(d), track=track); ## execute the test function and track resTrack <- track$getTrackInfo(); ## get the result of Code Inspector (a list) printHTML(resTrack, baseDir=tempdir()) ; ## create HTML sites @ Note, that the tracking object is an global object and must have the name {\tt track}. The {\tt inspect} function awaits a function call as argument and executes and tracks the function. The results will be stored in the tracking object. The result of the function (not of the Tracker) will be returned as usual. The tracking results will received by tr\$getResult(). With {\tt printHTML} the result of the tracking process will be presented as HTML pages. \subsubsection{Technical Details} The general idea for the code tracking is to modify the source code of the function. Therefore, we use the {\tt parse} and {\tt deparse} functions and the capability of R to generate functions on runtime. To track the function we try to include a hook in every code line. That hook calls a function of the tracked object. The information of the tracking will be stored in the closure of the tracking object (actually a function). Because the R parser allows very nested expressions, we didn't try to modify every R expression. This is a task for the future. A simple example for the modifying process is as follow:\\ original: <>= foo <- function(x) { y <- 0 for(i in 1:x) { y <- y + x } return(y) } @ modified: <>= foo.mod <- function(x) { track$bp(1) ; y <- 0 track$bp(2); for(i in 1:x) { track$bp(4) ; y <- y +x } track$bp(6); return(y) } @ Problematic code lines are: <>= if(any(a==1)) { print("do TRUE") } else print ("do FALSE"); @ This must be modified to <>= if(any(a==1)) { track$bp(2); print("do TRUE") }else{ track$bp(3); print("do FALSE"); } @ The problem is the \textit{else} branch, that cannot be modified in the current version. \section{Future Development Ideas} Here we briefly list -- in an unordered manner -- some of the avenues for future development we or someone interested in this package could take: \begin{itemize} \item{extend the {\tt checkEquals} function to handle complex S4 class objects correctly in comparisons. To this end R core has modified check.equal to handle S4 objects.} \item{reimplement the internal structures storing the test suite as well as the test result data as S4 classes.} \item{record all warnings generated during the execution of a test function.} \item{add tools to create test cases automatically. This is a research project but -- given the importance of testing -- worth the effort. See \cite{junit} for various approaches in other languages.} \item{improve the export of test suite execution data e.g.~by adding XML data export support.} \item{add some evaluation methods to the code inspector e.g.~use software metrics to estimate standard measures of code quality, complexity, and performance.} \item{overcome the problem of nested calls to registered functions for code inspection.} \item{allow automatic registration of functions \& methods.} \end{itemize} \begin{thebibliography}{99} % \bibliographystyle{plainnat} \bibitem{xp} http://www.xprogramming.com \bibitem{beck} http://www.xprogramming.com/testfram.htm \bibitem{junit} http://www.junit.org/ \bibitem{tfg} http://www.xprogramming.com/xpmag/testFirstGuidelines.htm \bibitem{runit-sf} https://sourceforge.net/projects/runit/ \end{thebibliography} \end{document} RUnit/NEWS0000644000176200001440000001554714561532231012030 0ustar liggesusers Dear Emacs, please make this -*-Text-*- mode! ************************************************** * * * RUnit * * * ************************************************** Changes in RUnit 0.4.33 o checkEqualsNumeric functions when given two one-column data.frames Changes in RUnit 0.4.32 o runTestSuite and runTestFile takes additional gcBeforeTest parameter (default FALSE). This disables running garbage collector before timing the test. This setting speeds up the test suite at the cost of making individual test timing less reliable. When trying to optimize tests for speed, set gcBeforeTest to TRUE for more reliable timing information. o added .Rinstignore to cut down on warnings when building the package o removed Biobase specific tests, replaced with direct S4 class creation o fixed CRAN URLs in README.md Changes in RUnit 0.4.31 o checkEquals and others output optional message on separate line Changes in RUnit 0.4.30 o printJUnitProtocol added for JUnit-style output Changes in RUnit 0.4.29 o changed maintainer to Roman Zenka o .testLogger global variable now stored in package environment RUnitEnv o added imports of graphics package to NAMESPACE Changes in RUnit 0.4.26 o isValidTestSuite: fixed insufficient if expression handling, reported by Rich Calaway; extended validity checks Changes in RUnit 0.4.25 o enable redirection of error and log messages to file, controlled via new global option 'outfile', (following a suggestion by Seth Falcon) Changes in RUnit 0.4.24 o added RUnit specific options 'verbose' and 'silent' to global options list to allow control of test log output o moved unit tests from tests/ to inst/unitTests o added Makefile for executing unit tests (using R wiki example) Changes in RUnit 0.4.23 o vignette: fixed function name in example code, reported by Blair Christian o init .testLogger to avoid R CMD check NOTE messages o allow verbosity of console output log to be controlled: added 'verbose' argument to runTestFile and runTestSuite (following a suggestion by Seth Falcon) o test logger object declared as S3 class 'TestLogger' Changes in RUnit 0.4.22 o clarified applicable license: GPL 2 o defineTestSuite: gained some argument checks o isValidTestSuite: check for empty name o includeTracker: fix `<- if` handling Changes in RUnit 0.4.21 o documentation issues corrected, identified by new R 2.9.0 devel Rd parser Changes in RUnit 0.4.20 o test protocol generation on Mac OS X failed due to incorrect code to identify 'gcc' version o Rd documentation updated Changes in RUnit 0.4.19 o test protocol now states check number per test case o changed check for object class to is() to allow derived class objects to pass (suggested by Philippe Grosjean) o removed start up message Changes in RUnit 0.4.18 o seq_along introduced instead of seq( ) for efficiency and R version dependency set to 2.4.0 when seq_along was introduced o some small changes to avoid warnings with options(warnPartialMatchArgs=TRUE) Changes in RUnit 0.4.17 o corrected documentation example code Changes in RUnit 0.4.16 o changed the environment test code files are evaluated, now a new environment outside the RUnit namespace is utilized, allowing e.g. setClass calls without specifying where=.GlobalEnv o updated documentation to use encoding latin1 o use LazyLoad: yes instead of SaveImage:yes (to be deprecated for R 2.6.0) o internal error handler rewritten to be more failure robust o added new test cases for .setUp and .tearDown, extended tests to cover S4 class and method behaviour in check* functions o example on S4 virtual class testing added o utility function to compare to RUnitTestResult objects added: concept idea for comparing and optimizing test suite performance (share/R/checkCode.r) Changes in RUnit 0.4.15 o compatibility to R 1.9.0 as declared in DESCRIPTION: removed calls to isTRUE as this was introduced only in R 2.1.0, replaced where needed by identical(TRUE, x) o fixed printHTMLProtocol: on Windows Makeconf does not exist so CC compiler used by R cannot be queried this way (reported by Tobias Verbeke) Changes in RUnit 0.4.14 o stated all package dependencies in DESCRIPTION, required for R 2.4.0 compatibility Changes in RUnit 0.4.13 o allow the RNG to be set by the user via new arguments 'rngKind' and 'rngNormalKind' to functions defineTestSuite and runTestFile (patch by Seth Falcon) o fixed exit status: RNG kind was left changed after runTestSuite execution in user workspace o email contact address modified Changes in RUnit 0.4.12 o allow *.R test case file extension (suggested by Gregor Gorjanc) o fixed code typo in example vignette (spotted by Gregor Gorjanc) Changes in RUnit 0.4.11 o checkException: argument silent added to allow to suppress error messages emitted by the failing function o inspect: added argument track, which _has_ to be provided at each invocation to avoid recursive default argument reference call errors (experimental: subject to change if this leads to new incompatibilities) Changes in RUnit 0.4.9 o checkEquals has new compatibility argument checkNames Changes in RUnit 0.4.8 o checkIdentical added, to allow to check for exact identity Changes in RUnit 0.4.7 o update for checkEqualsNumeric to be compatible with R 2.3.0 Changes in RUnit 0.4.5 o improvements to error detection in runTestSuite Changes in RUnit 0.4.4 o changed maintainer Changes in RUnit 0.4.2 o checkTrue: corrected handling of named logical arguments Changes in RUnit 0.4.1 o printHTMLProtocol has new parameter 'testFileToLinkMap' to allow to provide a function to add dynamically generated URLs for each test case file, e.g. for use with CVSweb Changes in RUnit 0.4.0 o New 'error' category DEACTIVATED introduced: If the function DEACTIVATED is inserted into a test function the function stops at that point and is reported as deactivated in the test protocol. o New function 'getErrors' which takes a list of type 'RUnitTestData' and returns some very basic error information of a test run. o The name of the currently executed test function is written to standard out. o 'printHTMLProtocol' fixed such that it does not produce a warning anymore. o Dependency on package 'splines' removed. o Various small fixes of the documentation. RUnit/COPYING0000644000176200001440000004313113267374743012370 0ustar liggesusers GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. RUnit/R/0000755000176200001440000000000014563457515011533 5ustar liggesusersRUnit/R/textProtocol.r0000644000176200001440000002110113267374743014420 0ustar liggesusers## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ printTextProtocol <- function(testData, fileName = "", separateFailureList = TRUE, showDetails = TRUE, traceBackCutOff=9) { ##@bdescr ## Report generator ## Extracts the log information stored in the 'RUnitTestData' test run object ## and generates a well formated protocol output. ##@edescr ## ##@in testData : [RUnitTestData] S3 class object ##@in fileName : [character] string, full path + file name to be written to ##@in separateFailureList : [logical] if TRUE (default) add a failure list ##@in showDetails : [logical] if TRUE (default) add detailed traceback for each error incurred ##@in traceBackCutOff : [integer] number of steps back in the trace back stack to display ##@ret : [logical] TRUE if execution completed without error ## ##@codestatus : testing ## preconditions if (!is(testData, "RUnitTestData")) { stop("Argument 'testData' must be of class 'RUnitTestData'.") } if (!is.character(fileName)) { stop("Argument 'fileName' has to be of type character.") } if (length(fileName) != 1) { stop("Argument 'fileName' must contain exactly one element.") } if (!is.logical(separateFailureList)) { stop("Argument 'separateFailureList' has to be of type logical.") } if (length(separateFailureList) != 1) { stop("Argument 'separateFailureList' must contain exactly one element.") } if (!is.logical(showDetails)) { stop("Argument 'showDetails' has to be of type logical.") } if (length(showDetails) != 1) { stop("Argument 'showDetails' must contain exactly one element.") } if (!is.numeric(traceBackCutOff)) { stop("Argument 'traceBackCutOff' has to be of type logical.") } if (length(traceBackCutOff) != 1) { stop("Argument 'traceBackCutOff' must contain exactly one element.") } if (traceBackCutOff < 0 || traceBackCutOff > 100) { stop("Argument 'traceBackCutOff' out of valid range [0, 100].") } ## just a convenience function pr <- function(..., sep=" ", nl=TRUE) { if(nl) { cat(... , "\n", file = fileName, append=TRUE, sep=sep) } else { cat(... , file = fileName, append=TRUE, sep=sep) } } ## get singular or plural right sop <- function(number, word, plext="s") { ifelse(number == 1, paste(number, word), paste(number, paste(word, plext, sep=""))) } ## header part cat("RUNIT TEST PROTOCOL --", date(), "\n", file = fileName) pr("***********************************************") if(length(testData) == 0) { pr("no test cases :-(") return(invisible(TRUE)) } errInfo <- getErrors(testData) pr("Number of test functions:", errInfo$nTestFunc) if(errInfo$nDeactivated > 0) { pr("Number of deactivated test functions:", errInfo$nDeactivated) } pr("Number of errors:", errInfo$nErr) pr("Number of failures:", errInfo$nFail, "\n\n") ## summary of test suites pr(sop(length(testData), "Test Suite"), ":") for(tsName in names(testData)) { pr(tsName, " - ", sop(testData[[tsName]]$nTestFunc, "test function"), ", ", sop(testData[[tsName]]$nErr, "error"), ", ", sop(testData[[tsName]]$nFail, "failure"), sep="") if(separateFailureList && (testData[[tsName]]$nErr + testData[[tsName]]$nFail > 0)) { srcFileRes <- testData[[tsName]][["sourceFileResults"]] for(i in seq_along(srcFileRes)) { testFuncNames <- names(srcFileRes[[i]]) for(j in seq_along(testFuncNames)) { funcList <- srcFileRes[[i]][[testFuncNames[j]]] if(funcList$kind == "error") { pr("ERROR in ", testFuncNames[j], ": ", funcList$msg, nl=FALSE, sep="") } else if(funcList$kind == "failure") { pr("FAILURE in ", testFuncNames[j], ": ", funcList$msg, sep="", nl=FALSE) } else if(funcList$kind == "deactivated") { pr("DEACTIVATED ", testFuncNames[j], ": ", funcList$msg, sep="", nl=FALSE) } } } } } ## if no details are required, we are done. if(!showDetails) { return(invisible(TRUE)) } pr("\n\n\nDetails") ## loop over all test suites for(tsName in names(testData)) { tsList <- testData[[tsName]] pr("***************************") pr("Test Suite:", tsName) pr("Test function regexp:", tsList$testFuncRegexp) pr("Test file regexp:", tsList$testFileRegexp) if(length(tsList$dirs) == 0) { pr("No directories !") } else { if(length(tsList$dirs) == 1) { pr("Involved directory:") } else { pr("Involved directories:") } for(dir in tsList$dirs) { pr(dir) } res <- tsList$sourceFileResults testFileNames <- names(res) if(length(res) == 0) { pr("no test files") } else { ## loop over all source files for(testFileName in testFileNames) { testFuncNames <- names(res[[testFileName]]) if(length(testFuncNames) > 0) { pr("---------------------------") pr("Test file:", testFileName) ## loop over all test functions in the test file for(testFuncName in testFuncNames) { testFuncInfo <- res[[testFileName]][[testFuncName]] if(testFuncInfo$kind == "success") { pr(testFuncName, ": (",testFuncInfo$checkNum, " checks) ... OK (", testFuncInfo$time, " seconds)", sep="") } else { if(testFuncInfo$kind == "error") { pr(testFuncName, ": ERROR !! ", sep="") } else if (testFuncInfo$kind == "failure") { pr(testFuncName, ": FAILURE !! (check number ", testFuncInfo$checkNum, ")", sep="") } else if (testFuncInfo$kind == "deactivated") { pr(testFuncName, ": DEACTIVATED, ", nl=FALSE) } else { pr(testFuncName, ": unknown error kind", sep="") } pr(testFuncInfo$msg, nl=FALSE) if(length(testFuncInfo$traceBack) > 0) { pr(" Call Stack:") if(traceBackCutOff > length(testFuncInfo$traceBack)) { pr(" (traceBackCutOff argument larger than length of ", "trace back: full trace back printed)") for(i in 1:length(testFuncInfo$traceBack)) { pr(" ", i, ": ", testFuncInfo$traceBack[i], sep="") } } else { for(i in traceBackCutOff:length(testFuncInfo$traceBack)) { pr(" ", 1+i-traceBackCutOff, ": ", testFuncInfo$traceBack[i], sep="") } } } } } } } } } } ## return type return(invisible(TRUE)) } print.RUnitTestData <- function(x, ...) { ##@bdescr ## Generic print method ##@edescr ## ##@in x : [RUnitTestData] S3 class object ##@in ... : [ANY] currently ignored ##@ret : [NULL] ## ##@codestatus : untested errInfo <- getErrors(x) cat("Number of test functions:", errInfo$nTestFunc, "\n") if(errInfo$nDeactivated > 0) { cat("Number of deactivated test functions:", errInfo$nDeactivated, "\n") } cat("Number of errors:", errInfo$nErr, "\n") cat("Number of failures:", errInfo$nFail, "\n") } summary.RUnitTestData <- function(object, ...) { ##@bdescr ## Generic summary method ##@edescr ## ##@in object : [RUnitTestData] S3 class object ##@in ... : [ANY] ##@ret : [logical] return valof from printTextProtocol ## ##@codestatus : untested printTextProtocol(object, ...) } RUnit/R/html.r0000644000176200001440000003104613267374743012667 0ustar liggesusers## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ writeRaw <- function(htmlStr,htmlFile,append=TRUE) { ##@bdescr ## private function ## write raw text in a html file ##@bdescr ##@in htmlStr : [character] text ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal cat(htmlStr,file=htmlFile,append=append) invisible(TRUE) } writeRawCR <- function(htmlStr,htmlFile,append=TRUE) { ##@bdescr ## private function ## write raw text in a html file with a cr at end ##@bdescr ##@in htmlStr : [character] text ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw(htmlStr,htmlFile,append) cat("\n",file=htmlFile,append=TRUE) invisible(TRUE) } writeTitle <- function(htmlStr,htmlFile,append=TRUE) { ##@bdescr ## private function ## write title tags and title text ##@bdescr ##@in htmlStr : [character] title ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw("",htmlFile,append) writeRaw(htmlStr,htmlFile) writeRaw("\n",htmlFile) } writeBeginHead <- function(htmlFile,append=TRUE) { ##@bdescr ## private function ## write ##@bdescr ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw("",htmlFile,append) } writeEndHead <- function(htmlFile,append=TRUE) { ##@bdescr ## private function ## write ##@bdescr ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw("\n",htmlFile,append) } writeBeginHtml <- function(htmlFile,append=TRUE) { ##@bdescr ## private function ## write ##@bdescr ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw("",htmlFile,append) } writeEndHtml <- function(htmlFile,append=TRUE) { ##@bdescr ## private function ## write ##@bdescr ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw("\n",htmlFile,append) } writeBeginBody <- function(htmlFile,append=TRUE) { ##@bdescr ## private function ## write ##@bdescr ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw("",htmlFile,append) } writeEndBody <- function(htmlFile,append=TRUE) { ##@bdescr ## private function ## write ##@bdescr ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw("\n",htmlFile,append) } writeBeginTag <- function(htmlTag,htmlFile,para="",append=TRUE) { ##@bdescr ## private function ## write begin of a tag, with parameters ##@bdescr ##@in htmlTag : [character] name of the tag ##@in htmlFile : [character] name of the html file ##@in para : [character] parameters as string ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal if(all(para =="")) { writeRaw(paste("<",htmlTag,">",sep=""),htmlFile,append) } else { writeRaw(paste("<",htmlTag," ",para,">",sep=""),htmlFile,append) } } writeEndTag <- function(htmlTag,htmlFile,append=TRUE) { ##@bdescr ## private function ## write end of tag ##@bdescr ##@in htmlTag : [character] name of the tag ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRaw(paste("",sep=""),htmlFile,append) } writeCR <- function(htmlFile,append=TRUE) { ##@bdescr ## private function ## write CR in html file for better formatting of the html source ##@bdescr ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal cat("\n",file=htmlFile,append=append) invisible(TRUE) } writeBeginTable <- function(header,htmlFile,border=1, width="100%",append=TRUE, columnWidth=NULL) { ##@bdescr ## private function ## write begin of a table ##@bdescr ##@in header : [character] title for columns ##@in htmlFile : [character] name of the html file ##@in border : [integer] border of table ##@in width : [character] width of table ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal tablePara <- paste("border=\"",border,"\" width=\"",width,"\"",sep="") writeRawCR(paste("",sep=""),htmlFile,append) ## if header is provided if (length(header) > 0) { writeBeginTag("tr",htmlFile) for(i in seq_along(header)) { para <- "" if(!is.null(columnWidth)) { if (length(columnWidth) == length(header)) { para = paste("width=\"", columnWidth[i], "\"", sep="") } else { ## recycle first para = paste("width=\"", columnWidth[1], "\"", sep="") } } writeBeginTag("th",htmlFile, para=para) writeRaw(header[i],htmlFile) writeEndTag("th",htmlFile) writeCR(htmlFile) } writeEndTag("tr",htmlFile,append) } writeCR(htmlFile) } writeTableRow <- function(row,htmlFile,append=TRUE,bgcolor="") { ##@bdescr ## private function ## write a table row ##@bdescr ##@in row : [character] data for table cells in row ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@in bgcolor : [character] color for table cells ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeBeginTag("tr",htmlFile) if(length(bgcolor) == 1) { bgcolor <- rep(bgcolor,length(row)) } for(i in seq_along(row)) { if(bgcolor[i] == "") { writeBeginTag("td",htmlFile) } else { writeBeginTag("td",htmlFile,para=paste("bgcolor=\"",bgcolor[i],"\"",sep="")) } writeRaw(row[i],htmlFile) writeEndTag("td",htmlFile) writeCR(htmlFile) } writeEndTag("tr",htmlFile,append) writeCR(htmlFile) } writeLink <- function(target,name,htmlFile,append=TRUE) { ##@bdescr ## private function ## write a link ##@bdescr ##@in target : [character] target of the link ##@in name : [character] name of the target ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeBeginTag("a",htmlFile,paste("href=\"",target,"\"",sep=""),append=append) writeRaw(name,htmlFile,append=TRUE) writeEndTag("a",htmlFile,append=TRUE) } writeEndTable <- function(htmlFile,append=TRUE) { ##@bdescr ## private function ## close an
enviroment by adding
#@bdescr ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeEndTag("table",htmlFile,append) writeCR(htmlFile) } writeHtmlHeader <- function(header,htmlFile) { ##@bdescr ## private function ## write a HTML file header ## - DOCTYPE ## - ## - </head> ## - <body> ## ## should be finished by writeHtmlEnd ##@bdescr ##@in header : [character] title of the document ##@in htmlFile : [character] name of the link ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRawCR("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"", htmlFile,FALSE) writeRawCR("\"http://www.w3.org/TR/html4/transitional.dtd\">",htmlFile) writeBeginHtml(htmlFile) writeBeginHead(htmlFile) writeTitle(header,htmlFile) writeEndHead(htmlFile) writeBeginBody(htmlFile) } writeHtmlEnd <- function(htmlFile) { ##@bdescr ## private function ## write end of html code ##@bdescr ##@in htmlFile : [character] name of the html file ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeEndBody(htmlFile) writeEndHtml(htmlFile) } writeHtmlSep <- function(htmlFile) { ##@bdescr ## private function ## write horizontal seperator ##@bdescr ##@in htmlFile : [character] name of the html file ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeRawCR("<hr>",htmlFile) } writeImage <- function(img,htmlFile,append=TRUE) { ##@bdescr ## private function ## write image tags ##@bdescr ##@in img : [character] name of the image file ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal writeBeginTag("img",htmlFile,para=paste("src=\"",img,"\"",sep=""),append) writeEndTag("img",htmlFile) } writeHtmlSection <- function(title,sec,htmlFile,append=TRUE) { ##@bdescr ## private function ## write titles for section ##@bdescr ##@in title : [character] title of the section ##@in sec : [integer] size of title (between 1-6) ##@in htmlFile : [character] name of the html file ##@in append : [logical] append the html code ##@ret : [logical] TRUE if execution completes ## ##@codestatus : internal secTag <- paste("h",sec,sep="") writeBeginTag(secTag,htmlFile,append) writeRaw(title,htmlFile,append) writeEndTag(secTag,htmlFile,append) writeCR(htmlFile,append) } writeHtmlTable <- function(dataFrame, htmlFile, border=1, width="100%", append=TRUE) { ##@bdescr ## private function ## write a data frame to a HTML table ##@bdescr ## ##@in dataFrame : [data frame] size of title (between 1-6) ##@in htmlFile : [character] name of the html file ##@in border : [integer] 1 (default) table borders will be shown ##@in width : [character] width of table ##@in append : [logical] if TRUE append the tabel to an existing HTML file ##@ret : [logical] TRUE if execution completed ## ##@codestatus : internal header <- NULL colNames <- colnames(dataFrame) if (!is.null(colNames)) { if (length(colNames) == dim(dataFrame)[2]) { header <- colNames } else { ## don't write column names header <- NULL } } rowNames <- rownames(dataFrame) if (!is.null(rowNames)) { header <- c("Name", header) dataFrame <- cbind(rowNames, dataFrame) } writeBeginTable(header, htmlFile, border=border, width=width, append=append, columnWidth=NULL) for (ti in 1:dim(dataFrame)[1]) { writeTableRow(dataFrame[ti, ], htmlFile, append=TRUE, bgcolor="") } writeEndTable(htmlFile,append=TRUE) } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/R/htmlProtocol.r������������������������������������������������������������������������������0000644�0001762�0000144�00000041413�13267374743�014410� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ printHTMLProtocol <- function(testData, fileName = "", separateFailureList = TRUE, traceBackCutOff=9, testFileToLinkMap=function(x) x) { ##@bdescr ## Report generator ## Extracts the log information stored in the 'RUnitTestData' test run object ## and generates a well formated HTML output. ##@edescr ## ##@in testData : [RUnitTestData] S3 class object ##@in fileName : [character] ##@in separateFailureList : [logical] if TRUE (default) add a list of all failures ##@in traceBackCutOff : [integer] number of steps back in the trace back stack to be displayed ##@in testFileToLinkMap : [function] a function transforming the full name of the test file to a link location ##@ret : [logical] TRUE if execution completed w/o error ## ##@codestatus : testing ## -------------------------------- ## CHECK OF INPUT DATA ## -------------------------------- if (!is(testData, "RUnitTestData")) { stop("Argument 'testData' must be of class 'RUnitTestData'.") } if (!is.character(fileName)) { stop("Argument 'fileName' has to be of type character.") } if (length(fileName) != 1) { stop("Argument 'fileName' must contain exactly one element.") } if (!is.logical(separateFailureList)) { stop("Argument 'separateFailureList' has to be of type logical.") } if (length(separateFailureList) != 1) { stop("Argument 'separateFailureList' must contain exactly one element.") } if (!is.numeric(traceBackCutOff)) { stop("Argument 'traceBackCutOff' has to be of type logical.") } if (length(traceBackCutOff) != 1) { stop("Argument 'traceBackCutOff' must contain exactly one element.") } if (traceBackCutOff < 0 || traceBackCutOff > 100) { stop("Argument 'traceBackCutOff' out of valid range [0, 100].") } ## -------------------------------- ## HELPER FUNCTIONS ## -------------------------------- ## get singular or plural right sop <- function(number, word, plext="s") { ifelse(number == 1, paste(number, word), paste(number, paste(word, plext, sep=""))) } pr <- function(...) { writeRaw(paste(...), htmlFile=fileName) writeRaw("<br/>", htmlFile=fileName) } writeP <- function(string, para="") { writeBeginTag("p", para=para, htmlFile=fileName) writeRaw(string, htmlFile=fileName) writeEndTag("p", htmlFile=fileName) writeCR(htmlFile=fileName) } writeLi <- function(..., para="") { writeBeginTag("li", para=para, htmlFile=fileName) writeRaw(paste(...), htmlFile=fileName) writeEndTag("li", htmlFile=fileName) } createTestFuncRef <- function(testSuite, srcFileName, testFuncName, asAnchor=FALSE) { tmp <- paste(testSuite, srcFileName, testFuncName, sep="_") if(asAnchor) { return(paste("#", gsub("/", "_", tmp), sep="")) } else { return(gsub("/", "_", tmp)) } } printTraceBack <- function(traceBack) { if(length(traceBack) > 0) { writeRaw("Call Stack:<br/>", htmlFile=fileName) if(traceBackCutOff > length(testFuncInfo$traceBack)) { writeRaw("(traceBackCutOff argument larger than length of trace back: full trace back printed)<br/>", htmlFile=fileName) writeBeginTag("ol", htmlFile=fileName) for(i in seq_along(traceBack)) { writeBeginTag("li", htmlFile=fileName) writeRaw(traceBack[i], htmlFile=fileName) writeEndTag("li", htmlFile=fileName) } } else { writeBeginTag("ol", htmlFile=fileName) for(i in traceBackCutOff:length(traceBack)) { writeBeginTag("li", htmlFile=fileName) writeRaw(traceBack[i], htmlFile=fileName) writeEndTag("li", htmlFile=fileName) } } writeEndTag("ol", htmlFile=fileName) } } errorStyle <- "color:red" deactivatedStyle <- "color:black" ## -------------------------------------------- ## PART 1: TITLE AND BASIC ERROR INFORMATION ## -------------------------------------------- ## title title <- paste("RUNIT TEST PROTOCOL", date(), sep="--") writeHtmlHeader(title, htmlFile=fileName) writeHtmlSection(title, 1, htmlFile=fileName) if(length(testData) == 0) { writeP(" no test cases :-(") return(invisible(TRUE)) } ## basic Info errInfo <- getErrors(testData) writeP(paste("Number of test functions:", errInfo$nTestFunc)) if(errInfo$nDeactivated > 0) { writeP(paste("Number of deactivated test functions:", errInfo$nDeactivated), para=ifelse(errInfo$nDeactivated == 0, "", paste("style", deactivatedStyle, sep="="))) } writeP(paste("Number of errors:", errInfo$nErr), para=ifelse(errInfo$nErr == 0, "", paste("style", errorStyle, sep="="))) writeP(paste("Number of failures:", errInfo$nFail), para=ifelse(errInfo$nFail == 0, "", paste("style", errorStyle, sep="="))) writeHtmlSep(htmlFile=fileName) ## -------------------------------- ## PART 2: TABLE OF TEST SUITES ## -------------------------------- ## summary of test suites writeHtmlSection(sop(length(testData), "Test suite"), 3, htmlFile=fileName) ## table of test suites if(errInfo$nDeactivated > 0) { writeBeginTable(c("Name", "Test functions", "Deactivated", "Errors", "Failures"), width="80%", htmlFile=fileName, columnWidth=c("20%", "20%", "20%", "20%", "20%")) for(tsName in names(testData)) { rowString <- c(paste("<a href=\"#", tsName, "\">", tsName, "</a>", sep=""), testData[[tsName]]$nTestFunc, testData[[tsName]]$nDeactivated, testData[[tsName]]$nErr, testData[[tsName]]$nFail) rowCols <- c("", "", ifelse(testData[[tsName]]$nDeactivated==0, "", "yellow"), ifelse(testData[[tsName]]$nErr==0, "", "red"), ifelse(testData[[tsName]]$nFail==0, "", "red")) writeTableRow(row=rowString, bgcolor=rowCols, htmlFile=fileName) } writeEndTable(htmlFile=fileName) } else { ## skip 'deactivated' column if no function has been deactivated writeBeginTable(c("Name", "Test functions", "Errors", "Failures"), width="60%", htmlFile=fileName, columnWidth=c("30%", "30%", "20%", "20%")) for(tsName in names(testData)) { rowString <- c(paste("<a href=\"#", tsName, "\">", tsName, "</a>", sep=""), testData[[tsName]]$nTestFunc, testData[[tsName]]$nErr, testData[[tsName]]$nFail) rowCols <- c("", "", ifelse(testData[[tsName]]$nErr==0, "", "red"), ifelse(testData[[tsName]]$nFail==0, "", "red")) writeTableRow(row=rowString, bgcolor=rowCols, htmlFile=fileName) } writeEndTable(htmlFile=fileName) } writeHtmlSep(htmlFile=fileName) ## ------------------------------------------------ ## PART 3: ERROR, FAILURE AND DEACTIVATED TABLES ## ------------------------------------------------- ## error table if(separateFailureList && (errInfo$nErr > 0)) { writeHtmlSection("Errors", 3, htmlFile=fileName) writeBeginTable(c("Test suite : test function", "message"), htmlFile=fileName, columnWidth=c("30%", "70%")) for(tsName in names(testData)) { if(testData[[tsName]]$nErr > 0) { srcFileRes <- testData[[tsName]]$sourceFileResults srcFileNames <- names(srcFileRes) for(i in seq_along(srcFileRes)) { testFuncNames <- names(srcFileRes[[i]]) for(j in seq_along(testFuncNames)) { funcList <- srcFileRes[[i]][[testFuncNames[j]]] if(funcList$kind == "error") { lnk <- paste("<a href=\"", createTestFuncRef(tsName, srcFileNames[i], testFuncNames[j], asAnchor=TRUE), "\">", paste(tsName, testFuncNames[j], sep=" : "), "</a>", sep="") writeTableRow(row=c(lnk, funcList$msg), htmlFile=fileName) } } } } } writeEndTable(htmlFile=fileName) writeHtmlSep(htmlFile=fileName) } ## failure table if(separateFailureList && (errInfo$nFail > 0)) { writeHtmlSection("Failures", 3, htmlFile=fileName) writeBeginTable(c("Test suite : test function", "message"), htmlFile=fileName, columnWidth=c("30%", "70%")) for(tsName in names(testData)) { if(testData[[tsName]]$nFail > 0) { srcFileRes <- testData[[tsName]]$sourceFileResults srcFileNames <- names(srcFileRes) for(i in seq_along(srcFileRes)) { testFuncNames <- names(srcFileRes[[i]]) for(j in seq_along(testFuncNames)) { funcList <- srcFileRes[[i]][[testFuncNames[j]]] if(funcList$kind == "failure") { lnk <- paste("<a href=\"", createTestFuncRef(tsName, srcFileNames[i], testFuncNames[j], asAnchor=TRUE), "\">", paste(tsName, testFuncNames[j], sep=" : "), "</a>", sep="") writeTableRow(row=c(lnk, funcList$msg), htmlFile=fileName) } } } } } writeEndTable(htmlFile=fileName) writeHtmlSep(htmlFile=fileName) } ## deactivated table if(separateFailureList && (errInfo$nDeactivated > 0)) { writeHtmlSection("Deactivated", 3, htmlFile=fileName) writeBeginTable(c("Test suite : test function", "message"), htmlFile=fileName, columnWidth=c("30%", "70%")) for(tsName in names(testData)) { if(testData[[tsName]]$nDeactivated > 0) { srcFileRes <- testData[[tsName]]$sourceFileResults srcFileNames <- names(srcFileRes) for(i in seq_along(srcFileNames)) { testFuncNames <- names(srcFileRes[[i]]) for(j in seq_along(testFuncNames)) { funcList <- srcFileRes[[i]][[testFuncNames[j]]] if(funcList$kind == "deactivated") { lnk <- paste("<a href=\"", createTestFuncRef(tsName, srcFileNames[i], testFuncNames[j], asAnchor=TRUE), "\">", paste(tsName, testFuncNames[j], sep=" : "), "</a>", sep="") writeTableRow(row=c(lnk, funcList$msg), htmlFile=fileName) } } } } } writeEndTable(htmlFile=fileName) writeHtmlSep(htmlFile=fileName) } ## -------------------------------- ## PART 4: DETAILS ## -------------------------------- writeHtmlSection("Details", 3, htmlFile=fileName) ## loop over all test suites for(tsName in names(testData)) { tsList <- testData[[tsName]] writeBeginTag("p", htmlFile=fileName) writeBeginTag("a", para=paste("name=\"", tsName, "\"", sep=""), htmlFile=fileName) writeHtmlSection(paste("Test Suite:", tsName), 5, htmlFile=fileName) writeEndTag("a", htmlFile=fileName) pr("Test function regexp:", tsList$testFuncRegexp) pr("Test file regexp:", tsList$testFileRegexp) if(length(tsList$dirs) == 0) { pr("No directories !") } else { if(length(tsList$dirs) == 1) { pr("Involved directory:") } else { pr("Involved directories:") } for(dir in tsList$dirs) { pr(dir) } res <- tsList$sourceFileResults testFileNames <- names(res) if(length(res) == 0) { pr(" no test files") } else { ## loop over all source files writeBeginTag("ul", htmlFile=fileName) for(testFileName in testFileNames) { testFuncNames <- names(res[[testFileName]]) if(length(testFuncNames) > 0) { writeBeginTag("li", htmlFile=fileName) writeLink(target=testFileToLinkMap(testFileName), name=paste("Test file:", basename(testFileName)), htmlFile=fileName) ## loop over all test functions in the test file writeBeginTag("ul", htmlFile=fileName) for(testFuncName in testFuncNames) { writeBeginTag("li", htmlFile=fileName) testFuncInfo <- res[[testFileName]][[testFuncName]] anchorName <- createTestFuncRef(tsName, testFileName, testFuncName) writeBeginTag("a", para=paste("name=\"", anchorName, "\"", sep=""), htmlFile=fileName) if(testFuncInfo$kind == "success") { pr(paste(testFuncName, ": (",testFuncInfo$checkNum, " checks) ... OK (", testFuncInfo$time, " seconds)", sep="")) writeEndTag("a", htmlFile=fileName) } else { if(testFuncInfo$kind == "error") { writeBeginTag("u", para=paste("style", errorStyle, sep="="), htmlFile=fileName) writeRaw(paste(testFuncName, ": ERROR !! ", sep=""), htmlFile=fileName) writeEndTag("u", htmlFile=fileName) writeEndTag("a", htmlFile=fileName) } else if (testFuncInfo$kind == "failure") { writeBeginTag("u", para=paste("style", errorStyle, sep="="), htmlFile=fileName) writeRaw(paste(testFuncName, ": FAILURE !! (check number ", testFuncInfo$checkNum, ") ", sep=""), htmlFile=fileName) writeEndTag("u", htmlFile=fileName) writeEndTag("a", htmlFile=fileName) } else if (testFuncInfo$kind == "deactivated") { writeBeginTag("u", para=paste("style", deactivatedStyle, sep="="), htmlFile=fileName) writeRaw(paste(testFuncName, ": DEACTIVATED, ", sep=""), htmlFile=fileName) writeEndTag("a", htmlFile=fileName) } else { writeLi(paste(testFuncName, ": unknown error kind", sep="")) writeEndTag("a", htmlFile=fileName) } pr(testFuncInfo$msg) printTraceBack(testFuncInfo$traceBack) } writeEndTag("li", htmlFile=fileName) } writeEndTag("ul", htmlFile=fileName) } writeEndTag("li", htmlFile=fileName) } writeEndTag("ul", htmlFile=fileName) } } writeHtmlSep(htmlFile=fileName) } ver <- cbind(unlist(version)) ## add host name ver <- rbind(ver, Sys.info()["nodename"]) rownames(ver)[dim(ver)[1]] <- "host" colnames(ver) <- "Value" ## compiler used (under *nix) rhome <- Sys.getenv("R_HOME") ## on Windows Makeconf does not exist ## other than that we have no indication which compiler ## would be used for R CMD INSTALL so we report NA gccVersion <- as.character(NA) makeconfFile <- file.path(rhome, "etc", "Makeconf") if (file.exists(makeconfFile) && identical(.Platform$OS.type, "unix")) { gccVersion <- system(paste("cat ", makeconfFile," | grep \"^CXX =\" "), intern=TRUE) gccVersion <- sub("^CXX[ ]* =[ ]*", "", gccVersion) } ver <- rbind(ver, gccVersion) rownames(ver)[dim(ver)[1]] <- "compiler" writeHtmlTable(ver, htmlFile=fileName, border=0, width="80%", append=TRUE) ## finish html document writeHtmlEnd(htmlFile=fileName) return(invisible(TRUE)) } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/R/testLogger.r��������������������������������������������������������������������������������0000644�0001762�0000144�00000025650�14563457515�014045� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ .newTestLogger <- function(useOwnErrorHandler) { ##@bdescr ## creates a new, empty TestLogger 'object'. ## TestLogger is an object based on the 'closure trick'. It has the task ## to store, administrate and print the test protocol. ##@edescr ##@in useOwnErrorHandler : [logical] ##@ret : [list] ## ##@codestatus : internal ## private data: ## ----------------------- .testData <- list() class(.testData) <- "RUnitTestData" .currentTestSuiteName <- NULL .currentSourceFileName <- NULL ## book keeping variables for individual test functions ## can be reset by function cleanup .currentTraceBack <- NULL .failure <- FALSE .deactivationMsg <- NULL ## if non-NULL test function is deactivated .checkNum <- 0 ## verbosity level: 0: silent .verbosity <- 1L ## define own error handler ## ----------------------- errorHandler <- function() { ##@bdescr ## used as default error handler during test case execution iff ## the user specified 'useOwnErrorHandler' as TRUE (default). ## called in case an error condition, typically stop() has been signalled. ## tries to create a traceback object, currently only used by addError(). ## ## not provided via testLogger but used by R's error handler. ##@edescr ## ##@ret : [NULL] used for it's side effect ## ##@codestatus : internal res <- try(dump.frames()) if (inherits(res, "try-error")) { .currentTraceBack <<- "traceback not available (dump.frames failed)." } else { .currentTraceBack <<- names(last.dump)[-length(last.dump)] } } if(useOwnErrorHandler) { options(error=errorHandler) } ## public methods: ## ----------------------- .getTestData <- function() { ##@bdescr ## return the protocol data collected during the test runs ##@edescr return(.testData) } .setCurrentTestSuite <- function(testSuite) { ##@bdescr ## record the test suite that is currently executed. ##@edescr ##@in testSuite : [testSuite - list] the current testSuite if(is.null(testSuite)) { .currentTestSuiteName <<- NULL } else { if(is.element(testSuite$name, names(.testData))) { stop(paste("Duplicate test suite:", testSuite$name)) } .currentTestSuiteName <<- testSuite$name .testData[[testSuite$name]] <<- list(nTestFunc = 0L, nDeactivated = 0L, nErr = 0, nFail = 0, dirs = testSuite[["dirs"]], testFileRegexp = testSuite[["testFileRegexp"]], testFuncRegexp = testSuite[["testFuncRegexp"]], sourceFileResults = list()) } } .setCurrentSourceFile <- function(sourceFileName) { ##@bdescr ## record the source file whose test functions are currently executed ##@edescr ##@in sourceFileName : [character] name of current source file if(is.null(sourceFileName)) { .currentSourceFileName <<- NULL } else { .currentSourceFileName <<- sourceFileName .testData[[.currentTestSuiteName]]$sourceFileResults[[sourceFileName]] <<- list() } } .addSuccess <- function(testFuncName, secs) { ##@bdescr ## add a successful test function run. ##@edescr ##@in testFuncName : [character] name of test function ##@in secs : [numeric] time in seconds needed by the test function to complete .testData[[.currentTestSuiteName]]$nTestFunc <<- 1 + .testData[[.currentTestSuiteName]]$nTestFunc .testData[[.currentTestSuiteName]]$sourceFileResults[[.currentSourceFileName]][[testFuncName]] <<- list(kind="success", checkNum=.checkNum, time=secs) } .addError <- function(testFuncName, errorMsg) { ##@bdescr ## add a test function that generated an error. ##@edescr ##@in testFuncName : [character] name of test function ##@in errorMsg : [character] the error message .testData[[.currentTestSuiteName]]$nTestFunc <<- 1 + .testData[[.currentTestSuiteName]]$nTestFunc .testData[[.currentTestSuiteName]]$nErr <<- 1 + .testData[[.currentTestSuiteName]]$nErr .testData[[.currentTestSuiteName]]$sourceFileResults[[.currentSourceFileName]][[testFuncName]] <<- list(kind="error", msg=errorMsg, checkNum=.checkNum, traceBack=.currentTraceBack) } .addFailure <- function(testFuncName, failureMsg) { ##@bdescr ## add a test function that generated an error. ##@edescr ##@in testFuncName : [character] name of test function ##@in failureMsg : [character] the failure message .testData[[.currentTestSuiteName]]$nTestFunc <<- 1 + .testData[[.currentTestSuiteName]]$nTestFunc .testData[[.currentTestSuiteName]]$nFail <<- 1 + .testData[[.currentTestSuiteName]]$nFail .testData[[.currentTestSuiteName]]$sourceFileResults[[.currentSourceFileName]][[testFuncName]] <<- list(kind="failure", msg=failureMsg, checkNum=.checkNum, traceBack=NULL) ## traceBack is useless in this case } .addDeactivated <- function(testFuncName) { ##@bdescr ## add a deactivated test function that generated an error. ##@edescr ##@in testFuncName : [character] name of test function .testData[[.currentTestSuiteName]]$nDeactivated <<- 1 + .testData[[.currentTestSuiteName]]$nDeactivated .testData[[.currentTestSuiteName]]$sourceFileResults[[.currentSourceFileName]][[testFuncName]] <<- list(kind="deactivated", msg=.deactivationMsg, checkNum=.checkNum) } .addCheckNum <- function(testFuncName) { ##@bdescr ## add total number of checks performed ##@edescr ##@in testFuncName : [character] name of test function .testData[[.currentTestSuiteName]]$sourceFileResults[[.currentSourceFileName]][[testFuncName]]$checkNum <<- .checkNum } .cleanup <- function() { ##@bdescr ## reset book keeping variables like .failure, ... ## should be called before each test function execution ##@edescr .currentTraceBack <<- NULL .failure <<- FALSE .deactivationMsg <<- NULL .checkNum <<- 0 } .isFailure <- function() { ##@bdescr ## return current failure status ##@edescr return(.failure) } .setFailure <- function() { ##@bdescr ## set failure status to TRUE ##@edescr .failure <<- TRUE } .isDeactivated <- function() { ##@bdescr ## return current deactivation message ##@edescr ##@ret : [logical] TRUE if deactivation msg is not NULL return(!is.null(.deactivationMsg)) } .setDeactivated <- function(msg) { ##@bdescr ## set deactivation message variable, indicating a deactivated test case ##@edescr ##@in msg : [character] message string if (length(msg) > 1) { msg <- paste(msg, collapse=" ") } .deactivationMsg <<- msg } .incrementCheckNum <- function() { ##@bdescr ## increment internal counter of total num of test cases ##@edescr .checkNum <<- 1 + .checkNum } .getCheckNum <- function() { ##@bdescr ## return counter value for total num of test cases ##@edescr return(.checkNum) } .getVerbosity <- function() { ##@bdescr ## return verbosity level for output log messages ##@edescr return(.verbosity) } .setVerbosity <- function(level) { ##@bdescr ## set verbosity level for output log messages ##@edescr ##@in level : [integer] 0: omit output log messages, 1 >= : write begin/end comments for each test case if (length(level) > 1) { level <- level[1] } .verbosity <<- level } tl <- list(getTestData = .getTestData, setCurrentTestSuite = .setCurrentTestSuite, setCurrentSourceFile = .setCurrentSourceFile, addSuccess = function(testFuncName, secs) .addSuccess(testFuncName, secs), addError = function(testFuncName, errorMsg) .addError(testFuncName, errorMsg), addFailure = function(testFuncName, failureMsg) .addFailure(testFuncName, failureMsg), addDeactivated = function(testFuncName) .addDeactivated(testFuncName), addCheckNum = function(testFuncName) .addCheckNum(testFuncName), isFailure = .isFailure, setFailure = .setFailure, isDeactivated = .isDeactivated, setDeactivated = function(msg) .setDeactivated(msg), incrementCheckNum = .incrementCheckNum, getCheckNum = .getCheckNum, getVerbosity = .getVerbosity, setVerbosity = .setVerbosity, cleanup = .cleanup) class(tl) <- "TestLogger" return(invisible(tl)) } getErrors <- function(testData) { ##@bdescr ## return a brief summary of the test case execution result, ## computed from the testData listOfListsOfLists ## ##@edescr ## ##@in testData : [list] S3 RUnitTestData class object ##@ret : [list] containing no of errors, deactivated, failed, and total test functions ## ##@codestatus : testing if(!is(testData, "RUnitTestData")) { stop("getErrors needs an object of class 'RUnitTestData' as argument.") } ret <- list(nErr=0, nDeactivated=0, nFail=0, nTestFunc=0) for(i in seq_along(testData)) { ret$nErr <- ret$nErr + testData[[i]]$nErr ret$nDeactivated <- ret$nDeactivated + testData[[i]]$nDeactivated ret$nFail <- ret$nFail + testData[[i]]$nFail ret$nTestFunc <- ret$nTestFunc + testData[[i]]$nTestFunc } return(ret) } .existsTestLogger <- function(envir=RUnitEnv) { ##@bdescr ## Internal Function ## checks if .testLogger object is available in specified environment ## and if present if this object is of class 'TestLogger' ## ##@edescr ## ##@in envir : [environment] to search within ##@ret : [logical] TRUE iff .testLogger list object is found in specified environment ## ##@codestatus : internal exists(".testLogger", envir=envir) && inherits(get(".testLogger", envir=envir), "TestLogger") } ����������������������������������������������������������������������������������������RUnit/R/runit.r�������������������������������������������������������������������������������������0000644�0001762�0000144�00000036427�14561532231�013056� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ defineTestSuite <- function(name, dirs, testFileRegexp="^runit.+\\.[rR]$", testFuncRegexp="^test.+", rngKind="Marsaglia-Multicarry", rngNormalKind="Kinderman-Ramage") { ##@bdescr ## Convenience functions to handle test suites ##@edescr ## ##@in name : [character] test suite title used in protocol ##@in dirs : [character] vector of paths to search for test case files ##@in testFileRegexp : [character] regular expression string to match file names ##@in testFuncRegexp : [character] (vector) regular expression string(s) to match test case functions within all test case files ##@in rngKind : [character] name of the RNG version, see RNGversion() ##@in rngNormalKind : [character] name of the RNG version for the rnorm, see RNGversion() ##@ret : [RUnitTestSuite] S3 class (list) object, ready for test runner ## ##@codestatus : testing if (missing(dirs)) { stop("argument 'dirs' is missing without a default.") } if (missing(name)) { warning("argument 'name' is missing. using basename(dirs)[1] instead.") name <- basename(dirs)[1] } ret <- list(name=name, dirs=dirs, testFileRegexp=testFileRegexp, testFuncRegexp=testFuncRegexp, rngKind=rngKind, rngNormalKind=rngNormalKind) class(ret) <- "RUnitTestSuite" return(invisible(ret)) } isValidTestSuite <- function(testSuite) { ##@bdescr ## Helper function ## checks 'RUnitTestSuite' class object features ##@edescr ## ##@in testSuite : [RUnitTestSuite] S3 class (list) object, input object for test runner ##@ret : [logical] TRUE if testSuite is valid ## ##@codestatus : testing if(!is(testSuite, "RUnitTestSuite")) { warning(paste("'testSuite' object is not of class 'RUnitTestSuite'.")) return(FALSE) } ## check required elements, irrespective of order, allow for additional elements requiredNames <- c("name", "dirs", "testFileRegexp", "testFuncRegexp", "rngKind", "rngNormalKind") if(!all(requiredNames %in% names(testSuite))) { warning("'testSuite' object does not conform to S3 class definition. Not all list elements present.") return(FALSE) } for(i in seq_along(testSuite)) { if(!is.character(testSuite[[i]])) { warning(paste("'testSuite' object does not conform to S3 class definition.\n", "'", names(testSuite)[i],"' element has to be of type 'character'.", sep="")) return(FALSE) } if(any(testSuite[[i]] == "")) { warning(paste("'testSuite' object does not conform to S3 class definition.\n", "'",names(testSuite)[i],"' element may not contain empty string.", sep="")) return(FALSE) } } notFound <- !file.exists(testSuite[["dirs"]]) if (any(notFound)) { warning(paste("specified directory", paste(testSuite[["dirs"]][notFound], collapse=", "), "not found.")) return(FALSE) } if (length(testSuite[["name"]]) != 1) { warning(paste("'name' element may only contain exactly one name.")) return(FALSE) } if (length(testSuite[["testFileRegexp"]]) != 1) { warning(paste("'testFileRegexp' element may only contain exactly one string.")) return(FALSE) } if (length(testSuite[["testFuncRegexp"]]) != 1) { warning(paste("'testFuncRegexp' element may only contain exactly one string.")) return(FALSE) } ## RNGkind has an internal list of valid names which cannot be accessed ## programmatically. Furthermore, users can define their own RNG and select that one ## so we have to leave it to RNGkind() to check if the arguments are valid. if (length(testSuite[["rngKind"]]) != 1) { warning(paste("'rngKind' element may only contain exactly one name.")) return(FALSE) } if (length(testSuite[["rngNormalKind"]]) != 1) { warning(paste("'rngNormalKind' element may only contain exactly one name.")) return(FALSE) } return(TRUE) } .setUp <- function() { ##@bdescr ## Internal Function. ## Default function to be executed once for each test case before the test case gets executed. ## This function can be adopted to specific package requirements for a given project. ## Need to replace this default with a new function definition. ## Function cannot take arguments and does not have a return value. ##@edescr ## ##@codestatus : internal return(invisible()) } .tearDown <- function() { ##@bdescr ## Internal Function. ## Default function to be executed once for each test case after the test case got executed. ## This function can be adopted to specific package requirements for a given project. ## Need to replace this default with a new function definition. ## Function cannot take arguments and does not have a return value. ##@edescr ## ##@codestatus : internal return(invisible()) } .executeTestCase <- function(funcName, envir, setUpFunc, tearDownFunc) { ##@bdescr ## Internal Function. ## Execute individual test case, record logs and change state of global TestLogger object. ##@edescr ## ##@in funcName : [character] name of test case function ##@in envir : [environment] ##@in setUpFunc : [function] ##@in tearDownFunc : [function] ##@ret : [NULL] ## ##@codestatus : internal ## write to stdout for logging func <- get(funcName, envir=envir) ## anything else than a function is ignored. if(mode(func) != "function") { return(invisible()) } if (RUnitEnv$.testLogger$getVerbosity() > 0) { cat("\n\nExecuting test function", funcName, " ... ") } ## safe execution of setup function res <- try(setUpFunc()) if (inherits(res, "try-error")) { message <- paste("Error executing .setUp before",funcName, ":", geterrmessage()) RUnitEnv$.testLogger$addError(testFuncName=paste(".setUp (before ", funcName, ")", sep=""), errorMsg=message) return(invisible()) } ## reset book keeping variables in RUnitEnv$.testLogger RUnitEnv$.testLogger$cleanup() ## ordinary test function execution: timing <- try(system.time(func(), gcFirst=RUnitEnv$.gcBeforeTest)) if (inherits(timing, "try-error")) { if(RUnitEnv$.testLogger$isFailure()) { RUnitEnv$.testLogger$addFailure(testFuncName=funcName, failureMsg=geterrmessage()) } else if(RUnitEnv$.testLogger$isDeactivated()) { RUnitEnv$.testLogger$addDeactivated(testFuncName=funcName) } else { RUnitEnv$.testLogger$addError(testFuncName=funcName, errorMsg=geterrmessage()) } } else { RUnitEnv$.testLogger$addSuccess(testFuncName=funcName, secs=round(timing[3], 2)) } ## add number of check function calls within test case RUnitEnv$.testLogger$addCheckNum(testFuncName=funcName) ## safe execution of tearDown function res <- try(tearDownFunc()) if (inherits(res, "try-error")) { message <- paste("Error executing .tearDown after",funcName, ":", geterrmessage()) RUnitEnv$.testLogger$addError(testFuncName=paste(".tearDown (after ", funcName, ")", sep=""), errorMsg=message) return(invisible()) } if (RUnitEnv$.testLogger$getVerbosity() > 0) { cat(" done successfully.\n\n") } return(invisible()) } .sourceTestFile <- function(absTestFileName, testFuncRegexp) { ##@bdescr ## This function sources a file, finds all the test functions in it, executes them ## and reports the results to the TestLogger. ## No return value, called for its side effects on TestLogger object ##@edescr ## ##@in absTestFileName : [character] absolute path name of the file to test ##@in testFuncRegexp : [character] a regular expression identifying the names of test functions ##@ret : [NULL] ## ##@codestatus : internal RUnitEnv$.testLogger$setCurrentSourceFile(absTestFileName) if (!file.exists(absTestFileName)) { msgText <- paste("Test case file ", absTestFileName," not found.") RUnitEnv$.testLogger$addError(testFuncName=absTestFileName, errorMsg=msgText) return(invisible()) } sandbox <- new.env(parent=.GlobalEnv) ## will be destroyed after function closure is left ## catch syntax errors in test case file res <- try(sys.source(absTestFileName, envir=sandbox)) if (inherits(res, "try-error")) { message <- paste("Error while sourcing ",absTestFileName,":",geterrmessage()) RUnitEnv$.testLogger$addError(testFuncName=absTestFileName, errorMsg=message) return(invisible()) } ## test file provides definition of .setUp/.tearDown if (exists(".setUp", envir=sandbox, inherits=FALSE)) { .setUp <- get(".setUp", envir=sandbox) } if (exists(".tearDown", envir=sandbox, inherits=FALSE)) { .tearDown <- get(".tearDown", envir=sandbox) } testFunctions <- ls(pattern=testFuncRegexp, envir=sandbox) for (funcName in testFunctions) { .executeTestCase(funcName, envir=sandbox, setUpFunc=.setUp, tearDownFunc=.tearDown) } } runTestSuite <- function(testSuites, useOwnErrorHandler=TRUE, verbose=getOption("RUnit")$verbose, gcBeforeTest=FALSE) { ##@bdescr ## This is the main function of the RUnit framework. It identifies all specified ## test files and triggers all required actions. At the end it creates a test ## protocol data object. ## IMPORTANT to note, the random number generator is (re-)set to the default ## methods specified in defineTestSuite() before each new test case *file* is sourced. ## This guarantees that each new test case set defined together in on file can rely ## on the default, even if the random number generator version is being reconfigured in some ## previous test case file(s). ##@edescr ## ##@in testSuites : [list] list of test suite lists ##@in useOwnErrorHandler : [logical] TRUE (default) : use the RUnit error handler ##@in verbose : [integer] >= 1: (default) write begin/end comments for each test case, 0: omit begin/end comment ##@in gcBeforeTest : [logical] FALSE (default) : garbage collect before timing each test ##@ret : [list] 'RUnitTestData' S3 class object ## ##@codestatus : testing ## preconditions if (!is.logical(useOwnErrorHandler)) { stop("argument 'useOwnErrorHandler' has to be of type logical.") } if (length(useOwnErrorHandler) != 1) { stop("argument 'useOwnErrorHandler' has to be of length 1.") } if (is.na(useOwnErrorHandler)) { stop("argument 'useOwnErrorHandler' may not contain NA.") } if (!is.logical(gcBeforeTest)) { stop("argument 'gcBeforeTest' has to be of type logical.") } if (length(gcBeforeTest) != 1) { stop("argument 'gcBeforeTest' has to be of length 1.") } if (is.na(gcBeforeTest)) { stop("argument 'gcBeforeTest' may not contain NA.") } oFile <- getOption("RUnit")$outfile if (!is.null(oFile)) { if(is.character(oFile)) { ## connection has to be open when handed on to sink oFile <- file(oFile, "w") } else if(!inherits(oFile, "connection")) { stop("'outfile' must be a connection or a character string.") } sink(file=oFile) sink(file=oFile, type="message") resetStream <- function() { sink(type="message") sink() flush(oFile) close(oFile) ##close(oFile) } on.exit(resetStream()) } ## record RNGkind and reinstantiate on exit rngDefault <- RNGkind() on.exit(RNGkind(kind=rngDefault[1], normal.kind=rngDefault[2]), add=TRUE) oldErrorHandler <- getOption("error") ## reinstall error handler on.exit(options(error=oldErrorHandler), add=TRUE) ## initialize TestLogger assign(".testLogger", .newTestLogger(useOwnErrorHandler), envir=RUnitEnv) RUnitEnv$.testLogger$setVerbosity(verbose) ## store the information about GC before test assign(".gcBeforeTest", gcBeforeTest, envir=RUnitEnv) ## main loop if (isValidTestSuite(testSuites)) { testSuites <- list(testSuites) } else if (isValidTestSuite(testSuites[[1]])) { ## do nothing } else { stop("invalid test suite supplied.") } for (i in seq_along(testSuites)) { testSuite <- testSuites[[i]] if(!isValidTestSuite(testSuite)) { errMsg <- paste("Invalid test suite",testSuite$name,". Test run aborted.") stop(errMsg) } RUnitEnv$.testLogger$setCurrentTestSuite(testSuite) testFiles <- list.files(testSuite$dirs, pattern = testSuite$testFileRegexp, full.names=TRUE) for(testFile in testFiles) { ## set a standard random number generator. RNGkind(kind=testSuite$rngKind, normal.kind=testSuite$rngNormalKind) .sourceTestFile(testFile, testSuite$testFuncRegexp) } } ret <- RUnitEnv$.testLogger$getTestData() return(ret) } runTestFile <- function(absFileName, useOwnErrorHandler=TRUE, testFuncRegexp="^test.+", rngKind="Marsaglia-Multicarry", rngNormalKind="Kinderman-Ramage", verbose=getOption("RUnit")$verbose, gcBeforeTest=FALSE) { ##@bdescr ## Convenience function. ##@edescr ## ##@in absFileName : [character] complete file name of test cases code file ##@in useOwnErrorHandler : [logical] if TRUE RUnits error handler will be used ##@in testFuncRegexp : [character] ##@in rngKind : [character] name of the RNG, see RNGkind for avialbale options ##@in rngNormalKind : [character] name of the RNG for rnorm, see RNGkind for avialbale options ##@in verbose : [integer] >= 1: (default) write begin/end comments for each test case, 0: ommit begin/end comment (passed on to function runTestSuite) ##@in gcBeforeTest : [logical] FALSE (default) : garbage collect before timing each test ##@ret : [list] 'RUnitTestData' S3 class object ## ##@codestatus : testing ## preconditions ## all error checking and handling is delegated to function runTestSuite fn <- basename(absFileName) nn <- strsplit(fn, "\\.")[[1]][1] dn <- dirname(absFileName) ts <- defineTestSuite(name=nn, dirs=dn, testFileRegexp=paste("^", fn, "$", sep=""), testFuncRegexp=testFuncRegexp, rngKind=rngKind, rngNormalKind=rngNormalKind) return(runTestSuite(ts, useOwnErrorHandler=useOwnErrorHandler, verbose=verbose, gcBeforeTest=gcBeforeTest)) } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/R/inspector.r���������������������������������������������������������������������������������0000644�0001762�0000144�00000035312�13267374743�013731� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ includeTracker <- function(fbody, track=track) { ##@bdescr ## Internal function ## ##@edescr ## ##@in fbody : [character] vector of code lines of function to track ##@in track : [trackInfo] list ##@ret : [list] with elements list(modFunc=c(sig,newBody),newSource = newCode) ## ##@codestatus : internal ## get the signature sig <- fbody[1] ## get the block structure (important for if, for, while, else with one line) block <- sapply(fbody[-1],function(x) regexpr("[^ ]",x)[1], USE.NAMES=FALSE) ## vector of keywords kwOpen <- c("for","while","repeat","if","else") ## keyword at begin kwGrep <- paste("(",paste(kwOpen,sep="",collapse="|"),")",sep="") oneLiner <- function(code) { ##@bdescr ## utility ## search a character vector ie. the vector of lines of a function body ## for block structures e.g. for|while|repeat|if|else { } code block ##@edescr ## ##@in code : [character] vector of function body code lines ##@ret : [logical] vector of length of code, indication which are one line control blocks ## ##@codestatus : internal return(sapply(code, function(line) { opBr <- grep(paste("^[ ]*",kwGrep,".*[ ]+$",sep=""), line) ## special case if combined with assignment or math operators opBr2 <- grep(paste("(<-|=|\\+|\\-|\\*|\\/)[ ]*if[ ]*\\(",sep=""), line) if(length(opBr) > 0 || length(opBr2) > 0) { return(TRUE) } return(FALSE) }, USE.NAMES=FALSE)) } ## set Brackets setBrackets <- function(potLine,block,env) { ##@bdescr ## ##@edescr ## ##@in potLine : [logical] mask vector which line contains a one-line control construct ##@in block : [integer] vector ##@in env : [logical] mask vector: which line already contains a opening brace ##@ret : [list] with matching element vectors: openBr & clodeBr ## ##@codestatus : internal oBr <- character(length(potLine)) clBr <- character(length(potLine)) lineIdx <- 1L while(lineIdx < length(potLine)) { if(potLine[lineIdx] && !(potLine[lineIdx+1])) { oBr[lineIdx] <- "{" if (!env[lineIdx+1]) { clBr[lineIdx+2] <- paste(clBr[lineIdx+2],"}") } else { bbl <- block[lineIdx] endBlockIdx <- min(which((bbl >= block) & (seq_along(block) > lineIdx))) clBr[endBlockIdx] <- paste(clBr[endBlockIdx],"}") } } else if(potLine[lineIdx] && (potLine[lineIdx+1]) ) { oBr[lineIdx] <- "{" bbl <- block[lineIdx] endBlockIdx <- min(which((bbl >= block) & (seq_along(block) > lineIdx))) clBr[endBlockIdx] <- paste(clBr[endBlockIdx],"}") } lineIdx <- lineIdx + 1L } return(list(openBr=oBr, closeBr=clBr)) } ## check for new environments env <- sapply(fbody[-1], function(code) { envIdx <- grep("\\{$",code) if(length(envIdx) > 0) { return(TRUE) } return(FALSE) },USE.NAMES=FALSE) ## check the block structure block <- sapply(fbody[-1], function(x) regexpr("[^ ]",x)[1], USE.NAMES=FALSE) ## is 4 a convention or a rule? block <- (block %/% 4) + 1 ## check for if's, while's, etc. ol <- oneLiner(fbody[-1]) ## create brackets for control structures without new environments br <- setBrackets(ol,block,env) ## create new Code newCode <- paste(as.vector(rbind(br$closeBr,fbody[-1],br$openBr))) newCode <- newCode[newCode != ""] ## include the breakpoint function bpVec <- sapply(newCode, function(line) { nobp <- grep("^[ ]*(else |\\{|\\})",line) if(length(nobp) == 0) { return("track$bp();") } return("") },USE.NAMES=FALSE) for(i in seq_along(bpVec)) { bpVec[i] <- gsub("\\(\\)",paste("(",i,")",sep=""),bpVec[i]) } ## create the mainpulated body of the function newBody <- paste(bpVec,newCode) ## return signature and body return(list(modFunc=c(sig,newBody), newSource=newCode)) } tracker <- function() { ##@bdescr ## initialization of the central tracking object ## which stores all information related to inspection results and code execution structure ## defines accessor functions ## - addFunc (fId,src,callExpr): add specified function to the track list ## - getSource(nr): get the source code (character) for function nr on track list ## - init ## - bp ## - getTrackInfo ## - isValidTrackInfo ##@edescr ## ##@ret : [list] OO object with functions addFunc, getSourcee, init, bp, getTrackInfo ## ##@codestatus : testing ## object for information trackInfo <- list() class(trackInfo) <- "trackInfo" ## current function index fIdx <- 0 ## old time oldTime <- NULL ## old src line oldSrcLine <- 0 addFunc <- function(fId,src,callExpr) { ##@bdescr ## ## accessor function ##@edescr ## ##@in fId : [character] function name ##@in src : [character] source code character vector ##@in callExpr : [character] function call ##@ret : [NULL] returns invisible, used for its side effects ## ## codestatus : internal ## preconditions if( length(fId) != 1) { stop("fId must be one character string: function name") } isThere <- which(fId == names(trackInfo)) if(length(isThere) == 1) { ## already in tracking list fIdx <<- isThere } else { fIdx <<- fIdx + 1 newFuncInfo <- list(src=src, run=integer(length(src)), time=numeric(length(src)), graph=matrix(0,nrow=length(src),ncol=length(src)), nrRuns=as.integer(0), funcCall=callExpr) ## append strips class attribute trackInfo <- append(trackInfo,list(newFuncInfo)) names(trackInfo)[fIdx] <- fId class(trackInfo) <- "trackInfo" ## update global state trackInfo <<- trackInfo } ## increment run number trackInfo[[fIdx]]$nrRuns <<- trackInfo[[fIdx]]$nrRuns + 1 ## initialize local variables oldSrcLine <<- 0 oldTime <<- NULL return(invisible()) } getTrackInfo <- function() { ##@bdescr ## ## accessor function ## returns the main inspection result list with ## elements ## - src ## - run ## - time ## - graph ## - nrRuns ## - funCall ##@edescr ## ##@ret : [trackInfo] S3 class list (see description above) ## ## codestatus : internal return(trackInfo) } init <- function() { ##@bdescr ## ## initalisation function ## sets/resets variables run and fIdx ##@edescr ## ##@ret : [NULL] returns invisible, used for its side effects ## ## codestatus : internal trackInfoInit <- list() class(trackInfoInit) <- "trackInfo" trackInfo <<- trackInfoInit fIdx <<- 0L return(invisible()) } bp <- function(nr) { ##@bdescr ## ## accessor function ##@edescr ## ##@in : [integer] index, function run number ##@ret : [NULL] returns invisible, used for its side effects ## ## codestatus : internal ## preconditions if (length(nr) != 1) { stop("argument 'nr' has to be of length 1.") } if (is.na(nr)) { stop("argument 'nr' may not contain missing value (NA).") } trackInfo[[fIdx]]$run[nr] <<- trackInfo[[fIdx]]$run[nr] + 1 ## cumulative processing time if(!is.null(oldTime)) { dtime <- proc.time()[1] - oldTime trackInfo[[fIdx]]$time[nr] <<- trackInfo[[fIdx]]$time[nr] + dtime } oldTime <<- proc.time()[1] ## graph if(oldSrcLine != 0) { trackInfo[[fIdx]]$graph[oldSrcLine,nr] <<- trackInfo[[fIdx]]$graph[oldSrcLine,nr] + 1 } ## store the old line oldSrcLine <<- nr return(invisible()) } getSource <- function(nr) { ##@bdescr ## ## accessor function ## returns the source code as character string ##@edescr ## ##@in : [integer] index, function run number ##@ret : [character] string, source code ## ##@codestatus : untested ## preconditions if (length(nr) != 1) { stop("argument 'nr' has to be of length 1.") } if (is.na(nr)) { stop("argument 'nr' may not contain missing value (NA).") } return(trackInfo[[nr]]$src) } isValidTrackInfo <- function(trackInfo) { ##@bdescr ## test function ## returns TRUE iff trackInfo object fullfils S3 class definition constraints ## - S3 class 'trackInfo' ## - with elements ## - src [character] vector of function source code lines ## - run [integer] vector of no. of times this function was called ## - time [numeric] vector of function execution times in seconds per call ## - graph [matrix] connection matrix (# code linbes x # of execution calls) ## - nrRuns [integer] ## - funcCall [character] function call ##@edescr ## ##@in : [trackInfo] S3 class object ##@ret : [logical] TRUE, iff object fullfils class definition constraints ## ##@codestatus : untested if (!is(trackInfo,"trackInfo")) { return(FALSE) } checkElements <- function(x) { if (!all(c("src", "run", "time", "graph", "nrRuns", "funcCall") %in% names(x))) { return(FALSE) } if (length(x[["run"]]) < 1 || any(is.na(x[["run"]])) || any(x[["run"]] < 0)) { return(FALSE) } if (length(x[["time"]]) < 1 || any(is.na(x[["time"]])) || any(x[["time"]] < 0)) { return(FALSE) } ## TODO: graph if (length(x[["nrRuns"]]) != 1 || is.na(x[["nrRuns"]]) || x[["nrRuns"]] < 0) { return(FALSE) } if (length(x[["funcCall"]]) != 1 || is.na(x[["funcCall"]])) { return(FALSE) } } ok <- sapply(trackInfo, checkElements) if (!all(ok)) { return(FALSE) } return(TRUE) } return(list(addFunc=addFunc, getSource=getSource, init=init, bp=bp, getTrackInfo=getTrackInfo, isValid=isValidTrackInfo)) } inspect <- function(expr, track=track) { ##@bdescr ## inspector function ## an attempt is made to parse the expression or function ## insert track info statements to be used for subsequent ## code execution structure displays ## ## can handle functions aswell as generics ##@edescr ## ##@in expr : [call] ##@in track : [list] tracker object ##@ret : [expression|ANY] either the unevaluated expression of the function or the result of the function call ## ##@codestatus : testing ## get the call and its parameters fCall <- as.character(substitute(expr)) ## get the original call callExpr <- deparse(substitute(expr)) ## get the name of the function fname <- fCall[1] ## check for generic function if(isGeneric(fname)) { ## get type of arguments selType <- sapply(fCall[-1], function(x) { if(exists(x, envir=sys.parent(sys.parent()))) { varSig <- is(get(x,envir=sys.parent(sys.parent())))[1] } else { varSig <- is(eval(parse(text=x)))[1] } return(varSig) },USE.NAMES=FALSE) ## we have to check for missing arguments formalArg <- names(formals(getGeneric(fCall[1]))) ## number of missing arguments nrMissing <- length(formalArg) - length(selType) if(nrMissing > 0) { ## check for ... ellipseIdx <- which(formalArg == "...") if(length(ellipseIdx) != 0) { selType <- c(selType,rep("missing",nrMissing -1 )) } else { selType <- c(selType,rep("missing",nrMissing)) } } ## select function selFunc <- selectMethod(fname, selType) ## deparse the function fbody <- deparse(selFunc@.Data, width.cutoff=500) ## create an identifier for the generic function fNameId <- paste("S4",fname,paste(selFunc@defined@.Data, collapse="/"), sep="/") } else { ## deparse the function fbody <- try(deparse(get(fname), width.cutoff=500), silent=TRUE) if (inherits(fbody, "try-error")) { ## in case the function is defined ## in the test case file fbody <- try(deparse(get(fname, envir=sys.parent()), width.cutoff=500)) if (inherits(fbody, "try-error")) { stop("function not found.") } } ## create an identifier for the generic function fNameId <- paste("R/",fname,sep="") } ## generate the new body of the function newFunc <- includeTracker(fbody, track=track) track$addFunc(fNameId, newFunc$newSource, callExpr) ## build the test function eval(parse(text=c("testFunc <- ",newFunc$modFunc)),envir=sys.frame()) ## create function call newFunCall <- paste("testFunc(",paste(fCall[-1], collapse=","), ")",sep="") parsedFunc <- try(parse(text=newFunCall)) ## check for an error if(!inherits(parsedFunc,"try-error")) { ## call the new function res <- eval(parsedFunc, envir=parent.frame()) } else { ## no parsing possible ## simple call without tracking res <- expr } ## do here some error checking return(res) } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/R/junitProtocol.r�����������������������������������������������������������������������������0000644�0001762�0000144�00000010133�13267374743�014570� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA printJUnitProtocol <- function(testData, fileName = "") { ##@bdescr ## Report generator ## Extracts the log information stored in the 'RUnitTestData' test run object ## and generates a JUnit-style formated XML output. ##@edescr ## ##@in testData : [RUnitTestData] S3 class object ##@in fileName : [character] string, full path + file name to be written to ##@ret : [logical] TRUE if execution completed without error ## ##@codestatus : testing ## preconditions if (!is(testData, "RUnitTestData")) { stop("Argument 'testData' must be of class 'RUnitTestData'.") } if (!is.character(fileName)) { stop("Argument 'fileName' has to be of type character.") } if (length(fileName) != 1) { stop("Argument 'fileName' must contain exactly one element.") } errInfo <- getErrors(testData) # Create entry fro all test suites xml.testsuites <- XML::newXMLNode("testsuites", attrs = c( errors=errInfo$nErr, failures=errInfo$nFail, tests=errInfo$nTestFunc) ) for (tsName in names(testData)) { # Create entry for test suite xml.testsuite <- XML::newXMLNode("testsuite", attrs = c( errors = testData[[tsName]]$nErr, failures = testData[[tsName]]$nFail, name = tsName, tests = testData[[tsName]]$nTestFunc )) XML::addChildren(xml.testsuites, kids=c(xml.testsuite)) if (testData[[tsName]]$nErr + testData[[tsName]]$nFail >= 0) { srcFileRes <- testData[[tsName]][["sourceFileResults"]] for (i in seq_along(srcFileRes)) { testFuncNames <- names(srcFileRes[[i]]) for (j in seq_along(testFuncNames)) { funcList <- srcFileRes[[i]][[testFuncNames[j]]] # Each tested function gets a testcase xml.testcase <- XML::newXMLNode("testcase", attrs=c(name=testFuncNames[j], time=funcList$time[['elapsed']])) XML::addChildren(xml.testsuite, kids=c(xml.testcase)) if (funcList$kind == "success") { } else if (funcList$kind == "error") { xml.error <- XML::newXMLNode("error", attrs=c( "message"=funcList$msg, "type"="ERROR")) XML::addChildren(xml.testcase, kids=c(xml.error)) } else if (funcList$kind == "failure") { xml.error <- XML::newXMLNode("failure", attrs=c( "message"=funcList$msg, "type"="FAILURE")) XML::addChildren(xml.testcase, kids=c(xml.error)) } else if (funcList$kind == "deactivated") { xml.skipped <- XML::newXMLNode("skipped") XML::addChildren(xml.testcase, kids=c(xml.skipped)) } } } } } xml <- XML::saveXML(xml.testsuites) if(fileName=="") { write(xml, stdout()) } else { dir.create(dirname(fileName), showWarnings=FALSE, recursive=TRUE) fileConn <- file(fileName) write(xml, fileConn) close(fileConn) } return(invisible(TRUE)) } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/R/options.r�����������������������������������������������������������������������������������0000644�0001762�0000144�00000002741�13267374743�013416� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ .buildRUnitOptions <- function() { ##@bdescr ## Internal function ## adds an entry to R's default global option list ## modelled after version in package Biobase (BioC) ##@edescr ## ##@ret : [list] extended options() list ## ##@codestatus : internal RUnit <- getOption("RUnit") if (is.null(RUnit)) { RUnit <- list() class(RUnit) <- "RUnitOptions" } if (is.null( RUnit$verbose)) { ## integer: == 0: silent, >= 1: add comments to test case log RUnit$verbose <- 1L } if (is.null(RUnit$silent)) { RUnit$silent <- FALSE } if (is.null(RUnit$outfile)) { RUnit$outfile <- NULL } options("RUnit"=RUnit) } �������������������������������RUnit/R/exportHTML.r��������������������������������������������������������������������������������0000644�0001762�0000144�00000023137�14563457515�013732� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2010 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ plotConnection.trackInfo <- function(con, pngfile, ...) { ##@bdescr ## create a plot displaying the execution flow as a graph ##@edescr ## ##@in con : [matrix] counts of execution calls for previous functions ##@in pngfile : [character] string specifying the full path & file name of the ## plot file (PNG) to be generate ##@ret : [NULL] used for its side effect ## ##@codestatus : testing #stopifnot(require(graphics)) ## experimental 2nd order connections ## color for arrows color <- c("black","lightgreen","green","lightblue","blue","orangered","red") ## create nothing if if(all(con==0)) { ## open png device grDevices::png(filename=pngfile,width=1024,height=960) graphics::plot(1:10,axes=FALSE,xlab="",ylab="",main="",type="n") graphics::text(5,5,labels="No connection graph available") grDevices::dev.off() return(invisible()) } ## overall connections allCon <- sum(con) ## connections with percent con <- ceiling(con/sum(con)*100) ## normalize for colors con <- (con + 14) %/% 15 ## open png device grDevices::png(filename=pngfile,width=1024,height=960) ## basic plot plot(x=1:nrow(con), y=1:nrow(con), type="n",axes=FALSE,ylab="# line",xlab="", ylim=c(nrow(con),1)) ## draw text lines text(x=1, y=1:nrow(con), labels=1:nrow(con)) ## offset, to avoid complete overlay offset <- rep(3,length.out=nrow(con)) ## minimal x xmin <- 2 ## check all connections for(i in 1:nrow(con)) { for(j in 1:ncol(con)) { ## check for an existing connection if(con[i,j] != 0) { colDraw <- color[con[i,j]] from <- j to <- i ## circular if(from == to) { top <- from + 0.5 bot <- from - 0.5 middle <- (xmin+offset[from])/2 ## top spline splTop <- stats::spline(c(xmin,middle,offset[from]), c(from + 0.2,top,from)) ## bottom spline splBot <- stats::spline(c(xmin,middle,offset[from]), c(from - 0.2,bot,from)) lines(splTop$x, splTop$y, col=colDraw) lines(splBot$x, splBot$y, col=colDraw) l <- length(splTop$y) ## draw arrow tips arrows(splTop$x[l-1], splTop$y[l-1], splTop$x[l], splTop$y[l], length=0.04, col=colDraw) offset[from] <- offset[from] + 1 } else { ## "regular" case middle <- (i+j)/2; splxy <- stats::spline(c(from - 0.2, middle, to + 0.2), c(xmin - 0.2, offset[from], xmin + 0.2)) lines(splxy$y, splxy$x, col=colDraw) if(i < j) { l <- length(splxy$y) ## draw an arrow tip arrows(splxy$y[l-1], splxy$x[l-1], splxy$y[l], splxy$x[l], length=0.06, col=colDraw) } else { ## draw "inverse" arrow tip arrows(splxy$y[2], splxy$x[2], splxy$y[1], splxy$x[1], length=0.06, col=colDraw) } ## set offset higher offset[from] <- offset[from] + 1 } } } } legposx <- nrow(con) leg.txt <- c("0-15%","15-30%","30-45%","45-60%","60-75%","75-90%","90-100%") legend(x=legposx,y=1,legend=leg.txt,lty=1,xjust=1,col=color) grDevices::dev.off() return(invisible()) } printHTML <- function(object, baseDir=".") UseMethod("printHTML") printHTML.default <- function(object, baseDir=".") NextMethod("printHTML") printHTML.trackInfo <- function(object, baseDir=".") { ##@bdescr ## create a HTML representation of the TrackInfo object data ##@edescr ## ##@in object : [list] trackInfo object ##@in baseDir : [character] string specifying the full path to the root directory to hold the HTML pages ## ## ##@codestatus : untested ## preconditions if (!is(object, "trackInfo")) { stop("argument 'object' has to be a list of class 'trackInfo'.") } if (!is.character(baseDir)) { stop("argument 'baseDir' has to be of type 'character'.") } if (length(baseDir) != 1) { stop("argument 'baseDir' has to contain exactly one element.") } if (is.na(baseDir)) { stop("argument 'baseDir' may not be missing value.") } path <- file.path(baseDir,"results") if (!file.exists(path)) { ok <- dir.create(path) if(!ok) { stop(paste("could not create", path) ) } } htmlFile <- file.path(path,"index.html") footerString <- paste("RUnit ", packageDescription("RUnit", fields="Version"), as.character(Sys.time())) ## create index.html writeHtmlHeader("RUnit Code Inspection - Overview",htmlFile) writeHtmlSection("Overview",2,htmlFile) writeBeginTable(c("Categ.","Name","Signature"),htmlFile) for(i in seq_along(object)) { funcID <- strsplit(names(object)[i],"/")[[1]] funcCat <- funcID[1] funcName <- funcID[2] if(length(funcID) > 2) { sig <- funcID[3:length(funcID)] funcSig <- paste(funcName,"(",paste(sig,collapse=", "),")",sep="") } else { funcSig <- "" } writeBeginTag("tr",htmlFile) writeCR(htmlFile) ## write function category writeBeginTag("td",htmlFile) writeRaw(funcCat,htmlFile) writeEndTag("td",htmlFile) writeCR(htmlFile) ## write function name writeBeginTag("td",htmlFile) writeLink(file.path(".", paste("result",i,".html",sep="")), funcName, htmlFile) writeEndTag("td",htmlFile) writeCR(htmlFile) ## write function signature writeBeginTag("td",htmlFile) writeRaw(funcSig,htmlFile) writeEndTag("td",htmlFile) writeCR(htmlFile) writeEndTag("tr",htmlFile) } writeEndTable(htmlFile) writeRaw(footerString, htmlFile) writeHtmlEnd(htmlFile) writeLinkRef <- function(htmlFile,leftLink,leftName,rightLink,rightName) { writeBeginTable(c("",""),htmlFile,border=0,width="100%") writeBeginTag("tr",htmlFile) writeCR(htmlFile) writeBeginTag("td",htmlFile) writeLink(leftLink,leftName,htmlFile) writeEndTag("td",htmlFile) writeCR(htmlFile) writeBeginTag("td",htmlFile,"align=\"right\""); writeLink(rightLink,rightName,htmlFile) writeEndTag("td",htmlFile) writeEndTag("tr",htmlFile) writeCR(htmlFile) writeEndTable(htmlFile) } ## create result pages for(i in seq_along(object)) { absGraphImg <- file.path(path, paste("con",i,".png",sep="")) absGraphFile <- file.path(path, paste("con",i,".html",sep="")) relGraphImg <- file.path(".", paste("con",i,".png",sep="")) relGraphFile <- file.path(".", paste("con",i,".html",sep="")) relHTMLFile <- file.path(".", paste("result",i,".html",sep="")) htmlFile <- file.path(path, paste("result",i,".html",sep="")) ## begin result page writeHtmlHeader("RUnit Code Inspection - Result",htmlFile) writeLinkRef(htmlFile,"index.html","index",relGraphFile,"graph") writeHtmlSep(htmlFile) writeHtmlSection("Result",2,htmlFile) funcName <- strsplit(names(object)[i],"/")[[1]][2] writeRaw("Function: ",htmlFile) writeBeginTag("b",htmlFile) writeRaw(funcName,htmlFile) writeEndTag("b",htmlFile) writeCR(htmlFile) writeRaw("Runs: ",htmlFile) writeBeginTag("b",htmlFile) writeRaw(object[[i]]$nrRuns,htmlFile) writeEndTag("b",htmlFile) writeCR(htmlFile) writeCR(htmlFile) writeBeginTable(c("line","code","calls","time"),htmlFile) for(j in seq_along(object[[i]]$src)) { srcLine <- object[[i]]$src[j] leadingSpaceNr <- attr(regexpr("^( )*",srcLine),"match.length") if(leadingSpaceNr > 0) { srcLine <- gsub("^( )*","",srcLine) srcLine <- paste(paste(rep(" ",leadingSpaceNr),collapse=""), srcLine,collapse="",sep="") } if(object[[i]]$run[j] > 0) { bgcolor <- "#00D000" } else { bgcolor <- "#D00000" } writeTableRow(c(j,srcLine,object[[i]]$run[j],round(object[[i]]$time[j],2)), htmlFile,bgcolor=bgcolor) } writeEndTable(htmlFile) writeHtmlSep(htmlFile) writeLinkRef(htmlFile,"index.html","index",relGraphFile,"graph") writeHtmlSep(htmlFile) writeRaw(footerString, htmlFile) writeHtmlEnd(htmlFile) ## Conncetion plot plotConnection.trackInfo(object[[i]]$graph, absGraphImg) writeHtmlHeader("RUnit Code Inspection - Connection Graph",absGraphFile) writeLinkRef(absGraphFile,"index.html","index",relHTMLFile,"Function") writeHtmlSep(absGraphFile) writeHtmlSection("Connection Graph",2,absGraphFile) writeImage(relGraphImg,absGraphFile) writeCR(absGraphFile) writeHtmlSep(absGraphFile) writeLinkRef(absGraphFile,"index.html","index",relHTMLFile,"Function") writeRaw(footerString, absGraphFile) writeHtmlEnd(absGraphFile) } return(invisible()) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/R/checkFuncs.r��������������������������������������������������������������������������������0000644�0001762�0000144�00000017550�14561530024�013763� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ checkEquals <- function(target, current, msg="", tolerance = .Machine$double.eps^0.5, checkNames=TRUE, ...) { ##@bdescr ## checks if two objects are equal, thin wrapper around 'all.equal' ## with tolerance one can adjust to and allow for numerical imprecision ##@edescr ##@in target : [ANY] one thing to be compared ##@in current : [ANY] second object to be compared ##@in msg : [character] an optional message to further identify and document the call ##@in tolerance : [numeric] directly passed to 'all.equal', see there for further documentation ##@in checkNames: [logical] iff TRUE do not strip names attributes from current and target prior to the comparison ##@ret : [logical] TRUE iff check was correct ## ##@codestatus : testing if (missing(current)) { stop("argument 'current' is missing") } if(!is.numeric(tolerance)) { stop("'tolerance' has to be a numeric value") } if (length(tolerance) != 1) { stop("'tolerance' has to be a scalar") } if(!is.logical(checkNames)) { stop("'checkNames' has to be a logical value") } if (length(checkNames) != 1) { stop("'checkNames' has to be a scalar") } if(.existsTestLogger()) { RUnitEnv$.testLogger$incrementCheckNum() } if (!identical(TRUE, checkNames)) { names(target) <- NULL names(current) <- NULL } result <- all.equal(target, current, tolerance=tolerance, ...) if (!identical(result, TRUE)) { if(.existsTestLogger()) { RUnitEnv$.testLogger$setFailure() } stop(paste(result, collapse="\n"), "\n", msg) } else { return(TRUE) } } checkEqualsNumeric <- function(target, current, msg="", tolerance = .Machine$double.eps^0.5, ...) { ##@bdescr ## checks if two objects are equal, thin wrapper around 'all.equal.numeric' ## with tolerance one can adjust to and allow for numerical imprecision. ## current and target are converted via as.vector() thereby stripping all attributes. ##@edescr ##@in target : [ANY] one thing to be compared ##@in current : [ANY] second object to be compared ##@in tolerance : [numeric] directly passed to 'all.equal.numeric', see there for further documentation ##@in msg : [character] an optional message to further identify and document the call ## ##@ret : [logical] TRUE, if objects 'target' and 'current' are equal w.r.t. specified numerical tolerance, else a stop signal is issued ## ##@codestatus : testing if (missing(current)) { stop("argument 'current' is missing") } if(!is.numeric(tolerance)) { stop("'tolerance' has to be a numeric value") } if (length(tolerance) != 1) { stop("'tolerance' has to be a scalar") } if(.existsTestLogger()) { RUnitEnv$.testLogger$incrementCheckNum() } ## Fix for R>4.1.2 where as.vector.data.frame returns a list instead of vector if(is.data.frame(target)) { target <- unlist(as.list(target), use.names=FALSE) } if(is.data.frame(current)) { current <- unlist(as.list(current), use.names=FALSE) } ## R 2.3.0: changed behaviour of all.equal ## strip attributes before comparing current and target result <- all.equal.numeric(as.vector(target), as.vector(current), tolerance=tolerance, ...) if (!identical(result, TRUE)) { if(.existsTestLogger()) { RUnitEnv$.testLogger$setFailure() } stop(paste(result, collapse="\n"), "\n", msg) } else { return(TRUE) } } checkIdentical <- function(target, current, msg="") { ##@bdescr ## checks if two objects are exactly identical, thin convenience wrapper around 'identical' ## ##@edescr ##@in target : [ANY] one object to be compared ##@in current : [ANY] second object to be compared ##@in msg : [character] an optional message to further identify and document the call ## ##@ret : [logical] TRUE, if objects 'target' and 'current' are identical ## ##@codestatus : testing if (missing(current)) { stop("argument 'current' is missing") } if(.existsTestLogger()) { RUnitEnv$.testLogger$incrementCheckNum() } result <- identical(target, current) if (!identical(TRUE, result)) { if(.existsTestLogger()) { RUnitEnv$.testLogger$setFailure() } stop(paste(paste(result, collapse="\n"), "\n", msg)) } else { return(TRUE) } } checkTrue <- function(expr, msg="") { ##@bdescr ## checks whether or not something is true ##@edescr ##@in expr : [expression] the logical expression to be checked to be TRUE ##@in msg : [character] optional message to further identify and document the call ## ##@ret : [logical] TRUE, if the expression in a evaluates to TRUE, else a stop signal is issued ## ##@codestatus : testing if (missing(expr)) { stop("'expr' is missing") } if(.existsTestLogger()) { RUnitEnv$.testLogger$incrementCheckNum() } ## allow named logical argument 'expr' result <- eval(expr) names(result) <- NULL if (!identical(result, TRUE)) { if(.existsTestLogger()) { RUnitEnv$.testLogger$setFailure() } stop("Test not TRUE\n", msg) } else { return(TRUE) } } checkException <- function(expr, msg="", silent=getOption("RUnit")$silent) { ##@bdescr ## checks if a function call creates an error. The passed function must be parameterless. ## If you want to check a function with arguments, call it like this: ## 'checkException(function() func(args...))' ## ## adding argument silent was suggested by Seth Falcon <sfalcon@fhcrc.org> ## who provided a patch. ##@edescr ##@in expr : [parameterless function] the function to be checked ##@in msg : [character] an optional message to further identify and document the call ##@in silent : [logical] passed on to try, iff TRUE error messages will be suppressed ## ##@ret : [logical] TRUE, if evaluation of the expression results in a 'try-error', else a stop signal is issued ## ##@codestatus : testing if (missing(expr)) { stop("'expr' is missing") } if(is.null(silent)) { silent <- FALSE warning("'silent' has to be of type 'logical'. Was NULL. Set to FALSE.") } if(.existsTestLogger()) { RUnitEnv$.testLogger$incrementCheckNum() } if (!inherits(try(eval(expr, envir=parent.frame()), silent=silent), "try-error")) { if(.existsTestLogger()) { RUnitEnv$.testLogger$setFailure() } stop("Error not generated as expected\n", msg) } else { return(TRUE) } } DEACTIVATED <- function(msg="") { ##@bdescr ## Convenience function, for maintaining test suites. ## If placed in an existing test case call ## the test will be executed normally until occurrence of the call ## after which execution will leave the test case (so all code will ## be checked and errors or failures reported as usual). ## An entry for a separate table in the log will be added ## for this test case. ## ##@edescr ##@in msg : [character] optional message to further identify and document the call ## ##@codestatus : testing if(.existsTestLogger()) { RUnitEnv$.testLogger$setDeactivated(paste(msg, "\n", sep="")) } stop(msg) } ��������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/R/00Init.r������������������������������������������������������������������������������������0000644�0001762�0000144�00000002524�13267374743�012765� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������###################################################################### ## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ RUnitEnv <- new.env() .onLoad <- function(libname, pkgname) { ##@bdescr ## Internal Function. ## Not to be called by users. ##@edescr ## runitVersion <- packageDescription("RUnit", lib.loc=libname, fields="Version") assign(".testLogger", NULL, envir=RUnitEnv) ## add options to R's global options list .buildRUnitOptions() } .onUnload <- function(libpath) { ## drop RUnit specific options from global options list options("RUnit"=NULL) } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/MD5�������������������������������������������������������������������������������������������0000644�0001762�0000144�00000005411�14565704512�011635� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������393a5ca445f6965873eca0259a17f833 *COPYING 6b94a58ea79e6ef655370420f985c731 *ChangeLog 1362550515306c15e4df3859c3fc1097 *DESCRIPTION d67ace25e05eec76fdb91401cd1b461a *NAMESPACE d5e61c4fb2e03e331699c99b589b56c3 *NEWS fdfb94feb8fae5d9b5085d808b592de1 *R/00Init.r e09d7eb635cd67aee843dbe167e69727 *R/checkFuncs.r 0af7c3a4b469fd74ae00feb78ebf85d5 *R/exportHTML.r 683a514df0cc07ffe238287faecd22f2 *R/html.r 7d37388f85a1c445e4c11fca01dee7da *R/htmlProtocol.r a18fca4276409763b6d2ed72d63282e8 *R/inspector.r dac8cdcb71213d0766b2e7421cb6d5e4 *R/junitProtocol.r d2259ad7818d1a12586f84dc0a34b390 *R/options.r 008c7654ad519fa9de7af8e431ab9098 *R/runit.r 1946f1e7a4090ffe510d99f82c91b58b *R/testLogger.r 3ad2fcf437829b6b6bb7d198df8fadd5 *R/textProtocol.r 35fb76293a8cddc0e2d0d0b247ee9744 *README.md 47e9c3a551694e24719bab5b2d4e04d6 *build/RUnit.pdf bbd691ac5c08e62dcf51efbbd98ac001 *build/vignette.rds 4f62202a93b4588883bd6cf6c53adc2d *inst/doc/Makefile 4b3f61d679e4bbea1dce9fcfa690aa62 *inst/doc/RUnit.R 2352b006ecb7a74230bf757319b3d4d4 *inst/doc/RUnit.Rnw e19d4b7f9cb7364c361ba45c21c1446f *inst/doc/RUnit.pdf 42b28300a717f74028c32c0a87b8adfe *inst/examples/correctTestCase.r 0d450a1fca30e1c3e193d3b2d117b423 *inst/examples/runitVirtualClassTest.r 3df11b61be3a51c4cd3f2aaa62b4c005 *inst/examples/runitc2f.r 4201d80ec130d66cbc544f78522c3608 *inst/share/R/checkCode.r fa963471970494c4d471175456202d27 *inst/share/R/compareRUnitTestData.r e97e7c5dda56971910efb622ab8c71b5 *inst/unitTests/Makefile 87db5d27b6c4cc1dfd0182e6d1872113 *inst/unitTests/runalltests.R 20fea06109317e04deac5f348245dfac *inst/unitTests/runitHTMLProtocol.r 8c83fe548809a81699e4c05483e8171b *inst/unitTests/runitInspect.r 3416f47fd504913d182aaba77ffdd14d *inst/unitTests/runitJUnitProtocol.r 376b320c3d1576b1dfe0d6dc6925c83c *inst/unitTests/runitPlotConnection.r 3f947455879084d9eafa9d7ea6663b73 *inst/unitTests/runitRUnit.r 713c2b0f3142caaf4a0ebfaf69b5c8c3 *inst/unitTests/runitS4.r f58c90583929b356cc16862b3fd87ce0 *inst/unitTests/runitSetUp.r 1ab9a24c678577d379c64bc455139134 *inst/unitTests/runitTearDown.r d2406b7de7c13f3d5b31bdd5c8fc226a *inst/unitTests/runitTestLogger.r 128d52ea94c7462f871e3242e39566f1 *inst/unitTests/runitTextProtocol.r 9aaab36a3a42fda03d01ad73df977fcc *man/RUnit-internal.Rd 31d29f6382dc390328ca14c36d5fa6e5 *man/RUnit-intro.Rd 04138fda366b5447fc26c40ac83e9d61 *man/RUnit-options.Rd 549b0f02d2ae37b3033251260640f6e7 *man/checkFuncs.Rd c0b777e713d145c47f051a6915916e55 *man/inspect.Rd bcc10620027dc39f2018453dcbacf935 *man/printHTML.Rd bd9d34629232ebd1ed9f98ad7acb4080 *man/runit.Rd a6390ca77a460a8b3791ff4c50b8f4e0 *man/testCaseSetUp.Rd e7107c7dcae672ec729fa662a8697449 *man/textProtocol.Rd 9d79fea474c52eb8ef81bf8ee8c6835e *man/tracker.Rd aad073d2afa8c98edc05a00dddc8dac7 *tests/README 2352b006ecb7a74230bf757319b3d4d4 *vignettes/RUnit.Rnw �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/�����������������������������������������������������������������������������������������0000755�0001762�0000144�00000000000�13267374743�012310� 5����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/examples/��������������������������������������������������������������������������������0000755�0001762�0000144�00000000000�13267374743�014126� 5����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/examples/runitVirtualClassTest.r���������������������������������������������������������0000644�0001762�0000144�00000010013�13267374743�020642� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ ## example code for test cases for S4 virtual class methods ## the following class definition code would be part of a package or script ## ## execute these test cases via e.g. ## testSuite <- defineTestSuite("VirtualClassTest", ## file.path(yourSrcPath, "RUnit/inst/examples"), ## "runitVirtual") ## testData <- runTestSuite(testSuite) ## printTextProtocol(testData) ## package 'methods' is usually loaded, but make sure it is checkTrue(require(methods)) ## define class className <- "MyVirtualBaseClass" setClass(className, representation("VIRTUAL", x = "numeric", y = "numeric", description = "character"), validity = NULL, sealed = FALSE, where = .GlobalEnv) if (!isGeneric("getX")) { setGeneric("getX", function(object, ...) standardGeneric("getX"), useAsDefault=TRUE, where=.GlobalEnv, valueClass="numeric") } setMethod("getX", signature=className, function(object) return(object@x), where=.GlobalEnv) if (!isGeneric("setX<-")) { setGeneric("setX<-", function(object, value) standardGeneric("setX<-"), useAsDefault=TRUE, where=.GlobalEnv) } setMethod("setX<-", signature=signature(object=className, value="numeric"), function(object, value) { if (length(value) < 1) { stop("value has to contain at least one element.") } if (any(is.na(value))) { stop("value may not contain NA(s).") } object@x <- value return(object) }, where=.GlobalEnv) testMyVirtualBaseClass.getX <- function() { ##@bdescr ## create a derived class with no own method definitions ## which inherits parent class methods that can then be checked ## ## getter test case ##@edescr testClassName <- "MyDerivedTestClass" setClass(testClassName, representation("MyVirtualBaseClass"), validity = NULL, sealed = FALSE, where = .GlobalEnv) on.exit(removeClass(testClassName, where=.GlobalEnv)) ## system constructor this <- new(testClassName) ## constructor call succeeded? checkTrue( is(this, testClassName)) ret <- getX(this) checkTrue( is(ret, "numeric")) ## class default checkEquals( ret, numeric(0)) } testMyVirtualBaseClass.setX <- function() { ##@bdescr ## setter test case ## also check correct handling of invalid arguments ##@edescr testClassName <- "MyDerivedTestClass" setClass(testClassName, representation("MyVirtualBaseClass"), validity = NULL, sealed = FALSE, where = .GlobalEnv) on.exit(removeClass(testClassName, where=.GlobalEnv)) ## system constructor this <- new(testClassName) ## constructor call succeeded? checkTrue( is(this, testClassName)) testSeq <- 1:23 setX(this) <- testSeq ret <- getX(this) checkTrue( is(ret, "numeric")) checkEquals( ret, testSeq) ## error handling checkException( setX(this) <- numeric(0)) checkException( setX(this) <- as.numeric(NA)) checkException( setX(this) <- c(1:4, NA)) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/examples/runitc2f.r����������������������������������������������������������������������0000644�0001762�0000144�00000003135�13267374743�016047� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## This is a very trivial demo of ## the RUnit test case execution system: ## --------------------------------- ## functions to be tested (usually defined in a different ## file from where the test cases are located): ## centigrade to Fahrenheit c2f <- function(c) return(9/5 * c + 32) ## Fahrenheit to centigrade f2c <- function(f) return(5/9 * f - 32) ## ups, a bug (brackets missing) ## test functions: ## --------------------- .setUp <- function() { ## called before each test case, see also .tearDown() print(".setUp") } test.c2f <- function() { checkEquals(c2f(0), 32) checkEquals(c2f(10), 50) ## check that an error is created for a bogus argument checkException(c2f("xx")) } test.f2c <- function() { checkEquals(f2c(32), 0) checkEquals(f2c(50), 10) ## check that an error is created for a bogus argument checkException(f2c("xx")) } test.errordemo <- function() { stop("this is just to show what an error looks like as opposed to a failure") } ## How to run the tests (do not uncomment in this file, ## but execute the commands at the R prompt): ## All you have to do is to adapt the directory locations. ## ------------------------------------------------ ## define the test suite: #testsuite.cf <- defineTestSuite("cfConversion", dirs="directoryOfThisFile") ## run test suite: #testResult <- runTestSuite(testsuite.cf) ## print text protocol to console: #printTextProtocol(testResult) ## print HTML version to a file: #printHTMLProtocol(testResult, fileName="someFileName.html") ## In this case we also have a shortcut #runTestFile("directoryOfThisFile/runitcfConversion.r") �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/examples/correctTestCase.r���������������������������������������������������������������0000644�0001762�0000144�00000001637�13267374743�017415� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ test.correctTestCase <- function() { checkTrue( TRUE) checkTrue( !identical(TRUE, FALSE)) } �������������������������������������������������������������������������������������������������RUnit/inst/doc/�������������������������������������������������������������������������������������0000755�0001762�0000144�00000000000�14565700136�013044� 5����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/doc/RUnit.Rnw����������������������������������������������������������������������������0000644�0001762�0000144�00000050422�14563457515�014610� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������% -*- mode: noweb; noweb-default-code-mode: R-mode; -*- % % $Id: RUnit.Rnw,v 1.22 2009/11/25 15:12:11 burgerm Exp $ % % %\VignetteIndexEntry{RUnit primer} %\VignetteKeywords{Unit Testing, Code Inspection, Programming} %\VignetteDepends{methods, splines} %\VignettePackage{RUnit} \documentclass[12pt, a4paper]{article} %\usepackage{amsmath,pstricks} \usepackage{hyperref} %\usepackage[authoryear,round]{natbib} %\parskip=.3cm \oddsidemargin=.1in \evensidemargin=.1in \headheight=-.3in \newcommand{\scscst}{\scriptscriptstyle} \newcommand{\scst}{\scriptstyle} \newcommand{\Rfunction}[1]{{\texttt{#1}}} \newcommand{\Robject}[1]{{\texttt{#1}}} \newcommand{\Rpackage}[1]{{\textit{#1}}} %\makeindex % \begin{document} \title{RUnit - A Unit Test Framework for R} \author{Thomas K\"onig, Klaus J\"unemann, and Matthias Burger\\Epigenomics AG} \maketitle \tableofcontents \section*{Abstract} \label{section:abstract} Software development for production systems presents a challenge to the development team as the quality of the coded package(s) has to be constantly monitored and verified. We present a generic approach to software testing for the R language modelled after successful examples such as JUnit, CppUnit, and PerlUnit. The aim of our approach is to facilitate development of reliable software packages and provide a set of tools to analyse and report the software quality status. The presented framework is completely implemented within R and does not rely on external tools or other language systems. The basic principle is that every function or method is accompanied with a test case that queries many calling situations including incorrect invocations. A test case can be executed instantly without reinstalling the whole package - a feature that is necessary for parallel development of functionality and test cases. On a second level one or more packages can be tested in a single test run, the result of which is reported in an well structured test protocol. To verify the coverage of the test framework a code inspector is provided that monitors the code coverage of executed test cases. The result of individual test invocations as well as package wide evaluations can be compiled into a summary report exported to HTML. This report details the executed tests, their failure or success, as well as the code coverage. Taking it one step further and combining the build system with a development and release procedure with defined code status description this approach opens the way for a principled software quality monitoring and risk assessment of the developed application. For our code development we have utilised the described system with great benefit w.r.t.\ code reliability and maintenance efforts in a medium sized development team. \section{Introduction} The importance of software testing can hardly be overrated. This is all the more true for interpreted languages where not even a compiler checks the basic consistency of a program. Nonetheless, testing is often perceived more as a burden than a help by the programmer. Therefore it is necessary to provide tools that make the task of testing as simple and systematic as possible. The key goal of such a testing framework should be to promote the creation and execution of test cases to become an integral part of the software development process. Experience shows that such a permanently repeated code - test - simplify cycle leads to faster and more successful software development than the usually futile attempt to add test cases once the software is largely finished. This line of thought has been pushed furthest by the Extreme Programming \cite{xp} and Test-First paradigms where test cases are viewed as the essential guidelines for the development process. These considerations lead to various requirements that a useful testing framework should satisfy: \begin {itemize} \item {Tests should be easy to execute.} \item {The results should be accessible through a well structured test protocol.} \item{It should be possible to execute only small portions of the test cases during the development process.} \item{It should be possible to estimate the amount of code that is covered by some test case.} \end {itemize} %\paragraph{Background} %\label{paragraph:Background} Testing frameworks that address these aspects have been written in a variety of languages such as Smalltalk, Java, C++ and Python. In particular, the approach described in \cite{beck} has turned out to be very successful, leading -- among others -- to the popular JUnit library for Java \cite{junit}, which has been ported to many other languages (see \cite{xp} for an extensive list of testing frameworks for all kinds of languages). Accordingly, the RUnit package (available at sourceforge \cite{runit-sf}) is our version of porting JUnit to R, supplemented by additional functionality to inspect the test coverage of some function under question. %\paragraph{Motivation} %\label{paragraph:Motivation} One may wonder why R would need yet another testing framework even though the standard method, namely executing {\it R CMD check} on ones complete package at the shell prompt, is widely accepted and applied. We think, however, that the RUnit approach is more in line with the above listed requirements and can be seen as a complement to the existing process in that: \begin{itemize} \item{test cases are called and executed from the R prompt} \item{the programmer decides which result or functionality to put under testing, e.g.\ formating issues of textual output do not need to matter} \item{test and reference data files need not be maintained separately but are combined into one file} \item{test cases need not be limited to testing/using functionality from one package checked at a time} \end{itemize} Moreover, testing frameworks based on JUnit ports seem to have become a quasi standard in many programming languages. Therefore, programmers new to R but familiar with other languages might appreciate a familiar testing environment. And finally, offering more than one alternative in the important field of code testing is certainly not a bad idea and could turn out useful. Before explaining the components of the RUnit package in detail, we would like to list some of the lessons learned in the attempt of writing useful test suites for our software (a more complete collection of tips relating to a Test-First development approach can be found in \cite{tfg}): \begin{itemize} \item {Develop test cases parallel to implementing your functionality. Keep testing all the time (code - test - simplify cycle). Do not wait until the software is complete and attempt to add test cases at the very end. This typically leads to poor quality and incomplete test cases.} \item{Distinguish between unit and integration tests: Unit tests should be as small as possible and check one unit of functionality that cannot be further decomposed. Integration tests, on the other hand, run through a whole analysis workflow and check the interplay of various software components.} \item{Good test coverage enables refactoring, by which a reorganisation of the implementation is meant. Without regular testing the attitude {\it `I better do not touch this code anymore`} once some piece of software appears to be working is frequently encountered. It is very pleasing and time-saving just to run a test suite after some improvement or simplification of the implementation to see that all test cases are still passing (or possibly reveal some newly introduced bug). This refactoring ability is a key benefit of unit testing leading not only to better software quality but also to better design.} \item{Do not test internal functions but just the public interface of a library. Since R does not provide very much language support for this distinction, the first step here is to clarify which functions are meant to be called by a user of a package and which are not (namespaces in R provide a useful directive for making this distinction, if the export list is selected carefully and maintained). If internal functions are directly tested, the ability of refactoring gets lost because this typically involves reorganisation of the internal part of a library.} \item {Once a bug has been found, add a corresponding test case.} \item{We greatly benefitted from an automated test system: A shell script, running nightly, checks out and installs all relevant packages. After that all test suites are run and the resulting test protocol is stored in a central location. This provides an excellent overview over the current status of the system and the collection of nightly test protocols documents the development progress.} \end{itemize} \section{The RUnit package} \label{section:RUnitPackage} This section contains a detailed explanation of the RUnit package and examples how to use it. As has already been mentioned the package contains two independent components: a framework for test case execution and a tool that allows to inspect the flow of execution inside a function in order to analyse which portions of code are covered by some test case. Both components are now discussed in turn. \subsection{Test case execution} \label{subsection:Testcaseexecution} The basic idea of this component is to execute a set of test functions defined through naming conventions, store whether or not the test succeeded in a central logger object and finally write a test protocol that allows to precisely identify the problems. {\bf Note, that RUnit - by default - sets the version for normal, and all other RNGs to 'Kinderman-Ramage', and 'Marsaglia-Multicarry', respectively. If you like to change these defaults please see {\tt ?defineTestSuite} for argument 'rngNormalKind' and 'rngKind'.} As an example consider a function that converts centigrade to Fahrenheit: \begin{Sinput} c2f <- function(c) return(9/5 * c + 32) \end{Sinput} A corresponding test function could look like this: \begin{Sinput} test.c2f <- function() { checkEquals(c2f(0), 32) checkEquals(c2f(10), 50) checkException(c2f("xx")) } \end{Sinput} The default naming convention for test functions in the RUnit package is {\tt test...} as is standard in JUnit. To perform the actual checks that the function to be tested works correctly a set of functions called {\tt check ...} is provided. The purpose of these {\tt check} functions is two-fold: they make sure that a possible failure is reported to the central test logger so that it will appear properly in the final test protocol and they are supposed to make explicit the actual checks in a test case as opposed to other code used to set up the test scenario. Note that {\tt checkException} fails if the passed expression does not generate an error. This kind of test is useful to make sure that a function correctly recognises error situations instead of silently creating inappropriate results. These check functions are direct equivalents to the various {\tt assert} functions of the JUnit framework. More information can be found in the online help. Before running the test function it is necessary to create a test suite which is a collection of test functions and files relating to one topic. One could, for instance, create one test suite for one R package. A test suite is just a list containing a name, an array of absolute directories containing the locations of the test files, a regular expression identifying the test files and a regular expression identifying the test functions. In our example assume that the test function is located in a file {\tt runitc2f.r} located in a directory {\tt /foo/bar/}. To create the corresponding test suite we can use a helper function: \begin{Sinput} testsuite.c2f <- defineTestSuite("c2f", dirs = file.path(.path.package(package="RUnit"), "examples"), testFileRegexp = "^runit.+\\.r", testFuncRegexp = "^test.+", rngKind = "Marsaglia-Multicarry", rngNormalKind = "Kinderman-Ramage") \end{Sinput} All that remains is to run the test suite and print the test protocol: \begin{Sinput} testResult <- runTestSuite(testsuite.c2f) printTextProtocol(testResult) \end{Sinput} The resulting test protocol should be self explanatory and can also be printed as HTML version. See the online help for further information. Note that for executing just one test file there is also a shortcut in order to make test case execution as easy as possible: \begin{Sinput} runTestFile(file.path(.path.package(package="RUnit"), "examples/runitc2f.r")) \end{Sinput} The creation and execution of test suites can be summarised by the following recipe: \begin{enumerate} \item{create as many test functions in as many test files as necessary } \item{create one or more test suites using the helper function {\tt defineTestSuite}} \item{run the test suites with {\tt runTestSuite}} \item{print the test protocol either with {\tt printTextProtocol} or with {\tt printHTMLProtocol} (or with a generic method like {\tt print} or {\tt summary})} \end{enumerate} We conclude this section with some further comments on various aspects of the test execution framework: \begin{itemize} \item{A test file can contain an arbitrary number of test functions. A test directory can contain an arbitrary number of test files, a test suite can contain an arbitrary number of test directories and the test runner can run an arbitrary number of test suites -- all resulting in one test protocol. The test function and file names of a test suite must, however, obey a naming convention expressible through regular expressions. As default test functions start with {\tt test} and files with {\tt runit}.} \item{RUnit makes a distinction between failure and error. A failure occurs if one of the check functions fail (e.g.~{\tt checkTrue(FALSE)} creates a failure). An error is reported if an ordinary R error (usually created by {\tt stop}) occurs.} \item{Since version 0.4.0 there is a function {\tt DEACTIVATED} which can be used to deactivate test cases temporarily. This might be useful in the case of a major refactoring. In particular, the deactivated test cases are reported in the test protocol so that they cannot fall into oblivion.} \item{The test runner tries hard to leave a clean R session behind. Therefore all objects created during test case execution will be deleted after a test file has been processed.} \item{In order to prevent mysterious errors the random number generator is reset to a standard setting before sourcing a test file. If a particular setting is needed to generate reproducible results it is fine to configure the random number generator at the beginning of a test file. This setting applies during the execution of all test functions of that test file but is reset before the next test file is sourced.} \item{In each source file one can define the parameterless functions {\tt .setUp()} and {\tt .tearDown()}. which are then executed directly before and after each test function. This can, for instance, be used to control global settings or create addition log information.} \end{itemize} \subsection{R Code Inspection} \label{subsection:RCodeInspection} The Code Inspector is an additional tool for checking detailed test case coverage and getting profiling information. It records how often a code line will be executed. We utilise this information for improving our test cases, because we can identify code lines not executed by the current test case code. The Code Inspector is able to handle S4 methods. During the development of the Code Inspector, we noticed, that the syntax of R is very flexible. Because our coding philosophy has an emphasis of maintenance and a clear style, we developed style guides for our R coding. Therefore, one goal for the Code Inspector was to handle our coding styles in a correct manner. This leads to the consequence that not all R expression can be handled correctly. In our implementation the Code Inspector has two main functional parts. The first part is responsible for parsing and modifying the code of the test function. The second part, called the Tracker, holds the result of the code tracking. The result of the tracking process allows further analysis of the executed code. \subsubsection{Usage} The usage of the Code Inspector and the Tracker object is very simple. The following code snippet is an example: <<eval=FALSE>>= library(RUnit) ## define sample functions to be tested foo <- function(x) { x <- x*x x <- 2*x return(x) } test.foo <- function() { checkTrue(is.numeric(foo(1:10))) checkEquals(length(foo(1:10)), 10) checkEqualsNumeric(foo(1), 2) } bar <- function(x, y=NULL) { if (is.null(y)) { y <- x } if (all(y > 100)) { ## subtract 100 y <- y - 100 } res <- x^y return(res) } track <- tracker(); ## initialize a tracking "object" track$init(); ## initialize the tracker a <- 1:10 d <- seq(0,1,0.1) resFoo <- inspect(foo(a), track=track); ## execute the test function and track resBar <- inspect(bar(d), track=track); ## execute the test function and track resTrack <- track$getTrackInfo(); ## get the result of Code Inspector (a list) printHTML(resTrack, baseDir=tempdir()) ; ## create HTML sites @ Note, that the tracking object is an global object and must have the name {\tt track}. The {\tt inspect} function awaits a function call as argument and executes and tracks the function. The results will be stored in the tracking object. The result of the function (not of the Tracker) will be returned as usual. The tracking results will received by tr\$getResult(). With {\tt printHTML} the result of the tracking process will be presented as HTML pages. \subsubsection{Technical Details} The general idea for the code tracking is to modify the source code of the function. Therefore, we use the {\tt parse} and {\tt deparse} functions and the capability of R to generate functions on runtime. To track the function we try to include a hook in every code line. That hook calls a function of the tracked object. The information of the tracking will be stored in the closure of the tracking object (actually a function). Because the R parser allows very nested expressions, we didn't try to modify every R expression. This is a task for the future. A simple example for the modifying process is as follow:\\ original: <<eval=FALSE>>= foo <- function(x) { y <- 0 for(i in 1:x) { y <- y + x } return(y) } @ modified: <<eval=FALSE>>= foo.mod <- function(x) { track$bp(1) ; y <- 0 track$bp(2); for(i in 1:x) { track$bp(4) ; y <- y +x } track$bp(6); return(y) } @ Problematic code lines are: <<eval=FALSE>>= if(any(a==1)) { print("do TRUE") } else print ("do FALSE"); @ This must be modified to <<eval=FALSE>>= if(any(a==1)) { track$bp(2); print("do TRUE") }else{ track$bp(3); print("do FALSE"); } @ The problem is the \textit{else} branch, that cannot be modified in the current version. \section{Future Development Ideas} Here we briefly list -- in an unordered manner -- some of the avenues for future development we or someone interested in this package could take: \begin{itemize} \item{extend the {\tt checkEquals} function to handle complex S4 class objects correctly in comparisons. To this end R core has modified check.equal to handle S4 objects.} \item{reimplement the internal structures storing the test suite as well as the test result data as S4 classes.} \item{record all warnings generated during the execution of a test function.} \item{add tools to create test cases automatically. This is a research project but -- given the importance of testing -- worth the effort. See \cite{junit} for various approaches in other languages.} \item{improve the export of test suite execution data e.g.~by adding XML data export support.} \item{add some evaluation methods to the code inspector e.g.~use software metrics to estimate standard measures of code quality, complexity, and performance.} \item{overcome the problem of nested calls to registered functions for code inspection.} \item{allow automatic registration of functions \& methods.} \end{itemize} \begin{thebibliography}{99} % \bibliographystyle{plainnat} \bibitem{xp} http://www.xprogramming.com \bibitem{beck} http://www.xprogramming.com/testfram.htm \bibitem{junit} http://www.junit.org/ \bibitem{tfg} http://www.xprogramming.com/xpmag/testFirstGuidelines.htm \bibitem{runit-sf} https://sourceforge.net/projects/runit/ \end{thebibliography} \end{document} ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/doc/RUnit.pdf����������������������������������������������������������������������������0000644�0001762�0000144�00000645337�14565700136�014622� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������%PDF-1.5 % 40 0 obj << /Length 1505 /Filter /FlateDecode >> stream xWɒ6+t"X);Jʑ+L Xp}zF3l_^|{{nLgI7Fk&+ؖ~L})ڤT[ ^]*z>2/L>O>E>l]l]fP6fi슂N}Gؤs Zrc?p?u]'Ly򫟦Sfa@46T<.e?cdk3mڸ(fk@>� `X`S7ԑ%qಉW]d5FhJPRPŦ0EG2ء4Uj-xV$ʐQ;)<|FI&,ϾB—(A$_6 J],I, #eA TPlfXG4IAq0O5'-d^~蠒:1Hv2~mR/|dk#KQ߅gn$飧F<֐anGZ5\roӿr [-l'GS4Chs|afuV&ATmF8 ֠{/aX3yxF q3&PcQ;2.vlתO}jP޷8|BX f3j=a%JM&#|4sO =CY8y6yXCwPALسI {(-. \EU�+lv'Pda$!Ḷ +uwf{xYN CL(v $$x$q0us WmB(.Qog^ŽI%R^e U@R4f K $9k$GmyQN 6tQ{"LiދĒiNY-o>y$^mmC܆i-WLHoso7K2Fάq>_ZT&jJ#7 {Gy �Hji}(1:$Q$+'.Ƒ+#_b^뮪TKAOLxtN)O�c wuJX ;;tb �>T@nU k٣#3fh>hx<~TcYfO|fO y j rHb F|vg~/]H`&�ĆІW 2k[ArhEU}yDc�BFQ cof ܦtƶ'u^y,F=h]BU?3\a0/mJsqn6OIJ l@V,/={02恥R0'L)vWSkg_u<yOWuU endstream endobj 58 0 obj << /Length 2177 /Filter /FlateDecode >> stream xڵXK۸WHZ\$E2ǤZomR[9>`HHB ZX)qLjsF lT\=vE]fqf9Ed;HbqzY6gÉWцNt‹W@ȍlq}XfVȌ/OAeqYnl}L9櫳q -~4IUL!򬊞fކ\8u2햊"0GE ֯6FxedvMF7Ձ*J(6=Bkʋ^ 'n]"L]g<FGLI_[ç 7S" hhM+LL9bRd\;y˓Aak`!+S2,R)V8t8"IH Ǿ/xpfn7l#|üÉ@x zvL/p<Op_r$k,+βߜmk!ywR Tz>#G'I݃{4fjL) pdp(G0'#N_ջvW 5FL0IS bH\Y4 /B �y:b[<!qZ/Xu [NRS 뢊  MbX܃)Zw>KCO=S"$6TX�p<{i<L�k1q!k̳y8Ba3R yP[sRS4<ln 7/E>nFk71S;UZRVq, %`O>zD$jk1Xsɓu D"sA TDFq?X<*$|R3Le.F Zp*J$nWo đؚ<|f~ 0@<3a-]˅1oT}6p EjXȪH} L} k 2[.)r730`/8U0EFq ϓoPsl{9IllVx۞CFS{5J;I5�[2 T85ͱCB!BAZS#2SKI��X=6ҙsJa:σi_` g +omnqeZRNq8u8gUj@ 3*ƇeTKC$Iql15T,a;H˔ F/oVuSS+T)eV}`NSnw:}#.י^&zi4,1ʧL?uQ2? Jz !0Ω 9QB y `97wpԭZH|߲h ΐ T %xY0#Ug4̹WtoTXv5l UQtB&KIt:lDe$ \)I3[^'c tH'?|d`PӾʢ B>s<V ͛ƝRpE [ C60~u )VdaAhp� P-x~la痨T4e* <vG%07cqn S`Lg KFl0KI �~�^ \8 V,.+@*j`X$܊6; 7Y//xK ?Qz>"a/mN a GOE o`z 3 f]F~=>QdUD7OEuܹ79$B(I*Ov֍8wQS9YuIp?h&ɣ/%*.cAO%ҧA:vô<"Bbb#~# M2Qa* Ēl-4:qlnNeQ+xLP<-o=HY|@ZWƑ', AZWBsĠo!$޴?2ʿ;v2-g? __EwD^!㧟xU�ϺO=b }TH V nc?~¥~HuH�x߽]{UG*?Tf$U\%q݇w$ endstream endobj 66 0 obj << /Length 2277 /Filter /FlateDecode >> stream xڵXK8*i?dn3b@{VZvW׿_~$8. f0{2EEI9IWQ=dY?6^Idu_oa7I kO Φwy0t¡Ia\֛/c]2& Z;ȰO}ѿBŚ~β]mٳ 'O1*\mt;كu`K|4E e]D]8'3,tC(\6ic0ߡSk׺AFQ7np&Ǿhf5=Ɖ:(Nxm億jMvI4zʲ+Z~]N":!M=hœ&<Ꮗk~,m ei`Q�]=d0R$'ﰈv0C|d/јɂN/4ϙ'~7S{dc[ʘVPCub)a0NYe''F_5v-mEѸdop+Lеp$(f_'9n+(q=Ƞh5]0gY,<񄤎| E[}%Ɛ;BF(8ǰ(4ȶR?SUU8ms:P~)'aPҺ}71ۆQC"%T5hg7%6Jy&_;w6r@{9 /2\"rRpIT^l|cgY q8P5ys D iپ+bMom[V9/v8[HmIĥU~rgTX= HB:1mpwסAy2z[CE&E'X}iMs1aC Vv QDaRkN9LZ`o3aDt#EhsqЌ5CL!93~p qeJ q'Yǰ7nd? :V`K*/Ue͗jȝllO[M7V:΍_1=^tՉ�Maha5ht(hτEOȃGӛ42[5oj"N5̒%!}3/.NRR^Aœj[Eg>@ ]<e mPb{6 jJ8:b-%Èޖ!2 |*L .M_+)~A{ 9)ɸv2:Ő8HWq*EPZ Y$yrdz 5rl`?nɊܱcAqh oEck bo4ïAf0bٷz)-X(mַ<l2gd}LNA Q4  gd1ud;I sLR 84kUJJ9Ifr&RSWT ,c>LuBĞ _G*SvZVEި33DZ~^aCŕq=&/ie�b�F P|RE`LVP9f|g>>dTÞ҈|U=Z_Z rXL:?(VN"a.p襷W8|)<H% /\y;oR�qʤbN ӈ0fVz`/^ojT9p]05Ac^20&qm =яX߇}L,QSG9A4-RTv>1Bԅ-$O l^PQEDX{7I*kYBIEBƚѨۜY`i>4ַ8&F\IwS0+@gU&6Hy.FY9-cpQbn]wUFP�cX \K1x ׬t*ü_|hz*|#8Q #9 Aѩ! 5!(lP,K.a~M ?4D +�96b_q %Qn<AQvvzv71T5|MhkP6;yx \$N+ē\H#$;qűTd^7& C*l2K%S n#6;tnnU-JP>6`?H endstream endobj 74 0 obj << /Length 2183 /Filter /FlateDecode >> stream xڭXKϯ L{"`r 02h[Yߧlh,MVb=*꧗w?\$OI"}z9<ջmK,ߖq>+&/,aO16n2oKYy<F͸!W9nȂadq{iWkxz khߍhA?YVT|Nmjwl̇eEuFQFgkn$~i=&{{3a$eOska42֝ѲiGZRt"v؃-7h=qnNkϣ4q~[udlwetfU,*t6ٸ`<[ ytRsHX39&pONpl6N_ĨL[ey&.١gd[V'GXY5ytwRuhA>?J-4,!]%?SyO$yV0*) 9Zq7Օfyl5E`YV{%<j (ރ8X>E<Nk!F݋bI<kpIAenl ~>ҁ g^Nva^|"6lduexd@,߃&RW<tJ�M(ZTTY4W$ln[{EIS@g(a)#q>FP`BF18l*sJ՟)fC#ӧg;C!0ŕh5RI<}p؉ܭl&8} %>evxtdOj"DG=Oq<Zy67(9+Jj0SrvQ<iy`^-jθ,B4q^0:Y:�E*[ )jMw,[4TmH$Md<6#Fby%B4qʿi�'р!h0gh`"A"i\Ey86 iO<=m7Y2 },~6(UL̅ ^�I~S\`NOkV '-IJ4kM3R/xɨ1(l4;߶*:x<`0Q#KA1 Яm[+ v#;d&?3Kw`]:u : ,,#q]_I#?Wqmȹ'j DŽ!0!wT2G@ǂOGRd Vv2 YӶ64Qr )4P%w~e-vytAFڊ&af@FfُgqcP<'Gj)?@֝,w+pQ-ſ_ yY N.�BP )%L)6N~ryrGr ?G9[һ!< 7ڼHOlh PDϢD,+oDQQOt_GLnYTݜᴤVtRC0M% ZjŭbtK!.2_;=?x63%ƭ&a,Uq WB3bJH(XwEOXOȫI3ޅ"[mCq}@z륊F蜦zN^nӤ%z譖:TOi?1,~YLKc+@Iw[z`Gx:-K`o-e-+Hˊ(5Z\,]8 >nГo2LOy'4EtCINC'|5 |p B[Py [>]=Fӝbۛ|\m'FKSҌgmR Tr?rp#{ፎe @0قefYKlJtamү|B_ VW؍T^ \wQ+gTDh�#w^Q 7(K.ص%4&IX֡BfR^D?vѷV>,+ZtwyZ]H, {mBKR-�"Ů q,!9?>Lo_5>ϡ;:l]Vy)KC)Ro\^ }"'K>a"/ -DrsX۲!:[IoŶ*mΜw}y_ߐ endstream endobj 79 0 obj << /Length 2201 /Filter /FlateDecode >> stream xڍn6>_ab#`2$YqNhX-v(il#z[�HEU7ivaLқנ&)ѩnM8~iw݌<$-J^uϿ83jAaQBsrv�Ac;Y<(aY20tc Ǜfga,iEhiR=u8Hw!K槏0^mVTi4 ~ Kiíީy Xi7:F;N8Z5 AE< ,l|!R~2}a~jޣs1b|TnPΨG4ʹ7+8gN',3-QgAfy-Kv;LK;~zPt(9uZAk_4:AZ򟰁#xϛ8Wd !P?n?@̑g\qpĠ�4R=.jNnnk7` W]<nl?;nHJ>?ڑ*aǴ;ʶdX9_(̋a<8 ."-ʣ>18=N=s7<;҄VT LqXǾT5^⟲ BMq兎s}!x+*ƃVu-|/UlMI'aK/hyIuivHv{&h:~<Ӌ(+>T oqztgi0S%zu$!|_qZQ"ƥ)C#;pI58crs9Ys"QjܕAVf{ e3B,4xTI`yY;8g8uq 0zFfi$yDe;D&�O 3 W#&b +=b*Zǀ4HUKI<:'qD(|)=�2r,A2[nQ&v6 3$- HWiTRu懇ݽl#u6v7p:EopTvI\U<09I!?<uS;wZ0~OQIH+s�~dYk0 ubgIk_'oxh.% pQwj&qkq]^iitV5ϖ_Oix\xJtN5,J +u64.J&]W;i4pD}*q"1 t?78ဥ3Cl~=.: (ZkQa&�] vR?EXK!sE2Z&`M1>bG^^;Eǁuxؼ`٠:'k MZ/5ZԴ=yYB!v]g`&yǦ,Zòcp0R4lCRF!(! !mqSՉ[t\x9Q:rR4x[L4Lp{#ϥ9yN"M/xw 8r%G#34<hs_0 R~NM"Q {)Oܜکv5wpaSȽ!ޑ0ֻɲ|ϗo*˔Hg#W#^7aʽ fs>v N ,rJ$Nxi%]FTHG:y Y;HlY<'ӄI;\d=vr = @浑2h2$UW h|Ռ[k ~ WQ4cCLCHEB7%piDb�ik0]YgHYF^<2j$oHI\ _K{qJ LJiO1/8Jbvo,PFTܧ]CQ_sYs q$Q]/NzgǦ?Jths܃gbjpE0#.V;C1# Cb"LsלfgWz[e#:w;k_aK�J<Ldբ6G񥩛?)ųKn+npNӹ.%+1~jȂٓ"}I嗼8 _"@+/1�QxR �>3&l}}x Ȣ(Lp+<\x fσ[Nd9":[5 endstream endobj 85 0 obj << /Length 1799 /Filter /FlateDecode >> stream xڍXK6W{V@EEʴͮ,΃-GFrz4Cf_֯ޥBJQiXoE<NDƋfw뮿3;qJeß$'O؛7ֿ-J&"I35CS]Srz~mvfKPQNjV7媆O=pTAڬACJT.Na"R*EYHV~[UeAW=Ry`Acq}CkGY wL7Fhr%f[N\ '~Й"r;yk}FK!cd h38e}`fӯsXk[s°5^.J#"2͎?HMʬnG9%Lcշa$*8Nݵ3:jKte�Ijm;6bJ pfMCtu}d,,up<d�dpPIR2\6c~ 9d%8O[GwQvQo{~ۍ@ӒJJuz.ѭ4W9ҟvyKs6-xHqT?�2@ G(hǢo:kݽŠ!HB�:⮫f"J)P9ITV+4t�sge-px;R#sovᠬ A4+,ڶppE^޺nqW2ՕpP8 J2YeoE[ >byܽH^W~󽛮otNZČe9}kt$ %"i<@ =t q:D(ȜٓBɴ8 grkZQW_M3IƅȊԛtSy{=zchMO�6(jh̽sMlZBBmwN pX"*Tb}.ft\~8C ِ猂 /{5 ./sʒXi*"Hl -7mz،7"𜓼]~g=o^ᠹw?Bó|\V*$ *v.֪G09,䴑=D2MQ C[8l_W9B:>b48 ΓFB|΢rbL<%YF4g'ƥu(@Ci*wf6! lY�m_h@ҍ�PWMAz1 fv2 s�@DEG <4q͘:p0swJ^1@!uLcqW =os/.QX`,~Sy!tu"f(7(ܫ}vYjne_6XAIݜA0�(zMk]l."*#!>nH" UlHEv6'<sR gcC=μJ#dP7Ƨ2].U *SrB<cP].t_ v6 RR0cQ5{M?O: >3Y;1;X0Wʧ=tutkoC)=^tg*DRfSw@ƅSҋ]$F"*<x}YԶL+a u1`�b/d7ؽf|to5sөFiT$g@s0CD%TF3z~? 1 endstream endobj 93 0 obj << /Length 2079 /Filter /FlateDecode >> stream xڭXKFWHUY ߏcb;UGGIq%1H-ѿ_�fiצj3Bh4Ow?;w8v.O4wiK{?0Zz/u[!c>Huṑ/WzoԖ#ǫȍӏOxqKw/d.,TbLŬv <ȍ,ƎLA"~$h0* y /q8 Vk}< yNSn,ޣi"?ׂotz{s"n$KfS[яu95EIxg~q/ēȳOKMzc!C=Aa�*[߱ *Cg %swȖm7>iENtM\Uzp! _䞾 rk/E_)1{%b%m|`ɥn+2c,ٶ6%sRCeq� ʋjb�av5̫)RnX{]d3-N#, AxaԨG\SF]A |mR0_+A"j@L,Ӏ-]0|h XLWT~gφ|;]R،y @2)-.G,S=,/u; 4 iF!?Ky:=\d-khN1‰ЄcVf~ zzhe:>2gY[fQi}y.f0i8='At3muۊ'ٙIYY<%ߨ3HS4]7M!�hM[J2`BD8A*7KtgR =>Ok[뜄R][:>VD+5~j!A8TG.l]4 TzTFq$7Z߄zilrIy޾xG+co-AFy:3M^OR7y(2г9kR�ϓ 'DUTo;]YJ2! X> y":ogR= }IPh 87=ڗl1/i!L:d{87s|'۩J2%j$l!JCΦ frT2D=ۅ0I7|j ̪~47&\?} eu NRrX+΢ZN'{5%ٿ[i,RSa[ڦ4I] H&Mڸ:Y,w<!v`"`"m>+ђnPzuDo4~x\vwĐTƺsDtcٹr%eu2׹%AO`e1m/ڮuenקn V25 ENYrW<^ED ޜڸޜo([ҶjG)<)Wn6:?f/pɴ態Qt[SM- w$k?G*EJl^R@.*twnnkh"BrzFZ"aE M[wo-I&;[m |r'[/T>hJx&FEsIa&o䈷y8"Ph iaEor~ t8s!k3 Ah~oucr kabF6>cm^i(٘HIo?@ %o2>ܐhc:<$E+Czy|sI1Vqmٲ݌x2ìfeEߞJRII*l}d$cu[8jCt7ՊٺRBuo+Lj6rLTfJֳߣd{ffF䗮gӅ'f,ҮY_Ç,WoEj._R -SVa3q+P&y]R"o.Ut#S*#\S/;d߇}:[, endstream endobj 98 0 obj << /Length 901 /Filter /FlateDecode >> stream xVMs6WhFbn<u!CfA M(Hwhx|Ɂbo=ON^FoJi0HA<AF^쇃$|v/;"$έL;e(�156ɒDN ͪv.; b'۫R͕,-QNSs78+#9pioFkkv߭ 湣($&$IEؠEQcg ,ڂUFAG}Z=ۤ!.YΟpp@VYVۿ|-rU?=SA:y]](.Ji$JVL*ȅ0�|C�C>�>f36׮Î}Oȃכ듩d4_*jU ,.Avϓ?|BiʅZj_󱻡IGkzrYZmnn_B=MyH(pm p@vp/˜Zp&36eA3uR5N0'큔\1١˶_w%XG"Vڊ+2ӂk fj(րWbM!ځ_sS&&5't?'=پd_?$C#O4['9mȿU0V U=mhf6.�R=4362k^R e~ɑCzTguUIZ0K KFv!2ʰ!*U\c]WT$؇/:Snn.C%`ǞXTYBѱ=ZS;NM ` ģqh؈ƆK=;8ʡ r_bFi_/w80XvIЋ'�Q ~:X endstream endobj 102 0 obj << /Length 1272 /Filter /FlateDecode >> stream xڝWK6!@eVė(m ڠ(@MZ+KD';!eк"@( o>z߯o}rXZ*B)_h!Sz3iYuf)X*yUHfEj fr%,Y?̻(Tp0s*uʴc[;۵˕*\'H*(P&u4A=wǽiс v+&yE96O>:{>A r:N({3Ar Mf[2uSGc;pd7Yod%r t[43#zY$0 1;/ Zfr?ǪAqIYR%T@ GO> IqݼwbupbV(#m/*SA*O w . վ xvfH)kɧYCa֙f.O`qw\K@/l3̵�q<N+y3y؍kayl–R`[ؚ%vڎ}8dYR + kZf@6|yc:KUrJ`,pӥ3.\ܸ1/z-$с2dS6@+B5dϻqmc]%x|påW[hgh7gZ2W7^-Ip1B>NJp 3zDfXm[7Ghy c 4D'5m1o#Vn 1g8v>J<1 ӛ{bWl_P<l W?ni;fz:M;Jaa3@m8M7ٶ{PL(ݝ0j} @v@3I*Zز@Z{$oSH >xkl}quf <!`iqIxFIńy3<;$Q`*a(B8'"@sY$?rC橊2%""vŎ!ζUsN]>!`d$t~U`=_3y%//蘒l7K8B0#8x^K|)g2gZޒۯהk }|}){(tLHyեnSxx|s�B@_~b Mtŭ_1USȫ0QIL]X|L^c/.J 8¿D@D7?oH2= endstream endobj 107 0 obj << /Length 1240 /Filter /FlateDecode >> stream xڭVQ6 ~=(ڲ+:`02`@$9v&; #E_}ZIEQG~~<{q"\)iE'AƋMx'^d2ams~ ϋp I֣7sQ(/VÌ6GbZ-ܗ2u՘v5ߒ٩˫n9vnU~{_BXl8<lI}9 J3$fhzfL\d^4͡b$K@Ab0NLI-fs[OKY]|=Q}rgfe5E}ʗ( Z`_ہӍ:Kϧ#l7HO%i.GN96|E\%hAkkuS`yvtMX3jcxa4hyDVt LEcm8hy˜cL@e :?eBh p/{*K_2d'֤ć% ĩq) p:f:!im JKc#hy;x=뱫Ʈe3; }JC=' Cp($ 02TtNRq,U" }K7F:r^(}�"xViLQ`r@偒cmS\z6 o [)kC=jsG,v!OuCC I:mJE (\}xe0{mյM FI$6K%! 0@jhvЬ2A&0\y #83SiB*Wf5ahQю x\tҡ#εНI‰\WQפ0H>tL)a-JJ*4 L:]6?uoh&Z=aCf\qaL30;̟1*_:]r:*۶־rюk|p3A=F䐌/3R8~2Qg�4x=HH:>k8WCKz aEMAY/. bLmG1A1D\wo}ǶU_D!l-\gS}s@Ǘb~Jnv<\A7Ϫw~#$-$b#A/o|xtixP=|W 5@<<\S endstream endobj 111 0 obj << /Length 569 /Filter /FlateDecode >> stream xڭSM WTTc{T-)M;"eJ=y0o=:첽 %>iJR4 NH]rDk/=*IPpZ4eR!RW,8*^搞;-8elȸ9r'R¡yK-&6  jխ{0TŔry[bjuzNurݤ]K7O\娝o #d`gBÍ&hQS~1[EouK)ʼOo=`$|9Y~j ?ΰ0ՅhcnQAun"w*?^sߓm Pa%[ 0pps? ?{[GVQ7! Mo}S=T RCB>N>Νr+49Mz7\\V}m{H_{X՛{֎,fT6<a#ƺ/Ԩx%xz>O;jZˠ!N@//XƴfD5H%1,R|>g endstream endobj 126 0 obj << /Length1 727 /Length2 18121 /Length3 0 /Length 18675 /Filter /FlateDecode >> stream xlcpn϶=vضm;{Ƕm۶m';9ݪ֗s9zXkU7)=#7@E\XHCF&djbio'jb 05:�@`�"N.�Jc'6&N�u{{7Kc �++??TLM.�3KSRB^ ajgdhPt54Z9R�6�L,əL\!fdo SWW0� L�Lsqmjl\71:y'abX�L-`=?iW!?�?RLLAښ(Em\]L�r&Nv�a{ц6AH? 2'e,najhf6H&_3VcQ_,fglobigPqGQC'Mhh=`c9C'K/F_#Mcab0s|]cW''S;Z;LM=LaWyR[B} *if hVW{n~![Y]ܫ]J7ƚLNETl+Շ]c˃sIXTb9v,C`2(Gډ[[|ҥ@3=F)6x_ ڃ!ַ((p8 ;x48d`hϚ. ߕ[ʅU#g#J_sVFC}wYF.s#~B#z<alfJElY!1n+2_1ǟ]=F|eSjUHQ9<ɤMFsOd=h= -bqƔ\E6ƴO7*(H}[Y>h4M)rYkSb CT>IA :Œ|"GcMY}h5$EPUHA# ^2ؼ.8\4F꜊(W9^ ]*H.ۘz=f7c Qt^]0Ota6CTߜ嘩rٌ}9<XK̷r,Ǥnu̶LN+y0ZFKUxoI,.L'- M7OoҝdIC)pӱFGr)jFvBDi$ݨJ@vtSu(gㄊll?*! 0;Ae$O!Pc UtPcךo@.uA``5svxVFiBC^M dE ̈օ_ƫ_ _�'WZW.j^cB\ѺŠ7@5d%DuZ}+ՙߺD EyeTL YhB[OP2ˢ๻ELxN_2i22Z a?ӧM͕DS ѭ])<@3P29*`ѽL0RZO`bS%5ZrԒIrۖUe f\Ҳ *u%Y@.W% F\oL.rsOJIOQE/k&k'v –C`EV&|Ru6#`b6Y{kp[s)nTaoт~_?udPMywkqkңLld /Hi꧆W{l o;Lۣ#'FMKa#Iɡ(g7nFܪ\$*$[*.xhS󾃞o2yQN-4"* /ʵo -Bu2D5.g%cK R(Oo ˦ <.t(W_ [¶Uع0~WɆAltaURP /v6+,/ax9s !dG<#S[{KDH:M}o'P]%h30"$L҇zZ-PytZջFegLf"D`֌Y(߃=Je%�ҐXz`KH^sۄw?B^AxU%ɳO`Y@\\YEϽX֧ 7IԦ(;!!e~.5*U6LOu*%yET.A}+ә[Q N$/d;~)9ⰯU0-R¼%v!{=V)9G Fnt B*]~LO7ϙ84BtcKo`zxrE֛Ӥ=ul7tkS8"7>L#WAo!p3O`@w&Q:#zuFt%xݨIsEeҧ9!Դ|H F/t7ℊ;o_NA?hdDhvBl#!Z j̀sJvzN 9*䌗2G6%4E5`a+aSe#TP@<9h<uyHA;)bqY$%nb  >`THPs/0=3C" x ueT+Jdf%f,?zmiz 5v(nܶ5#"wz~-�;MeZ 6G!k穉HkGY1MxsCayw2I�1aZÁ<`^|ǵ2ոk1A0ܨ&P9S g<3NlBΌ εلiԂ'dIl1񠅗:JK-犨9tVp%zLMוpB61ؓ P/|T?f5�nۈr@@< 0`W*}.lNFsB$\Z?i"ᔤfh7e&\UY @j KK&ØtcUEM R︵P;zYk4#\?gOb4"T*f +ՙy@\^Na`}6|ziZ˾vH~o{uX\˝D=ij:N&snC0O E}sX0:ܵMpHOӇBrS3y05COV"13L:$L'W7hy%gxQ|Ì󾏐id*PНؼpB8.!U#Ll6%i~]"?~`- I1$"}b*cR$1k"2=-9 1u!hA4@J>+cgR6YƬ ·YೖU 1P=SqHܫ^4*V% ;oZjpJx ءj+<ĥLE\,9 1 )ST _v"&(Utb cA"ĕY=83 Tn%~d%9<  Gm)J'ph!ïSxt)\ ~*U?9%XBvSD'm Sk8>.΃ =~'E ]S[@qxL2Id뾱]JFZU0: WmMf~jwDj;fbNjlUFp%'-#NDσÝsF[^XIwj3nLq\S|r075o|Ud#tHs<AI'NC~XCnz.j;_l%xG/L7>h]y}χ44wZ0r@>;nu;*F|E$Z~ AP_@-=Y5tk;o5@wպgV69 rjUJqNh`vya׭h"Χ 哇kNv`pHYS6tlTA[W3k5F́9>(s{?'*rko1՝ iJIXsFB}gl~<|=~YsAo(jzrSD[V/FSEie2ՅSb޹>e=+k>q.'-aK:erzR褛,0[ =by TbBK;# Db[TI\]6۽: LX.zS'.t vi�QdJ<Dt ;MpkFLwQaSjn@{vyƾ>>�Ly@؁A%XJ7*GdZb]GM?Blf&gAT쀇bۘ4ّ:WWQ443?[{YB+x(ިH:ZSߓǣmI]QhޝڻhۣcO,! r8_)623EBsQ3-<I pq&[֦ g5 OZ˩(Z+p<7KJ4<l,ae"+Y7FF8[u(Tڼ ImtN5*ju5u.ecbv/U�oc)b#j?E`nJeB2[`GLߖU :lG(jU ii춉&2<2p ^Pa'd P k{TM&s1?t#"bēǒ_lnw2K!cf\v@[:B"s#k$.ԇŽlQ5[%nVbPoPv](Ay6̰)eS-LhWS R uZd 7eb-oLY)IǦJsUfSEtF5<wX1xSݰ/S;"m9x0볢GQ0tVI+ގ5k`yeUr~sWf?N?MgA{I jM"bv!颁\=}ǣ(1xg3Gd;; KUûhv.?e?\+y�r" RsJ 4f@p ?^3#4]kYY;1R>M/9E*G ^WT0b"k:ŶGwo�F(0m$clUGESxּSqj߼Bk7"no8C㱦8ܨa| `kR;ҕh60PYW >Q@/CUn.ctZ?3ΰ뫦 YʭNWOv!C]c3o^-Iwi}h𴬢06,1z턡ogm9͙n,*!8s풝^QY8:{` lA|n.1<wPjW:ILU4l!\fʪEmbUZP; [~_v 1w"⨄(@ cA@gӠ,<7(vҸ'0ŷSH/7I=ȶ5xZ9^u\MZt67Y_@UJ|T5b}VsR_+p{kYBn }oE<I%*:I> m: FbY>_i\Щ@SHpdZ_yf."KC%U&d.Z^o{ZĂ&x~|ۭSX;DNMP7ZCC<Ό0~Q],Ϭ.U <;W{CUb@:KrGkXDN{{g1ۂP?x%5ż&hCc,.KFZ'"u ӻm90r%XXhKXGv4O8%|2%X\mFZ}5%RmlWW`"Ս:3d3 CFΕ9k8}sy'tNm-> @ 8TBZTEj4"ګO20R;R qsLSf|բ3K&sBpBʼR0َ8Y #qnx(IIhw)Mrb�i]`@'Ur~=$X>Jt0+;DaCABI;bhX|)v >26sQ#̿YEzAl]D&HoXAPsA;(Awܶg=l_rib$#%iut eW0(3l2. X.=܋{YaJ&pڟ=13s؈k>.+7Wh3Dv_ (2[sp熤U+G3Zu֩R|r_1H)^QϕvRB3\mE1}ksX *| Q&KE#ܞ'qi}Rh+L ϡ^@lX@QQ,<m-rqÙ= ވ1+>NH.d|s/ϰR ARdE�g❄U.Ψ Tz>xލtK5 Ʉ>ާ _{:݉�ON{9/z5ԯN;xI n~eɅ\& KsSR IGbFST-vj#'z,vH* $ܸӎǴ b[8M$ƴzqTo~q?oa}eV8-bQ)>, ;EN<t7+Ri5ݤLP$-.g5cY*B6M,a&i];qByN;'͗ibPNL sd#ض^lA߳&^`'s֦p{PVĆ.BKk]węL{NZ58|O*k9~>6eæObO}V {FyyxeX{,"rRCdmq1X[v7HU-}.WMuM'$v�t8m(sof[pd'B k`?ZK7^ ܯC18MZ'mdf?%)Lf2rMȊ:ʃaZȴF""%vʙ֑<{I[y0IP\ƓSk.nf,+MW=)[l#\2\U.@{*Q4]%#,Iln'G%tU{'tud-X2"q`1t! UcF{L׾_C>RJݫd2 f-yڿV#'Y 8*f&y7J ׉pKCAfHȹI˻PƕW H } &8(|[Su~>O '42&_.$ߎӊ)!"Da1l m1N[SF ~-@d|\;0bp;#PYY WeL[ :( u86YZϲeWqS<%u%p: vE7T_kdKp>> Ҭ*O-Y7l`>-R՟4<y1 \v|z- . |T Ql5c݋tV(5]1?_OV[f{oDR/vțww;S}sόG?cO)Osm-lCg+W9Ѵn.G3oQS~Oho HDZ QQ9VAR9L%mc F[''*ֶ뻬b5cT;Wkswf>:6e_L%̶\$zkA2KЦ}n7QIf0.VLŰ;g.bbĶ]v/fAU(TJ"w`$lsܬoYy&瀽ѩk%w^"7{8(],tFzmw@S!/J} r97Ds,g)Pc඗(ظǟK/?DvU$Gϭ:>nO1:e:J (#Rz)xDE 87\GrVP:=<Nq-V h efi}n8ŋ@ 3RwR}9d)"_G1]Թ8瓄uJzFQ9s8;Ax;pM^MƘb,MfXXҩݠ|QfoG=)+j@a茫'pN2)J;N ׍&pIH̝ zC#@ aDEtQBiA8ڹ?6MvE3o9!M!g0|v^m@oA? Fy8wyr`yՅۃ^AD[yO+;6l-ꞹ'vi蜬H3/wR,ߛ{A1jrmʠ-,U9c "^yT]PbrI_ niJ ڔpحL= R#QOecjȐ+QLa1S]lPJ�Ɨ=vULЍ, &*@ Cڶg襤78LwՊ eWrh{Ioj>t$z}=mNlnw-W'[_^G&M6F/y&Q c5l^G.a*kA:pPRuz\{W5*˿ܧRK6[.z7'5*"~ ۲<zczVk˄!iPL XSny`CaxTT u˓E.YyT^G1S۔sIFo$Mkpz8޴ڮsw~d0-믄7X?{- WfW{i}x̙f1[J6 -]z"0'@1-, G<7:ڡ/V>>"*k3Z??Ɖ73Ix휯 Glk$B$@U_٢RkCy3vO)~ TS3DIܔtmjdYQjͷ 䑾xĸ/YSŴ{*M܁N=n8EwG};eQy鄤фC%/:V> Z2 7&۳aj(Š}ng&m۪v/AFaskh+sr]}?tʒko]&i4eDĞ7=K7^P)ivCe0ļזCbڢ ɟ&<ϒA##&0Mr=d\[qn]9Wq[/<ɣ.O`1>ʴY͗LޤN׭O`LPpGcY2QMڍ~. `.|Bn'w'k[%0Q#b=_`h8%Z:M1<ke|r8IZ]\[ ҅[4.Y!ekT!SRtyj;ɬ vQhXkr?su_XIN<w<^]z #r+_xƠ^yd YӇ8/?�R`ǡC1/۩d{;z{ˋ .eu4+jPc<H5n琤ta#[kt8]'7eNzmnIVUX9z$Ӭ#:5V3&+ށ JT 1? L۰nm>B (dmH-쵆s�;77ڐ0kF|bNʔLjf" ۖP̘eK uRy`5i4Xou-b]W7r+s&xӎG,4Hbum7uQ`D@wPہ}�{BfUǀTŁ!3Q5bݯ]۩nnn:B }ė~ % ɴ*{M6YTaP1a\UxC_Q\Ȼ$c F2"Z}Uga.*DW<,:&^t+lNɯY JddT uV w rf&'ÿN[E, [֣Ey6 R7]3d;7!|ΓTɞ5m'݁jeA;3 K>;u4^wKg9>* Meﴅ 8|c"їÕ .Sr9t,0~Z^UzU5/m"al3%I]gS .U.AŐ%^FSPq'mTG=2ͽ+ 0T b?'4o5PÃX(@~1 iL|\(oxfBγ[ŇbPTJQr@!ѐ;. p *[2漴2V4NdOHEa,-/=8v;BvTz!$Yh?X?Q"_6  Ds(cEcŴMM`QM`RQkx6xx_5u&z3:3-5eqkȱ.A�~faM=1.K;v"tu|{5;|k l$=ǯ/@v l6U�7WR?i18ЃRD…[_ h\#Q@A7IbYP C(B75D7!> q`}A]%FՁȥy d`ߊaf; u|y3#% 6y:a4}|J.%+YEf*zAk�VGxGuA64tr.Vû}kԣ/W>Q G(IzMUIX#i>ܝO-/N?H{f3ZTByR`_x'x(f^I /xdۻ~1.Bι5/!@crnh= W -e+>>oXo=9Ӱf " (\0.ZiUj+iL8앰nQ6zibo! 5 J| W-!e}*RInEp%H̸m\^듩i)tsYG,VO_ !٤z-QDˁbii7(<pM 4> .;X̸8 A8Ѱ_o},6d+#]N[9ΗNr1 YȤdmbC ɞ- aH`5c-uد:rfuG^i8mm_!ѭzkIfTʬxQ~HRc -5dG^$~,,ϤMh8ՀruS%:ؼFjmns8LYEzv<^MBc&N)v܀7֢Fdusi haLn틳Yؗh F;jxE},E,LP3x}I+R}@ćK[ajlf 6+=|Dr8[Å "Aq7ro&AȍǠi_qY*:݈IW4&cMuBtJsܱbjs\/iMYݏ ;P$ JbZ2 7#j|5f%ZT#+Aœ4-1~HA!qHsD%Ƙ 0HR"t%+P5rLN)&zirRw_"}?uXe'4kP9`tՊ﮿؛yPדjx^ /:U<cbW14*;;I nn\>-ǫ6= }D;(%QbGɬ(h"x[*7Xt{DiK ۊT @�VrϜ1az]B#sM@u#ӝ)ԣ$T?xNL!.WtۂhGf*bK¡bN-ejF+O7ߔ;ZKǺHK 4m] %D=(mΔ Iy,|KwHw]f^Sҷ�-2zv 8RrnE69*9Oxg_sˀߞrZ$d(GV|[\iC%6\cv C+BerG TZϘcxDmGD0̵({W G#uewaw!8PApĈXG Ҏ#dNz4+KS甃m|WQ!~R6q`6qz\42և`zO5v=s9lIO5]f1rqhU(utn;sQH4J6s dLut@ڬC2b d}.M D"m$[ʄL؜/�<v(I)x& a*puGb%"9%5 a L l,ԁuNJON1Y.N:>Af |-б}i[l?o!b?;.E(Vh37PRO�.N2;Oz3'ФoP݆cƣG ndX _‘Cjtl5W{LJUu$&E˨ʏqǒ=pLz"!G=p*Nacx5  !kQ0 , f; P ļwW͠c9dOU aûFq Q1ŴS.TiCbJ;ECQ?WU\`obc]IX(R.�?reP&5otOVݘ/Kpsi 嫻^;f!Ll}]Pɍ+O/GH2}9БF_JBt5YgE Z-bX& #=A|MuvdZ-;tT?wƇۗjvo%84 U|8."Y3brmg>bjZb�)FWdGT'{A")RYlO(+'^DzPS^ogrsN^ZH\뼒rJ9ЦtdE_{\n=%.I?\ymMkDm_s=k›x|F)Xm;,1SJ ; z@V}~.ꭍݧe8OPm62J>DxֆE+B>ߕdLVW2v| pJ5=M!gMC\{:(U]Vm OO e�1~1N;;K Q#pT/<0_X-]q#Oc� oPݸ^x }^ơ,q) :`Y 7@ Kq4z0Z<kEN}*dѰ Nir-VkB)".FY )jOf u61Kca>o#=dt~`j`Q{вcup U5() ')h]=t�W&ܗZI&Ů֤E5P$2JMd,u IŦ >$0C5IL$}sƵ=u]. tG;m9) By)^6aw{*|Al#>Hbc{Cvߨ.ֻ*0;]+J,K3|yPEpc@ x dj&-3 t@vQ-ְ-2#٫FS(1ur;ʕ&^}QdntqPNLG7d*3!_򞲋3%ڇ=:aIGFG;UPtl&tO/;02g/J2T!Q=u5qz;iptW+pe ,^EStG/e'5$j&2Bpr_&'aL1@pvI%]qA&U+:d(~3`k0D\= n"zSrfŹ7e8hˊ6UNJl, í'(̼qغ-З47@Ӗ{CBih *)Y:{=%fCς~TdI<=& ч4sՙI91mPf�. R01<붽52H`7$ZbUQa&X=;}DH?_1cACԹ�ubW][fm. 3ܯ◿uE) *J"Q.W+m hFߧ)cb0u4Ìb?)qNTZPGq78z]Kvz(K`4IԟiЌpY\M}v',LVX� /c*@ng^%Ϛ躾'HP2VZ'%Z6B3qc0IGǟϒ KPt  (\ /u_Kv[' ;J6v-6v ɛ 0?Q |3;:U<7,[w \.Y0QZMIIdx�)!fD5f)ES2'o|AeF򈭖p2M�"Ą4 yrNS 1KCAq2Fu ݱŒbp9u9oV@M+Qy]Wȿ\1<ݠ|P!;=fR`¶Zj|E%}=sۘʖ8䮺꺄frs�a6F"N)Wc>*S6Vu*kE#s ph1Ys<sPcoJ2+%d,{&򷉃 1\#^r2jen%jnĥ2xg*DLoRj{-nO7SZn!yȢbU pe8x3R #F0i*%8X!*/zKwmڂb1 mBΨSm}|#:{nĭFgOweu1^zv Q}7rpqb^煶h_ rP_o]|5 uL]44 |j׮_[;g[^ 6M[8*ROjJ?k}H-<X l 6hJt*eMV( $@{s/FaG}T`8p;c>U9OJRAa]Wcꇜ#I𶀢eſ}H,\�2`._o)r]o DWĐ ~u6sޞ"*N~O8$,h]c!qo|9s̺bRB{ŕj MS*FBŏܓYe^Y/ZB ~2)wF.w*"?#k#q $CհŚE*\5C cIX?_�@>=Aڶgi*`r%Q{;=b<j̲\ec|h zo;b  ~b+1;k&"tm K?gq†Rٻs_/柶閕¦Wpccy՟Ko"Φ*}@H]u~>1@eYV UBc9%3>GQG]^ݺDC1[Jp]?B ՞ajT8 0Ֆ-ېOP&Q�|fi;b01#VkVg&ׅ ,n`)HǢ mg�k6C[蠶RbFRn(3f*Jd`>ZꞚxpj" Eu9Px-<ːru5ITaW 0jK)w/Ï …i nT3tSU5]J@L*Uc°�XchK OgxƇ.wkM[-_6<4쫈1Դ혗/AdŲL♷f[* ̊]Tƶ\^D4_,Os#-;8e~gOgH.EOFKS`-hzT+ߖb%{OF}8v@20}zO|-2d}$Z]x^%,\,ys=^0J+-I_@Q;<!,=lpo4a{tWg<60y0-9'vJM{`pI0`x;-;3+ W&ԑ,nez! #qʅ y,{ҝh}3qm#y Q_=y6al s3l2 yqBLoT E*hE ͐}SKK|MÚVEjԗ\̿�ݛ .c䍧urL/85TsQ8'aM!Sޏ Xg"4y۩THqH L5i JR }_UeF80S/ʤU>h<KqԾ^ՎSn :w+�(rOoy!g.W{3jڀγݚlr&~WcS-ű1Lp2tE:pK <Xg\17zU1:A֯}z&=ݚ֘En` 8C{n!xcnxrG^߇`-D LzӜ86gzZr4Tf兜M>80uDȄR{B=_- vδȚ5\]漲|i8Ucᾢ/kHrg%xn6 H~'  9BzjJ}{�S- n轅_#F[w347l~%K̑b,#R#*1|-PסƲ $ j0At!8 mlU!axK̮k1{,B ;mAΔY>I(mX06fpqpO0|$OZse~F7&g_-c8jOnpN0VqxN>C(xcךc⍄=("q|D"8A:(mP.AD#ܑ6J� ~6^&t[}*.A+MnB"uCaV҅҉/c 5>o5C}3G,!_ =yߵӈilDo! rRf I7 N ==)CA7݊D8υ?)y[QxH -r?z0 )<,C�Bd(T{6[KPp'dWCV;+`^TH,>ôkINCꑰYwXI:%|b 2MlEJ8?՞Co;\އMhA2[`g2gd!J``% �S 8%sp|ސ²c? endstream endobj 128 0 obj << /Length1 727 /Length2 11430 /Length3 0 /Length 12025 /Filter /FlateDecode >> stream xmteP- .ww6 n \wMܝ{{zi(dM/' ;3; �@CFB ‰BC#4Rf�@h �:9�ll(4�IG'/ @gAж79\�ڎ & �@@,NVY ]n v]�N�`Kп4#�tf�,). dՔ-JrWl翶c@ٛ72oߪ#vv%0Z(Ml7l_AzHZe+9�tNn ೣ% pH{B`Kf {/~:o*Z -2k?A; ]-^32cPWdS[8Z� _G\, SV5#?7` 0`cבOd, GOfv>�3';ɵpsq!w @Y^r Mk/.g2b\b9}naci{JtIF:d.y[,?>"h衄ڿfI{62y%$ce9Xa++FTKiY{<F¯,'L746c5MXᙹ2LD~&9 Mayn~Y;*G(0LtϞ7ڙN7KdOBS噏>Œ$Ow*蔉2[a{T*%q(<8 I9q�_O8RN`\o A1JHd'K_gI|"e$ien7@׸uiOF=c uR69Y*‰Ox"(dWٞ 3D pʶ1k:=05 0~xNG11{ٷvxFӨlOTxh'^VƆ>rTX؄^2)nMod\3J+` ]|H钰AFWK>CP^1J|ߔ躽 -7#R}9Ӂl?GsH<aHsy~fͶ[[yB]+ Q4k7f"'u10e$9ʊ<muH qWAU|ɻY_vx 75$.%<1!yNT�_RugPGD+O_25(Oe:D K3QᔺT(=|o|fn%^?/8z :o:$aԵIpxavQ6 oX5~ m9_Xw>$6g`@\) 1 Syz8EN۷UۭLfsP-jGau,A|@S|yC҇O.71}9wqp6"@^ rRtWə+@'^3S6[Z,YOb m8tMӛF-q$ {N7iOi+ |g6͗fTuo*y㬲KﶲatWlZk]C )#a%Fm~MC0#':4N�hoOD^wK6[,C]*J/ r;(K=G,1K.65+a#CHL)qNs*.mT# ۪qPǴ!;X"%;w3vm`b ("yKQ+•՟&Jh͎%ߦR &hŅ8q=ZU,+U()rb/amřMwh;c]GZd"ôKAGZ4(ћFALɡMh<t7 q|u]{Mc\pi ǫی+<Ԭ~Ur!1nX8ixx"Z(Fߩ>cT<IP;Tc3]q?;T{Oxuj_R) %g7 G,On%6zU\ۦ#wVׄ_pW6zͿ7x|Xqǫ~M8QK-lض~zһ Lw@&`ѣ)d7>JFg^p?>{hb0ش ׷1DQwE1`HIʱ_vwXm$)r*+qЄMz|Zq6cIBI)2h-U|RN;jNaIeJ]Yd:Hq1.ASVn#)2%,.Rx:]!_8&L:6ڻmq!E"qp _kH,AU?kKRߢ6nRL4da {E1FNeC›ܡ.)$ߩܠi=ð19+*~x `,}kkK2_0ʋS֕0p"/,0]tOޔ񝫙g b_B7pLb*0IT1]?S֩yr8eJJbk c>sO<sT7#-0[=VY )�*>*_ ϕƒZP& ;|҆Nʎ}i|"Rey(4+{&":?_E*Ke.y3[\|5 )FJq]_�5BiMm#AP*=$y[|S8%RtC.w`{'ceTgy,eU{4#\$yK 㝤rHdD�F,?O}\2#nQ;n^b4\Roɪ v"?[~[*;ygTQaz@ ZLpv!TX̘HKLIWš񫓟pȏ:v@>}+LK DeTVxz_9u~V�8eK 5^6EJ9#&\ |%^;G{TsDMҎIyl$PU6?:hjt"z Y9 UqP%5a@`dcff D쁒BuщQE=犤A4ئ5]\B,i59k>Y£ZiGM·1H7s S*iIcۆNv[+QP |=|{$I/p|X&SaHt#Y.#jQ-3>Ú3ϴua8I ~-섏UO(uT4!8Rfhkf6~yh;oEÊ]YF6ۀ&b&Fbtg~c'-#ܙN1Krtkg okI)N~Ҳq :'s9OϘɗ˵X Pס4ĸ$ZTwzHW+okaX03o<7|0(4y5]ղiɭ]dJ;cW)kSupg})بD3̱/a5%4iqxv 3[`N1JdB J[+݇ fF$bLG^ E=V\Bq".E܅&XIdP40N} l<o{Qt'm5NkS5"aJu^[6.?U[DH± Be5f* j=951ֻ<RLÉ5lr%ݢ$X0eJOuAy) )'G˅u#E2,8&:(jv7dep3 .�rwx ӯ&ɌyXLb/| 2Tnpz6}gX h<U'UXP/41%Sg*WcOp 'snȎqi<&5/_wof(]k;ډy}ZSE/tTp55DcY%"f7X_]H79_ }2qXI1p am`np}x8`+̠ꁧ "Ja 0:B6zCib 24g~Bk2V~<YL<5=>,񭼃99 mvc{dجq6Ǘ܍RSﶶ #9 wXy%dV?Ql<H$"p/F(𝷷 b9+4jt> q4i+B1KT?[!?)7CWx0߸m4g:ʼ |>8? k3'MIN\0-*P:\}4,y>GuܞEn_{.Y&jmN'짪íA�)4 nN7{Xqוbq"rhMZCV$P#+-e"^\qw83t5-Lz_iF~z֑) 9xWVO;TU44/֪2p`ACFGJ:״ԺW1h;]ʚ=.J AAjߐxA-%6.ҢX<mĿ)Ł-5ƃW+#Iȇ+6H*YȸDV!=`&B˚9\[ 8J+fzz+v!Ÿ' §?EU%Q]}oIʙWTÚF?ߦ`%V <D͐.39 Iei5>GVd8 5ƍW67Z AJF"DI.8$&<7rDZs3jPg?�<{n,i;$7(DqUM!e_?;AKAƯ)5>,0JqIpSGŖkƸ > ?Bd@.Q]MFԌX@.<x,BqqY|j} 7!J5_QÑ/dwn{-AiB`HW[~ċN~ÝZLaoΛ !" eVi|Nod YVI< 柳uY8W_PgauN#E-H !_0(7d=v&7fn'!n\Dۘ:Uю 0Eˏ-Eא_ᄨu7 uĈ}N®# -?dAQ0>2#٥X/e3=]cwɯgB5dRߊvlNUOaJsLL)pE5a09v Z_Rsٺ3FaIf|UBQfLZ"@}jhcETFUZ"$A 8ﮦ3͠k/7(5.uW.gbIv-] c~V>k7g O \泯㿴wE zb%-Щ0ȃֵ+IPr O#e4BGڎ<EفAʡj bS\m@0 Yekc7K>v(�ĉ"[ _u9oI5�灳4 mE)xbc.x%d v'nPUS?Tk?RdA-? %ΙS+:ڽ;h{ | MfX@ IMZδ3U2[~AJ_fԄFttM"ƱP ũEtRM^v+wfx~"x{%nqP{k&&[7(%N`COҤ䄧NeЂ `5RϋڈV\PinNof�t-lJ#" v0yCuN4zTg3 SͶ( qsKWU?L'}js^0n iN\^jP+.x=H*` :PC_/3D|y/V];b堹]J2>åL|de@hPXLy f>QO+L筗/kQzsTkl~m>/$vOkD8Lt]20-Oik@2 yR:P|.ɷh?@Zz Cf{S>p&{V*2`US+47HN PmDb~Ǟ| tݡR(|wE֜{My;Jua=:wes K?;eGu 1 ď9l<>P&K3_~6H nõ9C3vwZe?c\턲Zb~yo6abX^7B##ʈӅ,Lh.Gӹm6wT-}@}븮ޮ5O.rӢEw=2e�xWiWY%cV}OX0X״QuƬUa5vd V@FxFj; 5xi#;U?|8RJ(1-xb2[/Nš;a@.g>\ygr0سN;rFS[§e^p~$ N۹2M9{3 d`"APytfR+ѫ^ˬlll+�"-ÌaS1;UiR=ßc4&E|QK*[Q>܃T,+˄ G"rУ"PxbQhUj_;I:>d7b35Y:F$؎iPK$!&}e~?U#P%fX=AKus3k5+ S0/@2A+jm]l8Oi74VQ# k0oF)j</_r# zIJ5#^SAلYCzL%>^i˹dih}'䠂~jlj<<\vv9S@;|00 VLsTcN/4XE4eXKڤo)�E=1i ޸jN?Ъoۿ=cuۀ\-l3QpQ;06dZ 錆qFNNw=9"kxW:U~ 1|vi'C2"2e䱋NEd5S}9NF 2'NG<HE.gѶ7׈8*e7Ҟ{m('/^+@6يɩ`)ԝRN}{, |,‹{6f JK48k!y,Xz|~(X2 L铼+~ߖ U}Yx)CCg_4}0=F1z-'дX鵒!t¢7nOUNO? r%^Scb bp友I iħM^E'm?c/XT BI3T5iCElѿs<?*@VFX\Hsߌԛk&mL7oi %]\ s8GIsկY n q(^q�uTՀc\In3Ͽ&# 6 _襩m57%4xN5$َ ԶRfov28-`q.$>Ɔ L}IHҸ 5&eJYr4rV*vwVkv R [Ohh:O{ƨ<G cqcVJ[͠P@UiNdyRpeS'Q_VE L f7`Xf;H+aXZ¹iĦ坠Z|-ŧ= r,sujR5e0PRT<~YL0+|چ.vP] />OʕSR̳Xu{F9aƥy7eY&{+X3(#Q=ʤ45{|喔ut)pB7vEʘ:D۝4CU<BCCً3Gg2Y[ܟ(J4Ea!mMt,w9pl0˲G:eكP~D5]xC7?u P$6!y\?,dכLvZdC gj<|3jT"R}YF| UF ,N 2oGwA^4~戄fXnXp srGQAu18D-pdy=~; ]e6eO؛n!<d) j'V_N k6\aeVksҾ 7 7 اzg?QeHYGgSTEG!$-0'Wg|uRazu(;F>&'<`6KWcz +2)#j(㨰jTc_É&$muRnD~UɨS"=@sRkK{64|u1|#ERxX5A1 $YWLV*Yd^|/R3 L=CS䝨hJGzGڷQw[�[΅#q;ZA&v| v NӞ\$kga/XJ "Z$Iכ%)￘N6t5Y*F&ǯ= ǬNfQW2KK)Ýu>[K苨%}~xg*-=9F+CMi*w<1O(yF=>:X?%e}t/-/ ߗ|qkBߟe | J3 W�MLw+;y-bմOp]陬OB[B$oNiQAŭǑeVBoHC4d �\=% x&,8oD;܏X[w%+*%DٱHib?&^ԑ'>eEdbz C1U?-2|i ´%<kߑ^)nmTWiX̶xNUZp -p$N5 ' vP"(=\!'B$}[e+MI/ BgR$I!~I3 Nl:U qh1^I(!8{T6H8믜&Xbt]L^LRĿ3tS.<Qm_)QS.-9_3=D䏞>\gYܔX9 Sd~6ee*2 t1Kt-WPEJ9 FaN؜K}1qMB Xv2 b{ԷGYpfSS3帼ۂ2-jwIѶf~M>`0p6Vr!Vj6dzs-Om+t\')\)wC-Fnb*w8h] ECgسCrX<!cAT|L `GfP K)znNn@Xj$|6 vݽxkѠT٣e薫F0ɧ@^Դ5 *:e2P/2&BXNrާm#zix/:Q\h9ٷ9mP 14, [ D<xSjaܡytK+f%˶aVn70؄߾R=4WҾ(x 3b|'e{of-{7͞# PVc]_&Qam!$q7OupRVu~!7=ZdGkٶF I/"WI7zgذxRg$PL- %G,k益<S^l]jiDpTQ'W1 J}),4JNfPqP`hѱHhC/"UF29_X FoMzk䙩x(皅:.tj6K+" V󺿀ṀO_d͂LBf6A zN}Jπ>axvp :XAIо⡁!-+)JSCaݪO[iYƓ8)KNUxEF2,N*qL[i@ٯm< /<CuW}קQyCZ> QiMRa L3%aI3ql B v^{\ ޱKmDzDyY"P[ ` B,-{e J#zcȜ|a*.7i&ةX(?<"2D&wioR}/puKHv6dO=~9{ c{<2)/g4 }` *GTf bPꟐ58tshI;KJo�796jꐉ5,x*FgVX|qeOJγ0H~kɱ4[Ez9nC/V%Hu Av|Pɾr e›=&T:I8VLkugq oB<Ixpy =|ܙc~wLos* Q}p[uiVM_s!7\[rr3su?-2ԢF'?+)sY7f_P>1KB1U"9e٦wPd(ZF5ӻ;8#=- ^ǟXB c,$$y vFN|H*)1|]#a/-" ,Ͽɡ<YN<wUD͊FPE"2{gy_f9CrgM6u@SAkTӗ]x`ܟx&)`r43Jn|12fR {rܽ\ZGY $ۿJCN@`,ޕPÀOKDڄO/:E(.Y e2=<Yΰ41w ٻgqjkɳ  ZY0TLnT"?N$He,MKuE%׺]e:’'ҲlN!tJ\DsR] >Lvҽxӡzd;^q9AW-4B# oz IJI.z[Zq6%EmRNn)ͭM4A8wP?Lf2)bay5k7 Eȃسu7 1J| f :\K,s)EaR=<ɿ Ɵ{^�`h?k?nj|8# V^Ư +!ĄA>>ZX^ ? ~%4QTP2Bs%y|z{3C2C[6Tzv.G % j}W we%A+v]yMۓp>bj,?X-9sg﫣ĸ·� endstream endobj 130 0 obj << /Length1 727 /Length2 14944 /Length3 0 /Length 15522 /Filter /FlateDecode >> stream xmspf-ctĶm۶mضm;wι[gbc5w^j:x؛021pń4ٙ8� t̰Ž&@g ;[ @lb`d0100’=-̝FԬ6�5;k;W #s� ?ML�&�S k8B\N nbk(Z[d,LlL(v�8�#;[cqr7[WG:�dEUT�*�1@FN\M  ,kXcO?,##`hbfa K/$mM�l 7!(ѐ`lbZhcwq6q8쬍&&Em,=! GA[dOIXMH︺_RPP1wZ @G ;� g |Y;@_C翖C ٹ{212h�l�NFE߲ڲwejKhhl촁*2#T6Š% v<L!_ɻf"` d4a2K1qo֛bk}uĭsZ$KfYC)/8cAYGvƒ8.9ju5yH!XX^5)yAyAVl)dhE΁g&b.c~e,8y 4 NqlSkZkcǒ?gn6?P#ml WB.@3Qk7  P+\#5I9{az%$Q^+YpC%^42ZZIO4KAO6Zd>"!1DnUU�1d vSȅc u)gT}jR.ux15lFnڠXiP{&K-k~]ܖVZaCrH]kTW{z<v XݎWZԤt ]�!T(O0>XJF~Ȟ 3˨V(_\H>cQvBHs|}\Ja_7&c] zdUU\ FREWOyCw9vt2<. U%Q$PE`#/!g7tkv݃>.l0�ˇQ}g<oEOC5kY茭 #(_;F *kgEQ:~2V50^lzbʣT/x@n^ FM9_9DغcCTѰʪR`UB,M= 1 [)2D\|2p'y+<`$)ڒ,ɉp&դHe+,^=Tʏj5_=OܜGM^fO@o=焵@x"4ú!z76&ERUف"INK^NaУ u`bHܱMo3YjOr[?qzlV)b]om;&6GO7{Q [0Aڼ֐:8 Cqc(^ *YrCi,jESIQ*VOҡNN2lJ؞[O_sa]]oo<)u%BϩtZѣi=#YAȻȺO$մU5:5?RU:$;bKC +eƳf�z(5J?{M}3Q:gT*q52\o*9wL\o7Af'nrq֭C&Ԡ?ia%k=z} egL+@ͻO6B@~h0é>z_k? r (ѕep~Jy1 c7n8D&2E dEqZH 0楰m |MFw b7dΦ R`_d[ xv̬@έΘ =mF؎ZEq`g:Z rKzEeC+(<䄡jd"֬Ka &ISn*P{.JNF~=3F>5c?d¬> dA0oҩOlj|͙p傯扷}a3P h< 6źe`2j @J0v’!$H<_FŬJQ*ְ^nOnc%Yܬ>ywt@M7KGÎփ֊xg@<C[- Oyt0w`2xI)j$ a"4!* a2;H5Z 4QS>U0I@o4z؊6Yy4,ajJ2TٵLMf2c// 5 =͆4mObm&kP ߳?MJ(Aѷ":M@֪HZ.8TJ=>h|3}dyzHI'oʎư:Xo˟< BMW7@W8HazԵJ;O Z̓`+VIsMόu4Npyp"r ZI0Q,v#2@Cv(VBYfoo2>tECl~εHF2/$[]ω$Xͣ*刋JT.Ejnt\eB]a -40'JaυTT* 6u`ւ}ޱE1 d(/Y?�#ST+B2rFJKH*jx2~3)>ڋnW3spqFa Cq6;AL0|ŲV젪K \+e3 �o5пW 1s>jFhM':8tU^} 3ΤTyH]#^W:.ϚÁ_-"A?`Lm6i{2`.)"OeC2HïYj Y 89o2u,ߺIQ[d f$'-qI w'w(:OKEͼ;iN_�'hɃ+g֐0ݦ<筟(elPsgBeO <Mye\lk"YIZٱD͉XéP4`P`{==: q̍S,%SV ^g߶NT*S XBsxsOoj zYmǥ#z[m3A빀rD*5CwEUjhK04ᤥ +׺{ ȥ0}ssu][0[<aiTAœ$% r׏yyr߭3ʜ*se]"sS/# 7TBտ>  Cn#WzUwchaٓXn$f4D%ZX3`/I,Fj :<rBʺ CTyrȎ] i#$=%/|M%[<Ȑ$2cL_|^LUƎc8潐r p(/^TZpKx~wU5JiHr`fQw70A;LOYv%ɋNx3SWah/v SLd"~{ԙifC%teAǒ:;):Fn4{^�7_UzVjb5PEBnU{Z9j𪯟ϸ]oR4u*W kv9q#R Nq{EZfė_ eQYvF]Fl .rG@G3·+N5vrz-OA8~t%wiDXr! h-aO\@xx=Κsx(GET^ �6=ޗ2MDGE~^ N(kUttGP~;*Fn=XƨL6O-xȩМ 0EA:_o..o! {A,$#{Dot 5]Yj7a٭r*srzלL>~>/�BbEZE g1wǕ@OC"b5RCTTU/#ОBem8U˓]27St؆P"k7)t &<*-#M#JG .e=@WF %)Ω]nӎܥGD66amOb1ˡhHAC9U&B4zYX A0ܾ/v^Ɯ:ɼڑ(?=$=暈= 9{[Ic\~ˀ4޶-ۧs Jm;L 屋d, 4a?:=YL4+4#i"|F*xH5(vrD&*xm2њJ'8  rVHZt.|{JXEy/VA'Ou,-Oo #SB Rmҏ KM*t 斦c-"pvw6vZiE+eȓaM78Q2,.#cѲ-}dv5X.I|8E^*j4fԕbFZCJ ?PE_l}',#Ga&Bɮ2uKmXg(:e'˾mKil)Mb\z2pHKR_x B 赇XxvS(Sͽ!=h@B΢vcZ4ȊY.U{ZLWC{9og,6 (}⣅&i"d"B5H!{dBPF&@߅J`>!ڋ"Ww�U9 ׳#XBoڃjEaO`/j<:Pp K<"E".D>]-rZ-FȚ o Ez<$dHe6⽆%�ݳY;[iS\7jY,e˹?x3>O$ p=mI/g'ǡ N4ݴVˊFVW>Gј?o j!~8}]i3)5mzXFK=gwhO۵�TAJG{]M4 Ly` OJۓ-zaKtNZj(иBy5HW`kHg 6١/y8@aMYrI�_r8V㹭{�=Cݶk9^vl֗^ǘ6v iݕ)PF8 W8}'៕rz?mŢ%>2YJ= 91J˒Jԅ}}WQH+B·WhPy/H Dƒ,}*|n!O!z":_^245'qN Vmg}<G6؛CP/5j+|Zhc!̒ˌ$m?xWl:ݪ߄!qR%XBܫ\mv9�4J؜%'BR`J`WChDZ,5xf7 Zj& S{-{u䏄NiVM|ccE5'w8+;rMXBȩ)ƴ1wVI 6GQ&poyGUu[کOʈRQ>!MIFT+{S2Z>%w`r3bGm yawN# k6 Ͷ4d sKL1ZrA||ɜ)<FAZ<r^C{#TxlqSH6H ]h#:+Ӂ#F�\G~E߈ = N5oSBkT31Ǧg$3 I}֮)uՑI'2u"7̲s.XJ!cƣq#{ޞů}kdJ=m {FmHS CVzJپ֩p|V頵۸G?h>k+tZIHDjD)Sb )A`kNѶQS*QkfH泈DEz[ 9h@@l QnuFxn~0,5| 8L] ``Ɣ,acֽ#P"LF'PCC�1H.Ǜ=Y6y'Ȝn#n]s%Rc cܽa/e-/*$uTn?*,Ju0�ݐNZS~UBV&5O.&#:b~"`/fB IL툕  E̟PR35#h0P"C,Py]7OC#P3ߕ>rW% w%<n;:ŀ**9\x9jZ<<v{; Ntxv?vݼ-ө[^m[޹'gb3]N;;e%wmHn5WjgV~7*ՉtEtMQx=I$ ر%8aU8lǫFM}["vIIWL-9'Wp49eTԡe 7HV2Ykg?F a#xvuCFF-)]0$4,'İdMVyG'aK(7Ò@M*8<Y_s/ʮ;HReτ(N[Md-ڤ1#XK_A6ү K+y(<1͘wMg>IE ]"Qf|2wK6>.**m #T9hk [ӼAw !UC T9;:[P)}ZvC((8ߦ{MYIi~a0h32rdlўpQrY\5viN76zVʈ v<\w>l%q0M1l[mtFfBq7ge[e2i #]$*.۬v-}HːF"kA) 2_[tFnaގ/f?35ʒ#7))ɲ∕!趀7A�Ҟ2X 4J[dQc6栟F>ű=W^ˮx8teHD6=!uo*8DZDB̾Lqcu;Wt>;)N{fؾ)ҋ3(L\2ֽ4 bJ2{ Ia[<蘱dD1cҸ杤4'KV^ZiiQ,Hܶ]\ x!(#4GO19W=ϿTPC0h$; o=uGt'S<H}[hl[- 9a@)-x_$@yچfYkup,u#Z@\ݯ#,ر#J [4?XwJ[Z(ڀ>jKUO˵eN1l#AdLσ.P9UCϝ!]J7cH2[bN*-znω¨Ro} ?TǕ<밶h?S_|Yn~;G<sg~OFaoBZICVۥ9+HGf u#^%td >frKdל|Tܞ|BJ~N+K ?}j- 1/e6~:0L~-6}K4:E[H`rAΒe}aԿ&_h6 ׭|y0d~B Jvg2v1+{lLcZ{qg&~T텾p9wf!,Fg\nT?)O/(,FXQH[к{6�XOXإ#=\*s-ASlY_S 76S ;?㏯~ n'u?F6E,=V[_F, "23&VzKd2B؋<40j-6DFoo"ޥւqX;:[r|~W ?y!jk (R�8ݥlG c?[|yw!]<j yL/MWqF)O+֛N->'soq^P72 v+}Ѡ]Cjԁm8M (0o+nvBU؟j*nWNDex wo C+h-EJ3}Q0SߴvV禲8K4iug$씜*93Y<RL:rf1"Mí<),kP4  ;R1H: d/"ծh)Kz"xM<q|.sk&Hj'B#/CԥŁDR&19Ufh?I;n.acC.t$K^_OdcKM~%T?#{l,Ȥ4cWWQ<QEm)%2/D6J4l{,-bCO7dk$S&Xm?-Y[v 3E8a\6ކȝcGGB1z+U(P-Y Q\RJmcl k]f)݁T(|53A#r ˇ0 îvv?8F 6Tu7f\57FvWɳ4{\,.γ{S5H xLfgu3_H*SkFJ͇Y\>_?_G5+_sm]EMS^St/x~7歫gҠ.<ۣicb3+B37Hbᵷ: s3*H7 Y߇ ~6=0πa"ygbi0*۲N.MWPOb, ܎ )<wXm쇼{gTvZ~J}N$KeZ5 [sD3uSU<n;xyݖ IRK� Rw笻P.DW:"4LkG_h Qrc2!lZѥN]%{KxDY-@cG7r~Q<-r0gJ'fFƢY'>q1|pӊܿ> <Ѷب6UzB\yJ(p!V4Pi߅y K63] 1~� o x ToXe|d'W 7pz&ȹ1wjxY,7eX:Dq"1 9(6: \RqN1* syjn #AB|T 1Npj'(FIԥ ~r>ŧs>#hZΙ!n"f'�NN:#]Mdlf}܃`yh뇠/?$dg';.cFd$Zӿrb` a3;O#H^47Y%ip% NYŝ! ^UB7>+a?uQTgdvCDjȽ X,q0d6g{+szQDarGɎ�-NQbS7(5)կ7Ue#y ń?B0`uRnYs]s-.XԋɎik0%eNh[{L n^~xRt/L /"579Ca "C+#HXK0kOX5icʶ<߰~^jZ '5pѶgdlЋJn@F2pǻ_žh|QO]gWU0h/Z{5-SJL?Mqd'ʓQTԴUbG%EK�[!kj_23jh҉ʏlğΨ6s [2OM^ڴ#u>c#P7Êa M wp25D >{ $@K?I2]9ʨw7?{4Xֿa]JmuL.;$=0%yEA<VX{S.O\<p!NRsy;rL׫5c8a믭ohcsWxYDWb~ʷaZ<W[}ʿ7@B(ќwD^ćׂ!ţcZ;E*Q\l/~ԺXFA`>fK<zjUp?^k,DP `IVI,^kC@AxVDU|Y ]M<!JV|_�FH Yդ Á[C~ɩ8]*)K۳xV> X[\6]"nd;7Vh俛5>=r4&[љ RZ%lP5ZXug_dO}-E,ɪ\ݪx{cq, LS>rpsK)b?WZ�E_'嶘'Bũ"6`##rz#k'k(Oxx$ %4gu[%B)ndZbSv?C{>oI'}0W3i9EK!m:xVXK(8:emzz&}sЛ_ҙzx"D g@r9\~a"w}1L\r'=ėqz>,VҿYTyAaɣpYMjgAq`j )Y6?STP)*r1/DH'Q)D1& )IգpeB_Q8piD܃`9߷2`qi}2<]:bRn`聀^oX o)w5 {>bJ)Mw.BJZ$NAJn7.� i!sC:9#8G.H(VXv^B$1�F/їIkl'1+'x5pqw*1(l0S}{ m@֍DxP/OdG%+z,h a(Uvt(@fq IPc{xin3fΡjCb aff^<gO߻ );@L0JhP/~ų-CV*N99Pb7h`DZEk6}&rbt;cz6*P߲u0bngǠAX8Vi7%Ƽ[Q "GճDϭe(4󺃕SM1d\m�Ur#,gsxPw7RQo_d@SmjMY'Ku7('{3dݲ'ZۓۯOb}su˹qVp.[|gO ږ*A.AxJӐՠVc6?-fwy.Fga]<$@ ۾QDcRJq Pz4ɿ(O9x8|K8iݜAp'$W*؎ \P\oʦ[ػ-~o1.l XzI^XeKjpu $͖-=Gwa `vXשOX~#@6=xa`99Y%|f;D)qgwwĆ6F_r'm}^p4[5Jw]l!Hn-"UJAFffpO M}Y{Ŏ�s 0́>Ͻ",r)$vbAD6mfr=N6R"WVն[I>N x".?f*=b=Y]H{B<g&N.0]hqT[lUL,`dEPjP-~-&'0%hu9M.Y^sK^я6m=e(wn7T*zhQ}tNP, +Z:aO_LКRA.bfsDc�?g_yR툾Z8uh,,e4^Gݨ}lzp ʺ lw ~ |&w <r_3&84@<0DV p$1 ;{ǡM= 6j 7 8\u%PorF@":#sh5̾:5{8kT_=lRf`Z)Ldn;_TW1b0Lc΋(촻5+k_~OIF J s&ETI#M w[r3E_Ń)Wlu&ų7ˊvMmɵLj>q$($i0Bpz 5i"֍ȿ<4{늡f& "sʼno2$mCj/#P$j*h;ӟn{p|^B8Ӊ]ce˳? "rW *4o! u?ӮGpW:O)OGHgŢ? $ȿQ7vls,o6Nn&B)sLLz*Gw=$8YϤ]7ҳ"#f: _h$*xdf/N{<D{] gJ ~7M|#o̭p.s߫nAx)óBoBGpZ}MH{Jn܀B.#,ƒ:cít]{ 9dԹ8ID'^ &ߌj2S|P{Q;YIySu/#8<y{@}zᘣfKM]2Yg4tz):i8D{ h}n1 {K>Z햽G%):-X}30ܵI.VԾ< e ՚)0ĵ7ή*\á%a.FПDH{i�M՘Yhl{HF$_YIГ J׶@[ ыF b9V#S}NTSn�γj3'Ma@:eZft1=LdIo#9r9|VUb[c\ 3IIki\QeMNn6B-yuh>>V<+{:N_:VdAB""@HRlZiV$<S乮h-&q3D[(F]CmfS4~Vdv4P섰2CbBh9+q%*FCk{Faw؄(\(GRd3D`,{.z\j3}a">J4欉<x&pX"6VÊ1ſq wºsRXsZ:Hj0;D= })&$�7vIIB7$IODED -n Dm=%S'߭"Z-4HpaU⏳ᓤ6X*#-_ GQȠ6h]gV&{Cn+VF|xsc}: ^͆#$K!0ZZהy_[14[1Rڦ̈+ 5鯚'3ޖf6qǴC2Ncd?6P'ãUxewjR@|7 "Ť LfN$&0JQs`5:}`}ˡ�oҷ;׷?}XȄ5``i/gĆsz+J8#{4')#e0 6CF]?>-HUDٿaԹbN'u:rK)xG/ s*T$v3)gNϝHaj̝֤F̢s~sn6 gk*Gx1k$EkF YKYk6+G ܃,s'dLgmhQBϱ\�Q�o+BǥVEzߋ^}% o,NE5cOk")E.՘o@,>_@s8Ebu̻ώ&ԥ">+^*M?cY▭XьETʵ] Bs-7HlͨcGd3Ym6Ca?S]ˎz\h- kr*1@ᰘVHϰtӈ<mbWD\kbDќ@_2 pg֑7PxU3VE0ojm1r.Q oQ` `#G `A%lr3/m"?N- W2Kכ9!k81Aͩ_ቁ`2A{U.yz}:b\Nׁ07;<s$=Av:}ѾOBC2:_ �M�4?AD^F)SlRN;=To3).> %WwgB}hF^v*NZ*X`:D]颟2,nE d %/Y? endstream endobj 132 0 obj << /Length1 721 /Length2 22577 /Length3 0 /Length 23130 /Filter /FlateDecode >> stream xlpn-vڱm۶m۶mٱ;sn}V]s]s"#sQt0ecg()2bd03Ð ;XۉrMM�ʦ�_ L0d�a{O'Ks �1տ�5CK[K'Mٕɕ$eSS) ,))'Sڙ:�\l,2ƦvΦT�3{'�c{;qr7;7S'9dEUT� *�C;?v.\MlU-,,z&& _Iڙ6quoBP!)R kobdP5/. hgrqY:Yz(X#?ۯns55tO?zs&v6w Z2ZB 4�ۛXڙ]+Z{K`kYC'K6#ڃ_-c`1b0d:9ڹ[jߍdjaj jobV'Z<_N3?kJ6wl9latsS?hĻw+yJlo w<5CZJReWXw mk,vyg#Hz%;NʱY)Ơk�i'a}NJ%Ki"ڈu$ x [ߢ2(\󁕲swV޶ЦK7"! O]߬Π=X]f5Z̍ժnK2^Iܒ#Ms2Rh2} :܀BJ̥ ~$bz7݀pc+31NHVl %In-2n"{Ӡau)/ɼW]ؙ"i߁P"`{x>t"s8(CI֦Y: _U|=&5SS+5L=ts+˕U]Bs3t 9vO([qVJ{y6c.jsLZHEEJ(e=1<5UeUOC_H�ɭD!79( ̹%J@Wp5-!0ZSTghke>Ac#:n #Jl+M>ߟ;9Sekطz&Cemgoo)Tzwԕ ?Lj/p{XZz4Vxq>%؆%捰p3Dh[~ƓPFGB&X!_™ sةә|qHpPݔB/XS4˽km8CJf &EiL*� A1x; ~m6:Q[vLѦa/Ntiqcߓ tO~څ?p,?KQG{P!M HSOUp+nU>Kn ~g LΥ牨l9|PFCW`^ YűrD2 VuK NY +<CxU3U=B{nė [9|p�֖%#S;>̦ҿ[֊wЋ#ؚ[' �8^ZT&Gg IՏ;YRq0{?zA>"'5/k<UZA8T.\֮p�lλt:~O�>(\qݫg(ƹi;lsc׹ ҹuٞ 1^$Ha\IuO/<7uy~пOv.sp]OAp:({&B"ʹ1ea3``x[7:6eK|EXa%B4MVpp{hˍedzT&- 4jKCͧ F ]U=Yfod2+D+V> z[@dtԎan?8h<tƞv~U/ HT)<PBW3=[5vatZWt$rS.9*hC?1zD۷a*xDDnLARf J6{_Bc.`v<aS4C;f! |r~q5z?;4G_z K*~?l) sAg=ˬ-RP;c7\Pc [b\y$@D ySXd/+۩Fv_TF}FҭeKuSNvtdaCA]hH< :`BgJ4Yx⚉L�z;sya+L QooZ$,".mPC?tIv�8TFےLA}.-VӶOdxq6l+.kW> t܎\w3NE,zKwGzSϥw gXU害Ќ RHBm7z8$#ƯjcQ*N-&1�[mhP z.gs76Ok;7;pڡ"AٔijQssXIB`{nj|H'g0_$HUͽ|1 Պ&z'HyQOJ(m _QmBQz,G>AywP0g2Y;22 װT{,8f<f,`/g¯kQ]~bvjn[,M�QI:~XqXCT_Йx{qjoZGD&k=4xE;hlf[_ fvct ҽNNja*UKYi ]s]4J--}1=ua]ս6b ZZHA%4WkpkQoh6ڵ4T?8AF2|vPHFeٮ z.T;<k"u\\gaː5C0iCs,'-iON@u 4<qǪߘLA:*']FB&=qRݻXKSEh a6Gj1+=pgW)4 c,APM:rM.Ĉ KB°c > vC`~Mt'1/ S~V+  sҍ1V@VRih |s HlDL${Ny62JQpsnpyC� rd] $�}8kу zV*& !ڍ"g/6p95ZCz&;Ѻ0.]NuTDKO|Gon} @ţݝuihl8:Ὢpμ G$v#w]FVkрJJ46􁒭<+;M醚CF62,AEՔܓJM6�b9}~7G <D[C~+ ;�0Wkq"wSﰩuY5 ՂkP)b(y׌/A0q4MIf76"ĕ2)OٸH`߯>dn"zӱnVl(nmS̙l[fq4Mڕkjs [)OzɏԴj�*iFr o KnBE>`Ui!ONaH4Z l+_EE՛ÅGoGIو昨t߿4V~æB�{z L ޵=Hҍ^^[4f%rdc~̬V] R%2UODҙE-kqnl"Llgxz羶0K\-Qw�=!>W) QN)X>39 CO bYXq- HoKM^ʼ;o^rVG\0|T8'a*+ >nVKiJw=ٿ԰^!¥ )!XqXj&c3[JBfKK+سsZ>VuTwwFJ>bRzDӅ9%,6�` ({ʞ$J\ӛܦC yJsӲ{xO A4ob|;5/j&7vԧY1H.F\\x"%Xt}Ig-?S <6mVW\D۰5Ԧ]eM hg_۔*052G~7̛ÑtKHi\@Y?Gb`Ib=hp`fD̙E̫4Xnqdɩo/m/3*K]ǃp[XJ3i0Y-|VMHsB@Z5G解 zT`Bõ! FߗkN*-=HJUVeh\;5b ;R1 R u oxa|;8/n4vq Mf[c=uچ{°7%XeL-V>d2up)Wȃ5ͩUx}Ӫ^!xM1$uau]'_l+J +vުgRt!R ϝB5Sgf h3s MQc%-:x )wXC3JU?y>UT.2l x&]. M;{~9yL>(q,t{u��U7϶MЁe-l9cJ~q|'ў j<rF7g4[-xVaA6gj�O*7*0f@QE:>XcSJ7t0M/_]+P^{T)81F\-)z u֞l i ,ڧ;4==$j0 BaUu!-BMhf FtL-Sm?Y}D}P5Rrwsqk^>,!BJ|gYbXALX`I{%j𝞑j.z:d0)>,z"6x*\J?K[坾?)#˧  X-q%Gv>_4g>Q@"ɘH( ;y LݡF&ݣuiLjW"4"\;mø$zGj`l~c$ =OOƤh D^q?s*XJEg o y8q)1wP/p:Y ]ރMZ񱲶)VH\5xQX6?Ҟ=oHМ]bǬtPm Jq/ӂAʊsPwoF00`t6`  Xx_QsM60e.P;3pjHm~8& V1Ulq9Cn QnP{<Fbo$&e_c;RL�i͋Z~&IսLIr[a x= Nx_WEp|J1hvobF95-Aj8 fJ2p>ze@l|֌D0+0,`x7MK�ÏCo5Å/e8~LlEI=:w{4Z_cXE x<mJ1s=B91ʷ<+ʟBCpCCi9J+,)Q竫hzySs4QwRrrc xKRhJ+Qxvj#ݤ<*mrp؞7ъV̈́j dH]H*k0Rrkϸ9uW ?1\\ӈG[ cDӻ]qI~u3mqno4a>Cldca6XU80݆!+xHX!+t mYKNTRM]">*mW"9.I핓IX%l_%DiB398[ԪF1k3sMBq:=Z&|Z vD^A oGWMô*,BsƅmV RU'J' ؛E=Ցݿ : �]c=Yyv k8m5`?8T \-%pu])殕:쪾$Ԃݗ8,ΠT�#<p™WB$?R֔?:LexlRFq>*j|I!9~{`cг\{,KtA 6d2 'zڭgȵ~ 6D/[hpz*?^ 3YXe-{faEm>_(K̙e}4 _KM;AQ/j%J(\K-4kX Z>ý[KoNc3ivVk rgC2"A$re޿E<a Bͧ -$/]l^"]\"9F4\Ã[[W,9V׀"TwC5>,kA>@Ґj c~%XϘNJB �Y*(#X^XP3K48 Fb1jzj-Sj QX>8p,sg"s&w.rV1̴'lhLwF{>{6MN0``LRf*Jnq)W15`j_52(1o6bv#6"6~L Rnt~*Kn'e*8r\ t󒂟X L f/܀-ˑ~\آ_@]؟6?"y ]8n#UF>7ŇvT4oHY fTEY>K-Sw5zr~?:W6anԆߺjIH-Ӧ~.zqd@WTP\Ǔ !AeŸ^D<7-WsّIS47oA[.n{w;.׀eWT.ض_PVc yk a4F /4t ?;P`u']oԨEM%$%2OBW*VhX« H6uũ<ŕ11b.L^i}(:Ot*4fROoۮT,2hYLK#$~ `GM4ښN֡O/צǮӟ-W꿙S1�Oo6 #xqcz3^;QkE07sjZ/Xa1]x4$"Lg9U135؛,ev ozaHUN7sEz"ڴda v/HP_1=ϽzZi ",L<X!!đMEN]_8uc #vPPdHX ֙,<Pbc~G=Rd6bC +n(klGliG|*5F}sl-Eg̭*`[-眞ENqQEQ8Iοɾ+8N)YwA8PCe{lMD֞kr=[0y\W@41 1|=kY]Z{�*{�mؐ2_MQIo嶾l'(+_BG|_Imׯ;bs2_ ?TX|R="N1NF ݛoA`*[tϱJ{"&9#GdҐ?{j&` ms"3wY(oTPew{(zq\226c5g\t8w1' ŃAdI}ğ~9?2J:)٫<'2ϭ2? E֑O  ʭ_-=p/xJl"FcDA]Ɖq&xD0�qTgsBbZ ;b F/c!RP$}mV̿˵9trInO#ߞYu u%(~ *@[ "GQAu@*+>/]XQ>l֦K568@x2Z|.H{Tv >o`$s3=87FZ^omlpQ^Ӿfȥ nU+Qo_7R&DIS$ܙ |z1&~o9ʈӍꁖEI~71%k_7[(O\,w Kr)%xtl_xʏ]PUa 躦j8 Ad*pue}&FO C*#fUZFC[SO "Re$h4LeyPX͒_簿 nؒ>I"1u%i-QUr*'g25|'hB-SXx[:5)Տ(sT"E7RFse$!(E#L=f*Ck,/ţھwʁ[+F\E]ɍ,~ NGw"Y7io NZ~ ?OyKf _!R.MҔ>�X+ 'cوıX)bs<i3eL#) ( KՊHN^͈ S`j[/!#ӚO]Tol r]J.4ѥ>ǥM4ڸd$+4<cbo~DDj1}͸+:zx5OAeP? Y%>/I{>xjܥBL[/)3?h-Ke@`6Dy"T#˭ybΩ=-L% u/PGB';t\V>H/.L=j̰&5>�L%mt3Tؠ̠_ahMfFpMgZOZ�#<i.3ॼ5uZYX+0j~ԓ=bVu 4Ә2<8|]< {~c̕;(=:o!v(bJ1<k9裋"NVw>H71ps><!$f$dKzjb=>&r*FxFH\m7)^(GŸe>!ra&*`r+Jl`S*PضPۦe.r)G�DV]>,`q=(qGTBR<N %%MXO,Jq3 (0Pw=ΜSmh7^,uN{@ԋگgsFZAoKR}a+Lm2+dl8?\e[кɧ n)Cv4M8L u^U6WawԔ44n]T巟+ l]Qa2oa&v=)|1M@n@p0 8 yԟ}Xeuf[Fun(I5RzFf�$/Xs*nHZi>P)6y-dž; >fQb)[E Hp51x` I]5EQϽ =g 9-TVψ,P ǜ (^ajELIͧ6.wJ9hhUGIaOM>R; ӿ%Dʱ1 CJv:a3]i5S*k,f_<3B WX\D_}Q9zj!'zvg/Qw;Ur(VJI(jv!D& <Z)vgs绔;fh`_#p+5-ހoz]ϴW,OeХ)_<2VBpf~nw(Ab75f R)srw(Q 3`A\Y�hP|<>T[rr'k5bVOhk V75ợ8ٌXw)SպGxZ<\kGIqе,i{Oƺ};WgDt>5 GA%t9 NAT12*>kh],=>A2*Uz31m} (6Ead sG>F`m�&BoqS?OsU0R6Ѥ2f%XLix3 nwS.bJ 7:N~='=ޖ>jE+fA:XSoΡG JHerDN(bd5?7d$Qw46=6?3 ҍ.D.: r H8)]?!Q=ASfB1W ;wGo_x;�ONfPmy�#dtؾS]=ؗ~�fm"}-gG:r르eiurCDswۚ+;Z#AVE� ~O^CkWdN>v-:˧&�w<ɻ!Ɩ2g{Љ7?t=+1vECru :df7|&=9kU IyjZ?X]Y >dI:AשrC0<&pN:; p[%vfbs㬙,nZL+cH:Aeg 9WV\( >|Bh(0\B )f<Цھ=;} S @Rq2 o/ 9Mwf`N~l5|CoݫdѤ/r$~# LLA %)GADw}+e^}.ztglsW86cS*5x(/=)[t1К\>] -֋HaPK9)êEtx5 a R',hn@Up 믗aYo -D>䭱_tif!-t.ʚ2]J ! d :˯nfGu&d?4Of(Ų5^E-"yY -oe}05^{AןVsu9x틏nZޮ/%W:K$bs.o{Qxn>R%W>L>Svuk^崕'8$_dmIG?8)3 d,jqR/иԩ:-G=ȦȰvd 45 7۠EZu )= ~_ߧ Dw6F͸7:xrTy` wbںg*[̅v,Q9.Ǻڥ2:k FIqN[X}WY?iF_o֑›,kbGc_֓ f]Y :Ǘ)}$3yiK"Ӎ7۫\IL)1|rBMwf(l̴%GvBhdL!`˩TH8]WoJL^6ڪCߪ<Ė'| 1{:l؛Ɏ”yG }` *_oDg%_z >IÉF,/"J~+oM{[,o ^M"޺k.ҽk #ψ$ J 9),Z2lٙ'qea2Kj"Eer2x@p:ShFln(wCX SVSDwF@شA6d*b}3Í^g<E=V՘iZ~=7G15A0hT~qgiŢ.hŚQ-ΰO�5bԀN5P/n'ND}Y7'bn$bYeGJL-顜 Β:ZφU{NOH2&i$Wz",ξLYx*̔UU a[u-o274�l5Š,QYJ!:kKR&^ȅ!4pפ #\xʰٞsG>6ISA2oTP'>A?) oF1$S_rF:0X*C+%xLk$(9tN4/Dsuv/;oxc*¤1oBlN$0aQ7~HA md|TŶ,USĨe$[F^ &sea _t4@E6ppclP�SO@#dL(Ή{|A y:?#c|g-pc*z``ݖ5mӿx_,}kmfAH#�ԫM !&3+mN<>�:9wbR aqF.+ezH|1 'NGf܁x,]�Jz.̰ p-F -T>Jha.4FW }&$|:xT[ؒKmGp KfeR&uEr ITi? ½`2ca:+0z4m>(#| 9cKOjD:hEq(2k 4"HζdG#C1/L̊ ÂFLjyjJX &0b\g#!$Mik?{lfD<f:z2?pBVM*n։`~Ӿ(CQy-i3,a=+|em̗W a*vtSL & ,=dU*kyT,rBpMя!SEHi;u2"6մTֳsR8ʶ:='M<@(ZHc`QӮLǐ(�@.d!UI91F׊1aqF+r#`ACx{u=r\ŖtԴdbxY:\w20a܈3ތ;%C}UU r.Ҍ4#-URlZɾ`PA`p_uLjjc|kmIIR4 AT6t!: 8pi p:jVOb3uOȭ!%mLЗ]5.jp`Sޚ3R;tv˟hH)$aQ4m.z]%H_ }UۂLܨaމ>H[jjpZrj7ˊfA('pFb ԆV}P\nwb+Hx bQ>!#˩.Jw so{7tC׮vwH }1mr`tvO_$/? K8C[]F#5YyHț+ bv| W(<xP$6 _*D҄IP)$92ưpDk< 5 ƍHԐ15 Ra�nV%р @1]%Z0p�GfԌuW\ )h˛"VT3U$垂BPvT);iV ۶Dp~\czl- J= w+'ɯWi/xݕp-IS] fxwlf 6eA/amPDف0Il=-R鍀F%ER'GqZSxz? gvp{9Lz1L$B:HH}8 |tŷT]Zt{$Z}->3 ؚ_naSf߄ C�s%3L`:i׺Ća{;83e#y1VM4As:ѻcn+3"Q,^ U1zZ,:_5Pk%NXhۅ@AFxl`̝- ).S 1鰓vQPYexsxVa_(D\[ħ:Z,h45N[@\W"L)w}b$y>s+M_'jP畞 9/ҠEGj60rV,8. r(Dg9v+]o [tײ`5/^yd3qOiM;<5}2O٘^F6+!)ۥML;RLV8.O#7;t/>خI!65FZ#R.*=L._MURe<%od`@J])qjBUAg<%s<]MeCݟfU7vUK8$k%ć[?==O#(*e|\Mqe NW yNb:׿k}ge!Pk[LG" 9Tw[X5yroQopgQ[I<kviCɊ?"8&vDf;CSn(CNFAB*=p`dϕZ0R"dfE [mٹl)ejyD$7tve'gHbtiY_C\2ߕ*gMSV>u;ϢN`h@߻Or m#g:顸2ALPҕ߄xf;1_;Ҏ~#j4Z)VkDMOȣy:.*=0G϶%En$<5~A9z:1ׁ Ks@԰'"=vBK^AC["?K =9ys[[zk<keJ&ĊN'{Xh{J|⨲ 9js D9n?Οe WzxEOt<|N 2eMfhr^ҭ~4 h/7a>0J蛮kk^(\K|!Js/v'jEtA$#.b7TUVGA7ʱ#5k?JIx2EatE[* Kq嶇v؅UEV2c i᮳WQ/ur+?'^Zʇ{nJۯZ<Epۡ<F1~TzV3ـvoV\mUc֣YnZF+&ٽlnY)ÜbW)* igJ߰֟DSOߺ5.P@uu^7Ԥ DPMePZiRXۖN|/ +2RKxmmn|}t{\o Ӱ~CtXX焀6Za3wFܛUo6<Afp=ٳؕȸviO c^k>mٍғ6B3QYq5>̜X*9 6Lv# ש MҞ9CbWf^Z  n8Y bq{^nǍj,(Op%$G4A0 @,bCxf.sUl: 8^D ~'&<Jm(^7~#!�zHZF2V:~#I�Q*A/2ݠy9A{a*]mxj :tڔ_oܳE{ǞuYjhԈPVc??o4k|9Ss>74E=ɬËUg9-2792<A_k a 1enSCz<0YC3T_TaZWbU=ӺuC1J]x;C9oAP-_@6A,foA7.$Y=x[q7r σ9ČlM d*-#< :>xS@"ϻ_>lF]ZLml8y =hƫp2-4�V=&q;›i.yŠnx=!E\Zy:mƗU5#r[FG㞒 :΋=J|K6F<+" 7SwhdD:4|\i9}%f3*(134g[|;;56&&Mf .fx;q}w_NSRɈs}rjJh1<A(knwM2s-V7{L/dg9:q/~nͦ`;s|* RhtfC*QZY^'SVZJ- “^RguT@2x M w/v~A05ķ[&0䀫gH[%<c-DM>9PVG#T>+'ADN�"”_y0,P=s]uYo uz}C0d.pTn&`-\x¬?&-}lMNL(9C`"XNiys9Î 3bN4t Xw:|(.]ǹrߠRUl5%cebWj~f{cFm;<]?ߏ2f!Z= Qze} ƇUkRsP-(i,Kta*$LEz2'e(WPc4ϴ4VxXW!'G�Bc|V7luwD$94lvx<N&|ap܀I/@G{ӆ;Q6a]ǑEb9yKKu6Vm[<5| =8F?ɩ5;a]9 #ZkӪؚFY |P%N. $kdӱ=H @w Nc w*ا�O̰gEYfףmf�DZi/Amb&{Y}·Ϸ ϵ_C(-kiDx~7Xgx? aITXg-J' l (E{('׸)M~C2,Xnsًo�`s `f::%1Tp`rlZH`:1"TN-2C#ѻOw/ Hca Fu^@njAS9 ЉjqԮU7 # .Vtzѽa=񠵾81oRLɖׅTݴ־'=7tg*ccCFl e"%WS?>`7IynM{I<3I-4JVA?gGxi(Ed@�s }{g6D$fȥ()]&orBޥYqA CƴgmDT*A> FE23q 9\%{�� RL _璆(?UVzB/+ȅy.P'4[Eå@olϤdDUfrhS؜ 3e_MN)ĝwV?)K'XZ8EA�`@#?ڡ+|ۼ&8Ǽ(GOZ=C)lV CyN;Ҍ^uQ-G#$Z1_+|UZ4I#g w\ ZQ՚‰'l6ljїcB*+$2^݀C;k AFMr[#w"?JC(#ſnL^CqJ2|πWu΀Zԋ0ޓj+* !lR/UԿTd #niUId,Ui#jK_m \|Fѿd)";fSuXG4UdA :I.| QŌ6 w1[f)1G墤x` j7dk6қeZxAy{<炣y3DIg5 (}8}]Fuz暦PPxF?5c+!EɻQMpMu mN:$~#p(! YYzK r'1\S3 3'm9z!q8Z[Bēk$<0]e sWcS"εY Eobs.c7=ia(;a^ Aj= r0HK HJ\/N+})dm^¶Ѳغh.^zL#0_i@Iӭgbj@]D^ ŵ$J#~Ncd#䒦Nɩgٝ0q9\~HmsMďp0Z-ftΈB*lO=kdUe%hj e vdF/y1cT໒jW;7C�*pniok,EVFLA(0smGe JvmWSFbRY.7}1]#RCTs+}'dz,T 3 MQ"w|U *Z+>"*'/]:iA U6pDëY. ?o`D b<�@3pvλ+15w[`AΙ�k_@K'S4lĈ  Rl #~Nt摼hBe[E.ob yY|XV^e6Nt y7ȣ\}S `;)\X<xO^Avg@6NPO $�[KHZٓȢ#eᏻ7KG x=m{1k\H{BSq0gGAGeB^!֥؝7K�XzhcBH4ɸܕVYwvkJ˓ g >!kǵb uc#րw;|!^/w)wbty(}`8t _(V'& W&ޔuC﫪D%(p]CX,.c|tcnb!9SP#ApUMhM+*OWO m wVkV%86>-G3ڀc_�ٻC4;Us0HҔF^�kBNqdž<a�h hb ,M[*q c)2) i㦏7WgH0L7RN5.I])P:ɠ߈M(k9!rxÈtpQ;:I+! %7ַq@H% l3RG[~9#  w̃>[xZ.A! n_~WhJ& Au*^yРGBA/] 0D8sӭ%%j)�uާ >Pl֯pjsy &*aWI*?K7 ہ"?P'Whc 3}!qN#*Pq,Gn ?ܙ�H;Z%N&qr MtPy >;{KȒ~ry<ag<zt ˾6?'&RRRA#ަuEkE^s3d7bQQ$J k-(H4h췊<w΃{P!<ŵ~qK>O}2 ?#au!2<�P~cEIt{9G3f%V/{GR _SʠVoAWX3/١lZmwgGݢ Ѣq?~HUwM-v&s;fxa(.jc"蔅 rJ*()P=ApJ$ wxx `be27=vzJ ]Wy.ߥ@y~]3Y`M,<?'`7JN'PoxkxYǴpk^RaiAkG&wYMTR3&Τ�$,T ݑw2oITVoXa�C+gw7<\ާ慅 �;I]Y$_CC ~cVR83ScFT%Ge&G5CpET<hϛ<"a$Ǯ!ɾkB pap Dxʴ0 pA=`zzmSUbگ\Hp<94ΑuE? ?m(0{ԻAٝҞA_㇡k0}6GsHWrǤ 4T7S{]O/XX\vB-Կ:5)`E HL[y'+djEsiJy> 7xԐvV� [emu\*pХJv:J4|Rh͹bn1C7G&�Kt}Įn;d/?99ϏN,C #`_arB7Y֗;1@ BDJ5(Mg:@}'jԆh(BG%K%05P˭{KZ\>14kq & h/X"&wujTzml]#PT{YBe0/8Pn c#l`.1XT*2;<V6]>~ɥK uo= Mب2pRI]`BE{ nxN%&G38<[kgZQ𦱣m|ށT5lTF./=:s, 7h$gDV_榖c<4qq=πکLPW;7q@a:N φ;0h; R,!;/¼:{xVY-+ciX"9H/"O!Dc?J ;5$#i~DWL5!c"ߝ[~/ȆknX= q9Q$C7ϴd! j3,/-O}"t$ӻ} j<ЖmmCTi[˭=7jlʤe%&WVgo_uFȈ+Jۧ?8C <. JWo%hǶ@)͍Efµ+Up#͔3H;e d<A7ItVHŘExJ0n|e<hqwͧɚӣM., NA en6kfq /L.#[tt4p1H#"g=⎿oi(k)D424"OC$5>J@.6 V}!<UI\D_Imq{^i"?t〲?n l+h!_ QW`YW\%Q<0\SfCaR\X Ee)+VՠuĿ[9-EoLh}_ݷroĩйn/S v>=vSA~Rʕq ~kU4 sZg"`$VxЦͶɤI.6uXy'z}*ìêV+aRR]7AHn4h%0A>5pC9KvWZ7sw>Ar"/PC)tB#?R>Fv3Wд{oϯl$)0FsXD(/ܣqBhP BaqƖ^V[AHNi7|[k!vgVqh)&d$2?.֭BK2GoA  loC|5:8_hPT}r_kE @O+mlgӱBh9õ/7y :_MD rVGO\څ2i#KΊ!ҟHhpʨ&GlrD.Ėj@^ ٪TGi_; r\PRՇ2zFxޯ1&ņsȘ{c@Lةf+t `"5NL~'eYQkʍ K,FœYj7Y,@2eDJ iN>A;Fhأ51 YmmЍB}4 ̄ڴ<b* 6$5Qѥv\u<QEb7!5\)̄M۱Qi*+ȜBg N4C=YJ7Å;,RH㈹6S|hK8j63g#P\M@ T/q Q;+ %f۲w{Ϫ8YMOjڸ endstream endobj 134 0 obj << /Length1 721 /Length2 14155 /Length3 0 /Length 14743 /Filter /FlateDecode >> stream xmcp&\-vضmIOlvctl۶;sV}ul]{uv9$^@Ff^ ;;3 Bhj`/n h�j@G� + lea 6wiklfeg tup2𻻻 1: 2S\-�s+[ @LIYGFQ @-mn&V�y+S ` 0u7'w:C ."cRۛ)wu 4ut�YLg/`fe 0ZX1K7{s�fnMC~�4A*bvn@g`gl_/ d\Ar)[#?;Ͻ(�ͬSGng7dUV;-ao`feoPsGKcgV6 }cWg+O3փ_翖E:x0pqXx�,l\�vv3߂3/ 4[]r0 Nk (A<}j5`iftpw@8d"s-sLj�D8%4X{)!|S)x4ĵ3UuX[ V`=u*@bQ2`/Ec@�ܣF +2z]A5,r<- dm_=P!|efY�i;=x6jU3wՆU]XcNaf<dFJ]WKw CZ9Ui+` P¡޶~RȘHE z\ٮ �x#4ҚH{ں  -TЇ\esSay\A˟'s$eU<=1qM|>:Zi,1P;4tO"/siÙY'\ChQeVV QFʎjOk i,؎ v.S>#N>avf] ˞yD2Bfwгӿ1߇7(?\Rhb|Ӟ;)/v}3 و0Oxh;Ss{Pyj@=,0;\ȧD?yS'+m2eRX FRuc*DJr ?@s5l U΢qA Rl\RK3Ƥc5~#UO9S7a{BbXj*ƭ&'3Y6$JwH5XݫʷGU˃j<ٚkkCjEA\P踮ib^}gGZs( &o5.BފXSCVsU6)XZiN6MX=O׵xWxɦX pkZgF,@!lÔZPU#S8bުmo oy-Q;gd]0ss~e9;F.;=2X8y>ozK<fb[ QM\ r8!jz'[M;x]X٩_63ܗɌYxjz   }ȗ<e1�# f� PHa0@4ľy x-ϕ7I0u#N]k<&+w}zd>;WH DŽh\?N+5!Ӈb:W&&LkzXaN.%1"}z)fI=©1FQ|)Yp0qTAnT(KĤV/1d00zo9 LhSU)ptx U\؅"ˈIPJ:6rAI#_1?FO~c_l~>B1^f-6ćIhPk$ j6L@?Y=?|6=vK<L]^ܺ,b]#G@j%,R2tu-t{9?LET*'բr^mD^9`&91 *OT9' 1�QS;=e!-LYPd 33Y:Moq*,+`7Fw;[TgӘG $b6S16ꀪ:{c 4˰N ?_\[P:QnճZva[2E ?f2$*w#ҽNwV(d֤%=~Nn4F Nޙ=zT̼pL]99G;}WR|yIjp$*6tidNF 4 chh37&@STlN ѩF0B)H{⍳Tx \e R*;AW[Li](@\ h'I9ywYeKx6 կ{0(.MV bA݉-1>nݯ%8 Oίa_CtW!,σ(T+4u݂26Zyľ|TD 6EEXHJ{+r0@<m6KW͡zZ6 y|[*M:^JR8p!#^oIyuõx0c^/H;(TwmwPr)Ul}^k|5SEVop/+فItZ!߿<VsOX"H@+UC^F@:#$*\Tyo<)AЧ3,qyxէ~84PW6mH IR40vG#e[,@ޙ֚k"9 g3azy\S 0^۠3O(Q#)pn&C?vPn4Ь=SUhb^s; + 8[H=ZkH!ҩ\- 43 K9|t??p|.G1<d1e!:? li@6aߏ"՝oŞScR_5(T&kW39>dHƾaCoK %A6zi}G8uE8P#|<HzÒb#@NuIc:៬=.aRy:.!l$3I\G%N?;!/cJnj; &Uf `i9ZL яr_Ðk`P ڛsYF/!Cls&nj\mpdB\ȡyz|s|3:C ŝ1bb:!v0BCeB9^MǸ:<Eď \"U#kq@V+j38A[o0]T[93@u2Jmݟ|[>hKxapizB,?FH6SDȉD:-H qM,1q z!Z܂ o=LꨝS_h/O9٤;1.m(>i2`{W:ϐר_yTm2NcT'+K5.?GtXcg> 5><c:lN^t#[,ϊD%n3$* AJYĔuԑƈ@d[8L`X#h?Vk#F1`B^딚č4wZcmk4xk:{*84˭!L,ۺ A(<ߪ6Z_ KQrz\CՎoTtD_vZ'_xL %lxH).UR+&H5V~پ;'B=;T܂�eq 8=$XFo0pᱝrCqp+U^׽ƣH/-s܂ɿ?N֟ɗioɁtK|2QCٜ{j6tդrkYprc]qa,.ry?E[^,`c-sa *ϥ}&*aAWpwã5 A# c)R:JnU*xyE.a'_"$8e[ꂂG|GmJٓ&hXu=+ڇK>:HY_-C?~0';3ҲyV7lX MtHz,پ2U_7`P,pUHįJw�Dif;# q7t{^'A^e_LQ uӵ(2xotUbbtNS @}-XPp;]8ٯYk)MK 4,c-2h'kXmU[j n ^! 2ȾFA>oѶL o]/& _n*B"-8^iJ6ޥEw5YbdPIA%+Z~T^Sleٿ"@2F2\S2}ڋ|N9Z 玔-<]^G:[7K  +MFpeIp|;T'9W'62C M 觽6g_/풮bO9VsY%oyˆ. lj .I+Z ߃䷩$s+[5ni$v';y[ d:yVKH . ?wqBmBD6?h^xH!SQ~2+Z7W$jܢq|~dң*"ѹhO@- m>ĬsM䰛Ѵ-VjnXEM'<tV\-qT/pl_=gٕ}]\"|[ѱۮ MQ6?QѳZ#Tj¸|K. 'p.l TO}f.j 7)͙SM04%{rP=Y/'&%vXNDx~I;mg/�<FPN=Aaitݦ F>"kһ?qpTv:26"O0RaKQ_LL>,n_ecS{ NT g,vV�b"4ށ0 aoYDs?i VS`^: 1x%7}|t3UJGn Qg"l:<i!SIQ+=*zձ \~t �svͯ zmXt\r8cE;@KɛywāAʀ`No`\%m{qYkvzccyDD*kBLT %X b.`[)A˪xd+Bw#2u.aƹx-pWW֓5S" 3AV.VͺH+RU8ΒSIbpOdI-#XO'UhV*'1  Y BJi(2RT׋ Kn /NDSn/od\1A1!>:fdtʥH֦,C)Kw%`LaYQ(?]lqcrھ~{� @}n1wwS*D!T{DvCkF$ٻo"/\\so;p1aLkQ̐4J7O<q ^6# ?Ln!JnL'4 Htfe ̧A`DS0dt3�XC)K3^ȶD\\[Q^}iC#E+x3;{<c&E+>=ɑ7՜G *e\a*Aar Jc.(PFV@y$ :ʸ F8�pq>GsEpW}k_ |PRҒof#!3 r V.6HeU}͑~Yb~j-NZ )J5{$ d!e:i8 H9X4 s@9>O$VZv?[ړ>wبkNg o&׵*JMH0L0d\EѵEm&�no:e3V9e sA488:\DK<c|i\9=kR&t_$w Hڨgkv;Kh3.Z `̧o,("'8Lv?A|ԁhD#cDhx(5?*")swX[OۡԪrwD,p0j0S q zqg«+My48tf#"b7 rga/kA]8_B*:QS7_f Vrř}s1ٍBZP)dLe+YN"2uEľW�o>M>ڋ/E.CՂe1 ৽rdi0jM&mN|v7?#hU~ 7-b9e| 0}DklѶQ5з)dpE͸2kv SU-~94+FveUj:Z v:"aվSruьh8gxIxPH}~uVe9kE axI'Ahoj͢J֔r PX^9I ;[($Caqَ3K@zc30YVN lvN̖FuٯG !Kq<(hBk1pm2KÍS,$V6}>xVr55?s_�{wPVqÏB Kyk2l;;P}P*KƋPgy>WXr~t?^W-WeR>P\9Nߣ -Y_ uPt[BGԴƕAvz('8z"2̮SذB慱zv܂cZ;=2 Xۯx A4Vv DZv QZv նעfG)adp]y՟j0o9$H̎18JHdbJ�]6\VJ. )>-u ŪυPF<x"? EAH 0=?y M{&~Ű9c\Q ^ 6 tgzN:ZK*p0TK^~gyEPLc˫lDh(Y:�4nlQ:WfǼ()`6g\1S0bP'* ^^Q8ϣBz9چ")8d%B#)X%i"/"b@>ݦz=AO5zuEm<O<%鶙c=qr05^T٥Ea;Y܎b̌\:1~ʒ`r@+׉ @5ݒy�58v҈B%fyTy?OzpOr>cw=ޏBK >O0JV4>sf$~Yt-+8IOy6AJh@`? cIriF#_LC73vY0])R7?g\v6<Mީ΋g`(3 9 }~CXҬRpWTvs cb\_yZo<"CbҋCCp7>ۭOSqy�=_YkyO*g|g]MG .]{q4-fzjoDxQ:HPꑔZZgLbjAyIuʶRpU 2G mMƆ5Yp3{WWy&!>TMҕ4:|#h} f`ŠHiFE.ǿYjĴ 5amo|WݵxWQ3{cJ76yC[)"fpt.�t$Jdv GW7}G$f\ړ0<h(+#0[J+1cf ~eT |-#QryapLt h+ژn0w25&f܎kلվʿK < Zb�%Ow$aF- n]oP)d` i,LaL~Z{cXr^6t.I@y<r*M3)`'ⷮ v!6GmCQ܄C )0y2FvO@ܸm0Hil ohu] Gq9&\_".Lt0ZSHtq{ e2TAbLt}Nmj'JS.P^(MH8sm-$h2W~aL'MY�a Œ2g_SMY 3U_G00N qۭ7Τc$ OT(b9hC?7[L8fjrZ[Rw ,K~g,v|Uo[yFPa12Uc-PˆP=]0CI. #[#8J12MKd07|,<|":"jcgStGmLߌ"pMdf}~Z""*cJ(=�>&Zsff Z ]L QQ%M]wڎR5ՙDQ7:m�s! M!EP SFXXDݕ}% ,Cfb],H ZZئ wCMaSq[\H6OcWv>'K5FI=ΥFj<+^ީe7EKQI\/F)Z s0h?_ISFni&70a8vfMxL!wyuy_^hS7b 6 %$O+q>L_~׋c Hv"1E¸tSLYǟ}Vyz TbC2? h=ZaG;o1jrCziG㰫?|N 隻8 \U#["m<j7r<lq56 %PUM"3Q?z}*+SRXI <B"%<-8lJc,?i#i:~Z'O1japw$X!s`ECNP:^7m^@z*yXb(i \!!m_f`݅9YqM7c:kGD1UcBs*8kTHMteG'v-,5nk|GR L:3WNY.XCׂyp^ >S2&˙>t >{T`f|8*We|GWTz-B2Uh! ~V`NeXKqBUH,gmoMe?,Szky6<Co lSJ lF6m|Pќ#%*?l#K4 x0(1E%*Mޮ9/z[RHmLHyxi NmMreJ΁-8,r tʱgV0Oif�0  Dln7TvKPY'{1/fv{"I]qN rw3sh#4g}lJ DS<+ UMh]!Y)2Tw\@ǒ<: }UgNn*;MT�tS]@"ͳq+0pMOP}>̬kT|,A*"5K(إynd@1S/pr7"RJ%_ 7%MR{'EIBǷجr.`bR/)9o�ntc`ؽ9'b* z.UwU iydJϷ$4AR5u}'Vsvwz0]9D%u, J-+4yT{v4Ϫ) F*SzJFp^%۾T砰^?]ιq cKw+ M#y!HPe BUSni!^=ݺFH0i5}F7OMI ֒ZHIG-yR+.hge)֯]O"�>~ค*+(5o+Wt<6w-^/)AAZ*+i` ch.凳b^<Ay5׈ �H>8C#vݫ"B)Gvj3Ij.:/Te&tX[l['xW"S׏2B1cH["Og*D3bUJAm;op B or&d=#:džc_Be_>mb :dx`?fm ?Tj3pS1|m +1ZCp֡9ː}bWl2pbŗ1ȏ\61譜dp|�ͬ38 7UrAy<k= (i*)jFBYdŠ-Kyΰ=­$0SHh $Z@2^ Jo7wF, EVTÁ9  yhMseV4i,-]udA>}Y/,ʬ{\)='Ldߋ#jM492zT(%T 뜓v&~r ˡXvCpBr1Lꖵ]nM}06^+$*T_xHlI> znHe >^G: o.y*i&ﰷ'BGV\ Ke 0mGJ r(f>Q~h$mBYM*4rՍHέm ~yI^|HRed/< !6b&6eWoLMGq۾i77.@C%=a~ \KB&&hlqp -TtɈ]UEpSL<w.la^:;_ ڃ +6A2ecZ9X|-)̴HEyHDWeMcR=]p5q,0%J&j" 98yy9gml! 'u:w/;4hg�O__oг*h*b;ҹB Ԧ*iKT~΄AV:T%&B`jE0+6)ݩ0Ě#ä-FL`*SX(=RS5+JI%Qʚm%_qjb""A'㷀S$u&Ե$ G\AD8d=)T�*go}xֵᦂĵb2 O.�VD1LzB)U\ w1TTrf=rkЦЉYۏX.#; d> lRMY'3dNzp33^1I8RtQOj?/6Яq"%#F q1X Pj'7<"EH愿!ܡ膠~1A*3l~6'&8z+\eʷ`l㰨`AU`T?E(\PĨ p³⽚RhXN ' ?Lj.LS#[uQ~P;:G|$,\h+y ̝{b#cة9$y54b`XE+zߏ@4s=3pW |ӭ{1dz[xyr2;\Ac4盱o ĉ'<}2dv'ىd$0(̓fYõ؝:1THdqGaxw.7>II0/^t5Oje uo`;H]<�{!Punci'aHOk/E}́_|j? yF\x*;-6կ5t8JRmPA3aj$R/c/w& em:D#~ pw#Ǿe)7QkhX1JG(|z > k7V ,8+Sr&QD2 Gi d37;AS?+Z6fh@hl-!zqs"{Ty ݶ@U)АHH涵F2kB+.hxc >#%C3+S_O>ϙk>�Oz=xT0hNU�Z$ӭl~e@IW@ZޱCݛ;{L!MU=]n_gv 4Ɠ%VJX;.:0˹f)cG U`Su8:cyh!_]78D @OCAi ,n1ԷQ~$B#nT.\!4G5۰}fȼ;[;'>=' .8=lx+dVWhK8|mj{i-dP}rW>LÝ o\OK|HҬ }O-r4*/4*6O!aF!R?p,E~,+?gfef!ǿ :j #"t"pb)9Pel<Φ>tVFh ۸_'pM/na.$;gH =!?{x yK7{Se:Wd«%fvpXj1SfdT,^xAA\h={5tsY>]ӡl{H}}k @crYl0[MgzV(pֳJ[fCbTgrUCaGIl>;'4_I:"V!7Y0Vh?[[R|Q\%R< *R̬+IߔD|׀,‚v[?KiX2j4Gx@3 ,ߕ4\"fsr\&>3[iO<jGZ`y&_Vnw-G Bk 22÷qw-B$͡-1S;\5M2Rm]WXKJ`L}aQASpBRE 4� J>X^\<ٞM7MG\׽ٴ;*:tƞAyiV!W޺Nmơ3gixq۞/c*81ȒpS,9QɆoTD+ ӌ)- ?j͐g2} *xw_ช0w3MXR5y<^xgy,<^:-Jhr[ Hŭ6Es@EƣcqGFJ֘AUX7(6KQ2 т5%b>:B] 1R|: \\.ٹNX<<=t_qԆMc1Z3W3! Uhoך"űrK^㡰%(U,{r'bpgvL$,-(*H6w(S%DirUV,gTxKZR;̓)w (>oXbo;UY:,r4%>X!88QL\ȭ¼_W~Ò 1PG`i'%qe+n:ueۄ!@ש,0eq~=# �uAnEE#3Bz竽9ԨY}8} c?-Ь`/މf[vJ+]ƃMR3OlZӖNk[.P T,ŁtGAXVS1,D͇+@ROxgFC sII+} rSñGԍ7w[CJp"C endstream endobj 136 0 obj << /Length1 721 /Length2 10030 /Length3 0 /Length 10631 /Filter /FlateDecode >> stream xmyePݲ5mpww� ;݂K [psӽzuk]P8]5 @vfv6/Zh rK�:@K`�pP$!^ kW�? @�rh;;,l�Bb.n,n",4@ `$UTetZ�Y lfPu3Y�A@ ` wp[Ʌ`w_aVΎ�%iMqeM$$ l PvuZ8;k:&#{F[uwĂÝ 0 |[9x [AS+>�_@l7{{e3 N t(9Z�uG3Xf {/f}[uE TE:ߋ~_?ި#;?+K+*3w)K--A`k_/͜- SV5{{#?`\ 0`czOd, GOfv^6�3';ȵpsv]1,Y$ hh!j^/]4S 83eŸ<{i4dciNxIF2d.}%BaD;1CiDXBYi_Vi|M` wu횹0{R٩L,{iyg?<fXވ+K X9cVxa. vIM`bSXُ/ tM] C6_u\( Jg�b }wSV?U2So,5|ɩP~Xǂ4tFi=lͧ-9Au6> 'h(6ʂ@Ȓ]uHڲE'yRIDid=;,ZV![Ss jk(A8nI07}7HX>q!Cemn_J}E(:`Hd^g 9#RP,Tc[A#Y } .67dTmdTrćZRm4+0{z]Aw(l9qIgS7s3 @<@# oP2wq#MǓ/gtW3Oft3 0BuPʂxc/i]nQl~|HT^qltΏ6i`j˂هz%njD=Z*+,;P 9(+Q@PJn ;?u`UWF^щ}EX?H$NVOܿ?ŝT)J>3ݝ* D\Q.@.M{:Y7FD)w2b\/{�vO ʦIݠQ<Dx(G\RiL7:SջۙҖc11՛GOCW+_?=?0 v#vL`6a81s5f>Y?ЛbN1kVrUo p[Uhz=}G\<L׭ET=jm#> f6}{88GG{=lkyD;< zV ؚ7<|*xU 󡐟&+L&I(e9AM i(\!JYgmq8x+}.wB#fMwLqCOk B$ʥw1'7ṿؖҕg8>N7*btZ13$Y+/yM᱾ʓ~ 4 B%/Bar >|fxs<<CXl{8+'~{D&;eyKe4u&3sKKDTڄmRCjr>v=n�n=ŬN6h O,6Q$Ɵ`SRibyފ|>:굍GXȴ8DELyEh7O¨hOu`X^ush}`8'vq(ETW�;Dm_wӏƾlbP)t]Њ9 z$נ[:Αٰީ0X2=|[_ቯFͲ .1<<D>_ae+Ipx#kT<[F(tL z)fEg)>TٴKeM%zţYzG&p MࣈiֳD^rM$otgJWK?@8>`[->:R �S4daCΔ aډa:\g\hCL@2q g1Z˕G}pIYz@=t575^:Q͟h}@oucnDuZ?(jzTJ1M9̽Q+X/lepqɝMizfr-y_hhh$j6rn .TF hBR xXj4oiK? 1V=Y:p�zo #*}m(߈O�H�]g* ^gzt\vcŸ/ s7 bbFhbz:;1"Tq8fwA:n*&"`kӑɻja뫶%VgWk}+  oP1i V-71B�  T=1_-J>?`T)3W; +Pd+:!,OxßONNQѝCz+GhUESH]^#Z(bT4oY#&�)^& pt(/`xx�%y }*q:k$ DٓwFM*4bjWE`iYC ]̪h^EfVۗi_JP!xXΧӭojĎD@ur :2GLU@nR`]GLӟewŗpdU^X,Zz9P]; ?11sO> /HiVmW]ó:Q2~,DV"UEml~iDQIJܱB5=F+b4ЊM2-nec)hv-'5i=d$&lB)zy3bNeD=c 8I)?[b0 LIk,좵|ғ ?&X7vGG`"C4$;XZ_Mrp py,4<3$^Gi�>]i0l#2qZ??>I+n͙^dUÁG Lhq Nj%V_]t !kk'j<A%+]tD֟d7uzN_y膿LEvrha4w6ۯT KZj~8J&v}x4Z*RRItP,r y6QJas'2\[C~:hDpC!=C X?a)}٦!ϒkcjhBjuϿfTdC}`ԷÁ٘R+D uA9@Q&|Xy ">{{QΜ Oݏd+;"F0Ǝє -LYhҀU-oepLtΐ XY>|buzLkOIѫ0o=TʹX3kv#p_u7q-A�NQDN Sv[y9֤ϲϦJ4\'hU~2Zק T`w&l⨶�Y˝3]C:pD/_ear7qbÚ,̪3<bo;T"-x6'?G 29~ϳ`S QG+Nx+ p-&a,gn@`r8"'?:;5O M <Y�sͤ #P۝%IZt珱"Dۆ5h% ՍV0'u~Ap}+ov+$S a!.h]5䴕TɆQ7Spo>6LB&n3Y6~~*g/5 $?{O"+x'lί>Q(Qr#"fRXI{vkGn.(\:![kYDCwX|U!ef3XQ󾕉ћpi9_j/.gSntFX}=UW1m1 u"f[GԠ9[^<x7A0A%LˑXr$OM+{rO`pʬgN LqsXsOAOII$Upo!?))|'ڔ.ם:C{MOvg+*cG&b|ۀ &ݒ":TǺ(č[I6|B<dXc9f.4G Wį-'͍y6E_с]&=&OR# cV5o#ꄯ^+GmO]B|5@2ͦ\+|G.>Ed#*},Sgr{'lw툯iR$=|5wHVgsG-)_*i2F(OdnKx\8< v!%r$R!FYB9Eï&FW *o:.}6ٌF <y b+,y7  DHTP f_Ii虑mF@uW*b*q|TSRHt*||A>dz0d"X][[And.UCqLCB~F>pb-Y~a=r?qc4){z=86{ԒQГ,01H+7K=LXEΔEBn&\o̯무~Jb[G{V<N)|YbmD E\~(HP @\&{&blͥ00&K1|V+V,d._fА }pyL'PgeV2qrJ{sΔ\QgJ)6rlr^oٌHG r `qtLYP\=~Tb�|R%㩒E& >O)~X]uBʺ]5]sij9S*򚗤z<Lqz7>`)GoO1lҵ\6N^ E$-,.JGsYZϔ�c{1禋ςqEw'} ŮFPcy|ZuR:k`CS∖rn/[.$=8}4 g`IP ̰ bCbwr- '?JFw9 EC̩]tvr%JJ5C~ъ"1=-AXs;-x+Z4Tc  _I0_$FOHz3Ҥ$*\9-)[a7iR=kd&!"f<}ZRL92l`Ib^*Ɗ8th*{b(Nu~n80@jŵ},6@)BڶU3 It%=߈Tp}^d# >v dȁJy;j$$Jx0hAR6*% =D$ֲiӚx#=e)Zb#^!_1W7h18M6%-D RQfu#9 pK'OmsS֗>PErd{M�!9 s:S F: *>...:;Bn#_|.@>W2 ˆKE�QD5e, z͠]PV< T8aY|lt.w:-{As!f$>ǫP";Y1%yą 'v#A馤Iv h RwJj@6MVtE~6c3OCH}|x(wì+*nSQ/2&jTВǻ|l/^d(57ԦBe!ST |P(dtP9_2[T_R1kNֱTuOP[_۬x< kRX-_Gz yFS ^zCDa FN#SP9gX+НYWV]\G6X0N=Q4Yy?{*ujmFbj:ԼtLהxAZ*TCZY8!BMC lc2:ô TdMf#wB0?8_p+O8\ev\]%3괖؈\Qq>#FruhJEϓ>{Xp>3pj#q<Ij#U!-yCkSUK:fhyz#%oշ+~\"]y #3(\W붹[eYr'LuTP/ۋ1<~�dC0h꿁cH VhQg%v܇Ndrbm:Ȍ*D≙#AQ g:V(Sk܍Y·J_le1M@kº{P2GcG2J[-PFdgQ&ZF_l<x3d�[22q(JC}[ dRh D+FY/7qXi No0q(.NexxQ! ;mÍz%ܬ穣jf\806^vG.e O}ē4)Wk -rxN<2aav%I"�ꦟ𨆻跛#ш*r}ik/С2m= H*|U(@]Ȣ_3ꗤxe_o8 ![nu݋Rlic;NJ?uzՐlnׄDclR}Ц|q\Im'dVD5Nߔ?fȉD^ghbGOՋz*hR0͓K7yK�=T[ujQTr.=P0gZ>*0L|Pxʑ~}qTv&\z:]w JbO3=MYV~WKk�vG3+Ji)?qs Txdo_TB[weV3Q=ؽ\-YatWZw umDo"qJ]2<M)ӳ۴.DO|Ka40jJ`\]ǺYzCk2XG)9f1ܠy3+VP nzvxEG6bh7,xNfwZn8'ym$~ v QFjMe=?:MUN^_8Cf'y$ނ3MBh[xj/ot5DӲE |O0!~h ;,* \U%aL@W2Dj /YASIT)AG磣I{z]�t\@Ca18ihwk*tΒ>-*`v_=@׮,o[qWZRf['6B$(SԆ8xh1$a L8|2Y o11)dK2嵄P꿓Z%zz0[_l ߟ z]Y73J3) g-FZ6" WwSA *Dd)U]$B#wvGЧDk/',UP] Gܷ$Am-*}-,xc478Ӌ gTyeק28r7NoCM:WYWخDTj;tG"9.N#4|Hɞ H"+ck/jD[,]bFMQֶF 0-D?i5*3<)x%jG'AA`4ov{t4+mX׶S“q,6*޹_-;Jٱm]<v:0\"|pj ~m$1J+09.1GۨGK?1&WNK\ċϪI>Ǯ +vqԋ̛2+]0TD+d�rʾH*T[sip|m>w˘i/e"fS 5W4+Xfs?E3I\;'%q`Y)CeߔFzG)jʎ`9=f?IģqFZ'^jh!h>RF�,qc"ŔBy'y_cD]5Ӭe#<6FI(? Q T}"A"MzJ(4w⎮P?NhW'-:c#{#{~<@r%$:g[_Cͻp? uCM^4,R ,Ld"so~7:~0*Qs^(8ht_TD[0QS=Y%5:פbG=ђEUh}~(d++^,]6># /-Xn0a0}yB}eӨM,&jUG'4d zHc4o*=ro] 3S*^hU(N^H@n;t+{i CzPn5u,z u(hLߩb$6ڙ T][p_'3w6 h)kz. حQ^[88Cw,iY &;v>z 2nI Z8Kʆ]F)ҮIh*\vpNJ4{&, qi];iֵiG8ÜuJTZ(r {ډ t'vnho jImwچ4WesQזV5}ą - G[jOu-2N驋7ׄ"vd-lnVQ~<\~DEH}8NJ*d6>32dBh_A@g]Kk[pc뗷w6^ʼn򎙲_5Πy,_D!(JIјRQ}޸wMwTR^jGȸ Swv]BBA^ Qu޲aX_(`GAE _ENh2+:0k5v ͂$s-׌fuCq�ę 3uP״| {T3ײ*Ϻ&8cśLidX*cӯ:0OXꏼFI>E/;N5ƒlAElgҀ@yOZ*jG-/AQQk:I{9˦XA4*M6m+oW1|i}k9wبC;Ƴ~KS[g).i"p{wkdLS¯g, L.R}{JjhNnV>RQ&#]:Rȹ\#E%:s-:kPZ0)Fi7H)1(W1B$P'm1`i$ - z-sB~ȯq9"&"] An�&鶃RbQV7jenrφ߬%;3Q-};f0y;/٢ {~ZUD:/.W~N'5Ld]2N[^8 5 lj)d Z`B@cT@=X-dqjq*.1M.){Z0a/}}7҃-{9^�+{G6 >9kۈцT endstream endobj 138 0 obj << /Length1 737 /Length2 31777 /Length3 0 /Length 32356 /Filter /FlateDecode >> stream xlcp߲7NlN2m۶'c۞m۶mĶN[חZݿ u:z**Ľ�FzX22G34�L̀L`�"vf�Jc F&6�u;k;W cs� ??N*@ 0D%�j� - ka uRLa�v&ɉغ  '*$ aPٚ�d%quvhhK/_(zX&&3_Iٚ#6q_?9 �&@]l�J;{g#@h nXXX{:?ٚ#=+N@E wvtG:M,\l3|Ya;[k=' j1[c; [3?9Wo 7x9#gG w#ʅ_RzJX݋@`bacp2sp]^r_߅aזy-S̖CN,3Lu_X \ީ۽ؑk"1MLF-Vo)օ_AC\:*ROfdUK;H0EA"oǮ.<JPQkig_x[ څ&10/ ?0jG9ĸg ~u!L(+ir_ H9nDREdh9 ;WW{s^-idBsbG(C8iPL74h_S(<{Fγ/]i;(}`ݳ(e>gfh,`bDc:'Rts* PO8'l�̓'a#Pȧ@`/*xH/t="=pU[٫#һHsSb5½P%P-qG>X҄W OeR-cI{Br�ֽc;\7"ızƏT:®~d`a| ~4W()я%|YsC"6o^mr4yt5Wڮ{q\7jԁ>Aq0|52 mY@~:!UO]b˱&V:f ^ 6cTV&a�={IGqRCuzta@Q+zNMxFt*2"(!VIaU;=JM=_)s1aD@N^xL2rDɦ[ݻK bmcú*e*q6X[  mQ8 c<v"?Sv*-_O<Ux  F+Px ْ۫mq赃jPh4~šмLߤ “1꒺|$4Yԉ~[F*MWc`S#Td E`/e-pBS#:glCǛ{!eG) $]e8n%_Cbb*=Ibdw5j QU:TCz&0e3]asg)XybcP5gtnݒY#,t=ȰKyu>]g=lD‡"?ku�e#G9$eretd<'0A0 T/% B7#3k1|(z}gp +m0p>oODZNWhh܏g Pe(]0: R0|9 Iw\I ToG~fʣ.  oUۮc%)cQj>mIs#?N-nqy& ﺖ <bPH]iW裯�Hoq}uUF.\,w&ߖi֢/<VQR\ ^ 9Vuv+ 8ߡimH)<zzQ}~h"eWd`"]!BsR-l6->Ř[X+s)L Š߄CyJxB5Xf'nnh|Pba\bB]{-h3< 70$!8arg@D(|}|-vØ qp]eJCd#D&G |`VC23  jq{Q֐jwx:(y]C>h8J >lְ6 <*Lk;Bp-[)~}yѓ4}i׍B\;2ȵA36g2m:m'U)O$9{R]<3C b�Ð~\;gڀf ]%R1eA $l^){*`M�ۅc{'܃kPm/Nl\\M_mokǣ<"aJ�}ѓ9>eߝ &֝לk[#͘寧l0)`ދ2aP:5, ѡ)@r6֟^%#e)<j61|ϖ埳3(țl;H׫~pq4cl!D3j71kseLmr0N~Tt-E!&Z'W#3Rw쑃MJ6nQܫWoGNnph)bRT vO{œbB(Cy}88 ÿ18k%z1"`d2+cAٚ3+QI$"0!7`tRw5b[,0!ʠ܌L D.Vڸ-FOp $d$.=&-{ds48]{g; ukјJ8*Mv;hmo?cgffbN+Z>IVyqN&@%\.'tBn3pШ|)#G)mtֻzFOΐԮ v>`Thps+u. gd9d4Z \[7zp; tOpN~a\{p*Um$_GDBReyޒk[?&f)&y�M,bYmzaT+13=IIE:�> ; 2e£‹^f%h&r=4)A*}g~S)^P!M_sS/%"u`Rq RV5k- rOJJ\ha+<M�S|k-: :<US%/؆3L-Kc><\7$Fc$p:˳\'Uߋ_G<G_'wClWOuQd"m45&xAOe-D Af)7UL3]c֍JvDt6ؐ*pd5*}ˏ,n[%ix ,mOQo}1sZZ+U7❞T }[|�S:X^9=j9]!Yrש A;YH|뗎هM JRzbϿիd&=IGGFS_ t*<ZnF "4P Tg˼P!7k$!Ƌ*p&2qAF;RܶzTﳠ'b:b] `�dc 3(˫ NaQtτה<!,w<26qPۡF]̗dN'bHV0Dtfy̆S9"tq/ϨhM!<"-0 ΨbF?E!\Y' cL0 E_Am ѿ`tǀoM0J$&:Ԇh|d]YopêL@vo _"toX6f$< c-QWz_ 5X,Eذ)}OoXB7Gi+RNނXq]5рN6r�Y7% m**NBj9Ui5NJ%Fajھ`R \?JZ-̼QZ:wt!Xry3,c-{M2L5qJ""( <Kr'w}^Ů/?浝Z J&[d$LV?\Ǧx,¥n`24c11x9)tfV"rWVe&2"^7$QRɖr(S Z}:Vv =H є0|:e{tHCޒZ<{RYĖ7q'>zT@0FtgZю,l`ڹ5 K Kvu,9^R}R^ܤb�QYH{&F8#Eeɇ$v 6R BdThVyynHK|TcL/l]q kђ;|1Ci+t qns^{kG޻*;Ύs|JO=򲍀Vk(r˨y=Bxms(�n1!Y2Y53J5h%9!D 8ϲb+r&e]].IΉS :Ob~y߫2!;@a|Zݽk ~\juۅ<7gc1E:dP9ǃUeU aOޙxتmViݱ5AdJxT<?&ap@nƽ пz:Sm" mRbx^ YC,dDgJ ;ᤉȕ1^ J!/ՄjhYa+nu }Ieh,!]6ؓI"jECQ7֞"W]ض ē=?>MQ/DGL=loLkjM}#SRORf(,Z8q2r׌Mr1tu;fQ u˜˒tIU ؅0ﯵHd~u2ghkJ^+4'X#(I/?0~Q >K{<ϭJxM "ty[Ёp!DP~pV ^YU[bmZrbEyOe&B4vR;F?.= W`z^6=̟*k#u Gn,n'2tn#>W)A/d<SÉDenWc0j[ 8µNW[/[}LUpm%xbi);[] `Dd|_kKI-8,i�Nғƃj|BmD{8_9Ԑ38n?(iiCI^{r580LCb�.$oY(ykM=Zu 22 J0J1(9n3=ngj  QJxі  (Zhqpb;o<XmE򪉸`R1O2/^d]D7I+h,[m)~ <B/8Šh^7Ͻ%-O#<Ls" Ē Dcq%*ǃOd(P'Z>C:н$fA0m<]BR"П89@l \~~rKY13wwJ/2u_?,nD~ -Q4N7>aѤ $n88t90P\~$MR) e!X$\O-h4`0DX4|y <if_,%)4d~{Y Xw&VK<Ac+?*)0|(z[%VFOi '-=Ai_QM?0IYv<`gJS:IQ:!Y[,BOfj'b3{AFn"}f2j‘GCJͻsTX@ ]λ>As(13:Y)]j)t6vcB{Mt/�9D@惓\֐oӋ-Υ?p[?;mܹɒֵSIeJiWG}h[@]9c+ITX-!D})|MRL#k( /K[GG-\Ϗf5 i#|&ݿTKy3xtYU/lA3l f9~%Pҡ BϭyEJ[~%Z0q F?U8z:]@/= v"ՉJ3Fjk{ӌC^n }^iPS!;~HY[wt~PRӘ^d{nR'].,z# 'VoQ_ >Š BR+^ʞ)U_+{q/Z'P rNA+SNHJQl~b Vܚ0$P`M[qXTj?A-#{I `2lwRQ_BȭvpF]fvĘ%zՄzJBeG @%#O\}M|IkX {'?TҲd;-f\|eGN Y6+H*7p֒^zu~ĥUǪHda {Cy <#tQ(Z)9^<Ar׌ϢB8IpCu9.c.+RYR}I }hjfϿ)\]3Xi3y~efO& q_ B"g|Mȍ|j't> (ƃS Ciǰ@Wkq#JaLA<Bֆ*a63\ lQaQO EM $N=55Ri6/ 'm\Gۖ.0D%jV!IҲ4gT`q᷃@{tF%ۮ.Pn5YM:=7[+՗DH ! P vo;b,(?[#$k3hpܞHRQ(αh  XFcq@Ee\Vt  &3HL\8`i[6%(1Km\ɹi٤Od.Lٽ<oJk f8XU [vP)$bdE"#O$ S*YO- Lހ9mhDnX p"20֕ o;8Bq+HռFxQiSabȼ\{ {e*4!p3򦼲%i~ksIUNN UNK<t㦲E#F4)(sϙP6Q^HerZ73G6RH0C$Ȟ�F".٠i Ļ btWc:t-;YKo~Jy() Ҩ/9МP߆Jl6uߞBXmһ_Ae' AVcbwP}o:#• tymw-P.ٛ*w-pΤ'sfGA HMy7crlJ']:xeNK.~VH}XWdOcSCy+wPI), :u<O`z&{"s!G~n34�[ѷ=꽅 aݓTiKi^Z{}Wv &;nHCKȌI5%ݠ]CmTL#Yɡe绉 ^Ztp 7\]ݜU)|b2p$J$:~J)F: u%}8[Kwcpu:[o$ ɮ-C1ys1E-6*ʋN=@<塨ؘe(XS}I\r-.,<+uw~8u{7*.j(Uұ]-+iq2sqd\>=#$F#.hu#I}I i ^?wRtr5;"@)=:=eN2VVvV>o>qߊB-ڎy DkugiVy+*1 / Z@$cYFԶ.S =nb>GBU5R\1!K}<5fˎ=,\:n[eZ6§'\G1y a-pQK,CEmM/Li3 xTVC6r:VU̡=8T gӚo?дhVbyg!dZ60dJ'l;S/IUͧ`~R}H4ݥ~veq>"dK׺ߔ.k>{D7n67uռb~bRQlN}-ɵi˜Ea>ڵa9|6L)tE[ WK!^{�Ե* 48r6"4Bx2F` *y�RC&fM$~d!IXxk_[ܼ6nT zG_Ϻ0!8z׉Gkfè7tI`o!MdvzI.ϭXY6)RƸaH69i ^C*\ 7(}| tRMxu7g�z/i_ _BHH?GoQSȱ9(>VBW,nGz$n)24XU#M6kC4EQrHxX;OowE^JS#73lim]NQy QF1Glck  ::׷U{x47l5ժ_sru%/&Pf'Qq J) hUm}'f t ˤt40 gOu{Xp+6MNBg>.@G S > ) q XڠL`c@ w%m[%zf^ǣ8CBBf $)=\43?bqKŘ d[c9" ;_LJ;8Rr<gY+HҾ-% cj߯dHR7{غe)&[i@i%VAuĒ0;\ }4Y󇣼` Cz̠  x <R: >0aE|sU=Sc_謻GR% / K݁9%󅵢?sYnmsY<D@:581ツ:Ms,up*‘QFbmU@0n¬.WrN`8q t.=ʕ, ȊAZ#BӻyւO6c`>td }*MiwЁ~6\80X<iz9;OEz51baƭ.a@^'Ai#̿{; BpPJV\CYs,KV*Ա 6faH2&ܶg?IKس6t|A NG~$t: BBuff=u jp̂ؔr_GU&4󓬼Jtx?{BʾI$|e"`zh䴩pHP'pߺp?MgN?e"t@i+]͠O4sR1%o|)]J84tKE*Yٝ~# A[+..'1]YR<:,�N<˺! -*akUpT8DeŴ.Ҽ]ވwķo7Z$ yXsHTD~]?8fF b"⼣S/D z"gyp"zmi{5On,R9>064S*!8dPs">j]wѠ#:O<T>?�R3�,[ _`Kfwrqj+t<ą;nv6ޅ9WI1֕85}k3K1+ة<H0=U֚b oT Crôd0B3z{?:Rߚxh>Ҥ'sm5:cLİ8ئY)@{dt]ԞjB%CHjQZv+:*R?ŤEb#Y3w^%\eaǐ# xp^/<=(0 Cb˃-1OS*4PCz3�;("uº1mp!;2Hۛ(ު9gj\/Ume!"mHgp0] "`fRY9d&O7[:"eHO>Va>eYj�j3о&؈so$yJ7lͲ|yD& bov`jbLZmK̶'jP2ǽi[{zq;4\ksj+ZEJg z [Fq|LDXVݸ;f$N___ .}882ݾ7T5Mz 9:&q \Yc#[IAN%UZo~o,vc!(;l-{Ծ >?o8*Ftc]> UhFt=cGҳ%g2UdG} iO;ӿ?guDž0"K]-Ed=cR~i%p_775~YVktH&F8$дg=.( pd/ V#Ս ?PM ep1!TQMY"N=G;Qu\s TΈjc" v+-,k,Inf.Iÿ́YKޓfn6*rtC\"$IVj,mFB~ )W)x uWacN1?JIs=VN#]բ#b1%.*PߐyXZ.t}Ɏf\QOq$1ᮦZrG gm.[v&iTdeTM3t9Ӯ:kH$%20X&yclvZx(j'-ȼ+/^anU'XI?o ¢|M!5tbfƨ!=% O;lj oĽ/Q!XFROuǦ V.KW bjGsS18_0dE\E6 $v/;VVe̿t#s\S {dX}Tit!c]MYzJ㸒m<$C)$4³R<U͓EpcFC~)}ƒ>/ $.>e~=CEǀ[ Tds{{<'j(U;!'}Zbz7ZJFk"neNn�px/焺P8a<FEz)zeJ 8o!S uuAb~suˋSKzȇf?]$;.A6GOT.μDGڨڭ*BW9c^Z̕I4H(~u T+߻G8,SVm ;<33eeN-1V z>)>73ّTc/#c '.A\dbIhMI8%º Ф]Y[b SR QNy9n̞@:|qQV+a::|rHJgI|b! jw pb2*WjFr\j0X~qb ;Ɍ/2 =H 6ΨUXvX$mQ&J[#|%5}@>gϖ$;~kfAj#߯ S=AKz.xMu:<"*xTh )w n<,jQܤ4jaH<!^:P$NcfmZN{Ȑ/R5}b"i![].ŚGTXC!�A2PefMǕM\S0-r1g K@\S?2季Z5+/RE ѭArD5}s"T!ܟr56^7NemV`Jܡ!9+֣Qsh"8ՍE-~:H(S0vF6}q,'T2U87hp;|,(c~z .Tk_]3 Y!G .IK>zŠ˥Ud< U?-?AZ/~B#a*ÓSA)B~5UnXP@u<i>;uŮH"HvQi/":?u? *ZO**HҬ!{@_b+1~QAAEH`Yp=D6F+lmP r3-a?x 7c?Nk8%Q|6,d/"oX@$?{K=Iy4dQeGj7 uVm;"`tM>,S8(7?ݗ~&d|`ATZ5E�q<[z$M'y�`u=0[#OXsXU.F~6vx4<NpU+kS$dM͖*!bd=,?ALn҂-ydB,TjZڛHAUzZ!nӛO.6ݔ�Cƫ F89*?*՟vӾ5 I)oJ_5 nmdc.\DS>ʸl;at .zƵxCjsth ,#l4,At ΍V8y@kzUI]٥߳0Ihb;{O78!vp~sɪ\wۯ= U*R_,.#KȚ+4ViB(r<r$2~u+Vk{@xC8ߌyggKoe13WےKh~e*ܜ9L;g/*d֤tf~-[)h$(`\auaFyc MPOFoUB`oMY)x2z(^W^PLݫƘ̲?Yъ ̿ ܯ4v#7i.,")(Ԛ^{8BtŐF ށ}{ )k NET=*#Xq>$GhW&k% jݷ|m-S=]C ׽b'XjE7?aRk ԙa;C\Ս\#kYͭkyAf\&c+6|ZU8Z37I1*|`pǽ;@c&5;;Sתi*;l'h#ՕR4WMJTѳI}є$Vp<MF&{(^O80+ UuvӨ͔izv$֘BGؽgsyp1mZ; b{&pFMmϣ>"rnεeMg".~^gp[5xVN[!4yT#Cr^cUI#hԡh{-i ofG.@7ě.]~V%RtR@ESjPGCAIeL7\$W_ߵ3cY #x wA(`E>Yr 0ĮIti- XI~~"޴g)ێψGp'̽Fz$vu@VbBOU*aԯh _,X]l>ۥPֿۍv?+8%hS|:yk=lwJK !$gGa |/V9ք͏ȪÌ]UcL1y^t~ "lZz.mRQÐ.q\ҭڂ=43mGh_(S52{9>WNkA&UDQ#AU;K+q%AqJ`m<29fU F4a�V5 %uQMc"XBLOf:l{E:Ysͮ0=O3 j7aM�c260%m,2BV[í2*焀dH'oκhCZ|?!.txuʽW>]I97ǥ&Z*s1 @k-լ5l[3*Na)/rj@\’ : a .Ax؉F[1$+X~J=qUQIy!<:kضEC6`$MATf%�!(q|@;vT+<vpJ|URBִ m<#wHRLaTOx/vtf-ÝWVeHvl(�l .IVm�} ? ڄCnSJ`|d4[2~§`ƽ=o =4UG$OK[Sk"ҮS?s|UUݝ}@7-]sj~Gp@4k 0=Qa%_%f۷7HϹrC~K?.pR JՁL7:b꧳.\H] #15dTb\pN9,ߒ +#XJkQ'f]Vu<zV6z>q ]WS KfݼylJb4 j{&snxnHʐ�WZҲA,?~SMS %K/ǰFZxy9=Й].pm(%Zq{Q[ L *:Njakaxz� T9ы ^YZt'1&Ww ({@\y7N:ߵ<LS7=#58))"IJ{| b˜tw`{MA>BfP[9$F|q~g򿝕:W %džGXKkl*p YrB"X' zkx=cES@KWި�u?$ؙ^uqIo;!%|Q*hW6M < zA}AlWd[RE]7\v7a5>|$CG|(ȅvr‡,]FH{!ڛ<13^fȓyu,Gbd:4{[ }>Lx:.-c{wŐ"0.@[^U4;JհDKTs.T 6!_ZUK#YH̗:@au3<zΎ>SF-Ny4T3x_wܷ8yUl֭@NK(U f9\<x1ZK|{GT-Z/%(A)dQ&x?Ժ3nӒ܌bB\? c/?3~ 7H<Jt>G}bSN6\6@lGX +N^Dk#E粃SQ#!3}WѼMO æ~X�VSȚÓ.e !fTݠAӖKI e{Տ5to=SM[S@m9%VQP{<Nlԇ5^Bd03)b. k|dmG{,N 2Z#z/3i]m\ܶJt)6ΝY<jD;E#2[;`P%'QsWA7╯p=4fg1MTngsehA<8G#k\{>O6.6؋Rc&O a][6eSf܀f2e.fMj jY+=ØgRSFŎ~R*m'X]ߑ F4Hl NHohKL;:1d635[0ahbƘĐJv(%z1ߌШͧ5XX9>Kid'˨N^KF[_,(ycjwU_o9y{XbUsK B3k=0^@K ʾ|U/@ꠉP ]qvS2 Bc՝dɷ?FR ѻȓ"2(i`x"R8ohMm-p6!ZJ8(pd[27Ng1mX,zT1BIׯi޵x+\`e#pbw"'O - s J5&ȵ&\Jsik˿uhX3ۖ\S8 % |H$NˆͪoakG]*6OiG,<q -#2w@.=ۜkFRWff^]۹(ZaDe/W`@*_3(ť)P,m ʕxW.Vglnvȃ5S2"o9A`�Rvj1W`F1p.{LY�ьlNb\TN@L+@7nd|\w>n&ޮxM퐌5n:xސ+,2/yGL[/DKlɗHn`DeI,eq4*JѼ+G&l|ǣzA[4|V_Aƪ<~.gȌ,(PZ?]k{$-<Rbxn&pVWSP5<?ś ..uPقz"pB'ڇT >18xA]ή8|œ8U5yHg<8@ KὫ"\|54 :$D,CηC;: Bj~ڡp/{EZB >a6_MIA">`w߳hLC7z\\ePZY 3&c6Wp8V jD>C7ьxS}BnT=Z)O]x"}ATnG9ȆbXc yQƟZ9}:2+ ׊3PPs pUPJjYѩxhQ� WW>w# Q@u[.U(_al\׊p|g, 35*\E4A<tE͸uQ3B+xqw`YPaL}�sӢZ{߄&) G3v}OYR(+ԼAcU#A()NZZSE*J[dJ˰@ I)4V=@qA0/؅㧃?Zd[!:RqWUh< ;sa7QI-r.`ln,8iHDwX50ޡt=;*;3A$\&P['+b̾lPK|wAwKh+tNS-1,֒{GI*P_6|ze) /K؁h]ꋎiH4I딆L9 _.?9ll߾rєkeLO$*4#8L1>l$bs2gJ8-J/[x `,YeZm#]fF8Ԁ떁 >�Raq<pm &`i)מpgz`8m5=+*@U4; txi)\9y$6 &еm*W V Q!iWĹX&+lz 7aB Y,Ɖ訨:;OZ3޹H;87@m5ڽO3ZeWcc"�\9TOgScD!,<.4]V/K~gS&>reh|sVX fw: 9o(qDl:,Cm}ACGQR:.TBr k~z{aVGY\~n ٪}$7gXpܧ Z150Y57>غ_9˷f)"Q4?^Rwu p><~$w?}^㢢�S%!{yxC{k;j.+p[!+DKѥ^H]ih?�wԸXQ~EkofJf >ƒR"2]'ҽ XUdgOMۡ3Hsq\;xde[P޲[)5^xy=7'.?'ierih]tlGAӯz>ő3lʃ/p8Y}:?d5roD>U J {#>J8Y$8<ɓċɯ@rqL'QR1rnxjꏬ2*Go-)*쉍!PWTVP !Iڲ|=#Qrp 05:M4?}7{74"澎 |,gFx.1ɫ͡RSHwHgS%8vtphn J ujRZEcYKQf}0hvgyaam/:wk_q3͈o+uQU<þ` #Cw$sKo9ɑKOZi`6=h1{)gD<*5Ag3RW+~#i&Vt=w,pPѯ 3q$auz/Ԕ;1aoRt[YTԝ�:O4f #G-Kњ^9drV^rqTeQ #~@B&Φ60c%&]-0տxma*t Pq(8'#2v?(5+. X[i8_҄OEpr#q[۷(FP'g?{@#m?%}\3ĞtK5ZZ$*&aCIW FR=S| Vq qZtEױq$ ~N*Ctת97S:Q;lu/࣓esDUȃ erQgf!%�UeVժ[-@ˬ pЍ]ywtz:t31 ?]:YSrxXf%6 rU['[Ҳyԣ~ai.q ˷k;D)V8Lr9Ca;FUL>0? a;I,1*fK]ÜDG$J;ݰ)f ^F >մw⡉F?edڏn8'I�y.xB裚NѠm,3*UYJYƵԤ 0?{]Xv sC[Բ{G0%^J 9c7ǝD\iOUK>!UF{syprڇ2jIf+nyR3S"PQ!n>:-,8)jWTDGHeW<z$#ʳ u8?i)vCD“l,H`?p;(ܳ;ؘkxEJ\Sly{VfnIm.o51TK%zؐL.Lbsb|& r/ W ,JĸѫӘ"!\dpm*s^lPazI}SG1OApmlר2wIFI`cXsH5Ƈ/-4aSP(.`U` 5 l7$ou$:d;IYnD,A~mWK1(-`jin5ƽQJhA͗|Vhs>he V寀Ub^qz_@:&;P `GqeT ʣ Xz#|DTl7w1@(: TPmKnKRcD>kqqOɒΉJ -v\G䂻צf' BawO`ϨhFg1W[x:F}_I2$[h.La:lyk +5xmڙwK"*{J'2PWu+M_<$ F.qcU&fqyg :QHHK MOqƍvˆ@7W�G#{A Mm`*v\:8y ;::ݽde9 _J AJ?N (m9l_{S]q+ݩUhJ 7ZnjLrcb;>-JԀ,ϊCZa}eO,1 i}bKr}ϥcph].ӦF1\UI8k 6)ȒH2{PY1Y䱄GYYE*vڦsAa&k&n\׮~ӌ)ёVB5GT~~S&0?@>5>nC5OZo0}k\ N#6 <C^ef9~TEv\3Ziz1nc*!仇=a/ '@Y'#!lɔˌ [s0Hڕ R]頝Bws#Ejܬ4R3mՈNU ə-jk24h㕿s�렌tq]1n ^hV"(A'M9Rn<E�|zj>puHqr) g;nRRdM_4k\ГZi#C1%E} IxGwF`cQ䍲vcEOZL9s(~z_p$LοA4|dis\µ".ćdTWk,÷n"}I=*}{u[99Y ׏=)3U -p)!b:bZe(iP3dXYs*|1_[2`[ [Cxi[kE j<,-y&a ы PGs׆-� `ɉPig`kၨnOԨ~nh$,m'7Wi b!HӾ4a _Anڷo#m8J(.?6-bk,3?, UUy6LOU6|l|tSSB.msW1=H 4K⨾2V":wDyw Y<_V<!@ZtUE} ۋdlEfN}�ue+_>w9 51U29^P9'-<KQ1ܮTe,~P>RQ'P661pAA] B u",P=B-5~Z E(^Eq<ek. ^cI+ *Fl;2]v~u25IG[l f &Guraj'BF   |hqhhUl.m j-:W/W5eK'`fիC,֭m<GP�H)i;90Xx@<8_:lƲ<ljTV (pF#wl ukAMw;(QrUT4X:"sx&1K5uݪ4Z8aKs*_0,yQŻs^K(аso'wۮUm1Dw0"޳p[�w#Ay÷笵dqY?=_~?4%ױz`܏ﮍ]}b'N )I(-#@YN]x~:iȍv{1E"+o$cb翌禕r~yzU-DEƢrGN;!,A0|;_%3-&k U@,VY(:64Av IYeقdj)m/W=2Hn1hPa{�~D13^&AC&Uu:F2d[(Wo>SLIeλ]f umD9`#Ѯo/e9UWqCY@1F"QkXiW >'k>s7ߴ9KÖI}&͕- {F:dW�pE0�E@]kroMz)@Ӧ7(#BrF>33zG 8W!<:m4I"*Fc QoY0D�Qoz#grl[J 8]܉؍ 6D~i 'i`q2I'KN2L+X2 9׏hZDc (Pѝ$'guț (JVFsrHBjO3NX{}>@X><Hg}Au.tZɇ]D6)<|) &X-v7+4a% ŝ &p]CRO}C=Vԅu <n`MBcDtMJ77p6Tt_"zUeATKj^HO-a)8J $XUa6t[kd]a NWoĹjxaܢD(4}GRwq[ yRB.Y߆ Ņ'Y r zz1v>S$fN+jgf s)* =sFkQ_*JsI+D0>\^M7.Z (uzԲl~F%德cos;I52w$"Fw'ǢXPԸ`fN$_5/P5sD:SGj Y,$cY r-]4i߯mg$T\)'>h1ڣP"tXzOϮ~: –._@uJuhu"N (w?{TjpGDN=-"Xt0_(_I 9]>߄Swܰ3"EOel#Y`(! XG}Կ 'Kj̄apl@x痷俖&|? @&^t8+R !xǥٷ|I]0zz=ٍ݄˗⋻nG=n4y]vwQW �.vdPљ- `ppڈm$)-<0\B4fdgZ*ikj2`Dn<x0@紵2:ŰWFCH2$c܈Hg;M^ZWhp %fZ>R,7-Kwg Gi|x+Ay~3\vl04,bM{5Nͷ N8ϷG�n8h| 5r6Z{89\kZ7XIQQ%u+ʊH縀P\߸xn"12y|;ƛY6v]b9 $f*Slʇj4iw^t:TI)}2{cߓehLy :8~Ϥ+ Tڲo4~({b{5q*$cX҇ ^排.<ZuQ(l?"xBdm=A͸ҍ"�Q#m-D~zRyD0qٳo~JϪiCḔyQҥK!Vu] 1TS[ $c0Q1-g8RqnH_~9[bywx\4|nbo(:An6Ibu ujpVDgH|28BGV^̋}/f?R<J=Bݴv<f>B٬hhBND4dD!X;SGgBфX{ q8m-.{LBWA<SxQ񂑞VYݢU-? NIkb8DJգ\@Z߾1ձxF}_+@!gM33k-]\ <E-sLQy-{C^evց"TJE5A: דn[i?72@w.z sT̅A!N6L~2=jqlO)2jM֎⣠*i}{6{׃>�W+9 S/|jj'K輳aa]_CdFZL_5˓IЎ?愀yݷV ~g7_0L[_OʜkOO`Nr1Z@p܃K,(礻wڗlڃ|n�oT׃1ncWaw*MxngI[LeZ#"tí hBc6i9(; f;'h"cXBW Se|3b6fJɏvrTϥc0m˩^wDm:}s/M -?>QĂ 7QĔU3he ILKB&U=f(9u a>-Ֆ4?EӶĽc$B=doG~‘wBznH3SZk1W{mV{dh )MSih;6<?]UղdH ̄)YpÁ&FyM8eݠ%&t6F�@ėIZ斂qY/[T=u<.\BZ1+Ez@N E+`Z gcJm15ŗ)žTQ!81S /΍)}XL wЁm<=IZI@wX!՛7A5sgcVF|g*fbӒ]j[RQJR�[ lɭ0zIO,muFd|p:5ܛlS}^v+``O\ҙΔxPűQօ.(g2uJ-iSӄ7"> ydY zMaAhׁ*A�%Ds߸5zPAJSG;uRؿʢ[Uw('<i3wBEVOrr5qq7™Z[vNLf Hiز\JՑ &Ⳃ^h ڱ WL@|wv}-%s@bhAw<6 %1ҽb Nk8P9roDZP:=mjoƽvVDxK%-Aij6 KV`=nXj2dla^ux<1e.HB\!3CJT&G% F)@l s{<\6!$"R1,\SU, ob[SxkE|ؓZ Ϳ,-utAKkh+CcI址|\Fb\R Vް;KMXbHFkЂ�֟=9$I)xuŤc.9KԂiqBf49f�(rbHқ\'@[c� f47hd^wer <]UWRyA[6*8Oɋ& ͎鲗l>'5W:N&65fh[Bh6j_", S#KYEϫX4 x,5?n Wq?vXho*%|ǞͅIkQsjcO[tt.c?iTq ,?*B;*vj4i)x*}B ?o+uGWZ s fNl{S,z:ȳmƃ Ƶ\lHdx 1ݐE@"HX_hѼr9QSܤ^rq*~D=?Wxp-c)]h\qG&}p9o;,a=Kh ۾:bD g޽Y;=D6RaA96)[Q0-I35;E\Bg !u._kj�%KS,Ae٩?F{k]'AXKTC)=A%8Eg�n2KK 4C[daGPj�InL�c~6m_k9upAؾ4CA?qA 'IBQ\֜H �_ݍ &"FPBhwŨ%`B2eۭϘ? Aܞ0" oLUlƈӈmL|<dqt;2T^N!W,UЀ+`w lWlJ6 5D (^X1앷Tܪ|4)DeH+j?nmlHe?tx3MXܠ7}YG̞f g{ѫ {ٿGXCQOgM%b##M{h,ٱ=1!Ƨ.I1v?űb+W*l(Wo#-٥=Xٗ׍2ڨUQ޵EN-Yq|vySiQyu9§&E_ȝc{X&XOP^$%opuA/PWmMK*/h'O,G:jT5l2?�|޸q/9 ITᶵorE`Ņ09cGiNUԅ0(h$E"�K;[GK1 HΫs9J fo\c%SV2'*)NxqK6T6i~ ,9CEt adq/#zE{zGMXT}}t:i{r(eX5@h Gf9 7,zo{p XyM%8%=p)d ɹ~r?L2 }LD+mC+WU&t ױ[SZs"[9<9.ԫedsbǐ`) Bx=YuÌ)"޼.$˽~#U邌॥dFgVu}L�5 >+ָxOn' K:7i8,HT"ڊ^oz+v^@ fhWZIP|A1mC8&P5wi~ǟZf*Dizl{ X|RqRq?Ν=L%O>MI1C$a }Ϫiv*I`@"Q2@VkX̏k)<7{Ϻ3(׬(u~[fwl?4,TȎ" t>.7?zű}ajt� eyvdp- `#݋3c-p,e8ጠy> #ZM>iY VMh{_o 8븠3H*/_`䳽,2 kD!P.^^͉#m<6c}Q}ˌ.VꀩICG?[Dw@YG-cФlNl耬9R!BAU&0w!\YDi占0dώ#椁n}'YU͝l*k8?oeu/n1 T Jax}E ;ZRߚp,3z>*_mq:֐A0_L]#- 2L*}!"x`xo1<�gM,m|aۜNαUi4+.ϕz\A'T}-S־ϼړ{ >azO-wX4+AZ ђ ~_@W0vqӘ3bGemGz0B8ͻhzOU>dZMᢟ5(p\nyyswP[ 9 b*s!覆`|wEqiɯmOw|im =ȈKu00wnE08k=B[C$.Gt>I OXwѷ[nWuYhvasa󂘙pJ>>u͸o֟Ӄ=M�\u #IBG62m*EYu_e*35iO$s1 SE~8nZv urt/~aim)�;xRw2 jC؁=scGgȒtӟ Frg$V #kxجųX3Е@>آndIwi ϱ쑌WX8;aNW?jN7�+3a݇к3FZ2~D,ádm@*Iաm"q%i4,Y^uBѯ\xK!5[ ~ݶn!p!i+<@GUnh!i_üڔ# +'{?r)b' m~:s*fsPXA#rCe%gAp֓Scp:sj" ~B Pj6n9+v-ꐎC0t>L%,!/?kG?1uM2y<cKy!f@ /lL"3^قQ!H9C~n-Oh}�C̜GM�5Ҽ7`W[-f vfp>0}LPe]A3n3%H>I޴+:;EnBƆя:O ܂?&F0eOJS=kɾPpRp-NNrM: ;+o)oǴ Z.!u'U>cX'_8d++blMPfCiˡ݇zU*eu[y ci��>XKU2_sm=I?24킎AW3̨RIKq&i[/3Ϗ;n H[ Ƽs|P;nH>3BvĺxG>8Cpf{Ms3r/Kk48ٖJ4aYp"Q2HJLp:dK2c>h?;::mѾm?}"Jǰu5Dv|#lsd1^k{#¬%Cר\p#R s*%<g%._(E a,I]A<y% B/ c<)T%E؁=f[rпP)laʻ]~v>'99_bH B% ao&ƓQLGnj~aP=֫XU3YѦ7e~ꢜw"Y8N+詫㬥"V9TO>1a0zFNWײvH<]un[, wT}%҄ `FBf]ʻ6Bc{bxjBru2sgp)b̋<9E桞ԥ;Z$ǒw_MɶixV.A»NIԭm$%}K+٘sJ<ܷ$hfDWOtFˑOĔ.fk2w>nH5Nq�Nt;!i(?Ï:|ʆTmuj%5!H8Ԓp?tm2X'DZ¿@`}uW| ,%wba_i[J>ngKIvbCv 7L?YM9$6;7{ZlKH)qh: H'קSb o?3Z`Ij#􆪾K?Muf4K OC[WצDgq"FȇE#eVVGl^Uda ^/;[ݵ0{TXfp <cah(Nǂ3Qy3u)Xy/)4I_=%!-^B"d9uDA${N#EȻly56G]WIٵ-sJWV>(TAbw 0+,= {U0rumݵ UE<D 3ޥ=a -g:Sxx>E 8)sK+G'V1_j?s .ʽ)K9ާ c=Yˁpwq mtp.S_iB6jtitdRks7{PVl['5, 9ɾV?afz7@#�x Xq5x//o7,^ ӕDsuRhC;<"O&$LVF@c&#Z\7san#鶷Cs'&1d Ջ*( &/ҷL]i�} D_@!-E[o na\6A>*ney7HuK7\}V@QzkNf~C xi~r4I0 IlV4w8ف8E%o$#"y&5FVzZ]毐1P2gW߈K,tG!0JV|5'2HgͷKOGuk .7G'70n5 _볁+^DqF`0Bp1 m[EhjL\r3\\8ڼ$LưvP0Ètȫ endstream endobj 140 0 obj << /Length1 726 /Length2 20843 /Length3 0 /Length 21397 /Filter /FlateDecode >> stream xlzcpnͶnlyc۶b۶X۶m۶q=ԹukxFOYs۹x:213rU,0dd"N.v.�uS bdin4fchbikPw4�𺹹 9;lj p0YژD4$�r� S;S'C1@ٔ `f03?9~˩�DTD�v&�Y\AXWu�H6#o `bi025a/ޤ6:럄@T�SЮ66r�J{[WS'/{S';?7 ÅvKgqKSK73M:LM,]m=?&v6w׬ "4iM,.0jdÿ !� /_.N�mFjz[7b_:9ڹJo_djaj lol9Opfv@f xeFrD ^'&orG꾉0xb2(\w絘ܯz]1.\L X&%}}-Pz53Z/SAi{8v£.J7>MҬEHHX^. EAYaD(,̓q&4 rS>^fYsߗI4ZM?{פ㌼W]ݻ핸Ot;* I[k$JeߓTY6=o2 !t;S7RK@mBTgpeΚZ8`K08^Oqj\R>ߌb+wD9gVE AɯaI-U!HB&H×* p=1dAܱWbC$m`C)" ~A"!6LMKk{1vR ^D5}w(:| :~<&D[?t{)Z\8n(hpDLz9qOƫ Os1'8X~R}Uc[-tM:C+x҅Am0ߏBf:^<tV �\*e~7UıDAQ{q"EVy"]ړ06}&݊LLy81ؐ_$c]L蠺bC`_JTŮD/dKscWB\`!<HDٯ3Ri!rTnʋ}�ZQX6qoI@dD>;"no897g)w$˩V;/&{UF%di/0LsgOJ7C;֯p2P6`h i9T磟.lviu(18vH_R.`CДsO[z.zǟW8Yw\OE`T|WNv̥7i4M<8:@<BTdK`kK)E$@�bUL>2pnx*Ey` \_\W޻׀|U2Pj>% {"&$w!g"6&$39n5r(7gHW멨zw"@HWS! ӟX2zCgxȿ+,9 A.c@mKȇDv yfs: Y}S&q(-(8"O/n>>x(6?Or-ف>GTО1*U[maYRzX?ϲe׼Uk6߾׆-A!Y-~G n Y"};ez`:a|/hhёF?.Sr\KУ_@Ђ\K$-:4Ӣ?W/-Q< oHΖJ%7<I@I8FDGE7hLO: ʺ}Q/ol.<}yڝT[i,V4Q2N.Ǻt�<dѕ(| o;rmj[y(!'о#B@ۅd.=5Y0% lxSxcGw(P=IL.qnq&K`?,)J$ʜMq9rreP)#0ia/W/ǵE@Pu$^x^mϓS ʊА8Rhi,e@R2M'0fMR/ȵ f?h|`ɀ8b/&"n#OW-m,jr,KLj1RN-#}J̯ 7諢?Uz\br~=WG@CTIs=W.iMTP4 t�vnǔ*B3^I9tf ̓zL0w^>!]9ϔ31J 7SNb|MRDnXtΒm NJ>Rʤ"N.)Cm{@<3V\XX*~t,$V) E>`Oۛ,ު1gf�ڎ[$ ʓha/5K8Oy@QOVa-Khى#d$uvIK+腙ie-3h. '떖rh,8ސ32*fAX UJgn3 M27g;NZZX < Lgc gtS_e@N~0]M)9,EU q�{ y*`:Pn܌gz\^J#1蒄pXkl|ir铻X>9 ?6 0ehx0h)4 2=kG~,PJEt@Go; ML& XE$rT򶯵+ZG]x\9�G'R?՛\m>|>Qگo V/s$JC4;n(ST@�/=n~=s8>peٖbHI4슎BoNwpu|'qZ` i R&2 P i}SCʫICQOjET*s5z gu׮~.~` ޡ.HyE@?-pbvMIf3;.N(×~N0t �q-{)bsscSm#BoY٘r<$~^PXE6۸UXc)J1,V]w=ɺ"cLboV MsTVKAk/kiG##?5:E/V!m!y` G|Zu<�礕RiD( \|^We 7%` R_>ڡ%\o7~riLM2E ןdcoI/ )cINBx(PK) R!]+Cf]lp V:r84XlPW ``= xerl좻-…Q,P摝;$ " ujH%S aȿKoMŚƹ3qef7 @Tp"-?IFY͕aH~کSp,l$9mk0U/ibNf <$SJp+#߳ 鑲̍pNlYY0眧&(l4hr]vɮO*UJrGXefD~qcZm�`p=x@'#f{(x5t&9%](-mY$g O@N,!ǁ $o&Ռc C69l٤l9ztEk?nڨwhұ5輦W]aIv<G+[H^Ɯw6J xY_Ǡ':PSƪ^\Qz|,푖n' .XԍU+uGVk)SvI2fNHo>6hfO+VgZ(D:V�_iʒ/;%묗CP_|/|UzJ*3~l=SCjd-pc0\ 阥 hmN}Own-3%(\Cݎ٭GO~$-LޣG C<.AHAEiIyEc1b*2W<oJ*{8_-&shM~%߉{j4X ~~k*/xՆ:fzg{ӵioșdaT< |BdS5 ߙWs!!?'j4))v;ImOE΂ƱE\U9|JE0aQE *13'P vr>P`Tk XJXGQ_C*[+JU2]".yцw^ee9ݴX!ҠfgP'uكoզsqfK'fK6zoPo} rVͻNB iPe38o飜 m!05V Ry9YNbazWGP3&DvN,NN4| Tp_Y?!566ǶUɍ\㹀][1N]}5/$3Ad"3aA2^=ogOLguCi ]QWط:IvjH!y${lp*s6LK1Vp=ɢ/^PPb ;o+6!Ru,Ŝm?w]l`*w.ZΌ00Jް* xǺx0*y\:~aJ~[8o$*fĩ[8:>Drepڣۍp Y_,d5&)}z\n.`ũDN4j2j02oA5ϭOyBZ>&iO-fULמ%yz>*Qw A{D|I$5LO|lոe"qԂ+B%TXܘEOᵵ(E'4Խ5ݑ\j ^̕<vLW7EC.iZ%Tvg9yO~ 0m`[(u@7Yo%6^ lB9R.JoC~2Gb1S /ΕVW¿GmIB't G $ɳfNaYQ]BKP<$<iȕinpD, , ':M= ߯!k5\1ٴxA}"&SSw=D %\zp%He!45R�7[9hf"3 Ӵ@:m$ql<mQ)Ubuwo"YAMǂ-sH:P1('i1(%٭ ]wHc&{v~~Yu{ Z;?c cΣbҟ" %|.^Yi?;bwDYXJU L-){Wu/3i.k*@Ҽߨ20!֫¹*f )PKj]&ŕh;iYD^]W.;w)@ e0] "NmZ9Ǻv!h!牦xƦOf&SV-]Z�sgvO_^$۳:Йӻx!­W-hAfD~+s.w8NXm"WJHԶ~{/#%24z8D]dH 58CYErU#*:]wW#ZP\^_{NXs ssAR"rU& !'nGXSee^1SfE ו`,QKEu7pDX`vQId} zG?e)ΈxZ#%7`ޝ~qvr3Wك5Q.롔1wVR@letļYU=7ʵk>Tm 18|J7Ny no&Ii=ck%$?kYsT1(ma0FF %j2q'L]yJ; L(|U|oiZ"IKc|F{@{36hD"v w>~Nh?B)w ҥ'V#?>�kQW*TsbB|]v, ) ?`l d6͸ÈH;"5;B1v><ƠUd9saf5YOBrh',l1yb꧿C=M`W$�4ßΜZD1P`"(=bËM$խWU$38bdMVhW?Z/3;Jă!,Gf~M^ .t%]&xWu#ԩ UhB asqWvRT,lm:W65f3I�^|wr,>P#<͓~O4G)R {$϶8T) [ߨAL+r:&iܯe֯]g`Wa7؁XVtLGM7 e͵LgFPdﵚKE@BP:k)UŃw0(!oSwu9!ڶ1`J S@,y17Y#Yu"d˜$UP~쓝8,ƼOT!:^J{[N<\ Vѡ3N.E6F\E)-ҷnݥ_,߅W;\zkSK KG0$M΂xr.a~QDrn Ǣ]W_)_$N;iV ~dEs*,/ݬCtEX0}E^~#n`b[Rg<s5-<l,SygJ;?/Z:=ݨ5[,"N tB _>:.RQXmӆ6G \x{-<l$ mf q!L#;(Wָ^ELߞͶV 9[g)#I\jK+_[xNj޿I@'ʀ&FWb-RZC+[`Ed�ٶIg#nE:*Hg;FKeum :/(ވ?\4:=*%D]úoG[=.hpz=GTL^u>x!^o"_ OA=( g+rw ͏i.Uo|5r"=$|;ȡ]Ne5p 1 0 A*1Z9ۓjm<# UR=XZJ !!  :~G#N9{:O;,#rqH\PuNMys>󁁑/ }sb1iM҄a,$9պkiF1B$$Bcƽzb\BUB4 r)>ߟc5�ȶ9 3v&.^\q{f./yc qtp硑Oè.N(}>@]ƔvMȌ},qO,2/[K^a;U[]=)b̑e}ZvRҤjg{cY52$}hLɞ-8uFhDZ v($(wX BИ@IS:>'zw&''.i_e) \/3X 4n[ߐXV7<\Cpuir!hoJ%}44HԆTN FBA u15Vy0<G}Fl%-iH7أv7sѝf1[pEˈbImRǡh B]`^, >)tݫw59Pt{Oڨ;+5HuMP-$.~rЦÙQKQyBgugG=2`2jt؝TnM&T*ecB*jy<0^+dp M Ȏ!NЗ睐$㘛I}2g5b bTRt)҆~fn%PO'6_ߎd,,T雝XGdJ44g+l'ii'dS )Nbo0)Hu:޺�0_Ny�a\b�04SIWWbZ&Hi+hN-.UAe z]y"~L`>IAƻbK[1"wvK$kH N!3`9W7S򗐥4ݽݭUl4pL)4> Od{CЅ, gj$W; aET9#aZ�tJden߻y5bl hg+8ZD8"-An#\F%Nx կN wC~!qqGU k0gsK YeNuTW@rqJ%!\diU/=B^`A#dGK<>>gbDo)0z)QxYhFjfʴVc 502;A漏8dѕx$B^Im3 B>Vs2l6$TF<7N4:,p�!cPFEW9 uҁ9z#-D7q Dā&v/צA q vrJn)*!GIy'"Pf};5A_TdGυn F|"6\(־aUL&|VZ R9*^E1ρ}!TU=*243[\O ޫIbhsԿ0/;*OfR-)ݶKK~kȯ> K&Qܿ1|>Lz(\#&/t]qFD&B$f|r퀚 l NmDsO&"u%/Æ]LSd )I {/<[†{EHܶlԤ&y1P>g_\%hK[2VT/%VQKbd=?N^зZCXo'Z2?3Uv3o}E8GFq@M`y{z.ԚNZY޿fsݓzmuXK׷?$)^s/HE{@ZaAYhZ9 C*yDԼ+g\ᙚJȍ+WfرKVUyhs Ju'YC`yh!Awq%M1 vS'iroy]<EwiNl'\BK-u ?q "|wQc ?!oKF st9S6붵7nl~?K'.Na ML-c2kqO.&!|+% mR.sfVc=Ȉ/n[p:{N8X;D:qWtƟb|H6ՓQCJ_yxjsnɩ)D"V]ׁ -3Nk/(947 O8c!<Y;5mxV=v$*(JG`i 7[t{Pk0۴ Mv,�=sdΝGFRI12'ϕ43O,1mY}\AL^a͗8ݰnAihHb0Cz\`?o@eg[;XMZ +(J2K8fsruG2hFU;j? XY\$ ہ3.fqsE%G1Y&YN#WeudW9Ѱ<Z+vI,�jц/8\ o~13|ƃEuNF 7XL滑vb.l,Eht�NT~IcO80Z AWrj ӻAUfu;_)IEPIMܚR4ziྐĜx=ՖxmnaV4�|jEE'(yS: '\7+YYA=w=5niO<}AanMENq mP;|l}S�N/dDH!5i&frtPm ?,Wo9~Y'(:*PaOc4b=~Fi541h݉olSԭ_LvP#}~)1&U'y5h´btfPHj2ԺkU`_*@ [{z[CMt/zDKHWfb f <Wx,LnŲ#yHg7?mEEr=o܍r.>xr;" YxùMȑ"$ȨV^;uY.k5MtXuD/v޻%xJXH s6ڎZ+pyYb%&z؊5nWma36<Zl΄exeMi99 *̶2_X[guݜxo}{Xo ٚ#R-8M?cA{5NPvGk},x)'&,<'o "�3k5ԽcKr<t,v342 +BWGɯ\D1QՀvQePv/WwUlCCꪯ6IiBX~b9hJJ�"_`2lF<ٗw溎S('Rޅ+tZBf}P:*)Wm{G#IX ܃1Á ~3r4#˂(3O:G{2Vk'6]nB۩:xIx'43OJ'M DM t?95]EF4XpFz%{?Hwy]=3h<%oUx5 th#ъj#Am:nh~b 7Tkug_LF3>nE4mE^KDzK;~o0'NcuCN+Ya~.O8"e5΀Z!ig\Vw=bg tX� zIQf&*cT+c;SCzNq '|"s FqUg(c^t:f)ĉԸ<71ꜥ}:נh]unG!뺹5:@V|llYvm/EO(W+ƽi SvW5K !d-D F7Y8m-!w5ڕ^YU 6ִv:ZZ<R-BiO\T^ݥ/qSFFph)x~ wi250|לaioI,Nh"?$o �f9}=$1cTmF4jBv|Ⱦq}XM T S 2;l7G>.i:lY=ZR/9(n%귬K{0\:;eZ3(w(*G!.x=}xۂr&RB,"hr{X}v\;PH,.3a9sc1=zVOêKWu*248Oh뗢h.[h^)ϒ&Ȯa:7-{oV B-_7?{ؠɉqzQQ-d6nYR6RQ0 @+W2lW<GߚWy?uYg`\,`OmI'AKcb.Ė Uz7e6xѕYtBj�6\ yFm*#h2g&r-^o;D8q-FuGcxuo#6W6֮ bJeRqY3Z(s=ILlyR՗ZЮƭ)kQ|@l70Qʅu Lvq>$=K~gǿb [:[|hE*Ia>T4scTKr6Վ ~CrQѲ4bte5aSE5,_zi/V{3;4 .i*ᤠx.t{#I.k%iP&8HDe%ydd-ǎҾ  ӮC|[5EPu Ȟ~g?ЅI}T ż#mW Ty<ixAՂo"nm \v�9O:w69,-A[n0k jWFbE?>5e!°$DShmIa_!#|$=3yNnƼAvx?WAK靪]\yr-樧c@' F(~ߘ,_FZUB 8ZK\ojP[ I,1%n6hoQYV ]I]߂ &W%ӆ+<gaIA#&>b|uC$&}x Tzĭ5Y,^l],e :%>oTiAr= )z(�K>"it@<U͠jRviy8p >y&1S#ْl}݄ `~dBy&S23[sRuXS;umbй;z8֒;C2MgN,$v.K R@\/ |_!,وR~3cj{]H`PlY^m| O.Fդa3-hjBDZz;9:~MD?Z egC$:m{-J_Іm#%_*r&rgvWYwUW/#HFh7A5-PxXvڵMɆ/J&|P[QЬT*n5<GO#IG*)Ьಇtϝ�uK؃le]PCA~˻1 e W Gh΢4(`ia'ŋiֳڹJE1)$^'gnۤ-׏96:@e;j))uPs&':ǔq+fcDP2)*;&(!yS~#{V:,6:Õ!w5VMDLPbu[gC-OAHejRD<n'e@PQ28p2 GaT sX76'q*L '䋓rgaTNԳ{Aisjry9a*]77GGlTF D^n |eӆ =qbz|:ےV9L}]J5Xk,9#tvI }{bqҪʇ{8sY3 #Oӝ>ћa 1P>3 _<UnIl#kAt߭*(#2Gꕮpȁni&&~/R ~Q@jT6Fw0fʑt¸H.pBr ^\RyCXG<Qt>ێ�уX! CwG=3VІGh4=u' w)TnO5cʍj3R&3cPc|N"o+ܒoY3dl2(h=+S lW3::ӥT~RɎG+k#XʉNΕƾ)/+SBFx}q6�Z84d+q_[ G^{Z,#ṕG' 9WQYUgM%/ ۔!�:1hvZnXMNujyIxo٘Vu9G4FlTStY1LH%/Ij'a3l~?hˉ \9;#2goIQؽ l1dGo" =[ {M=981!Ѷfa~vD]#:l2زCGjKS[1yE4lI{ ̚5EJ],t+=/tzo1LPd5,#T*1=OCK&K$!'a^-@xrf0}>'?t3Ƨqπg%W$=H6Yޗ!ѹ^͍n=bqnmu8Z|qk@8VNZ.C@aD`PxВ^ak\Dy.ŠwZQ+^=f^ 75+6NwF6# 6'*/=y'hO3gX㫳y* XDJѐjhr~7mC),G<ay / 3dFFui]>.K4F'?O)bk<'!_yIlNӤ%/bL7 &^}oM2n!?{$UTA™qŭ a:tU]^s7p`6nxPUorfB<ctE6p5HGP\$Ś>41qK _{\l+\ ߔ<2etKgdȢJ/QtvC凕}ʡ}K:[Z@;ϲT1 wY[q# oYk[,_6&"M^n>];47*f\tⒹPllHb)lre.ws;)({sڜkW_~G[ZÜ^xOv[:nY}Yfx莻nT03a0Nkc7{BifUϔ}8<j #*Gs? <2?j:`it:W#%M: K)BLq\c#.# k!=8Am�oJRf:_#U*qUF`duЁ\hs*q޺O7F^E͇B¹|/?BEd`-oyx`̟vT!.⿱'')C7R-B"o% ^B4 Q5aspSIRly:gv\fL /*HAFpNQ9FřKBH_')y0i&rEf9z+m*l ͯ*LO oh,yM Dr%qیKtI܌_O8�Uש"oÁQ6t)XOkP'z򰤲sSX$/dM *V�s˥.4Ă@}*ۢ6 X[C995 ?Bk�^*ew[E:[4ǁ2h �jd3e9ir,DlSCxΔEc.; XE2ֹS䧂(~L1zo[WQZjTֹ_u1Nv60juHlgai,&QO 0n$"ZҸPy!gS+|JFsTarW(>0`fu*7F#yC 9 82?MT|QJ1 S_-LƎrƉ};j Q�[b#U).|/060RX sdNA$;G3H8 JG0AکJNH<!Aأ3"^@r}v(ljIk'կ #o&VK.YI<o;!ZQs36" (8 "$Ǖ~Lɷ4Kgm*O#d.lfpQ�'Tf$(sax! 87%Gvşj]A|ut_F0?IcDدzp "`4_+uN>}NZ-+I'sfz)QuU �b# e�ll8ܕ{gn6lI5pM  CqoJ\ln^f;ȡ<{;f!].CRsԳHˑ.WpP3m�d L0Ӧ̫}?f&^TJCg![`SNpu xlB`?jezś[7T z^A]]-lS+=Ƈ>6eG3M!vj LY2gXx|9LW):[% !0/Qv:LA Pfuo{ǧLcqsӸhΨ(Cxld;Y凔yyspl"Qg55xLA;+ ITyӡc\7n]�0gn!Q><_r OɄ$3<"2nߎ9搟FNJZqIP죮 u$\Aߣ(Pϯi`&>Wa_fseYg"19xk;4 ?tNnkQs% G¦ D6|y9Ei hc{:ᨮpW2kdB.B XLm4hN( }|#,$97"_ F y�qnngz߳[D8wpo]tPF $ p-fObqB8A$XHK>l<[ځ(J(i;pF54So >bD{9l46Mcrʛ N=)حqngLcfG�{CSXBߐ`C̅<kj֞q )炖ch}ZGq<},7xIaP:c~QH-gX::O4d|ssf,!we=;6AD+[nExq)ab=$+W}*_ǃi ʱ#X#b)}Xz2$\𷟤'eA�D!c1Ӎ"fLwc| 5�r N]XP1o9T.-M5̽` -GqvK(&wT su돢(]:L-[TGp1[~N_.\1 .&)Yv6xx<*z6Ib)07YA>$�DMlgnPz3Ma㐖qfa`}J;Kiihڝh؟"\  xmȯZkl˂$ mOj-ŜQSJ%j5p>C ĴlkpIzm۵eDN'F3˼jumbMP`ԏFB0v KRT4SR̕(m1M:t=Dw#iү:tQ|6r[YtV&ځ UuI6MA Hclfϐ+EJ FaHd@bafr6$WcWՒkJ] +#ޘS!he ]W2=|M(mkh 8|K Pa~2$k',O+jNa !d[ kbd3(teg;Cۋ3H7#t{L# պ#"ܭ Ӝz-F�u<ڈXwԍ5&F%CgD њtub\NxB 6&)T).@Nf_ZI[8$$ CL/!~pY;y%<� d°" <ܣL (?+9e_0.a$2fP' 06ݣtd~L\$/:P`]nI&evZKDCVu ]� [?p':4&g/~5~Q#/z&J&%m7w\:^hc_FSﮤӌ qyf7,lF$Jh9qa>pH2:nr.S1Ntj+E'@+l,A:boW252$!Cnca˾^tx7/Q2,3}i/3xO p7ay#({tqtוW--Q] nM¥J6`�06J`~ƁF/Xf]+'H~V So Z7J!MCR+/^|cr.;gճ\gKV" !A誌uV OAy.6>1kCs!}^pNaz=Bsw \ >",q AF2A7PAP9=1CZ.xT[f™b7gDx aIK|N'_ϛrI�śdtϙa fQa^XbHv*wZ>#(]<8}b)B͘#}n ^Ҿueįt:jP Q+:7)Rm*{]Jɶ>lk{B9ND^,!M$a%IH%Aw q=mۯʃU-LA_= Z`::3_l>&r]tuQY2.z -F6X>L )㇘ |K&ƃ%`NeY!>P?nYz'*(i&#[Ke=: %RUb궀vX ACh`o P])>iSSYǸ`=Phqz컎Г㕖 eK0L^ Т)N_H, 7Ʃ> k�`7{wO2CzF#gʢDd&U֤}U_}>nzU6l0ZT٥(HѓgѤI,SIwEqȷkߢhla+S<ByUojۛq ,^m^i}db;R pcrBXT,Dև# ~ ќ@C]|:ז)e8noSoLܲ627Fl!I}ZM<:}O9䪌rpONG9ESe(H2ɘa6QePWTwkl!cО#x?9)/`G ´L;lKy9n(r!P.l*+Oʳy,Z綥j$ 3nTsZe�H"\q_ԟPFCCgGec'Rc2lC)}jE3_6\mAA_'2}* C繅475W@%Yo󏰾:[ ZN uW4*aEpubn3ɽ9ƭA#t+{9DƶykJ}.0xWIrc-gsB 9cf -?k(k9\zAQn]^AVH`٣~}d|(ݿ ɡ3 9J1 [XRsaXKf˯UrGI:aYA_) 2vp"Af1OZi{-U`ՠ?}KXxEf"wUZ{)`?^10̉,94\dp<A~=i͘*ыk<dp䲁gTb\ O,'WszA?b/5*6+OHc}|mY HS?t1ʎSmdp| FZaŭ~Dn\[d�lt [im']q"#!1R$Crm]vz;mw.L0bJrh'_Ѭ8F/mGV:x[%IȞ)-^j]%E ҝڋ L4ݾޭ(M1!dHr4*\7PX2kbCɋR,!8mrIe ]/,?˺Yjm /ҿ7xbo jn\RBC1cGgs:x1<'Ha&Ep�̐8xwTWT#R: endstream endobj 2 0 obj << /Type /ObjStm /N 100 /First 813 /Length 3677 /Filter /FlateDecode >> stream x[[s~ׯǤN;K*0qp@ ˋ,98|ݳZJs^Nph,Da - WH!XH-1*ʅBBQH_i  Ta#l4q^B;EBCEa^+*k6"M= )G4P‰@.ih,`"AWLago%b+T1B `Ls!塡=u'Շƒ{_`9vI c+# fHkOAk(df; T5n 7;D\F}& T" v.~> |/BN�&F@  (C:XiɺZm5}A5/)�JD 0BJ �X!ƒ( i*Mzȹ JRXGIҁ$@XW"4<K8= T!|D" 9eP$H B0#q. Ihgu:)&zV|3d\o}!19) " "vuخݺU`d!ou~dY8oոVfVlCS Zlv%Wl}wtXyjbS?Pm�"]Y.W^=ʭ|4o-l{MnZ^߾v4jdۅFuտ]wfQp~F>~ܫN?EYx^3cbUl<N>F[@eg=șB�h~2O h3_Ltq%я??E=eMY:ޑT̋w>*2e!',AֶтIqwzH-Ltt'n%)Wy&4K:$M4 v-6+ك<&UD]J%/h,5VήJ4͖6ʭVV!#ʈCVXzdHnG0 !a)wFεϛ$!D8^Fg#1W:=_d;0x6TK(c1Bĸ(%J x4_/ik%׆k^%&گbGE)%Ƣ6 .?]%^<S.[|zZHZcHv9\,6[ nXj ř%ŹM!nӡC BT@k]z.=x8q8r)@<{´6uEQjz&)(hJL GF6G<Hjt"쁋vU/]@rÛ+ce;V#11Z/2*K6VlǭSbt^ ~E g2u+`v#H:BrFC3-Zb/ľ9'']DT;zØ�6[ñoōV0صɉ~{jzVw7O/w:̲H蔮!دde8hH_<2jzbrIlLwMvfIr׺QM7"[]prd+SZw$up.‰?]ѱ[+w4PS{;+XI/oka铉e܋&9kKӡTrF *AмQe,"8o `^:P,Ym^z0lmR[:pcCsJbM F.[k {t-gZvC/Zj-cE2k:*d"hۭ^^Q~y^4̻Ǜ82=xRKd{Hi>Wh6gl.!gaVpzK"?_aa(@oI/ Ҷ4ݛ8Oc$4K<Iz(riii#E�z鍧<t,!qiq"TJJل篴$ҽlEQcى)l!Q)s8`$ eg\t\*]ތ?,@8_%(QޛJ*ѾL@8CrKNʘf bJ&4蒂 8 C tT|([vs4LP8hta:"l:@:"|5s?-/ FBopIpDC7O<Ʋ|5Le9AG>V ."iFNW*j"8ONo&Pzv  o`J,A vj020 jo4D}hۓjl|6Z󤻚y ){'O<}ޥFN )GHht<6Mr2$t s4N(G ).+전Mo4p&ILw7FVqt }i擬|5I4CHE>,1A~ўL{ێdE'J{:7YsijLނt&dty"ᚣ>˻[s$ ɳ+$;EtTr{ #V]ٜV;V6m^qDunGuN4yhhɳ|ZK^†;;/SvS_ *L=ۋ&9zרѮɎn/њ\'�pjML(�C'4YB3lffOъ(eHqOUk,e6R6lf`C_Vx][>z7<^gRz; Ûdֽ_VNJ,$B=Ғ~Qn6W.➃zxy|h푌s4'Q]`d^_4Uo{UJUQSzUW/U:`r}ݯ.z_,euU]}հPj\Mɸnz:\T.&sQ5fռ_M뺚5sz:6{8R�<=۟lP%Vo�l}@F` 7AaO]}LˋL*l3sNӟxNܤ`ٌ[bA틷O_"/g (QHZ!#CC](2s:їI?"^pR\@' oԿFd �v>>Q~9_Fq4x1S`zT?ſ!}[߭3C Q7QںۛbVӳ#Άو^O盙~3sųܭ.=g: r=NryY(Wq1l8[E?ho"uWlCJR_jU/RmIb _]u<yϿ;rMH[zjRvJя=ks>RS? tƘj�>e-Mm.#J~N}Ӻ?gU/Q{ׂQS Im`[%wpooէG]eS+F6IGpqvu;L>w 0I_k endstream endobj 142 0 obj << /Length1 725 /Length2 30753 /Length3 0 /Length 31330 /Filter /FlateDecode >> stream xlcpf5$<m۶';ضm'۶m[ĘkZvm2"q{;_@&:&zFn*3##L hbio'jh�M*@�3@ᗓ ҄A@&JOO p�,m�E-)y �@ht2(X�d-Mv@*?_M 13'{[*@LAU`dg ř4_\g2,z8&& hni/ݤ :7!(ѐ ` 4jc#od P:��r@';ww'_ͯ_AH#?1'd,n4UtGo'W?4gWMl~i1h I+o373N+Yg`@_#翖E {x132XL,L�Nf.]W''˿E& hlodR+V8[I3;mF2}j9hajpx{sߘorG꾉(p<1ERLRa[Z D&%}}-@z53FڱU)ʰe;nuNJ-C OS2k\pm,pC0T;oѨ %%εA2c2Q礭{YY%jxLRy\ÀZ V_jtVoYаc7(&nA1X5JG@ <#sdϜ{~5ī6o0kkk.2]VI"z栈sɽ4FE?]O&&%y25WGr#=yG+eb|z1i6ydvEe˰˺^4*菏R,ϤP"gE>mr XdpiŮr?_/8XK\m1dn#`O*r|tNF΃щ鰬i\ J0Wvڜ9@Pe$J >۫IXJ/$0/3(YQAwtKs;! Q^޼㾈+ ^ Cx+U`o8^񼀝&)N fơC4]RpoA\ 3zZOtoQ<Kg̓֟lDl7q!K>ݞݭ%KqDoXI|w-7` 1N*rwR.j}g=4ޗL%Y*YE5:A6{uRԨ jPQ8Lx%مb}:CmQ1uՔ2?^?yۗ1$Hw,S%d2LpL #yS<AraNG-9tﻃr@{qcx+gi ;.K<_\Jb?? l}z[j?c(]luUa/z(F5!t yꥢU AKE:" ~+<t �p Oڒ#ʆpՕ/d6ͨՐe0aceu<3 9\}}tPwMxcepL!(~?ع/Nz> H'g xf n&Sw�73&r{3c=>z7'Hq9B%Cì4#CzA3; GoLP-siVB>[ y1[jգ:<+.`NQ2rk5GZbzڻShِSŞ˝97PL)TLM0 ;o*߉]�{1(cyw3Jb~@t\�]Xm%t2kD#D+=~JOVNwsEZ֠|^%Ý4bՙ@4g6{jG p q֟N#~&hRS^H,sWv/:4'ci+fp?f:!gG'DqgWN $9嗅XFɧ4s KjSKFSm|] a}ՅAɡ+ml `fK'%:SղOG1 kb|d}]B>T jfRgZ\Fl'OO| Y&^pg&k#pko2@sPfH/{#I8G԰%n%JQ\`"c8-ŵs0fbաu=N!6ul'@U }Z! 2L{kOJ|ۻoFEb͉ RE\c(ƚi`jl'J'-S10l+tn =I GJ#}qRcwL+]F5 ^sD>c1 QG# Xe 1+ޢ qLpĢ~! dQxe2y~ J/)& / 1}bC0j|N0mߤg۲Iiam׊R@Ge-^vҫ0M9ø.TM8.PumZv:|< cv;[f)pHeXۼbk4H[Kԣ9Pv iy*V>-MOlCŵ/jb=$E E졼F;2"JĻkAN&Z˪K6T!]TBKƘzt˻ZklBQWG3_ W LPf\Q` EjG&T)C8kc:U1m=bTiqźf\'+#>c2 4CQr4S,Eτɉv9C?2M_^5 3蒼 ƻ&Ά٬O.\l{u n ~/ u<$:H@gSlP{@QT`O* } ]rڦX?e[\ =Iĵl[=E; >H2!2s7'g~IDqW5%XuSO+H UǪ$9%z |:Z.~i߳hг'D;ˈ,r7T*R Yؗe.G^Y�0;5FNOho&#p:CΤ|5O 1O>G*zkpk%RlWBo v\҉=W9N$ZcLmP߿l7g9{[Is4EQ Fy>8k\Be'NpRO&vr*U9ص>80LT@]a$ 4ŸnwϮtmP˞>:X>]ծ"T轃qP%#~F:z@IlP2e2^pʶ  z:}_؅jmw/W</24䑐-+Rl##'LJE 15Qhqy;w>4ji.H],mz0}48MTjLbb Do}d<Vݦ.zq)8e79hj!He Ui3~2 .;Pq΋Ğ+aB�5ȿxF e&&TWV�>tG\ F4]TDNRR'/Nw]DŽLTICHx#8$F:)fKC]$ax)k7#ZWч ~)�:0: FCR;Ol$:-.5^BzAp x-V:v2VŨܶn [ȑS#rdapӾQ[ߊݫpD#¿!ϸ\t$Ƥ nM*c 4sˡq㎡Y^ S0d%CoJKh(Q@ĪۭX8N\Jz\]sEˮc!E+fqn6_rQqO$>a:5y<LaFsŊ OjTatՠoKZ%Uk0߱e<Lb1Rippp 7)a;$EAh달'ΙP%h__lG1m╋y^!a :$|c)F9/xoE2I} e=0ubbb{Sbyk. س!uw=HBb4uUn5Kd ne4PπT<bQTiIl([1$dK*&80D/KqꁉRM4 iuU/C&3\BL:JXjc[M+4w}`~\% Qhq}ɫ"P=b=6=ٝ+_ܛK5*xײr{WV$A= jܞ^nza+`5}o*2la}J\R.R7#bH[ORQ+Gb9,k:xhS%$qzPAh P{Ȏ^sFf7!@)qHRut3/3*Wu+]q43cT4y =MU[B|e22Mz[DCg>Na./CehjUMe#d�!@۾yl{}fYfD([":1J쩠0A/y=jRO6bkC!|-jϬɕasd9Օ.E1m5鯜pY)5؈6i](2B [N�a9e)T4x[teài,r)k1>�|۹qn6jXg ?I<a~\Sc�emJDDPTrKAt!ߝ1 z ~(t;f�Ui{d A{.A'n'u_Qɧue ,v41b*A]vQq/Y)$[#sI\2f{LtQl|W}8,�72p&`+^�a cfv{xh7>7oAlrFw<<{[d^#Y2YvHf#>nPǟѳ;:>M~{JB͆Ɲ"&9p90̙g:-?B['w}H2q,; ,<rƞJFDcl'c=$(m*[˵SOm1J; LI8mCa6 FLҶn @`(mm Wvً2ńF<6K@ I'R_K=Gl|-PMbEfECPGޞ%6_�1.ʜND. /aT1 tjHG ƃA5EFd!yÑd=i0A[QŴ~ʉov1{6&mJ5 HpM¹2X342'. hћ5m؄PLSI6%/ۥc l4^M C4nqZ @dYq 45R>%9~H tGRPͰ& uzHIf?N.k9~A2|wJU쉺0*Ī5J *�LfScR!$7ѭL0i8 A5F^A0qwDdHۗRQjrH,#s$#'A1 V'HPq{Kݟ- eV5jh!"3F_O2 an ΞߦZi w�yLUOT;cf hחrr݀s'J {rpGAdF5q*M;oLb22 ٫;&0'Jj;Wi{x7-1QV\S9ޏxf#¦.o "{ egzqhmge9vq7$,zj#g<m$b _ƸNT3'"X)IQp]D:ɱ5鐓U߆d618>e7;?EVMOOM:[Q!OM)Lշ>)"%+~ J __〬vc`3\`֏EYk>}ZI;{N<5pXYHRlڑ $:#$>+ﻉ{@:5|ͩnc85yC2@Gnq$f�ad˭(&V免 WY*eg2ш:Xԛ7{VeJbڀY?9<oRPp~ir m(%)Bj±'㠜!:'BjXŴv , T@8g~}!( ClZ^pV9M)?$}SFɮ 8Si-*C=3$ʁvBLO"n#a3�_ŝTO2ƴZk6?_X?u&[[a9�F,'nm|@HFd5 BIwnǮjk tҾo2zhzSxTN;hxreg>̾L=i7n>ٽLṄD6=+|p9#<T᧼д#thRή³h./"pXϬ?WZd94Ge I1$J7c$Ua ׺*!?9k,2pl /oNO09jb) A J{SbccA)\!ʖ[J)>:Ί]^{r<rXK_e1ȑ/E>L@رc\Ǒ#fl܈k"btQP喚3eڇjxAgcieҐ"kڙmy"6>aS]{/t#7K#yI &L?|ʶ* tTb H@N:r p�MbI-<."H#9ZөWTH eԠֲ"CCscÈ)o7]}Zeŧ׍$^鈢 M8#wyѪJnj,_<q^Nnx9D�oB&౺RM#[duN('#)@7; \oŔ#ЏS6j詐LқDKFu@�ɫ.`7?*d jې MiY`iL 2?PMTiy>#gdM6y'gkn +A4{*Q*#{=wUkq%q ևk L SBi`^(bZ ~o'7411$ i?�WdҪ1v2ܰԉ6i* %> xyZ8ͷyoG^SQK/Ja։y").g fhHo%6}P\k`p6Y)|q;`hGT^ٷO?</)($k61 ) wX- ,ۇEP۸nlpyiWQѾ1.] { b*ŹȿxtF;~<c`5>Վ4ð !$PE4BYfTCV"Y2 ]eYْ?L20qZ5uYІޯ~S6 PC&(1Kvߜ&?] #S# D/V0 ]1Ho,6~i{iу,[5� ' @u; saQǔ0Vܴ)v X2^u8 8lx b"`38(E"(Ds%y%*'7AVq~"<C3 rXSAhN¡n<bJɓyV5hpШ@n= cَ]Ӵ~VQ]x0|Eڥ !{ Co̙;<</&ʹx)b-~e"hMAehX*`K^-w.Vު5]LhNM\)"Ԃ$ϭ_i]㒽c(:Uq6;,a~_:EGV:O6jnVMV`iHxj;1h(qmx*Ft˯Uċ rYKG]L#oQxajb.hyL^ST} f8SUxrb<QT[.mG`5%EEy$~He&:C]:u*r[vu$@]7;ΤQŞDttەwcrES JOopIkl\+=pk$G O"yg|omwpl}غ<6F3 ufQ, K mgjh?knӯgS ̦? P_}NmcdompNꂖ]~|XAll;W/)c%{xFJ -;1cqeI\s9 3>$o3:?E_/W6: Z8g㤾/B@ҶQֈ'6 gG-#(h@_:u�y ^Ca -|·try(PCc1fɱ%vpzQ=M?l On+,cy7C�ol.*Uv—.\+yAwB UGt2vGM*fkMܪ-9h6Rs`�dˋl<"QJLD"l _Iծrxj6WtMEKZo|A_Jy?&ƆiˊRӵv$N%YkYdYl qlAw:T4 y@=SDu͊9ǐ˲kSOQ^RzנذiX;HLJarC$5i۠ƣ�MC"<ǫZBJ!UU ^Fq=ugpE?-^:7』=J:|؇ÿ{ DGۿӃ 1=)``G:{~}9l".8'`]'^)?-NֆUEmah W);mc-:q~J2 7O^V !<f0V� QW%,}(ZA_P<in ]KXhkyn\DXVza=Kh94] )D_$op12U'|s}N8yiȵʼj^)ɓ>Cɾ񖲢mtډ*>>c9)U΍+ս6�/pihlW%3F`ũjWn\hcZwC;VAЭcB:/ǚ2st AuqXf/LNDt h[A*P5Mb=nRM߮kvqC^'-T`nBx iȻ(9)/r`wᗱv]?&nwT64j"@e@Z &)ꕹԐ 29ӻt29֭e1�kp4J=~UGbFa&5 E KLgf[ua_}.!Ā}]fIl#O&i7ڊj0 лױm ^6"ۃ,6S* ҅7!P5F $/-pMϙu,x}gu{Rc <m-7R捂U V| ~]n#ˈ:^]'>e<[2a_H|CG4SZtiLkz=>Lc|\ο\_DX&,͸a_X⻖&:%@Uel$Xe7k{xaS2W՜ӟhT)�FcXۏp&_(Ed)зs' ?R>=dQ\jok!an`dWqz}O)>1Hgi෸qJ ,S4w eƢ ]ݩ/ĕ"d 4ѝlkyI?ʷ9 jl"! Ma {V�h^fquCbcό]e% GG}fq- Gz5\94fRfxf=1v9Gǟ(Cq؛\,\�gmFM,j&KȆ.55+IȥOW6;Jpŀ_h\ۤǢA9M֒ш /Hs|Hw⤅&I`M̑ B~{I MV]7?>w' "mhEJtNujG ļqG* ` :}hIlIMvgJ,x AN8@̼BKG)1!p pm/*+72]"3Rcvgq#/�M_ _X%*<tuBgnǟg+7 W7E#?D>&7!G[xx3oҕ^S (S|m0C#�R?_Z60]:D,.dw| ~٬ĺP!jogwd1ˤe_># qOWy!R?8ތs޲hVdBnta!K,ϟ9Vh~YE3fa@Gl f2ђVHrKIp�v6Ns׫3Ti֤+kZI4{.(K%y㑧XǬ!2ر5ه6ԣAÆC?I':b| KzY/dfh!?brkP5_J(k=Fȸ&AU)fQ<as+H!ݐVdZT4vț?mF(ZwO2kwVR_Vn҈uXn"o[݌ך{'&z=gN2?tuj^0{MX3z`ޑ~UwV2fd*&QZ+9ZjT5�;D w,Dg Ȱ,13NFpבD0 .zN\9+ D0bP̐F2׻?7 3g3r%gPlZqY$7y}Nv2_E.nؓN { 5%-16SKsӐ!.xh@ٺU5@]GLK־V>UDsJP7%`%h}6Y@(vwp=ڰp$O7)yy4cF,bCxFfN:sQ=cڃJSN6No #Z+Zw G[F>~~Y $7h{yd%#-Ns< l~aeqi2-5"X6empJ%]z~:8='WUpa3 [[&ԟ`κ#3'Z#{ ֎SG{z }`GƔ[Q+=Ui*Wӷnfn(r3_)fҔS tD- X`2:/&^HJ] 1jPZbw+X�14S(/l!A}?2`A(b%w$Fx/w[I_d.(nC|l9&tΥOGQ)A!iNTiQ,}d[*Y}&:[.)Fj12ǜ:L#k&)l*YB 0ϙF]hsoQ&C0C@Mw:@jp?<y5Q<t3,3waoド1Mxcz <3=hݎae!K|ҹ_f6[Y(Q_*OCa5r5_B+/.Brl^7V냗~HVqC| & [n0,Yg&pmXW!W -6gx : i.kxD0W2gL􋟗m |lF"A;|9ǹR B|?W<=Yz0\eKmXٰh!S%#Qr8n/[N|6մv_s~ 36}T<BcM2~,y.GԟmtQCotdZZĮM> XZm% >%mGg-cJ�QH0.Onaj5�+ڴic]Qe@]hL5x=70O,aP:z'*M7ҪO7x|ߐ3U۠Kchu_/.,Fh3\j nJ(�96)p8բ\�h72ѥ߳ab?J6bVCܨT #B l@C͢uKK]Q+WZ<V}/xTM4zwYLڹ:!ꀭv"{~)߉q>ŜZlr 1ֳd m9C+Q֨{2@kޔժ՚[EbǍD@E8Ͻ(:%kVG*wZ]aUZx7DkW0[R&b3ƧRhfq#pɖ\9;u!`0lS x0WsY#;8JN3<rڬ(kR9XxGlk21z6O+TSngڒj՞ w4b@΢>|$<-;_;tUH2EO8@h`7(a_ 㲈dImI^Z͈�bh?Z5|R~̶@Oe{tq`z+}Fz_ӏ[Y7벣1 U80^4RqwOM-}"?2�<Vm`r ?H/1kMդ,q UjE`PM!f:<#NhRM]OZu-GLdi廅U4Ll`\+9Vci%/VDd{u avE+/> ZTA]H"Йaf 6<~S_Ǝ7"ꉱC�mOZ&H-G�٧AݍB}sJ`^`t @Ju9{?$'BqS/p1ҺpN,'o}F3 I^]CduWԆaƴw֕Ttg[џQvr( A#GjD[wH?(%gY$X{ݯΠW^J-9jc7!";aan` L:HJl\A@،`j ~V3vpbeBNVj[6?O:&v6 S/ }^PR ''F:{ڄiG;ݷ?ɟFknXނPTC(C!_z2^EmT~.Mܯq4R<^ [HW qCC,/w DbQ lt[/KV!c 7:'bsPW=VL-N[fp;u[_)7#X_ p<U8ܵĜnDiȺzo( V~I9O?0;qYp%&Ut l nVw#<sR PLƸ⍷ݺ* ;,3d`akdr9MD$ {YiyǑЯ%(w�Rp vE/N,O"f XؚQ h%4pj\ؿZ+̽ G)Bik}=~Vyx1XtV 2q)'L#̜ oOz^~܆+rL@XuG>| zCnݧ,+IF Ȃk(=WR A\+x;a<󇋍 w! >rJ%$1EρhHH\-7f5M'R6_?]}xBؓĶ9WcV6L8Go{Hd }0T[ݡp4A*>9e\Kɶlw'{8f;r&D?Wr++oR+~&fT'o"Wᛸ-}gw1zwlTtU1[l$SLt3IB A L!eh֎VQ O_b%==A#&QЭ}*>]WJ{͞i)Vb O+T(0? "-XF~P 8K`޾~$6 4o0) 5eB?V<B4=> I;5iuk#YL-IӈCnYU^.Rtm/-XfZΣ&::lv죱ί6Odo%nҨ6Ff#<~ F3AFƕƘӱ)t$뎸JCJo&HQ_d�'ȹq|!IR 1g8Ԇ$oSE4:Q{X=Qȗ!Inm̸kV;V:|.ze11=t+3M-(L]ŕN#:JMBmA3EZ�]>w#gfr<ሎ@ZZ՘$kX1<4tnoA|5T\Io!S[mğ4=O rRڥV Z SJxDZAB$A"tO YaA~h)n&"d7pz/-[QDrID-=Uy6D&脷iP,A1'3)}WØ~^K=z1.X4\Mytl ޴@;6$ GZp!N:.uf1ٱEm۟Z6\kqGr3$P{ =\P[t$l|Cy˷@ݕ|"He_ &<FC^kQ7h6T1g,"›!KSJ۾9a.r˵tqNە( m۶m+m۶m۶;mtsc(GGcPߩ8/䲐VD�L ۼbR3v8L[ ,́%"OFD1;%\RS2qNT}>"r?Fl4[)hy:]{ UbQbC)0 f�&[wYrs;N$uV.>5WYPz|уvFܙe4DHˬ~`j:U7.Ɍȹ;/ں 1ՉJ `g̖:%uѩT33WE4503gvbU`KX8NFЁN+]h7)*5}_N[&3뇸pL ̖ӉR{6G1lk$PN=6 "U+6( {bk�q�W/1 )Q�_zz(�uc* uO9MPDMDO/^j "D9-ITCjd*r =Oă-Y@uu}2Vj5޽gP>dDByw>N{oS<2ߡ~]C&J4 1n&ڋj{[z))opx+i96S(CM (9z@KX2vi "Gn";Aj@a'ܹ.EFͤd 0pU #+0ߋ "&~H&">b�]sZӌ+`߳ɸ͆0TUUڍ |~cX%]VT;u]ĤD 2{%D(8SCLOp"#-Uw^�ek] 4[TeO)FA6C5Ha&oAJpЭvA!]D$xpn\7[)Q hhgH/r-sϠ_LS K{,dŝZqVxJN75Rc�mpFNWVg8.k8I<GM%8"m99\ܑJe8TH180yxLJQ<pVS7Xw?"u'ZR8j51}n$H Q*;~p`ҸlKUg ,t2S>~%AtWK 1h{>xyH@M?$+8,]v ,%?Q2vو]E?4 B` t@|Ot-wf\Ɯzyp{IR-²7^eX<Y@*8' lԹ-~Z} *M9wz+̙Ked4l4H($e_)y"L)8vϢFT_�GL?THY@^$Uu 4 6x=瑨_{bϥ h/ _�0X� Bk)=(MEՖgVVGF\|] vYQqEᑪ�Xd9GT`1yZU6leNі}9xiM(b؝E^םa4v#S?]F`vH0&O r=e*2 xYI Or4hWyɀl7+6~* =lPTЎS*eB] '- c  _\ͥd񈶎H &YhоBJV93㲷>=YrE1֕Y9C\)$�qpTTιYq.՝3(}| ܢ*Yt5 %Mƛ_` `FTUzM&=N}&e?f@0pMe%18T lv(W_%]K{H1+^} ƕcxzH2JC)vP�11+A <7y FЯJ%LLeҺ;1"9Jx# D٫޷ҷeeC$B?HWʓeJx~Ap{D#홒i:}^[t:^|<3M< I. )^MuJ3"F_oz\!}ťN8aY*6SgTLuFu|Q_6A'I9[4ސ!Rf8~٫ -~Q៖. c^m/O_k s^8ԣ᪮cJ qPw{Ly|YK6h 1&bRB X7M dž- @)z{oGK$ Ir'?4hB 'z!U>ɨ4X'$sh}LC֍q"5x4߇CߝngTP%T.׶pdqaΖ+fTS`!jIzTWu O)*wLϽD;%_z1%[8Ϫ˅zA\A1k%P0*] $_+_�Otz5<ClܴjRCݝz6~N7vG@)v5,)gxbeYy27cl}k3e.*!IFkaxkӉ Z;qgI)!}-Ty_8\ڣ <⚋G⛫9S`e>@>%|zSFAO /Es`5/S+)hN'4£9zh]1<ܤ^k;ES97 ]R3R3wy>y#p6P4J$QY :wpup0$ Zm&RNLQ/77fsFDy]]a7cS@H%cOdDtZ$lUB rb^>CgqY*/zu1*DLs<-(}RC"8\xLA7 ykO-Te7&k߫D9Wz <norLN87Lki: E;Qou3)]p?"*϶#\ucZ^ҀC2G [&ו6IՔy@P@l~miL F z"K (Ե]BM,>nb?Dm;k2u&_"2sf@*[0ɶ4 5G} `^{ EDі] vDcM +$ $A]Y/ir?7QriW,⑭ĩJ}mZZmEo4Hq%u0`*~:SkN@xScI<Xrm/.iN?A 0zT ]Ң {.!jJ|93<'=ޘ@=1ئ!%a$TxjaR{#9v1+Zt[9CB2E܋#\I3LE(Ә'F/~�3C*yGt;(Urkѕ4|dNKM Xur ܹ젥?3|kUjd&聃G'YC)HrE wڰ�,M?/Ω4NzY|Q"TPege0բd!�V^PBɱ"۟.Ey+>lx2UCfxlSB}nk)zB`șop %&hOIŪ11oaIHQ ]o3֬>A y0-3f] :(Z`SIzJG J3})\]OyUILn?Jt=%z8:ܹ]e@K=UJ(;6bm>:;}' ҲT 1=!&9u~HF|ܹW/ @(:~+<rʥ]%Λ}- RSbZ�42eyZ(=3z۠䲅GpR?XGch[US�#\7Cf#7IO2`(oc"T7H<⑝{ҲL6l*Fc$Hԇ},FG~[tlvf}-FO;'FbgvU TdC=3y|.վO$ˢW2ԡrCL KBmttL(8fqWhde !$[qY4Dv={_Q7sr Jr o9xN{C<yFjd*w^PGx 9 gmB9r�U�51VS`"B= 6b@OqZQȝ(½,+@0%~bҽgO!Ao϶ЬpRCpoFL$V#Iu >ܧKڞˢoy] uiY3?h1BKgߤv(.>N,F%r]6;* U4F&dhӚӒ/09Xnp̷7-gHn:2<is\HC#tXEMq~AT_|5k[^Y$`[3@dgRqH!~e0B 4Fkj_Ac29We1�؉QK,ٔAwg%Mį"\mڬd_2n�nO=V ,Q G֝82j'CzJrM~ Z:B%R?r/"-qxR$5AH]Xl2UCM5W-Z%EwqVm0Po_=1ʫhJxClF'J7f#vWk@R.nE'gZ 6\XHY{٤PZ!v K@ Sc`w^l{@{d#M8"#|0_nEmn]* RQd.\V^ӂGSS48t"C ǘEolf! 9:mC�|. MJX2]bK/JN"zFݾp76wd!ж=_P"rM%x0Jdoҩh) S+#M܌̧̽@^͒HD,r(4 썶@`yL=֞ 0 #P;=Z5QKH*m ܨxUv6T-M(lVxje 7hf+A 9Zg&u`h#ԄxMM,M2MUBծ,=S8H _W<Ö{DM{ u[Emb'Fa#yv^N{uy۹noq(/Wu>;SFS.أ2YlXznV1YP4w;LJ:ԳVj>5k'N~2}Wq<CB"(1_ WEMS&`&f>ŽQ>o=۲NL bnՃX.쿳Q֘tΦr?]AB5EwlP7nu 0R/vx{5 ־:Dt\* _?C)?[xxj/EO%WIJMޝ@vIiUrI8,zDg:KVsQ] یAzȧ?LSrBjJݰ΍e˥^џ  s@U'ka*̝]TkKKiV9zvJ7(yE)FDAVg0K虏MkZ zvZQ04)JxJ:K"J ؁飱 slL$cd9,LQKK;_Wwc)Tg~i:/�Q!ndA~K'r)DށS-lVD߽iAx1*-҃iQyE^E*Y�Y*7q,BRoh8ʽczU%(Uy hOQ0rJM02JGԪ6ח5ܨ8{JR:ɦ/1@X`n5'mWH;`eڿ֚[%)풄+l1t*O/Uj/|jZ$dS+:; E Z.Ռ{ēX8@B>^2ybcvF_EbUqQ r O͇m&M< 6 [_h&Ln ~{b4H<Q"SOBG`qQ;:b'X0$UR ='5!٪ я�sd1a^ax<?_I6*wNeZwI-S@KGi` �@U6BؒbY`*|>Q e'EX٩EeV.WMWf"bp2xi &%kh `,*z'k#_y\vxZLB9y* WӟtzmCNG!/N�xo e9jk^RHȚJl%}vh0Rs" 7L[:X*< R(KHFDj!)}4Q8,l$'C:wOٗNGiVq>ա9V"4R?o*4848΀!dE<na5X ϴf*8M=VRGemy!0 e|S1GA=&8QXl4Bm�Weo#VJ֝`\g%t|,AOP@6'fڭ7aYkRq* 2+}KSupDݫմbpJc[R.Io:_V~'�L,НR0^vj6++b0-7+\쐟 (;x~QD 㔭/Ñz$f Bz ׹ z]ڡ>*دBltow`V* 76>>1o5-3 \`Ax`#6QgLK(Y*8`x(ͯ[fؒݹ#l!Ǥ[A,6-\qʶe7{Gsi. o\-bBrzb.$"t#%7Q9 a F*K<"ʻe&_1ߋl>/s>uf?w(CҩJ@SN:NvkJzQ*ّEYJB�nLKKx)1a&Sڀ|*)]%} Em %1g克i\p|5-6NglJ-D`*QlfۿFTpяt4sFGz>jݚW�MPDkA'ީd%Ra(wJV7Ͻ}t>YJn ڕJ2>F*nyoA'ە)~ȼ>BDZN`bO=Nx?[g߳]8o>SAeh1X% n(Qxx.Sv̤&l%d\fy7/pCĭK}k l%PXvGZϻ q2J]+&W+0VÓ;Fၖ%5渒EP:*Jos'LPvOpLͥ=";i@lRTHxDNLe&1UYuFaD OfMeK]˜m{Y~ ӟbc/O'7q|6#nKQ�16⨬n(7y!G�Z\82TAg`$Zhy -Kcc-ԩ8{ObsOwr262mwa@#{`mwvlye*麓/ŖuP 8˞8?pj^DCS@+J<d6@Q2 GZr}3h]=:qy{�CQ7O0"P fX[Z zg`,2:WX!n+G@ <=ynE'q~2:1rb{IDĠB}"N0㶷 XX6jrYdJK5W (W$]ŢPkciMP "MT&Wں#8AE'w\hڞ}]c}ηkb`%(W΁,q& 0fs3{|A;#=`P9ZzdݮIueIKj;3#5?iǡ:J)xHn K$SAa6kxp2>!Mx E+S@wW|puWh%GVBOvQJz,'wolμaZ�̠v".ρ v˘EM/$R->\V6$EM 'h{hG1AQv Z<EziRZD8~\d ~r@32`CB')7Dsn~ $ !l\5B-_WZrwsG]L^t7C|nN]и:oΈxU娚W7L{ }(D\>!UCz".3V!Y魶I͒ie2 jSUJ{;܍1zӝ9)x3PkW2Jf.kcмq%. ʎD$ ۦGN'vWɒn 930Mp뙏2,"f >mԺ\Uޝ 4cnaWA57* RVk�𽉶 澊.[Ah Gdu4dj]2:x^7Xգs!OE~5On Z1Ai!\>y3$'q3@#`˦)PE,ہB_u9T%(z^!XLN~*"w1l?J6 jpyS!F D1Z-ۇsm VfwCע 4uYf%k\p5 ~!@>7=*Qxv "}Iay=-ͅl;'�!]~*ZsuXZ/%/? 8L75j'o]ѡϥ�Y}ZsA_0}JHA]eF_͜� KsJ/V|^D`AWhTOBCĩoA3u~ Iei5x7^ U*M$^Q:&C8ݞϣjJ{HK:c+o.%˘h1K+'A0eb= ۅW#giY_{|5/Kpi:ַڑ:J{o)qɢ<=‰ɟFktK^Αo[csexr[ۖS8 kЗ*O5OuѬREme .ZO޿9(nbׄ++<8,؉ k\ydm&PM2諭\pY842av�+#K嚋#cVԁzSK?Hfy9q+ I*x=D]0;7wh{-́ǵ:(I(S4'<B1�c9ay 8ِ~л ظgfj穖GrY{5$RG<p[&5cdUGqʞD]t "nTh30a " \ -)-SCPՏc.GH[ؑn3~g{F-Tu_VN|f„lz]!㟙f"'V;ty1'.qqd7Mq+KTQ^F1OI3g[!"v8ÜH?D쐙ƱeG?rȞ$Eq,l[RC/u!;u/V0J ή$msS5g3P. {Qm*Vt">]S�u³o|S}%IĊ&/$fɒnj\[C)R᫏K >fvg]ky9r8ÜFCwCߵ\\ az62goWEl2mgۙTz~tTh\-?qBiǨj#z`bEَ(⿂/K?}(N j TNmwS'0=?@e(gmVD>F+oTz|i+$ %B 5yl|HŴl9^sC "E"lϸud&Bu& ;6D4xi [QuN6L/ֵZnUx1$w<JHӮr6>0oK"|ɷA\qw*aE(0jbʣ.fiا0}FsX52}壖Su^WaMMFr-ׂ0ԉs#_ (c"vǡOok#_m o+F&A<qRE@w}ᇜyfHxC+EFɳV^"rXpsF1?IK+J 錢Xn:u0)f)ltw>3-<˥9f~AݱOPc+&T O$ ӿb'u p�0T?/qA&Nʖq*ttwD6F{DW 9h)JE(pɍeiRfPQvaB9((AXv'-ҸG2ؤNOv]5Q?3Y2NrO,)bEG3"@(0׌TJ_ף~C:5%5p+IflGWQ[m +lƊV#6 ,J璒}8-ԘޘB`G$uo0UsuP:Lg8mWoqf$&zMi.ZH1L<V뭃cOn@`/O f.:9pV)ܤ&COL`e.>.7J?7&�}WlUɑ(ZWCNE> AN>|2(dž+[]5:;w sQ?\M`J<VYurn!@?{X\9\P`ƷusoKFt=CxIƱdGh?WA}>6Ne⣁3R%dE.޳Ʋ ۷Ǒ&Xweoa˖ҭ͔I(cY [(B>*BDNa&5/6(c Z{,0՟xԿ| -.$OYZpsI�aE/d9eC34ڽ-:+,JٕJ"3ȏ+SHCP+b8,&|�Z"?l1")d{ELLH.۞~V/"Xvׄ�n!gB曠֒?S ~"Dsہe'_ª}4X!O{Fc%*zۉqny%+cV{EZ8χ`K\ '�9;6!#["4UwNxM/ibX̴ hcW_*NK8a%ھ`mpH`Y 9n{sdi` ڻv(q&|W=>LYq=!M{zRᬒđS,8S )GD<V+}ǦdES89�D1m�7?T3ykJ̇}WYa$ kQr4X!C3N*;,8!%hg-m146DS>Rؤ(~\8_^ Mrq]l./Dft0U]3pjnz{]W+ŀXn(0 ,oyE(6KBuZS/6fcFQͺ!م0omĖ2dX" s %R Yb�r7\zS=ۅM,1ln'<t):pun ?p 8jr]HܮSҢ򭾨*}?hZh=xcIrqnipz`F Vis+iUon?ޥmQD0w!mK-iqP7tkLS7@8F$|x?s䞮>'3ׯNJKU ]iBW5 b9S?qhoV*2z2~]֨݁fp�z%'8{s©3?ڕ L_F:vD*S(n) >X6goxu-%XͻwrÅpݚ!X!]ӀaV?Re"#Bx>"W%t sr7 -ڟN[, 㷩IWSVmH2q_TpLȘj i"ΪZWXH.;w@2W|eM"&I6MīīGƺcP})?xɵU5"Uc,߱u?SzYDr0 uͮaq4‰_'KWI]nL§k\fr-W y)F"E$qJhk5E̠¼ٌV`WDQ ͑{D u .Ѣ9 Y(a7"ЬV$3Bg[(fpbS ˡhgd>YbliWO󑳫֠ۮ`=9Aӳta&CY¦XzsVd'E[^2)LR'_q]r%iF욓 5\$D+{D]S[#@!S^ܲ{!sy mhKgCq|waAuJɧ29"Qib3�pÉ Ii=^9 ƽ_Uj>y(3p ER6.rJr)0x{󩊍_QO.Z{B)fӍw g)OIM Oasai4G#}u=C" 6=='Oś]A+,YPRqk,<SQ$/ڒ1z{~[S:}$Mu+8AVTNb\γ9mV?lS@zὭ} _Z= C |^]p׎`f(o;c<Jͨ`G9~r)YU4s^Ý^RBi֫H  )+EeC?(IC9d~45 _^'Ųȴy՝&A-hσyHg 5]~䦅u||Y{"Vpbls(1r)D3Ī E3x/ |<Nؑ{zs, fɗ�%.ĭ|�FsT Y .ySM mi�gCHH<ϓ}/4Bf6+U3xmD݂߻}4Akm:B/1DH@D6f;TKoy>=1YV|DpJo[ +ϊuo^ }~:-iXZ$W۸)R&YyG;ѮEj9]y#H=[ 3Qޒ# qj8Rn�`Κ!ۈvܪV4|om'3[ӸU/f"?+k{v endstream endobj 145 0 obj << /Length 867 /Filter /FlateDecode >> stream x}UMk0WhFCۖ,{Mlh$73nw҃ͳ,]}{\Olo$ɝиI}s \wwu8{SC߬Y]j7KF½ Q5&z& h<ϯK)ٔ?pݝ2ZkXvm)85];B7gѻ9x~;a`>W'?y:o&> ݋L'/㫃Bnz_7_t|~;:ذƦoiܰ^\0zu\7g"NFsu_E07H6!L@@B@q\s *<G`X S*0ԄEkNQFJ]r�)u�}泱f8ֆp1F:,p8pg'~ι58Kp^8q )q4} 5'G`~ӗ>Tg ]8 i/nTv<k0Yڲ1g3K`ڐY1q22%/ dchJ(S}Y:scݜ笟PcYB?A9Os֙3\Q.4cX=Z9#>c-+>c_ZZ~Z83z3[:ޭ ߬Lg3t3-g B|B|\3gg|2?z)BXIAup*^+&#sU-'H8qɼe5A78{Y-7^=!U endstream endobj 146 0 obj << /Length 868 /Filter /FlateDecode >> stream x}UMk0WhFCۖ,{Mlh$73nw҃ͳ,]}{\Olo$ɝиI}s \wwu8{SC߬Y]j7KF½ Q5f6ӓ09]nD#y~/XOϦӾZkNTĩ(wA-&T9ˈ;n>yx#7ahݰ^g=a}9_(,u;_r985wѩƆ56}NMM₹ 5E9qv7rk u/A )`JbD>`2$`TY'``9&*8W`TR&4`(ZsJ5RH+h3}76Xš60a�G+gıXF888sέ-.x]/+5MĹPN<1\?ǘt1:˿#7^YH{upQF^odž1BЖEQ?1^׆ƨqА.yaf%+CsV2GYŘS&ƞjЙ??grCOe zYJ|֟uМ8gΈrY}Ŋъ1LkYҊX׊ӊѻߊigngfg/>Cn!>_33/>?㓁EK!c?RMO #SZ1|8Gxp4aj9DQK.h. ljeuȢn�( endstream endobj 147 0 obj << /Length 868 /Filter /FlateDecode >> stream x}Un0CƆ�"RVmkN7RI̤߯]U=ooƾ4qݰZGNc'>wÍzeu[U gҿl Qķ&M'G3Hy_ S)E~e!jءC4qjz( z#neDvBwfs5} FzqۿitXoBjߙo{b?WWZ/ԼiL)͙ \݄Wb MLS!"q �#u!`Nȩ(( LFUjp49cIMh ,hPE4pbvŢ !\΀Ѹ 8!\=#2:x 1v9/8vӺuSqk4 "nXCI8'ľ&p 2<Wcǘ,9Ϳ1bxb Ài'�,ymƌ&Q/kC^،1ۜ1q, Vu 3/d Ͷb l͘S&Ş c,Xu ֟_~CO` ?'>ψ:sh.Xgo\΄rYgBЏ>;gX|&}ggg݉gt3zw3|s3߉YX/gKzJrg^od ,gz)R؇O5_qTԼV j M2GFN(:pTy 8kn":qo{~Y=[|btD�&U endstream endobj 148 0 obj << /Length 866 /Filter /FlateDecode >> stream x}UMo0+J! ᫊"Rj.RI73W3njNelܬ;WW\?pu4{SlY]jwOusR^u5sx0ֳ0;]nL#;z,gS t;en>r8S0qj>w};B U5gѻ9x};a`TG?y:o&ߏE]&AjZu/?v_t|z;:ذfhkܸ_\zu \7g"NyOܵڿB`ilB =@ )U 9yI(J5<T` M55֜Rh�R 1ڟS(yq( buX& &q,1+N978Nsk`q8 ^8% FMq.5Sh@kO ׏p$q1/]}/ĩ»p^`D3F?x[a 1ec!/1g)cd?4dK^| МQV1Կ'1t?ƺ9Y?ГrYs֟'g)437YgD3\ib-z3zs ,>G|ZV|ƾ3ֵ33qgng3tZ[Yog,g[3 =L3z/gd ,gz)R؇O5_TTV *M2GZN(:pTy 8kn":qw{Y-7]%# endstream endobj 149 0 obj << /Length 867 /Filter /FlateDecode >> stream x}UMo0+J! ᫊"Rj.RI73W3njNelܬ;WW\?pu4{SlY]jwOusR^u5sx05rtlf)OS`)?>RTO)suߖSIQg Lڮ9ˈ;~?˥?y|#7~l/3bV+պ};85wѩƆ56CN]]₥+U9q~=W(_KdR$| 4hd52HHNsL FU*q8cMMh QEk%RWp gn~FȋCam `42W0A/c9^'-pʹ)pq[[i])9^�W5js7 Gb_#xb~ ' ˏ1}cLu'No ޅ0&1Ie76Z cx-~`& y%Q?K'!/h],KV0d 1էM=Ռ3g1Y ~i?'?!>L9g)q:#eNC?lыѣc`83ֲ3#>w+>Cӊ݊͊T_|~+>Cg!>cB|/g)g{!>_|&~'a9K!B>,TGbPq<h9<WrBщ]\_tѹeuȢn�A endstream endobj 150 0 obj << /Length 867 /Filter /FlateDecode >> stream x}UMo0+J! ᫊"Rj.RI73W3njNelܬ;WW\?pu4{SlY]jwOusR^u5sxu5rtlf)OS`)?>RTO)suߖSIQg Lڮ9ˈ;~?˥?y|#7~l/3bV+պ};85wѩƆ56CN]]₥+U9q~=W(_KdR$| 4hd52HHNsL FU*q8cMMh QEk%RWp gn~FȋCam `42W0A/c9^'-pʹ)pq[[i])9^�W5js7 Gb_#xb~ ' ˏ1}cLu'No ޅ0&1Ie76Z cx-~`& y%Q?K'!/h],KV0d 1էM=Ռ3g1Y ~i?'?!>L9g)q:#eNC?lыѣc`83ֲ3#>w+>Cӊ݊͊T_|~+>Cg!>cB|/g)g{!>_|&~'a9K!B>,TGbPq<h9<WrBщ]\_tѹeuȢn�&U endstream endobj 151 0 obj << /Length 866 /Filter /FlateDecode >> stream x}UMo0+J! ᫊"Rjn" W3nj<4nV~ߝoGM?k]{7[7rSmtɛy=TCA:fL9 4Rӫ~R~J}6O7SVk-#SkQ70j$#z7Go䎛n˥?qHM0Z7u@O؞OW1Jj;THcui׸a׽`J-zoDpמk uA )`JbD>`2$`TY'``9&*8XV`TR&4`(ZsJ5RH+hg b.h\e`^bsN[sS9ӺuSr�k4"nXCA8%ľFp O<cǘ_81Bx.B Àh'�2xk=6u2,bق6E0F,eLLu /Y1<*T71DV3ΜX7g19=zr֟P.O{S3u9(uF: XE/V|FV|gXˊؗV|ƺV|V|&ޭ V|N+>w+>7+>S} u B|)W|FL| ,B/^ &+jRP׊C8ƒI\U E'j\2wAsMMD>NC/SB=t].Z9K endstream endobj 152 0 obj << /Length 866 /Filter /FlateDecode >> stream x}UMo0+J! ᫊"Rj.R!W3njNylܬ;WWМ~8׺2{SlݤMƓ7}zn݅9p/]A:fL9Ngi<uӫ~R~J}6O7SVk-;soGQ70jfw۷~?˥?4ޛ`~?nu@O؞W1Jj;THcuqqRZ*p}ߜ8y=W(_KdR$| 4hd52HHNsL FU*q8cMMh QEk%RWp |q"/!\,Ѹ8"\ 2:x0)ljsn l9ux\רi"EܰpJM}4$x0.?1Yſpc.\X7ьO*ezl,d mY50ymȋ,aYʘ8 xAf_14g%cxU>ob쉬f 9돱ncsO{(g1?\֟g Yg 9LsQ.(ug^/u ?L[ V|FV|oV|3[: 3 ~!>CO!>S 33>Y^ ?a!SMW,:?8ÇqG湪N$ոd<}]/pD=t].Z/ endstream endobj 153 0 obj << /Length 866 /Filter /FlateDecode >> stream x}UMo0+J! ᫊"Rjn"B73W3njehܬ;WWU\8׺v=ߩonTtƓ7]ziTCA:fM9Ηfi<WOlJQn8N[hEOrDz=4CH޾Gwt>X.Ouá{Qן tzutZ}]ˏѩ]3NN46[w>7,^e]׵͙SwPG*X$D F @F@k} 89@FJuFF#`R0JRq eF)kjBS` F5(Z#.9Bkx>w{7E^ kCX�q pD zAet 8alSM3?rN%NbQDa 8>#h0\?I`\~KWc?qc.\X7ьO*ezl,d mY50ymȋ,aYʘ8 xAf_14g%cxU>ob쉬f 9돱ncsO{(g1?\֟g Yg 9LsQ.(ug^/u ?L[ V|FV|oV|3[: 3 ~!>CO!>S 33>Y^ ?a!SMW,:?8ÇqG湪N$ոd2 }ν_V,z薛.ZP endstream endobj 154 0 obj << /Length 720 /Filter /FlateDecode >> stream x}TMo0+J6*ħöUSEj�9߯ IVcf͏睟ݛ{)^؝}]u:vzyu|CW$nmmΑmq5)M{`qjS5үxO%r^q &\TƦkR@YwDoYia) SZM5_$$>kxq4|;o4vhwqB؝Bf#j{p7P_?{+4}+VYu}e}n.ˍggfj�j{k:lF #QhJq  HQ/e.!Pp #]gQtVTv)#l-g!7'uӾ:[sI r.39uf *gQNxEqV11V啣Yq:54kDCZ+)]Ws8:а/9R\Qrz\8Ç]按Sp/ d8D(B!4׳030 =;fzÞJmw&^0C~/nS0GKW皠NdzG�5cC)!=E^K<3Iò8ȿ q3NOg{ACt~Qn~ɸ\ %1.: *4hH`<4̶E hS!| endstream endobj 166 0 obj << /Producer (pdfTeX-1.40.25) /Author()/Title()/Subject()/Creator(LaTeX with hyperref)/Keywords() /CreationDate (D:20240222111157-06'00') /ModDate (D:20240222111157-06'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.141592653-2.6-1.40.25 (TeX Live 2023/Homebrew) kpathsea version 6.3.5) >> endobj 144 0 obj << /Type /ObjStm /N 33 /First 270 /Length 1543 /Filter /FlateDecode >> stream xڽ[S:+ ]L3\B R:<88vJ뻒ĆpfV~J"TpJ7$D$pC{:%S#]AQz0Byah0s.'.~]%̣J6e 8B(g;C)G�UΑ0tC{.y9#:DrZ<KP3}~%~Ud^EO˧ Gnv?O_]Q栟F3go/"w0 d#»얱*Qڃh~(ɴ'5QIMRIe%g_0=uMLRVd va#`�'pp"B #0)$p) 0 r\Vl\I>xXȲJ (Lr T?sXOx_f)`.z>X!(&d<!%(l008qCy%Gl(2d04* !Ye#3ZAT预8"Oe\YoYgR/c/ zP&OPG!,qb<J9KL+e a0)dnż5su~N q[EpCB "e4ĦA{닓$Nx1#g¡۹lXNe |4)ڲs`%zôIKn<SpZv ŰҢ؋JF_`R(+UX=D@im1H}鞫:<uۻoWo ]:V(gJoB齝z拡<mQrڢ\% XM(SnO;㜯S[d%݄2x;PvfX kȢPo8ݿjJU=]xhQPfoHyyB[J]z6͍ꏺ)Ez<PS$딭QJoJ8 606hcGY)_XB=ԋT'#̩H\]*xG+ݗ:$4֩Cged4Lu[lQj)됒:*꽭c7 <׺i5Xʅ|$GtMi+2C.5uy*ԣ;QShKʬ͵C!P5뼱tYXkc51|-^[`#.52݈Mi'm}%Y<[ysYEEMl?6 |Kf(Q-Yllĭ8仡﷉̦A2K5Ļu,=c%8 -)ķU Piu+GH6&4X=7[|f�MR,%7d[2Fw-(:5si|̀l(,Z9JPAqp"\j Zpr]9W{ [v-ͼw:h+CZ)\Gh:UI蔵B9`oZ0 5W^o}7^Kz;ԏp?4|M-o Q?GD ץ|f2 7Gk endstream endobj 167 0 obj << /Type /XRef /Index [0 168] /Size 168 /W [1 3 1] /Root 165 0 R /Info 166 0 R /ID [<58F2F75EAE602797B3CEFF226999FF09> <58F2F75EAE602797B3CEFF226999FF09>] /Length 431 /Filter /FlateDecode >> stream xҽRAs`@"H "~' Ce^bi`pOg{ޝcfv%u##=j7H)Ԯ.@ƨ]#BLPk&]zvt .&R4B\]YFݫV6nh2}PH: :!._ wne}Vuk׾ٮ,.A i#x 006}E#NSJ0eRU) ̺Mja^[Y%xV- w>GknKtԺ^҆OGi}mWiw/ߔ=XQG 橩H6s*G '_YXV-hJ J`T!x@eA endstream endobj startxref 215088 %%EOF �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/doc/Makefile�����������������������������������������������������������������������������0000644�0001762�0000144�00000000626�14565700136�014510� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## ## RUnit ## ## utility ## create PDF document from dvi (usefull if R CMD INSTALL fails to build the pdf) ## $Id$ ## all: RUnit.pdf clean RUnit.pdf: RUnit.ps ps2pdf -dEncodeColorImages=false -dColorImageFilter=/FlateEncode -dAutoRotatePages=/None RUnit.ps RUnit.ps: RUnit.dvi dvips RUnit RUnit.dvi: RUnit.tex latex RUnit latex RUnit clean: rm -f RUnit.aux RUnit.log RUnit.toc ����������������������������������������������������������������������������������������������������������RUnit/inst/doc/RUnit.R������������������������������������������������������������������������������0000644�0001762�0000144�00000004415�14565700136�014234� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### R code from vignette source 'RUnit.Rnw' ################################################### ### code chunk number 1: RUnit.Rnw:251-292 (eval = FALSE) ################################################### ## library(RUnit) ## ## ## define sample functions to be tested ## foo <- function(x) { ## x <- x*x ## x <- 2*x ## return(x) ## } ## test.foo <- function() { ## ## checkTrue(is.numeric(foo(1:10))) ## checkEquals(length(foo(1:10)), 10) ## checkEqualsNumeric(foo(1), 2) ## } ## ## bar <- function(x, y=NULL) { ## ## if (is.null(y)) { ## y <- x ## } ## ## if (all(y > 100)) { ## ## subtract 100 ## y <- y - 100 ## } ## ## res <- x^y ## return(res) ## } ## ## track <- tracker(); ## initialize a tracking "object" ## track$init(); ## initialize the tracker ## a <- 1:10 ## d <- seq(0,1,0.1) ## ## resFoo <- inspect(foo(a), track=track); ## execute the test function and track ## resBar <- inspect(bar(d), track=track); ## execute the test function and track ## ## resTrack <- track$getTrackInfo(); ## get the result of Code Inspector (a list) ## ## printHTML(resTrack, baseDir=tempdir()) ; ## create HTML sites ################################################### ### code chunk number 2: RUnit.Rnw:312-321 (eval = FALSE) ################################################### ## foo <- function(x) ## { ## y <- 0 ## for(i in 1:x) ## { ## y <- y + x ## } ## return(y) ## } ################################################### ### code chunk number 3: RUnit.Rnw:324-337 (eval = FALSE) ################################################### ## foo.mod <- function(x) ## { ## track$bp(1) ; ## y <- 0 ## track$bp(2); ## for(i in 1:x) ## { ## track$bp(4) ; ## y <- y +x ## } ## track$bp(6); ## return(y) ## } ################################################### ### code chunk number 4: RUnit.Rnw:342-345 (eval = FALSE) ################################################### ## if(any(a==1)) { ## print("do TRUE") ## } else print ("do FALSE"); ################################################### ### code chunk number 5: RUnit.Rnw:348-355 (eval = FALSE) ################################################### ## if(any(a==1)) { ## track$bp(2); ## print("do TRUE") ## }else{ ## track$bp(3); ## print("do FALSE"); ## } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/�������������������������������������������������������������������������������0000755�0001762�0000144�00000000000�14563457515�014311� 5����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitPlotConnection.r����������������������������������������������������������0000644�0001762�0000144�00000005402�13267374743�020516� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for 'RUnit:plotConnection' function\n\n") testRUnit.plotConnection <- function() { ##@bdescr ## test case for function plotConnection of class: none ##@edescr ## requires X server to be available for png device if (!interactive()) { DEACTIVATED("plotConnection uses png device which requires X sverer to be available.") } ## 1) no counts conMat <- matrix(0, nrow=5, ncol=5) timeStamp <- format(Sys.time(), "%y%m%d-%H%M") tmpPlotFile <- file.path(tempdir(), paste(timeStamp, "connectionPlot.png", sep="_")) ret <- RUnit:::plotConnection.trackInfo(conMat, tmpPlotFile) checkTrue( is(ret, "NULL")) checkTrue( file.exists(tmpPlotFile)) ## clean up try(unlink(tmpPlotFile)) ## 2) num <- 5 conMat <- matrix(sample(1:3, num^2, replace=TRUE), nrow=num, ncol=num) timeStamp <- format(Sys.time(), "%y%m%d-%H%M") tmpPlotFile <- file.path(tempdir(), paste(timeStamp, "connectionPlot2.png", sep="_")) ret <- RUnit:::plotConnection.trackInfo(conMat, tmpPlotFile) checkTrue( is(ret, "NULL")) checkTrue( file.exists(tmpPlotFile)) ## clean up try(unlink(tmpPlotFile)) num <- 25 colNum <- 3 conMat <- matrix(sample(1:3, num*colNum, replace=TRUE), nrow=num, ncol=colNum) timeStamp <- format(Sys.time(), "%y%m%d-%H%M") tmpPlotFile <- file.path(tempdir(), paste(timeStamp, "connectionPlot3.png", sep="_")) ret <- RUnit:::plotConnection.trackInfo(conMat, tmpPlotFile) checkTrue( is(ret, "NULL")) checkTrue( file.exists(tmpPlotFile)) ## clean up try(unlink(tmpPlotFile)) num <- 25 colNum <- 3 conMat <- matrix(sample(0:3, num*colNum, replace=TRUE), nrow=num, ncol=colNum) timeStamp <- format(Sys.time(), "%y%m%d-%H%M") tmpPlotFile <- file.path(tempdir(), paste(timeStamp, "connectionPlot4.png", sep="_")) ret <- RUnit:::plotConnection.trackInfo(conMat, tmpPlotFile) checkTrue( is(ret, "NULL")) checkTrue( file.exists(tmpPlotFile)) ## clean up try(unlink(tmpPlotFile)) } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitRUnit.r�������������������������������������������������������������������0000644�0001762�0000144�00000052531�14563457515�016625� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for 'RUnit:check' functions\n\n") testRUnit.checkEquals <- function() { ##@bdescr ## test case for function checkEquals of class: none ##@edescr ## integer x <- 1:10 checkEquals(x, x) ## return value checkTrue( checkEquals(x, x)) namedInt <- 1:10 names(namedInt) <- letters[namedInt] checkEquals(namedInt, namedInt) checkEquals(namedInt, x, checkNames=FALSE) ## numeric checkEquals(pi, pi) y <- 1/0 checkEquals(Inf, y) checkEquals(y, Inf) y <- log(-1) checkEquals(NaN, y) checkEquals(rep(NaN, 23), rep(y, 23)) checkEquals(9, 9.0) checkEquals(NA, NA) checkEquals(rep(NA, 14), rep(NA, 14)) checkEquals( numeric(1), numeric(1)) checkEquals( 0.01, 0.02, tolerance=0.01) tmp <- c(0.01, NA, 0.02, Inf, -Inf, NaN, 1.0) checkEquals( tmp, tmp, tolerance=0.01) ## complex checkEquals(complex(0), complex(0)) checkEquals(complex(2), complex(2)) checkEquals(complex(2, imaginary=1), complex(2, imaginary=1)) ## character checkEquals( character(1), character(1)) checkEquals( letters, letters) ## matrix checkEquals( matrix(1, 3,5), matrix(1, 3,5)) checkEquals( matrix(1, 50000,5), matrix(1, 50000,5), "large matrix not identified as equal") ## language checkEquals( expression(2), expression(2)) checkEquals( call("mean", "median"), call("mean", "median")) ## formula simpleForm <- x ~ 1 checkEquals( simpleForm, simpleForm, "simple formula not identified as equal") compForm <- y ~ x + y + x*y + offset(x) checkEquals( compForm, compForm, "formula not identified as equal") ## factor alphaFac <- factor(letters) checkEquals( alphaFac, alphaFac, "factor not identified as equal") ## list checkEquals( list(100), list(100)) checkEquals( list(100), list(100), tolerance=1) alphaList <- seq_along(letters) names(alphaList) <- letters checkEquals( alphaList, alphaList) checkEquals( alphaList, alphaList, checkNames=FALSE) ## nested list with NA, NaN, Inf nl <- list(a=list(1), b=list(1:4), c=list(ab=1, bc=list(list(2), list(NA), list(NaN)) ), d=list(m1=matrix(NA, 2,3), m2=matrix(1+1i, 4,5)), e=list(e1=NaN, e2=list(Inf), e3=list(a=Inf, b=-Inf, c=NaN, d=-0/0))) checkEquals(nl, nl) ## example from ?glm counts <- c(18,17,15,20,10,20,25,13,12) outcome <- gl(3,1,9) treatment <- gl(3,3) lmFit <- glm(counts ~ outcome + treatment, family=poisson()) checkEquals( lmFit, lmFit, checkNames=FALSE) checkEquals( lmFit, lmFit) lmFitUnnamed <- lmFit names(lmFitUnnamed) <- NULL checkEquals( lmFit, lmFitUnnamed, checkNames=FALSE) ## POSIXct sysTime <- as.POSIXct(Sys.time()) checkEquals( sysTime, sysTime) ## raw checkEquals( raw(14), raw(14)) namedRaw <- as.raw(1:14) names(namedRaw) <- letters[1:14] checkEquals( namedRaw, namedRaw) ## formula a <- 1:10 f <- gl(2,5) checkEquals( a~f, a~f) ## S4 objects if (identical(TRUE, require(methods))) { setClass("track1", representation(x="numeric", y="numeric"), where=.GlobalEnv) on.exit(removeClass("track1", where=.GlobalEnv)) s4Obj <- try(new("track1")) s4Obj@x <- 1:10 s4Obj@y <- 10:1 checkEquals( s4Obj, s4Obj) ## S4 class containing S4 class slot setClass("trackPair", representation(trackx = "track1", tracky = "track1"), where=.GlobalEnv) on.exit(removeClass("trackPair", where=.GlobalEnv), add=TRUE) tPair <- new("trackPair") tPair@trackx <- s4Obj tPair@tracky <- s4Obj checkEquals( tPair, tPair) } ## detect differences checkException( checkEquals(1 , 1, tolerance=FALSE)) checkException( checkEquals(1 , 1, tolerance=numeric(0))) checkException( checkEquals(1 , 1, tolerance=numeric(2))) ## integer namedInt <- 1:9 names(namedInt) <- letters[namedInt] checkException( checkEquals( namedInt, 1:9)) ## numeric checkException( checkEquals( 8, 9)) checkException( checkEquals( 0.01, 0.02, tolerance=0.009)) checkException(checkEquals(NaN, NA)) checkException(checkEquals(NaN, Inf)) checkException(checkEquals(NaN, -Inf)) checkException(checkEquals(NA, Inf)) checkException(checkEquals(NA, -Inf)) checkException(checkEquals(numeric(2), numeric(3))) checkException(checkEquals(numeric(3), numeric(2))) ## complex checkException( checkEquals(complex(0), complex(1))) checkException( checkEquals(complex(2), complex(1))) checkException( checkEquals(complex(2, imaginary=1), complex(2, imaginary=0))) checkException( checkEquals(complex(2, real=1, imaginary=1), complex(2, real=1, imaginary=0))) checkException( checkEquals(complex(2, real=1, imaginary=1), complex(2, real=0, imaginary=1))) checkException( checkEquals(complex(2, real=1, imaginary=1), complex(2, real=0, imaginary=0))) ## character named <- character(1) names(named) <- "name" checkException( checkEquals( character(1), named)) checkException( checkEquals( letters, letters[-1])) ## formula checkException( checkEquals( lmFit, lmFitUnnamed)) lmFitInter <- glm(counts ~ outcome * treatment, family=poisson()) checkException( checkEquals( lmFitInter, lmFit)) ## factor alphaFacRecoded <- factor(alphaFac, labels=as.character(seq_along(levels(alphaFac)))) checkException( checkEquals(alphaFacRecoded, alphaFac)) ## list checkException( checkEquals( list(1), list("1"=1))) checkException( checkEquals( list(), list("1"=1))) checkException( checkEquals( list(list(), list(list()), list(list(list()))), list(list(), list(list()), list(list(list(), list()))))) ## POSIXct checkException( checkEquals(as.POSIXct(Sys.time()), as.POSIXct("2007-04-04 16:00:00"))) checkException( checkEquals(as.POSIXlt(Sys.time()), as.POSIXlt("2007-04-04 16:00:00"))) ## nested type sysTime <- as.POSIXct(Sys.time()) checkException( checkEquals( list(a=2, list(time=sysTime)), list(a=2, time=list(sysTime)))) ## raw checkException( checkEquals(raw(1), raw(2))) checkException( checkEquals(raw(1E5), raw(100001))) raw3 <- raw(3) raw3mod <- raw3 raw3mod[1] <- as.raw(3) checkException( checkEquals(raw3, raw3mod)) checkException( checkEquals(as.raw(1:1000), as.raw(c(1:99,-1,101:1000)) ) ) ## S4 objects if (identical(TRUE, require(methods))) { ## class defined above s4Obj <- new("track1") s4Obj@x <- 1:10 checkException( checkEquals( s4Obj, new("track1"))) tPair <- new("trackPair") tPair@trackx <- s4Obj checkException( checkEquals( tPair, new("trackPair"))) } } testRUnit.checkEqualsNumeric <- function() { ##@bdescr ## test case for function checkEqualsNumeric of class: none ##@edescr checkTrue( checkEqualsNumeric( 9,9)) checkTrue( checkEqualsNumeric( 9.1,9.2, tolerance=0.1)) x <- 1:10 attributes(x) <- list(dummy="nonsense") checkTrue( checkEqualsNumeric( x, x)) checkTrue( checkEqualsNumeric( 1:10, x, check.attributes=FALSE)) rvec <- rnorm(132) checkTrue( checkEqualsNumeric( matrix(rvec, 12, 11), matrix(rvec, 12, 11))) checkTrue( checkEqualsNumeric( rvec, rvec)) # This got broken in new R and is explicitly fixed checkTrue( checkEqualsNumeric( data.frame(a=1:10), data.frame(a=1:10))) ## special constants checkEqualsNumeric( pi, pi) checkEqualsNumeric( NA, NA) checkEqualsNumeric( c(1, NA, 3), c(1, NA, 3)) checkEqualsNumeric( NaN, NaN) checkEqualsNumeric( c(1, NaN, 3), c(1, NaN, 3)) checkEqualsNumeric( Inf, Inf) checkEqualsNumeric( c(1, Inf, 3), c(1, Inf, 3)) checkEqualsNumeric( -Inf, -Inf) checkEqualsNumeric( c(1, -Inf, 3), c(1, -Inf, 3)) ## numeric difference checkException( checkEqualsNumeric( 9, 10)) checkException( checkEqualsNumeric( list(9), list(10))) checkException( checkEqualsNumeric( matrix(9), matrix(10))) rvec2 <- rnorm(132) checkException( checkEqualsNumeric( matrix(rvec, 12, 11), matrix(rvec2, 12, 11))) ## exception handling ## type not supported checkException( checkEqualsNumeric( list(rvec), list(rvec))) ## S4 objects if (identical(TRUE, require(methods))) { ## class defined above s4Obj <- new("track1") s4Obj@x <- 1:10 checkException( checkEqualsNumeric( s4Obj, s4Obj)) tPair <- new("trackPair") tPair@trackx <- s4Obj checkException( checkEqualsNumeric( tPair, tPair)) } } testRUnit.checkIdentical <- function() { ##@bdescr ## test case for function checkIdentical of class: none ##@edescr checkIdentical( TRUE, TRUE) ## return value checkTrue( checkIdentical( TRUE, TRUE)) checkIdentical( FALSE, FALSE) ## bit representation identical checkIdentical( NA, NA) checkIdentical( c(1, NA, 3), c(1, NA, 3)) checkIdentical( NaN, NaN) checkIdentical( c(1, NaN, 3), c(1, NaN, 3)) checkIdentical( Inf, Inf) checkIdentical( c(1, Inf, 3), c(1, Inf, 3)) checkIdentical( -Inf, -Inf) checkIdentical( c(1, -Inf, 3), c(1, -Inf, 3)) checkIdentical( as.integer(2), as.integer(2)) checkIdentical( as.character(2), as.character(2)) checkIdentical( as.complex(2), as.complex(2)) checkIdentical( as.numeric(2), as.numeric(2)) checkIdentical( as.expression("2+4"), as.expression("2+4")) checkIdentical( as.expression(2+4), as.expression(2+4)) checkIdentical( as.factor(letters), factor(letters)) ## nested list with NA, NaN, Inf nl <- list(a=list(1), b=list(1:4), c=list(ab=1, bc=list(list(2), list(NA), list(NaN)) ), d=list(m1=matrix(NA, 2,3), m2=matrix(1+1i, 4,5)), e=list(e1=NaN, e2=list(Inf), e3=list(a=Inf, b=-Inf, c=NaN, d=-0/0))) checkIdentical(nl, nl) ## POSIX sysTime <- as.POSIXlt(Sys.time()) checkIdentical( sysTime, sysTime) ## raw checkIdentical( raw(14), raw(14)) namedRaw <- as.raw(1:14) names(namedRaw) <- letters[1:14] checkIdentical( namedRaw, namedRaw) ## formula a <- 1:10 f <- gl(2,5) checkIdentical( a~f, a~f) ## call cl <- call("round", 10.5) checkIdentical( cl, cl) ## S3 objects (ie. lists with attributes) ## from ?lm Example ctl <- c(4.17,5.58,5.18,6.11,4.50,4.61,5.17,4.53,5.33,5.14) trt <- c(4.81,4.17,4.41,3.59,5.87,3.83,6.03,4.89,4.32,4.69) group <- gl(2,10,20, labels=c("Ctl","Trt")) weight <- c(ctl, trt) lm.D9 <- lm(weight ~ group) checkIdentical( lm.D9, lm(weight ~ group)) ## S4 objects if (identical(TRUE, require(methods))) { setClass("track1", representation(x="numeric", y="numeric"), where=.GlobalEnv) on.exit(removeClass("track1", where=.GlobalEnv)) s4Obj <- try(new("track1")) checkIdentical( s4Obj, new("track1")) rm(s4Obj) } ## exception handling ## type mismatches checkException( checkIdentical( as.integer(2), as.numeric(2))) checkException( checkIdentical( as.integer(2), as.character(2))) checkException( checkIdentical( as.integer(2), as.list(2))) checkException( checkIdentical( as.integer(2), as.complex(2))) checkException( checkIdentical( as.integer(2), as.expression(2))) ## value mismatches checkException( checkIdentical( as.integer(2), as.integer(3))) checkException( checkIdentical( as.character(2), as.character(3))) checkException( checkIdentical( as.complex(2), as.complex(3))) checkException( checkIdentical( as.numeric(2), as.numeric(3))) checkException( checkIdentical( as.expression("2+4"), as.expression("2+3"))) checkException( checkIdentical( as.factor(letters), factor(letters[-1]))) fac <- factor(letters) levels(fac) <- c("1", letters[-1]) checkException( checkIdentical( fac, as.factor(letters))) ## nested list with NA, NaN, Inf checkException( checkIdentical( )) ## POSIX sysTime <- as.POSIXlt(Sys.time()) checkException( checkIdentical( sysTime, as.POSIXlt(Sys.time(), tz="GMT"))) ## raw checkException(checkIdentical( raw(14), raw(13))) namedRaw <- as.raw(1:14) names(namedRaw) <- letters[1:14] checkException(checkIdentical( namedRaw, as.raw(1:14))) ## S3 objects (ie. lists with attributes) ## from ?lm Example lm.D9base <- lm(weight ~ group - 1) checkException( checkIdentical( lm.D9base, lm.D9)) ## S4 objects if (identical(TRUE, require(methods))) { setClass("track2", representation(x="numeric", y="numeric"), prototype(x=as.numeric(1:23), y=as.numeric(23:1)), where=.GlobalEnv) on.exit(removeClass("track2", where=.GlobalEnv), add=TRUE) s4Obj <- try(new("track2")) s4ObjDiff <- s4Obj s4ObjDiff@y <- s4ObjDiff@x checkException( checkIdentical( s4Obj, s4ObjDiff)) } } testRUnit.checkTrue <- function() { ##@bdescr ## test case for function checkTrue of class: none ##@edescr checkEquals( checkTrue( TRUE), TRUE) ## named arguments namedArg <- TRUE names(namedArg) <- "Yes" checkEquals( checkTrue( namedArg), TRUE) ## errorr handling namedArg <- FALSE names(namedArg) <- "No" checkException( checkTrue( namedArg)) checkException( checkTrue( FALSE)) ## incorrect length checkException( checkTrue( c(TRUE, TRUE))) checkException( checkTrue( c(FALSE, TRUE))) checkException( checkTrue( logical(0))) checkException( checkTrue( logical(2))) } testRUnit.checkException <- function() { ##@bdescr ## test case for function checkException of class: none ##@edescr checkException( checkTrue( FALSE)) checkException( checkTrue( )) checkException( checkEquals( )) checkException( checkEquals( 24)) checkException( checkEquals( 24, 24, tolerance="dummy")) checkException( checkEqualsNumeric( )) checkException( checkEqualsNumeric( 24)) checkException( checkEqualsNumeric( 24, 24, tolerance="dummy")) checkException( stop("with message"), silent=FALSE) checkException( stop("wo message"), silent=TRUE) ## R 2.5.0 devel example that failed ## minimal example provided by Seth Falcon ll = list() ll[[1]] = function(x) stop("died") checkException( do.call(ll[[1]], list(1))) ## S4 objects if (identical(TRUE, require(methods))) { setClass("track2", representation(x="numeric", y="numeric"), prototype(x=as.numeric(1:23), y=as.numeric(23:1)), where=.GlobalEnv) on.exit(removeClass("track2", where=.GlobalEnv)) s4Obj <- try(new("track2")) checkException( slot(s4Obj, "z")) checkException( slot(s4Obj, "z") <- 1:10) ## missing method argument ## coerce(from, to) checkException( coerce(s4Obj)) } } testRUnit.DEACTIVATED <- function() { ##@bdescr ## test case for function DEACTIVATED of class: none ##@edescr checkException( DEACTIVATED()) checkException( DEACTIVATED("some message")) ## compound text checkException( DEACTIVATED(c("some message", "some more", "and more"))) } testRUnit.defineTestSuite <- function() { ##@bdescr ## test case for function defineTestSuite of class: none ##@edescr ## correct working testSuite <- defineTestSuite("RUnit Example", system.file("examples", package="RUnit"), testFileRegexp="correctTestCase.r") ## this also works for S3 objects checkTrue( inherits(testSuite, "RUnitTestSuite")) checkTrue( is.list(testSuite)) checkTrue( all(c("name", "dirs", "testFileRegexp", "testFuncRegexp", "rngKind", "rngNormalKind") %in% names(testSuite))) checkTrue( isValidTestSuite(testSuite)) ## error handling ## missing 'dirs' argument checkException(defineTestSuite("RUnit Example", testFileRegexp="correctTestCase.r")) } testRUnit.isValidTestSuite <- function() { ##@bdescr ## test case for function isValidTestSuite of class: none ##@edescr ## correct working testSuite <- defineTestSuite("RUnit Example", system.file("examples", package="RUnit"), testFileRegexp="correctTestCase.r") checkTrue( isValidTestSuite(testSuite)) ## error handling ## has to be S3 class 'RUnitTestSuite' testSuiteFail <- testSuite class(testSuiteFail) <- "NotUnitTestSuite" checkTrue( !isValidTestSuite(testSuiteFail)) ## expecting list elements testSuiteFail <- testSuite testSuiteFail[["dirs"]] <- NULL checkTrue( !isValidTestSuite(testSuiteFail)) ## has to be character testSuiteFail <- testSuite testSuiteFail[["name"]] <- list() checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["dirs"]] <- list() checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["testFileRegexp"]] <- list() checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["testFuncRegexp"]] <- list() checkTrue( !isValidTestSuite(testSuiteFail)) ## length 1 required testSuiteFail <- testSuite testSuiteFail[["name"]] <- character(0) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["name"]] <- character(2) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["testFileRegexp"]] <- character(0) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["testFileRegexp"]] <- character(2) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["testFuncRegexp"]] <- character(0) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["testFuncRegexp"]] <- character(2) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["rngKind"]] <- character(0) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["rngKind"]] <- character(2) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["rngNormalKind"]] <- character(0) checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["rngNormalKind"]] <- character(2) checkTrue( !isValidTestSuite(testSuiteFail)) ## director has to exist testSuiteFail <- testSuite testSuiteFail[["dirs"]] <- "doesNotExist" checkTrue( !isValidTestSuite(testSuiteFail)) testSuiteFail <- testSuite testSuiteFail[["dirs"]] <- c(tempdir(), "doesNotExist", tempdir()) checkTrue( !isValidTestSuite(testSuiteFail)) ## same, '' has to return FALSE testSuiteFail <- testSuite testSuiteFail[["dirs"]] <- c(tempdir(), "", tempdir()) checkTrue( !isValidTestSuite(testSuiteFail)) } testRUnit.runTestFile <- function() { ##@bdescr ## test case for function runTestFile of class: none ##@edescr testFile <- file.path(system.file("examples", package="RUnit"), "correctTestCase.r") checkTrue( file.exists(testFile)) ## The issue: .testLogger is the hard coded logger object ## regenerated by each new run call in the global environment ## thereby overwriting the existing logger. ## With the current implementation there seems to be no way to ## test the test suite execution *within* a test suite run # tmpFile <- tempfile() # writeLines(text=" testFile <- file.path(system.file(\"examples\", package=\"RUnit\"), \"correctTestCase.r\");\n res <- runTestFile(testFile, useOwnErrorHandler=FALSE);\n", con=tmpFile) # # execEnv <- new.env(parent=.GlobalEnv) # sys.source(tmpFile, execEnv) # unlink(tmpFile) # checkTrue(exists("res", envir=execEnv)) # checkTrue(inherits(get("res", envir=execEnv), "RUnitTestData")) # rm(execEnv) ## error handling ## all argument checks delegated to runTestSuite so no need for comprehensive check here ## check if any argument check is reached/performed ## useOwnErrorHandler ## type logical checkException( runTestFile(testFile, useOwnErrorHandler=integer(1), gcBeforeTest=TRUE)) } testRUnit.runTestSuite <- function() { ##@bdescr ## test case for function runTestSuite of class: none ##@edescr testSuiteTest <- defineTestSuite("RUnit Example", system.file("examples", package="RUnit"), testFileRegexp="correctTestCase.r") checkTrue( isValidTestSuite(testSuiteTest)) ## The issue: same as above ##res <- runTestSuite(testSuiteTest) ## ## error handling ## ## useOwnErrorHandler ## type logical tS <- testSuiteTest checkException( runTestSuite(tS, useOwnErrorHandler=integer(1))) ## length 1 checkException( runTestSuite(tS, useOwnErrorHandler=logical(0))) checkException( runTestSuite(tS, useOwnErrorHandler=logical(2))) checkException( runTestSuite(tS, useOwnErrorHandler=as.logical(NA))) ## gcBeforeTest checkException( runTestSuite(tS, gcBeforeTest = "hello")) checkException( runTestSuite(tS, gcBeforeTest = c(TRUE, FALSE))) checkException( runTestSuite(tS, gcBeforeTest = as.logical(NA))) ## testSuite } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitJUnitProtocol.r�����������������������������������������������������������0000644�0001762�0000144�00000004017�13267374743�020334� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA cat("\n\nRUnit test cases for 'printJUnitProtocol' function\n\n") testRUnit.printJUnitProtocol <- function() { ## copy baseenv() logger tmp <- get(".testLogger", envir = RUnitEnv) testCaseDir <- file.path(system.file(package="RUnit"), "examples") testSuiteInternal <- defineTestSuite("RUnit Self Test", testCaseDir, "correctTestCase.r") testData2 <- runTestSuite(testSuiteInternal, useOwnErrorHandler=FALSE) timeStamp <- format(Sys.time(), "%y%m%d-%H%M") testProtocolFile <- file.path(tempdir(), paste(timeStamp, "test_printJUnitProtocol.xml", sep="_")) ret <- printJUnitProtocol(testData2, fileName=testProtocolFile) assign(".testLogger", tmp, envir = RUnitEnv) checkTrue( file.exists(testProtocolFile)) ## input argument error handling ## missing 'testData' object checkException(printJUnitProtocol()) ## wrong class checkException(printJUnitProtocol("dummy")) ## fileName arg errors testData <- list() class(testData) <- "RUnitTestData" ## wrong type checkException(printJUnitProtocol(testData, fileName=numeric(1))) ## wrong length checkException(printJUnitProtocol(testData, fileName=character(0))) checkException(printJUnitProtocol(testData, fileName=character(2))) } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runalltests.R������������������������������������������������������������������0000644�0001762�0000144�00000000564�13267374743�017022� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������library("RUnit") options(warn=1) testSuite <- defineTestSuite(name="RUnit", dirs=".", testFileRegexp="runit.*\\.r$", rngKind="default", rngNormalKind="default") testData <- runTestSuite(testSuite, verbose=0L) printTextProtocol(testData, showDetails=FALSE) ��������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/Makefile�����������������������������������������������������������������������0000644�0001762�0000144�00000000513�14565700136�015740� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������TOP = ../../.. TEST_DIR = `pwd` INST_PATH = `dirname ${TEST_DIR}` PKG_PATH = `dirname ${INST_PATH}` ## Note that 'R' was not right here: there may be no 'R' on the path. all: install test install: cd ${TOP}; \ $(R_HOME)/bin/R CMD INSTALL --clean ${PKG_PATH} && \ cd ${TEST_DIR} test: $(R_HOME)/bin/R --slave < runalltests.R �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitTestLogger.r��������������������������������������������������������������0000644�0001762�0000144�00000005535�13267374743�017646� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for '.testLogger' object\n\n") testRUnit..testLogger <- function() { ##@bdescr ## test case for .testLogger object of class: 'TestLogger' ##@edescr ## create new temp test logger object tlTmp <- RUnit:::.newTestLogger(TRUE) checkTrue( is(tlTmp, "TestLogger")) checkTrue( all( c("getTestData", "setCurrentTestSuite" , "setCurrentSourceFile", "addSuccess", "addError", "addFailure", "addDeactivated", "addCheckNum", "isFailure", "setFailure", "isDeactivated", "setDeactivated", "incrementCheckNum", "getCheckNum", "cleanup") %in% names(tlTmp))) testSuite <- defineTestSuite(name="Test Test", dirs=tempdir()) ## need to init 1) test suite name and 2) test case file name tlTmp$setCurrentTestSuite(testSuite) tlTmp$setCurrentSourceFile("Test File") tlTmp$addSuccess("first test case", system.time(1)) checkEquals(tlTmp$getTestData()[[1]]$nTestFunc, 1) tlTmp$incrementCheckNum() checkEquals(tlTmp$getCheckNum(), 1) checkTrue( !tlTmp$isFailure()) tlTmp$setFailure() checkTrue( tlTmp$isFailure()) checkTrue( !tlTmp$isDeactivated()) tlTmp$setDeactivated("This is a deactivated test case") checkTrue( tlTmp$isDeactivated()) tlTmp$cleanup() checkEquals(tlTmp$getCheckNum(), 0) checkTrue( !tlTmp$isFailure()) } testRUnit.getErrors <- function() { ##@bdescr ## test case for getErrors function ##@edescr ## create dummy test suite result object testData <- vector(mode="list", 3) for (i in seq_along(testData)) { testData[[i]]$nErr <- i testData[[i]]$nDeactivated <- i - 1 testData[[i]]$nTestFunc <- i*13 testData[[i]]$nFail <- i + 3 } class(testData) <- "RUnitTestData" res <- getErrors(testData) checkTrue (is.list(res)) checkEquals(length(res), 4) checkEquals(res$nErr, 6) checkEquals(res$nDeactivated, 3) checkEquals(res$nTestFunc, 78) checkEquals(res$nFail, 15) ## check exception handling checkException( getErrors( list())) } �������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitInspect.r�����������������������������������������������������������������0000644�0001762�0000144�00000011014�14563457515�017160� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for 'RUnit:inspect' functions\n\n") .tearDown <- function() { if (exists("track", envir=.GlobalEnv)) { rm(track, envir=.GlobalEnv) } } bar <- function(x) { y <- 0 for(i in 1:100) { y <- y + i } return(y) } foo <- function(x) { if (length(x)) n <- length(x) len <- if(n==0) "zero" else if (n==1) "one" else if (n==2) "two" else if (n==3) "three" else "many" cat("object contains",len, "elements") return(n) } foo2 <- function(x) { if (length(x)) n <- length(x) ## should not confuse tracker len <- ifelse(n==0, "zero", "more") cat("object contains",len, "elements") } testRUnit.inspect <- function() { foo <- function(x) { y <- 0 for(i in 1:100) { y <- y + i } return(y) } ## the name track is necessary track <<- tracker() ## initialize the tracker track$init() ## inspect the function ## res will collect the result of calling foo res <- inspect(foo(10), track=track) checkEquals( res, 5050) } testRUnit.inspect.extended <- function() { ## the name track is necessary track <<- tracker() ## initialize the tracker track$init() # from ? svd hilbert <- function(n) { i <- 1:n; 1 / outer(i - 1, i, "+") } X <- hilbert(9)[,1:6] s <- svd(X) res <- inspect(svd(X), track=track) checkEquals( res, s) } testRUnit.getTrackInfo <- function() { ## the name track is necessary track <<- tracker() ## initialize the tracker track$init() ## inspect the function checkTrue( exists("bar")) ## res will collect the result of calling foo res <- inspect(bar(10), track=track) checkEquals( res, 5050) res <- inspect(foo(10), track=track) checkEquals( res, 1) res <- inspect(foo2(10), track=track) ## get the tracked function call info resTrack <- track$getTrackInfo() checkTrue( is.list(resTrack)) checkTrue( c("R/foo2") %in% names(resTrack)) checkEquals( names(resTrack$"R/foo2"), c("src", "run", "time", "graph", "nrRuns", "funcCall")) } testRUnit.printHTML <- function() { ## the name track is necessary track <<- tracker() ## initialize the tracker track$init() ## inspect the function checkTrue( exists("bar")) ## res will collect the result of calling foo res <- inspect(bar(10), track=track) checkEquals( res, 5050) ## get the tracked function call info resTrack <- track$getTrackInfo() outDir <- tempdir() ##checkTrue( is.null(printHTML(resTrack, baseDir=outDir))) checkTrue( is.null(printHTML(resTrack, baseDir=outDir))) checkTrue( "index.html" %in% dir(file.path(outDir, "results"))) inspect(foo(1:3), track=track) resTrack <- track$getTrackInfo() checkTrue( is.null(printHTML(resTrack, baseDir=outDir))) ## error handling checkException(printHTML("notCorrectClass")) ## baseDir checkException(printHTML(resTrack, baseDir=logical(1))) checkException(printHTML(resTrack, baseDir=character(0))) checkException(printHTML(resTrack, baseDir=character(2))) checkException(printHTML(resTrack, baseDir=as.character(NA))) } testRUnit.printHTML.extended <- function() { ## the name track is necessary track <<- tracker() ## initialize the tracker track$init() # from ? svd hilbert <- function(n) { i <- 1:n; 1 / outer(i - 1, i, "+") } X <- hilbert(9)[ ,1:6] s <- svd(X) res <- inspect(svd(X, LINPACK=TRUE), track=track) res <- inspect(svd(X), track=track) res <- inspect(svd(X, nu=nrow(X)), track=track) res <- inspect(svd(X, nv=ncol(X)), track=track) res <- inspect(svd(X, nv=0L), track=track) resTrack <- track$getTrackInfo() outDir <- tempdir() checkTrue( is.null(printHTML(resTrack, baseDir=outDir))) } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitTextProtocol.r������������������������������������������������������������0000644�0001762�0000144�00000005130�13267374743�020224� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for 'textProtocol' function\n\n") testRUnit.printTextProtocol <- function() { ## copy baseenv() logger tmp <- get(".testLogger", envir = RUnitEnv) testCaseDir <- file.path(system.file(package="RUnit"), "examples") testSuiteInternal <- defineTestSuite("RUnit Self Test", testCaseDir, "correctTestCase.r") testData2 <- runTestSuite(testSuiteInternal, useOwnErrorHandler=FALSE) timeStamp <- format(Sys.time(), "%y%m%d-%H%M") testProtocolFile <- file.path(tempdir(), paste(timeStamp, "test_printHTMLProtocol.txt", sep="_")) ret <- printTextProtocol(testData2, fileName=testProtocolFile) assign(".testLogger", tmp, envir = RUnitEnv) checkTrue( file.exists(testProtocolFile)) ## input argument error handling ## missing 'testData' object checkException(printTextProtocol()) ## wrong class checkException(printTextProtocol("dummy")) ## fileName arg errors testData <- list() class(testData) <- "RUnitTestData" ## wrong type checkException(printTextProtocol(testData, fileName=numeric(1))) ## wrong length checkException(printTextProtocol(testData, fileName=character(0))) checkException(printTextProtocol(testData, fileName=character(2))) ## separateFailureList arg errors ## wrong type checkException(printTextProtocol(testData, separateFailureList=numeric(0))) ## wrong length checkException(printTextProtocol(testData, separateFailureList=logical(0))) checkException(printTextProtocol(testData, separateFailureList=logical(2))) ## showDetails arg errors ## wrong type checkException(printTextProtocol(testData, showDetails=numeric(0))) ## wrong length checkException(printTextProtocol(testData, showDetails=logical(0))) checkException(printTextProtocol(testData, showDetails=logical(2))) } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitSetUp.r�������������������������������������������������������������������0000644�0001762�0000144�00000007317�13267374743�016627� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for '.setUp' function\n\n") ## defined for the life time of this environment warningsLengthDefault <- getOption("warnings.length") .setUp <- function() { ## define global variable if (exists("runitDummy", where=.GlobalEnv)) { stop(".setUp found 'runitDummy'.") } assign("runitDummy", "this is a test dummy variable", envir=.GlobalEnv) if( !exists("runitDummy", where=.GlobalEnv)) { stop(".setUp failed to assign 'runitDummy'.") } ## create temp file tempFile <- file.path(tempdir(), "runitDummyFile.txt") checkTrue( !file.exists(tempFile)) write.table(matrix(1:42, 6, 7), file=tempFile) if ( !file.exists(tempFile)) { stop(paste(".setUp failed to create file", tempFile)) } ## modify options ## current default: 1000 options(warnings.length=123) if ( !identical(TRUE, all.equal.numeric(getOption("warnings.length"), 123))) { stop(paste(".setUp failed to set options.")) } ## define S4 class checkTrue( !isClass("runitDummyS4Class", where=.GlobalEnv)) setClass("runitDummyS4Class", representation(x = "numeric", y = "numeric"), prototype(x = 1:10, y = 10:1), where=.GlobalEnv) if ( !isClass("runitDummyS4Class", where=.GlobalEnv)) { stop(paste(".setUp failed to define S4 class 'runitDummyS4Class'.")) } } testRUnit..setUp.Test1 <- function() { ##@bdescr ## testcase for function .setUp of class: none ## check existance of variables, modified options, class definitions ## defined in .setUp ## remove for subsequent check ##@edescr checkTrue( exists("runitDummy", where=.GlobalEnv)) ## remove global variable rm("runitDummy", envir=.GlobalEnv) tempFile <- file.path(tempdir(), "runitDummyFile.txt") checkTrue( file.exists(tempFile)) ## remove temp file unlink(tempFile) checkEqualsNumeric( getOption("warnings.length"), 123) ## reset options options(warnings.length=warningsLengthDefault) checkTrue( isClass("runitDummyS4Class", where=.GlobalEnv)) ## remove class checkTrue( removeClass("runitDummyS4Class", where=.GlobalEnv)) } testRUnit..setUp.Test2 <- function() { ##@bdescr ## testcase for function .setUp of class: none ## same as above, only reason is to check correct invocation of .setUp ## before *each* test case function ##@edescr checkTrue( exists("runitDummy", where=.GlobalEnv)) ## remove global variable rm("runitDummy", envir=.GlobalEnv) tempFile <- file.path(tempdir(), "runitDummyFile.txt") checkTrue( file.exists(tempFile)) ## remove temp file unlink(tempFile) checkEqualsNumeric( getOption("warnings.length"), 123) ## reset options options(warnings.length=warningsLengthDefault) checkTrue( isClass("runitDummyS4Class", where=.GlobalEnv)) ## remove class checkTrue( removeClass("runitDummyS4Class", where=.GlobalEnv)) } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitHTMLProtocol.r������������������������������������������������������������0000644�0001762�0000144�00000004637�13267374743�020057� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for 'HTMLProtocol' function\n\n") testRUnit.printHTMLProtocol <- function() { ## FIXME ## this is not safe, think about assigning .logger to new environment ## copy baseenv() .logger tmp <- get(".testLogger", envir = RUnitEnv) testCaseDir <- file.path(system.file(package="RUnit"), "examples") testSuiteInternal <- defineTestSuite("RUnit Self Test", testCaseDir, "correctTestCase.r") testData2 <- runTestSuite(testSuiteInternal, useOwnErrorHandler=FALSE) timeStamp <- format(Sys.time(), "%y%m%d-%H%M") testProtocolFile <- file.path(tempdir(), paste(timeStamp, "test_printHTMLProtocol.html", sep="_")) ret <- printHTMLProtocol(testData2, fileName=testProtocolFile) assign(".testLogger", tmp, envir = RUnitEnv) checkTrue( file.exists(testProtocolFile)) ## input argument error handling ## missing 'testData' object checkException(printHTMLProtocol()) ## wrong class checkException(printHTMLProtocol("dummy")) ## fileName arg errors testData <- list() class(testData) <- "RUnitTestData" ## wrong type checkException(printHTMLProtocol(testData, fileName=numeric(1))) ## wrong length checkException(printHTMLProtocol(testData, fileName=character(0))) checkException(printHTMLProtocol(testData, fileName=character(2))) ## separateFailureList arg errors ## wrong type checkException(printHTMLProtocol(testData, separateFailureList=numeric(0))) ## wrong length checkException(printHTMLProtocol(testData, separateFailureList=logical(0))) checkException(printHTMLProtocol(testData, separateFailureList=logical(2))) } �������������������������������������������������������������������������������������������������RUnit/inst/unitTests/runitS4.r����������������������������������������������������������������������0000644�0001762�0000144�00000005661�13267374743�016055� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for S4 class inheritance\n\n") testRUnit.S4classInheritance <- function() { ##@bdescr ## test case for noneof class: none ## test if S4 classes can be instantiated inside test code ## given that no where argument is specified ##@edescr className <- setClass("testVirtualClass", representation("VIRTUAL", x = "numeric", y = "numeric", xlab = "character", ylab = "character") ) checkEquals(className, "testVirtualClass") checkException( new(className)) derivedClassName <- setClass("testDerivedClass", representation(className, scale = "numeric", title = "character") ) ## Attention: ## invert inheritance order! on.exit(removeClass(derivedClassName)) on.exit(removeClass(className), add=TRUE) checkEquals(derivedClassName, "testDerivedClass") objD <- new(derivedClassName) checkTrue( is(objD, derivedClassName)) checkTrue( isS4(objD)) checkTrue(require(stats4)) ## instantiate S4 class from stats ## be sure to use a unique unused variable name here ## i.e. NOT className as this has been used before ## and the on.exit call will look up the name just *before* ## the test function exists classNameMLE <- "mle" obj <- new(classNameMLE) checkTrue( is(obj, classNameMLE)) checkTrue( isS4(obj)) derivedStats4ClassName <- setClass("mleChild", representation(classNameMLE, scale = "numeric", title = "character") ) on.exit(removeClass(derivedStats4ClassName), add=TRUE) checkEquals(derivedStats4ClassName, "mleChild") obj <- new("mleChild") checkTrue( is(obj, "mleChild")) checkTrue( isS4(obj)) } �������������������������������������������������������������������������������RUnit/inst/unitTests/runitTearDown.r����������������������������������������������������������������0000644�0001762�0000144�00000006251�13267374743�017306� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## $Id$ cat("\n\nRUnit test cases for '.tearDown' function\n\n") ## defined for the life time of this environment warningsLengthDefault <- getOption("warnings.length") .tearDown <- function() { ## remove global variable if (exists("runitDummy", envir=.GlobalEnv)) { try(rm("runitDummy", envir=.GlobalEnv)) } ## remove temp file tempFile <- file.path(tempdir(), "runitDummyFile.txt") if (file.exists(tempFile)) { try(unlink(tempFile)) } ## reset options options(warnings.length=warningsLengthDefault) ## remove class if (length(findClass("runitDummyS4Class")) > 0) { try(removeClass("runitDummyS4Class", where=.GlobalEnv)) } } testRUnit..tearDown.Init <- function() { ##@bdescr ## testcase for function .tearDown of class: none ## setup vriables to be removed by .tearDown after test case execution ## check in subsequent test case that this operation chain succeeded ##@edescr ## define global variable checkTrue( !exists("runitDummy", where=.GlobalEnv)) assign("runitDummy", "this is a test dummy variable", envir=.GlobalEnv) checkTrue( exists("runitDummy", where=.GlobalEnv)) ## create temp file tempFile <- file.path(tempdir(), "runitDummyFile.txt") checkTrue( !file.exists(tempFile)) write.table(matrix(1:42, 6, 7), file=tempFile) checkTrue( file.exists(tempFile)) ## modify options ## current default: 1000 options(warnings.length=123) checkEqualsNumeric( getOption("warnings.length"), 123) ## define S4 class checkTrue( !isClass("runitDummyS4Class", where=.GlobalEnv)) setClass("runitDummyS4Class", representation(x = "numeric", y = "numeric"), prototype(x = 1:10, y = 10:1), where=.GlobalEnv) checkTrue( isClass("runitDummyS4Class", where=.GlobalEnv)) } testRUnit..tearDown.Test <- function() { ##@bdescr ## testcase for function .tearDown of class: none ## test that all modifications resulting from the previous test case ## have been removed as defined in .tearDown ##@edescr checkTrue( !exists("runitDummy", where=.GlobalEnv)) tempFile <- file.path(tempdir(), "runitDummyFile.txt") checkTrue( !file.exists(tempFile)) checkEqualsNumeric( getOption("warnings.length"), warningsLengthDefault) checkTrue( !isClass("runitDummyS4Class", where=.GlobalEnv)) } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/share/�����������������������������������������������������������������������������������0000755�0001762�0000144�00000000000�13267374743�013412� 5����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/share/R/���������������������������������������������������������������������������������0000755�0001762�0000144�00000000000�13267374743�013613� 5����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������RUnit/inst/share/R/checkCode.r����������������������������������������������������������������������0000644�0001762�0000144�00000011656�13267374743�015657� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������###################################################################### ## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ checkCodeFiles <- function(fileName) { ##@bdescr ## utility function for code checks of files outside usual R/ folder structure ## requires package codetools ##@edescr ## ##@in fileName : [character] vector of file names (including path, relative to pwd or absolute) ##@ret : [list] with elements per function, that incurred any warning ## ##@depends : codetools ## ##@codestatus : untested ## return list retList <- list() ## initialized before ls() call to avoid listing ok <- x <- c() tmpRetEnv <- new.env() tmpRet <- NULL ## generate listing of existing objects before first source'ing lsTmp <- lsInit <- ls() sapply(fileName, function(x) { cat("\n file ",x) ok <- try(utils::capture.output(source(x, local=TRUE, echo=FALSE))) if (inherits(ok, "try-error")) { cat("\n file",x,"could not be sourced:", geterrmessage(), "\n") return() } newElements <- setdiff(ls(), lsTmp) cat("\n functions",paste(newElements, collapse=", ")) lsTmp <- ls() sapply(newElements, function(x) { ok <- try(get(x)) if (!inherits(ok, "try-error") && identical(mode(ok), "function")) { cat("\n ",x," (",is(ok)[1],"): ",sep="") ## this function will be used in signalUsageIssue w$warn reportFunc <- function(x) { cat(x) assign("tmpRet", c(tmpRet, x), pos=parent.env(tmpRetEnv)) } codetools::checkUsage(ok, report=reportFunc, all=TRUE) if (!is.null(tmpRet)) { retList[[length(retList) + 1]] <<- tmpRet names(retList)[length(retList)] <<- x } tmpRet <<- NULL } }) }) return(invisible(retList)) } checkCodeFolders <- function(path=".") { ##@bdescr ## utility function ## code checks of all .[RrSs] files found in one or more folders ## requires package codetools ##@edescr ## ##@in path : [character] ##@ret : [list] ## ##@depends : codetools ## ##@codestatus : untested stopifnot(require(codetools)) if (!is(path, "character")) { stop("argument 'path' has to be of type 'character'.") } if (!all(file.exists(path))) { stop("argument 'path' has to contain existing folder(s).") } fNames <- list.files(path=path, pattern="\\.[rRsS]$", full.names=TRUE) checkCodeFiles(fNames) } checkCodeSweave <- function(path=".") { ##@bdescr ## utility function ## code checks of all .[RS]nw files found in one or more folders ## experimental: does not convert extracted code chunks to closures ## thus only functions defined inside a chunk but nut all of the chunk code is checked ## ## requires package codetools ##@edescr ## ##@in path : [character] ##@ret : [list] ##@depends : codetools ## ##@codestatus : untested ## Issue: ## local path e.g. 'RUnit/inst/doc' ## is no expanded to full path ## which I would wnat to use as absolute path ## in the Stangle call stopifnot(require(utils)) stopifnot(require(codetools)) if (!is(path, "character")) { stop("argument 'path' has to be of type 'character'.") } if (!all(file.exists(path))) { stop("argument 'path' has to contain existing folder(s).") } browser() path <- path.expand(path) pwd <- getwd() if (length(path)) { if (path == ".") { path <- pwd } ## do we have a local path rather then an absolute ## how to infer correct absolute path? } fName <- list.files(path=path, pattern="\\.[RS]nw$", full.names=TRUE) timeStamp <- format(Sys.time(), "%y%m%d-%H%M") tmpDir <- file.path(tempdir(), timeStamp) print(tmpDir) if(!file.exists(tmpDir)) { stopifnot(dir.create(tmpDir, recursive=TRUE)) } #on.exit(unlink(tmpDir, recursive=TRUE)) ## change to temp folder to dump Stangle output therein setwd(tmpDir) on.exit(setwd(pwd), add=TRUE) codeFiles <- unlist(sapply(fName, function(x) { Stangle(x) x <- basename(x) gsub("[RS]nw$", "R", x) })) ## codeFiles <- file.path(tmpDir, codeFiles) checkCodeFiles(codeFiles) } ����������������������������������������������������������������������������������RUnit/inst/share/R/compareRUnitTestData.r�����������������������������������������������������������0000644�0001762�0000144�00000010671�13267374743�020045� 0����������������������������������������������������������������������������������������������������ustar �ligges��������������������������users������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������###################################################################### ## RUnit : A unit test framework for the R programming language ## Copyright (C) 2003-2009 Thomas Koenig, Matthias Burger, Klaus Juenemann ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; version 2 of the License. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## $Id$ compare <- function(td1, td2, tolerance=100) { ##@bdescr ## compare two test suite result data objects obtained ## on the same test suite ## identify timing differences exceeding 'tolerance' [seconds] ##@edescr ## ## ##@in td1 : [list] of S3 class 'RUnitTestData' ##@in td2 : [list] of S3 class 'RUnitTestData' ##@in tolerance : [numeric] positive scalar ##@ret : [data.frame] ## ##@codestatus : untested ## preconditions if (!is(td1, "RUnitTestData")) { stop("argument 'td1' has to be of class 'RUnitTestData'.") } if (!is(td2, "RUnitTestData")) { stop("argument 'td2' has to be of class 'RUnitTestData'.") } if (length(tolerance) != 1 || is.na(tolerance) || tolerance < 0) { stop("argument 'tolerance' has to be positive scalar.") } ## helper functions commonNames <- function(x1, x2) { return(intersect(names(x1), names(x2))) } compareTiming <- function(x1, x2, tol=0) { d <- x1 - x2 if (abs(d) > tol) { return(d) } else { return(as.numeric(0)) } } comparePerSourceFile <- function(sf1,sf2, tol=0) { ## FIXME ## check if test case file was considered in this suite ## i.e. test case file name can be in list albeit it ## was not executed in the scenario ## thus list is empty if (length(sf1) == 0 | length(sf2) == 0) { cat("\n skipped empty result set:", sf1) return(NULL) } commonTests <- commonNames(sf1, sf2) t(sapply(commonTests, function(x, obj1, obj2) { ##cat("\n test:", x, "\n") if(obj1[[x]][["kind"]] == obj2[[x]][["kind"]]) { if (obj1[[x]][["kind"]] == "success") { return(c(x, obj1[[x]][["kind"]], obj1[[x]][["time"]], obj2[[x]][["kind"]], obj2[[x]][["time"]], compareTiming(obj1[[x]][["time"]], obj2[[x]][["time"]], tol=tol))) } else { return(c(x, obj1[[x]][["kind"]], as.numeric(NA), obj2[[x]][["kind"]], as.numeric(NA), as.numeric(NA))) } } else { ## no timing delta ## should check for timing in second case ## obj2[[x]][["time"]]) return(c(x, obj1[[x]][["kind"]], as.numeric(NA), obj2[[x]][["kind"]], as.numeric(NA), as.numeric(NA))) } }, obj1=sf1, obj2=sf2)) } comparePerSuite <- function(s1,s2, tol=0) { ## absolute file names recorded, strip path commonFiles <- intersect(basename(names(s1[["sourceFileResults"]])), basename(names(s2[["sourceFileResults"]]))) do.call("rbind", sapply(commonFiles, function(x, obj1, obj2) { ## match exact file name in abs. name idx1 <- match(x, basename(names(obj1))) idx2 <- match(x, basename(names(obj2))) if (length(idx1) != 1 || is.na(idx1) || length(idx2) != 1 || is.na(idx2)) { stop("ambiguous file name.") next; } comparePerSourceFile(obj1[[idx1]], obj2[[idx2]], tol=tol) }, obj1=s1[["sourceFileResults"]], obj2=s2[["sourceFileResults"]]) ) } ## main ## test suites to compare commonTestSuites <- commonNames(td1, td2) res <- matrix(ncol=6, nrow=0) colnames(res) <- c("TestCase", "Suite1 State", "Suite1 Timing", "Suite2 State", "Suite2 Timing", "Delta") for (ti in seq_along(commonTestSuites)) { res <- rbind(res, comparePerSuite(td1[[commonTestSuites[ti]]], td2[[commonTestSuites[ti]]], tol=tolerance)) } ## postcondition return(res) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������