From 14e1809d28557a5c2081ec7a0a363e88da9fe734 Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Fri, 2 Aug 2019 21:05:09 -0700 Subject: [PATCH 1/9] Committed test test_spec so can be restored at end of test --- test/test_spec_test.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/test_spec_test.yaml diff --git a/test/test_spec_test.yaml b/test/test_spec_test.yaml new file mode 100644 index 00000000..c5afe468 --- /dev/null +++ b/test/test_spec_test.yaml @@ -0,0 +1,2 @@ +MAIN=test_driver/main.dart +TESTS=(test_driver/main_test.dart) From 95ccb3059b01a3fbe93a0e26f3abb79d9e8122b5 Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Fri, 2 Aug 2019 21:47:22 -0700 Subject: [PATCH 2/9] Updated test spec for testing --- test/test_spec_test.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_spec_test.yaml b/test/test_spec_test.yaml index c5afe468..315c6f09 100644 --- a/test/test_spec_test.yaml +++ b/test/test_spec_test.yaml @@ -1,2 +1,4 @@ -MAIN=test_driver/main.dart -TESTS=(test_driver/main_test.dart) + # - bin/py.test tests/ --junit-xml $DEVICEFARM_LOG_DIR/junitreport.xml + - MAIN=test_driver/main.dart + - TESTS=(test_driver/main_test.dart) + - cd flutter_app From ed0d6c45e945a6ce31f465e05f887205d2479fd5 Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Sat, 3 Aug 2019 19:41:25 -0700 Subject: [PATCH 3/9] Declare each element of array in test spec test --- test/test_spec_test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_spec_test.yaml b/test/test_spec_test.yaml index 315c6f09..a81258d8 100644 --- a/test/test_spec_test.yaml +++ b/test/test_spec_test.yaml @@ -1,4 +1,4 @@ # - bin/py.test tests/ --junit-xml $DEVICEFARM_LOG_DIR/junitreport.xml - - MAIN=test_driver/main.dart - - TESTS=(test_driver/main_test.dart) + - MAIN=test_driver/main1.dart + - TESTS+=test_driver/main_test.dart - cd flutter_app From 92e4041c0f7398c0729342e58f6e82aa801ada79 Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Sat, 3 Aug 2019 20:51:00 -0700 Subject: [PATCH 4/9] Reverted to array declaration in test spec test --- test/test_spec_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_spec_test.yaml b/test/test_spec_test.yaml index a81258d8..5bfe35a6 100644 --- a/test/test_spec_test.yaml +++ b/test/test_spec_test.yaml @@ -1,4 +1,4 @@ # - bin/py.test tests/ --junit-xml $DEVICEFARM_LOG_DIR/junitreport.xml - MAIN=test_driver/main1.dart - - TESTS+=test_driver/main_test.dart + - TESTS=(test_driver/main_test.dart) - cd flutter_app From c03c11db677f0ae429630798ef62d87c3249c045 Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Sat, 3 Aug 2019 21:33:06 -0700 Subject: [PATCH 5/9] Reverted to literal array element assignment in test spec test --- test/test_spec_test.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_spec_test.yaml b/test/test_spec_test.yaml index 5bfe35a6..4d702524 100644 --- a/test/test_spec_test.yaml +++ b/test/test_spec_test.yaml @@ -1,4 +1,5 @@ # - bin/py.test tests/ --junit-xml $DEVICEFARM_LOG_DIR/junitreport.xml - MAIN=test_driver/main1.dart - - TESTS=(test_driver/main_test.dart) + - TESTS+=test_driver/main_test.dart - cd flutter_app + - TESTS+=test_driver/main_test.dart From 84dc87c74f2a87e9d2a5f22940e6ff399bde44bc Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Sat, 3 Aug 2019 22:24:13 -0700 Subject: [PATCH 6/9] Updated format of test spec test --- test/test_spec_test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_spec_test.yaml b/test/test_spec_test.yaml index 4d702524..c2ef4e40 100644 --- a/test/test_spec_test.yaml +++ b/test/test_spec_test.yaml @@ -1,5 +1,5 @@ # - bin/py.test tests/ --junit-xml $DEVICEFARM_LOG_DIR/junitreport.xml - MAIN=test_driver/main1.dart - - TESTS+=test_driver/main_test.dart + TESTS+=test_driver/main_test.dart - cd flutter_app - - TESTS+=test_driver/main_test.dart + TESTS+=test_driver/main_test.dart From ae5d02b1a23db8d52d2b1ebfd67dd6435484498b Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Sun, 4 Aug 2019 08:21:06 -0700 Subject: [PATCH 7/9] Modified format of test spec test to use comma-delimited list --- test/test_spec_test.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_spec_test.yaml b/test/test_spec_test.yaml index c2ef4e40..85dc4e57 100644 --- a/test/test_spec_test.yaml +++ b/test/test_spec_test.yaml @@ -1,5 +1,4 @@ # - bin/py.test tests/ --junit-xml $DEVICEFARM_LOG_DIR/junitreport.xml - MAIN=test_driver/main1.dart - TESTS+=test_driver/main_test.dart + - TESTS='test_driver/main_test.dart' - cd flutter_app - TESTS+=test_driver/main_test.dart From a786db7a2af7fe6f28c1ddf26ffdbc7813bc044c Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Sun, 4 Aug 2019 10:15:32 -0700 Subject: [PATCH 8/9] Removed exposure of test_spec.yaml to developer #28 Added support for running multiple tests on each device #20 --- bin/main.dart | 2 +- example/sylph.yaml | 6 +- lib/resources/script/local_utils.sh | 5 +- lib/resources/script/test_android.sh | 38 ++++++++- lib/resources/script/test_ios.sh | 79 ++++++++++++++----- .../resources}/test_spec.yaml | 59 ++++++-------- lib/src/bundle.dart | 21 +++-- lib/src/sylph_run.dart | 54 +++++++++---- test/sylph_test.dart | 19 +++++ test/sylph_test.yaml | 6 +- 10 files changed, 199 insertions(+), 90 deletions(-) rename {example/test_driver => lib/resources}/test_spec.yaml (84%) diff --git a/bin/main.dart b/bin/main.dart index 3a4f3e8a..b67ede7e 100644 --- a/bin/main.dart +++ b/bin/main.dart @@ -49,7 +49,7 @@ main(List arguments) async { print( 'Sylph run completed in ${sylphRuntimeFormatted(timestamp, DateTime.now())}.'); if (sylphRunSucceeded) { - print('Sylph run \'$sylphRunName\' suceeded.'); + print('Sylph run \'$sylphRunName\' succeeded.'); exit(0); } else { print('Sylph run \'$sylphRunName\' failed.'); diff --git a/example/sylph.yaml b/example/sylph.yaml index fc3a5a55..4cccc8ed 100644 --- a/example/sylph.yaml +++ b/example/sylph.yaml @@ -15,7 +15,7 @@ concurrent_runs: true # device farm config project_name: test concurrent runs -default_job_timeout: 7 # minutes, set at project creation +default_job_timeout: 10 # minutes, set at project creation device_pools: @@ -50,11 +50,11 @@ test_suites: - test_suite: example tests 1 main: test_driver/main.dart - testspec: test_driver/test_spec.yaml tests: - test_driver/main_test.dart + - test_driver/main_test.dart pool_names: - android pool 1 - ios pool 1 # - iPhone 5c - job_timeout: 7 # minutes, set per job + job_timeout: 8 # minutes, set per job diff --git a/lib/resources/script/local_utils.sh b/lib/resources/script/local_utils.sh index 5d427bd8..7171a252 100755 --- a/lib/resources/script/local_utils.sh +++ b/lib/resources/script/local_utils.sh @@ -12,7 +12,7 @@ main(){ ;; --ci) if [[ -z $2 ]]; then show_help; fi - config_ci "$2" + config_ci "$2" # dev only ;; *) show_help @@ -30,7 +30,7 @@ where: package a debug app as a .ipa (app must include 'enableFlutterDriverExtension()') --ci - configure a CI build environment + configure a CI build environment // dev only --help print this message " "$(basename "$0")" @@ -39,6 +39,7 @@ where: # install certificate and provisioning profile using match # assumes resources unbundled from sylph +# dev only config_ci() { local app_dir=$1 diff --git a/lib/resources/script/test_android.sh b/lib/resources/script/test_android.sh index 2a036321..d3611073 100755 --- a/lib/resources/script/test_android.sh +++ b/lib/resources/script/test_android.sh @@ -16,6 +16,10 @@ main() { if [[ -z $2 ]]; then show_help; fi custom_test_runner "$2" ;; + --run-tests) + if [[ -z $2 ]]; then show_help; fi + run_tests "$2" + ;; --run-driver) if [[ -z $2 ]]; then show_help; fi run_no_build "$2" @@ -31,7 +35,7 @@ main() { } show_help() { - printf "\n\nusage: %s [--help] [--run-test ] [--run-driver + printf "\n\nusage: %s [--help] [--run-test ] [--run-driver ] [--run-tests ] Utility for running integration tests for pre-installed flutter app on android device. (app must be built in debug mode with 'enableFlutterDriverExtension()') @@ -41,6 +45,10 @@ where: run test from dart using a custom setup (similar to --no-build) path of test to run, eg, test_driver/main_test.dart + --run-tests + run tests from dart using a custom setup (similar to --no-build) + + list of test paths (eg, 'test_driver/main_test1.dart,test_driver/main_test2.dart') --run-driver run test using driver --no-build @@ -49,6 +57,24 @@ where: exit 1 } +#run_tests() { +# readarray -td, tests <<<"$1,"; unset 'tests[-1]'; declare -p tests; +# for test in "${tests[@]}" +# do +## echo "test=$test" +# custom_test_runner "$test" +# done +#} + +run_tests() { + while IFS=',' read -ra tests; do + for test in "${tests[@]}"; do +# echo "test=$test" + custom_test_runner "$test" + done + done <<< "$1" +} + # note: assumes debug apk installed on device # note: by-passes flutter drives dependency on Android SDK which requires installing the SDK # (see https://github.com/flutter/flutter/issues/34909) @@ -104,18 +130,24 @@ getAppIdFromApk() { local apk_path="$1" # regular expression (required) + # shellcheck disable=SC2089 local re="^\"L.*/MainActivity;" # sed substitute expression + # shellcheck disable=SC2089 local se="s:^\"L\(.*\)/MainActivity;:\1:p" # tr expression - local te=' / .'; + local te=" / ."; - local app_id="$(unzip -p $apk_path classes.dex | strings | grep -Eo $re | sed -n -e $se | tr $te)" + local app_id + # shellcheck disable=SC2089 + app_id="$(unzip -p "$apk_path" classes.dex | strings | grep -Eo "$re" | sed -n -e "$se" | tr $te)" echo "$app_id" } # note: requires android sdk be installed to get app identifier (eg, com.example.example) +# not currently used +# (see https://github.com/flutter/flutter/issues/34909) run_no_build() { local test_main="$1" diff --git a/lib/resources/script/test_ios.sh b/lib/resources/script/test_ios.sh index 15570b18..a12f86a1 100755 --- a/lib/resources/script/test_ios.sh +++ b/lib/resources/script/test_ios.sh @@ -6,11 +6,6 @@ set -e # run integration test on ios # used on device clouds -# constants -default_debug_ipa_name='Debug_Runner.ipa' -default_debug_ipa_dir="." -debug_app_dir='build/ios/iphoneos' - main() { case $1 in --help) @@ -18,35 +13,41 @@ main() { ;; --unpack) if [[ -z $2 ]]; then show_help; fi - unpack_debug_ipa $2 + unpack_debug_ipa "$2" ;; --dummy-symbols) if [[ -z $2 ]]; then show_help; fi - dummy_symbols $2 + dummy_symbols "$2" ;; - --test) + --run-driver) if [[ -z $2 ]]; then show_help; fi - run_test $2 + run_driver "$2" "$3" + ;; + --run-tests) + if [[ -z $2 || -z $3 ]]; then show_help; fi + run_tests "$2" "$3" ;; - *) + *) show_help ;; esac } show_help() { - printf "\n\nusage: %s [--unpack ] [--dummy-symbols ] [--test ] + printf "\n\nusage: %s [--unpack ] [--dummy-symbols ] [--run-driver []] [--run-tests ] -Utility for building a debug app as a .ipa, unpacking, and running integration test on an iOS device. +Utility for building a debug app as a .ipa, unpacking, and running integration tests on an iOS device. (app must include 'enableFlutterDriverExtension()') where: --unpack unpack debug .ipa to build directory for testing - --dummy-symbols <> + --dummy-symbols generate dummy symbol directories for ios-deploy - --test - run integration test on debug app + --run-driver [] + run integration test on debug app with default or specified test + --run-tests + run integration tests on debug app with list of test paths (eg, 'test_driver/main_test1.dart,test_driver/main_test2.dart') --help print this message " "$(basename "$0")" @@ -57,6 +58,8 @@ where: unpack_debug_ipa(){ local ipa_path=$1 local unpack_dir='Payload' + local debug_app_dir='build/ios/iphoneos' + echo "Unpacking $ipa_path to $debug_app_dir..." @@ -74,14 +77,47 @@ unpack_debug_ipa(){ dummy_symbols() { local dummy_symbols_path=$1 + # shellcheck disable=SC2162 while IFS=$'=' read build os; do echo "creating $HOME/Library/Developer/Xcode/iOS DeviceSupport/$os ($build)/Symbols" mkdir -p "$HOME/Library/Developer/Xcode/iOS DeviceSupport/$os ($build)/Symbols" - done < $dummy_symbols_path + done < "$dummy_symbols_path" +} + +#run_tests() { +# local debug_app_path=$1 +# shift +# local tests=($@) +# for test in "${tests[@]}" +# do +# run_driver "$debug_app_path" "$test" +# done +#} + +#run_tests() { +# local debug_app_path=$1 +# readarray -t -d, tests <<<"$2,"; unset 'tests[-1]'; declare -p tests; +# for test in "${tests[@]}" +# do +## echo "test=$test" +# run_driver "$debug_app_path" "$test" +# done +#} + + +run_tests() { + local debug_app_path=$1 + while IFS=',' read -ra tests; do + for test in "${tests[@]}"; do +# echo "test=$test" + run_driver "$debug_app_path" "$test" + done + done <<< "$2" } -run_test() { +run_driver() { local debug_app_path=$1 + local test_path=$2 # disable reporting analytics flutter config --no-analytics @@ -89,8 +125,13 @@ run_test() { # update .packages in case last build was on a different flutter repo flutter packages get - echo "Running flutter drive --no-build $debug_app_path" - flutter drive --no-build $debug_app_path + if [[ -z "$test_path" ]]; then + echo "Running flutter drive --no-build $debug_app_path" + flutter drive --no-build "$debug_app_path" + else + echo "Running flutter drive --no-build -t $debug_app_path --driver $test_path" + flutter drive --no-build "$debug_app_path" -t "$debug_app_path" --driver "$test_path" + fi } main "$@" \ No newline at end of file diff --git a/example/test_driver/test_spec.yaml b/lib/resources/test_spec.yaml similarity index 84% rename from example/test_driver/test_spec.yaml rename to lib/resources/test_spec.yaml index 3bdb3bd8..de023c8a 100644 --- a/example/test_driver/test_spec.yaml +++ b/lib/resources/test_spec.yaml @@ -18,33 +18,14 @@ phases: - cd flutter_app - ls -la - # check for node - - node --version - # check for bash - bash --version # env - env - - echo $DEVICEFARM_APP_PATH - - # upgrade ios-deploy - #- ios-deploy -V - #- xcodebuild -version - #- xcode-select --print-path - #- gcc --version - #- lldb --version - #- whoami - #- groups - - # install local brew because cannot upgrade ios-deploy - # try skipping upgrade (takes a long time) - #- git clone --depth=1 https://github.com/Homebrew/brew ~/.brew - #- export PATH="$HOME/.brew/bin:$HOME/.brew/sbin:$PATH" - #- brew install ios-deploy - #- which ios-deploy - #- ios-deploy -V + - whoami + - groups # install flutter - echo "Install flutter" @@ -52,10 +33,8 @@ phases: - >- if [ $DEVICEFARM_DEVICE_PLATFORM_NAME = "Android" ]; then - # Run EC2 setup code here - #java -version - ## Install android SDK + #java -version #ANDROID_SDK_TOOLS=4333796 # android-28 #ANDROID_PLATFORM_SDK=28 # required by flutter #ANDROID_BUILD_TOOLS=28.0.3 # required by flutter @@ -77,7 +56,7 @@ phases: #sdkmanager "tools" "platform-tools" "platforms;android-${ANDROID_PLATFORM_SDK}" "build-tools;${ANDROID_BUILD_TOOLS}" > /dev/null #sdkmanager --list | head -15 - + # Install Flutter curl https://storage.googleapis.com/flutter_infra/releases/stable/linux/flutter_linux_$FLUTTER_VERSION.tar.xz -o flutter_linux.tar.xz tar xf flutter_linux.tar.xz fi @@ -87,7 +66,21 @@ phases: # Run Mac setup code here system_profiler SPSoftwareDataType sw_vers + which ios-deploy ios-deploy -V + xcodebuild -version + xcode-select --print-path + gcc --version + lldb --version + + # install local brew because cannot upgrade ios-deploy + # try skipping upgrade (takes a long time) + #- git clone --depth=1 https://github.com/Homebrew/brew ~/.brew + #- export PATH="$HOME/.brew/bin:$HOME/.brew/sbin:$PATH" + #- brew install ios-deploy + #- which ios-deploy + #- ios-deploy -V + # Install Flutter dependencies # brew upgrade ios-deploy # brew install cocoapods @@ -95,16 +88,13 @@ phases: # Install Flutter curl https://storage.googleapis.com/flutter_infra/releases/stable/macos/flutter_macos_$FLUTTER_VERSION.zip -o flutter_macos.zip unzip -qq flutter_macos.zip - # build experimental flutter from fork - #git clone https://github.com/mmcc007/flutter.git -b master fi - mv flutter $HOME/flutter #- echo "export PATH=$PATH:$HOME/flutter/bin:$HOME/flutter/bin/cache/dart-sdk/bin">> ~/.bash_profile - export PATH=$PATH:$HOME/flutter/bin:$HOME/flutter/bin/cache/dart-sdk/bin - export PATH="$PATH":"$HOME/.pub-cache/bin" - #- export PATH=$PWD/flutter/bin:$PWD/flutter/bin/cache/dart-sdk/bin:$PATH - - which flutter + - flutter doctor -v # The pre-test phase includes commands that setup your test environment. pre_test: @@ -129,23 +119,20 @@ phases: # Note: For most use cases, the default command works fine. # Please refer "https://docs.pytest.org/en/latest/usage.html" for more options on running pytests from command line. # - bin/py.test tests/ --junit-xml $DEVICEFARM_LOG_DIR/junitreport.xml - #- APP_ID=com.orbsoft.counter - - MAIN=test_driver/main.dart - - TEST=test_driver/main_test.dart - cd flutter_app + - MAIN=test_driver/main.dart + - TESTS='test_driver/main_test.dart' - >- if [ $DEVICEFARM_DEVICE_PLATFORM_NAME = "Android" ]; then - flutter doctor -v - #./script/test_android.sh --run-driver "$MAIN" - ./script/test_android.sh --run-test "$TEST" + ./script/test_android.sh --run-tests "$TESTS" fi if [ $DEVICEFARM_DEVICE_PLATFORM_NAME = "iOS" ]; then ./script/test_ios.sh --unpack $DEVICEFARM_APP_PATH ./script/test_ios.sh --dummy-symbols build_to_os.txt - ./script/test_ios.sh --test test_driver/main.dart + ./script/test_ios.sh --run-tests "$MAIN" "$TESTS" fi - echo done. diff --git a/lib/src/bundle.dart b/lib/src/bundle.dart index 20c0e99c..55487fdc 100644 --- a/lib/src/bundle.dart +++ b/lib/src/bundle.dart @@ -7,6 +7,7 @@ import 'utils.dart'; const kResourcesUri = 'package:sylph/resources'; const kAppiumTemplateName = 'appium_bundle.zip'; +const kAppiumTestSpecName = 'test_spec.yaml'; const kTestBundleDir = 'test_bundle'; const kTestBundleName = '$kTestBundleDir.zip'; const kDefaultFlutterAppName = 'flutter_app'; @@ -30,14 +31,14 @@ Future bundleFlutterTests(Map config) async { // create default app dir in test bundle cmd('mkdir', [defaultAppDir], '.', false); + // Copy app dir to test bundle + cmd('cp', ['-r', '.', defaultAppDir], '.', false); + // update .packages in case last build was on a different flutter repo - cmd('flutter', ['packages', 'get'], '.', true); + cmd('flutter', ['packages', 'get'], defaultAppDir, true); // clean build dir in case a build is present - cmd('flutter', ['clean'], '.', true); - - // Copy app dir to test bundle - cmd('cp', ['-r', '.', defaultAppDir], '.', false); + cmd('flutter', ['clean'], defaultAppDir, true); // Copy scripts to test bundle cmd('cp', ['-r', 'script', defaultAppDir], stagingDir, false); @@ -74,12 +75,19 @@ Future unpackResources(String tmpDir) async { await writeFileImage(await readResourceImage(kAppiumTemplateName), '$tmpDir/$kAppiumTemplateName'); + // unpack Appium test spec + await unpackFile(kAppiumTestSpecName, tmpDir); + // unpack scripts await unpackScripts(tmpDir); // unpack build to os map file await unpackFile(kBuildToOsMapFileName, tmpDir); + // unpack export options + // todo: configure exportOptions.plist for provisioning profile, team, etc... + await unpackFile('exportOptions.plist', 'ios'); + // unpack components used in a CI environment final envVars = Platform.environment; if (envVars['CI'] == 'true') { @@ -93,9 +101,6 @@ Future unpackResources(String tmpDir) async { // unpack dummy keys await unpackFile('dummy-ssh-keys/key', '.'); await unpackFile('dummy-ssh-keys/key.pub', '.'); - - // unpack export options - await unpackFile('exportOptions.plist', 'ios'); } } diff --git a/lib/src/sylph_run.dart b/lib/src/sylph_run.dart index b7737ca4..62ada3ec 100644 --- a/lib/src/sylph_run.dart +++ b/lib/src/sylph_run.dart @@ -51,15 +51,6 @@ Future sylphRun(String configFilePath, String sylphRunName, for (var testSuite in config['test_suites']) { print('\nRunning \'${testSuite['test_suite']}\' test suite...\n'); - // todo: update test spec with tests in test suite - // (currently only allows one test) -// final List tests = testSuite['tests']; -// for (var test in tests) { -// final poolType = devicePoolInfo['pool_type']; -// print( -// 'bundling test: $test on $poolType devices in device pool $poolName'); -// } - // Initialize device pools and run tests in each pool for (final poolName in testSuite['pool_names']) { bool runTestsSucceeded = false; @@ -105,24 +96,27 @@ Future runSylphJob(Map testSuite, Map config, poolName, String projectArn, // Setup device pool String devicePoolArn = setupDevicePool(devicePoolInfo, projectArn); - // Build debug app for pool type and upload - final appArn = await _buildUploadApp(projectArn, devicePoolInfo['pool_type'], - testSuite['main'], config['tmp_dir']); - + final tmpDir = config['tmp_dir']; // Upload test suite (in 2 parts) // 1. Upload test package - final testBundlePath = '${config['tmp_dir']}/${kTestBundleName}'; + final testBundlePath = '$tmpDir/$kTestBundleName'; print('Uploading tests: $testBundlePath ...'); String testPackageArn = uploadFile(projectArn, testBundlePath, 'APPIUM_PYTHON_TEST_PACKAGE'); // 2. Upload custom test spec yaml - final testSpecPath = testSuite['testspec']; + final testSpecPath = '$tmpDir/$kAppiumTestSpecName'; + // Substitute MAIN and TESTS for actual debug main and tests from test suite. + setTestSpecEnv(testSuite, testSpecPath); print('Uploading test specification: $testSpecPath ...'); String testSpecArn = uploadFile(projectArn, testSpecPath, 'APPIUM_PYTHON_TEST_SPEC'); + // Build debug app for pool type and upload + final appArn = await _buildUploadApp( + projectArn, devicePoolInfo['pool_type'], testSuite['main'], tmpDir); + // run tests and report return _runTests( sylphRunName, @@ -212,3 +206,33 @@ DateTime sylphTimestamp() { DateTime.now().millisecondsSinceEpoch); return timestamp; } + +/// Set MAIN and TESTS env vars in test spec. +void setTestSpecEnv(Map test_suite, String testSpecPath) { + const kMainEnvName = 'MAIN='; + const kTestsEnvName = 'TESTS='; + final mainEnvVal = test_suite['main']; + final testsEnvVal = test_suite['tests'].join(","); + final mainRegExp = RegExp('$kMainEnvName.*'); +// final testsRegExp = RegExp(r'(.*TESTS\+=).*'); + final testsRegExp = RegExp('$kTestsEnvName.*'); + String testSpecStr = File(testSpecPath).readAsStringSync(); + testSpecStr = + testSpecStr.replaceFirst(mainRegExp, '$kMainEnvName$mainEnvVal'); + +// // Device Farm does not accept a literal array declaration so must declare +// // each element of array. +// +// // build replacement string +// String testsStr = ''; +// final testsEnvName = testsRegExp.firstMatch(testSpecStr).group(1); +// for (final testPath in test_suite['tests']) { +// testsStr += '$testsEnvName$testPath\n'; +// } +// testsStr = testsStr.substring(0, testsStr.length - 1); // remove last \n +// +// testSpecStr = testSpecStr.replaceAll(testsRegExp, testsStr); + testSpecStr = + testSpecStr.replaceAll(testsRegExp, '$kTestsEnvName\'$testsEnvVal\''); + File(testSpecPath).writeAsStringSync(testSpecStr); +} diff --git a/test/sylph_test.dart b/test/sylph_test.dart index 030a298f..19416747 100644 --- a/test/sylph_test.dart +++ b/test/sylph_test.dart @@ -389,6 +389,25 @@ void main() { // rounds to milliseconds expect(durationFormatted.contains(RegExp(r'15m:34s:12.ms')), true); }); + + test('substitute MAIN and TESTS for actual debug main and tests', () async { + final filePath = 'test/sylph_test.yaml'; + final config = await parseYaml(filePath); + final test_suite = config['test_suites'][0]; + final expectedMainEnvVal = test_suite['main']; + final expectedTestsEnvVal = test_suite['tests'].join(","); + final testSpecPath = 'test/test_spec_test.yaml'; + final expected = ''' + # - bin/py.test tests/ --junit-xml \$DEVICEFARM_LOG_DIR/junitreport.xml + - MAIN=$expectedMainEnvVal + - TESTS='$expectedTestsEnvVal' + - cd flutter_app +'''; + setTestSpecEnv(test_suite, testSpecPath); + expect(File(testSpecPath).readAsStringSync(), expected); + // restore modified test spec test + cmd('git', ['checkout', testSpecPath]); + }); } int sortSylphDevices(d1, d2) { diff --git a/test/sylph_test.yaml b/test/sylph_test.yaml index 9f1d1cec..c53429a7 100644 --- a/test/sylph_test.yaml +++ b/test/sylph_test.yaml @@ -45,9 +45,9 @@ device_pools: test_suites: - test_suite: my tests 1 main: test_driver/main1.dart - testspec: test_spec.yaml tests: - - test_driver/main_test.dart + - test_driver/main1_test1.dart + - test_driver/main1_test2.dart pool_names: - android pool 1 - ios pool 1 @@ -55,9 +55,9 @@ test_suites: - test_suite: my tests 2 main: test_driver/main2.dart - testspec: test_spec2.yaml tests: - test_driver/main2_test1.dart + - test_driver/main2_test2.dart pool_names: - android pool 1 - ios pool 1 From c0f85e96c778d01ca2ba7639c0626e7d02cdf447 Mon Sep 17 00:00:00 2001 From: Maurice McCabe Date: Sun, 4 Aug 2019 11:43:50 -0700 Subject: [PATCH 9/9] Added sylph config details and added CI config section --- README.md | 84 +++++++++++++++++++++++++++++++++++----- art/travis_env_vars.png | Bin 0 -> 44969 bytes 2 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 art/travis_env_vars.png diff --git a/README.md b/README.md index 75898e3c..6f8f8215 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ project_name: flutter tests default_job_timeout: 5 # minutes tmp_dir: /tmp/sylph +artifacts_dir: /tmp/sylph_artifacts + +# local timeout per device farm run +sylph_timeout: 720 # seconds approx +# run on ios and android pools concurrently (for faster results) +concurrent_runs: true device_pools: @@ -35,41 +41,99 @@ device_pools: devices: - name: Samsung Galaxy S9 (Unlocked) model: SM-G960U1 - os: 8.0.0 + os: '8.0.0' - pool_name: ios pool 1 pool_type: ios devices: - name: Apple iPhone X model: A1865 - os: 12.0 + os: '12.0' test_suites: - test_suite: example tests 1 main: test_driver/main1.dart - testspec: test_driver/test_spec.yaml tests: - test_driver/main1_test1.dart - test_driver/main1_test2.dart device_pools: - android pool 1 -# - ios pool 1 - job_timeout: 5 # minutes + - ios pool 1 + job_timeout: 5 # minutes per each device run - test_suite: example tests 2 main: test_driver/main2.dart - testspec: test_driver/test_spec.yaml tests: - test_driver/main2_test1.dart - test_driver/main2_test2.dart - device_pools: + pool_names: - android pool 1 -# - ios pool 1 - job_timeout: 5 # minutes + - ios pool 1 + job_timeout: 5 # minutes per each device run +``` + +# Dependencies +## AWS CLI +Install AWS Command Line Interface (AWS CLI) +``` +curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" +unzip awscli-bundle.zip +sudo ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws +``` +For alternative install options see: +https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html + +## AWS CLI Credentials +Configure the AWS CLI credentials: +``` +$ aws configure +AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE +AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY +Default region name [None]: us-west-2 +Default output format [None]: json ``` +For alternative configuration options see: +https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html + +# Configuring a CI Environment for _Sylph_ + +## iOS builds +Special handling for building iOS apps is required for running tests on remote real devices. In particular, provisioning profiles and certificates must be installed on the build machine. To install the dependencies needed to complete the iOS build, Fastlane's match is used. _Sylph_ will detect it is running in a CI environment (using the CI environment variable), and will install fastlane files that in turn will install the dependencies needed to build the iOS app using Fastlane's match. The iOS build can then complete as normal. + +The following environment variable is required my Match: + + +- PUBLISHING_MATCH_CERTIFICATE_REPO +This is the location of the private match repo. For example, https://matchusername:matchpassword@private.mycompany.com/private_repos/match +where + - matchusername + is the username used when setting-up match + - matchpassword + is the password used when setting-up match + - private.mycompany.com/private_repos/match + is the uri path to the match repo (if using git) + +For details on how to configure Match see: +https://docs.fastlane.tools/actions/match/ + +## AWS CLI Credentials for CI +The following AWS CLI credentials are required: +- AWS_ACCESS_KEY_ID +- AWS_SECRET_ACCESS_KEY + +For details on other credentials see: +https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html + +## Example secrets for Travis-CI +_Sylph_ runs on Travis-CI and expects the following environment variables: + +![secret variables](art/travis_env_vars.png) + + + # Live demo -To see _Sylph_ in action, a live demo of the [example](example) app is available. +To see _Sylph_ in action in a CI environment, a demo of the [example](example) app is available. The log of the live run is here: https://travis-ci.com/mmcc007/sylph diff --git a/art/travis_env_vars.png b/art/travis_env_vars.png new file mode 100644 index 0000000000000000000000000000000000000000..38768d01a1a3428590a8381aabc51f457f6a6ba6 GIT binary patch literal 44969 zcmc$GV{m0n+wROn6Wg|J+qP{@Y)zc(*q+!vYMhLz8NO)Tc3oA$p z3lk_f+L>9}n11?15oc^*KqW~-F>GjPU@$yEO$p`brW6(ysbtXA*FDkQN6>FDK=30~ zN9QL7!q2|%$lh&(-Cy(QPXorKZeBd*iWx;Ti!GB^9`bA<%AWFZk5Y_t_)@PDgF<2YnPxVihBq9H3^kk2@9*_kARx~q;2~Ye@9*y& zNAK@l5&rieyJrG$pGe#+e@0p(!GFv{mZge@vxck;m$98KouP@HktvF=YwB!B;BIU4&56sMm*}4wTp!nevgwHk{;A?@%}b;qt3V)Z=V(g6O2=&qYt~=H^D{#!P4DXim?_$;nC2z(mi)MEg;L*2&|W zv!OffHz(r1jr^w_5mP5)M@xHWOS^9bf7&%PvU73fB_jIM(f@q@-lwU%<^T5d&FSA^ zeGHKPPYFFE9RvOUwEY3)`IF0~VCimZqak8xYx>RUqYpk7b{3w0>i=IQ|J&n#AvOOC z$-?yC$p0$&7s*5aX8`{-pug7oC-=i#d{8{}|6_VSC^}O=mQSAqK1qrQs|ztSZT}Op3orhRO!Am))SL-K-?t zfP1Xo{erZA5jA=HY##;xq<&%{8HjqpZadC?H1!WaW5yBtpN2nD_OOsOc^x74oR)uD z`U9x2Xo1xM`J(>)>1l8d)$oLphWk&E00h~z9O}6#&RP@tG%kj38~th_(b@VP zq^lV^tE7tZfgH^Hr7ef4D|#iGSEFy(Hoo^~c$K=gpyil9Jx^CbuAh`Ti$XMQ!*4Bd z!SWS7_-OFmtINHnyVM_9k5j#d=e-4ey+MGPXQxiW=9(?cV{Xnl1v9>^k{eZw3B>me z154p1K5z9?e`-G}dPKxD8(Fkv@b`;`FUtu%(;zJa=o(DKbqB+y~)Scjdt6o{5fxF^%5iE|6A0v)?xy7=? zCff41JedJo4eR;aNh+x|=(TKCDoM1g(n{2=oYz8AO!ZzU(g8NJ#X z6uhR%q@R(EHe~YW_pzP>I$t$y{OhzT43iZd(D;U<)QBtm^9sU{;u)(tlS^KWl2e!c zg_#NpWTcy`@etXj@)H@)6*tUjrDSnp2qiyvV9x_j*sreRXyZ0l;_|U-Mz4z5nH0Rf z0Qxe9=Uf8s8%;F5(oQI6*xtA;cn~T;4f!sIUTI=zBgB()-x(pikw%T|jLz?Cq z+*yF=3a&VpxKzDMN_YbrSKIQ<=UciA$1@ZdcFOUH5c!DAeB2RkWmCDsJUC)5?j2`1 zivX53EJGCci9?68<)vkQ=GG36=8=ZGfuAbbAL+7z|pyZh)@V(JAe%4a_#S zwM?3I^{(}n03CeMn#AwFQZWPHqJ$UGX*uBIDrC;2i47=w2c(ztk1y1E+ zUzK9)tgIOy>N|deP-+SXLwR_k9g;xtY=Gr&UjHpB6OqL=2byTNPH={Pz+IYHO(ugc3*r0a#h_E3k2c4aI6>i=jZ2`3I%O zL{lwLPr9cCQ=MO+PtGiBr;-GZD4+JI5AADYQVp9D_K7MKPE-bbilI%tucnTEnczlz zx*LAFf*z?$LvhD9i+M^43tDCtM|dC5tl;$pH5MA`BPb+&(_r{f(eemjWv6`jG>AtW zaT)3;1J$6}MFgACP&Nu5nVUPWo^L9BHQ!n3-Z~$U2fcuSCg3m@y?xAtRd;tB?BnPD zweHs=GEAvgZG7Tmf#*OkN_aws-xNsdmG%l0PQu&gA9P&+WzrPJtEIl$RSr-uzTXJd zI>&(ve(nm5B-(F+3^2OKQ9n9w574o%+o|8T3?B3Icv!9p zJ0xa~hHI-WMvW+KEzUs=elFN%&2F*YTohB~o6SLqlqJ?wO!*Ei7)7B)AbF5$~Qz&f5x-Tauk-ew18 zi&Q1GI+U=GGI%M;^=L^zJgQ=cFKcE6t%F*WF@RKLHM7cv#n}>@siX4noJgT>q4xIL z02}Edq=z@SoogJ!IszL*Vv&V2w;-7M=|0mL=g5uwMB0+$K?YH#kd@&nyjfl0A!3jA zJ@;0Xl=|EH_LgpE^%1inY}sUGme~vH#MGB!I_fqDbT=X6LVw8>gl1EgbGw%mtaP6v12?hS)v|nlU(5 zz`~#S07<0|?JL#}1r}$P3OyR_gTTw{f#5u%@aG^q?unblx%Qjxwtk6z)qcCJ z5Z;x?W|Mm&QtS8lc?R3gzCR0{1@{*f+_BK6z*L(tVz*A1?M_! z_iDstH#F@Vs9`9qfNJ?Pk{HN`=~uDL+|ay4;I@UFS|foiJFJm~hIjgx$`EBb)!k4% zl)*XLUxab_$XpVz2bFSf;RW*<(TIts$}BY4x(B_=PTCp1Q+Ps(FBQM;ZWBXdK%Cu+ zE1>jK^m>K?PVp}Mfd_V~ZpYf`Q$>bXH2Z_UtGM>t_0-?w{BhUKZ z-z>!`aq@q${@GOIRQ_4yTXkXf%UGhb>@W3=J1=O|)P4jW&*Bc36@>BmTxEPJmpr5* zo!6bAkmE}7ozCmjs+(ou0!g9cPPLX(Zl4A4hY@y3jDN+9vV6D~=n#{MYe;F;7zxL@ zi*GpZ?+erhfxxm@PDeRT`?Bt;+0Jx?kV}VCM&HXMIdW3t-5-d7OryPxsQr!&Z4so2 zWjMWOIHcyZ<>^T#tyah9_A@&lzrXvFp>Ddp<&F8WCG6fw5Sln;#6o+Ai0n#*>oZ1H z%{LpEkQswOA6QqVO$ToV_iS^@J$nk1-O#n7*mJ>ez&{&0xUVc8 zN6T&XYFVsJpc@QDT^bU~Q4L3wd z8a>}PymgEFAi`G8RfcT}S4g3yOiu4mxk0wxQ|EbTZ1=x3RG3I&9-vc}$KdX2UMVMu zsiNWbtws<<(udqFH0DmKftA5QUq0S@b&tkycqYUzj5+kfd!*9MNXs5pF}#^^R1#g1 zn~(GFA8CB{p7_}FauZI2MyNF|D@FT6Dsy6u-E5hldOi%i427~vD&E7u4yGytDT=k; z4sP6b=90;{@p-t~`Sl(n`J6_$KPhX{!`$cT%TguqzhdA^+=PFl86h)2Bnv>No!87# zj%-PhW^lb>qroxg$Gt5bofOgvUiU$W=3(rYwNcko)o3|oj?87c!;#3Q#>}F$Vf0=P z4~a2_b`Z;%Nl#VBzZQn9lZu(0(o8?HUbAXfvck?wKCr6H#pY+cy@%*zH(ZH}u|z~$ zSlYk2rWa=oqgo9GtH1XSljQZBF#=+p(WLM%rHN#7OG5vg=SX&1PVmxu0*<)9GssXC z1UnYKOP9w?8F{?xl~nW4kMPj$5so)MFfH6EVmJKWw(#-B~8Y_lbjli@IXrz)G^k8F{rg@f=r2NdrfNsQIkw;5c|>?wnn zu75$j(W)IL8PdUirhnE`vxgQ2-=5WciWH9%9cxsvH zh(8^$b)muN>X#O(HgrwvoK#-5PNcD(rg1RnC$o(uARh<50 z^dYSWNDkot$>KuztOmp_VQQiHn=}3PwI) zzZ=ai9Z0Z3y{+Nf4xxLuus&ha4Km!D1QMKejk9w zE-u6Hzg}$)$cGEO=1-Ln(fb#09`gaXS0|@I{Oct<6MT5G+zO_SivI#${{Y@jf=G@2 z?BN*v;Y7Zp+Cr4c&_VqLeC+BfEY|e1YDE4WlmEw<&IOW2L=FD}?!`X<&*jL;VSmT; z|HTMgjd=|~{-3gO!_HjrU}R3pdCS2KCHq}QVm%`~sdOtIYjjvQO~JRn$J3jJ7*{EB zMoXgVx^nC8YLq65zU*bOwa{ouuQiMXt>hDa{>{J>)!&j4 z6Vvk;6DfjN>N9T|c}$frHoOLoNK9CF=2q0?a9*yVw`dayHI+`;(nHX#NtqC$@^od_yX_s53@SP;7&GE$2{b6WO&qfKneZH4{CBJin z%?zE&ZM&ATY&BdUn9BGWVh(6zg0Kw!?y!R+Ix=d~J2*5 z$06B>Y7FVt%MjN{_=8><&4>jY?Uy62gogC8Rf=DC>QDw4y3@QliNU}K?TIy`TbmZ5l~&%$32-L!TLcy!i4EE;4?l= zeMTf!6A3q%;8=xVs)8*+!fvPow2@xSt7A_T%K{5DIb7xQRMb^!CA3=*xC?$+0^a*^ z#xoJ39jQ`S?Mi7Y{I`!G8SDy=Ln0FC7PNP;ZxCj6Cu>qqya^Xt?70FzzcrA#M`=Ya z;cA36)~djwV?>!&_e*Ai4UzUcjK(^W6q`sN2g%CxxBn6=o$z*jkddMmX{ArGBrGGR z=^O9lY-GzrwsnPl@xzBzmZEYDzuS3G%W-o6U!;*&Tc!Kux6O~ptnv-N>ZY)L-tnCg zyy$%;636PfHCBVhi|p%hHR%YBr-;$7Yxl=A!sGE`W{Y7~Oi>}FY=vNJZTXv9k`bB!7Phe(Y^y^d7cGehnVS-46Y5smmay-fXpI?B4W z+U4IcuH%b5bNgV+Z~T`=Lx>(4YUoITrZ>j>uJn%8v|^s4`f+0CTFv%lF`TYY3wA#%ff%ch*Mo6vRrGSkIQz#9A`9Itk{nGH#ty|RF>NP;A2+QEszT&ek>CHYxw z=*@Za=NIkg#W6#^Q>@_F>_%lwFk=1_d9<-t$h&4vZFR#1*OtzgkU;_m@P%R!8%4dt zslZCV&W9bi0zAamTDaP9J3=v^>EJ8&QYY3_i_ktDErJe7S&ZEm+8G^1P-WY=3Y@}J zG?WD`1zmc&@#W6UM4NYw3XP60PGsOc&Yf_%F){+F@E+Jij5om^sX~?+OzM)jmRHU0 zG;*1NS6WKbPUu2eW|b7>0v8t(9P`9*;m}3h3K@Z_kdVvXoKWHOl)(=r-3JZ$(u*1U zQmiBSqk9oyk_DhGdgQFH5H|{>I&h`qGlgwU(Rfu7Cr{n^Y{9IvTC$BPZbktkOTNUAiVOzHc3uYFojhWYHEeG6{q*vaxe z*Li641V-adY+uFaGvew|E@BgHP&$5AXvc-sM3AL==OM7SHtkivDXxj_snn?@lN*_s zav`Z*B(;NyLpw-qo=fLrL@=5re&*C(RBoHeccae<3qdk<5m@h>ARsjt?C$_yZbH?kmCLEdGw>!HCIV`j)g(f{^t5w5&$&pdl%N-t{}TXlnKM7h5S#C-EBXk=uKHz{Zo zq^CnQK2*>S58e_z0g6=9WlBz#8_lB}bEY}!Q!dhLfXtz`WO2~r%R<5c@}gzh7cdbY zU>b7y#aFltvhX|T6xKF;RrUy4+5@#MO#rJGx+>%aCV|H-8nxbxlo|#@8K;T_sQTQTU9}IF7lsi;Cl{^z3+UUP*3-=i`Pecl9JAIFJZ)c%I5+vMC6-KSfr(?RSH{v8TM2txf zMO8koJo`Z*gL^1$_co*P@I|u)r1w|9h9`D0;URdHlUooBe6e@r{50T*mJth@Ja~Ft z4LpdJfs_!Au;T@hi3}^wxq~>OS0f8A=9LkOLPj`xUOti7+6xQ@S-dZM^QFmG)Gk$7 zJ@yN{;IKkn-H(`pVowOBpx|{1^j~B~0z0dmVK{gpzJTCjGhOGrf`zm{hSeUJ>HdqqUI_9A|;*IBiP@C z=)lE#pS|J3CBw;P2G9-Us1gCsqX_9j^pw1JUJ*-?op6Y!+%|s`O!;`7`JN-Y)1wll z{YFL>JO^XppML6JjgF%5JiL-p7b%B8TT-hj@Zj9P_o0QmZ15*_ak-OuiG>63Db%t{QS z#B-Y1;N($Ol}vf;_7yfFi8@^^u$)op_$R7!9LPrpbY*{zlOLSf(7~ZXacr9u-y#Qn z0DD%>=v227CN%lHrsF2UhfCH~AvwTK=;HpWwc*ywqc z537V!E8(4@KB@6y{T!q9qRx1hDD)Rgi}u=1QG^geu@mXu(2$(A4TUBr&h-UNL9_=-cx1B|>O#d3mR-HV)>bL9@sDjsaEDs*{$&a#2Nle|F3ep9dYyD(Dg3daR0653R` z9*Zh+2g1B#S{V6mpBO0fw?B+R}^Mu)Imj%K@ua@hnJAe*IsKG~8KD9IITn zPo9K1=UQ;4&l&ZUpO4XB8SHd3RU)q=lr94&NS4gB8R8Y@)~0=4rzuXpJrL231zj1o z4Ci<;i-Y#)v!U4j*G};XoZ6Jo>szgm_%Y1_ET1yhO#BNTFJWfbp6~at% z8to}=tK{hjC_oRrrlL|G_6a+Vi^OfaL41BWX91OWlkhYrU$u*^ypykal*p*UjA)t z^GK+a61`*Id$3I7`rc89;mp;Vgfj6~8ls~)TZiKjgSj*RH48DdDePiIuC7v|?>r&E zT-im)6&w1l>fC~EB=69JHwsg+LV(}0zng$JhA1+uGD!r&(~Tga^SgH>(f6J8l(o70 z!XmQhld)qjW#OX;@U$>EFbUfVJ3`WJcB=8t4N6Gvl)j^)$r2s$@M1#}7*{B5>e4nR zkY4an`R^^w0w>0fY-9%4VzM~es+8>e_n-~@3!n=>R#)*iRG$+rr5H_xXrfwGjP{F2 zWaJ5FLgjmU5A#_zIw+Klrvi_%ZL4A41VJbgruI@K+aR>4XC@pj;_?1;8HcQnq|V+(kAkvj_}DjjY3c= zJLXTEdnH(z(g~vR{Pe<_1w5O%?K(&()o7^xqG)1RvKVOxulUS0zIKJzi?g|T=EOkY znuiF@FC+Z)sARcG5_#LXseTb8Yr62ADchJYgP0e?S1(b>SVx4+HRsGJsY-=!CofOu zWH}Gwwg_0i-(8hD)_Lz2ER*%hC}7`7!$p|kL=}Q1(yxr8+A^W2r8uWAKA$~4{CKNd zAtfpFkP~&QG@FVXP7$!RsC}~ONL*!Zd|9+G!Wd5%0d{T`lW^mgyFP6gdJ~it!pJMPO<18y;7m zo4fMb!4>s_w>2K#P-G9uhNy;}BF|%Ej8M5l;;Zghkc9erPd_%;?L&Geg2sjSc6_h3 z1t!MGmbk&G!pw!njzX!C(=zg@kt`6LHnIsJG_v!MG0=6C zOr^SQ^i4Sz6+W7Wa<~IZRjQ^&+8<2x){c~9i^XB?w-Xw9OYS`O`nm8ZUS%cBk~@Gs zs6uu7QLthc>Sfgim1$^Wm%^oGjFw*mKjblO@K;Rin1`e6<8o(`4#`up3nr_@NS0R* zg^03=H#6SLJ;fe7==>H{VwyDT8jCr-Cqyu0fc!<_23VA+ZLC%RW#RwRDJJ9H`VA0!M zC{i#9FX@6^@f#OGD=WF^^b?@hH0uNX{)}5e9&+yXE_b=2ee0(I(xhqZzS6Y@g9Yo1 zIP)?QQrattEvXxi^b)I(*TJwBQLR)FC*o~n;ypNFy8;j8I^j*t@DB0_L#~gvKNtmm zHibz1j?0)Ri4A#mdcuJORBb>-$89x~0oAzRwe{)9To{9~@*LSBNm1n)zp13w4dd1``i6U*tC~vDq=(z=LFV^MjceZBjr? zf$LQ`OaqC-lI@(&5sX zLqTe|>@~!z;XG8vfWfY4kqW_CG_A)Lc{yQmYW@@+Nht+C@*N8b4wq-D@eMgtCOWt{ z^npNWxxUy|0WgN-B%Nz@!UvHN%JEMS{ZK6t;qZE@F{I=m8VjYDDHVnwNfBAxBOu zk#7%$EOLT&>x8+tDQdiKwN?b82wI4;iw2X)xDu%ifgy(Zh|G65A6M`wGCQjh6`Ya^JdUQw~8E zZ~vkMm~L0RRXlP@$`=@5XVuzGIqEGWsxu);I6#uCU5oLdZCX6SAOdUb=afk3C37@b z*q?ke0qRoX`~>j_$(9=ew6X&fb*F)1vqnkIo^JGa>P$M z>zUHC`>RA5qowVGj|e+-l~Y(<$=I^eI`Z(^%ICYiawAm!M@> z@Mg%}edW3g_3Lz61+BS_<+vgSm8ZCTx;SE&m9d+C3wdWk!&I(hJR4<=rnj_23up33 z@>d_}mur>+*YKg|@VLgc(X`)ca^AZKG@aUXuu6`O!iE=-p@){p=4GE~&0EIlPNa!L zblAvU$a_obS{~t)6I24{&Z6dO4vIN!UZhc+S=1{|O$MV$>toe0g|k^X#uos~^K5(X z5*Mcn9P>=GEa4YghmszeM{VX2ztxskoaVphcnVkP>mr{DT7jrknbkVq$&?e#%&0!? zbY@jljaJqzB}+q{zIiD{eZ4Q9IM>b`$t^mQa^0@(N8UQJ#%%K`ns>&Hp3P()p~ngt zGb>4yf6g2mnFc(r%>BajVI$`wM0pLlXbd@SE~DpGR6$$X^{5&9J=k%zoY#OBN26#y zD)RfZbVr9PG{g*6oHu2&_QBD03Tt~clf@>r(Hqb;Dm1xtJloXoFebjr!(u*}m~(QT z9Dy~wQQIbgMAT7nLN zSs;yW>WG$jIRctqWnstV2>fKW)+X`XlkIC^9L&PvIY-!R2DV6OrAH*!o*5KeKU>)n zG#e1fdHaa^M^UI1@f~mQ*#fH=6)m|jS%h7gOBSE2GwsM;vl{<1dW$ff@#Xa8RD-_F_Sn&Yr)qpO1izyU=&kO7cVwnE%u<`%pcu$=KsW{SDTn zekcD3AbARf_x%kxeaIEa{NXAs2AVc(FIJ_(sLfC^3$p9l=MO-Q(TdXPgKeA~?u zkSV@7B9|D5$*4gFT`luq_93$;Mm*vGB=f=7T5xR#R6?e*(SG)@Ew!Qbxb>R;6Wq`n zTMZIk;gPDS3iI7c^R23BHl_e`n$e@0QbJ+9ZtKFUwR9z5W=bY8JT4ZUkjvE-Md`Yl zLy)1Pd!F!q0zr z%iFqEV}mD}zh!~kYxcI`z{66)XHUme{Llpp%Yf+QpEHT=}_^4caw)WmFeQ>r49 zolIq?GhJc>yJpztK8Tf+0m9(lNIt#yO=EL+rsZcccw5zSC~{ZB>3TT3^&#J^ZfMiD zV>cnz#d0j=wujZHUT*Fs#8T|g_pN-UEG#ZW4C<--^7Z|A?bwX2xW+f zF;R+?ri~_B#3(k-;;scbCHpavI-D$#>z`_YjZ~vYM0&<9$0}mkAzg0N@oq`Ujkv9z zgE|8ZfQ>JBi!8r`AlC&zdEOw!oD9L^_2iP>dUB)kqIkzC?xK5JmyEv8?KO$qK@W_R zIw1&hOKU*QF}kfl_D_?Ib8ku}G#bspM)hgai)cTr(+n4Ed}q5ziVffRp4=g{HWN2M z(AcZxk?Gocq#miVnz?!z9-NA7k965tFT84^y!Sfc;CrtIx9!3?T<=rq;RW9wP>gAik62PbkNQP zzx_s5Y#M=SKNQVBCRxwuZ)I9S`o^B4qy&-9bn`xuh-k}w}gC} z*-WV=9KBRiZVw7l5uw+Q`ib8pz2+;4%X_}eL~cmd%gP;Aa)X!R-a^Uk{i4Qce)|g;c3hR=rqRzdYm=C5+CL2qL(JFy+mr8 zyqZbJfYLed3iQegyU!3gS07zLi1xTKCmPph^%yufIkntU_t3}g%4L+uM1=O<5108T z@&l}>y77fi!%DByJ)>VrTn1>#o zPhVgXmj>ZjCzBJWG%BIA&G&a+Wrtx>b(u&_!SPNhha?hnicT)wpM_nWr3ws@{!9~< z23SE-t^jJ8Yd!m*`ZuhLqB8GNul-7#*i8+%J_;0-YmDxoWLm0GPSGp;!z(bylB`tb z4~%ZOy%0ML7?Fy_daqTf_$Cuf?w(Mj{OIR)#A6S>y2E2?1i%!R61;l|r zN9zuoP_9P+k%0r7)?*0#$*!CmLRnktM&`077NbF&|Btg`c?b2Xjja^Juj`JhbbJTgZad=$I5K zDRRiP9vBdl;K{cVQCm}tj> zdw|9BmyGfcm*bV(Lr#w-`h$)}3}rFQqZQ5S8l~{-I7nxiFL;z%%r0tq*xA?5UsvT5 zD#pq4k47W817RqJi4r2&RI>Rwq27GKMffnqms&VTTvDB-sO%?On-G2E5rhWCh!ZbR zGJ?La>`tqB&f#OlG}jVdbw%966v`o)2o6t?S(1BF%js}K#P#(}U%G-Ew7>K148;}B zjzrv-p-?U;50#1!ORT=fRrTVB6>!cVI2Uion~-xMWkh)U;vOix2lVGwOBc!n^)vJv zY(O)9ZAu8dnDcE!KH4WRKP4;-z|rAg(%vaX*Vu z$?Ij=lemQ2A70h_+}A&Yq>tDHOifITiDT1n59hLqQmQkRq*paT~0fI9x_90eOQni7?7cM)MaE zQE60gf(ySL%J~;y7TY?RZCO506x(_K4_B)IN2-j#MgSUj8;>q+E8GsHv1TX1 zqnC~%>4Fx`c^fanN&FF$D1|8%-(%`m#taa_R2z>IW5!B=>?Oisg{LkOQ(VbsaLLS` zzYuAI!bFHB?;ne2TH0x)Go{q!^?TwYY^G2Bk&gEuHKiVgk!B&q?d(1XDDWkS)O*>H z7(`fg^29v%m!|JqOkoq``a|s#(=dEhoQZSR^`DJ6p@!G}5K-eMeis)Wdg_idnLEpY zU74#rdl2NfjbZ6Ug!IFOjmkohV#bCpmTwd%`9V$ZFoxc_=sO8dIIn2SFYiFJz~Ke1p~e+A@EP@l%~zh2MpvcZRPhPrG(LB6Kl5 z?W&e*yL#zN$FMzR$5-h?g2q-`v@KN|?(&qn`L&WSNF|=y`@6zMv|K@+Y&P3vkqM0l zSYlUPA~Ebq6jVZDnz4+7&(>w0<;?-^FzZm>L#=Zwvg9Irp?U7)<3g~>#EfqRIEbzi zXJQ^_@`R2Kfu<@aHZp@pJ{dY|`vIEE)UqIT45@jJXRO@#u#>X+Q?8=Ql1YPk8Uh~; zIwp$wK^Q^lGW3Y2o+R22ASa84gwzigqz#nZD%ynKjrBQb| zDIDcWwPEVCE0m_pktda#pIhe!PYQJI^(sQNGmzK)Ur+su= zecr+}JKU{~{F1zsS94W4j)V3%q^5v`67@Qj+(d)F33myizEB$eoZC>tzcnIFxA90~ zb$cdx1VDahus&Wc&iBIno>Y!<1;*QPR<}hhMFWtv@kz)mVXZg4_7z@!0GCbGePh1L zA1l#$`SD!dx^_Vd#VXEQJnj6v<7fh$)Cl4;*%XuBPsWlXgJf02(f~3w+2hpASi0v? z`MSL21&k4eZm z&gAKxt$0!A7#I>;R9MwxOq4*{cuzJK3)c?i3r@R>D9@+$XySMEj2hY`v?$2B%x3Q6 z#Y@h3_m;SyuXdz-IlsAQUo!Ydi+AVe8Y@7=%R!L>LZp^#-Jx&ZZ!tWnwWc|E0437_ zak7{Us_BY)sZqeJ6+m?vX{P@iv3GkMO^hlFdP?Z1bP@$$d^E1Uf>Eosm z>sRK#Sh*S8@L@^O=Wb7QkxTX^=Z9i9_}d)9Ub7M$KoUDz`j2l$_=e$b$U{-PwHHjB zP#fmu-ZUwN`75z-B%cEi6>s%oe;r7f?4=o0u@IWOVbw|XM+6QkZq82-T79O~$TJ*! zOb}88no=@RSU$Lb-(nLV)YQjG(Sv-DwIqt;#g=b8gqWNk{O(;d@?aaquZx0l7R}Md zxciU{F16*~ikzaD_usrD<_mX@Ov!aLLdKQ|%BMk*Y16eDydgZw6z0D>lT+wmfoQ&K zF;#w9be#Fe7vj#%({DX;n1CaXrt6rY8#c( z48~R>Za_v7*YMAn9{Z)UE)C`in$z?s)36mBY&M!XStkhg3iw7AGBL9V9g z7n$f`Y0?PQO)m43jPPTxTKw;}4+JiPmyMV`cxtN%|63VyJoxWfPc+1WGB9=Rj7%V}%dn9WS?=(6PxGra^OMz!7a-RaHVJ0x2uBc`m4=_+pkPk&)_od75=7mtXl&~Z8sZ?mi67Exk{64IR1}XzR$?wkVqVIZA z8P?%PfU90lWK?(45>kq)4I79LCpai1(<(hz{nvvI}(w2${QHfvuYG<=x+Ka|SU{#GV zt+HWXKP|beEZ>-irGBrGm5yr{VTc}V-IuivT{V|U&JTF`PRmaO3 z=NdAL`g_R8KB*~<&m8A2ov@NarcRi&9A}0mbXFHmDsO2JxgO6Rf+rm2d7m(~vovPE z=$3K9mz$C1I_nT^{y;H;)vdYXq?w59^|CfLXc2LoW)G7c9ZiolS*2y1xKe%vN@ znwdkJSzjl)Djq9?7KRgJ5#{eqU}Lcb$OQB+A#t3ewzQKxp^ zuA{>R;nJgyTyR~AbhSvy!`kZOs*e~$m&pl4`rz{`J2gZGs>L54r7$ip;Kl}cOwPv& zo6a!Sj0z{YBn91P3E2!JpVJq@o2sDWatq zp3HNZyN%}Ww?Nmx(qfL75-yo*mtC`51+(4+`{ncy@Uy~(>vNlCH!5P*yyh=;&EZEb zT+3jA_3Jp;(H)s+aR1DWe+KC^)z65%WE(g;Llfi_xpc%zDbds*YdY6f3UO#^lPeP~ z#9b;qL@-&6i@~0cyIQy=;oVl_-2?g!>CXb)M|`W<*CcI;VV8^=LB}Aag|OcUQWx@V zh@6Fm+ewfHjZQyC^I3({B`m!Y!-4R=@CIKX8_h@D*bjGc=1a2eCDA#A-;;MJAA21( z+htAgLBKyM@G8pBF(J{FNF{wjCkzP-`No;e_=9H{rh7_%DY))C(rFe^FCWw0^}iDB zkCUudf0(_)5PCB7f5qpi0&56Y7SWBiBD*?vEMM&2U(c~+O_G~%5!^2x4_EzBXucpf zyMnqO{a&rzPyEmM=P-JsmA^{#KPQ@(03XtDYtiI6(|?5ApUM7+&mEcn-$$VTLlZWK zd{ovGZq8)+ms$J}vaf^x=Z1BJKSblzggSER|I&<~sI|U<{1uRYDCY@3DlZ|>k$;>e z`Fk=OmA2_0f&4$W75iO018RP0DZi)){Qd2rt2&qBe|qu1m;afp{Y@~1PWR{j{(f8y zo+hxugx6@q|8Gk_$p{D{<#N$^T|OqT(S&zkWc%L&GlFT9j-$A|4$K?}%>Nif2l~%_ zf~m?S{D%(x;7e)Q!TyTtKTH(+$3$OK63qT;-K}N1e;H$hRvY6SDpf8?wGUQl3wx>o zD+S}`{+i9dkp06_}k>ccQ1|RXvK~|EDAUH=MUX=Guu07qQHe1xzSGU$Z_En ziuO;pu&_hIF_8k_Hp+ zL)b{5v=$G1I9d=%RPiAOmBZ5ZAE$J>J=$sg=U=MJhWFd@XK? zi}eZc+RMXd>E4Fb2N>`A-8oQ$iq5`9I_4qQK|h1-BZpLGc96r5cz(ipopaOLKl|LZ zpOovGwrDN8xbOGs-Hqr}uhkXydjuyWAz4(?iUf)R3{t&qTNE~xU)0s=1u0cWvr1W0 zeQ9zC>R;xVVkn~ynv^QuuG9xUeRnS^f=)a;>v~1_TB=P=+FxutQ((JIPHqEVQF#3W-3t%>2W`JH;aT0Hne8o-)(eEHd7v-J*;DckF&5iXsPqffgrP+9}I*m zVWB|>j7JJlI<2L774$xQ6v7ZUIADWyZ36OrvWtAd3<0Au$PSl=jvicmQKan>{^GQm zGQ^EUvxeW1M^`*?^6Ee$fsPDhwPjsVybk{}6X(uim7htISzJBIM?0z$-G5o?j(bf} z$^BA{x8AC{a{tRGZ*MA&ZI#`m=WaQf^+6vrhkNtyek|MWO)%2bZvHvPw6?1;_4U@n zKQYVS=wUE9psMksXw*{AuiQ}G9UrVjE{ut#a4|e;+3&KCBOM@@Soct>^v}`){7>!7 zMuYCH({3erW1Q>cPO)&@-WYRS8bg)*HW!|->xXK;Uach&=?-lScJ6feBF85#-i53nS-=K6q?rZ zy$?B{UfS3ZGbVm~&YK(gf|1bI9gQ(S1EbGA$os(tuLM-el(lKv4$p2fD)J01FPsly z?t*)n9p2#UKQWea!iopN?6l)|XFBm*}3 z9Eo>KUYY_xq%_OWMAE?f(K@8T0<%j9wBwQ#4sKey!qW9DlW_VOx$ws~`_0eFHYi^X z+;FYK;v>qz^21aR5e2(zf1)|Oo$Xt|Yow^CHn(5DLq4D7Ea^g^QZBZ&u@CA;MuqqG0zhyNJjW zXV~{Bfl|noV?HS#F8;XV2idxoq*QdFKSx2-00RjP%hn3V=9Jo0gw2U^6t@Q-DMTRJ zsoUG2jJ%}yk-gyca#zhf!XhbPz~Q^oQN?P!@a;d63{f*i$-v=?!Wf^D-ta+Z4Os&@ zGQ{8*$M_T4`h8b=xU9w$P&j`@VA8sH-mBQyQY)6RAx~%j$lpRM8B-FI7XO*qd8(rD z=g#B!gE-0C50EHkvkvg2BO1A%d8&!FuII-?0#jY(%OI*1DZ9xibEib;W!06B`@-u@ zYPvhao;Kf}d)TlD{E@&ub)!d-JIa2IdNZM^5;Y6rxUY2h$u4EHo36MqdHqoi&siALC}^{jY5 zjGt7JS>FfCGDu;AtCxE+gfv1ttnF-qQqKCj&xV%uBAP>6w;l1RGbu-dCu8kTQK6z3 zkZ*`2jgKy;g_t!n6P%oq^uXECdmJnHc}9uLb0wEnEu_xCgjxaVXJ)HWY&k_WFY(b4 z9Z>Eml&dB-gRXCIjr4U_G7gN-`_;ZJdbhhy;1`p_sLqAT##|I|(e(B-UtKJ3qMEWG zdbU&8)NtoA2sJ;3`q|XT6sZRihvJan<9kk;D_}K8b`&bgHzt7Zhnv@ZvcU?o`1(7I zSOoI~M=$r5D{8+fNGX_u?@YE?&F6^~;uecrZ#si3ezLZ?ypDZI1ii)C{W4QD+W|u>9O2i zFd4a7^9MYkm4yx>a+rUj@h#%D-du}#2o+97w{SgY+m&^DSj}|MchO8zlq3_QNAZoZ zfO5@#d|ZAIzE{kgk=$lo#M#E!iPkH7rjP70;@=&ay{dHJA+mE#46lOG8{le!Q8oOk zYOiS-?QiX!o+NTqSzMuw;#y2CLb_{sFrD_yn=JmIcS{F>sNgc(=TJPhojoDvrS|B@ zZ1t-0*iwMK`8EzI0{r^Ev)%?cEY5&ECEM&`!s-`u&H6#1rE{+~Bj%dtqiKFPq%Q~f3;^v8q>MxTk6<4=mtO~aqTw#CwWOgpjDhgX}L@%w_efDd>pIgQ$E zPCg|CE8w=OmAGt)+&s?%-cRoz`zCea<@v1cKx`Br!@EX^@Wc2x z`m(^M-^iN#&nk|MjEG*Y@Ugi`;S45NiNfTj35~zqw+<3{u+bI`4bA*Tb!l2gmUuzC zeyTo)P0zA9q+}2#{utaQ*3@=iY7No$!V+;g$qs{@uSk)mAXC#7*i~z$cju6DFSjTE@8iT0(Acq^zcFo;)IS#s*MZU$01BBLv@818T9|~F;xLtGo5-k? z7@L-U+A@I^`;`x6Yno(1&7Cf?N?+Ext zEg#D(nxh#|2^|VjAnp8<9DP99cL1${$>cxyoqx~D4;ND)A=nuxmZ{&5w+8U|n*A0ngw5wvvxe^XJ^z5n)33qE@)5CAp{ zqY+pm|07yzfW5s(L9lBEe+k|MG~k(a!2FV4|L6`Cz;N}*ji-NisCagi(nw?ja$>I7 zHyaL{|l{uSUv0-sT`D2tl5-sA_hE ztWlb*B6YrEK&Pby*jCEt@ih8t!y!9oN=Lvs8#QL<&zk%@T}S_I518%PU;1T^1u(}V zsc6^h4~{2rt0DrbRnTWxrQZG6U<7bdt5H!B%+Nb?G~>zWU7M`q-+z2`l9Q9mSd~H0 zY^5`Af4Pk#>9_ii=h%VG^e5A}-0}Kt!mR4oQ9tG!hF&5;HAQ5u;3Kd0UNHvbf|CNPWUtq=#? z8z`PmU2w3`rb3B&;E4&r&CP9pxv-icW%*RNlKY=`lXx;DP)jQeTU%Prhw0iQtXpwk z9&fx)6>vvp^?$b2()$pZhtX?N2t9yd1=($?JLV zkGVu{s`IbR`eiZ9joUT?&AE+IGcWhp^yg?>J;49TUAqw*PJ=Uzjg50Mb?`0lMLyb| zt>V_!);7qK2T$dS#p`#Jb_c2t4p2M`ggG04>OX(MS)Gc-Zg0Y`*uqzp&e@)4-lXJU zEGc)H6$kvYo%iz11zrG|pMw!(!xdFz`99XY8l(`SG-|!jAdOcOOyKHyIVKr3OHAXh zDf~R+5Wh!_+n>}@oo4ZhE3Kb-%>_h0F=&NKoGZcd(;MpH(IM$TS9w*HOj0o#^gOZR z9dVmNY)QcR*7p!1C!8cHPOTb? zpNi_r&{vvm`%CMGHt*Lq!mAC&H}`_ltrR=dM|sc6w8~yuCLi5WR|d|;#%oC|iF4W8 zcXJ(D6B{~vHQpgFqQkb<(4uh%eZP6XG@*yNhAdj7N=r;ktn{uoFroX#!5_u~#1H&I zWor$K1z6CwScute)gF$wU*aA|D$)-y-``LhFoi;D{h z*OwS-KmVtd6$CIaFi2?Vsy<3eN+5Hf$LrG{t#k7tsg2OK?GDkg)Z>B1-Oabnz&7%X z8CSXSAB1446WtUKrRCKjEY{4=%_DM5+Nl`zwGmHs2=N6M?n}J? zJI0yo|GrnrWo&C7*8ZckqN4wj^SK{znCbmrZOYsQcusyYXcS~DHl^B4sJNG$32lPy z;z+vBP4I-~+du5I>_%!RUTXpI3L zHTHiX`A_I6#HOJ90}V?MM8nFgljVhrd^B?pnd*Q)k;FvO@uH92W#L_-T4H#1-VU;C zxR|laA_jB*2KU(g>O8R?-( zHIIxb$I27Kz(_VN1~T{BqN%~4ewk@^CijDw|F=-zpLZjaBDy{7U@SWok7}UIrJ5i3 zNT%xbI={W#W!s;v{yN7!=@}m%&nBwOx`9N2ct5TJD3)7KFW2etcD~iwzW$ZDDL+5x z*X<|k?0BWvHC}SIhpZ9`d`9(0q6$S~J`6<@=>(~zyBjD$Zbe4I(aJp7hhD~gY@7gE zGtS}@0+Z1P-jw14FRkoueK`0HhTO)oe7w0%+t?s_!02T(g7&l=*RQ$3;N#=Ri$D(7 znaq_11O-`t+||D{G^qtrYxNijuh)LFdU`QTmW5&zXg*(F-m9R|93h-pKQF3vAl_2j z5}8fyUaX%Zs_tO|>~N8@p^biC>u6*%QXx4CshYW$6D%ac+}=mG_%nIYSV>Q{H0eD zY?UHo3(_OT>r?6L6I$%U`l)kA=H4b-RRh!(a^^T%iYAca?p_%g0)1& zt25Stq9f5NNA}8j+%870?zrCT26&Y?xPE~Axz2`ketRouW>y-bYGw7s$CoNa$?SQ> zeobW0*47qrV}1}+tuXGWZ*OJ~BBsSUQhla_)`rSIw{KN#<_5aE8fsieiSBN3_DkA8 zkNBJUO%z%Uy?efwgI49?^_X6vRm6(X5)xNUS8x*c~TU!#9en( zr?q8_{6AU^(Rv3N&iy7e9OoV$aDaPjw1{cRaG8uMg;>r8p>>*zsP)ylr@yKxT(pyo( zKY19parsGLGZQMRWURu4zZx;&;SmUfFU z9TSop`tf7Go1^*tv-XH4kLWLU;PEkMBw75!;3(_F6pSwwIQgs&iWkCz3aea5!Ev-* zJ%oDmG1p}?;Lu#^Vxmll)tVYXVD4@>aZxbl!$WC+d9Q{KtP@sO_Dg@#7_F2|joL46 z_UedO{%I^%0?q7n?`hE51r=n__=H{;G%{=B$7H2FTwILUsnCL`P@4CPTzR;VuV|3T z%~J6BIYHH_`E0iP=fdbFr{^h50~v?YO(IQfAmC1 zUlR_y_M|Hk8(nro66_S*ozbimP=_x&S$_!xyr9WG$R`+#Cq=%PD2kSp1R)L!Y#t z4VUVK!FjeEK7xrLEdS6kfR7o%07nT>;h9=*s>#=M1D%m+cAL&W$Bqhyopo zvy(2=T?C}tsH$G?i76mTQ{83ts}{Wf^@f&dGJ>`RWFizbNjgZv_H^iHctY$gwr5-2 zg?S9_ieLMS4Oih$T;Ks6w5YeY+f{IuqOU7=FCYg!Vd4;>NleicqmzAAxv(0W5?=*U zB$mPZ0%bR5)+uXlrAalIWsg%XiFjEPO&rMu3Sk9SaE$XY6WKlrmGJGtTVc}#%;~0l zX4i%h+Vr{Qh5;4PW&8ZyFqI({e#CY4X7b?lr+t#-z}_2;aCz-&`QA(E>5b<2Ra-Ip zGn@_#4459BwXit^IPj(647rY`B%$@@a-DMbZXq!s;O3SB6`3tUaFRT1gy)?`=bR@h ziorvpRI8dq%{hKvgi()hA`^H9l4bDAIhGDJ%ZFbSpyIMgUf+-NFR?GY_-YD~&!E2@ z*j=c5427z0R0Vz{Vrhji{$AeIZvvx*AvH+~)L$2tT~Y6-?XHtrcdF=%syY@(US}4} zO1Yj=yZ*{H8piG^tF~GbalXv){s%E%uGK_5HDb(tv`T+4LUwlS;(}#-Tnj zb*{gef1uv{cUSt=gr3G<-C%;D84$HZRk!3t6#`d+tpy88D9kdSkJVH+ETQ~=;T1%N z+OZ>kKf^1uFZ-}$S2~%16^BWu5c91SEZ5}4(!B=5_Q~WTRbXj{jCxXwSalr_DxxEl zFvGESv-8uJmrew?4BN|>Gk30;k9_@vsEdL)_#{;mq4PFxl@dL*u%vf8ooOcioJ(PP zR6{vb2yBvtjy&uNWkdc3?yU|kOwSb*QuM?H0@OWAu#f9EHQM8D7kRvMs~f`&pGtNi zT=O}TAd)K5y3xh7p~nPUxRl^o;9qt)Yw%83%mkS+K`CN) znJD1PZB=w6;rW!0d)K??y0GbGilXZduHij+!I_N{Imd5{QDyNhXNN#JZ3~(3J^je2 zh7x1UtE;~yd1;V;60hC<)L zOiP`=KUFnFEJ@46Ae=#Qjwh9Mwdz-GcAl|L*0e*NrBd{Uqs;L6NjQhq;TuTbIW@0( zl(4N{yEK|>ritkjBu~gWBj#?p##)s2b&O#_nGL&_)~KMV5#9QuYKEyu-}94Vxh(|X z($#lYwLr6{mvl9i_nNFJ!n9%_EqvOUOlCSNR}jjiW-^X!qRZxiZw!L&DP}@fRlX$g zfoEt@g}A#+wft5A>!9d!f{S3sd4gkZ81qHhBK(qb2Ruus5YD-`ZuH6LI0_jm5!*G0 zs@xn)kWBr#6o#teX3*mNu#K!`aW)6dCeWaS_e0=OZ1!V1xb)So#thC^VYcD#m-{;N zxONPu<$QZ?t#i+4*wS&>2M^&j{zAeYpk!X$8{NM9M@PX+vr(jCT9xNl#=uEwYql;l zknY@VBPwkLgf1GhICA&JP1Vq`VtHvC^#>Sd<~?+hhW*pq_vgvv1!-Kd%`Fq_kWayjjpZZC+P0~n5}fo z2d+s{MXDfCh|y9c9fX1JRvtC-5HyPy9{B+*DfzbB+MHW;1ppkQRIvX$-!yf?B9QRfa*g7L7#}TM&m%d zHx@$zXe=tv-c8^8C1Bmx!)+U#qLlHZ?xc!0_PER{gQDgNsw?yms13`*;K{9`B( zvT)=FUnT*|62ZTw3?gPsknsiTy?0xfKmgD&)Xqw*zvle68=s1zWME-|Y+nm+e|tF_ zL^b$lNJ1s(Ncy0dl$4Zlh=^ki>OziE72>npmcqW4V z_3(1E_JQ$EPiVbVJKbunIop@-N{3<_Whay z+BzCD{@n0!BJ_EO>PO67x6M9Z4epVjQs@&tZFjc7V?r$MU>N1HzG4`B&f2u+E*B1u zR0nXst1rcizaF7NVJa~zS1H6s^WJyr=3$(A1l%*g*}&?#6sl1)KUf2zzv+cW0`m>D z)ML^%Oxn@HpoP;-Cl;HgTe41sLTtC@t}=@Q+*%#OU9)H8lmi|%fn$2z_MI*U-@u)t zAS`*}QK~kZ2Ne0dtJVZ0W=5rirv}HE_qI}RuX};qK;dPVQo0f2bkl5`auVNU5IUWyGc=9_3QNkat+XJ*6I|O%CoO6FnEX?Kll5QpKox=gsup`r;FJgJC!*!o!CBx~EbGuHoq)~1)O>T;Gp=(2*M5nCPF1Gs7GvgWGn&W_#dimz%hR7UL88 zA<@Z$wAEnO)a*{Tc!R+2X)p;I(~nKtcX2zqHAJkSV|FT3AK&%b0Ch6RS1NdCj>CF1 zM;E|^B+h!sN<(Cm9xzm|@iPb#D0Tk?;&_4k?SZ~Z(}M#k`xHSSMqshdyqq@;+L()G z_0+8%Qidop*80`_I3A|X=0-uTbRZgp=s=x@CF(nYTbf$e^z3J|f=6XWoXO5hEoQfD zex_{HS@vIsydn7lHLGWWZB2Q@HyXK@?h8aercNe&_rv!u;=7Fysa4y-Vg=hY_+EF* z`s>h)=>m2&Il}X}!R%gJ)T$}v$TkGI!s@VSHR2tXI(1lJQCsm}1yL5F3^Y0ybAe@K z+WBEP79OV5t$zd=(Lb|XYUD2q%Wd9Z3^Svo*;~ut1?X;kB#J{3%Tp98G|QLhVv<_^HQG3Z#SUH-sQc z5UAOOKyBW>IhNO34%M+p0a2F)sF5oGS zKE3AhVknme;O>NDAox@6%ulRK7g*Od@kK=byn}NuytW+A)X~P)SBJv($Hg_HcW6U7 zvHl_RFe0m?3P^Mlu1Un#s1^3)GNurfXBBRztWkpvN!jy!m<*uk=ofmT)iRwkA2Vmh zk-mOKIyRZ++|50zXcJD?|J)q(V%(ekCI|lW#9zqV<@r>>AtF3Kj$D@6E|>!Vv!D4O zDaPqJBsy6at&mjS3W=>ZK!Wf!{JM15z5JKfTl2y>-heJDI9fN!MeeVs;k6F1IOKJC z_9``4k$VQiNtab4${i))q3SQfyDRly_iet^vQ=0^t-BS2)18?6Jf$=Y z6GvePz2;-5(K0XhFYn(Rlo`9b&GpbU*Hzw0cPQ?>SSg9vbS++RqH_@5!Mf$XPo)Jcb{FsL>UEBbI2 z>d}-A_wIC9%5-->Yw&5jKHECnBja~lPW2CpNszPV2riu*E@WOCDh1ADamzBMZ;o|p z!gpDEnyJt6E%MkEuU2yu8wxO@=}TdziAO8V?O?XZOoDzeN1AHkz(1_QvBZ{CohJw} zt};g6cFGrhXppmN2HKQLUwb5!qo!q=KZ@Rt$X#xevB)z{nxM5Fv(9)lh3kne!Y5tT z>uFov3CIyT|&HfP4_ z@w(hAlCJEbh3S>y6yk0I#q?4?S(Hcwo;z<7hvA^i!b|H}TB%kwA0Xkv9Ef+kq+v6t z)ZS^5sTmB7QEXW#LJK=rStk3PJI;efc;46{(_zHRfq;Y68JjgGf`o_Leu7 z#EeUO1#KQx?ZQRx=N;2m#oy+1Y(u_9f1EaCS|YvHAboq;TQPjL#Z9tr_j@*D|4xuk zIBUaDpd^Oq@^pHT84fiFwJ6ACGkt^=JOLwE;oBpxPV`>_ZobEX)L6{l3wp+ zmkh@f|2Hpxby5jac-%)Cz-JH5Pz`1M&7)7YEH*b5%(nVeU{%lAL33hF>MBz6+ z`2^3_iV-6Ta-|MTPfz}QYroVme!X5(QqqL)rQ8wU*pNsBM5t`*7jWk_R0pR;Nq?TW ze`E05J&Bdr2-h-tLUR8aNdsFFSJ0dr@O2rGswc>U>UZOmK82fETcJwvrBT)H1!3wf zRhs%zt3`g$?x!SGV7lno?sA}n6q|hM%gQ(9xUUdE?+y60Y(ec46`NzLs@bQXh;hs^ z31^XxA+#3%pkS1F|JoC*(c<63?&L^+xOx@=CaU4UDKvS`UPxIScEvZYG!(S=1MlbX zbGpzAS0?YA^<8DdB2b}NQy z>*@=sh^-O#AX(4Bw#~W6<6UC9+EOc`;1K>TuYpNy-|lLdZcxoWdW2D|`O?K|`mK0Z z&dhRwF5e|=1PS5knMv+-PSgzcrqsAigLjyyrAFXdx$ghpsXo`d)4wheIrW8K11P4}_>~lGaD+j&k53jc{Dt;w<<_}=GE%QKxYLcpsv-2kV`&elK9su6eL7vR z0_PzgUlxCSHu|Wbw0p?u)H{6eCMajYU!@hv!KW*~Qzo)6*CrthCQ$kdmA0=+Yq(q< zqfj*S5po(C?>xKtD1?cbBTGrJjsrb*J{X~V3q#|Raw$?hyHn(s5X$e(lo~%Rwr=cuqaXJLbgO!_z|rRQfI&ftFq>QuM%yz6gnGxgHa7Pe!p2ZSlZGi3glS zyNlT0Y_QMU;~+4}ED^7;vfO1*+CeGI_cqii`y_cZY{ypAspiK=#G|Oc(gHuC@ULt} z;A#|Y>mxc1<>%Bg-Wu2 zs(>g!%=kov{?C}1-x`2Hf6_avB>DUaKywQMl-?;YKm#BLXaN5g8_&trkS{;DdN`j8 zcJG}nR_tFyJ&OLV$%K%9wpis@wtF(GSTLn`E5XT-)EdE~5s0%O{p~2>G!JXh8QW;1 zTSUz2iK*yRP7Cy~7{L5?fjn0Y9il~pt|`nO@GerVA)YLrzfO+$Z74anzlq^{YXRHhVf7UA_%lVeFQVBL{N*T~7bTWCv0_XhP%pNML(+cXP?2 zO~675!K;(t+mmLxcBdCu0<(P@x@4Mlj^VrN@c}A_TT8}A!LjDx-~bZ~3)!j_%zn+4 znc4^)_Sx*i)zmHzl+`Drj5i|MTQLeY!Ju@E4n+*wO)5za zRE2kFz;l90QN|k2Rv;HwGC0i0Gh#iu+m?<#zo&vetS6Kv?9Sa&t@MS&M`k0PnAlif zK5x!BI5mFg@@f^>M@883cQQS3ip$TOHITFou*bzk>Xc(Hni640e>vXtU)MF0%6MSiic#Y;fG>9+>6f zfO2+>(?Mr?JEU@yiiVdJF^@*kmZTirYeTo+h{T`x$pj^v$$lBueD!iZZH1^`wJ!Ff zr>B>FDlDM#q#CHwBczdF-n(5g?@y#RIKRm)x5|GRi0lg(33buek zR!XFxt3@YI5&>2lA^HPt!dkqXi>x-g1k*_$yK>SpIo)4db~^#}7V()|#(bk2WG^rn z9~&L^SCjc}rg3_e3}0t{c(sJEpLq`>7y0=Pt3%3*eWAO1^+(i)3W~?dG&~@4(*Q@u zX!L{Y7lS0n14?IEkWdjD|?abP;n@omg~m7D+gubF!#qjsml3AOy-rJ z(Q)~_gP%BD?$qtKuEx3gTklq}i>8;BU`Bv0+mbQZm=*=KI{Z7N{Tv1#Qw$v*?j04s ztUqCEH7c6}XK{|8UX_8bKdFBB$h;KsC2J7y=?locDq_PzTp-e{%t$boIaGN6PV8nw zWQGP2*#>#g63J&V$mJuPw$;%n!tn;}B67Vbk-)6L!5Mtn#Iun%__oJGsuVU?=%FQC zlc51r?cizQ=N89-n8wX{%H9^o%3h5lVxCowi(50RUtYOM81Bp5&n>9^H1!Zx%4C@K zrnkSzAU80*cZZK(SaxI~>mt=Fm9%87it7mfUwM;0v?B+))XQkJhRBlCBeQrH{?kDbIyBF};N16vl*; z{`lSVJf{uUcw+^o8^J31ugaK*>j?#i%Z>wI046<1LR$5_(4q_2!}AV8a%7(?@k%2q zmQG|7AVB0DvgJgxf=kCKC= z&}N(lVB~o={tKkd-r+Y@RaNZWk<{$Ka^*hb&@194eNW|cHX%0{g|lX^kzQ*Fe(CPv-Dfu0 zf695}xt4?=G)7@BH}|}MJ5Ub2!>XN*@148`Gmhc5&5m0kX;;BHq~NZ}?DVX_f+6VY z|5L6JGdn4QPbA$Ns?skN;EE6k&*6AF@>y{O1qGR%Jd;daU%M(jkcMC3b$8g4*3_2< zmoh+STuVNo;}-ncLcUDT`}cY@b}lpYPO_eOGp9uYs%MtMBrQ?x`4X4*8RUp=41p(F zh?f>FvN5Hc__JBJ@n&Ba3sB{0KiPZPm2csQM+h7P#Ct7M(3zHF2z-|hXk=k`?X|UN z`0mda^K3~xwf?TJB3LnRa_m^}HNs~a4R&VSF`9Pj3b}!IhfTx2&(w_6P(}fY7W6Gk zK5!xzrj%NN_Dhjv9Gj6`D(MvxgV)FINVT1C^EWj%HCcmuLJP);bai??=kwV^q-&k` zapI3SAr|$fp`gO+;7APzdC3v!=?G}(VevD|W`Nfl){HG3%*p9TJ+TXCDq9y$7I%(mC64YS&~6>D`DjeclPdku954{#n4Tda zuxgFg0!L@TbZs7&mhv*ip;9RuCD|8x-Bsw9qhbG;59Hz%--*pSl;P20rj#=uo2AFsTn4GHx&s=2~ zKY_DkrM75sg}EU%rx{bozS_H6Big=|el$lT(vbEj!kFv+I30PD`9l-;7H_J1Y7Fh@ z;`k64*8V_f=~Oc#Q!G%3Cd~p4aF9Nk9Oo|g-3Itf+bp<*!c1tVlsmAZT@5D03#>dI~C8wXa2b?U5 zW*vV0>Ql|%(#Dn)yKZiH81Sx)nB?VJy{thZ-j=`5ycOY@o2{h4?TpK1>J)zC+IoA0 zRatcc3HARm!@fkAdDc21Jk+RGf1pe~j26X1mvpGXS1p90HCJ?_bmzdbrAt|X0{{RA zWNF|cAGzG?>z@v-8XV^xAds-&>sN*2EHy6BrGC6m^o4k(Z!<^^Jt3R^@tGC#Ve1=A z4!zFR%f=X!ml-<^tQJ%@uI(leICtG{gWjzWfY*DOqZA21s>_&deI{^G5i6tJ?3(Plo#*GVn+QKs0HgeDL757O z#SvbM)j*AWY*epExLilB`M!0~Mf4t%mP8=PYPan?A<>c21uBz#sSr;FiY;9`xS6Q3 zh1wg{)h`;Ns$hk3p&PMeel5@4m3E%`GS&@J6mxPc*@-jn@oBF<_wgRtanlZ|c=mOp z)nNKCQB$WvtfO?qDu}}gC&@c$I?Z#abnu!C*yQcLJY60{iDuR7#*H?3Q#W;hwhW4` zG4Eqbbvfz$ojjfv)~jC^{+k9T(!#KSiS&yo<1gx)EVm`>MdyqsZxtkEh$6RF=(=x; zo>~^}U6o&>0kd8Qk{{+|eLdq>rIUPDaTKx+nvVVylz7oZ6T;x~v6nS9dO4$oQKb^e z#t&QV^s||s-j`LRE#B2Y^hVF3d!cwTHFkUX?Bj12r&C`m2PmzPF=IY1e2;?E$Z`_x z7G*XU(u+iK9Mh5GR3tjF>v}SiYwk2>umvj1pUb{ZGu0JaU=$O`NrqDm-MoE@(~e}( zuk(lgUeQBZocO|WnTBoo&9`W_*OCRM2St+(_)Or3kcBR}&52UG3C6E7L<{RQFjjcR zekzz^L0ny3`oR7i>yv^awSjpt=aQ|BgFJEZ=RB@82Gd=SMDrdc?^b`?|S zJXrWd2+c}fQ)q`tI%dHYp;??}6jk>+^fF&~YE_!F^B+U{+~(HWl#3a)>{S}kF%%tA zZ#I-wW_GCMDl|`cFPs^@m@WGcYymB#JJ#QZx`@Dw^Z<*8=O94_D1)VdI^xf8CN)C| zItAaj(UZXntD+lL$ILA)y2WrxoYDD~O4gp-Eh%zR7c~zrcZ%-SMg5d(#g5m)ril2A zds-xUC`zVN7dVso?E&54I2e;M6XCY(M_h zqIRdsIDfC8)CY;}m$9-37Ly?o^~x1pQKZgsCtXD&SQwzPl)JC2$Q5?#4swG$CE$n4 zgv3A^G?$DVdH}>o)tZ6%(0yULa1ZXr{-hLu#rjD{^D3}r7egnsB`y*6)nBea6I_H0{tS4mje_7T;M69o3 zgOh}dz)=rp65o^3A@A!SJ;_CF$D$WPqk#r2PeeMn=1qL*HkwwxRDd7TZNN3dUzcT^ z{0(Ap&MUNCni*T0lGBu3oVq*gF!`d7WULmC_Omf4X8~GdE!THnnk42!fziIChDa~% zy+vq$F_xNJ?oiX3CC>n-JKjN4J^&|hM?AC%M&EPZYMOtR< zKyd5@;5$GXW)2HPS}|lIzk2fg@W_kh7q(R$w&mSZ{#k?dl&dbVAue;fjbq;Cq{09o z;|U}(obmKiGSV6L56M4V28jQw<~d?3SP>4a|9IVbWYnGZvMJ$P*4iC!C9gD!@F?lb zfWV;5S(xWi8@Kife`=#~ZC49;rA^iRr#+<6EwSWcxlyX&*i4Oro*KdV^#4zr6;>eufu{(7;sN0i**Y`tRy*( zfL;Y|f&aKNnMS#JLv{gh-LKXHS7r49GYZNf|2v;F0|~_q4SMB^^}7_q#WkNh$(YT4 zs6)c;JWipdaQXu0R&@(*1zZcKHnn=XaZ>sS3Bw}J0GW%np?ZZMS_Yv$bvx|}Dy7i0 zNKbr0bV4)~E?LNM?WR3p@k`pu8osGg3fe1ko3&@fzN^6jrm>g%63I7;#o5y6*tNnd z@skApY64)J^2O(sBw}ek6P|wdN9!7GC}R+DKxaV}ty_UE8utJ1>U%m`8n0^loy^-F zb`w?#&-fH{MsiRR(lk3A{#5ux@cyarVf-Z`PtGAno9J}8oTu5+jHKyPiYpbp)s69# z;q)rkwj>L04CAkJQ{IL(M5jlzNFSC&2%|BPaHhBe%9`xZpohN%#?l+=z39i;yMKK5UEu-L=>Y0gBm<|piQjpX z{%_?=oUkxGJuz6)syg#cBK z{$|8l?Y&J|FF;X{XbL#i-^?Z#&4hppag zZN#B~MW9YnSZ(rrD*s;M0h$Nwc{JIZ8*I&#CnhDI;}{+Ho2Ac4l{KuHX7@de~xjVj{*cNx!ruZD<#CXv_M+xz_K<^E~yElu3&KgCQR z#X6o7TduFKbNUiugv25xf=FTBy*UC5Jaiz}lwc|*79cstKy@mNV zD%J%7QLzeHWD3Zr{%D{8T~MVB?frKEsR4}BDtCiv()+MQLGg1@qx@EKzp4K?4flRP zU9|ETAp1MU6o3jKyWiUQ_!p*;7r=`Mj%5sGGyB;7Mu_+OH>-ZOlf~YAPlBzJxBWjN zP!}&U2AJL-K<5AV3dX}>(k@xUwV!IE&n*83RYc5%#Hs{M#vnd9W8B*gLB6QLSwTtVBF zV+inBBCiko&3uov93z|8he?lWf)Za7mot&dbUUi+DF zSXbb5x3kajhrS<=#92q~DHda5^6s`iI%mD{V=qc-`e}1kRoPVRYhYaF6tUHtKLDfC zNcH80QnNr7hHj$i4d7XBwRXFexVR)T^$pa?NwImyeg)Soxsz5)W~A~p1Jq#+G!CMv z#t%7DK>Y$i+6t^8MC#8-ne%q%pgg{Aqc^K3d^7*A>dq=Esy}@93W#)fH_|ZDDc#M` zf^>sOH%K=~gOY-DcS{Q*-3^jM4c(lL`uks;n{#n)&Y3l9)|$0v_N*QA?(h42KhLx2 zKf5u6h;c9)k`f~!ghqg7S>mIlebK;HQ&K$h53|oNi^#Np*QgR)%#F$)vmyRuhlxjH z!2nJ+vdy)am(h+G^OT=$?WIm>kw43m9ACPRI?1MK`j-jJP>+D%E^k>ZJ!Ww!sl-#3 zESJ$SpJS>w5ui1!1wDSO9m7vVRzftGtHRp@O*~$ES=yhksbcDiD@8*~E;91(d#P%v zJU>t_o_eG5OA_fz1|zsOs1`FwneA&EadADBSqkS_i7}p3l&hQ^CAAMvf&^Gz0ZHY$fMBkSaUf#uFFW3 z6*PK5+AH5tn3DW#dmKyqSA&Uzoj@yiL6zZKuVBYTi$p2R2YXo9$`yk`)SAbd!0}jg z*KgSMOv3KJ7$V+CPCcSRwzJj;UWApRbuN5c8J;^D?DuaKu`S0+MDZM@65G+OLQ&!n zn$%Qkucf$`5&At}rB*N18j3Yv>U5lCE#3W`@8t*cw=LYAnQ)M7zDxpc8F9x6l@4#Z z-)-hU9J}H-LuV%Ppg;X`k@U!%jCohkTowvr?J<#{k+-`%sus|HGyKE)PhkNkm?&;g z)HWk>Usq}h9ge!*S!8U&E(Kjo_yPo5D`u1VoI(;ii&CsGg<049u?yE~{*oQ9XFb*? za#^LR40TAqIk!=JNW+oH6eSh1M*9{2;GvpNY4CO+-5U?;Psa7sIFn{mwXd2Ln%9i> zmu5oD!15T3Ox&ParCOPcvL;l~Y?`$J`c zGwbQ&iOJYJDmmRftRqfsl+fOQbFPImlkM~>=s8_t$1y)69uYN?>joEXHDP|?a+Oqz z%!U`xXQ=GZ*h>LwSUxZ?!?p>Xq z&MxeS7zW|x+*womnZ5=N;m*ifs8`~^eAU{Nbz|*7FgZ(wgA@RBIJ2&V!z>H{C=OhK zV1N0|*Z^jos zv$SS-t&s6K7TigQhXtP5th9ZX$?6RCB*JO=EJ>A`2~Tp?b46v96~s33gVwe;Bu=Kd zzB(m2&v~s9p~uGXQqeg_#9gMVTz9WO-W<`V#1x@z9wqZgqGeVcAh5a`Uy{4#eZu_QJ0rZzwmz9p0MOa(jY{JLzV)UDJ$;% zat>ZJsf5Y`40j?k7=M0Ess+7--5e1q1#eZf>8j!JyU=Gsi;|Ap#;svGEFan;QUCyp zJ>J1#J;b&rf#5RAB82cgkSFh8Mr)fF7Wyl9E!f9?HtfBVa9F$y?Kq+lPTd}sz|?g+Z`K3&RI>0o(dJj z)}5xlyq~!>QeheV{0LS5K%GR%Q=Su>@#zUQSo~+A%!CAaC{7xrtt-&TQ8lU%JnoZOW&sFsQsG8KJ`Qa zYdlt&@9Kl-&XFE!6tVanrd@62GrO2TvFSe%NFMbFPgcr$zkwckofY6*WM3rnnfhM8 zp1{!Iq%|_h_$fUv@e#>vx!%JMS@6Nw&Uc&{_r7=m_fzSPt)1cAw!jly7RrLkcqG%~ zJ&D8h(71U!UuoeMYKnXWxE|0QIQEm9HvJWNY$wyK&Yk3{81jkn&HU(HTPF!Yc^X^c zGlf7bWMa^;eberDK$c6k40EyKc%O|a{j2EMQA2O-z#sgL9*{q zm6P-=NhaT8N+G&VYv8_`?JH}Er3&KJxm4-{0^C}5I}!NFB$e{2>7(R#6UdQc*9*F} zXLsO)4WsLbnIF%X8+f7J`JI_=M2^)%q1E`v?-G(a%#kv&9n=qMr;YRYTwb+E(|Ye~ zWIBBQ8cYSdQfMaHO=hg2lk*XT892y}x$DybUlDSS4K{PIzGTZRB}Y^k zv44!n7aZVL#fPk0no*b9KR;BV4GKOY>&2~{lDY0fBzF50RVUCvp)KlomAyo2A4hTw zi7Uuqkz9Iw#4u9Weprm;SJ=jwVaCRHCemTBRu7J*2};{I;pWyBYCH_>DQ`Fi9VcuE z*!|=odfA30rjW|)%B;qJ7wk4mpNuR~OD~ukolSnfLL|MdyQW`-rz|VM^J-0Cf?A~s z)5(3EkcODoDcpZMa&hX=IvF|HKALNWmdqTZ!$T`U6m!9r7mr`{u45=#+Wi}KRJ0&Q zD^lY_6^y9P@89v@h5VDoah3P8)rV-b&8!-28qwAO#P$KxfMRV!{0pMc$=&VL-m)tv zdE}Yy#}z3;d@jmNW?Y-{Qam*B4nAf+e9U;5)4rac=xu`puiIh0>Phn-Yn84dv@Q!y zyww%0Zf>4o&Zbf^4ab<*JUX}FahESPf-Ro(Yuve2d2VkKO7of5XiWPnb=_5W3>v?m z*|l8ksUzmlfv}~d*L~22FD<)U;?+@oN65`0V6+qQAEj6(xp-vW@neN$+C(=vkY$ZK z)iVAhcr6=Nqi-mq(b56iw(Ye!H_E?{=|I`h%D%oe?{ zoVV2`-2E-n-d$-xzx>8eb67yKp#Dn+^kh0^)L9!Z?#yfLx)BjIcQ-jSy8k^@B*ZH> z#X1I5^i}gpSRG5pxgC2%IG)YP-Am;ZV($LSYfT6u1@U`zOGP78ZE%*!$Qb{8s@X0{ zkeacAADl#KWa9C)pKc_Ef721Sy{0pE{pq#Jti<`*nf=nSxa6&LKF9$jYn=XE5{Ae#4oe;>@|e;I=s=S3amDK z&^Cpd?UNR}NXoly_M8frVNd4Vi!|*C%(!ldScq_^QF&zF6<`poL8VZC0w)C#@s_U! zRKr!;1w-A6vpL4+n!i-QF!E-)El1^R+YW<N4wqlvBfVPCaKEKcrFTR^NXIXGxnT$0Mv8-v zny4=XRpiG8oK>!EGPoyG{hmWePyT``TqE@;#gRy>)m&^NX=Dw~g>VFij6Psj;R)eu zk*5l75t79rH^Cf&LnyUcm$)#yC~DJ{LQpuq!z^TLsg&@ukzNITbSv$l!kkBJS>5vGnI1yZlP?ivO`663V_(!y!^5F(H3Iz}T0%NC)mJro?~0lpszah*HX&`eS5F z3C-x@ue<9FjC#g1q4^>oUrW-zf+Ht@+I`ugkK6hSQYhE~g5&=$i{rR8Z+(QxA3UKI zjh^Rw+8M?^f2b%Z+}3bL=z@I*DtCrmUu+WWBx2NDUjD#I*&nRt2eyh*c(Xldm<+}Q zPuH`$W_!3R9^f3K(4QOd^oMRZTbcb76L9 zoU;Gz>HibsXW|><$}EMBhPDm>sst!#>svoF9EVsMqVoBs|AK%N0Swab#s%lw+l5IW zg77Om!mCIAGZ4ucC@TsIiUOYuXV^bwP>q(IL9C=*ac}6<(D=tE+Q#!~Jr1`+eP;be z47Wwcmr7ZDU-q=MWB+};fCPa4<|F;u!WI|xy>Evbb`Ew$*$H|q9wD!pwJV=Jd6m0< zsK?--K4D+LGXUoa`(upd77`*A7Z;~0Z&*ESISQd2{ut1K`p#fp@D-&JE$W$JPp>bc zWWNmb6y|ELOVWE2xu-D6*x=nW{QTir#l8__6mQz|cCpoY8FjT`0Ij0%HHg@d`sf*+ z8b!kT#~a!iMajMvnRDmcfyda+utBF7Rb5^Eesd7_tB$jJsiqCmd)TX5D|F#KvT?|= z>PNo^w%~!$3#{KS9zOalEcw?+#a`2vRC(&Ox2G5ztaj1Qo{FG-uY{v5c_a+>2%$wh zVRgMHUVacCz$*215*K;ro%c%hhQ;`>FTl=H}KSxGuEsU+B~`f6d?BTJ&8w z;C0$UX>UK{I;>qu!@7}xjm%03fR1FObUsLUKv_g#n6NkVr`IK3%lb$D!JODp5fuuh z1uTA%nCiY3Pn~>SyFHRMBRI>}aR+kNw=3jZ{OY$xMU9WXJ1msse>N$@lFle_>ksYw zQwGNAfz_%uozQIKIjexmV8Cph$|9b)hOaJ}7lx$GtG z;_2lT!1rd^?NqE^Y5@V|3WHz}gH0z38rWewWc`i~f;xsw1OH*>@QuP@y*Ejpmzgggr8 zUll+Ms+=5YHztgyU#?faYZ>|ktAA4#^=L6sBut~SbtDSU;z3UXX^{xzr}D7X&)WFV z3}nzRx3m1xt<*SOk0lPvN?C8*yr5tmg4>80&XoQGFPdYdj*dEVg1Dr;di`Du7HO8m zru`G}-xXk{YUF^~%hELA%Hi0Zr@Sf53n;hT|1LIv{Y2lD^n2v0AAV^0ycFk)m*Hi)yddKq0&+W$wu)l#$Pf(g8rYtrCEPJY|-V+L4X(lPaS|&dEHyac~=qC0l*|+#~&j*xB z`(4e^#h#$(KNA`2*aS7w#2r>!f9JD+R~5_ar<{a8wT2jA`n+J*s;&OKeU3$NO2UHq zmpK2a1hYr4p`0a5s*B+TWGPb7(_6T9$*@~{)*`O5NB&tog>b+|e{A>BrHxpL@7`I> zh;aAZVGWe&=QxRHZOvJa)9llR#vwhYEj0(SC`o+J^2m)2^_MJcY;3weH#u=axrVgM z9DPj}nAwpHwg()(4{F`hD=`Xo{)b;adA`LlLPA2~lDYRQitd}7wu}!2-?~ReL>w<2 z`SNJ^7T5u0l%j}oSTF~5u#x3vw#`E5SnqUgfN(QrFP(ge>>8XSNWXt-^8OSU>vzR^ ze}kR=fmSa)w|4}Qid{k4?kYTri($k!(ZK%jYS)RKa=H>5sKQW+_tz(zU(9F`IWUd}&cCg* zgpi_z2Wg#L*2+Ghoxyd*(p8b7h&@C*L{!&p=jl#9R4YmmC*=-GZYqr)x_k~qtPWcO zj!smxe|pOOY9M-cR}b`|p}u|{2o59DlYU)zQ8^+a8+h`hDhJ^0E+Nm9qlTRfl@Aqj zLJi)!HN|D+htN`@Lig-K+%hUD88TU3FhlJH#*?H4PoyE(^$cm5_iVY`jn7*$U1Kyk zukT*zv7GkSM-h;DX*B&7AXxbXAdJw1@&6zZ3QMXm zlX#TvGO07|tM4T@^Kzcp@>RhjgR4mMx+$sVN=rOT&0RVi>%%3nO0bHpb~6PL(3IkP zu!`H!6k;=WqXGv4kvc9uJ^+BrE$@Axz4$6*<#N__&~R+}+JiR~&%iOV^w~^9relr& z35kJib!26J{DBd{tZ*ZBYrQi9fb`198_lPFwAP?{N~Usu`r*uXHtYuWOY;<=QYpS8 z3x*?VrcJyjnTFlUGwIp{{QdqhbmJLyRI|ayLuItw!w#3{__p<**_whIj(k< zR|jM8HYl1!gu~h#xFfSFKE2o+u@mjZYN=1z&yp|!@@=|1$QtsPD_%yIkDrO>IlDR(%`2NgW_?$2_SvhA)#nO*%D(^gtA0{?c z_3jvigSUVwxCme2?vSi^K&4AoOTx2lXI6gXcHqC^JIm%#dFhXzG-4-Khma#bcyJ9a zq0<+if&X16jbF9Kv8QL)Tk#F)RnciJclW~vqivZd(sc^I>a85?Swr-4&;w6c%o1Dp z0DM(JG0_y*X4P&fkOwWTzF4iAcQ+oXaT4{d5{K`}B77->W2aXmnt+5UDN|-wLGk%3 z1n1nt&6#XP21qo-&uqC_Qo?m?@4V30Dgl9cwDp!}XPS$?)wJtC!UXBY=h#Dc^y(65YWKrS!C^1QKGkqVHn7P(w%ElRQr(eVvb{}Y;k5~WyhNjf#o0t=gj_4PNMWAE znSkMn$N}@jciqStmG+Xto@{%+$Ad+WQ-6N%FQKRMYO+KvHZ1B(&o`KK@B-YI&VDLP zRFF4QBZl9nw;3_^50rT|&4GHWfD&MtLrOw2$*)-<=MGjAD;4{xz#kxst9wbAmho04 zrgyz=i-iR*lE%A~b^r8BdSl~?fU*97z!(*e3hv|1c#zF@xpm?a{qkE~Iy^o}C73`E zwSXMfcXmqpS3t~I{XZ{MD3OvIO}T2wrI(#xe|=M_YbHRm2ykDANUpY7?(anJo0gM8+zz_t zan}`p)ll^3Y`L2mO%(=4+0sE%HOfiBToBdP$zVa~Vr14rxSev!ch>b(k6WsPEtA!V zv4Z9R7Ts98k;Lzmr>jhwVT#Y$ZZ`W0Zwu2++>9Y3(_M9=0cIt=*uDyLCuC49se5Ag*$Lqi{o%_C zkv|@eg&>#h_7;y;Hwh7PE7b|pvpEBj-z`UQ9$^L#cW9&Y-R{?*SY(xBbhBB#g{XuF zq*)O#{s|V&3T-YUWpCe?ikwY-jARn%$6E>Z4`Ndr4pC~#!xkZIA#y`%LwVl3*IFK> z^rhg(l%@0(2w?ptUO^fnFzUvry>0c!;*=eKw7}A=i`*uYtM7e$R zHV|tbsO+ZuSReuQRL|uQ$~Mo($D#J)T}D0Xk9I#8#*24G-e0d`9O_=7s8_&^npVG{ z3bl29M?n!Q z-@>_QvOQ||&~!h|a|dgBRBdgre$I;)q+XjD8~9Azx-GKA+K#LOSa0>YImF?OqvXr@`6op!{z;AY!bE}fXZFOD-F;)C|o+l_50x0P`fdWZy65ie^*K7oA*Bc#tO2uw0K7J3n3GGGu7FE?-B9<-p zmFC*(y~J|G{{z`vaGFo)WnV-L4Q0AV9lfU=z2mNM`od=n+%`3>mV1ozbxjd}@1&@E{q@iyalIb7|IS5(M!wsza{ z))C&Nq5*(CS;vdN;xN5l9S(4sxK{{%!G%26LTJ#caJtR@_@P zDoUq3Wyj4a#qNQxksU5ZFY1M_Q4N-6g0x!FYmHfg|j}yx97Acn8&M1)-*xq< z;?_2zRGBX*1KF_Vuu2LhkqyAFdLz+0v}Cuud#z*pfqumPRRA(RYj#Bin-Mu+fRs%Y z!3GWU?T+<W95 zMe@d9;UpCc5QWs&IY$xxwl--`j#qU+IQ^3+L&HuBNJ!s$tgfX300V-+VqHzS$o{2u0iM?o!0AgU$NblF_6CLlfYAyNVy6D~cQO=&z-}>p z^*`+2SD>ZKpyJj1&nRXJY4tfZ1ex!Ajp{|lzdKD7V< literal 0 HcmV?d00001