From 110a87e978c820ede02c49580690eb5092fcf776 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 13:09:42 +0800 Subject: [PATCH 01/20] Move funcTest sources back --- build.gradle.kts | 30 ++++-------------- gradle/libs.versions.toml | 9 ++---- src/funcTest/.editorconfig | 2 -- .../gradle/plugins/shadow/ApplicationTest.kt | 0 .../gradle/plugins/shadow/BasePluginTest.kt | 0 .../plugins/shadow/ConfigurationCacheTest.kt | 0 .../gradle/plugins/shadow/FilteringTest.kt | 0 .../gradle/plugins/shadow/PublishingTest.kt | 0 .../gradle/plugins/shadow/RelocationTest.kt | 0 .../gradle/plugins/shadow/ShadowPluginTest.kt | 0 .../plugins/shadow/caching/BaseCachingTest.kt | 0 .../shadow/caching/MinimizationCachingTest.kt | 0 .../shadow/caching/RelocationCachingTest.kt | 0 .../shadow/caching/ShadowJarCachingTest.kt | 0 .../shadow/caching/TransformCachingTest.kt | 0 .../transformers/AppendingTransformerTest.kt | 0 .../transformers/BaseTransformerTest.kt | 0 .../GroovyExtensionModuleTransformerTest.kt | 0 .../ServiceFileTransformerTest.kt | 0 .../shadow/transformers/TransformersTest.kt | 0 .../shadow/util/AppendableMavenRepository.kt | 0 .../shadow/util/GradleModuleMetadata.kt | 0 .../gradle/plugins/shadow/util/Issue.kt | 0 .../gradle/plugins/shadow/util/JarBuilder.kt | 0 .../gradle/plugins/shadow/util/JarPath.kt | 0 .../org.junit.jupiter.api.extension.Extension | 0 src/intiTest/resources/junit-3.8.2.jar | Bin 120640 -> 0 bytes .../resources/test-artifact-1.0-SNAPSHOT.jar | Bin 3115 -> 0 bytes .../resources/test-project-1.0-SNAPSHOT.jar | Bin 3906 -> 0 bytes 29 files changed, 9 insertions(+), 32 deletions(-) delete mode 100755 src/funcTest/.editorconfig rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/util/GradleModuleMetadata.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/util/Issue.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt (100%) rename src/{intiTest => funcTest}/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt (100%) rename src/{intiTest/resources/META-INF => funcTest/resources}/services/org.junit.jupiter.api.extension.Extension (100%) delete mode 100644 src/intiTest/resources/junit-3.8.2.jar delete mode 100644 src/intiTest/resources/test-artifact-1.0-SNAPSHOT.jar delete mode 100644 src/intiTest/resources/test-project-1.0-SNAPSHOT.jar diff --git a/build.gradle.kts b/build.gradle.kts index 4b1a05d86..caa0a98da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,11 +2,10 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion plugins { - alias(libs.plugins.kotlin) + alias(libs.plugins.kotlin.jvm) alias(libs.plugins.android.lint) alias(libs.plugins.jetbrains.bcv) alias(libs.plugins.spotless) - groovy // Required for Spock tests. id("shadow.convention.publish") id("shadow.convention.deploy") } @@ -54,15 +53,12 @@ val intiTestRuntimeOnly: Configuration by configurations.getting { val funcTest: SourceSet by sourceSets.creating val funcTestImplementation: Configuration by configurations.getting { extendsFrom(configurations.testImplementation.get()) - // TODO: this will be removed after we migrated all functional tests to Kotlin. - extendsFrom(intiTestImplementation) } val funcTestRuntimeOnly: Configuration by configurations.getting { extendsFrom(configurations.testRuntimeOnly.get()) } gradlePlugin { - testSourceSets.add(intiTest) testSourceSets.add(funcTest) } @@ -80,23 +76,12 @@ dependencies { testImplementation(libs.junit.jupiter) testImplementation(libs.assertk) testImplementation(libs.xmlunit) - testImplementation(libs.apache.commonsLang) testRuntimeOnly(libs.junit.platformLauncher) - funcTestImplementation(libs.spock) { - exclude(group = "org.codehaus.groovy") - exclude(group = "org.hamcrest") - } funcTestImplementation(sourceSets.main.get().output) - funcTestImplementation(intiTest.output) - - intiTestImplementation(libs.okio) - intiTestImplementation(libs.apache.maven.modelBuilder) - intiTestImplementation(libs.apache.maven.repositoryMetadata) - // TODO: this will be removed after we migrated all functional tests to Kotlin. - intiTestImplementation(sourceSets.main.get().output) - intiTestImplementation(libs.moshi) - intiTestImplementation(libs.moshi.kotlin) + funcTestImplementation(libs.apache.maven.modelBuilder) + funcTestImplementation(libs.moshi) + funcTestImplementation(libs.moshi.kotlin) lintChecks(libs.androidx.gradlePluginLints) lintChecks(libs.assertk.lint) @@ -108,10 +93,6 @@ val integrationTest by tasks.registering(Test::class) { testClassesDirs = intiTest.output.classesDirs classpath = intiTest.runtimeClasspath - // TODO: this should be moved into functionalTest after we migrated all functional tests to Kotlin. - // Required to enable `IssueExtension` for all tests. - systemProperty("junit.jupiter.extensions.autodetection.enabled", true) - val docsDir = file("src/docs") // Add src/docs as an input directory to trigger ManualCodeSnippetTests re-run on changes. inputs.dir(docsDir) @@ -123,6 +104,9 @@ val functionalTest by tasks.registering(Test::class) { group = LifecycleBasePlugin.VERIFICATION_GROUP testClassesDirs = funcTest.output.classesDirs classpath = funcTest.runtimeClasspath + + // Required to enable `IssueExtension` for all tests. + systemProperty("junit.jupiter.extensions.autodetection.enabled", true) } tasks.check { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5050e5492..d416a9896 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,21 +1,17 @@ [versions] -maven = "3.9.9" moshi = "1.15.2" [libraries] apache-ant = "org.apache.ant:ant:1.10.15" apache-commonsIo = "commons-io:commons-io:2.18.0" -apache-commonsLang = "org.apache.commons:commons-lang3:3.17.0" apache-log4j = "org.apache.logging.log4j:log4j-core:2.24.3" -apache-maven-modelBuilder = { module = "org.apache.maven:maven-model-builder", version.ref = "maven" } -apache-maven-repositoryMetadata = { module = "org.apache.maven:maven-repository-metadata", version.ref = "maven" } +apache-maven-modelBuilder = "org.apache.maven:maven-model-builder:3.9.9" asm = "org.ow2.asm:asm-commons:9.7.1" jdependency = "org.vafer:jdependency:2.11" jdom2 = "org.jdom:jdom2:2.0.6.1" plexus-utils = "org.codehaus.plexus:plexus-utils:4.0.2" plexus-xml = "org.codehaus.plexus:plexus-xml:4.0.4" xmlunit = "org.xmlunit:xmlunit-legacy:2.10.0" -okio = "com.squareup.okio:okio:3.10.2" moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } @@ -30,14 +26,13 @@ assertk-lint = "com.jzbrooks:assertk-lint:1.4.0" # Dummy to get renovate updates, the version is used in rootProject build.gradle with spotless. ktlint = "com.pinterest.ktlint:ktlint-cli:1.5.0" -spock = "org.spockframework:spock-core:2.3-groovy-3.0" junit-bom = "org.junit:junit-bom:5.11.4" junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" } junit-platformLauncher = { module = "org.junit.platform:junit-platform-launcher" } assertk = "com.willowtreeapps.assertk:assertk:0.28.1" [plugins] -kotlin = "org.jetbrains.kotlin.jvm:2.1.0" +kotlin-jvm = "org.jetbrains.kotlin.jvm:2.1.0" android-lint = "com.android.lint:8.7.3" jetbrains-bcv = "org.jetbrains.kotlinx.binary-compatibility-validator:0.17.0" spotless = "com.diffplug.spotless:7.0.1" diff --git a/src/funcTest/.editorconfig b/src/funcTest/.editorconfig deleted file mode 100755 index 3c75e2d26..000000000 --- a/src/funcTest/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -[*.{groovy,java}] -indent_size = 4 diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/GradleModuleMetadata.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/GradleModuleMetadata.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/GradleModuleMetadata.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/GradleModuleMetadata.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/Issue.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/Issue.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/Issue.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/Issue.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt similarity index 100% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt rename to src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt diff --git a/src/intiTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/funcTest/resources/services/org.junit.jupiter.api.extension.Extension similarity index 100% rename from src/intiTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension rename to src/funcTest/resources/services/org.junit.jupiter.api.extension.Extension diff --git a/src/intiTest/resources/junit-3.8.2.jar b/src/intiTest/resources/junit-3.8.2.jar deleted file mode 100644 index c8f711d050eff209321f799d85ebb3bbe305d481..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120640 zcmagE1ymee)-_BZxVyW%yCk@~YjAf6?%vS2yEYy)xTSG-g1fs05Bz!FnR&i<=3D=K zwYp@j-mBKRwd?G2Yu}?H5A_iN;;(JO$BgTLF8+CdhxiDgD5)XJEUP5N`Zfvyq4G~C z9K`q^=!N`Gu+bl=?Vla%&;B=5QB+A*N>W{eMN#TjaePcco|$C^S)Q3;a(t>$jcuND zYu|NT9K(f4MqyG$6C(Np^~9ZmcUw-38m7FOx_d5z=!xPTa0eOLJsAmz%@rbli{;0e z9CH)H7$dLd7K1HxoAiszyUnZZ?|2{}2L2;-n7`us>S*K6`mdP(@8Kc-3&Y96$HMWy zfPW3{&wqb!NPn*WaWfN7_pdhp1&RJQ(!$H#!qLsf$?Q+Wi+a z^51CJua1ruuKxv!@;B7Y)5g*2-(tq{)&IBA{YBkBM)xnQjoE(>`~L*u{%4r~>*jC} zV}Bl<@s*rl_y-7x21p19(!X#1k4GnJ>h#rJ!p6kj$%=}d#mwHs&COp`U%rnGPk6cX z{KS2uHaoi=HrM!oOn3k(gxS6#15oL`^E%DeB464k_LR)Q6bFhRrjdF{Eqtc+#TxVU_%T``vp|)zCVYgONsKL^sUg7@GMW-2=1gq+s zO}^QQy3nLDOAC&pT452lX{t3nPa^k3Yx*c&PcH6~>2wlb$=fC_o zIaMAO!kc89&_iT04Ldl_tC4YJTS%wv`$*$!`G2JsZyd|m%AnEwA@kmFwrj>ti=sV@KnH^5&GLhxsTQb^MsX&_a(8A}NowIKQ^=hHu`Vc#WZ5ax30VW>?Eq{)(50+%-^5I?cJ&7Etr>m+ zf`77{BUo^gs(MLXOOMR_HZ#q6Rcq{CU{WxZYb@iog0Yw=3jF!-`0@YH^_NyD&VHeYo#w34p0X-K?ZiD#z;$1D0t@BpeRN;*?=wc?mJ^0z(=ZOueKA8yUEAb_cxi z)e%_Amwr4SVqNKtKK#VOCg2}}2`@A|n z&TAE%`+8+$e4$D(^uFEg3K_y6WFfv-5nznqz(ulPm3F2ss7fP=o4)AWVBIFeUUKzW zSdQ&;qUh(b)#l@a6RBboo~6C4h{fxOAI}RT)IXCC0W!wxa&1I4`5MNNyWo3%#bt2MgeS%)vgT4eCL$E2Y<~HQs(OKmDmT9ci$Q?;Up5I)V z`Axm_*F3`5nU=(^?2i4@rL`B7fPbdMUBj z>05l1D!3#GVmoh;qP#yGkNEoa*s_G>=4*6`&Y(3b9Xl}%wxiY0Tps&k+xE9#$?*I( zhSVV!8X{8!;arDI@%+5ZnGqNlgbwoSw{O=T@v0TTG~uy&M6Etf2bSqc6|4gb23bq) zUf#2gm>x1Z6wae)iAQYmSSLDsH@8wlbi7Xee$Vodn!?pZLZUtg*ILU`g%%T+d{148 zh3xjZ1O==q_=A*shOe;e1c_g=SXB`FDix$qKtv>m9>P^1tC3m2O0 zqfDh)j=MSwWHMluh&H_D>HP|Tc1S~>4FESVPQL!0?IsX?m6^5cLVYbR zzA6@V{WSd*nL|TO;JC{n%Dp7K#vL}%lyC;8eNK4C9JQu}420>5CX7ws;9FFjxeRzkebRYM8O#7AC&Pch~2>9xmJSB27F_!2SNX)6_VkoPnAGi9X6$^ zlumc}$+~J!`zlVkPJC7%>nbrzZG(<_s%DNZ_?5mrD&Pysk(z;Ff6ZM2OViabhr!Q% z$A~f2h}+K1eAFXXh{fV|qF%=Kon76FJByy0N3VgQQ}>hgfmV#dK*9>iGg;IVxhS&I zA-|9e(l6ujtOuC=X+$2U?7iwnb2$xN%t&va!;gB|MhK5+mt!2CnW~QZXzUAXtK$rp zOo-(?j7 z1Uk#e;Gx5thsX7qvc^T35Mk>QVPfK8C*xt_CSpaJ%rtl~q!u-kE- z3?at8Fzi91y#3?}E?Oj9XrqJ7W-=4iUg+S6<858;GI%bK<-B*(7enV4;Zca&$s=7} zRS263dJ2HWC|bNuRwfzcEnR1Q}nrgaUtSjeX`6ia=w8No8`=6q4Xcs18dPpi6pzz|Yj zi(@uTupg$1)2O9Y{WD1;f`=60Y@3M5q$g>WTx(~&d>)fzQ?AIU00ekzjuL_tQ z4FeTd7N2AnaZsTN|O5ZalOxFuP+3m-Rcg zNFtgbo?}f)d_Jl>thCuTo|%(CnAfodJCA*Ly&98tMh3*$#x1UJA$yYKj?ObKczzm? z9dqZNGtAscR_D|kyiKa-URaRZzN{v`RcoC03HtfX2a1kaSKp8>bFHNv>8?ZGb_#u? zp??W_Xu&c$&17bm-hz&7wP>XYWGjnSl+b75J3BE(5WZ}HTeCtI-`t04ps7%tf;k!o zc9GOluV_FFaN^pNmqb-OjQ~T2F^vWf&`c|S>2$cGMwx9%&x00NxK{D?U0W3w8b&*U zem^;ua6l{jV%_AnYXU>x}4}(w~8ss<#A#e@nqNG}fxfD&Gw4|72_)%Eyh2)Gn2J7SfcEHMNVs8Ku zSzt*1m|A(d42_KMCG&Tnf%xgBHSqho*vnq6br%q(NCkL0C+HKibVbX9Q-3U{XF$GB zX2EU*vo3}I5o~5lJF}az{&7F;!#TAPEf%C-Q~`C=4cQD|PZzbdp8zmYPK$I9y@7!( z${fA~VFVE*sAMegYToi93CFabq}KH`u37s7iEc--DaPQEX3>|9(N~zwp}VkQ*YS_) zveJrMcv{KEfCMlWr*-?g;BV9<7QfzHI_O6)>0N*SiD>ajR47O>WjQ)Mt|2`xDIG14 zAZo~Hwq!^x@sT--v0@cYLCqbUUH%?Q)%F!X{IVEa+&|h(NLOl7*?mRfpQ0A2Mu}L- zK}q;T`tRZ`FY6zs`$xR7|A_a0&W2T7ovd6f+}y-WT>qirUp4i$F|DxHlNIndfM|e^ zTtN#($e4&;HWiZ9rVFznO+j>b2N_5s)-uiCMjqS zqzq_qRok?FD{$EFJk>IQ9&J#-VaU_mG1n9I+i%dPZ}n)Z{Okm*ojFZW_voBMD9YTH zgg$AL+t8Zk-c31|Z(1O1?l4}ba{>95CN;{%q@4~68EAAB^#%D_IsU{mpI^DMojgk3 zQcn{sENC?Lz0KQSs61q#F8rB`pVwydO@BxDq;`g}*^=KvS(IUJt>Z|~PLftT7tKgc zRP+Ylx8#I0x52dq!4af2IhRi7o7L^+G1)j8o}_q)vdO>IR}{wS8;is-HwQVd^^0lI z%tWIj=a6<#!=58XGqbxSCzHfd*z8oRV$dchTOD%DnLzxbXo}#g!%YkLizyt#(_Ke)e0KezseAInN5?hkSNhi2OGoV0Y3C zoYZndnH@dS(QQ+P)77_N_79JfKOP>zKk#+)`fZ@f!VVevDheI~xq4A}_Ki~)nJcKO z1TNPsPIBK<5iCwt>|J*7F-$*qj8F1<9PsLHE?niLRiy~G^v;%A=Iw*wI`Wg!Lp(U( zDXBOGML~AgV(JhTS~e}CEX~_{!Y2jatOdgoCpbmJjIG-6QOp8o4*x5PjY1wt8S1AP~jb~BBiWSl1q;s|fOvnI+ zM}3ZJM^b4WQfOGxS4DwwridS=ZWw(H56mP2BATJkw{ccTd3rsQ&>tmiW!Sx(ud113x40@9F@#}f(|Si!b|wVR>5U$gaQ{jj%vKJcuZx3 z6a;67A|AI8FTA~uN_ENVXWJMZtx~ zlzT4Jdx>0gwt)hqridC&m~sz-9>9bleX=CvC@Lch6RbckaAJv1CHWP9Aj(~4W%(1J zhLQ&Se6qCKFRMyRHh_l+N;b!}x##du5AsL;a089B%i4(6>y$7M^A;k)O>7Qsz_zHk zBfJ-u`Lt9xBFl5nrTL^6Ljy~QkL>C{yts$|-VHZN;0@oCl6gUTq}r0mqd2I2dSqoJ z$Q>BRd;8U>=r-UL6FegIinWia!6Z3FPAV+cvAdSi{~Fx zsXnn+0Y`RxC8Mu1JR}?%Q8MCtdxC1kD4Ssq(7(bUpxE@7N3ul8bQ5=*M!ebOvNpe^6HX&_+@SqVB+J-wR`Si3@Df8!CEFypgztrQcfcG+R97HJG2{`Oh_ z%)<){Ud;04j?O`aDbiOC=B*xXD&$HohXacqv*#e|BHC`7(Q`=U-rd0!gf!0fPMQdI z{OEE7@UNKT@9|%3Vn8K~@zxpE(HS+v=NIS-;Kz?pEJJyiS}(o_U6uP5THUq-Q^?T! zKI>ZLo}J4cw;lqb9KY!eP#D@qqp&ENX;>9;BmSngUK&0Y2USY>hH4o7V6dB#5h9o* z`(p~OnM56o)J&4|W}K`hn;`8P);j2MGpc)w?O;RoC^dsGWpA&Bm~VH}z#mQYge|z{ z@YorAAP+uu|Lw2;gG2~dWg3c)5D>Mn|F^IEze!~Km(irJP9%x-=F>LL;jXiUhJn#5 zsfr8Pk$~scm%$VrA`fIL>bK*Z8$-djbS{PMKdrq2!{#%SszVbNX}&=OMz_q)s*TBy z&Na$MnU82GKD7D$&Yde$ehBb=sB(fZzXkw|avZ?2LZC=Dn#o-;L}Xk=gWj;TJjVeG zA+sHX8k)q#AX&_*9e#(r+493=FVXBW`fUsp(t`zo?9O*dvRy z4!Fk{VQ`Q-N1d$=TSMg{D)IQ`INY(dD5g}~)X22EYn+V( zFWBB67?IGcL3?y2nO@v~GSsS3LM6h?IDD7JAWpT~)$5ABxael_XURl8Or(-*rRpw8YG`WB3Jlx*e zh4lrn4GR79B1-o7B7L*9tBcv%U{MevNp z3xW1!(Obxymbm(6prY`yc4&%4CKXPF6ijsYihR_;(xk4w_rE@O0$xzcTV=ELEspAn z$P$d!1{&i|1r}1ur(RC9L0VgU>2+t)HQzc|joi&xyXFMCEJY2ERdIplCwsR+6)UA0 z?R~S@c7?#pwS~G9bH1cZZ)fDfonbS+l&)dYfK8{xAiBHoxK4<}mJ%ci3l1X>HQ( zV6xQvTEG#?4wURd4Y3jW>{svv*@c_+mkH!;JqQD6Ql^y}u{!bs#vyyE&#ZuoWoRxi z+2;KfPW`O5=q^vz_wSmy4AwQaFp9{Y3FhSmlG(EdD0-ehYOSRm28fB|3TU*a@A3Dm z6Dx?n2L^L~oJ74Fabv$;Jksfmb$@n-&egjtf|iz1@|;ltt5Pglda&l29bX{CE!xw1 zOgCx=39Z<0ZWhP>4kEdybUmZZvHD~d@;e0b1v}=9SLR+&I3)0gS>TTPJ+6pV;I8~V zs$+d%{lLZG0HG8m5rYAWnTAw+>yWJNEOaHdJ7hMWB9DM#4q*2aK%FbwoF5;T=>5aL z&)bK_vfZQq)QGD8UL*cqbI@mn62%iPHms^jX>Tc)C)LAXG@6khNTNY5nteDX0DkWn zxu_nC2RmJn^@oyfNduguib>{a+yvbwpC$utub=KwjG-i9aSqgEC=SWuy$KO{a2&Ak zU=~2Y=xn6MUsX%#qx-xJX%`hy^Rj0z6l<0;+!uad=~M2adME*!Cd28^@S}Mp%x;61W1~EmS1Wuu0r9KTa%lmm2Yyzl)uZv z`DLFVr8QK@4gDK=+L^8TMYaPW7rEgcvbp&U7);xTU#M#r>ZbMg<11W^CA z(sTZ^MyRbj&iz^YbCeSoU$MsqvcoV=$8t#`Vx*H%{h6FrIE$8Dtk_K)(0?uMw^=qL zXAgkc`QSy@Z&j-YT_n-aLrR+Ob~o@TF!vpzE;bGp_f%8EdC1;V*uLO+o8+RyYpZBL zCI71Aml8wXnYkYmUYgI4U$2?SLjAdrMftsQstAT23O>WL%)z%Iq^ho6FE0_@&?C7572(QDoETg{o9d+wM3rbBLd7`Y@;OeRh02pvpUSc@ z3;TfdR#SLJBQyp5^(T&Xb6!HzibkcXz&VW#%K~|y6rU#rjo7i*CZti;TDxgU)7zib zrA6f!dpeJjYwU6CmI-E3Zu{p}n9U(g4MY1UuP8;_!gwAEf<20kk{9J=T*`nV$-%9# zLF3<3svND9nfWDcPwDq`+b3)n`3HJH8~dHEd92>`dk94zOcvo341z(ziP_L6g24+5 z%_2?l+>@Y&MGZl$&?8Qrd46$vA9t9;P-vDJBXY7U?6L2>tOXKYeQMC0rpfqVjD5yd z{^9`1nW(23X})|FkM6C}oEFEn$&o8w({;UTAg z&*y@m(=p(mH{siV&u6ZGz6q*erPa?^Ik-tn?2dC{gk+!LQ=pJBE(%+U;KItou$nY6 zT{bfubj^Pzb-`Tg%6tZqJVKy2^uS$v0Ax^Qna?j)ug5!QRy$t4v_Ldp@qC6x=iU)VG*f??Z*=nw}|Wxs8~ZWIU` zh?NcTj10Rm@}YwNRWp0z<|2@w`vnO@%Cg;tDM>t$-j_7Qf-nR^%*&Yh9wYHEDe)kT z^?{Myl?y3Jw3P8J?rHI+5wMbr& z7H+9#kOU7TiCp z2BR(PgJF4Xd(_ldX7O~+nBvIB?TSG37Rep>?sA*I=GSRCUU`^fNy3^NO}hDe2g~uhXiQo z*165v?2IZ<`5KH))mg-9g!G4?8c*%op?T(f4&q4@ir;m+7d=Bvm3dNf) zooTxr&mxe>4u6!}Prt0(R|ySHY*Kl8SZnxaHAZCRzE@%S1JN>e2hy1ND8DmT4--n5 z29{fS^BsM|fJBNLMmX5^Uc84guYEFAPwLK*~nNyAP z;ix}>B)tuvVIf^({mzKf9b1!D$oA)2Mj`_mPmS<&b zaiG)yWW9z`{`tI^#P!)0fAfrI#p9X}b`gVGqA{Wqlt!J3IQ|4gjLuEn*>-Fsw%7tumEUh+lWt!%YYPe&M+EF&7LMK6 zdShplg+Du{4|E8yIRo|CtHgEIGN`;fW#}$J(n%_p49uT!%b+rU4Un0bmu3%A@MC2* zJdo=&lf#jP!TvO5MfYaGw^2H(e+xESkhIsR>=`}t&B`66>_xD`usuf0n+paHcR5 z*CAQ$ta7E)mB?7B<0sq|h7Zs>GAaa0#N;>jQdZq$)dL=@!2#4pA4<>p+D@+4$kBRyZo22g*B6o&I`q~COEYbMuL8NX`9_*$1v)YWk0q9b#5O>dEZAS zhU^BbeI{)Vu4gMAuh#>G{^Q#*0@Bm}ub!aV|6UAu|0yD>V{%ZUXw^zZ+0`?T0iSku z3(yUmNU+dDLeQ{)UinntGV2_TJ+-bIKay^NQLl>o^VrK_8`%VfxiuFgr-lw!1%rsI zU+{hyeBd8{(fLsn`BxRtf(tGEmKa!^PQev(W9_sY^2}U`Z13pmapJK{=-|C#%E=?@_J=b1}*+)Tc&g(FE z{7y_g*)(fH;Fc@SzKb(@Qvk*BT~v3MC+!S+iIs9}D1o?KBhuAFW*=K)IY8=Q#wB9k zsfnyNYJN`GPmh*KaY@uKE>Fn-2SJ>zR)3e;gbQKjp>p z#H{dj`++Hp&LGr)AX4hQ{vr__+y?&4@o{{mjZa3K5Y?t&=?}Zqd+gh080sXY!sA{U z)b81KsVp_#&HY_Wh*4|d#m6D2J`~Anfk&87)FSHvuAbMI&R*^AJve9^AMii=b zG{Po~e84zM-`y`BnoO!<77;ADhphZ2MEmk=*fBfZkj(E--{k{2e5)YR4N z-BN9Oj^1YF{+sU!r;Nl4>(!MppaYwIJVP^HU;+;&C;H3A`Yz_bS8$nIJd21w+?D-z z?)d-3-I!e8p9;Q6GdO7L-_xYoQI0V9?GXYUy0Bd5IDkN!*alOEMnq&RoQ~xc;*(q_ zC(c(BWQBvtOt(qlHt%;Yf2g{+XjI%eZ@!Fc_85NDTM`_h6u7QuJy0rguf{n|ObSYW z?xcn@i@JZBWTHYd+|#poNi9f={IB_!2Ylm#V@N6tcsZVxs!&HgQD|~SF*7H|GA1!m zk0@G8oN6>ju)s_V?K2$_e$4MQ8TFB2g>;fs#W+o@LM<0}hU76mn=wQ&04G4-zE8)L#`f0 zJAzUcFUNe?)2Hl3jY{emxVFlXCwZN~ttuW=*$u{@f(yl|Fr%-mhI!howdk0U4?^Q} zePhx+nbg`He$B%toYr5@;f3SVIDa@5L#MJ2|B{f_Hs@VB8B1o1u8&(pxs2F9FiLo- zzebjcakf5APknLNH_c(E%>AB4kED+s7~M5Q`|k`2)393C6G1?H5`cjCbNaV#)c<3> z{e!<;0a!1rg`bCO%#E2y^zx8lDv*#7R8(jQ7%Bn;ig46HqN?fU7&tr~t>GW@qq~Ij zZ|Y3@;tW4Gh?;%SuwVK`Z+f}EoWEZ7&hKeyWz`q*{(hU;cE0AO+;h10>-ZCi7aqhY zu~>Mn7jNLj_EZ;xWA6llqw19iwn3{VG_Obb72_cG**IC014Fsjr$CY|W!yj#jVp3& zSGEgqWSf83r&W9(*g%g;mB@IG=OM30K2*;NXBZK1AFcOeO0@DWC&Qz23v9l{^r6?e zw~;S6l$GHz2|=7aPc%RM=@uc-Xn(5b@s=Ra=x~YzdQ+v4=;3Bd5&P%1K8as8ypMEv zt#cLZrp4VQ`}2clE5xU*jYx(waD3|n`;>ogzB2v=C-kR>EnEhLK@{lsb1W=qiMHSg2=^%g3^TY0Z)X9H%SnhE?{^%mEvJT>X z3!IM+`B6<7PLfYUhbc$MnZ9>IXXm>-ozSkTM1*uXQx%G&xzCjyc2-@eH7>y~3Dh_9 zYqo4({?`%BFzs-y(n|OqbqQr`t6G0NCTCAkpn`e6XcBA>YQiSblNg-x=JANoM40Nc^QV7ap2s8GtvEKGtSqk%3Zyze0D2lr(T@u9S zV(bY&1AV&AB8oP-AGU7(ON-^ope{J!S}3<1u#uualDrp9oq=Sa7GWs2eyfHb6%XEcCY9=WP*TX zTDTtz2AH<+viA-$UU4{$*;Ye(=_lLm2btT>bVRb~KCw;}BxD5!h{HnBKYVoD`sKo& z^5p#p?b%ds@0^)3@RC%Dx~-Yic2Q4&j-nE^@K0H<;IuGD``(;x1t)`guKCHEJax_{ zpZn{^6U)UiXC6i@^GTve<=oJq1&ImwhRAYOJi{Zzes@?yMWry&*biT*I3!$!shCg+ zI3{psKynjqZVo)`xnOvQ!Pf=w5r^=J6P13>UfFu&jP5`mZqiO^WR^QkI3D|B?E^FN z5!0GVF>Bp4?)~jjj=NO)lPs6N+Q)pM+^@U(1ksWmj9v`NlUFa0g*eUSbmwGG;l%>F z8UgjOJ5MugBhD1+2PWmqvksjvVrc4}BAiS8d>R*oN+Bw$2Dv|vXiI(M%VWKhq}wRf zGRN~LX%-oO2h1WdACyUoJ09EF3J9IiLP}xLVhax8QpGebi>ibf&Mx6+~jsu?}G(N8s>E3OY8}Moq#Z`j&{pQ7sisq569mI;>C&b#VA|OeC$% z4m}F#H&=S!eCU}IWf$tanz)m1_L4A`c>t&5-6RF_si6|hpC~RP_mBp}lkDOl;B^@a^cd(R zh2XjQO-(Rl((2LXi?I<87V7Fx&MUUT?aTZ^65@qF5CXXd2u@@9o0DhnN;XBY=P)a0o+Kf0 zCGoY6zyBV4+JF1$psVmk%ycXIYC$Twdr->MW_UP}5*;Dt!P9D#Zb6C6ao0l{ul;>G z$@N>zy((O22kSVBq*Y=z_i`+&=qMeHMSp;-tdlliBDHgwvx4222JPfkg=tL&KyC%I zEX%bIl>xa&I6x;x++-dKr;VFfTqsSKE`!s&&AD!+G-ytPHv78UR zCJWnmk>%!kL=K?Eb4jFO!Lj4MR<2jVOpj{XlUV1^#j$g=+#E9|u{O{w0mu(nWVPl@ zodDVh5ZtYI5L`eJ>D&7Rl^GR)UwCuDSqDG{QHr|=XP+Jj0r88x1Ucmt!%Gh6xZl4Ol>cJ_6z&fxLFF4HhvOHB_IJv zUq|@Tx1YTa(%>p}C2$*rJ7g_U@u(Llr7;w;tygh(FGzfnj|ZB^v?`O-k2%?SZj_Me=}`PEFW_(UK)gZQ9T(s- zGP&yBGzldEA@Den!atS7$Pml2KXiZv+1HFzQ3%}OdszlY1yxYMpR}D%RRvPQ{KDuh zC8@oK$b(IN(#+72_@~Jr8-*;@CCf`r=$na%{#;Y`^1usZn*W)qtL)P#NHG05Qu;)qalc^0S75vG0l2mTgA(GT_PrkpSLjkbLGM zuf{D61V~>>H!zsgvRMMA#4$#|$=c$ledM;^H^u;|L})jrc8#l^VJmhryn(hqGwqNK zxTg?OTY1}#RBS5}E&596`3FNtuB&**^yp&N0v56Br7@kNy5N2@`TWii!v|o|chdhH zcJHR&kb~pncAAxW89pmbO^6`8x(l0IHf^KXI4JNZ$g2T(_IH@`9gBrU9Bx0Q-91bw z%7EthTj&rXiX^4P)JvisxURb&KVy8|BV{jh0Ea&5k(ED25@35$W~QyVPbOd0@|{2+QSEuz8K`D8HU3hY$Y?nDpJX@r(29Chaw1uZ4VsF z@e+=9Hw>Dy$)l~Ps`gF=;eT zL>UA#^z~tbVTMyoYRjDokT!u?T7BOxDoc(J6*DL@ad4!+9+^0b@1>ophU!}~iZZWF zl&w)YvuS@rrs7-qwQ+W8r9}X&v5{N5-DX{aZed+s9onHB2W|*ei+pFI24OAK2vv*7 zbxESUibnAh-C^^=%^550NMxKsz|k~igS*0td=_4!pZ3g|8Rd<7B39})1@}$%b*~Q* z*KfxX#S|Ni1b|JR7nVSOt2Ku^C6{9C1*Z46UDx(0d{hDWUHXO%?1`9~Y;Tt}^HQK- zjg?EgY0qXb+OQSYF1)!+7HuBa5yC7;_@FpC%MV{Nqm{M$JA?eYu?XAm)5NC&hT<LOe%D0lB4r$!DYnMd`&k14Z`IJs3!Vj}(%7#R#z%n!Ks?s7)UrnsA;k zHHP0|4^7acr;1LcUukL~KLE1V98dWcA#5%m9&7K$rrm7Rcy80|ebDXKcy6&%?3%O^ zV1T2(ii=oJnmC>R3<(<)iHJvl3i-yH;`66)KBE-QSd*Dnd`)#18?7yZc6- zX6u6Yiwg=4b}p+z*PLclQ7VCMs;KKV>Uxv z=1`1fJX9KaRnKpdM>%oboV3`kwwcBqu^?_?OS_bYsffPQoI5 zksx7A0WXn5-^Nh?WEZ*)OTD(vk}~C)lI@sWRJPyqDR$&WuDg!-xe@_39;g4Viq8F! z)YUkPuX{gbHoTKEAKW9*gw3GY!ze!3o-d6oE#VgtEXcVtHj5+XuU{hHXo)4_Oz-?S zW2buWxXCz#8gaN~AW`D{6j{|f4<1@%F!e@J19~o-b#V@S09*I< zTqJ5uq-e2?xUb z848|ifrqHKc=s)XN=odO`<=2o76_9;z}VbIH<%z&RrcrT{x#gbLVJ53rP!>9E220rcKzi#;N9ijyqH%wu~flXM*OJY$yB{P8`1s z8hFEf9GA1B=SsZvpSF{ZhJ$Ewf;pGDoB33;q0IrvbBK)L71gK-ND~sF7K2wRP}#Ca}v@qO+@!?gZlf? zW1<>u+X7130owzp6EEN z*`8Fk*{Z>MJCvw{TlUqIA~~;NHnT#pgX5Wcav0UIeP@#EyAx6ZFFe_iRcT=wIgz*U zjhLq{!GPK%V;##pmU7CjN!gpNv|G<5$hx8ay**qQvz}Ww0Zk}p;CV=!TEwlbEZwbD z{1S8?M?mG&e3fe|R_l(l zAAX?n5#*OpPa9e;tv3339`2M2NRT^aYo`w_m*;21Of`9;H2m27Z00&46vozcb)rZJ z&-Q}UCM=-%Fka2xHoyfAb5bUhcLly4MMu)EYM5!Y^*qEQLYFu)S!OVhunKK&1bIXK zW@JM7JcxUB>-(<+*#tZ5q2upfFf##`umhH|*Sa-Id>!Hu-2oeJe0!?WY95XV5JLlC z_#m(i1mE)^EyRst5MPe=exk}@qdAB55Hxb0(xY!AQvYuImKe<&sY~ndb7%RgPZHFvmmmrFN)PkKSTQ5@#v`PHvsqdK9fi~=T`NWwG&rCVqN~s zL5bj@!d7eR8eDTep^B|tB7Vp8xh#4WL;j0-%0)p}EW)sq&vE@pVQt{lUD-1R#^JjS z^hiN^^EFLNZ$gv^%JM)|@3}5_eW#*TA5O`>7rHMi2o}>Hu&3c8dp)8F1nJmUWWC3B z-Sa<*;*KWQ5#$whZrij%M^?e09V3>$e&bzkLdB?`E~dKV2IZ^hb!1T_`n^3Q8M0w5JLJ; zpnBRP*TD{B3URl1<~D5#c0j2A6)zJS%Ne7Oap6(y?*TL-GO1ng4jU7CW4!_s*cH7W zp&mv$x`|J2EhyC#F%DhOx!UW)5a7Y3^AUxXFNbet29cIO`kHM>Cg%)KJe@$gC>dy- z9Az^vs3+yW@TUVX1r>4Fg9sRNjTc>sjS~L&m|OJ9bR7%yXVIa!-@@2gL-rkD_aY)e zMSzJ)#ym>;WlSg%BBD1@kO{#8;@2FHP_9q($;5idQsL!{QJY1RUhJw!(^pqx$h~!R z{i7Sl;}jr4J_lz$J{T?cUSVp`*`$)*c&M+D*v;)qWs_8N#4G0b9a%mPN=DJdHX#=uL{?LD2EYOBx)>XeMOwKjjaLY|2 zG$8ok$!crK&1#O|gq)Z3El6C+P9wg<{Ju8F3n}|dpNEjz3PZDH!v;;?^MlivCyyTR z39%KSve9u&z`&Bm=<5+LX05;*zs|pVe5&t8Sdsx{%(*wBzrGm>FZ;VRw8 z0(Q+mnrX>aecyS!3bsX-n$g#|cF&{xLYs1Mg})P%#Pv~T+qz(uk)%MTs^K-J(zd1% z*%2f4%gYF-tKqx0rQV=^L|&DR4G|;Tk}Z+Z67!}&4#p75Oc|vft0>V3ZAPdnSwj$P zx8U^B%yQ)j4J_s0)y~A2Z8xUHKP+}%7x~HD(|anjLTK;BB@|jjxyz3@#dAH6;i6z$ zb4%{k2^O{Xn5d;+hf#LdUXT6$n?~*D1kd{j=&nho$WwNHe3L&-HB50CH&ZT0gRTbl z&{C-harbJ^!>h8nq#83%DQ`Yq5u-w#UvH!A}xyzk1>XE z#foM&B(R+G_k&)DCFZ7jJ5*j90*sF4|Hs%{21T|tS;Gx9H16*14vo9JL*wr5?iB9s z?(WvOyEg9b?#{=Zd1mIlH=ddJBI;CBMEyEvud{Py=E}9Ntn(qgS=7J{td5;sjalqI zv1zz`c-!%P%fTg}bk@M==B)vFl!rhM=@b+s;h>YR+eUT3>Q(kAa@?CPUN?p^s2=V* zwGUQ+5$M_M(MW6hYEoS>%)nB~&$=WQq|?5UM`Lwkj%a&3{99#HD|lhc>mM0WoZFb5$kshWx||nZLwXPtPj19# z*9nO_d?(Hdm5L!gWJrf8bwgnS9tDswql2*ak}89?OF}14KT7pLzh4vXJ^Ihv0&7b> z=7ejSRT@#U&OmA$0eedx9d7;l4*MJ`(|INrg&e$Sc}##Ye@dLr4a06u);Yo4nG5w^Sl(8D=fI}w~uGTdXQD2p)5n|J6V$OXpzV6fd& zNf2e+261LIR?3EKh}a-zuxy)-hz&xfO}tKrr1k@D>cp#@A6~cVTL^8Q?64zGp!pj# zDx#hwpl`>B^t?rgGW)18@2XIREn8pYx3*G@CP~0c8k=VmkCzmhb^4iy+7fNwn6%|k zsvSvnL-w_TVCNJ2yB9*Rk4t=qTn`Z=KN990vRV_me)2m?f=KR}miP>ym-3U)D(mO` zoCDZc+~iZ8Mg&T$qwiwT95j?2*ua&2s)D8!MdLcL*Afqboj(F%SD8^g%_^8qF?tnr z1Y!2%2`IOa%`hV9^sl?X+`Q?*IeIF3*c#w`(oGaLdTj%S#(u*xA9MBw(9Uhf5ckpu z%s6Z@2xEl3Q2uFI=q84>P5qWGvjLg*1^p-Z+cX7<7SzFuSu99vhVdfn0fk2=AL;WT z5IN3_5*h+?$q`zp(9BdQ*;yaEr>q~y z4WEilP|RRYeq0wrd(Vs@DET3^62qwZ$8QYuLJ@)b46q_0*Ypxf%StL{Gm2&#Io6|8 zEpqUVP}2-skrC6LeGgZkHU@bqR-IrTl1+XNiqdE;SRs;y zNLLfQhqjaq+#ehc>(WnK8uC*Uy7<4OFDt{9>uK$vok^gtA^Z&8a;WuzJ zb2j>`>#68u=4kX^LnDET(l&qi;Jr>;>~`3g0n^iRreZMCIEZ~1005=3Kp0z0iDrvs zeBtuWN*pin8{uRkCOr1mWVFCItxBLAfackaXJfJ*W0lv(>lIcP;1>ybp`^E6<*x<0 zj(L%5VgOea_=<{4uav}Ps~B4Vt+88AolyG8a&Wlgh1_*pjO^1Zm+1-pCs1rZqDSRT zl~|3B%y}WpwXoi?DudQ=he!WtpX-_cMUOC_{yKX~p%podXT*>a`L&dIk|}8MUH`?s z^fTVUc3c-%#G$8#Jt@~UH8VTJbyOBOjCp7#;>QqI(&$t`QQXuwutQ<=uqXO0aYFJP z)Ds81KYWF3P!O+W_1vL#uB1@~lxr`++#I`-7dd`W0TxS+a)IaF{Oi4)0akZ3*lbtR zxzeaE#-=6i-raqQKEFdivlYjj()E_k(JCu6=3;V19B^maBvJLFXuXV4UcO11mNA&R z{VXXjJ$N3=XRqc8xB#n)-y}YTc_!u7*g&JVsqx?D*38|5`9)91{0gR*y42014%oos zvEE6lS{yB>k~E`lD11pK{~al_L6H+NdfzTt<^yp{#R46p=PB}xbh5jK{nj$w&ancp zI+^X9tqp|f1=rt2s575C^lB-vyft$Y_DuA`AHXFM@zHoe8 zvxa`zalmb@J^`Pk>d~l{5f_bQwlN)Md0ceVuiNnQ_<-7i!C4m2S52^6OWSafU* zCJuQ|gXDd>{8dknYFo2U(Ov;RlZvj5$~yPpg$|18Co43WQ!lM@;(>UM%@5z%X3>e# z?pvL-8o*^IOPO;9WW?F{Toy4dv!`EQtJsGE-{ideRauS`Z<1bq5p)$3eP{PoR}3)f z^IcH4kjLc6IwMH}1fC_{L})3mMOHP~tkxd?X4z>eS?7&;eTIo@V)*xZ{$;DJxN1Xq z<_+>z*HT~L=N?K{==|E4ZPTwD)Fyl8 z&GB$mWX;w_^NI>$=_|g~T-JBoW5r)M>Zcd8Z~z5Cf?koFTK=ww7X*zlgoz-9R9c() zTnp2$jxifCf*#Z(t<;~>0fNpl;>NjRH=xn_5NmNnrx|R^wlgZCNz(reQt`G_3PY?u z5`^J69b?3*C(THOSi~)jl|O}rHNz7|OnQ4QYl-RPe6XlzHS<_+lGYHw&(h~^XPJIr z$Rr*2Iuv<;gdRGqLoXp(+?jLQI7~xvDg-oU6F5e zF9c!TPkv`-?&kt_M})-49&cTo(_MRT-@x`hxiOPdZJwj3@j(n3vMXhfG%7~!pNj)`G%vg(hf8K7nCBK|4Zm7_ClVgi|# zR(knfQsjW96l8SLm(}bsSwF>nxVGuj`T4yjEY2T&RxxFeThGXlnrb>mIfsahsG4t3 z;(NIO8lZ))A0!ZR!LVY?b(mgxliTVxZ3qryXWw8qK;9HnC11`Qvr5A-WsRBU&#L~g zsbKMImVtVtK0dEUH-6%o;3Pa%I;zjSnR2TlLz#3K@u>5JDK2^x#T&oeO_WqJffmoj zv=8Ll+-{P^N|MGBcja}HsBvJM_J}QeZ7g;EJ`rfiwDgl3P{5hv8)HxnDH{W&1T2b{ zu41PBb_*kJY?qBo2IeLcXvg!naAm(dLO&Q3rjYWMP~o6uh9ySaa##Oi#;j6u{rG7S zvrESlrwedqKRce#xdCYf$}9uP5Hw0{eT@NF7WAZ#P3oXPZ-w>Tq|Bws*WUdZ_|?%^ zgX}xeSZpS}nfBDNqtg=x2E!2MmP6T$nufEK#?VLr_<&=x)}p2Aj)_Oqx_-(Zf2g}C zl}Xd)R^itaJW+D^lgApixDAt+>bnq=Mw4}WJ9AobZ65?-Tuu@_`*FF zK*Do?f@>g5L{E(sGqtq~mbUs)xGUL|r#&b-9@9MLSfR~#f)D8XapzWm1`E5#WGd6< zb0-s1*XPF*gf5uu?vW^uzX0+88*oZkF5DkRH%^%`lH&;xTe2whi&0$}_gne3_|I1G z3(+fIelw+eRF?(UbPIpKN=5*hqavCl^s=YX5J~e~O(Uw0$+C@v@GgWirz%m=jK zp$n;f(-gCOy{6*uS=fPXFYE!75RYja={F(k%uay@vxT41U$;Z?;5B2 zk(fy|O3oFHN<&lQ`nd@OVb68@b4;I7$;y%VIAg;`e;_Wy6t0a{bM*F7j4|gwR(6W* zOJA=@jvG8$tHu$d9CxVsm=oalz&weQ3NW`qjF3bR`5>WdMNR}L=&qcZX>lsI0~U<+ zm~=Q~)ozeK2^T?=+p3AGE_%nE5c=R`#n_vJcD<#vDvRKOzXoA3R|+pDP3n|3mrdTG zrAC#!4w}j;=lo(;p_X3d`hjt#-V4_fQ)$%;Bk7Ue-Jf*5I|)&>RrA zkVJ|e5T1Rna1j+G?!0`@-(MZZc_fGNl|qw<+nJ3Rk)x5i1VMpz*tTmEuX9mR;#)+# zQrm^SKaF&Hu7LE@ab$-Ry~s!02$jv?5rJS8vmTLtL3tkg#Q9O^cUcX z{~h4}0=kfqfsMVMqs_l;GQ8rx=Ct_W1E&s#oUP)Mm}wwrq&0GgVVMzks!9+c%K*rJ zb1EG!YgV>mR%TWpa08%Xa!RzW-*6!r+O%5 z^%r{(aV2htNgrz?xw#l-7-oKMGbqu?xWzG6z>%pT7rjGm>)goJK20ZIgMjTCiCqf@ zshHSFJVxX)hD@>d-Oxuot)AKU50vtzDHQNTfcUr{gv-5fsETg}L-aKrTu)I%$I(Y2 zf_hE_s6Lrd!x|PLnnb=4Lr2wz?+kP6a-)RQOX`eobBDqc8l z#-QAwQ$@-uAcz(cNlw^9BvQVF0rID#UD~~7d2K-&6}rx0!`Y;1 zEhG)xQ9^rX^=WyXcc-gOQHwfqv+}~jKT@_Ia+^(CUn!fz7YQf)-w>r}$cd5^IRWv;J~D7WY3L0fmw$L>m^l@roy@ zWqkYo#wwmy*P)7;PhQU=DtL-9a<8o=WN!G}FRamxV^pe)Fi|vTjHAsvQ7_i9AXU#c z;2d-#^-_|5yvu>=<=S$fKPEbyYIXMH>Q4$|f!ooI*nB+5_&n#ui;mhb(eY2FnBZFHh?pk+=~Vhqz(lbcw>AL+7^7ak97v( zsz*bmo?00=;!fmIh!4~c^X3L+%V*)Gwun0O|&v_PgY(TB<3d!0g4qr zX2-9a!IrwQeU$1ov*4m*A1ypr_iawEjvF~Q`E{1<;jeI2FM&Bp;x2V<* zMPvJyS7u?3y13|{=Jyo_>v4hky5i2wXU&ryyXIl%smQPwYHyIqj39)Zh}!?8VrI=E z*t}mJruu0Augd*@tdaR09E|M$#lbR@%~UmiVGMn;QoFQ36BGDN!()OoLxaE^m#IlW zN|EB=7ue||)-beogMtOMIv&7^mo=$NH->9in1?h*N+Knzw3()WS)C-AT4||2(!GfD zwtCLm%s+BJ+L|2L9}+}!!I`vOK4)*fW;tp59L|=1YubXtCqX)C^3edovuYrs(ZX=d7;+;U@tqBAoMsJ7o%Lpl6uIS5+i z85v*ga04ULIWppq!8J9~?D^CbhQ@4ZG}W{tjHkWxW`x(d+vn2iIuq=M$7uN;2AAxq zDtFC|viVEyH&arGS|(%|aODpaVo|Hpgel_S!`SM zp35`lVAls2c!DLYdU!#tKG#CIyfFBBc>`*PqC1xIbCzEnbI!3z)eRh77|lpqsqWoT zlHXh4Uf)}=UX|O6a%R@shBC5p4akjLWXWJI++#T9Zb07T2BP1d7`*dzm6Ny)i;nyC z$EYmbmZHm_C^8+?!fn^)Xjq;w9=$UwFXZJ?e}1*ScHiC)-;PVdSl?Wcy0%7bv$P(? zC86&mKZKYXNMB1$1Y~Eob-`_X1~!Lo(esdu73km6x}+&pGv86gRR>$vZ)rZhUU6sz zZ`F*9y^un7_DT7S0dLS8kVS5{_2XKG1y`)DT#}8gA)9MXOrvW)3$YXxL5YfjL>a8b z=F2^vuHxkjOSY=wCF@rig`>M&kSF}g5Mc{zlJxC{0cXwf>#XLRX8x4UmQ(@ZVt7-nn{n${{$;~S^(4qod>b#lER1wpDuRWh~TYp>gofW zQ4gfBHP{uWt6+29deM~7X9@%em5BYE^&t1Z}65w%m}_AL{Z5} zSBSKq)+j;uzLOiV!6E>xCnR4-B?Grnn5@?Qu9pkIs2C1Y$_ttONnD4@qXdF#e-N{Le@5o}(~s$8p4o>=0arQ|5UQA>-sIf^RBD0WTTaTbyg28Y8A98RF`H!xz3 ztYeCcjA23&yu}iBdu}KbT*}O9#+Bvw(o}xcs6%qpM0M5Nr~}{-P1M!!0PCzEIt>M0 z5?6HkG2%E2IM3@|6Yse|)P!kDK`L->{~^z>6b+)JP5|apjqQo1#*|d{eU*{iVaNgw z#`%}bnKA*GJO{p^+F8!7cqn?iT__o*N1g8t|BW&3hoz8336$xJCvq2Kn!kBmdG53! zx0xfCSwRm`Z@dU57a=$M-m!S#YP{gyh+Z-o!cct3-Chl{M@)tCXaG$mhEf*EQ_{jU zw8}zKh8yOoKa}dvCC2FajAn%_R{kbQ(UC%fg3@@&zJ?2t{w4l2FBvo~NW_60TM9Ac zfZlEVpb{lQ*9|hamyn?14yk4A5}L`J(R1yHp!D%j^;Gz#W?HD#)zrZ7z$!*AQj)JUVf=XL4 zDzSFi7?l{;Y-BvOy2OR}GqC0}48lx@*a!9kFMFy;j@m{z&?u{S-F2?2Lr;)>-2N#! z%PEwI%I*Gzn0|w-eysJnDa30xwAyWWtT@-TfEK?{EOxRT5xAYhkf&$=ek9my0CAKX zqK*Anr%*p#TL>BU4um+@G5=bB|3n6oUHy-1Dk0BO{ObfkE|SpQtbW!OBRZsO3)&ru zU{?u5Sr2#{;~mt=(3XLKCrAVx8nF!95Tv-JJ7U-tqfPuBR;_-BU^apf6WhzUU{LOC z^>uZdSYDe$ZtA$5Jj1}bxIr zvM$8=&7@ojPT4fU@M1-c*XIE;DGil6iHrarvKAlsSzd?>9Z=cQ5(kN8*QRu`-1A0_n!faTa1x(U7R>sw~P67#gJRT*SI96Vc!8*L+*)^kkqFXsHU4 zGKzd9L0RLGd>IrEM&4vR0cBCOWXw&aN4DS!U2+~N>h?qX_1V$lY=O2*WcD zLj?cg&q3WGeQ4+3`d0!UgK{i#-%g%W`GA&Ec0H3p7WRFe*e6{i4pBVCj@Cc_k-#Zp ze&P%MMbN*>0+jzv(EmxizUsCjdS;eJhQjvt|5X@>jG6ix7KI<|GW#Q<|EXrx*a1P< zDBy6VN+H&m^PO&fqxoFDxd_4%(Ti3V6pqIij%1P)l%Kn$hN-QOwadntmuGVW=x*9s zH`lDUGZF-gMt~+x9m0pusi1f3<0X!d6>vijou~T!TIea18ec<*Jgb~<07RhEfssJ9 z&z2TKSr+t$MNPV}6G<_f%84$6L4`i!GCdE4w=Bf=j0P#F1x~$UPqHL?Ej(l>#LYKW zt}{c&4CjO=cjh{2N3Eq39QA^2fl~D@vb-Zso*1=sf)1z$BRDi`B$hF&vTKR^kFtM& z6_PW!=x1uk=JR=yifstjC+R2j3VF`(@_`1dM*GMWds2)2U@9BhZ;NRQN(u)@wsh0l zn%-gsQUR1k1mgR+>!rq%3Js72qgP* zFBSfBFQxc5OCe}uWvgdz=3ry}AHNL#yF?SIqM@kx3;9C^F%CYOKuCzhwNC>aXvMq{ zrC|^p6AK=uP}yQXCIg^L>l#PS+ub!C+}V)t92ESJI|;txTolc7SNmsE@ukvB2E2!h z4cel5Rr^#U*=y_at2PPMjlKZP7IAD1z4JQ;OF#iAE*;1-w8551z&LR2MvvC_ThV(9 z%^}Y9S}603XUsnF%8W z44Cb^uA|ziLGRfh_iJ_N~NoUNH*I;In1P13A-P| z%$u2e<=a?(G)eVkY27L=&_oMmdxMxi3O@tY#NuepVHC3nk%ZPI2@ z&ztxiy&C~LFvuW5lTGaT36@>T%`~{;HaWe}l{T3}mh%up6i>N0+a(Br2q#x5Z= zgcjsS2zJ&+6T8Wgx461BsX^_&ci0Xtu1k3Qs46^oOoYmkjow!*C!UWNgFMfG3k*Fj zLu1jM{GA$@J8)0P3vX;)57=5zkniYvEBu}1eVpIOnt{l!OU?JxP)$Ix43V*FBysaV zo?KXZChD)qfL%6qWmPx(wRH11>}_lBYRGl9Od+VNGuz9O}sf)89M+)&?qye#lmJ3H|X@2Q$!P*T4j5N(4QBYz>28+u)&X5&!V=<3$@GTdricuiV&h z-U745Nk(4d$x0p(w|S0r@=d4KV)4mizgjHotIFPn!_yS~^%Mi0>boW_c|ikqw(zhL zU=z7sE}*4A@5osJ(npf*NVe}_UGLbilvZ$-+pq1Y#5j+N@6u+_Kl!#{C2GH~eb&DL zMxTR7#`aLoDWc)Xhr8WnKQVI?odNR5MM5EL(|IW^!we)y$(UEGcLb=PNZ zAb~Y%)cVIfV#LW>4Z&tHaM4ZzS0hq1VkDHUB0wJporf5+u_0)OXWs=PyU>)yD>m*$ z2S{8^K47q5788i4YemELi!OTDsI2#3@Ov#TA3F2i0V*8vJ+mJ}n(#@vNuFEWE6Mx= zB>AdT(-tkl=#$rJ9LfGdS#CA`bwnKvT)8-MDyNytZD0x46OjqkAF&SA z-z5}*)nJ@|3ycU@B##?}^zvociQT9?N;I)ezo{SoEvQ0WcM*qDOuqi6S}T!CY`)K# z(43lKClhuU0vAgkB<3CX;zpPm;d>gJ!9y6iZ|WNlAU(gKCio*2AK5h=3gPBO%g!Rb zkuJFrBA4Fvk6NhC*3Ev(7t*@EE`oo9xG#rtMjCL_>Z_Yn%{H#WHy%29DIRk4WR~NYko;ZXYh*+$*jK22oP9Fu-B}_eloS2Bb zH@g@C77+*$2oeZ5ecd;$-frN%dSEyFG?d%h!eZZ*J>< znx|IMoRGbL8NC_k6NUtKSj`&3=~96Eoh1)v5fli|hHJh2jw3sSn5R_W`wtStffLg3*CKF}DpoFe);0nAS+Z~wTNbcnWk zh_4gmX1)%7`yaRB|DnTwS1%;0SU4alqkFSR*O@I5B9kMR6evriH7L>MAq(aBBLDF% zkfWuv{v)04EIxV>&4TVqg0z|-tE`%w_4=o6{RT2>qQK5<%YeyYoq8Bol zpa(ibPD&L6wbjCB%uBfoh=^z0NZgVfn9olGtSrU2Yg#SNkMnu-GR~&@!63e2{YhCjSC&{wSZ9j`-KMSr{w zfgI7FV9aAQn|pDWvrblg^UcKZX=c9z%gjW!$vN#5Vad{W8Y$a#(F|vkAe-oB3rYEpX=(BiQ4#CU?bPEe6gVNet;sB$E{M+$g5X ztYsHol3-=UAU%QKclc}8jZm>_iAAYOeziba;INtc!EO5$Qd1= zPn@l5LGWGy28mp*=Mb1~PHCiWT!zub!I)Nji71`GH#N#Lnhq=Tg0tTize9gSK2tb@!-O*5G~NKMky_QPen7W%TZ$LpX_S} zCE~p_o`fYAlC?<>NwL$b!>p9h5HxRGc{|T33%27>zULB?EcROZcoA!X$g$pmhb$(z z4fUB5E+|V-bciJ)aN1i^6k-M_D)ne$7T5(ib2kYwc_=Zk@W=SEu*+gjpk_bBBNk?P zRK98VbNr~E&q5mTs0Zvf|B7mqhLdx;qHS*;z5Aj0;uptyD*3{R zW0N3;+o?*N7R>8jKAw&6WEJP7O?YzPA0EP~|N$LP!4kv#`ssq)XqutH;?>GV-$ImZ`BT zIM5DJ-yfWp@rak9oAtTrT|d}{MsU6xsY!lJa0LI$cSRP-3ro8mg8ATkT^cy&j56x5 z773-W)y=KP_^?3Ynw@g!o-Yc4wh5L8NB=tp0r9mi=RoZc#j-IOlGK3%A;eTTfWFp^ z8tu=4fXpbA`>d0ULurmn^7LwEc|V!GM%CMJ;TaXhCB?c`;cuF@MM$StG^dZOBI|IM z6|APojn97sk$T`oH@dHe_`+8p`FDZ-uORX_Qt73vCW|SL{6UKk!UpNTV{K=#{JpTL z$sDb51twW2uhs?xmMsh#BWR_-rxeagfuU49zVku!C%20+2c?Wx>!bbTbLXWchWA(3 z6NwLu6X=fcQ1m<8GXve0{5Raar|YF)P;Xv{Eeh0U3-NCIZ<)gouBd!ySwFyU{o8cx zZ^L|*fh>Tv861;Xi3=(5Q1^l+;V1A|EyA*BEM`kRi_FQ?tabFk6QM-Y+*LF_mEul+ z=PjVW+zic%QfMTaCsu4Hgej*Abc{jDbD0eHXD(ygg>r*UrMZ(ceZQ5E-Z0NXHLC3P zg5GXdk1O+tE4tcDn#sqbfC-WpMbK7K+&%t2x_AP0a;^;BkMvpIn%#yid3StLRYlPV zDR>!)|7SE^&N6ANPUNk*y*wybpTkj_Ql3D+ujbMsY!X2pDYrzW-Rv;KL`5hYr&bW? zHx7K<=X@gtE2ctV%ezFMIcNZ9))U|Rl5)Db_=cP-oZzB+;&uTcSS@WHoIJuidX8nr zAGBJ(Md+#lbe|4{E0umusqmCu=AQ$lz(FB`E1FjwA$C$>!v>1|s7)9}E24F?ro))k z<7*2}(u$$BPf;_9cE3t((4zO_tQ&kE#;N8{?ayNfaHg7vsMm3exofbt7fLR{quHq8 z?z;$2A6yCTJQ5a=-Mo7^E~lz!6J;O|!l3(cL?;8@`yEQ#pdEQh9=3(}8eunWXsOQHrmT4Q9cuft-KF{5d!3xL8t`=UHXB}tbfR|`J2(v z*hbzs&SbU$4!3}Do)hl3QX95lplF`h?4Z2e8{u;tc!eJm6`JKOuz85-0}67{f2z?4 zr^GbO|l;p8&6?e@$>5qY4!KssHI9NG)OHYv_5$J1y7< zr=KKr06kM6ZW0cwC?Jk_n#)iaLmI>&a4WQCm|2T7qK0#4r$yP&5qi6rc2u99!ocAJ z^$E32XY~Xs`c{GP^AYXuiQG#ps>`1*-l_6c#rz)$%HJuQl$pcVnY>2-k~BHVwpsV` z!DoO5^th2z5M2XJFX8d|kqa162+O^I(CHdn6m&Q|u|o3TncjGtXB zJ|08k4;VzeqQe3FD8qgs5CI%Benrx1H6b`{<;Qw88J-ro74j&nP<2csaWaTDmbq~} z;}dVR$r-JKBN?-<`z*CKQdco^s6N-VM~17R^$57O#-b<*o+h+})4H)nR=90DfKU02 zNuH|K7i=5%9(ASYz-%ACEU%XBG@c@X;{d`41<+#gz`5@?8gWO3bsy;<%krBMf`nb zpiEkT5@aUKgVnWMQY>b-+qFX6Kuq)M8yE5p+t=AOn2mtFoV10@2r1>oM0ei0oSi^ zbMkfkGyeaQg#G7=|EJC-QTg&0vJtYkv091|27fFbp+s&;i*HJ3A$d_q5kX2^kUBec zeO=4h^mvtOYSA;%SlwHycL`l)Uoy`gdn@*B#@$}?N9+q#+2JH~-)~s#RfmVpOHPN) zL!L{oY>&e!AMaP3Zp^hveUu=j{(Nw~5@ad{>OuH;cn8h#kT65!LUfdBBzYe35z7i_ zvlRV7WlzeHSOQ!2 zuKyVi$ym5PZD~uz4iso%Xr?=3Vk5K$ztLrhw}Q=-aa`#`fC!g?dM4zYP_$7hs*Wg9 zhd8G}0gPG#>4V81kf6+M&E?7*HamQSCL2W*m1A7e!s6&=xCqq)K(`AdUZA}BZPZHqWE4hHmU!B+Oz6}Eq`5zTYdT#y zNNp-X-OX&N?rB9kWR`WkRbSju-Mnq|KD^emeih^>y330-GBenZEN9ysB8trpJ!>1# z{UiqCEM{Zigds)am0=`7<=Yd!WAx)~AhL9)`aOXXAsBYy=2U4D>;X9`k|46y-zL7< z9pp6I#w)8B;!hm3b#-Q-k^u(Jr%hA=1NB`8G>W5=*$zu4sTjDEZRg;9u020xI+6!# z{9)+a@1K2pOCQwqFkY@;1*alH8qN@y(lMRGBh>U$6IU?SM_V9hquPXen|A4K1e)Ot zckTL`kd~2M@aXGR)|OHeAj?xq3w|QX^r^S!>3VO&n41oo!!R(==&R2Z)lM{0mB4Xi z6?^)5X!W6Zmg=Ex%w3_pC~3}L{fshw0$WsFbD)svv#9f-+L*qg>HK4hv&eilhzr9y zUA|5JFz&yVRT^pnRBh;zrn^p&Nn!w1oQ?^*t%cIeyq0v!(jjJ7y8vP%)YI#WnzIuX zbQx&amC`&dnL07)$Z-fO_Bl;nfAJ8LmB3&;?4-i_i0a<5Z_$GLz}(gQvPBbp^q~ER z(cN>ZuECCSSMc>`dbIR@%K#5+Kp!F7F%V(P-Vinm7}dtljlKOA zdG{nVmrPM^LXPp=gG1nrcei$;&Zx&cI@izkUWYEEmM&g;Z#Mjo*zWgN;N4)5ol~IK zK3A;Qj(71Y;^2FRlbi?<8-#P1`FeIKHEO`&LblYoqQj;Y@nt}x!fm~Ua$S4g%gkI$aKgn+BP2sh#j9B|guf})9vW!hT57D6oA;If*jJ|sR1B+Y6F=G5 z4!*(qWIlj#Ti^V)xrS~Q--b=^*6PatS@%%HsUGd~fqVHz$#jeRnv?PA_4!(#Wx=j3 zPW?9iYru+^_sW&`fF!s7*T)ZUu(oz+l>vLx-@*2vSnNq@KNk4 zLu4|VftkzC??3)d-EL=8_${rjRlfZ@fbS{}UQX8P?8`t2<0#ecnVMgo>j}s^L3JIBo1Dc_`PY2;W7*65?mY}2 zj0G)e%^rSuo}}^eU|1w#fJ0!r8Ry>D=UJEuG&Ha0!m`71p}n9u{d zeK2Ff2twcBfz8q7xJoj?Y6xwp`)?9KySOM?z{yp3cZe`Iu48K)2QFFth}#X zPf9O?(s1X31L~r&E%D09nG!w_bjd%It*%l+(7yVy^={c?-%wsIAdVs-)gwG{UsN4& zT97^N%oQ>SE|r_Jd&)RvYjB~5$4x8Ie;fNUov^XCf1p{9Tj=#H36G;w(-_R%WdD7s91o0vn_<3vv;V4!;{9Js z^)G&*2H~nSkMdz-Ov;qXg%1S))Ih`QF)n{+jqv63#UucdhWy?J6OYD-X}m-2zo4>O z=2U7`OSQC6SXsKzx$BqfC5>Su&)z16Qlzby*a$! z+`I46yZ@;72IK?1Eh})dCHion;ETBV>$AzOcqZd~r) z7-=ghgO}Ensr(t6gI4bAGcOX*&Ef1EQ~F}wdT(*At8!e8^_67F;ziC`g>oFz7RC;0 zCal?#u#Hv>NUe@RZ9hYEU0GJ~IbrOpdN_*Zj(6q>n2BjmTbUEFm>os{z0|L|Jc;8! zp$*A>TsR!$1BMBqLnAPzQWwV5W*{m^PnKKc9n2lr8b|a-?_3IA%~l#BM-rAZHyiuL z?*?6<%?$p8K20aNl2Tkg=zld&Mh_U>YGw4YBxMG%(eq}ka16fhRk<2>hL>@!$IC)H zr?&Pue*Wf$a9~T8Di#XQU>Sh9D#AgF!+S(+LFF9b${HovwV#~G84ygIoFif82Y261 z%`~2r*6DXN9bUu0Q_mDjS*}PY7cw#BE7cth`&Ngr#j!?Doq6Oth(x$sEP=?3=GxD{ zB=3*hvwkfl5iCXh;`8*gC48{oMd47QzPplDE2L9|wS1#T%DwriSZ9 zLhVvOZ~8C#jZK?^ee@szky!fOS!MLZ#%s zOKNIMCL)lRFC_y)MIIaX3C7P(DV_ny?aIq%V-{XRGp)3lDaiV0QE|UL6Fu7%V)P6# z1$qs+Rwa1V%I&iZWngyiwlpSx_GM2d7 z13b_)`mXa@DO1-oHW?D6T0p1RqqYXmBVr5}q;YYhpfJa!RxrQh?w+Lc9>+EKCQc-Q z@$D8f00osg6LGuRd93y0HFrYwIbey03@JVCV^31ODp`s8)RXU4G*RS2GX0Wz zRc+h6^A~+qP}nwr$(CZJRf3+qQAjc_+KNX6mWxo)7T@;zXPsv3IQf zF7v{c6MvE9@nWXX%0U{?+7VqK5A6YR9)Tc3+ZZ-i8DIVWDexq#@W@mIh9znhyFH_J zk)Nhd`rMe}g#?4^=ZTU#P60H~l+={oR{n0GHuqis#zI=RXhi5YOU))*BQ>KTG~Y6iNYDE1!Gvd(sT8 zdX)aQ#ovfn^KQvk7n;?bif&?wzKk(EF7*QFPcYTaM-Mj&gE6Y2Zqt0lVvi;Y)htI} zc~1J11v8`W1Nwt= zK!5$kYhF&eg{?dtYN=2&6)CO=NM$CL8sOV7F_6Ek(5-^ZrkusOE-$sD2$VN`3hfff zxh!9{Z<-o$(+tXhg%RHkKPyhJCss@r!VhlCxO9F24U{*sEzmM0Z&v9llv9$5wMHjd zOTQ#p`-4d;4$yMQDwt8Nf^N|)Uq!DJs$sDQe?f0Yd!c6xb{Vo2OXXZ1YBTrcqVBkofW{6 zWV33}LUw9TliQh%p2_kCO&oNi&&jv6nYoAz<>0KIIV=F;MvAMh1=7H0+)6I^DH(|)z z_e!Nva{4+Tc)S67x1n13!3{%yj&7}_PA8eUvSIg65F4;mxT|*Ik_oboaaphx>An^SU9q| z4Y+`C0}PseXs=$7#=9@w3H)z?g=(@!ek_Z$#CK!D)?nna`*Y{)2&Q_-$$Efm?xs-f zRHQGIqejHl18YZ(5QXXys5AaV?N9|-US93+sIi{tZ_EMO-n*z$TFkqdAPZq+tWA3o1`UC-uzbY0E*z8-Ps@GXkn=?*y;q_R!y4TK;PeqzE8E#D z0zyYYr(M3nPHLMr(NkzrSMf!X8|6D3Dh$16!WLCfjiB3vazsMW z5vE1yh$`>rI8!l?US2(QWjU86wZ-Z?s7>Xh%OD{-A(vqvT~vtK zZ}?I_5cBq=w@VG-j9_es)k_B+NK=ugp69EJLT|nlgtRqneDy8@F#)=KhuMxkspt5K z>RUhs==!Sg$6$MgWcJ3hrS$W zTn$sTOBDIRl1NZ%F?eFV>B&v}nw>taz;ZF}w)_yyZR}J(Nf*35QGz%wzQavb$@{bA zb*;`G$8~W2fL@?ZLeS>v4S!V>{{>x~rBk4K15VinGM;qQh+^qw^GC}?1kR^b%wU~;wrNjg-r2nEoH%= z9!3(6)aXr_^|~A4dRKk=3u^2XNK%yqiF*`lO)P7TdtNPU;q*rj{Pmsi6Ff?9SojVX zL?NmYTj6JE{d(O=7r6pyfF$&4ECbwS3|RVKF`cy(j_65r0KA0_-}d;9Xz@t_CF*c$ z|B)u|ADT>h)G>0{F|z0=>~!KTVl0OKbzhiGn-HIlb9$T=V0{D!cNjimU8LZ^81 zK3GqUOqQ=%AF-~~DB7uA*M(+(h1Dl`Bb=U;f}x~S0s+FnP6u2GQ8@+^#md04lUOVd zA8&1L*`nVN>KCBpy^-mi+*~jkP7-ST2qOjmGZZNc*Dp3EINn;By_bxdb1j;?@-L|6%^nS*i}U`GO=_K z)H)SbI-!ANp*c~WcI2YJW@v1K`=<1d)MPuy!Apvp5d>YFyF9UrecHaz9KJ_jbuVwV z4w>Y6r*E_D0!s#Yb;j7ve6pAvt&~FYAqM4+XoHeuyl(<_OrMFa?DY`> zO)JgjuAA5Y{B0mi?=E+{OylO}nEtzvCg`~x^H8Cw?SV3$4o|Mw z>bACgXtDoZWm9{OG5p|FUVeky4^-&3E#%+URt5%O!82&_*Qa*I*ggTbYYG7a&r|U@ z&MSXu!2D2;J8=lz7LX-)J51t9b9EAfE~2QODvS%-b>e+ zcx}%+-oUJky%?W+Z>890rd|cRXUg%7)I59o`VT>}u3p*U48pHpIX}JP|4Ppt|6$lo zC-}qg_^*e||Mu_xXV#|<;f-~i;wP8Ne0aa+fIJ%E1i#K7Z=yv?pbdK@+aD1JpH&Q7 zBAhxJi>QIPKrTxT0(lN_Y%T>Q(_WXE+U$~8h^`%b1JJb{#pBbAMvsI}2J zY1{OdjVYDH1CaU6<0R|WYxiU4&F^;c#rN?uN*H-RjhS<*SF$tai&2p|TKTP7S$%PA zmN%tJdBsG%#xbUPR-s*W$BCu##6S9^LYoZv(WF$@6m`s~K-6dpzewakz4{I@2GhM_ zz|5jqU7aq;P5xNaLo1Ap!n<|i)O=|ua^jS-Tf6AkJ9mH}MsTazAzh2v_Lwz=2(oSR z6}eZuNJW1f?UCf$<)xbFjZ81Ca*M#RCUuck>CJK8HD4lIRFzkQ@L#N~pHpX+k%sx+4 ztwE0T_GsZOv;OkR?pi@;8PwAC2<|Q2Et6S=j9F7uO z$wPcfznr`JE?ktzaYQ%f02|HaWqitaKOug!d_W>bFx!|=xBK$adGcO6kKU9@yIdgPChEn5=j zR!Su7K0oPSlY8SW?LlP(%boF~?R3n63a8Eki6SyKPIKe;q96#NV42~hV*$p8s2(llwzj6X2*)$?}7(>N<+##OKGLzn|o)7S&mlseKHOp2{82IX(krP)a_xb%|YRG~`lw2f>-O4sz| z^I1%b97w)Qg+*({HoWLe3#QmRLDN#eCY*H4o6AjR!WWTWBLt+o0Gn@-3D}L(rJ`Y> ztY(ZA&`L+psMLmQeQZgXNN5_|7$2uywSv}n+lVn_ju_Z&f1&$!MR2NI_wIEypI*JJRx zH^_+yW_R(CiQ|TevB4o z&S43CKP(z~ueKq(Us^F`TT_hx5QLDj0a+_yqDXM@F(qcRB%ZD=?q{hr&6|!q z$~x(|LSRQAu?}G|)&!XBF4#V>>`Q3guUKI>H!Wmn8sneb&bf6Iw>C|(6y#!;Yz|UR zdCi0!$^?eJrE(8A--2V$--}Qm2!|xi;xOPatTMpw3o1BKig3|G=>Ae#a(MwGhYiWok5|jNxrJa49EF5U5KIR+Z8^Y2{@=@-+(}!{W2Y9 zC2}v)0_sd=Xc$yQWM$ZJg?2gj3LV$N!&rxS!+?E%4H|Iq`1z?ci;_HX4=RoejU2*G#4Y3Llj;k-}*YYZBpvnQc;V=VNH!?u|8k^t76VR}e zBl<}*ZjU^_B1m(-BVz{zXGB*Uji1 zaG`uR&b-`znGaPD%RAXG3EuCS(^`Ro#o$kwj~JP_Z*aB4`S)p_*4-i8$4+WU)AWff z2llg4gH9{c!&ZK=ucMX=ofdui55T3SZ}-$ve8$hfJrZ&9O~er#Z6s2V2|N2{=gpnR zoy+~|PN$7X=sL4C7Sxh1<11Q4_EkJBd##d&>O5Sh{sStKr$6-v-C5aP`?QRP%!V;l41o6p-AWj~CFuA*kb8(swj}SS^=x&eHJOj-?0Ql3+s?;o)5pc1hj$ooh zrj#jTqO2G}9SObKoH-NeoTnt0m7+`^gkXCtR&qI5Bd8Kez}1KkWzM-rr_R2)WzO#z z>ZfdmIh<_T$S84gX1CPJ$hN!lZy*8Xiw74Q57cP=4bht75#j9SMD%buNt+9>L|I9c z%X_VBW@hh!4=i1LvBAk0g+VRII82kM*tVqa*^MYvlZ1t`L*tv_+v_4% zL(u1+h3zU>F}T<*bTNo_b_H$jd0pBO8P=crng^lSI``EU>Rif9EH-nU@mBja{OXK4 zh?y}mKR`4yIe>7X5}6p6v}7slJ1f(Qqk$9OUH-N_#FuxZ84%1G8>8la1oXHg{QtuCl1&NmTw&@OaEfxX}b3#DOM? z9F_ChMjzSfyHL&&yt_?@LFbC7Jnf0ds-j}D92I8NWMPr}W9Qt-CudQqsWg0qd=PK0 z8a%n;CQasPSJaYb^TC}`~^_NqXkg2{M?dbd|-9?dcr1k5Q^TGO1`$fYpOZM4+xT^u1y zC1~p`&g~6?xA~!#!?gaa(?W`-ETt3b8IZ3TH zAp=*;Pa=rOY}+ewAn-1oELul_%pua$oRby_CS8xq7JXfZ{-2J zGI_sWn}m-FoFtNJQH@mI0WV`^0yd3u>U-jw%S{b{t7h1)}4Ji+4z!)5p%h zaX_%D&bY2<<59uv^j?YICNx_3b5{sGyr1Gr>XwbTph3$A40l`uz3UbpC?4Rl2|=dr zaO8nfJQiHX>nCt!6JzTjto1MRMUF$VCv55$C zctn3cr?JRtq;_cNaKH>umIG4Mog<$AAmg}FsL~C-V`_?9 zHFSddYO2jO{?tb24_`e2{dA|a!31)P%dmae*aOWSJ)_E%ZChgUca@KR#q>s&H~8|E zIZ*jxo}V+lH`V^E=-p|`?d7cKJEqUJK^qETl+aRrTM3KY<79^oQAj7Xtw2_7zl=-! z_!$N#=!NJzsE-q>ULMu1?%nn`!K-f+w{9vHf%T*#L(@)smIlH-_3`y1_vLSq>}T9+ z#lP^?No64BN?pT}P+g-HWo0ySQJmc{J8I**3;Qr~^M=4XiTw-QIm>&)MD@^d+#ruj zV@Nz9&rJ+bHK)ilj&fFeTajqy9=S7@7-hfO_nRpoVVZLbPax#O$SRJ>RYHJj{)EEX zVNHgnK}02_Rcs>^|K?(l&EoV9SyT;Y?&YqTS_Bax>y+14Z>Am%LV2)(ofhL}RllVP zR|;B49{%t}e;`&A-Kkro97TBnNCLmFoWmV*NUo#^%>n&W+HZpHEozQ&UE)Lzo4yuC zz71Ck5xn+eot7^f#2IvoE<3IW`xkB=pfd#)a2NJB16%(v+rd&6{BJfQZrcDVyD;Lm z0hhLUJwOUQFwx!!^1pQXM_m2GZVgHUgfJ1Q`%6mdkAjJ;nZ>7jM^y0zNl)CO9Ai{O zbnYMwnStMS3yRUbK?s5c@QSzdP^!4ozd`myJRvr))IBnKp=e(g<5BDrJNktBw8j|o zP*RtZoaY<4F3t{=b7^zPhw67DXT72tt2^!0S-x<;cvpI!AbT$Ay`c1TyAQW?8cZ!F zqv95GQ%k6Ta6B}9fDHnfd#$r;<0~d>C~6LfYcR^7OiXK7n!1mNOLlnVAb)g5cj{sJ zwVVQ%Kv)mW_x2!hNq`r&7&*SpE}~1YmA@YWym`Wxyuf>X&{;i!gE`x_H#3W(gMtSXnq48&cETX_w79k4hca*UC7g#mzVHUU^j?6_A4BNxOnPkA1n8S&MR)p?uJs^W0KPX zA#Yu;Xrs~gy>YXkH7rL_U{(Sm3U|!!Sf%2-RYy5+@f@U3w8Fts*>A5+JuTl9I%fEz znEN37pG(UD;J+ynzXljQ#J`Bs0x=WlLlOsFY@ay3TpT4XdtlEQ*m;}y8LuSsu5H{Y82Ogn zgDN&9;zLfvI|x=!#<$(+wbmt@mCgQ{_>ZNS?0C=DGe zAW0oiZYzLEos(?b9x>pF`x)OaC_XX1d)6~wn2Eo@$8>Ye-9Ybp2BUQI6Wv8dRzQK@0GULvH*IlY2^`i`&q!0dicfGSt_ z-V9r5>XGa&7}X>(gTLOpxToO)39x|t!eR(>Kte~zA;oj2j0O$oQJ&U?tj{}Xf-_NT zfYkvRfx7!POV_KfE0|kh?#FP`1Y6aBi3M{&&i8BiRjTOQ?HIgy^OVUFP$0qR8k6Lw zHOz3!U^N3bK`K`0sSx1u;6$V12xGw==A5e~uM@}*8B+ee2>5c0inq@jedbX@4(c)D z&yDBhEBg25ssjo!vSTKz5W#q8rwAbjvNAMYC1QCZ;7$ zn9XbJiaui(4{wDZY%9~JHXkgAN0g-!82ZKvoeFleA<*DH<#1Xa(c$wGx%U{Uh#=T- zB7UP$Md}|Dlf>ooV0tGR z+%NJ82%O)Ak>xK2KxEHHmO5(vK0+fP56?1qpm(+`(X=fBjg?1gQ=-!2M|-5yoJ3nl z@usjjjH<|@E6}~Ecism0QYB#ag~0J%f{E=5boxY{`MUv`py`ZxtNG40XZda?^_O($ z{V#ql6}|-c#y8&6?XYiF)N@s?Q3Q(W4AB&z3X{E(N8k!JPf*+{fBqt4eJkIUbFEIx z1r#%@JOcX~+a3(|1=byi?HSe`81D{SKCtZ-mON128CLl-nC*ZP#MaiTio+XX#ns~= zjg)+zokzO&j!_s7`6wTaAS)L<>)hLxNSZyH%^vPX@V#p8d8k^UutWz6OD8o5#CRWY z2kEh@NUuzgYFLrwm$vUIv5V(C3ovhlAd5uX!K1sttbazjjdQo7~LQ)LdtQwsShV-dn-p8v#Erk!2cK!S95Px`5r8 zJOxna)@sZhYni$6v3wT%CCT<}dFU#-h=ulhyv|xek(K%nZh%MtC9p_jl46`iQ>2}; zjA5R~OH<}mRA#g~8&v(%-B`V4EfdVEKm47=CDW)yWZW!E4A$eRMOMX|Y54#KN^K}q z7C;w!QVYz3i1*lxn~F67H7n^`#ZTggm&Ea}FfSFA*QYFDm8m^>RIM@nPsV?7Ip;{o z;jbNZVb7Kd-C@MazIviS@($mIT71>v5%)G4Y@*h0uvDH|@M?JVAhBTB3+C{j6dN#- zkt2Dzit-N{D^tRMB(lW{#y?(;fj)Mcj$?K$cGsy2VonuSs%KbPXg%OH)Cw2QBu627 z)M7QaJ_R+FauY6g)<~|+-)6NRfm4*~=vl{C=BGCF0Gg_7-7-EI{JGMLka3m3-@`a@ zboABHb;*+`SEIx5SQoIY(z(EiCOKfOE=?P{gv}BWO8^FPmmB(M*Bid1Om2h2&L0MNBCD9K+xrr3F+TW7ZDN&GfMzntDQH4a^-%vv+C5L**P5Dnn^CDR z&gkVVbW=?(-&OKZy?Qj5>P4>a+T00^6KhpgVZ+#Z#@7WX^@g%Yp(@v4tYby8Oj^rV zF+NyDNy@fmUTq3=CpBDHjRH*kclEMvYBJ7Ge~deE&Ea77c!pUlG*ay`pjS3ZQMbFJI4USsArD4Rao2Rp}kiN`T};FnAY!_sqn zj6AIi#4}*a71rS){D%dbQh%K}x{y(O#AgdxkZ&C{e0>bDH8+5o=z1 zklub1qE>v%+mH@#OtA~wO@oILoLf4CzyS1I!~`(nI79HNJJAkfrWie)EV5}_e#U7k zORw?}+I`A==La^_R(+^zpFxUQO5ZTvy$(l+FgHUi$Cpuf{O8uAIhB#_(CUGnY!{=g zbOPV#=fK%AUu{aiV@M`TBwY^nj(_WOFAB(SpeplesLa^kMBnQ)8IK7kja=lO~hs;wBF>qbI}pA4Jc z?Ho*A@hC$cc4q}yw?8BuTkM~2VM}PJ9CR8?0i#4TwzLNKrwcumicG5{4Ke0bB@5g9 zfh}>pZMXQWENd`T&O4Mwi#G4TbC+Z);Eii^g}nX0j;lioZI(t5 za4J>@+=(HKiaZjMHcDa`I33FDIwOq;OC{vsZP_w1G|RXTgkmP0DSK@RW}8)#EUFAR zPBLur?|Ah{EMd6UL9Mp}l1su^7H#KBP6lB}Xz|=;Z4p+vEQ{>=N@IV4{{7_BPU`&I z`y+Y#`!oJU&Hd-e_s>2qVH4*cN`R_`iQ9j_gqX;7!4A+vbEgSBQ2> z3{JDSX&tPy{k?+XFtObHhd7#7Or`=E>%NQ-dbjUb5ogFV&B0I$I53P8vzI9dj@`QO zdE}SPC_E>v*IDm>^cD;UG2FlYP!gDbxHSJFCH!;#(sl;M7Pe;p%efz-V&$}`3eOXX zGsy}GA?r@e9IR$xs02n%6JN{>pC7V8QA5Ezmu5KG+MiHPEOcg6>PPemP|EbiuScL% zdfnz)%2&DAq$aqwXYsi&>DJP9dbQrQ`uFo=6Yp2#D{U0R4--XVv`gb@zlR#J{fAYj zJ%8_u%2{hnAE1bNOpSUkkeXH1=uJK;b|9Z{oN3L6x_W7`6+cau=pm7srLi?oX81uJ zGwb3_Fhb{&u0dT?SyVc>ps)`)wUUnBkg4~+R%M&m5N85-rF+A=)?Dv zu}rU;a&2(z(SDObwc@kd<)og?az(z)dYiFGvuGh*MGtCB374I30phA6WI1EH_QNC` zueiiagtDDppjU6iv1;gPwDwq?I!2}GNYrA{T9uhi^)Z$PmDyaiFV8B0Z^{WJylmA} zWw8%AVck?CB`7dBH3n;t++)ypy&&xXne|y%xzG{SVj&TB!-%rAAGhK9kuRuR+Uj?~ zA}qkhnCiDOU)|p6IjanTRN;!c=N>TS8+RGERf|ZHqVx)ryFP58I4s>+(rxq*t2!7Q zSWhLe#7OrZ(U{oTW%`!bfT^xSX3|2fptm;-wlvKkaO&C6wOw5RI)UnHA+OlySDi?9 zZ9pfvgq9986&Bb%w^NUb*B!L>RHCe~ZB%)Pj9cXgC?UI6@tUu_{9V8@A7HfyZZq+a zQM7d!fx+$ZimtN57dz!qLNe1>LnwUn>N;T1agg2)KAq zI8&R>e+x|0b?Z3`W~t7%e(PPYKMIz4<{ak%i$&slDpWa{sd!_%${)WZ;vJne+Z#h= zf)_Hi$qQ-QV9D5%jw%ri+NK(u)-_@_{y6pCwPknQ1qXr&UAkDg>a_9b?QEreg!IO} z&~QO8f6suMY$&qW4`K1p&|#FUv!6ThvhR3)h)(syFNZefz-*=BHNJpW8&FCnQeJC| zmEf5_L_7nSLtvUjNZ;#2c-;lY0IRj&IM~YpemnO*WOv9py5auY%gh`0AQwNu7t;F# ze6E-9reKnO(3ykF4=Vv}1Wz>PWERXozDJ%XF{8>b@d8vd)F(e?!d7nst?!lzA!6&4&CAll1^m_&D2D=8O3wmj<+0vc*34|Jh;TiJx zkh0Q0se55t5nB55Q&4sUY2KJ5b`#bm4#^)PC_{ERWZL<#7ddN)+;qn&v|D0 zunS3;8gYQ0vxT*xT-if&VGriU7*u&fUhx|s9d7%7Rh#Ic?QySyXwKf-XOq2#pwk!q z+EwreV%XycPEV!cv3xJ&G`}+SMcO!AP$EtxH+z71|EpXsVfZ9cPGLeZf?3KAbXs0j zDm&=$2X)@t;)o=t{q8@~rf1ez$IBmyJIYT0rTTxm>3;@KMHB0Ph0lKo-Ty0`{@*sm z7{&D;@;WllBD<^QRVhu0HI2-!#tf=^?haHZrOtApt81S>UN_&gB03SdOM zeldcZHBw`Sz_5(2sh-mr4kkWd-!Itwpx9W@KhEan=jBFvqe|H3?8(1#Ly2HUF=f%0 z(tOX#PN7*Qdky6~=jG{{b@-5bt~~J%q%iLcFkDYYX-yhULig7?Q9M?Z<&0C8ipeKD1;GkB6NK)EH#y=fA* zJ(dnv-)K_b(VtfHxE+$-71GV?!xgC`8NyZ&O_z~VPH_iChYR+H1OJ-h6I^>VCK{!Y z4U?2{Kl>&R=eFOKNOt)l6%&nO%sGtq8(pE-&#%~9?qTFIHGi{&Ie*e^W32qW^JGGY zqBB|35X`$Yry-w@T8JWt3R9}UP(5x|>^O`B^U*7DaUvMbj}g}7zDYHqE7H&FwFhF3 z?QIcET`JtpA0wC?TiI;W*BjFakm$-JQ>mV}MFuf9%%Sd~XRVNA%_|bc&2J-9=TIRn z_tE87pGIhnszemk{V{iQr<%Q6{ z=h-0%ld?Z_mC(Ndk`2fL%D{EuwET$b?L+Mh3^L>H`ts{ z*oP5lQs&X4cj>Cp7iTV@d<+U`y^3{T&q?*75XE+FJ1=|LNr`C3{U{c*Icd^&<~yOjC0HZGMC%yS15%cp ze%$*8sZ|0P0H}#XKP?FM`sg9VkI{N zd;9jRX@ab?m3tynVfq_+l%%5MWf$;Fufak0A;sc4n+toYM13>QN3|PM> zD0ZN`<1$u1?t$YCXXuT>A$mKaV(T|@e+(tJ{-hj)P=Bc2Hd(8|7k0OFCXY%=2xhl+ zrg6GZ3kj;PwpMu7+ANAs_wCB?D!VEotr&d0&$k5JQGOGrshO-B9e&o@ZLa=(%F=Fs4N z?RQTXep@gV7gH@W^O4(j`(-BPa@jihGU%K2AyaiN zPond}nY!5Mt#wr)^xEhP(1Cpfu- zjCs2W<_GIY-KG_@q*S+YC)hXWX2ak{Sy&m)3xi2Z-d4LLYqQ5{NoW8NO+g^8MA zpq@=|!MC;O8aHAzH>=OYr25+k&@8G&@r#*kI^oLwE8zICl+5$T)s>Kp zWFLRRY=_Y|CZk1gqP08ep79SEpVr_sC z7e9z})DSo64vHgFy~j~|0HQii;`OdBCjPfL-IXpgd`=2!QZ#s01{)ng$o615+})Xz zCYAuYQo{MdpWW$55!6PTz&3+ZjSO;iOl+U5NBThZ_{i?xe?w1WM!fK!U<&Zl3s3n! z_NKFet+9dQKMa2Um-Ba0c90$(yUYf#;OX~=jDSC+_)};wzcwYi$g%v;wvDty3g?)_ z&(TW;560^sCs@50DF%fYR&BP*)MPbkcS~3I3%358FiHeXO5L^oK!5o+W{?kIO0*mX zzNOpTMTD!AmM@toa$+t%hMpG=>DX@v@T-RaGRO(X(kX;lD^wRiC^lC( zR5df>0NsQ|6Qic1hpb)<5OsuN!(u*EieZ;bKM)>Vu8^Oah_`Tx(d%Qj9yfFdslvTh z4m})(vQYTLWWcnL;=PoCiJlu9UGrnm_SnvLr7SB%qw>o+OYRctKQ2^dv602>KQ(CJ zpFs0J4#$6_p+CaJf7GM@?JlKeq5og~XgAh&rVoN+{D4FN2*PAsfu)>;l7e${#`1w{ z{>iZ8P|z5r3mVAQR%{!R*S#9nHYu&Zn$|*9NGy=9Nvd5NKY;PGgN5N6FR4DK-AQ@~ zp=Yrh9;e+7v#%Krwz%(?0HR--%1d!U0^W#ukcK?4APzV;talcCyoaOZcws=?U)A34 z+0-wu4s`*7@G0m?bT1g~eBtNr{HYEaxXE^K5$QyY*eJDO$5)Sy-UQ%uz2u3##FJ?z z9z*>M>wSnr+*>qDY9<~_{YC=R$w+j)q)V$`uc24F$+NqO$D57Zv@fu4_c#WhwRpQs zcVGCZQ8H2YK>M!+0eBz|1iAEghxi-xqVp4J&m# z-y82Jf-&?=FYm79R;JluOM~ov?yelqq9at z0P$<(%WWLzU%`wuJBSVoq{>oCS3)HJc<+3QPm$azE>W{?akQ$j=FB@U(}9{eZ;Vq^ zt$p5T+3r4&7vW_ zDeTLurzGN3@0Ja!Ogw**_f_}PsvucTbrLJ~3cZeoAx%`323wFVmhnPbUv@C1#n;ZM z&^ApAq{yu!?4sdN6n)5WjzA2t7=DdH-mqnKA!!XIOu~55apRP`%hvf^=%q5N88bfU zS~+qmkx00RM6$qa%svf{#YPgU zW?^TVvX>E7gLMU$Cd+7$C{L7ozMC2!>`B8_+$5J)5JV$B*2su#`f$?^%vl%v0yo^I z$vntoe4S}6XhzvLXxPKCBdJkkaMBlid?Ec~*l>iHIwDh7i@uq*ZX($$LW`)_*kW$p z42}-{xj58!XNOeRuc|S|6FA%S}Vd zs$Ucl4BjjC1B!~FA;~d*Uoxda{Tv;doEr_!ZTJno>L-&;e3Yz6tJIM)s;pZqeLP4d zV=bB&!Ife_*$0_U8D3pMnQTEwq+G&?a?&UNoaxT9qhD!JIib9$!Xm`SLV5l6TVSJ5 z&@!~bv=Y;B9=5u|(gJx$+N2%EOL{AiNB9>L|Bc2Nq>TIAyW&u$C9B-sPuGVyJ5cg@ zJ0#WASKqQxWz!P2(sD&dX=Ye^#f<9Xy4vItLW2(KQz@qy4gHd!Qce|W#s#-aZvi1; z>yA83sim?^vKa@tEe7quocuIt=)L4YH@39Qa^a8HiMoz{IZ3G|<$0I9&QPmzvPmWD zG2v3hF-c-y)G|q86&ku3l5e);>5vCSvYQFV!S(#nT62~6Y#kk2Gfx67M%-y%JFCRZ zgw4IR9H)`RlIb(Q*)CkMI{kaNKxFbVax@&YGieQuHKo$YY9`I528pI8OHVxyCyVJ| zSxydS{u1rtOyhCwTw>RST$;?Ycc!u9`XwTxTqP({1pv-e1VP6c2p}uN*ptjA=BX)& zo`UxXd*6i2Q_QuF&arXE%$alTOmy}L5y8Gcj_e2td`m)XY-7Rma9CIkavq2b9p$xa z$YU;FTJBk}?~YsGVUsIqBL}83kITaNqzosIn2@nim++345otn^g{Ux>1gM=KW6rUJ z2(1paQ9uT6l5gok#^T@6CC8xB)=xA`ROd4SVd`pArjy&mmBKkpj-(}~o@Ucz4%*H& zmWp?4_w`r~i);jns4NOG=m=;#*cNdOD(4Cg8uPSnS9c=p3cf?~8j6nq&m2b=O1V4A z5>4u%@nOwsR5&ETJRb?FmStbE(>7zA@+DEy!F37Tnf<>atM_>PaN3OL;vL3{j(IVc z4G@rYt7dBVc1_>9<4m!pwOX&2hPhNXzeD3Y;c6M$;jF%WISygJjZpeU(D6++vg_%` zbc(HAiiuzO;_qlJ(Syo+esmM~-S&VpGsh}SqEpIJlpnL~DPs14 zJJmx!I$+EUJJwXMpq`N7jYB?U=ox4zDY(f`#tIcvg$g;4|ZZm5_2|$s9gbLQLlmwqj7QAckRu1?;1UtHQ)o`NpxK=GTTN8@TA85% z4StX5K?3;SUs{Jjw2ITqn`50ui)f+)9yIAM8lPQ5={{gLR7H4ajz z4K1Z{HijimlxKRhUVhHebqVPYw$L#9$#aas_k8F`{V2{KY>K^Xbh}rq^cj%pB4x&; zyNW^X(Y@pHgU$#BPmcM{jCLISQGBMJ6z$Eiv>`y#%G?gif(#pIH!h==$azl+9gI0f z?;{{rL$pV|IC|TGDrQ8_NZLQ_w2;r1-Ju&kLsQf4G(iuXI!Th7L!)^}{-++K=2(h6 zH^)vO37l}3A(>WX1Q!^if}LKrhfk^JH6YZKd);$y+K}PGJun7x0MA|uB7|2sLPE3P z(qXn&7KZ#;qHbLo{c_@S=!2z2!`!X;f?P+1QYy}6v9LPQgKBe6xR^b;p|<%ItB)V3 zSG1u8uxfiT&Pzf!P9D7x98dJkiB17a@(lPw?n(`!B-*u!^!z*Zu;^WHB6Eyg3`5AR zTd++G!fIN72JWK!l>&LNPTG?DyP_stxw zcUI1+%#Szq#zJ^-RX)T?4{=Lb@@g+9t?*d6q+{F&T|&$nMoXg_R`{^gJ;4;m}{l(o)(?$U0dN#F4L3jH(A&V zR1WbWY6M1a1MH%3_{-bi(0pkYIBS4iFdG>5{XFJrn`&K_cJYIyvt{VX7Y2#)!_$0%bkI+16a~G!o&8sW_wg4to+0%`7u&ApPnic;>uR# zVmr=lnF?UcCMja&w**=Z?O8`}{{_4wYrk+0&H-+mW2`*`)ZJ4JOFiSwFT@t#j8~uF&OXta(eh`isaLg% z4+cv=_n zx8d@YTjhudPaBen?YkQq)0aVEab!ks(E7_l8MnfV!bMLY;>{6V5Zg_rds%zsxQ+aV zQHhXms@FPp>r8)wPkD+2=~Ffd_FWf>*4`ovh&D!NrGM?sh|wK3?x^LT<+MM{4s*_I z7uC~brWb}WvYjo?EWAElw#v(65^Aw1+lrk* z6UX7-8J>+tW{mu`KB(HP$EPLcObq)8_wTuMTF>oE>`x9|^)vAQGnf8fo&Z&%86W`} zkcCg{E=7k0ubzN6tYP&jG$Bwu1JDDBhw#QoacBOkoaIoIq69riZ>M`Zio_q);17?E zP|E3U=ggT!>k>R{>0^IUYb?7miF2A2LAlp8OS!QH`U9UdGdvYZrJJ}ubb7A5mP=E8!y@CE%-7a0B-g#X)I6kTlp z9UEw)FfALzkDhIVko>)7sbzEkv{%CIUpB%YNMHfjtk%?;1uB>dii$TQgx4R&coR01 zxv_X9ZF;i#_2=>O@Rxop7nBkdQ>bq?b)w4Wg~&Xkp+3OiUPpY&fntq^ZdC0brci^+ zM>K)zxx)qgC<0O;J4ttPWG0L+<C_JI}-!k8y7wxaX?RtdrHfUPZE+?BX0^;#i6A^FX zL4EL)vo-7;xt(+7g2oJA|AS>D{r=n5{b&BpKXuT5VbK5i(4%VN==9^E^WQ~K8-;%q z>Un@d1aUw--uhpthCyVEg$f;v4kF7TLSw1`NZEs1OPfi*Q#$1j2jK3CqT5O$6gG#q zI!^x&Wp5c)=azJf65QS0-Q9z`yTiiW2@b*C-QC@t;1b*k?(PsgaMr%vefsO&=eg(J zAM07a=d5>@jH((V*ZZntVW#d02?Pj=%{C+tIup`qam0&n_TetO*`BPLj^$EIzLddR zHO-mKPHz;LQoAOTF@bh5_1MhiA*-CVgRz{#D28~#TYuDKvM%!Oe;1=?a>PH?7C4np zImc%!UW_j==HxYTVycdCl+ajtN@gp}G>1D>s`I@6fy;xk72Z4nV+L7t4!I?iTQv(2 z2gNB$Dv9VBDp&Q(p73;aZ*>Gw@{2e@7ryr@m8A4HoXD?1jBh49H~zw#VHHevUQ}H9 zB5p@A9m^QncjM{koH%q;eekdP>GGBmOLA+e{N|@zA%?gWj$pH~$t^&TGGkD)o~pB$ zS+YzRWicNmVIgKlDt#1m*r(l&UMY^2TvtXw#Yowk6O;!9OBtvEnh3AFk8{dp38Xe6 z(2Rcmx(}bM9Kl|N()5V%Z(rtAn$9G{Mh7ScezTN7n-Ty0{hN81*t(gTnKC*$I{@){ zS1U6Y{kWdLCOU$j1jUQ(;W3r`!sPUE-awW2N@-q%-DKXLgQddzOi#P+U-UFN&LK^g zl}rmP+GY6Y)GkO4^eyGsfSVP}4e7Q7Dz*>+8;Cgu=nz$P%{+=#=S%_HUZYVn#~Y|8 zemUI01q$0|>Re-nx!s)B(g-zAr^kkPd9&UoaPl|j>W z2RpWD@MKi^qiPOCrH4Ji%13Ek7o)lU**tyPx-N(OpAF~z65&n*QqQ(D%7`ncO%8b` zABW#l4yrYEjEDkSPlnUOUb+QfK^*_1ck$OPW%ZItW&&^LF9qZOdbcii09)Jtyef1P zWYgzq5YT_$6aer7HUeK{gbwfPxYHN#DRlOA(4YA?(IhA+K|DD%6St!nGqm%VhppDm z>KMh)ank@vK}3kQfqkrGK_VO64{M#M(cqA!p076u3}aI;HmyM`K!t=888-ffr^%e*d^=DmF2?%jnk7R6?81* zNLZQe1*_@=7BV)R;TcrDKB%vKai|W)%0$fpBW{-uFB>FCfEedq$NdzL{%n?3L z)G-~qhT=A%sbNvhgourZnD<&D8D6V{RMM-~Uo;7>i`^)E=#Fp!=#@%WF3YyeEHPsk)?Ap$#21FYpVEoD@#+@8f@s079Ivg1gwEHAAUY?_)?#YqViT}zI)JmWeo`&XF#SZc(knyjZ{uz)2?755ML z6RY^|_xqO}q4@LnT!D5h_`lkb^`G4}<#C`Q8}>)(NNI#qmCln!zi7>DxMqn~WuQ{` z;vv5s6hVZvG$kgHtmBveN09A<4f9eMZ;XE12ak;?&*2)Ra@Cau$l8mUs$XriG?E$P z7FykzFuu$i>x1U%~q-q~2B>3koG$YjZcIXlPcH&kWC{!w%SvZl^))`Px z;z-)7oHB%*O2v5ibBTK&a#il0iV^96>S}L>Pn&)tceD8@RAB&SPf2fCH=~dX;_Jlh zbzjLJ@EYspm2h28h^h;IvU^Kz!Wiv65C*wggd^8P<;QAJ-EAtGm&aX@mu<;yW_3v z{ABo3;GX_F`bJO~_5sBtE%bnz5g?YuAsKMr)Yq4w zq~?fnkBVtZPYc~2?7}zU{=~jXqAp(hM)9^YpMNo0P50!d_c!}9HVA62km2U>7ET~YeE zInd-{MBrI2zR$wUC)QAqX&(-@H=L>Ak`A?(ewPmPZQip+>7-qW9ieQHF|*X({lN;t zSA>b%!=hQxB@o0uZoey;-Q`t`VqpO0Gp*v zlb3_7O5e7FnQK64G2pCd_?OjY?riCOFbqXab86nAZP2!6eBj4*1q-ir4Aw#i%I3Is z#+UV0Tj##(gXlmZ-FdgoCM|Z(x zW0s_S20?$>ZA)RdWD8eWnnPmFzh;}XSK?tJYR1$q$k@l}iu2VV;bvbPWhT3^GzHgH zKI0ltA$#)BfqL6i4Vm%Q%y`J@QPbA$Wkw46uKJS=c?SY_sW{>b(R}PEF~+IHg-un$!KZZ3LJHoD5xj2`qiDR*=uzWpc_M%4{rEh zl{|pH{f>@=?XBB`^3WJ?q4f(VQV!-i&bH0&$a96W)$cjTANEBaYlqbO6{pT0m#4O` zl$77QHwoY7+qZm|$Z?nn+<%ilW9pn%58$8G#W;cBAxsbz^7xmh+1W%9tO1F~lr=?Ek8n+hj z;=^>em9&OwT#KE{NE{e-hoDIvc3q8-HGw}WpYcAXzZsxHZ_f^m6l*lda1p!9g&{GOZUicrncB)cv8%@U`>tP_>DiqlGY&W5Q1B4mgeLT(vU|54=$N zcC{eqYe(L`Cs(j@&M)Are1v3II%i6?WI(3`ZBy;tjxtqjvs&wb>J*EYHl|?W>XFXE z3ZR4!)3-L`U|;W$AAR5#U^+uvK8w|A;N`Q(IH;+ze|pV#X&m|8o%@acPDUOr%l@lT zI*v{5nxI!w$IMTiX1OD^%owT|P>5iMWdjqPA5yQV=RA7-4CsR_6FM+P9{X}NiQHyx zFzmA@N9@7P##oo>2W{-|s*~n&JSd+S1tHv)c z4R*R)rR2`O^%E2$KvpCYOzf8?n^#z2Sz;m8Wn)#aKl3=4X5(XVfsPN}Q^skJIp zyT>h-aDGbYyFDq5aW7i6UoEPcn_68ZCw}#u=ht4Mzq>caLFD4vvscI3}z$wVuJRKDe|hUUF~3vVu4nc2i~K zF<}~3NA;HVTXKC8`o$SNiC+JeT6s;ht|XFrJr%~zek&@eT!j%)h9%M^Q-KK% z6m+P8aDMQ*bm1t`qo!G_Cu=ds+JcsFbn%`*fFp5zaM12|2bi4Q@BT1hdaD?S>~ykH zHx$sS1G|;M=&88cB6m&Tc}N|{we`)kD34(!xF|gIU8L}Q`X1E4|3*60Qf>lijtRk6 zO^GMe*hbX7>Nym6D18FqmAU+L%h@FqJ+^gh#6+p?Kok5yKAHA=X&I$xKUREUwm(YP z`9u1`$vgJoj&!ZFX+GHA4Mc4yntZne#M6IHB%rQ_E|agQ6Qztm!)}R{OQ*iLGK|L~&CQj(Fa3Uv3HCImULMhZ2dlr}3A~4=_(V9?qT8ds0}6=8i&3 zM7-v6N4m7fMc%Rcx@VUq<-Nagjhrk>8GNAayM}?hJc3`9C5aLCz$CdQj)(1gd{+=o zEU3B0U}w-I(iI<7<^BN86me(Rzgk@2_QUux5bTvYmvwN)r!^9BK6E^+;bc}i8@kf& zzPfv=b=w_dD~A#Sq!Z+tTb)0NEJ5Z7Y*&!+rLjghD;zlvM>|j_n-lo+1&ghvk`E;1 z?#>GfZvZcUZoy^-Qm%_RhO4U?@)dCWhv1&@^KaFC9?`hIJutA({s+JQzXH3cgPT2Y zoJ$E{Z)W??$gW|fi?@Q|2Sq}TK!*8i1okoD3xAu-F_0#Phv-9Z$8~BJ^d*7QAqTP^!gy+9y~hj6k{xSO_j~9K!+w92~z>hHiB*fN{#3IDHi;hQve#wtV62AfX zh_%PTdTNZ0-ZEJq$#|`^b!yh4@H}S%ml~j2(yg2#;Zf_()ejH^$e6=K$p@ zYC&SNq?S(}x&MG~p^iG-R z*I#Tu!4{SyxvX_N9=?Y@R^F6b9Y@xR-&(p|8neuFG<0xrULSA_wOS={oXz<%8y3>A zU3!=+1aXdxb!1am=XkPlnRK>J(nZB=1P=<_9V^7t27qAlxry3>b|J-+?l|Ral{A6% zxLVI_3+;z8u1|XQ7%Q@>!dy<9gq^ha+cG#&%04ddH{L{6&749DBcS@}iy!n?<{Q!X z@KL)6VgHe~bIaxF;2RULul@IsSHr|Lj}Y`uVyy`9fZ0B7O89OV3xsSmXTs{WRnC3Y z-Ui_g>@tYo5VY$FVA_6jeWLAS_^+Bu5g*AJ+uf#RbRQS7urB_SeX`Uz)FxAXo$+}j z1OpS0oHs6DKlH8Cu5is{G`swCvAQy1Nov?YHsH9S>Nek*d4`+1!aAyJeBc7} zAN|S>to?Pn*y>Y;uNd5CX0F>axSCR{YPEHz;>SCxmb(}2mhVEH%>zn$sjdt?422jP z`}o?{ne8x&Gw(4SROx^^)}59ZYL`~r7~3TzEekIJyrf^u=Hdc&sQ0&jvsmm@#Knb+{ zNf^B?rq%~`@s9g?_V~Uy?WIb!W>aNSeO%oaEHFA#9_pKgG}9_3i)N_PcsC@oToJTM z_0Gpxe@If*)x~9iB{xsZAU;_U#2G~PUd27QT}H_+HA=QZEUy@IOh=|$f*VFlmi1*6 z+P`JhS^64^9goF%RtF7l)_i5QK2(TQS;B58A8?q<(NPa|$e2p|R zN=10Wwwj|pv{fj-p`oNi9?3S}m$T0Sp@H?09F>V=ZLFtfbz(Dr%??hpEhP{URy@#V zm1v{XGzuFcGwUNRRRK0cO!kCQC;A4Vr8^&#Q^Jd@uvoxS0*bdG@ADhf^NbgAnPd1D z!9dSi{s{2g{j;j~v^OLLD+ak~Fn+V=g5e-38egBh_xgQpcGAAvS_Idr5&KgE(I*d9 zZ3#sjDBAsg)B0%9aV)3yXtHr3oHoi8CtaL2s=PW}ce;UM3#$#!ivpg_$p^sE8{!e~ zxNV+k>2Um@pw=+*E@j8Wx!fSrjSrY-uX{>nxN>KR3L4&tnRNG+Yct)N>HvZM92mt= zSK%A3?8!bdJ;@x#r72)zoJ3C~F^WE9#l@0w*HW{2g1X>q|0Zkfl89Gez!!#akS@3mm;6T5hrO+C;gnPaxa#CwgpsUXCI;peQv?TpX)H5 zG&ttF&w8Zg+`SpyYy-EuYI9lfD`=g2&%vye1GfYeI>GmnfJSikNxLnaTzKk5vPyJq zmQi&MJ67dka2al8mk!Z>!_omskKI~k0Vu2I$HfNi9_M4@)1{dYpyMs zr3$iECpRUP0akmR#hRj}mFKK6m=y*Mj$l+7t6Z*i`?aiBu{or>cM+^tH2X1T*;_Cz z-`8KhDhHj_i2T+lXjCunhfS{C8(b6YRW*za(Ap?etUFt%dc{0DXFEI9E$kh&udEaP z2%~)^JdmR$ceaRw?PGKoz?5*B7>-a|62JLoF0PtgBBM~m=InLr|DQ$p=TP*#7!Wg| z2Tmh@{V)7W%*-4B6h8W|y*+YOSAdC)xV@{hr;3?9a0%mIW7#<>`if}6z%`7zkWLYZ zNDU!n_JVGI5t(&2)riqhj@a|M zJZ*T-bhv!${d~T|=))z%U*7g(Fufdqhw#U<*8K(X`F@X{IuA=k@go_OsaRmyA zhdsGGU!Wi9!Q?uv^+5 z_;{FU*hqX&%PCJHuhz@pvaw0GVYaJ+H?ZeJ~av_8$;Kl4j z@#y<5tT^Lk^kcUR%NqptAjet;i^J@D!Wn!;(@1*)(z`h3H~8*1pc6ML!#RTILUUrO)McvN zi|zsffkAlX#90zt5hQ66P3VUaCb7A??3?f-PJ9ABq3F?%`pK4Dqisq>7h`_|_RryqHq&2pK!%el^W zu=js|eI)1u=TvJp;SYwv5G*FO93e3}0QE|}^NG$YBH1U^4A9Mf<(8cU0&o)N-*3eXgo*VTVIXSs?pmGIkRMYG zm?5yzqB=8H?1HXy*@&MIKw}S!4fr=2e;1D7#%>Tps|KVt&;!ckH=WwwL>}`+a#W@x z0GH~~i;=5KbftO=2u;RO7$oCkczdQNxXgPMmoOdHkz{~zvx-0cz)e}znOLRBc zfEg0P9t6G`3i}kK5OICa4(nRIhA88y9kbt??6=^emM21Gfvpp$4WA*Q)W*1}vvmNR zAlyCd`5RO#1UkJX7c!W_3Qo3>%21AK8;_9a&=n6f5sm(hPg#+B$AtwQC+m)#n=?~U zej(I38L~Z*mhbyNQPw$Z6Bu<9Hx#_`Qt4%2JNGpqrrMD&Te)e8qRbW^dO=>Z_g1Jq zmYN0+r`|f7lv_i(&`1B^ehW{dP$)T9nMG05qiU~AlQ9^T&5M?dn+yB23&YN*T349K zLI-e+!_LQvQ7K|+%RYe_{m89Q-cGQt(^pBlvkSY}eSV1L5Vk zB7P_BO6cJPdL9VvQ*J6K3{k78q{?uksqY>3PssVusu!~$$G5^3=mpL`h1Hz zZv>X&nDY}1-t%{sSX$RmVvz+jMUo#(;IDZ|YIPc&upEdMQ}a|3S&HlYe+5>ADK z-R8QE1p@n>=Cg7zvjiQHgVDxm|J$>?XFio!&^;RA3D4r?+zG=cPIF8HZRU1S4w-4I z+T82z(@d0vz8b8YN)gQ-pS;c#@=f)+V?Lx#9@Xv?T2Uo%DL4Y#RG;dybJL>@_%6$%5AB*01(l_T$l>F5(WtQ2 zLxq724-?!CVg=s@^@@Ar{1pv z%rBo};18mqv7Lp|s^R#V3u>Es7|Y3l5d13f5)%EStnh806q~h=s`bI6u#7u}!Bnt~ zW71=E9+82879O=mdW)W4#6uGXDFET^i*6|gS~>>D6vkfm@(F`#(L^rA+VCtyH$0b`1Z5}E=TL*nuf(s}BN{rY>kHg+T~+LGy63Bu zt5iD8O31_u@DKCnToDV+D*RO0{o0v0(|{qU=kM`0y0~xAsd-Ydb|gw0BK7SN%ksg! z`Qq5E#27`pLSnzfbP*c4VHal^j~o#qkLQJ1ayVQgHb;3=NW9ttB-u+@zQeoSlGZB! zP~2zH&$k8ZDdFw84}W<$f)P)i7ym6jVsr8lX^9}-4T_!DOmJ2#z9Y?JB-K}6 z{Dn29cICQSthAKjme2j9WaL)$)vk*+p0)*-oLNUBbGk4K_a&|Ih$WGs?QFhE#xSED zsx+G93p96`PLkoBDKauyM{mlVx9yyjEK{M1>KUCiv~3g5uhcgENLjo20$mMZEt0si z0_#%~uwhDww5?~u&hPqG;(j>_$t?W0>r!);-1P}AMkICs;J%Ub#?Hqh#0YRaY_;3l zpzA0SG|L~G^@~#yc%tqRX4D^RM}Lu5a#b)MKP0e?W8qxw{gfy8NvWG=7SoR}d5Ao* z2?K0mfMb-1H*Sk`%|Qr8=7Dq9IIf@8;fnh5_9&FzI+ER4)vmi=BB;oJq+DRvF(2QB z>AcD^#L$euds7}w8uOdqMn(MC{~MUOGVn&^Iv!55UZ-;Ea0>vd=lP^1mj)`E>GB1Zz7g{+2Kze(Hg4VZH^b$tI`Erl(U7B0ESXpWhwKVA& zpN;p5{1)Phk~(8aS?yk+#Kmw>4^^69$r@>?vUnG$LU#6zsdtejZwQpIO5UTpJld}$ zv~H=s7$82#1uEe--F#6>C2O{Vf}Ot2+%}n${lku@B|8xVJaOqQ_t+~z!e`C7+DIaw zkFA#$nXVIqW|qr==gz6Am$3)KWi$0>qAus9`a;uUFGDoM41?j0@%nN1A(Xh)y6k*~ zl%iL#sx$s6hTXyw1%|_n1N$jM;6k(WrMIlS#ZuyWkpr{n7|c2c9V~MU{EDfgUdhQa zUM*RUkP+nq$~{?$4zxJWda;p{uV`wJh?Sike`4mk@YRi=@38F_GB*Ekw9rhd71!aH z1_DfJ+nj@66e4RV$P}>j{@QE&K;f(9G0BX4qRfwMU3*q^WlU~=Uf3Gk1QOm6MmD#vLR*JI$il8uJ?AR3sBhxHBte1iljM1{390Uq{9d;n5B3bq%VDcJTt6xi3j zjDWlwxt#&0ou(;kbuUP{>ZGFX(8d5*kA}$1fa7|2Tcd^VG4cp*0a>4AmEg4m@;3t7 z99fSM1E?oob1lE*U|=t^|C)>@+7&=ge3Z@ku684~PwnE{T?OkY{R&V4FJ+~o&z3uU zK8=~2H5JbQvKSqpFISX7*_(zgEeAZ+E4&^mXmf&2xM3DwXK@TfX2n2CBd3pb!%UB7 zV<4(thuj{S%Nr6R7$SKnaf0z;zbSrGzt{cP5N5kcF~+m<6Dt&=bxFkj9ZM6FQqQDP z)=?l)H}9zLJ!~x&Ip|PDs<%s*byT!9Of>OT4(iD9x_d%BplK(2>`K^F_vBST zt!JQtON~G@TA+|DsA>Zzfpu%}O~}K`$TDE<%$8b~ZWF8_?Zh*{qj_#1#&6@nEqfSz zZj;;CtK3efd?Z>fie~5q=F7LH6)Fjp2DKSN)R*TW8}fS{9h+S_I0*yIs-_n)-iEj; z|AMD+!q<5(xIbXW+{I{2EPWjz{ZjYuOj2f_LOw)f+8k~1uNYu@KnCd|^6t!^v|v~R zx5m65>m={|U?74d?^)PG4D+6NMd<_Fg2Q0BjET$YK(3;6z}vPkn0rHr_ItE3wlxRr zas06U)@11Jbz(A%mvR-}JE?nE6~3S;=HeMyt=GxQ;OGNUnR^zty)ap*TVc9!sl;tE zcWHVFqYtL>v?}DQ=?Va6CI9npJ^Eioj&*Giz#cvP{~Y}M7nJ{>f#e_gUW|r~CeX!f zqKQxjD;v{05E%_af~!_7x2n{L&uIz4qDNf^=ovYtH?X1g^)|OW{_*>|a;8|X&#~C4 zq51Vm(#>r<;|nNxGr%bc2n4)k1E-n1kFNbcp3(XQ-;;&#LaXZWfXY=>aoMScSFRpb|@x5J4?PxJ6~U zd_GEQwAHASKtS5g(C)}CMf_;?1}BpMmMJNhT6gsx21Xri`SlV4+jY3$;BT*BD?}JN zD*RpQ*~V0gY(iCc{at5X?Sl#fNL5 zx5@;uGf*ovz6(qY$*FWiqK-@bU>_q+64ruPmjw;go@1+3^ccy$nrk6tsER%9z?W62 zXXwdvVvLR2$hVL9o^$?QlHX9B6d266yIRVl_0!4Ym)I!nE_Z7Z1zX~V@z)NCF!|VY zw9a2$YU(DP?24=nozCdiukATc5*GkPcd5q$R%{B*u;45%uy+|Q2dPfu*dG#woNN7< zZg(0?>(e=yJe0e0t*&RqmaEfzXoV~B49#wCO&3XGmL~(s2;OrQMiZTQ`Fk7)b^E0c zoh1bX1P3x+W)GfB3lj<^xm56*&1JJ=9LvpYS)o3&n z{)7Xw7p^v4Iyuj4Ppw<(s**Hq1tw9XXFJ`dPnBV~h&iPrd6>SDd7anb$t- zVvY-*2kyivnW!_?@7rh! z2tW7Z1-4C&%?{iKx+I7XM<8Y5#q<;;6oM)CztzUds@HeRNpI&DlSWKIg36>61&>&r zAgMLClBaZ6xbj#PjL|e-@LL&7^CdObh9_lpUYtLEWc z?8AQBtt9Fu`;HJa+JF|8K|YhoBT}Qs`f6JC2r|EGlDV?dSTpAs{|U&5U9=0 zR?Mj4Rqa6>>75SbE9_4?(-JynmORkWFiV)|31nDZ((`rCuvK&vzq~&V z_3xkHQ9nnGpqDR$2Cp}vdA8h$kRGV&X+esBTS52XK{Xjw$WM5rmTL$9% z@W67ezTmATkMF@j!X|~gy{QO{#w*RbyKQplXn$1?X4#miLCQ=`*HgWoY#A@lKHgBX z1HB>WoO0Dh3x!IJ^&!r<>sA-c>9w8yO1U@eq8(g&aXx3@Xr~A{O*Sk``ZOa!CZUTF z55vAEEodWv3qq}@MIJ~(~=l}ct+8Ht`cn85kdviMAExfpL-tF*zERi!CLiP z*UbTFF(qKF@ZSS<|79{am30Lm5~^R8Gye^{w1iqD3if9SL4cQJ5sNY?ifRl@DAZP{ zGqtEpSa!x2Kk;7B@a3VuCR$l**$6r+keVK|eYL$WeWzy(xBWrVM_te`Deq54lcm#_ znUf?jI1pk5w-%VI01AUXn7`?-;2!K4Xb$PVG)xc5*6oMXfzL0kWqxlsagYfR89X0D zn!5^B%iDy)jLy5kEhv#T(8Tj zhp@>Ei}|P3Ubk`?m+z`q*BxoV>%v4M6oR!FK}2&7cL}403hlLEy>tz`(d=L`l)oOKjh)cKYGkk$~uM*R&4 zQj>@2E=#VKMqPvn_tWfp%r};4*|ujb@UIF|7yDnKC=u@-Fe|t< zoO^}2PwS+36tGSiqtU!nsZX)t3*|2)b6bL{2@6Nh)c0^}7jV5iVa{!I2Y+ zMZ(}lh}*%;vJ=&G_Z!}SdYB}ScSsu`q(}#Z^?@qMAl7d7R<2C{N-TlxC092WIV*d! ze|i@+8<%x4bOAiR1ft0ORnLwRMQCc7aL*s8B3WVm!}9YZ#m!2*kv(o|=oMt+^1Ec8 z;mqxFd<#TlIHZhvHl4_lF`E%;3KldSP zId@R1Vz-6fV&%^6kgDIc-2n>p7&op{tO7Eub^Ua0DQ6b8Ds0)xw#>C-csfofaWNHPu)J_)#>w>CSE_aE8j_2A)|+( z5+#fER{Xl6XFt^Rm%4lObQQe3k&3gJQXkx9+tVBF7NJlUjd735pX#HYrCe*@()F^P z6d~y90&BedbYD5DFuXpPK=&C8bqe%S6pPWyz!{yTaOC3>#dG zId*-;=Xfo;|i&1*?CT^eayCwWq>@AAVTbO!_{rA(!4RAOCpV0CHW9>Y%## z6mH&Nb^`45TyEJD9-WF0?hzaGJ+!Dm@uV7(726-IP;YD&?^%1s>`yctmx8d1Ym_6r zX3c~G1~U0l{*K54Wq2&hy2M=qXFa3gLu?`}Czx=JvgqFF*Fajrifh1KO}-w zh!F>5(?HTi%l#0c{mQL=cUSI9F8gDSmB|)DJkx zIC!&ixAj61xDc4oN|YP$U!c@dOe$fIP-)TbVU}bT@u3Z9X7MCtLi8!sYiYX}*I2(3K zXBbrCQz453sC(niDPnCUR&zLhTVSlAzs3{I5~({|BsU`kv@;u8T=P6_@ZBF@U+V(b zw_d1?x}y2N`CRO!gjI$KAiaT#v#D79QOn=LvCzq2?$LN0xdCRws~jky}m z{q_!Ub}yX@kB&@#6J{x*xHDzz?4w;5qk-#ies@zDD5jP8i2-0TXwgejXj^-4C4)>oMs#u zoq3{&>8t_K!9NiU_q-{cD)ykQy}pk0%SxvNM9}&Re=rv^2_3?uY!A^Y7!p(eMo&Yw+YC*W|0#D-LNi@D2V#MT=DZNDh)W^*xZzxBS=TvG!1YLB-;cyC=m@mN#>x^CMen_!aPkd6gWg8$;on6>C#4UwK7dMz*z+mft0=l4++K@nOPr-+`)TV1Et$SP&=P7%)M|sdAb3d@faHJtqk+@!s`?vhq<@eU|;ub0o~ko$S{JFvuAqrP*X1 z{T4GV;o{Msun;zH2&&n%aHmE#pyo?i8v>zjN5h)$p@1l-@oWO%f8Q49>s1-?5oIMc^ zn*EnGm|b%*17O2|ZeL|K-Wq0)pzXIbi`}oZo^XnE!6RJNPT9Oe8k{~{T{#$m?JB&N zKn61DkSxglP9BK!X)i$WJ#P@VfLBCr1pfH>yu01%FWk|nnfggK!huq8&$0J;XOV+~ z>b++@=5tLMGEzJ2@x(^uvB48^zHH5Uief7jGa*9R9-6Ot#0P;k2MdGn3yp^b0Q2E! z(2}_O@;Ds`_pzVtom1#fwMvCbr}>VsoonE6D|b1%Yj+X4#}RSxRj6{Z*simgN7TDZcI~>0@?}K0QtN+37Gzr{ zM{uUnt94iH8g!Qxz-{7&n(W+x z_{1`?>y4(-KSa9FC6z5}KI;r}Ilo|rsri)cdT-3$fZ3NnD-SAi7aP6hOwb90pk(-$ z-i;u6V&xlbJ;c+^;!6ZiK+7VUO|iP%g^@E|vAVRiZaeA$WUlNDm7v#gvyiS}X z&g7w}NT8RPYN$-}5elrlYRT)QBlmr8B=q|~7FmNK0;6rvin}PH$+)V8>R=&NCP-4r zo_SxJv#uH_>$E7F5M2mN@uPQ^DW+(qCh)(E3nJ=ZX5`x|JIQq85K+OsWcxjT#w$EI zOW2UaiR15QD!%)K$7yTUK4#R4XUc~#dsX=IkmdcRvaXOZ1Wry*Z@iTcJ?W}eA@{nf z@l+UM4u+a|FzHW?YcIGwY4?w$XeXv-FTLE_!q&|fW>hy07jsY?#fW$~ePjh>CofFV ze}%@JWy*YxX-arsiDvbSOW+~-9Bb6uPi`v;vZ2ajB-=Y>Z`%r-U;Bn;R?|dH)Dbc! zFQ)4++8n-0zlcIO8dd(BQfVdyTp*=hfy|^*7PfF(6)RhJigP~_Hpcqq9h=nBUF#b%PUG*30t2PL>e29_cM2 zJO9zQJufx#V83t0euhi+39a&@B$(j-poXyY=o~y97IDYO=m%AO9P4uCOCPF1W)8iu z*B7B?@EZ`s7m%NvGIZ2*;1~p!Oofk}%Q>0i1UCBH7^jx!)W(18Y^5Y3EmyH>;tVA7 zHmJz3J_kl>wOA9QF&b!a?n8cU>RZ=eOI7{`-^|V!PE9bqz-;|Cyqa#9SLtts{{6FR5GQ7n2DNd!g=Mm+ zk3M@}|1;gU_B_&U1ICsk;P1Zyg}-79Fnthp01k$Gxc(!$sK-tr_6wnh%>350CaWeV z{2t>8A}g(l5fS-kHmZU}ehuf<7L%|~Gy;*%!X+u`I@`q%NQlsf{|BT8gaqJ`O1ad1 zI8RyqV#GrcUFLKY-MFVUMz?*xN6KHHehK{tZC#4?5|&YbCyLE8DTVs_XSP_iOxI7< zWjK-agIaFKVsp3-WE~l?zpSJg;qFtrnW!8BmdQl{qKtWpU3_;5x!gekH1J1!~7X3X#R-8p-J%z zb+V(Oc|!(+lPW*SI-{q>OU35g*uwLJAceHI!-?MRDDwKdf1LLE3;qDHb;L2o*b@PC zm}EH*ha9r*FVpIYpNI8SwZs_0_qc4Qw|RJUsHQ6c+$2*m6bgT9008YKm8kP6Y$`pm zv2BRh+Ee+@hdLe(VKCc5vjr3FEkz*b;R zRX2i%Rz&d#P4~zJobe29xUeYk&Kwc~UjBw<6mS@%Z;p0g3E-4>GhpE1Cu8Dm>5oX z=-~yp2KEn3?pWtvHvOdQg+e^rziNC~L($;k9AL?1ey7P0t2f~PW?x-bxq@5=pHJ~x zYUYw(G3HUB#4ZN`H_ezxv0b=Fw`wFnIV^?o+CacB^sFtfTLEjU)L>9Zx8_q|5sLss z%josRi+!Dz$sY8K_yTX8o&+RLMZMRWbu=_ak;NsH;vZ@hgNY;8s3&iTH0vClwgi7& zcfYl*Dy?Kschw#Ct1%Dbr6?erv{v^tu1)sXGuPpm=EElU z>u6_bci$1jglnq`%3-G)S2g~0{`<-wd~u4|a~l5;QBHZjor2?u-3`)ew&>sEG+Li7 zV27<%zXGaAztdFquFVJkcFG{MC9>0lg(>!=30DmI!R|1M60v*`F2?tu8##v~5qc0c z^oyM8G>@fM1RG6$DB2dy$)pje$eV3R`oa@Ej(S;$-8V>4*i=Iuy(5#(fwp86>ohD9 z%Q7q)drLm%hf$<_sAvlK%)EuTjRT;TLld1hq5P10TqWt$`FaVm33UPpPzY4GLw<#d zCV1j?0r8xy+8l>wFBIH77Id4>r@GH1oY3v=~Q48r@%OE=#C zZww$O7UCy=d4vJr_f7Ku`fW4+6aG-qX95G(Qf)t5s}`T!E8?JN8~D*kL`bcWhu{cH zZJ1S>#;>YJW)h#J_@BS{CAYKT@|Vk-A5Z^snHKPN-~MaooF1?&$tQ6?W;{8{O$jVY z^3`=OaQzLlfT2gOmN&H>wrdrr(O@}j$w|EQKw{;pF zMaJyKu@aT4X&Ij8*E6Hz#A~Ld3^`!M|A-C!%*`@dh9vo}wAzBH>y|*Cn4mJj=t4J< zT|+HWMCwbtKSAA_f)z}INO>hjj7u5su99W6pxkPAS%|`w{TwHD6IaNNco^Hb8~If} zt-2C~=n$0ufL{)aIfxbTo|hg>xKhpdpk8m-l~AyY_15ICP9f14Im_u#6x-ZL@vyvC zo*)IsQ5M4LzyB>4097ZL75Bt{k+OSOv@o4k-=3bcEHrjL%gQB&6*DplpuT(rcIL_R{ z{AXONwi+L40h;pl|7yyA`YB(PO?y;f^bbVwR{ARBY7zfgS~VK^&PSbvA!PaJAvltl zHdXEbtJCjYO?IAmhW%nTdm%_WqBt|Q5gaV%cc4=paS>RB5;|82IO_}Ym>Tvsceow;Ex?CPxSN3WUQfrFjZ`<@ zD|XGUy2kt>W&*0nJIO`S1Cxf61>_Ru?4d~unEYNr-t;lnjat}n5K=SebbX}a$~AhO zb0Hmz*i2}QfH3+?uQ^(9pVmlOj6__NZ8pA=q`A|JW49=cZ;NYlctDOH^U)*}9M>MU z{R(<^;U#?KxaKJO1i0}{PbTU4-3EoSA+7#FOsWs_oTP=Dr1dz3c2HGQ=Njla_)WB7 z?tLEA8rDHKy)%oA@)m+A>{Gh%U9wQ9fb^6h1lvv{R!rT@@ENuJ@0joY1a1BUbI>16 zJ%Y)$IC2!OVsQ z1ci63t546T2krfrH`(n*`wl1~5G(uf0oU~FX8On5=RIT(>XMgItyVv_iYJ%A_^=_@ zZOz$kX|QQP8OC|TXIKq3u2Cq9yn98wd{Mr(%w#c@xZ6{Z+_{hVL`vlEu)WHpE*yb! zMJn}eLZ!BOFA^rh%$wi~yPkS&<`gkw{4Cf-BiYG@$$NuAzaQY;m-0B}&BqkXQDn?* zyHT6uuy`)8j9WNWMnj&fM;Btlezd}u5G16>g5%6c?9~j)Hg2>=gpV(#bePx9Ca1~t zQcV@4oT$cM_a%SKz}%wHe(Qjq=tb7t;p+4{6OsyGdSPSV%&7HtBiG8nga^r8sT~v& zCuXaDe^h2v+!m7e!3PzA3ode18Dc{`~SEBUp`#^siw*m=FEeI%oVJ$_`Y}mIVBv z?B!^cX6o@Ssrm^|I{&7V+891aKytBLuSR@QD-hSEGgl>Eq7xwZ6R?N;JkD|>+NGX} z4UeNNM>yB#`}0qr9u^iUp;~NFU~?pl3iJ_7HRc+T6glft6c_N*v&3r&V-ME3&WA#6 zv@DKiZPkY-h}*Ty@-=}`R7O~$hwHS*EAfFxJ8@s zQ>Kipg;)3pGeTha3M6XzQ?@Kp^wFqGg_TW1+oWP_VhZcwPGqzIu6BpFsmq)#RIgnM zj~n-b{T@vF{zjiOaFfdOXN^5I3!hN=ZENWUJp$8SzKNc;KrV-X30xTn#xxVx7Jsj$ z4n%6%=9*99yiuGsE@Zs{otj7KFC zfBq(H@fB901!Vc9k@GO4IYpaCp#^sUm*aV0n5;3sKlmb->l{z8y<#`YH z{`h$dp$FIG3s@#Bl~dGL=ZjKtrZz9XTstM_zLVue+ZXc;ELh0A1GjP8vhBDgkN3Gn zFIf9|AnJu6o}3H+Xl%xcQC7)+F+Ku;D~>E#OxSlq^5lgugByq>zAb~1QF&X}o!?}O zacA1k$zae}KwGu$yg!|qN9rCOqBGd93H>O_; z{vEk_K`fs;0!TGOQn-QPFpI$~F9HOKIE92nexX+107((L(Fc@H%5+@Y6|67P%(tR2 zARh#ESbhZ6Rl5!Cy=GM?v#N?u^K3>=o`;MzM@Crvy^_g&EG2I(?;`FDyR&i#OU>{Y zMZ@qJrS({dO9tP19^YOHChmA@cu1vti|*Xij{+9^u~o6HTBlf54*fll};Zde|d6Tf<(F)gc1^ z`yN7F^D4zbxiaQgAUJ~G+n~;wBQGw=xoSRPkC`(JMr0?Pr5_aiPL-)c3SeikLZ#4C zfpezj*x>!#o;?&Duk8l8G0xD7VI{;Kh{Y~t5g1nkF=V(6BYF`{T(u|-!* z_d`EmZV&=-hqNg8aX4}xzoly)&?Ohp527&6l{U{2AqUUFcUC#{qdU=uK%fEHX+Pwf z8IBGz1fiRJVf4^#XkRtVBIg!?v{4gU(bBDk3pD+lio%ae3l!rwN07C-puo6W{#GDI ziJ>)H<5|WK{4(ybsM4v~_3{p1ed{gN~RmL0mshuT9PvV-pwD!^^mrzR7LQR&ans*unJEZpOEiswyT#Mi19 zoTY&5rXX#1aMaCI+WBPgPUAsHl2Fjhd71A)jk_4$7h$IAb48?n$RHJX#1cnxIaiL`JFi!c z?|Kf_hdZq5@o1i2SG%5g}KY8^rLxaG~h6AFOEUKcvNu3+k3|-H7S)vW+aVzi1zVYRR z?GuUSHz?Wr^*kf0%r3)2H1l%E^+QpGV}jZ)+1>55FtUjlpdc8dd5s}KQ^+Eyb`7f^ zf=y8J>WjZ(s2`$jD0>eckJ-K`yyEDR`|MaAPJbd&&(6k+$s^Rc;h4t(dcKIPg5H8K z808n~k4 z^?objX?XNU5NfK$z#ni&LD4dHjGPn+IB!ANGYOFi(FC0BlHe3Kza&C3dmOgV#d!#m zC3&Cmc$_H2g;D~`W^6C6#E6{+DV1=dn6Z1&DFf%OVMjq|G5AU0Zl#^MiXNX>QE#4m z2x_ab9jBUS&(A_{^Psrt@Zhj2&G2Dobp`Ru9QB}(`A^KDGL)&H2GAb&jf=LZ^}+?AnhJcnAKA_BK+*L8(sr!odhI4Gn32^ zmCg$j6WG%VSx0o8K~Z~$3+MBq-%U5F5KSslD?n#UH*g+x2ksMC&*jOi1Gcr(Za%%ev8LIK@%a9}U zkHla|5iG-=Uwe8}OmTmEd5OJw1ue{iv2r2gTGJEtBXN>3qu()J?`*gX$Kt$qg=(q3 zAje*;8F@_2P|&)xXH+>%#GL%G1@ULE(Y~eNz=7}?5rs9M1Df8OqJOp~pH6^KM+8ET z=#tuSbb|+VSiw`}Absc_O&JV3ILqBbm z;E>_mY`GXhSD~hL5DfL;vdPfMW2p;gD;2a+CjghI1eLDy8jPU$PJ#8Zy-+-Wq8ZN6 zG%J9D5x7#c4|UMpy2c$UGP?KxGn9QI`4GH<3?{n|uWXp`1rK(JBAfd=IAs(ghnmj|RIlBT$INJhQh_2lS%@lVCNb4op-CIDxIM`D< zqN){&lf%yt)lW)`ry6xB%g*r@^vjhLdkrh>GbJjPBW&R!l_dO`_n%p$Vp+rvv*00_ zl0upcakkn8Z3-3Xon(7Lt*g!Q-e{rw0R8vVW^RC=#Ph>9DF5}#lKo$N6SFPxbKx86 zp0#e?Y+|CO6h0B?L*8hM=)=MXFTfjox5siyat`=Zd{EOCnh}PZ5fnCE0(f~+Fm2?tlwu~_R()d zX4#JBz<_hh{uM?slcYc9R$hr2&E@(`IiG%iu6wYIS6Snp6HgDvrYrIm zL$u^75nPz*N};3mVc+j3#;|xs=FYVGwi&{_7p&bX8b=`jFm)aw_Ws!v20HKm5J+9$ zpl0ZP{&>EhQvYAY|9=TAiHht0;Ck-#0{i_oq5w?@YjSuz@mz6D2`KS!A)*2?1f_y8 z!EF|kbdDC5yTB0J9RhC<2*5pG(cdp*0~c#dV(`F8E*Ghp?8g@qZ|l#~y1y!RvZ64k zjB*U+hlQ!c9mR)@wPxi9luvcyubgp+6PT5ls4QE0V_;#ayVVTI8o+W5L6m*W*gt2= zt{`~busNb`&Y)ALph($}eT@}_`|~<*f&%9b;vACk7oD=fgRIsa2KsMGNIST<>dE75 z+ew<+H|h&;!4wt1e`-4^z-(>f8ASMqv<`V$Ok)lTr;d2E#C2naaS%un&}2B266h-f zYa~}s50#W@gQ>(+8Hl;ULXEK|N)wX>dSj@_mwJQV4$T<`253De9+iYrZ$$j2Y%}bcyU&nYpF;-UT-Fb8irjW|+M&>P!o0nMhwyk+H#g|;1g<-bx@34Ev z^3xz0ybkRv@OE#{E#6u>sh=UKt~2-Z=BtmeLAre?@wG!2vS2NXQuaZP<-)1S@0r%~ zR_}Rdf z*gqERHG*9cCyCK|iHC4v9g6J5BSqw3ZlUu=(<16@{_KHy!zl$2f#~C+3d&ZhTZFd| zqtz=RT?+Pk0i_6YvH6jP!@Bc?MneyO3P!pJ%9hmzu$XRC#OVp*)Fc4W5;BL`iNi^9 zfcO665*Z}Nvm5wx+7AEQY5SM79D{^X zdK%g0tF*HDCD)jD{5qz7fUe`S&k1V-#W|>E9bP7{hi;vpGq`W#IUsuTPAY!=LBvfv z<{DOzeie6#Q@d;kofXzdG)#!W%!6dKU}6Lo{m8xF(hnCgO}?m9LXmgoK8rHqBj~iTrLw(`VH9h% ziK~5zHINATl4NWYjVVH(zn^`8DMMJPH!Mh5OVv)WAJc}Sr7$;%hnKm57gFvzOZ$INYM+6eRiIo+xkRivF>$992s*133TybdNV3k|3mjcu`#)A z5ZQ>{(m(vPHePe%78*meFmknE(go8qTAz825Gd(BT?~k*hXndhN z%G=K%Vb5U(?<_g>f`@Q6S+T?~L3^rKr7#?K0Ii!t;fWu`i)WEEvisk=71x=1QTR`{ zYW+FJ|0`ksOVn~!*rWmCN9UeTvQ{O3{MO{{%!#w1Z6Oc{5Sqgac|XOJQv7*Qvcifc z#vcd-y8(AYy`40((`QXhwVldx?25O2e!PDL;X{$6ai>ca1XZLdqTknB>do*+6s+z_ zl5ZWNQ??zFu%JB?b4HQ8mJ)g+QjrM2f^MDHEUf-B#%Yn`KYJkvA3kcp+&`Fm zm^pPegpr_SvZs&=j|*pGP%3LWHH#f0RMfrwUC2Cqn73)@!2&hIJwJmy0M+#JV1PC( zIB%3L);Fen7URbMJU`I*$YjFx*{I{SQI9}0S=d)$^u;gF%kJqKM*DXlvqdgA|DiK? z{x=~1rDr++>t#@}(z>~@tGiqnw`AZc5N!?~Kb8r6&#Mh5V!^78Q}Tlf0{{2NFP~A- z4Gfsy7bJ|1#~19!SsRR`q}I$rqs7Mc2Ge=sOZ zJ5NN)?$z8$3Fbs=6$rFUM{u?&yzm%82`r zw$9)g7N9-1*bj4=o@r88_|bdhRv1y&aefQ?#iS0BZa+z5nDY|^WeLZ-5R4`wI&7oKD`}T6%jZs~=iWZfVKTIy zO!RV*YtY=8G$=8j*u<_8s#(7RlyZ)r=5Lz$vOdi7EG?pp!Zf`-8KDn}EnCLR^p7oF z#kvbjSI1l4y3Toi*pGj;7}HCn$VY2V0)tkDc>}-y*8O)JE5QA+|51?tuoeGb3-Vt| z*3T}6#5o_bc6$>^?JQekv~tJNMa;mKMVK(xF5fG zB{mpQx#39YQn@KDD?3->o1c?af1Rtr%Jo$Kx zIuHpySP5|$xj?evm;j{NZbS^mdG1Mo&lXxw6c;>pRPOz(-CPC<$V73ooMeH17LBm66cgsHL)w&T6j!;YX(OKEWznq5~Vn5}?%^;n&k9H%6uc5o-x8I+gFmu!b#@3BMX-i zV%x&JbOVB+UEX_DfpU$akwxR&hM^NPjYhAS1&c6#@>08qz3eff+Xyahu3};YC{J#8 znc)g~WP2lE`+k8w3urP0bA@Dc_o_S1*abreFep5Zf zNT%ewKc)x%p~6HnJuBTzF47@wuvDq~3Rr-e>z0WcQpZ3o|EawnU-6mApPpsS2{!F2P#`D>1)f|9aW!0NW+5wP0&_XF z(%Z%3AVCfP?1(-|p#ppUygfgbWsEF4K<%R!8|)rat)#9`&)M1CzpxEh2MBZ)0dqw@3VAystz2fv?or?fb%y=Hcbb08mDD`%CvzioX6mRd zxUp02lNEqcG1a*>dZo5F<(gnqV|R_rk+DUXbLC^R$k|Mh1$neE-Dv_du0#C&_dDu8 zK5UXVHS+?0ux$QsSpMr4{tq7gDZdQGa&g1Pt?M=SKQoX+s`5ezAh{)Kw_aGP+ND@x zEdQ57x!O@5dx2RzTo@Ql*;fBqe3oj^Gm)c+VJScU4;XOuzmK?-Ah?e-3MJ12N{6J2 z5+UY23ac4VvPKs&Of<%g;^i!O%p9pB!_;aEQ<|kyw5O6QGLXVNZUc#wFn=iYqKP=3 zan^#7VIhQYmIp1g?ig0Bfgc<%0j_dTN3)>R$cUtQd=6B!JY=LqEqmmELB{3~BzLTJ zAQODB6XG!QheDkbynZN@DASrJ6rO3HD)hj&Zk3{&d%2=4;#VUB*URstaW%fkhWsc+#A0t z-Qu1-C+KLIua;L{8y*pi`ZN3D)Sev0TNJzPTabTmg|}N~6PiD+QK1I^f16(acaraa zWRy%UFi$1nx9y3J$@Pv7Ck=4pQ+$X39Yj7nU~nP?{J*_JaaMZxtC8(kfKPs%*;#(3 zwkvQ8Ry}IdE>DU-KZhrGQ}#~9?`>Awr_1$8lC$jWsfnrd*Dl*G9+0e;*Q@tmdU-A` z9MF5m=xP~~mG>piJSb>#w_p@B1$zV(Ut)uUOjn2$E_2h`3vCHuycjny-BQQN=O2(j z4zG!z2e;+WOMTT#ugsv(Id2+ij?5^_ryuTV_CTL2mG@XM=tH*jFO?rGncFr!;NFet z->GNZN*9Itt|uQdKts3f;JU>CoPU`7U2nY>)?^1aL6PrlEH(uPJC!y>8LwUM!AiVi zOumGV;V|g@d98XBKM*K>7urT6MEXqbMBkH^xG5aV8uxI0p`*y2e_#Ni-k3Unu>w(; zlTV+1`~gC}M}sce^I3ikxA}?#Sp1+6xMgV6nSCt>UAS#O8Rj)xv9$T(us?v&cunW< zkvz7Yzj598(Dv^h-d=v?g;5jT;kCPmgJDBv4;d8Y_+FXhrPt%`mN?GFoDt(&Mw)s2 zAjG`IK*?@@MdSFM`#bqTlj&p32PKU=&|S3DUGmaA)lI)6_Ep*2y}y0_!3!z`t_1b% zd-5_n`7Md`b$rAt|5-ofL%)L#VAET3q}mF?8UT;!+-4Ub#;BJ)Q+l{0+@^^RnnA&^ z(N~}mHBz9GL{kUD1~!Vw4w^B1?GUcjJR&#*o=kHG(-Ok-$hc!LtBVFCW>Z4UImkx= znj0hRO9P5!$Bsw|IS3EPrG(b+M8EOZ(hP(%P!x?2x+UvZ1e0Dfy6~N6fJVME{m@^{ zl4I02`lY|?=2MRb*i{VGwO8A(9Q=3HbQ~M9nEuyhU>K ziS9)h25l+)yrv5D;d)a=D(sCECCs8|jxcUg6Z3-rpjDA#N1zvZ<0u&UbYC`p8 zPPJ@6D&fSST(n6FuFTG~`AZHKizj(Ye^o0-o!i)E{w~iN4M1vR8}(=!5X(1(g}GCx zoEt;}T;G?^?|RiHh5477+Y+eJjJd~R-%j&Y5zY;&DORqH7c(JX57u~9ut!57Gb3Ew zTPB=f;w)cFD|mtCs_5Hg6Vwu1BBbj|-+CGj!p>#SivkyOP+#(xL-S=&(|FQ7AP8#7 zcX-?!7JPb7`0zUGWOQn1bE_)IMU#d1#Us8ci@2i3XB0QB}C?d2HL#a z(s6{6yE7C+i(LiF^orFPi)%@g+!O9OsUxJj z4<~BjFWrTTePK}rVFUf7<`C)X|B_lfU<)PZO(vLV>=0|2>qhe+tw7KKT%(k^m)xwj{OD5$_` zY@u1W3!KF)AcKFS8251t1{x9(VxSAAi$nt$cG6l0=V=kRQ~HIRWOuGmmVEp1 zi~SQ^6mXrc3EC6*`tfk1dPI24P!=8~JB_gf`L;|PgQOR>`4i}*{W z4)2&f-z+|NG6}uAAQpm%ci6O;QV4Rfu6(rcNGifWe@{+W!MUY@oQhKBTptCZG~)_C zRE*qQpF(g<-vZLCu8=`nIv4I@d`v!S5J5nvkNg>Po4Hb^6T#_11tim~9U)b)8t#U^ zt`H_~6ojI+bNW_M(c6wf!=#=W=iHv?KD1{41j|9x^jAXbsKw$7AQoZzO>=YQB$}Cm z`?Q|H?Dc_^$w>3!v2`r~@&n-Cp}I=$&**;OknpaQUNsXr6f@h$zz&4&2Y@11!5VG_ z+=L1xErpGd9%UL4Vi;S3tV8BJU1GS?2j2d$q326qhf4LNq!Mu;v$%P0Lx0A!%>K8+ z-%NC{^u6VD=;lcC!N>Mzii=Gmy6Jm@Zz>-&%Gm339n{PtO*7P*k#?3|zAkf(M0cf{ zYv~^u*?0M7kFr<0%_o}91Ird(*?!P+mi2u)a6E&6+2yO4Ij>lwipOe_cP>I>WtyE> z<^lQubq3;aY_(>47P%r5&x1UwtQx(&fc#8B)F@IUnSEHLc($8(k!*{unmS{l?*jCu z(saYmXT$UBhWqkAIYmA!GZENONsJaSQlJK+te0ZgCQx?9Ty^BqIw2%zPlFtWQgK!T zf45PgUjPpR=2%ATSsYB2u;&Cnk@gI%f(YwRT94dpa+T(Bsmgg~O4F)DEUWT3>3dK} zfTX~)G}4n#4yZMauVVSi>Y`fmcM!0dIRa_b&qRF?EH)ard005}_FNB18i?-T)>O;` z*kK8j&rnR;N0R9T zK~9m)ImzuE+46sM_`jj%BDw=brjsoirLaRs0uePpbz&LI;Plzy=2G=CREM9Dq$E zdOI^wMCx8aV16R&QQ>k?1`@Ej!*0}I)VGG+a<`AXHo8S~SOlUy0!I#|ZVlYkV6*q_ zX?WyM>;`iN1$s_WgO^Rv|3ybl07u>kXc!f_jQdLtV{L0*8X=0HL>8W=6z@R2NgN-e z=*a+_%koAw7epk&%ue&vC~!nON|-s+K4uy{5xs6=jQvq_P?H*gH(n$>N>UHPUC-2S zf${e)ql*#M57995B2pMN&Bj#LKiu6ugkjz));r0zhB{_rzwhbDM0aGlVNMjl?*cQi zQ`zJ#T*4u-V6?cgRBV#G{| zW1@u8WK1=4%OV;hu<8hD{ehGE@^}Ut>p}kFYnqk$nE7p&+g#QOG^PDJ-z-T%1_|JB zN5Wr2#3BbP)+S{elOR@*b!&*;4QHrK2 zm|azJw}PI#lCg1Aw8DtSQpf8Cm|C}R{JhdZG*uA|NgA3QLVW&w7%!@QoU}Mm3$|0w z`pZ_(r?cjbfIh?${>c<{ZGFn+#s2l#a)kx{q5zFg0CMFGHLLIydFuSy!DE;~lQ@o8 zM8gBfreI9_hlwoC0$nJ*G029$572qs0hH5qBp{x#b&HgiBe5e59i|rDf7{SrJE{5a zc7vL05tyAH3dG~7qF2{#Zd3v3wpqwwt?q0=mL$h;{TTyys4jt~t~XF_t>a5m5Qpg! z=_WpK5=eee4D?75(jmQr&fml1vv_b!{$5+YV7~2TG_y-{cJI-XsveawXu(X~P(_QN z5-DP3uOC1UOAW}YkZmfiTUvQgkH;>fOZS%g+$11!!?3qbJ9uGc-%VDE9qk@_MJ83H z2Uh?={amx~N$;=ol}QhfVEJyYmBV3;x?T;m(jx8*KyjsA*nttmu~XU zG`It~V2Q`&8$_sBKL}Plr=$3=N*<-G)jO_-l5&COTrzrJ@s7_NkYS;>+^`8texZlv z>MkNiKi?NT^K+OX(5+`CLxv2Jx@EvMtIG^Q3D-uajz_`W7q->HZ>tWIw!c|Iu7;nX zJl)^fB8e7R2Zgp=d=NC|7xQ;hM*F54wA^#v$R!49+Cy=-1@!G6=Pq&+>@W{&hR4;F zeRz(tg%mhmmv%wsTtl`EcXhh0BHu!~PT$Mk15)%P<6#&a-a}IKq~Kv3Jl=y+^bDm& zC9V-EcTMMbpdVD-vy8_XjuJK96rCXmx?&yd2Ji1i0BXkyr0PQ0KHK9|^qjVozQw%- zBH8QweVB3qaBHY5nI<90Q92wlfRx^mg@r5yjl_b?ymg6g6!QLExaV``5r?3EBG5s? z3FEB#LF9cEUT0qPA`GNxh$#wUkBurRP6)-UZ-GheqqYskMz4SrZ7YSUe~DgCK~8)J z?1vDYc;dd`DU1Er)UTL3;^9wPMZGKBrH1pQv4wfsl%CZd;sSsdB!h!ThKt@rQm|0b z(bAkfWAPN;^+|@yP?QAm$3xmDg=F#Y%H?koj zVmABy?$rb7jby1jDRKx`pNO;ZnNJh(r$0+C0{RK1qRXHuYkUnx*6^+QrYrRW34n@N z6PEF-SiFjuM#ckKBIAK>cHJ0jR}iszR|}9B6WTaWe>$>;T$r|0EE7aYj`3xQPHGXI zWsTyeL1(a`+5ege&d4?9dRe|#bn#4yw8zd(H#_JP1$Ak>SfRy@@ynd;dh7>F8x?0i z?u4z0r>@c6)|bK*_x1CfBaIqqW9eHEHp-J^Pnix(P~VD`$^KH5vTk4joxjt8EwqbY zTc$@2HLoa*oDZYBe0_{B;SuNG#H~h2zp!$NDoxCpUX++Ew?xAB#aHIBszY0qgnldJ zm}Xi{zo1r3AK4P%MNLi*c6fD&_Zp-__UwZfPh}BbCP`mIhUvU3h6WmLzu=C~9G_xb zMZ1uU&zzc~Ycwwy6aOfAWZ{@o+8ciOEsw9?BY2??I5*sxH`=AYopyonR5JS8R5s;8 z%0;CvVDDJBh{gY8vviVaHMj-Ug~HwK3GOLvR7Q_4fLA(_qYYOI(8Fo<=441*y zPICh$`57IFQ-Uy~Udp;j#l`|9<;u*l0UOcoI#|1iB^gB8`Zo%gUhjP=bM|p6SFi?@ z+iwz`+x*a9HfHv70+n%|p*9xb18^kUe2}r330$cfiA^gDNoJBr3Yh0JBFkm;I_Be2 z*6fES#-0ii@zFS)Gkq3Pl}QC&YT7dlZfiI39=#iudH6W!Z#cFC4m`bl^*)3e!tthB zedZHv(YzNB&CQJD3AbFUJ%(-Dbm54$!choXT zvipw_^WpLg3QH^VX5^z!lbX^FB+(#qL!IXGEiI+2D@o|qo&5OMNF?6msq|BoE*4;~ z=DVfxJbu5zd=BU8fy!rAYUrROv?+U}s?$8~dd$f0CZHHEk_)to^H) z(=IeBM;AN`Bv-RwX-oY#nwXlKQ#9&del72=2K?#(h>mt}hpt$%jpksa|HFJbB0|BU zZ?<^wl@|oKZUSl5>U52`EDx?(+T& zZ0RU8Aq@LHKt}}9$RcH}-du$vt{I|DeqfU+Mm)NCMkI!>2o>)UZFX7Il!*7hKP?rv zc1psV!lLf)=3C{Qvc{6um6_KsPeyZTc8RXYndIh};nFecy{mV3*bV5alC5i+z6P=9 ztjHPb*6F9_6X(KE)UI&lYmWejGxz7jD(Vh8dQIzO4OWpe@PrNJ`FBU`QD#x5jfLsV zfnVcROyjkAL!;=~l$aEzsQZaSOpBvQ^vy1tP#%EkY69iBwD%Yt(P7&Roah0Fe^k{l z#}Nz58$n}jz9No~+UBB{RiV#yxo@vbaBbMwVhiV^=!X+($}XH0jLq$$TVWHl)9-5d zwwHOJ^PnvL)9Q>KHr6z!Jah;R?h{!%XJzTH7l=0h3bj3BuWB&@j$29`!8d}XExcV6 z!HXcnNatKd9Nt}2;r()Iz!FCQ?l`hCnLkKzN1Sz=>xfq%cO36RoS>qVajFVueWXjA zc!NglG{HJDB}kiReDQGi*HQ%*!Xu(8SY{8zlgiLlBzG{jii*nN)yi84=KA0APJq|- z9N5qDrCC%DvabM}yCk+PEa-R2t8xs0B{?if1L1V(;`1I}S+no41FsZD!s$>S)tv| ziA5g`R;QfSh|`JFrj(2j&^i#gOlOlk#sTM zN1h9L!Iq6?pSpIK3(*mXgS4QKU}7FU4i$`ZyKpl=U^F@JBa`V%QA$1t^*`9ih~(BX zYrFG1aNpE%0?@4VAUCqJ!m`-hs)hzlM0|ErMtoO_^(2AM7M#+wMm7X<_gB!p9$okJ z`Ndk)R32UrnA*52=ATG_#9bI)Q^ z`ywn$TDh{b7Lq<}T~=f%SH}4v{h%Xz9YfJRX|wyx)V)EuHqExbJ`71&FJJtpLC;b# zQ8(f2Oyo8Fh10)Om>ZXfjkL(ygRWs30D1_e&@C#4Va>QX*LXhCWk1Q?9oldcWiZLC zv-0$u{cnWlmz}aDR;_rx9DKWps)iM#2>aef-T;)VUkvWGV)Dz}{LE61yLusIev&MO z-`#6sKc_X^xGV<4DtMEARb8gr%yBrvuyAy|)_Iht9Ib2uMFkh-5g~VJmIPaeSzsn* z@fXeYb0yaMEP2*a(M-0Vl2R$_FOd&+F#oV^H-t?0*iVH=&8g;VA~Gb`obn48vN@vX z3<`PY(Q!x0{>HpS)TDJ`LaSRPlQ|=4)V95aR0)eX7$3H;w45{7P1h%9%hd&B*F8c7 zTHaY-((QL*vSxP){Cx3n_um(1;NrJ1Qnec6tY;}9>LxKe9hdi9;ilzAQ&Kd|6u;@9 z&mpWY4n;pA15r@k-IVx4=FJ5Xoh@SKQ6b~h&;nSR%AMS`#)=P06-K2j37ha{8kQuZ z=^lUN3hA`k7v(tXHr9QJbV3hVyL~tcU<#8B zw81y?S)h1Jo^ZCH`Ey*-NjZ5Ax3&8i(Op#yteGP8X<>W$v zx(E#EK%C2}nE-qyW{}M6fRBLUwnO`*E@8W{w*#I#wQVckj3E$uF}CZ&`%#cZ_3f}x z0d)gf`9a@s?xb8;xI_jxQn$>Vbqls8R0@plT?8CHVD816ZHTr z5xH>Fr1Z2CnFaCL>b0Ys2;#x`0vWmX*97hL%|U3>dE&aom2x2>WRUryYW6Q$lsXfR z>NZuGvX0KKUYNK*-NtQ>*(ipn?AX4sfWLA8rqVOmDj|y`?|jRn&x2GMMR4ljW$dX9 z;bHCLxp0aj?|!WY>E!3c9}Kzlw}4iJBQELFpEwnKK__ek!j7=B9AGzz@^4uR>O>ft zDXH9Q)A2wK62IpL2r@LH36f~-{Y^FQpD=J0OUZzRMw##ugOaaTn7}dcrS1eRN|19D zLnaZ@-=je<^G=l4Cdi`;#Z;5sYWJ_-?B?3~$<*K?mRS~Bp!_QtTgBT8-vTYte@F*V z_4m|{c%tWv-3~^nx3NTXITuBX*8EwFCaJz%F0Uah@rgpEPNp%e#G0COfr8{n+WFew z+!3=jNKk(z9qc)T zUApuv-ifUule~OEKid-VW7Iqpyznflop_iC-q`6TU4Cvs1C)t~KI&&wpVcLW9a^#z z|DTKyuF@%w8Ivag{0(35>jnfuymSD<0td!bfPL^!!SZ+0Y!@W+psD8mo*~>7d@}N13!5;BCjzLK zwYi0(U&q+q3zc~0PV2=-*`WtWNr9Vki!(6cSOFwj+BF4GNlQ7JWi}(4OCaN%xuEPy z{v^th&a4Zutn74z zg-R+39*TyTp5X z{1?uq8JQCo&B{xwLpBp9!Eh~2OBh7-5^68xjfMccs+>x!C@c6pC;@r!WGBIZrJ3J> zm9TAvl7_E(HQwmJ?}9v7e|IhLkZS=`hPYs-8TyNA6FMUxkgf)23OoCBh?R3VO@vn~ z$40AKfH;2pUvyZ%>v{BN&eUSf)sSA7c-b+@hwi?ofI7JhFI|xMa~9!Wx6rV46%nCY z%J#6e!^t${StH9xsOYhPLawTzPYvawMg*T$pr5IQPRRA*oZ;#kHSJ~HZkb$*eR?E!7Vk6?;tYh! zgI^`EZ`xT1ZkY#a+qqktZ(+!@IpStCLTwb9HJT=q(ArQG@yye;$p!;hfvzn{IT_Sr z_n{ZUx9g#MWPc(d9Osb8K#mZG{t>K+g%B3bF1yAD(vn6i`VpmVFtZ-?K z0E9T&hj9({Ozap=SeeIWub{&!q6e*Ql@tjHiUycX5i%W<=Ed@)QwuVU%WqrA5tL(> z1g(|a(Wf@Zd5!TXmcXi(1c@u5i7EZ@3F!+AIVb3Q99C*sBB=6@O@tX4dW6;Lx6=At zIfh*RxoHMkwnsQY0YK!>F-ho-p>0l zHfGh@j#&a>)gxM!PS_CBCkE;qlP(l{o!qp8dfbwr;Q_+gl}4(0W+8faIs0-%eqsyI zi}k?7oWc-0OFfyG63FKU!SZBx8L1Ui-6YVcY^D1pn2|3h64eD>H#(K^NE>&aa>lIdZphShAwZ_K?e$JK0Zl zW9v182nrtvj z&Ar2Y0x;fafbC#Q8d3b#B?CY5{BAE_!V0H1?SDw~c9SgQ7IGHMxf|)g3Jx5EYMPw@ov;SMh^m%3Fk%1I2x1#0Yy*~#XxtXO%`nyI z3TtdF!7%93kGFgl@?!ztwO{l2d}xPLY<_zKTdR2$cIHUO` z>vNBT3s{ALs{l|tR1;mqas^aWKXpGwN~uoIRj>8BmmRT5L*@m33!0*|1e)#u@}<`k zo9B3z9;Qd!WVwzbQ)ZQI6McXyw6j6S2E?q84}B66*jk#WuGEwBHu z%(*L6shxd;pn95p{4LTLdu#w)D%h%nR>%Yf|zSg=}hqRA%&-~)TVf>sMI zG$8tf0=%;9ASy5;3mm{LWIDdU*w!=*cPG8^3b8uYmqMzwy5cyh3fnwqOqF^(%3~Ur z(@)?v_b2^%{}Ulp6wc$K0f>|Vnv}{5dhV`!tKjMFeOIs1?1$BOzMZG^jtaEv2Fu3s zV`D#|hz`&$#_uryPQTdqb(VHrO$56aWbvT2`Js)Fe{gZQyrpma>ECR(j%j;==S|w6 z!5a#tlD8v@4TEzsms3!CLvNjxSh}9Q3bw->A@J=e*Upsxef!Yii00}Wn_V$B52+1e zq3|-D!-@R?gqg6BYe23SsGUY?6`60CfKf)VjE|$j6nQQmcE%o2(hsaWe7X^Y!HqQ5 zmRwjf@LK~suq`x}zg<)S7jIPT&jso4%-AENxSx0G!0=u%Fp`gUNt1%W*(J-=?tbB` zd_`DniGE+?<%>%+WG7Rc1E1l{Ctcf4QySbyY_k&IWq@>B`WptDl2_e+>Ku7sJ4D*4 z(T6_ul!cYfYiqTyxRD?E4v;>vO7mSPTcUo<;->Y=NC!Q0Fa~<=TF{6VW8@t2z zFi#$l7SoIP$U#B;1;fZm9eIocJ*Woq_B2*RRXTl`oa*~uh^O5#^``Ir2YcQ4HjN^X zK?*m*SLxxZ5ugc~gAS>DY9BbMF`Rgieh-X#J49rHsn)xpDTENCy=TZ=(p%YMCVSdl zA5;yABkjdWXl)-*Q;~@a!*m_$3e^drT@$t>t5zUN0T5FTF5vag@nfMHe#{eTXujhs z(1s0>Xu#!9F9ZX>g)K;6b$?RT_>-y^=Mk}i&sS^WCTtB=01|K7q+1XAXu97T-UQ0} zU14gHcO?WCLUH~~@fyUvlv+X+%7STdN-xwwqUvO~j(1vF_LeU&O7foNWh))pMf zKX#Pynp4wHOvyWqZ(3ITDKU!=Z-z1V@Ib%U<_&3HxzWpC@3joRu>GKO9MD(`_t+tH z!(~)Tx&1e5j*U(QE)!#DU!-=!vHW(;-Ewxrr!~P8@?v}F zRt0Hyz7w8f~W;CZ>V4Q5Mkr3ZzRA+sfK% zDsT0Ii6QmzX*duFQE+s>=;X?UNd()ncE6Zd??RjW0AOvbW*_5Dd+_VXjoy*3MlnAL z6TLDjuypGOpU{8*m3MdSSk=hCJ}p`h**kqm%NWeivDXTv%}$aV-9&a30>W&X<*E`Z zAW|prW{aNiVAJ_&Ls$J5ozkH0(oa>8?_kGtVUlK~4Yl$97p6RXga^3kJ-zZBtoj5| zZBmO6(Ke)vH&)}_<;+xUNWm+EJtH4#^}%p&^oP59S}wTVP2z<;0~Uu)u&!iaQR_AF zw8+s@aTTatdBY4tZ7y93B1K5Y3t=~~$t^NarD=!pLQ#N}L}^I~|B zimY_%2`mSHwp~EGUU+*d+B;VCp=b6q-VM_?dl&A>Q;1d{f&@~rNkSaNS8G?an>}0A zt>50&{LW7Dovp`4PFw_;;C)BjhVKBv#z~WM?U=i6hS-0hjGhzCrQ{6oJu!tw8t4oP^oZ&)ibRg9Pg z2X#^ivAd$8~7?G9CvS2>=(%v)A!9alCp+>g`e+HNRRajbibi+2LH}d`2~ibw~%MlFob^r zl)uEfN-_b8vD~_51zdlf5LnQ*$;%YCAJ_8<#+Nt750s@uJP2O2%^ZdUq7`EK&Mf>T;g>YjU} zEILOsjfKs|5lG@oA@A2q+EB0F)!vT=J;OwKAu(2^*;Gc?3n9!JbPzNg!MeKhauJR# zMfyxgk-LQ7S(dmVDeD|f z32>-aY&tEplxb?c#B~C@UmewM@cfFKW-YhEXwJDjHN66T(9hiC_&Xsjf^xQDNI!r` zjwncC@O)wxXrbk#BiGdBdlgJ_%hapHO>wFtRwbAs-9K*!n%X0DkeUCO1@3QRCY zJ^m(YK=V)dYZ%@ z<)A}${x9Pz8HjOUse(HIdUG?lHxhVF=nj@3-UpzOEVo?UqMUrw{N($@Q*^} znK@ss#!T!PSy`FP^KY^Bn1z|fZ@fk&d!~6MgTZTsm7nBa(irZmUCXeNwO8kiL9E#g z6Uq)`Q6RWG4(Vc9Swl(&cx%5dwf}_ zDw$ZD7&+Vh|AIt{-&3T^@?IkLrKymdoGrY{p<}G}LEO`bNB= z1fbpY!rc@MNKl^zvc?%Zx=y7hj&}QXb_1vnWyQKi9mNl(#X3m4r#;Xg8ffPAXO%ho zis#0W=Q5~(&0Vmmu|E}y7o%+h)yrId#UBvU1` zWusdC5jMfJ=9}aly3KI>m8yBAMH4D8MD4ZWAUbod{z2Ay{Q~^o3%R-@gA)H!$ie@+ zkpJTG{RBS#Q4<9A&}tz!HK_>ajR6%wtiP-cH8QYR2x=llUH3ay6rBGg=fvNf7WDMz zVh6zd`2CdUxq!;Jm08o%y{5OEu2#9;-={Zue$%=FBG)Pi;x7KmEcIBqlYgFpC(lqy zZrTE0@H8W;l1__G=EW$x`PnooNh3@wv;&d+v|GzcP(X0nqNv_LGH*PN*TsQ4cupjM zQ1+CZKb{@@Jx9b5hM{pu5+4<7Qf|!)a=#Sy3wSKIvDld}MME@d;rPMgvc4-KDoXN! zphz$AAs__F?oKhp(OR&b*rpGz`k){>n@!-V0*?+6{B4vx`h;J@jjBPXBSlU0w!)FvTpwK^QwT`&m(`yVMj>t>|KMg9Eg$VvAJXH*pb+yr7Bu%D z5#5RIF8N4Nu9ok_y$6ew+j@aJPC-QdNY{P5x)CGJei zuH$*t*X-+&&w;)`#hn|gqm7Bu*Y5@IN(R$aI&3o>s+d8(K(E+;%Y3v$kDL4h^*Z>! z3;JJ)>}bW&f7G=;CjJOf*k(MqOFQ)3N&&PQI+;LKp)f!g|EI zF%B0%gef>EE8?4AoY%~fq)ANM`SRB}Km{gY28l~?{qo(yNQ7#oHm=1WZ@#B8ngiO= zNK1q4!7^vR!QnX1b&THN6po1#egjufOW?2m$1`M*Z4GT_gG~{acWo%QUx2HvJ=nCb zO_Cdur?I={$=u}eJn1lnq9<_)8Yc7 z)bj{6U;d@mm?WKTn&Z3B)JFmn!vH!1XM&^XY-;^WP2scqza{7l=qG9aafy}u-{t%l za!)}3GN09f%!HsO&xBFSy+gffj}4EU3Xk`d10v$Y9QAHS%&)s=tE(s zg-QWdF!Aatla0x4?eoL)`&Vr&4VaSpWN%+aAmNftlC?Fx@j{h!GaR~PbcZc@a_WPL z`+Tyl6mdBP;BEyuui*#_o{$Sw(u0H(b-0F6>4h}nWiv*l=K=z>6AVVg_|<1qnAQg; ze&I}*H*TW`wF?h04NeFE^pyyDL@VwMF+xxh*~BVup*z#UiSVZK#xf@wN0N@Q{(5Qaif}qM+2O+F_ffX% zOCTbHkf#_KRm`(~Ef=Fl7^1*Fb-Ti64J0SvrN#-Fh#f5K;=FUYLKQ;E8>s3g7XG6# zJ8c!l-mmGjKQ6DK!5RnAkxUN@S5QJVNNX%qT0R12D5rd6!#i|^=uxdEew+2`0|^Rr zSd)-P(Gj*A54SM?{Z!?;=jp8fsT%nYZ;APTzaE9{Tnw#Egsd%$tp2eMoBU9@{|mtL zbIvaOWFg&JtT#3F5&piInji!jk9UL&RA$5}>!sXSw{ZE#&t3op^CG(v@*2hz?(P6pe16Roci|dO8r>d#=hum<>$*2U&^$$ zJ4OC7yP2-9M^{;tua$hB^*|OzBb7g9viaR0N7jPj!4t)c<)yjZ9D{bnOqIr~TAXlUQZ#tM z4o|&h6S!|uLX;O`e*ewM{htWkgwqHCFX^jE37# zM%_p8k!MibCD%R&C5O}R*MIK;y{;6=vY#Hn{OJM4|GfvKOg#Scx%BTHur5g?0MCOn z9EIVfw*T2)ZMA?-vfmMj-vDX2k{Z<6B4MyL5I*6Y2}`=Cx z@6Sq7nFz~#%o8xH`usc!w>^1ip`6W;Xf_+I`PCFJut475D>^*&kB$?4;O3v1F zr`Nc+j}Blcyy&%w4mf?Wod>jUxw1VL@H9>*;V<|`y+qfaue%HIU0Jq;oZeQCyv~=^_(E(qHNtCxy5Pl ziH%uj32{h;4F-)FP!dX!NY5do0kKIa(g#Z_GWyN(=UdpO8*&Gpi9|A)nz`F89{&~a zsaQIP>SU5#uAuC1d_|E&l#KJHgFsZ9PjDhRA$SK${-XdQk}(85sUKdhUZ7z;r%h=8 zy*p$Hzj@l zw--evW&XJHPitC(|N6!7zqjW<@lqiZYimUlTVoT)fAwdwik1?Q2=Z5kHYr932;k_L zjfmTzajS7xWi`A#L6|XH52U}2{UQ!24tkdH-b7Gvu3@stYct*ZZxu}Ps(SC6Q9Kh5 zPgDK2Aj;tU_lMK@?dRW82X07>{l>uzl3ScAXgLBC-R5LK$gr z2j!Cx`V-WtBMn5T@uXe^Mahl^3{}*v7?K7Ib*Nc|caZA5DUFb{5OXA6A_#OT9IF&( zsxXD}TT3pjz%-7771l=~w~+(2HLTESpNaB03QSv>p#M0V1`=eojAi|4Fkg6aENGv+ zOlY=o^fx}uX#2Aoq7F7y+hN5HtwVCv>S3Ev(W!dG+JccJST%InS;hfd5_Y?%8))v0 z14BdwTj`K^UTw17=AA(ddu88ZuI+qWWw}{?n=wwG*d4blgur>&R$nX{lYGe&kV9#0 zW8z)&)Hj)vP4-bML5A+B*+Vb<#M00!7wHBNAE8zRm=2QQ+|vVjK#$Fbq)R%CT2!5Q zA~LB_`v6)rq=MkchFJx(x^yyX!TM*_7`s} zZ+oM4!@U&>QHE;t4pC-GSS!9xh&K~&-Ye8j7qGWd?O%EgAi&If{}7Q4V}i8Zm65D6 zcH%VvjV9l<0@>ba3jlytbo0n;x8UfcZbpGNknimfSEAD)RlOnLH4K}~D8_d}HeXTA zE2#WkkgpF`$m#OBqFtc4+YAWg1=$@e0mIA9OTDB5!lDj&Wh5$qvn`QF4^|xn+JEwW zq7<@zyl$4G))?YZWK))LT(ATsr75$H;2WN?;TME<#ctjtJzjF&nsLb~2krKpp_N~e zl%=#`{6-n(en^-<0JmX9PoAIcIr@lcUL-h8q65E-=Z3j1J5wRy*mTxcs#>FT1a0(P zX7_28ff%h}4ukGgHUB)DX)9B9ZTVDrK3;fr>4B42f)kxXs?xF9v?P&{y^MW(D0wVm z{{6TdZSdClG;h`!{Z(p1;2YA(0Z7`OHDsVkqX1m+TI>m3Ou8lu|Au0~f^ZvkGL1W+ z#Y_rce9If1n+KFAZUAYQD0UmW&njwex`zWi<3}Kw>xN$v69YriKRUutIC`>@83Fy%+!GE&=|o#yM?~7} zK$$s}&3nj%Lgf+*J=E>kU#bP@3NbnOWit;wv2o<-r@hO06y>ltNl7*d#WZuDB<;5o zL>N!8V8naV{|@r?NlAX0?unj8vnBn2OhNv^S=TXe7m0%rR>uF6elZ>{O#d z#{>>2w}9Kl@3&p#!F2;1UM@J;UXQxdCX z)%KLzPVU#G7#>iq%oUphpr2jeV1cA*vN8081(#mn{hIH>03fgF-oCqZ zXzck71W+!$Laf}98No~CDt)gp*84LYIAmLvJ3Nt@R*!>Gt;b~9?Y=8EMd6?S<>vnywf}< z(}i%(-G#hNX$}Sxja4i;yjV^i1vYM#G#l*SUf~9vQ9!_*EX3Q^@@MaP|N~=I>L{ zpAX})B-6$*BMbfuBZ3M&EH=M8N9!G2iY1Xr8V6tLF_`&>a$|k;Z61kzwPCFcx->T#8GyP|0nx0&s4^=H5z-XS?s8A zx|s<|RYSe9?>|-D`T+w@*_U|nK;Kt<8XwiqLb857B-EZKFU?*ySE`P(p-D@tdZT%f zRDkt<>uzDc>P~(v(6k#s1d6RyrL8ouj3y<=1;KU56_7=IfWXFq)HIDf37KAS>r8m@ z(1FP>GiXpNv#9Q#t!+LkQa_lWHZqu^9nb=?M2BRAx>Zj7SYA$3GMyz?8)u!4^ze)`R zXb`maMj46grSGwY6JRq=o$8TF%|RX*5Mxr9(rKRpJ$iE&a?yAYhpn4qmP)cb8b(s! z7^F`pE9|?(c;G1&GX}$POr2Xs-KOvdTgg#+v?94vbW}#tHbsltqiUm%e98(Vj08s# zQGGpOAF-L3FotBG(O|UjD0FDB&Sn?dw&5VCeJ4p6CVgoQP3CjgC8bk$D^5ul`N0yLW4rXtw&ehp+j=y6Pt!4V!)BD{j#R)L zgBmTFqWn@W~BtOrpk0Y#%9O1C=j+nArmpc+$_IJOBu2i>N;xf$Vha zh;PY1Z+N*+!)KgDB${2JnJk;gf!8XFZ?8D1fuxn$tK57y!gDUX(2PLodN*kQuW9_o zMfpz8Qm;s7hOgA5DeYkjNID5(y9p87iObNXGxKyCqq?le)X|>xC51R=Fq-8El`WtE zTn^}FSK-Mzw)Wi+GL!q8wB+kG8D0ygCL3aG33EqX`#5IG-@1*23^mtYHaf3vZA5J> zYAuJeqe&?!MBNYJt35R|A|=JRxI=Mn+-yfBk=O!96EnBtk8blAxd=;IN|TIK%956B zMDYe-tPm(xEP>iC!U)g|)rdg=!-zC{XSyLvPrp?jx=N^$F* z!qq{&NLm66q*DPi#Uz3v<2fU1)-b7Vg#q@%-x>nR=5zgTi^q11;M>_O1j)dnN zB&k(Y6D={Kg7#j^5(W63*Cbtiv&+z9L$>M`0Z}UDefzOCyeqcT)p-rup=hb zR#Lgq()(m`ul(9#Vgf;5a2w=y>f#-z*u7OqU{{iO_#JAfmM&(guU{LLL8F0Tm-{$Q zug|2N^-De)vMO!0VrFmIN@LuMM#{Fv#N(o=GG!$LUiF}c%m)bnn*Wzd)~9hP4?P$J z%jl%cT)cotDP!Q6ycei{Ln#LAWC7=bwXr-=|K;L+>0}SI%fuhsRv%ncFtgbioRKFj zpx97UdA=o%P?QF9aGnTIN~;A|02w1E^C^824%O(>1#C^zx{Q7nqe-jd8O}%->`FYV zZhcrR)4Ex`)q|==g}mr?)=m3ilR>XIY^3RjUHOS8R z2VgaSc7{v=qB=_g+4Ha|P{O8obsh1nfB|x0XIJ=aDhIBoJy^?Doc>m%VSBWgeOt-x z{&cTf*G>$$U78oFjZnCIrweW~-kA4Wh!5Olf1O5SqT8;(`s4_P!)wsaEgp9yJASWc zMJbTy4<*a?SnsAcgX3*jJLVk;P>-!hFbUdGe0JnBcVw;g*GOfNRhT*~L$<&tr`HBn zCB%<|vOb{Fx%<8}ocBIkG5w+g*+a4DuUBnfn~fDJXKFaNry_n8oeny35O^K}KFth2nc7bR zKGn7O)pj|e@<@8F12vo|&D9*NJq;CWbe4A+paNYwF9Kacv9m9!&#Nn~sxyW5YCx9> z;_4Ft8L(H_mI^Jp$alB&=W$*>QnfV~%oeu8s+1h?i^*nxC7)<*D$KBLXo&bodU7pt zlYC}JKPoSlK~j#7w78%ibO2TPTUwgKltwK5KD#mZv+9|j!*u6hIoAYQP-+eCIKk@y zRNXOBm(jEBH+q;Do((3m$M|f|{Ax$b?Q?lyw(U!_zrh%qSDK*CE&7;$IGdgg<&(>O z1KbLv+q1iYHoY~me1YWXj-cDMa);F$j=h2D46~C2=_aANv9aiFKpb#8VDx6g`L6a!|!?((e7XIn7LmFJ*n$H#yye*S#N?&@ze(mrNs5` zwMLx*R%CBDz2ymIdC6&cw$!7=tFXfO>nB?k;C8g1AIp6fNhQ>M)-Siyt`P;lI2*8s zqFc%{{qzn!jECHIGgj%?+i_#BdZ=p$l{dKTe!yLgmNUfmBIHrDh&vA#nbdTU%oE^d zmdqn$J9$Q=VIp*X?2y#!UF|p>0xNg#Raaot@3A4@o7pAH6Nb$;WpqJMshAFffj!<_ z!|Ntjl;v}J>d{FWm*JC~v5BkTDYO7xm8-A9jDm5x5b zPbBx%<;ym3qV3wC7zRGA!hHOlyHxZpEp~_QCY4(X29~}f=W)iDufgB1y_#BCrP2O? zC^GL=27y`SRrwaco1W@}TS>F@)67WrfU?3|rt{qj`wP4tv}NC9R1KVxACu^9 z%hq}w#ODjR7r@jT+oM}pJx`CCNft^!vYBgLHtDcyGefhC^}cXB(AVE;YcG-J%O|Wh zIo2_)3M;>Vn@<^qhBA-+%*+*jJT}Dt^L)z1!r4ULz}Ce2U;ffym31d174+|H<3yQd zqG0B{x*}zo1gZ5#M+7Jl{0QKA#Y}VyfpJr>HS5hQ*C)k@eJ;B0QJ}Er@u-@qm4*1( z+f}}?TdvGTWpDx}j`!^CXRhtn>W&|y`mo-wA${{5_SL{Zc9K-K14qrlOiV-eG6O_2 z=B&Y_2&cQ(l%VN~R{UHv6=ar+{&mIsm?T<>Qyq`9`VhqGQg=C#bb`7+TK(NO-XTJV zi#@ELef1SJ;aV~85`QFLFhX+1Y}W0-x0%tIN6|9R+q(vOP|^2%`yWDvF+kUcyR?eC z5Yu*dCH3D7Ra=5ZO!v%W=5meLvrlC&FNOu1hw_lKNqT3rSZx@PDi?#b7SB-$M(d4E zRAt;`+Ux=&9-;xDJB`=-Xf)f8Ap}F(IqA6twnR63idl&4WT?SXSSsFOLb}fnReXf# z)Z4ohTQ&7iLMGr-gUYFdBNy--S+$O!e)!%vB=O0QaY;9BuXWL=N!F{d-t&|TBlY`; zJf>?>hKUI}`4?(gJuCwxN}|ipel^1f8ZiRK?4b5(`yibow}@{TPkgqjgUrLw)#Yi2 z?s!hnWwIQ{rFmnw0G3>=PeGHn1l0_MxtQe-hnU0MsO;57G5esrdoSpbl(vp1rApJz zbU-EtvsF(1HZ5bEGndFD5^T`B3UV<@YO*{9%k?-5jK3|;m{uDPstaZM?3e6Pt+)ToPCA=Qt+sWI7gL#z ztQrO~2beyI7fU&%9Hb+Is4Vg55sH}EY}N(Ke360EvPwr$iQN61K(D2s?$glL%eTJPZM{$W|(+gIsC zU6X36l9G6XhamCrOPVnnX|y#odcakaaC;^&yk8GXb^1$i$EEBkyl#jT{&zOfPB0VG z{Wl9@&O4GfDc{<_JzSlguQOIxqGV>4Drm-v9M&N(tz@}l;z(khH>c=>v$SBx0x2v! zmqxtm9D@bv*4$&!zDyym2&cq0>JBgB3!>QVFAeWt2=8d67trHdn&MlW@)z957XU!M zJ*JN!t``iwom6_RREi;ytUM=5^70R$_EB)hz9qT>_K2&F@XJ~ zvI6_CcTNwa-rJc9q#yvyQ`nH#A;16rADT(2ufFAv!`Kn>*DtpJ>n!+ZTCMD8V)8#s ztX0p|kWJ9PVLHYqU<(`_W<}(w3k-BN7bJmL5Ji^d!NU^9Fn4s&(OsLr>w!RjyQUEySS(CzjEH0J|3TI{C=%Oo^!hHV&K3T z3J;SHF>_ZKVNVZAcB05a?Arb)iSY<@hrI@ect zGgK)dCa&x@<~T$%o$?IHpqbU=1Clu$q=$VUbb=PcM0hvfhlI;wa__PG$dz&pCHXVG zr&3TcR~wxg5i20+AP=ePw0B+X%iDDrZYnM+6E=T{%+Wz_=8mVEg48R&Vg#~diV@KZ zB|RQcu50mvu}Gr3V;WG{@FQxFRei%xi8%MDh$RF`x#6oH&qvkbAkA zyBS_{vs*T^5x-%{+-HHtL&>lwC3(J{_ucDI3TRUtV>9H*iVD=zL4D8* zbZb}zWc>=s=}nhv4N`vxh#Kn!9*-e7+l$l0Dck}G&nW$th~PvTD0id-89n8)hrgv- zYBEF9va1C?HrbIwAWY6-w8!jaqX(_kbmtc?%MD3&*^6Xv_xGk2z1a?oVbK6uxYZLiCio=Unv9#*d%4pS;$2-%W7jjR((2A zgXuZjmef9b|?PssgEMTQ24S*ch#N#ur&KT8P>Ru_^xz7^p)?8C(?MXMyQw*>#!+ZnP-$Xg* z6ox{uDo(1x9DIV#c}E16g|kozFCm;k$ty6|ZqRW{V$czPpy|T&=)q(&NH0U9C}(OX zP*otdMXn`Wh_k}f3c2!jhDNcXfvk@MiML$l+P%x8FE-3iC#UP?XBOtRR}2ZB^?#yL zP>7ZI1iq1ADB4o%+Bjy7yd%5gwlVL{sXjbMw+Uaf3H;6>H6@lQy=CqaftRoHY(TgY z*K8U;Lu`(1^Rp4m*qg~gSF zMKrJw?wQ=`V~+o1vWIJ!hB2|oypdo0h5y=Op9N6{B}-PygqS6JhYTZI;vNA~LTytC zCtD(+bUOOnXb~5_7I1C8sDWyOLz9RLr$lES+NVoF0Cf3pTcRQJj2Q5szkVhE%<2jM zkK_BlCiSYPYDj9x-(9uD&_rB>k>(YK_^85GR*_NwO@;-Eh*dkB#B|2^6FbI)d#CxX zO%+Q^S5@2ZBGa=wWykJsWq5C5j}Kd;?8I?-dRv_y&s*Eqw%1c1@7sEEzaI8%QQGYz zQXTk6k&|{C1FG2fhc!Tw*-PRMI`PnumFo&2G3gQ^4a4pUfy9C72SX+JoIzmHOdHdG zDOz(?tBAfLN^f8h^A7gx>pVtJX4z)$a_WfMniO2Rz?&1RZ3&C>@$+o@^WW0ICGUG&A#V&?uKbN#LDjZ0Gjk(B?2Dy zJJZTzEx~wDGjUN=m9nPiCx+Bw%qx^ zu46eTBc-rX9m`k^Dd5=~o=RCDOE6oc-%Hc)xEm*`>)SXm0SNRRduP7g3yJ??#@~QyvC9|EiZfXW3?Uv6qo33(ntx;Hr8A}dXpK4Y^ zxu!z48Ok;!COyZ@gW%{ZX}q^K7Z97$fio>rOh%~`<}NtTW%;ckBw3C#ucijsO7P;y zDhirt=T!+KBVmgcb#j+r6q1m#@7JYCm$2DwckAfe**^}Q_=?)S(ISbf828pD&Mg^9 zoqR@h->;sC2&xjA4n>R#aX#A_{gI%)d2B)^s-LUkgaDQ+Y4~ zqgy`JbjJjH(6A^hLs7r`6A^n;$Nve5rtBRcr{W!gDK8T%jNr# zw0J5f52H5}u@lccUIyp$19g%wr6&;}h^A6Gj{rBxQGh+J|?D54C)b^{Z zvNaTEa1Zj_LJO1IaXrwW#22jOtlTU!)Q*4(Mf0^_lHB@MVW%#qB} ze&RfHE~siwt&6WMq{Fy5IFvJ5GTQK79Zw2_8^LM*Hi19>C&sIL>8xI2>~YY2;j8GhWx_mtfeYCW-xTEXxA3%4 zQZ!w@Hv&py6?P5U`q^IOjTu7TGbJJg9Dye;e;9T7tHyFt@Ef?5d#(r!AEqVNS$!#4 zV2FdSp76d9^W#i&*&mL`re_>J-Qp%N$f}Z&Oov9JfDc|7-JISE_DF%O0u0w2Sz3O< ze%hNb`_2KXLV&gFAuC&8GV&!x&n3i2x+D=Pxv&8B7GahBuzhfNbr=EqjiIb?sNs%f zh8_6@L%64eqCAN9zrZ3^4v^X8#TI<`*vNM5$bsmN6kCpz&BC)wNR(894)NsFYb7`l zO&p0R$bkL0H?&~>ip{_(r|xxeULTz|YWUbMsiXFwJ9>{mUmOs>AdApVmI&7cS z2o&_n%9C2s-@=V!19FP)^ot1A{Fj2|S~5Hpqb6owXyVixtd7c5+%yL5CUDni{QkfF z9OxU}XDI(!Td_m>e@P7gdH>7U8Jk%D*L9-i<%g_>;Y-TZ(a?bb4xup;EF~Tkq^)i) zr3sXu1}dKUD*+udKShF(04Ot?o(apmicQkWrmIm`^HhXNrHn;U(;!~UW)bi6^8@)z z>;_0Z*U6MoqmTGzdX?$8^VlQzndka_;)3tn?UU%2)=exk;|(+NPZl(_&ve3^Jy~?a zkDVr@WZdwfKo{g8XFZNQ0?wh}R=vX;Owf7-6c*6w4w|o=HjCpGbS+koektMb7(n%23MG?}% z{2|$2gEBw{yV_i$ud;nBxOG>L6vJSWQ=g5rDlq@(N?w|F(lR`$oqOnMy5+15qT(|g zjX|V!Q>pDbf_-ltYFZY~6Ov5DP71?F0{RZ^Hpe9DL~7F*jyqfjB5b)QPe6xlpR-~y zdrJ$_L^F%c3P&N|ArWBwR@vG1m!8FvGZ0WYu5LJLBW_oHk3(I#OCoZ_3W)nTDVa~T zmUu1CD1#CIP5}qQvx$Khmv(xvEU!o;#9@suhsA87Gqr16wqD5*l65P!DPS>%!(=)Q z0X7jy)mPhIxz;OQnYy|ChL`m@0oh?sm|4z=HizbDjVa5Blx?ANqJ-1NJ(1Z7DrI>1 zP5c9(j)JP!apIGEnaAQFHhmv*X*-=TmSHpv4$(!dW<>fE5tJHyu3<=cG$}gHU@#4C zgU8}<2#2Xg|1bz0fckJCdzt=VU-SH|Kh1RsEv5a-UZd;l!t{m5mzZ}F2|CA#+VbYc(oy^H-zE4n}HcUX=j3w$q2%yrMdl+pe zte=b=vmkX0M3BR@{d+k+tn)` zsvx$Hf2oDW1Fa;cZz3`_u(CsWXO6U z4aQJ0$QjWy*{y;iZX(^4%q(P=Jp{~$?gYO872nfX(Atetlk`dAM-lqywoUIf(48B} zCEkD#cFX-KwBZH-r&f)iMVEj5Zbg`m|CmMA&+>-({rtfP^WvH-$SEALE!nWk%t2{_ z9Ej>4*owM%y*jNQNhjtFy_?!=&J8#(+=vDm#{FS5&I-m2hd7pj%BClVr53_rO#ye> z+5J{ZB11)|399XEeqkMpe?65eP+Fp6+b@}G8XI4Z)0K8Y*9;Z$=xoLF<1ud13uA*8;+C!^;4GF{LsdCKm zQ%<7oEtN%rI?_6(!Cme~&njk*c~FqES1>2#!gbb=lOG_0Br{)0L#kq9T;nhVD*WCReP`V7Mqg8jyk_gFGAR2_5Z8vJm8`J{|9aq zS(#Z;c0yL6Bzt6)97QDSjuTGjWR{ebin1xAxn~=S-v-c=vR#eFUeU)_Q&iD6! z*QYx@yr1va`}Kak=jZ)?y*uiFr-vSS?dLWzF;jXWQ!xoF6-BAf?y}`TAtxUlOiav> z3)#uEk5&*ZUpe($nmH5xsJ^68+v6$vq&~N)(5G-FvCBs1O-A}2A9pj^rP+fBc-$xOBhs-PNHDZ8tUX{;Wh<#qpd#HfiJh*e3bZN)LnS;c) zZ3{|H`W2gy826%_?|rAs61$#CuQZhOFt9sHvDD~>DI?E2-`XpDS(Ch%w7D;qk}n=P zMOJ&dHqy#d;~Kbxwv>LdeTJ&tCFv{K)qZPZBhgCda#q^J!X#tF&16n2?zjYOGBDA%N_?uMxBuAOI?cJmuh zrE@;q#{*;W6&-~(a;3A=i7LOpmogeXLK38svhFj2YD-}{pf=*QeEei-$k^;a$?|Mb zUiQ_o$tUdt=a-+nV9g!MIN?9zQ2wp3TS3nF;>Dr<{{G(H>E)H8qN1DS<_HAh2>acd zu^xNrgtsBV!I2FU3kwnk22}>8{091JI_8fBV50&Sp#q~MEG`l(t`t$I`*gmxfqrf$ zeXRr0Txt-JDhLlhM3rbnkc81+SH$A6GBGK~E-vGz5&C`yP3eOS0uAr8HxB!Yqm_z# zJ1lm)`touz>_ih&xp9%O-4n6ZHL75|U+e3nf9AlMHXe%f#+{dnyMD3w>71z{E}eQ! z0&M53>vaZ>=w#ojeQIo-KFgloW6e*KYn&XQZY%vr^NwQVbjtl$U61fRO!1Ns11%$1 zVkMUg1OK5=K~KGpPDeH9qs5`DLT5%`7LiTLTGe}Eka2UIif@Pj?>#K z`Z@PPFRLZ-$gRig2(*tlMa#{9$~uu%A|d+oSm%q7VY{_XnanJYXCK#!oYu{=+y+-% zmddMTOx{n|FJ%v18d@2sj`-Lvb9!jts>|%^?b8ZFrxm7Sw4)FTa&C*?zrLyoK)A|( z@^1`b7coD%5PODRWyLw+ zUBsFvGnu)owGOpW+@w5MrF@e2lQ0wQDiN}iY!zb4ZOM=B=8~Six+COO_I=*CzzW$F zlYGw*sOuFQ*wEbvCYL@gpF-IbCS0g$xu)VtJu`D}VQqfq-IIujh|;-p=g!H<$hcm) za>dnkd1lv>JnH3@FL8 zUo0)IN!Fz4=Z|7_CDUueV+!HXFvk1Z*|nwvQl{B;TI}}OdbD?2?3Ke-rxT2{-7BduzVn$4%e%a5aYgM21%+$VBLial z2faMqzm^w;J#ievX~cxutGkPVWqAS%<0fEXws0uY0%>LIjiPs|z16yk+Oqi&CGv1aMfV#%yf;R~o;6ZkOM*!|viY#yzHw8B+B8)8 z5G^xb0V+#e0PQ4qw7X;FbVZrUe5QWv~M-3;*S5UYGPc-k4EKcSLA3;R@c-x zG8(eK{b0Z<7;k1P_)-e`%!lNr)IVpxFzvT$UPQte~x`Rw<1Ezom@d_4MO?3TJ0 z(|*Rq1#x+9jd&*H*;X@)> z>GLr-=ah?=Zp}Lv)4*Y#psz*y`)Iz`eF_b3kd1F$uxEYiw{TayG==-2{3mPEhSnBY z37bJ10m&<)jt9b=pPAN3PLcasbvH9lI zc*3%mSSFtxIsiXqQ@{|H0f8;u`(SN7E9T$dqhqGBQfcZ_O95PiF-zXk6uwwnNZEr- zR$F`g5;kxn6&_f%MhdTan8)le6|dS>Uo=?YbGh}&;F&PWD_p{dP?QDMu=qmTF#{8d zqxLYtf%uEV3%&V?QqRAZL>}X{^BoS)f)?5gQjj;!F2=NZDkceD; z(Pw>L&zSplq$NUy8%=^gz&`btMd!s!IoB|LK?%5;mc!ZX6dd|ga+}&68aR^L^{*&B zWJb8~k&8Aq&gN z4lhiNE%SiAbgIb;jY{1;`8kqTDe_6=vwZ!hAA5~rU4?W{j6qu_MXjXT6OU=uy#_92{s(=3q@6Pucf<~Mz_BQu8 z<>J4@Rdhho{at-ZEVZDptZAo%^np&c{`%=Aznw{s{nM1F&mD3%=K4C_`?Laai@a;C zdcsq(>io62K|NWH7J1$vc=yLg#WZfT(;u{GmjizOO#0SMIh{*ZKb&^>(&TbauvF-) z$+U<4JHwsBl^VeyXH=i6BrxzA+_25e9qh&${`r~V-H&JIT3$7O{gIXMLFcggQ3>nG zLla6;(4dU8EEe$#$v&=$A7hnIQs|}@r6>zQCq92rjx>8pJ#^P>K>Msfu}o$1Oyh%o zTXbECi=Od%>mbnK!jz+S>lXbDBB(@K z$2Z4{hWCci23k7YAE-DcO_yRlgIaFe-g|9}4y+E!Fr>_2v@i{k%2%S0C{`)GckheK zey3P13h#4!hMmQOO_d=i!~14^`jzZISz0-`o(AZ9T;|O^>z?|;UMouSdejS3?$YAp z$39mmv8a7dyPM0@dY(8qd1lAX{u~`vxpm)hElQFLH7^ep$`gHo-Edr2NGx{rYVmGW zrszvqIzT5ov`_YSsoj))BBb|rH|s!Ux%9QxrK|_dkMd$&J{zXKi!fHn^Yut5XQr{q zC=KAfvM&7ImS#_@yDU0xWllj@9i@DrBtmOI{ifM1PS&!I8DV*gGe^tU!%}xiI37P1 zEb+#y!S3F;oT2n*rtcSD>W?rDmJP5=$&!U7*&!FcC!I!$j!1cUq%40O?c5vbe{I*- z#%lG)23g&L+LGd=+M1GHn%o7DtE+7LUuT0UvXV@j7*m&FWka~iYdJL%WS5 zfB&teDt^fQR8;X+c*|9`6U6f?w*5b$l9e}U>v(I)*YXms*idwPl94~ZgcNRl;cWiE z#yG(!X1+$};0cfRV;yQ2MKu?Kp0DnLN>k|2#j%JdZKcjGeTy8FWAO_iI<1w4pfXuVCQ*ngyEuUeZ6YF>r3U}G9@!9V-`Qf@i^kz+p6-HX=;}bj;k$t)E;NrJ#+MR zWp|jNe0#llEN#-rPsz%M3BJ#LG!Ria$C1QR8Y(qzooTleCY~0fL;J64&!O85R+a3_ zR18)vq!%?J;p6-T2!pAiFaM0@Fk=X1a<$7=U>lC-o`0vLf5s5F_mJK-a`!UmWIG|2 zU0gT6i~FA9k>PyB08Ql8NPpsTQNWT)R%k-p<+cMRRFsX|EGzzRwoN==c+_^3Dyo&bhvIE&V!J_Zgw^NBLh&gr%jv-#N0tcJZxTMoUHbXkU2m_R;PHN$IxwX{ zI=ehZ{AtM|gw}J-jgI#Fd`2zoYIOMA)kQbeeIY-@pN>f=#Adz;5NlYutPo!oCNIao z|MbfI9;Mai_Uo~7@e3oy#_p%&OGUd*yd2v}Q#$fB#kL|>pPjuh&zj*(QB`hkPmFB? znQ+{#o@TouvL}edCr`3p&J{Oj7ygj4y}%B02&XHGO9+l7A5w_LW&B>rBOo1 zV%m=wxWsq17L>lebOB+Jt9)}AE%-I9ZJ1KDFS&f+{v9LsNt6cTS%iSR_0u0sk63N& z3d1Z8=eFvoP4vZ}`ku7r`2UDOucYOaaEF}~tfsm-VA4p{6lt5K7Sn3;OyuyOXkqbK zO0r~OZfc-E?+x~oMm z1lG@fIw@C(w>K&xuDR8g<@5KakJ49R?9uvWEyGj89jewXcl3*YD%PLu2y1TPXNSeK zpW<7iZ2jJyXx~OJAUt_FsISBn801?F41T5(%|Hm zj5y>}iRA8&lToZtkUM>tRN3ids;1f}l8FqL=J3*np1(625~SOE9+nspI9`e(qCmGc>!|b2}egyn4*?`E%9hLyG&!q(qJ?J~&KQlPJepX9Z(AAk0TE0l6Q_bLgJ%6a}dX z{o#3CuZFU`1x`+}TR);`zyRj3c(4OmsaU1=_5IV|?)#jZw-V5Z`64+_@6}OudLBZm z@a8Sesx2IG&;1sPKBniuTMJN1u{6=CAe)vL|Yg#HHA} z9dU|f3wc2HlUsR>lwmfvEax1ljWBF#$KrRvyXb@B@^(*C6>p3w9^uT;h`Wq5y1T;J zqiA%#Xa}OQ44U6xrm*1e&(B$>Bf@Z!Z$>eFKv0@GQlCL@AWwH;J<7*>OqX=M+*^u6 zo;Uf$t&9EfFK+i|o^UX^eDkc$2WXc_y z)RXotNU#fej7_HfYK&8KmE6NyDaK3Z^IqxprXTO-KY!t@me2zm)6=DsQ`%!guirHZ zJL}dC5nJAh@zL{)zTdTz?Qo>%vBzCB4w)4w+5&^@Xb9}6>+xDnL+yq**u=L+Ayl)= zn`6#}9lvyQVya}ao0XP{Mt3eyC4IJ+s%M1ny}ED&Y)$(cPutrt zcf@-XQWT*)vCuGN6vbCAIU45ck*wqeQL zPtaY9AEx#ik#TZAEWdw0Ohv|{L-(P^{hO^R-9*_c5GMz_hrXT+@%o8-b{suR+&SUP z$;FoT#zUE6JZASedwpQI!VwBY@`AIPge?knX8=; z)pSZ5i?GE+mInnqV~5R4Fa9zPhwh zg{hmA^}SUMXkz&Cz`-OPVyzVZS!9fEkM z4<6u#wD4I_u7U`C;OUKQ_udOtT@Mp|Y2Nr~KTXqrdKJHrMeUytdM zSA{F9BJ7?AGW2KQ`ks+(+z{KO_Q-cR)|r<3gD0~6-wS8C$qXDJP6%A4^BDDpL7H0a z_O8I+B*l&$xLm>5WM_BajvmDM(8{@wvviNn<+>|>-S<7ccVLqKLvbV{#f7=L(G!(X zYp7a?8^ueB9b#jKUz`0bql3vEEzJ?Itp1AYbC$9FLAd5o6sFtn71QxhnSueo0M4^YrOTE+QEGN`ecYPM%5IqW)q!Hf;K`6Nm}P+ z?(l+)br?*Er`@!N&pJm6mk5d|UN^x;XOFq1}+if@gca8WyDa z{V+=GxMYU{=FEF$ZnV_SCAXlU@0o0<4cSv+%BxGLsp7-Y`}thLO$-}!_PwQ>Gal59 z7oK)g_X+SheQaQ*-H-QL#Le>_#Z#x=veZsi@rWRZ8$NQ(unR+$yq4DWw24WWNWp*< zgeEXP{q^O~KNLj3zsavaM>L~^`J$_4pma#{l)Bi@dLpnmwh?s~5oTyRtKC&@27bv7 zJWQD1{u9tX!ra1|({055C9%cd=rE*(1Jo6PytHj>upjiF#4+v)e~XQDaRmO)wvD=b zBj&G!3;#xSc7;3I;N+Rf(*#Rfn}v(X^W>JezhA6vmu+mpS;700--7>{2W>{(MbrR_ z%6wkyxF1L+3;ZJHCCf%Y`>6bl`n#xBo7@Z1P!p9)}U|1$KD2pc5S*;(1*kDokTaSVG8 zyvDeu%?4`Px4{9QXrHq{x_?odvsQd6|5ag-rv-R`a$kb8XxQbqmz6L}9Dg2b8Xlm6 zfM74S2*YXiHN|* z-+=as1pOF(ec>ln8~n`s1f^zcyip-xZ>6x136#ShGsv)85rKxzO4w);1reMTX8fEbV} z)Cz&L01mYA6Up_4a)&_?IRl0A{N-UpJaU!*5duZII1wtGrN!6-(||_;485nKH z!!I2?Pn|;n^emPvxTffJeuK6-Qc!Ysh9U`Q4#K|9rGk+QXio@BuCG>vNT7Vx!OjJ! zwc4*OGFJ8$&d#_E)qrP--vQ9!sDTa#)U>|=?Q9q)e$p;VS<@82C`^Y$M#Su2@2NXrw6kxR53I?IrZPyM2eh%Rpmre4{T@09V z7vN!MQPTxKYg>)|xX|M+v?r%^M1co zCsf-4h?$h#Kvt*0XqIb3|KJntQ|!4N4}XDvZau4L52V959p%QNA#dQPZQD+xCSfFb zV4#Zxg^E4s;q=`GhhH|ci_gZI!CWH_G<(>x(T8rp{*3ySL3@m`HvM0d0Q|Up3)fq+ zf#Plh`LL_)J~R$aM)1z`_2pJ5?wO zRN1p|s4IR!t8PjxeA@xMKZEuKyCGmr#Ubh-EUdxY<$n~z5$K2k=rI_2+s6o!Fz{7(@o5e>1wQ&13v5bn+uM%)DO9$9I z*?K(E{ig9L!|&UIRM}GqEiO3gZ4N2&R(OzTMz#tb+Wz z0wA$fPZ%7zT;eDS;2v$k-D}+rM_@+U)p#@bAsE@~f-Via*H8gH@$ZU-M>_nnCG4-e z;0RzXTlz16 z-*9=l7FPecKn3||;)0p8>H%F~*Oc%dxV-;_g}JSgT>=Im49pFC5U#j}3j-#Ma&gvy zI}(@}+_^q6@DOB_0r0T<#wj9VoGOgz#7=H(-u;h)w_1O|w-3l?0{TbnNqPgx|H8#z zEOzdbCV8MZCc%SUEMgS+s6a>+1ZXMT2+TC8sKxSt%7`#Vk6kPRlmvK$hkuWx%p&4J z8CwE7LrOX>3 z9p9FsaN^t!M_{FsvW@?Z7$&2jrNWjDxqmwxfyGX^C3mDekZ#Ww-3@bZha<4w>H34J zMI4|MfplD0wc-NrHaG%i=dTC+oB`Uz7Fq-UHZ%fOCpT@JbQ@%~01&Zd3l`jlM!@J2 z)IS}^REd%;v~xn+&aLp{7}+r6NNm~mi)=$9U~;{Ov@K3u z#keTkv!a`*n-B1#V4EjLabdW}0ykk3arj|ahXiqPxJT_aal`TWala4V{ma43+i}mx zZE_F^8yvzX=5T4ayX7}&+0Srj7=r_IlfU==>1po-b0s1oH}I#253K5AUOW)}AIvtt A>i_@% diff --git a/src/intiTest/resources/test-artifact-1.0-SNAPSHOT.jar b/src/intiTest/resources/test-artifact-1.0-SNAPSHOT.jar deleted file mode 100644 index 009abad49de5cbf4857e8c429cb1f81c75c2dd56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3115 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 ZiPmb76=Ewli&KF45oX>c2|m`p{{iiGhC2WN From fb221e3b17289a52c96ddae2db4b4d40d57a62eb Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 13:17:59 +0800 Subject: [PATCH 02/20] Reduce extra doc package in intiTest --- .../plugins/shadow/{doc => }/DocCodeSnippetTest.kt | 10 +++++----- .../plugins/shadow/doc/executor/SnippetExecutor.kt | 10 ---------- .../plugins/shadow/doc/fixture/SnippetFixture.kt | 5 ----- .../{doc => }/executable/CodeSnippetExecutable.kt | 4 ++-- .../{doc => }/executable/CodeSnippetExtractor.kt | 6 +++--- .../shadow/{doc => }/executor/GroovyBuildExecutor.kt | 4 ++-- .../plugins/shadow/{doc => }/executor/NoopExecutor.kt | 4 ++-- .../gradle/plugins/shadow/executor/SnippetExecutor.kt | 10 ++++++++++ .../shadow/{doc => }/fixture/GroovyDslFixture.kt | 2 +- .../gradle/plugins/shadow/fixture/SnippetFixture.kt | 5 +++++ 10 files changed, 30 insertions(+), 30 deletions(-) rename src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/{doc => }/DocCodeSnippetTest.kt (67%) delete mode 100644 src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/SnippetExecutor.kt delete mode 100644 src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/fixture/SnippetFixture.kt rename src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/{doc => }/executable/CodeSnippetExecutable.kt (81%) rename src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/{doc => }/executable/CodeSnippetExtractor.kt (92%) rename src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/{doc => }/executor/GroovyBuildExecutor.kt (92%) rename src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/{doc => }/executor/NoopExecutor.kt (61%) create mode 100644 src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/SnippetExecutor.kt rename src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/{doc => }/fixture/GroovyDslFixture.kt (90%) create mode 100644 src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/fixture/SnippetFixture.kt diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/DocCodeSnippetTest.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt similarity index 67% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/DocCodeSnippetTest.kt rename to src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt index 887c4a273..76363f9c4 100644 --- a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/DocCodeSnippetTest.kt +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/DocCodeSnippetTest.kt @@ -1,9 +1,9 @@ -package com.github.jengelman.gradle.plugins.shadow.doc +package com.github.jengelman.gradle.plugins.shadow -import com.github.jengelman.gradle.plugins.shadow.doc.executable.CodeSnippetExtractor -import com.github.jengelman.gradle.plugins.shadow.doc.executor.GroovyBuildExecutor -import com.github.jengelman.gradle.plugins.shadow.doc.executor.NoopExecutor -import com.github.jengelman.gradle.plugins.shadow.doc.fixture.GroovyDslFixture +import com.github.jengelman.gradle.plugins.shadow.executable.CodeSnippetExtractor +import com.github.jengelman.gradle.plugins.shadow.executor.GroovyBuildExecutor +import com.github.jengelman.gradle.plugins.shadow.executor.NoopExecutor +import com.github.jengelman.gradle.plugins.shadow.fixture.GroovyDslFixture import java.nio.file.Path import kotlin.io.path.Path import org.junit.jupiter.api.DynamicTest diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/SnippetExecutor.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/SnippetExecutor.kt deleted file mode 100644 index 09f87c315..000000000 --- a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/SnippetExecutor.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.jengelman.gradle.plugins.shadow.doc.executor - -import com.github.jengelman.gradle.plugins.shadow.doc.fixture.SnippetFixture -import java.nio.file.Path - -interface SnippetExecutor { - val fixture: SnippetFixture - - fun execute(tempDir: Path, snippet: String) -} diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/fixture/SnippetFixture.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/fixture/SnippetFixture.kt deleted file mode 100644 index 8a20af065..000000000 --- a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/fixture/SnippetFixture.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.jengelman.gradle.plugins.shadow.doc.fixture - -interface SnippetFixture { - val pre: String -} diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executable/CodeSnippetExecutable.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executable/CodeSnippetExecutable.kt similarity index 81% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executable/CodeSnippetExecutable.kt rename to src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executable/CodeSnippetExecutable.kt index 7a734b6b3..0eaf65ad6 100644 --- a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executable/CodeSnippetExecutable.kt +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executable/CodeSnippetExecutable.kt @@ -1,6 +1,6 @@ -package com.github.jengelman.gradle.plugins.shadow.doc.executable +package com.github.jengelman.gradle.plugins.shadow.executable -import com.github.jengelman.gradle.plugins.shadow.doc.executor.SnippetExecutor +import com.github.jengelman.gradle.plugins.shadow.executor.SnippetExecutor import java.nio.file.Path import kotlin.io.path.createTempDirectory import org.junit.jupiter.api.function.Executable diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executable/CodeSnippetExtractor.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executable/CodeSnippetExtractor.kt similarity index 92% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executable/CodeSnippetExtractor.kt rename to src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executable/CodeSnippetExtractor.kt index 334cdee26..650fb9554 100644 --- a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executable/CodeSnippetExtractor.kt +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executable/CodeSnippetExtractor.kt @@ -1,7 +1,7 @@ -package com.github.jengelman.gradle.plugins.shadow.doc.executable +package com.github.jengelman.gradle.plugins.shadow.executable -import com.github.jengelman.gradle.plugins.shadow.doc.DocCodeSnippetTest -import com.github.jengelman.gradle.plugins.shadow.doc.executor.SnippetExecutor +import com.github.jengelman.gradle.plugins.shadow.DocCodeSnippetTest +import com.github.jengelman.gradle.plugins.shadow.executor.SnippetExecutor import java.nio.file.Path import java.util.regex.Pattern import kotlin.io.path.ExperimentalPathApi diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/GroovyBuildExecutor.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/GroovyBuildExecutor.kt similarity index 92% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/GroovyBuildExecutor.kt rename to src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/GroovyBuildExecutor.kt index 556f93fb2..fbe45440d 100644 --- a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/GroovyBuildExecutor.kt +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/GroovyBuildExecutor.kt @@ -1,6 +1,6 @@ -package com.github.jengelman.gradle.plugins.shadow.doc.executor +package com.github.jengelman.gradle.plugins.shadow.executor -import com.github.jengelman.gradle.plugins.shadow.doc.fixture.SnippetFixture +import com.github.jengelman.gradle.plugins.shadow.fixture.SnippetFixture import java.nio.file.Path import kotlin.io.path.createDirectory import kotlin.io.path.writeText diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/NoopExecutor.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/NoopExecutor.kt similarity index 61% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/NoopExecutor.kt rename to src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/NoopExecutor.kt index 3820b8aef..1123baddb 100644 --- a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/executor/NoopExecutor.kt +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/NoopExecutor.kt @@ -1,6 +1,6 @@ -package com.github.jengelman.gradle.plugins.shadow.doc.executor +package com.github.jengelman.gradle.plugins.shadow.executor -import com.github.jengelman.gradle.plugins.shadow.doc.fixture.SnippetFixture +import com.github.jengelman.gradle.plugins.shadow.fixture.SnippetFixture import java.nio.file.Path object NoopExecutor : SnippetExecutor { diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/SnippetExecutor.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/SnippetExecutor.kt new file mode 100644 index 000000000..0b445e007 --- /dev/null +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/executor/SnippetExecutor.kt @@ -0,0 +1,10 @@ +package com.github.jengelman.gradle.plugins.shadow.executor + +import com.github.jengelman.gradle.plugins.shadow.fixture.SnippetFixture +import java.nio.file.Path + +interface SnippetExecutor { + val fixture: SnippetFixture + + fun execute(tempDir: Path, snippet: String) +} diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/fixture/GroovyDslFixture.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/fixture/GroovyDslFixture.kt similarity index 90% rename from src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/fixture/GroovyDslFixture.kt rename to src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/fixture/GroovyDslFixture.kt index e7dacf74d..525dbce85 100644 --- a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/doc/fixture/GroovyDslFixture.kt +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/fixture/GroovyDslFixture.kt @@ -1,4 +1,4 @@ -package com.github.jengelman.gradle.plugins.shadow.doc.fixture +package com.github.jengelman.gradle.plugins.shadow.fixture object GroovyDslFixture : SnippetFixture { diff --git a/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/fixture/SnippetFixture.kt b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/fixture/SnippetFixture.kt new file mode 100644 index 000000000..95a2ebf5d --- /dev/null +++ b/src/intiTest/kotlin/com/github/jengelman/gradle/plugins/shadow/fixture/SnippetFixture.kt @@ -0,0 +1,5 @@ +package com.github.jengelman.gradle.plugins.shadow.fixture + +interface SnippetFixture { + val pre: String +} From 2339155a185ca37404b115e4e22abc46c3548299 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 14:14:46 +0800 Subject: [PATCH 03/20] Seems we don't need an explicit ConfigurationCacheTest anymore --- .../plugins/shadow/ConfigurationCacheTest.kt | 140 ------------------ .../gradle/plugins/shadow/ShadowPluginTest.kt | 36 +++++ 2 files changed, 36 insertions(+), 140 deletions(-) delete mode 100644 src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt deleted file mode 100644 index 29539c00f..000000000 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ConfigurationCacheTest.kt +++ /dev/null @@ -1,140 +0,0 @@ -package com.github.jengelman.gradle.plugins.shadow - -import assertk.assertThat -import assertk.assertions.contains -import assertk.assertions.isEqualTo -import assertk.assertions.isNotNull -import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHADOW_JAR_TASK_NAME -import com.github.jengelman.gradle.plugins.shadow.util.containsEntries -import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries -import kotlin.io.path.appendText -import kotlin.io.path.writeText -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.TaskOutcome -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -class ConfigurationCacheTest : BasePluginTest() { - @BeforeEach - override fun setup() { - super.setup() - projectScriptPath.appendText( - """ - dependencies { - implementation 'shadow:a:1.0' - implementation 'shadow:b:1.0' - } - """.trimIndent() + System.lineSeparator(), - ) - } - - @Test - fun supportsConfigurationCache() { - writeMainClass() - - projectScriptPath.appendText( - """ - apply plugin: 'application' - - application { - mainClass = 'myapp.Main' - } - $runShadow { - args 'foo' - } - """.trimIndent(), - ) - - run(shadowJarTask) - val result = run(shadowJarTask) - - result.assertCcReused() - } - - @Test - fun configurationCachingSupportsExcludes() { - projectScriptPath.appendText( - """ - $shadowJar { - exclude 'a2.properties' - } - """.trimIndent(), - ) - - run(shadowJarTask) - outputShadowJar.deleteExisting() - val result = run(shadowJarTask) - - assertThat(outputShadowJar).containsEntries( - "a.properties", - "b.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "a2.properties", - ) - result.assertCcReused() - } - - @Test - fun configurationCachingSupportsMinimize() { - writeClientAndServerModules( - serverShadowBlock = """ - minimize { - exclude(dependency('junit:junit:.*')) - } - """.trimIndent(), - ) - - run(serverShadowJarTask) - outputServerShadowJar.deleteExisting() - val result = run(serverShadowJarTask) - - assertThat(outputServerShadowJar).containsEntries( - "server/Server.class", - "junit/framework/Test.class", - ) - assertThat(outputServerShadowJar).doesNotContainEntries( - "client/Client.class", - ) - result.assertCcReused() - } - - @Test - fun configurationCachingOfConfigurationsIsUpToDate() { - settingsScriptPath.appendText( - """ - include 'lib' - """.trimIndent(), - ) - - path("lib/src/main/java/lib/Lib.java").writeText( - """ - package lib; - public class Lib {} - """.trimIndent(), - ) - path("lib/build.gradle").writeText( - """ - ${getDefaultProjectBuildScript()} - dependencies { - implementation 'junit:junit:3.8.2' - } - $shadowJar { - configurations = [project.configurations.compileClasspath] - } - """.trimIndent(), - ) - - val libShadowJarTask = ":lib:$SHADOW_JAR_TASK_NAME" - run(libShadowJarTask) - val result = run(libShadowJarTask) - - assertThat(result.task(libShadowJarTask)).isNotNull() - .transform { it.outcome }.isEqualTo(TaskOutcome.UP_TO_DATE) - result.assertCcReused() - } - - private fun BuildResult.assertCcReused() { - assertThat(output).contains("Reusing configuration cache.") - } -} diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt index bb1343508..39d67354f 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt @@ -773,6 +773,42 @@ class ShadowPluginTest : BasePluginTest() { assertThat(testJar.getEntry("junit")).isNotNull() } + @Test + fun configurationCachingOfConfigurationsIsUpToDate() { + settingsScriptPath.appendText( + """ + include 'lib' + """.trimIndent(), + ) + projectScriptPath.writeText("") + + path("lib/src/main/java/lib/Lib.java").writeText( + """ + package lib; + public class Lib {} + """.trimIndent(), + ) + path("lib/build.gradle").writeText( + """ + ${getDefaultProjectBuildScript()} + dependencies { + implementation 'junit:junit:3.8.2' + } + $shadowJar { + configurations = [project.configurations.compileClasspath] + } + """.trimIndent(), + ) + + val libShadowJarTask = ":lib:$SHADOW_JAR_TASK_NAME" + run(libShadowJarTask) + val result = run(libShadowJarTask) + + assertThat(result.task(libShadowJarTask)).isNotNull() + .transform { it.outcome }.isEqualTo(TaskOutcome.UP_TO_DATE) + assertThat(result.output).contains("Reusing configuration cache.") + } + private fun writeShadowedClientAndServerModules() { writeClientAndServerModules() path("client/build.gradle").appendText( From 8f92d7d6d501b8c5bf2b9111e602afa1500b6b8f Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 15:59:04 +0800 Subject: [PATCH 04/20] Close things in tests --- .../gradle/plugins/shadow/ApplicationTest.kt | 40 ++- .../gradle/plugins/shadow/FilteringTest.kt | 159 +++++----- .../gradle/plugins/shadow/PublishingTest.kt | 38 ++- .../gradle/plugins/shadow/RelocationTest.kt | 237 +++++++------- .../gradle/plugins/shadow/ShadowPluginTest.kt | 294 ++++++++++-------- .../plugins/shadow/caching/BaseCachingTest.kt | 2 +- .../transformers/AppendingTransformerTest.kt | 8 +- .../GroovyExtensionModuleTransformerTest.kt | 38 ++- .../ServiceFileTransformerTest.kt | 73 +++-- .../shadow/transformers/TransformersTest.kt | 48 +-- .../gradle/plugins/shadow/util/JarBuilder.kt | 4 +- .../gradle/plugins/shadow/util/JarPath.kt | 4 +- 12 files changed, 512 insertions(+), 433 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt index 2d05cec1a..c51bbfe40 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt @@ -1,12 +1,14 @@ package com.github.jengelman.gradle.plugins.shadow +import assertk.all import assertk.assertThat import assertk.assertions.contains import assertk.assertions.containsAtLeast import assertk.assertions.exists import assertk.assertions.isEqualTo -import assertk.assertions.isNotEmpty import com.github.jengelman.gradle.plugins.shadow.util.containsEntries +import com.github.jengelman.gradle.plugins.shadow.util.isRegular +import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText @@ -36,23 +38,27 @@ class ApplicationTest : BasePluginTest() { val result = run(runShadowTask) - assertThat(result.output).contains("Running application with JDK 17") - assertThat(result.output).contains("TestApp: Hello World! (foo)") - - val installedJar = jarPath("build/install/myapp-shadow/lib/myapp-1.0-all.jar") - assertThat(installedJar).containsEntries( - "a.properties", - "a2.properties", - "myapp/Main.class", + assertThat(result.output).contains( + "Running application with JDK 17", + "TestApp: Hello World! (foo)", ) - assertThat(installedJar.manifest.mainAttributes.getValue("Main-Class")) - .isEqualTo("myapp.Main") - path("build/install/myapp-shadow/bin/myapp").let { startScript -> - assertThat(startScript).exists() - assertThat(startScript.readText()).contains("CLASSPATH=\$APP_HOME/lib/myapp-1.0-all.jar") - assertThat(startScript.readText()).contains("-jar \"\\\"\$CLASSPATH\\\"\" \"\$APP_ARGS\"") - assertThat(startScript.readText()).contains("exec \"\$JAVACMD\" \"\$@\"") + assertThat(jarPath("build/install/myapp-shadow/lib/myapp-1.0-all.jar")).useAll { + containsEntries( + "a.properties", + "a2.properties", + "myapp/Main.class", + ) + transform { it.manifest.mainAttributes.getValue("Main-Class") }.isEqualTo("myapp.Main") + } + + assertThat(path("build/install/myapp-shadow/bin/myapp")).all { + exists() + transform { it.readText() }.contains( + "CLASSPATH=\$APP_HOME/lib/myapp-1.0-all.jar", + "-jar \"\\\"\$CLASSPATH\\\"\" \"\$APP_ARGS\"", + "exec \"\$JAVACMD\" \"\$@\"", + ) } } @@ -84,7 +90,7 @@ class ApplicationTest : BasePluginTest() { run(ShadowApplicationPlugin.SHADOW_INSTALL_TASK_NAME) - assertThat(jarPath("build/install/myapp-shadow/lib/myapp-1.0-all.jar").entries().toList()).isNotEmpty() + assertThat(jarPath("build/install/myapp-shadow/lib/myapp-1.0-all.jar")).isRegular() } private fun prepare( diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt index a79f2b32c..32f835ead 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt @@ -5,6 +5,7 @@ import assertk.assertions.isEqualTo import assertk.assertions.isNotNull import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries +import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText @@ -36,11 +37,13 @@ class FilteringTest : BasePluginTest() { @Test fun includeAllDependencies() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "a.properties", - "a2.properties", - "b.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a.properties", + "a2.properties", + "b.properties", + ) + } } @Test @@ -55,13 +58,15 @@ class FilteringTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "a.properties", - "b.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "a2.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a.properties", + "b.properties", + ) + doesNotContainEntries( + "a2.properties", + ) + } } @Test @@ -108,15 +113,17 @@ class FilteringTest : BasePluginTest() { assertThat(result.task(shadowJarTask)).isNotNull() .transform { it.outcome }.isEqualTo(TaskOutcome.SUCCESS) - assertThat(outputShadowJar).containsEntries( - "a.properties", - "a2.properties", - "b.properties", - "d.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "c.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a.properties", + "a2.properties", + "b.properties", + "d.properties", + ) + doesNotContainEntries( + "c.properties", + ) + } } @Test @@ -135,15 +142,17 @@ class FilteringTest : BasePluginTest() { assertThat(result.task(shadowJarTask)).isNotNull() .transform { it.outcome }.isEqualTo(TaskOutcome.SUCCESS) - assertThat(outputShadowJar).containsEntries( - "a2.properties", - "b.properties", - "c.properties", - "d.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "a.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a2.properties", + "b.properties", + "c.properties", + "d.properties", + ) + doesNotContainEntries( + "a.properties", + ) + } } @Test @@ -169,16 +178,18 @@ class FilteringTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "d.properties", - "shadow/Passed.class", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "a.properties", - "a2.properties", - "b.properties", - "c.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "d.properties", + "shadow/Passed.class", + ) + doesNotContainEntries( + "a.properties", + "a2.properties", + "b.properties", + "c.properties", + ) + } } @Test @@ -193,13 +204,15 @@ class FilteringTest : BasePluginTest() { run(serverShadowJarTask) - assertThat(outputServerShadowJar).doesNotContainEntries( - "client/Client.class", - ) - assertThat(outputServerShadowJar).containsEntries( - "server/Server.class", - "junit/framework/Test.class", - ) + assertThat(outputServerShadowJar).useAll { + doesNotContainEntries( + "client/Client.class", + ) + containsEntries( + "server/Server.class", + "junit/framework/Test.class", + ) + } } @Test @@ -214,13 +227,15 @@ class FilteringTest : BasePluginTest() { run(serverShadowJarTask) - assertThat(outputServerShadowJar).doesNotContainEntries( - "junit/framework/Test.class", - ) - assertThat(outputServerShadowJar).containsEntries( - "client/Client.class", - "server/Server.class", - ) + assertThat(outputServerShadowJar).useAll { + doesNotContainEntries( + "junit/framework/Test.class", + ) + containsEntries( + "client/Client.class", + "server/Server.class", + ) + } } @Test @@ -237,13 +252,15 @@ class FilteringTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "a.properties", - "b.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "a2.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a.properties", + "b.properties", + ) + doesNotContainEntries( + "a2.properties", + ) + } } @Test @@ -272,15 +289,17 @@ class FilteringTest : BasePluginTest() { } private fun commonAssertions() { - assertThat(outputShadowJar).containsEntries( - "a.properties", - "a2.properties", - "b.properties", - "c.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "d.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a.properties", + "a2.properties", + "b.properties", + "c.properties", + ) + doesNotContainEntries( + "d.properties", + ) + } } private fun publishArtifactCD(circular: Boolean = false) { diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index b0fa6f58e..575a83130 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -1,6 +1,5 @@ package com.github.jengelman.gradle.plugins.shadow -import assertk.all import assertk.assertThat import assertk.assertions.containsOnly import assertk.assertions.isEmpty @@ -10,6 +9,7 @@ import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.JarPath import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries +import com.github.jengelman.gradle.plugins.shadow.util.useAll import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory @@ -122,18 +122,18 @@ class PublishingTest : BasePluginTest() { publish() - val publishedJar = repoJarPath("shadow/maven-all/1.0/maven-all-1.0.jar") - assertThat(publishedJar).containsEntries( - "aa.properties", - "aa2.properties", - ) - assertThat(publishedJar).doesNotContainEntries( - "a.properties", - "a2.properties", - "b.properties", - "bb.properties", - ) - + assertThat(repoJarPath("shadow/maven-all/1.0/maven-all-1.0.jar")).useAll { + containsEntries( + "aa.properties", + "aa2.properties", + ) + doesNotContainEntries( + "a.properties", + "a2.properties", + "b.properties", + "bb.properties", + ) + } assertPomCommon(repoPath("shadow/maven-all/1.0/maven-all-1.0.pom")) } @@ -165,8 +165,12 @@ class PublishingTest : BasePluginTest() { publish() val entries = arrayOf("a.properties", "a2.properties", "b.properties") - assertThat(repoJarPath("com/acme/maven/1.0/maven-1.0.jar")).doesNotContainEntries(*entries) - assertThat(repoJarPath("com/acme/maven/1.0/maven-1.0-all.jar")).containsEntries(*entries) + assertThat(repoJarPath("com/acme/maven/1.0/maven-1.0.jar")).useAll { + doesNotContainEntries(*entries) + } + assertThat(repoJarPath("com/acme/maven/1.0/maven-1.0-all.jar")).useAll { + containsEntries(*entries) + } pomReader.read(repoPath("com/acme/maven/1.0/maven-1.0.pom")).let { pomContents -> assertThat(pomContents.dependencies.size).isEqualTo(2) @@ -278,7 +282,7 @@ class PublishingTest : BasePluginTest() { } private fun assertShadowJarCommon(jarPath: JarPath) { - assertThat(jarPath).all { + assertThat(jarPath).useAll { containsEntries( "a.properties", "a2.properties", @@ -290,7 +294,7 @@ class PublishingTest : BasePluginTest() { } private companion object { - fun MavenXpp3Reader.read(path: Path): Model = read(path.inputStream()) + fun MavenXpp3Reader.read(path: Path): Model = path.inputStream().use { read(it) } fun JsonAdapter.fromJson(path: Path): T = requireNotNull(fromJson(path.readText())) } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 8571c350d..61c26aeb2 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -8,10 +8,13 @@ import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHA import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries +import com.github.jengelman.gradle.plugins.shadow.util.useAll import java.net.URLClassLoader import kotlin.io.path.appendText import kotlin.io.path.writeText import org.junit.jupiter.api.Test +import org.junit.jupiter.api.fail +import org.opentest4j.AssertionFailedError class RelocationTest : BasePluginTest() { @@ -27,26 +30,29 @@ class RelocationTest : BasePluginTest() { } """.trimIndent(), ) + run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "META-INF/MANIFEST.MF", - "shadow/junit/textui/ResultPrinter.class", - "shadow/junit/textui/TestRunner.class", - "shadow/junit/framework/Assert.class", - "shadow/junit/framework/AssertionFailedError.class", - "shadow/junit/framework/ComparisonCompactor.class", - "shadow/junit/framework/ComparisonFailure.class", - "shadow/junit/framework/Protectable.class", - "shadow/junit/framework/Test.class", - "shadow/junit/framework/TestCase.class", - "shadow/junit/framework/TestFailure.class", - "shadow/junit/framework/TestListener.class", - "shadow/junit/framework/TestResult$1.class", - "shadow/junit/framework/TestResult.class", - "shadow/junit/framework/TestSuite$1.class", - "shadow/junit/framework/TestSuite.class", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "META-INF/MANIFEST.MF", + "shadow/junit/textui/ResultPrinter.class", + "shadow/junit/textui/TestRunner.class", + "shadow/junit/framework/Assert.class", + "shadow/junit/framework/AssertionFailedError.class", + "shadow/junit/framework/ComparisonCompactor.class", + "shadow/junit/framework/ComparisonFailure.class", + "shadow/junit/framework/Protectable.class", + "shadow/junit/framework/Test.class", + "shadow/junit/framework/TestCase.class", + "shadow/junit/framework/TestFailure.class", + "shadow/junit/framework/TestListener.class", + "shadow/junit/framework/TestResult$1.class", + "shadow/junit/framework/TestResult.class", + "shadow/junit/framework/TestSuite$1.class", + "shadow/junit/framework/TestSuite.class", + ) + } } @Issue( @@ -71,44 +77,44 @@ class RelocationTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "META-INF/MANIFEST.MF", - "a/ResultPrinter.class", - "a/TestRunner.class", - "b/Assert.class", - "b/AssertionFailedError.class", - "b/ComparisonCompactor.class", - "b/ComparisonFailure.class", - "b/Protectable.class", - "b/Test.class", - "b/TestCase.class", - "b/TestFailure.class", - "b/TestListener.class", - "b/TestResult\$1.class", - "b/TestResult.class", - "b/TestSuite\$1.class", - "b/TestSuite.class", - ) - - assertThat(outputShadowJar).doesNotContainEntries( - "junit/textui/ResultPrinter.class", - "junit/textui/TestRunner.class", - "junit/framework/Assert.class", - "junit/framework/AssertionFailedError.class", - "junit/framework/ComparisonCompactor.class", - "junit/framework/ComparisonFailure.class", - "junit/framework/Protectable.class", - "junit/framework/Test.class", - "junit/framework/TestCase.class", - "junit/framework/TestFailure.class", - "junit/framework/TestListener.class", - "junit/framework/TestResult\$1.class", - "junit/framework/TestResult.class", - "junit/framework/TestSuite\$1.class", - "junit/framework/TestSuite.class", - ) - assertThat(outputShadowJar.manifest.mainAttributes.getValue("TEST-VALUE")) - .isEqualTo("FOO") + assertThat(outputShadowJar).useAll { + containsEntries( + "META-INF/MANIFEST.MF", + "a/ResultPrinter.class", + "a/TestRunner.class", + "b/Assert.class", + "b/AssertionFailedError.class", + "b/ComparisonCompactor.class", + "b/ComparisonFailure.class", + "b/Protectable.class", + "b/Test.class", + "b/TestCase.class", + "b/TestFailure.class", + "b/TestListener.class", + "b/TestResult\$1.class", + "b/TestResult.class", + "b/TestSuite\$1.class", + "b/TestSuite.class", + ) + doesNotContainEntries( + "junit/textui/ResultPrinter.class", + "junit/textui/TestRunner.class", + "junit/framework/Assert.class", + "junit/framework/AssertionFailedError.class", + "junit/framework/ComparisonCompactor.class", + "junit/framework/ComparisonFailure.class", + "junit/framework/Protectable.class", + "junit/framework/Test.class", + "junit/framework/TestCase.class", + "junit/framework/TestFailure.class", + "junit/framework/TestListener.class", + "junit/framework/TestResult\$1.class", + "junit/framework/TestResult.class", + "junit/framework/TestSuite\$1.class", + "junit/framework/TestSuite.class", + ) + transform { it.manifest.mainAttributes.getValue("TEST-VALUE") }.isEqualTo("FOO") + } } @Test @@ -131,32 +137,33 @@ class RelocationTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "a/ResultPrinter.class", - "b/Test.class", - "b/TestCase.class", - "b/TestFailure.class", - "b/TestListener.class", - "b/TestResult\$1.class", - "b/TestResult.class", - "b/TestSuite\$1.class", - "b/TestSuite.class", - "junit/textui/TestRunner.class", - "junit/framework/Assert.class", - "junit/framework/AssertionFailedError.class", - "junit/framework/ComparisonCompactor.class", - "junit/framework/ComparisonFailure.class", - "junit/framework/Protectable.class", - ) - - assertThat(outputShadowJar).doesNotContainEntries( - "a/TestRunner.class", - "b/Assert.class", - "b/AssertionFailedError.class", - "b/ComparisonCompactor.class", - "b/ComparisonFailure.class", - "b/Protectable.class", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a/ResultPrinter.class", + "b/Test.class", + "b/TestCase.class", + "b/TestFailure.class", + "b/TestListener.class", + "b/TestResult\$1.class", + "b/TestResult.class", + "b/TestSuite\$1.class", + "b/TestSuite.class", + "junit/textui/TestRunner.class", + "junit/framework/Assert.class", + "junit/framework/AssertionFailedError.class", + "junit/framework/ComparisonCompactor.class", + "junit/framework/ComparisonFailure.class", + "junit/framework/Protectable.class", + ) + doesNotContainEntries( + "a/TestRunner.class", + "b/Assert.class", + "b/AssertionFailedError.class", + "b/ComparisonCompactor.class", + "b/ComparisonFailure.class", + "b/Protectable.class", + ) + } } @Issue( @@ -192,16 +199,17 @@ class RelocationTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "shadow/ShadowTest.class", - "shadow/junit/Test.class", - "shadow/junit", - ) - - assertThat(outputShadowJar).doesNotContainEntries( - "junit/framework", - "junit/framework/Test.class", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "shadow/ShadowTest.class", + "shadow/junit/Test.class", + "shadow/junit", + ) + doesNotContainEntries( + "junit/framework", + "junit/framework/Test.class", + ) + } val classLoader = URLClassLoader( arrayOf(outputShadowJar.toUri().toURL()), @@ -211,8 +219,8 @@ class RelocationTest : BasePluginTest() { // check that the class can be loaded. If the file was not relocated properly, we should get a NoDefClassFound // Isolated class loader with only the JVM system jars and the output jar from the test project classLoader.loadClass("shadow.ShadowTest") - error("Should not reach here.") - }.isInstanceOf(IllegalStateException::class) + fail("Should not reach here.") + }.isInstanceOf(AssertionFailedError::class) } @Test @@ -272,15 +280,16 @@ class RelocationTest : BasePluginTest() { run(":app:$SHADOW_JAR_TASK_NAME") - val appOutput = jarPath("app/build/libs/app-all.jar") - assertThat(appOutput).containsEntries( - "TEST", - "APP-TEST", - "test.properties", - "app/core/Core.class", - "app/App.class", - "app/junit/framework/Test.class", - ) + assertThat(jarPath("app/build/libs/app-all.jar")).useAll { + containsEntries( + "TEST", + "APP-TEST", + "test.properties", + "app/core/Core.class", + "app/App.class", + "app/junit/framework/Test.class", + ) + } } @Issue( @@ -316,16 +325,18 @@ class RelocationTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "bar/Foo.class", - "bar/foo.properties", - "bar/dep.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "foo/Foo.class", - "foo/foo.properties", - "foo/dep.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "bar/Foo.class", + "bar/foo.properties", + "bar/dep.properties", + ) + doesNotContainEntries( + "foo/Foo.class", + "foo/foo.properties", + "foo/dep.properties", + ) + } } @Issue( diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt index 39d67354f..1171550da 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt @@ -13,6 +13,7 @@ import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries import com.github.jengelman.gradle.plugins.shadow.util.isRegular +import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText @@ -120,15 +121,13 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - val outputShadowJar = jarPath("build/libs/shadow.jar") - - assertThat(outputShadowJar).containsEntries( - "shadow/Passed.class", - "junit/framework/Test.class", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "/", - ) + assertThat(jarPath("build/libs/shadow.jar")).useAll { + containsEntries( + "shadow/Passed.class", + "junit/framework/Test.class", + ) + doesNotContainEntries("/") + } } @Test @@ -137,11 +136,13 @@ class ShadowPluginTest : BasePluginTest() { run(serverShadowJarTask) - assertThat(outputServerShadowJar).containsEntries( - "client/Client.class", - "server/Server.class", - "junit/framework/Test.class", - ) + assertThat(outputServerShadowJar).useAll { + containsEntries( + "client/Client.class", + "server/Server.class", + "junit/framework/Test.class", + ) + } } /** @@ -168,13 +169,15 @@ class ShadowPluginTest : BasePluginTest() { run(serverShadowJarTask) - assertThat(outputServerShadowJar).containsEntries( - "client/Client.class", - "server/Server.class", - ) - assertThat(outputServerShadowJar).doesNotContainEntries( - "junit/framework/Test.class", - ) + assertThat(outputServerShadowJar).useAll { + containsEntries( + "client/Client.class", + "server/Server.class", + ) + doesNotContainEntries( + "junit/framework/Test.class", + ) + } } /** @@ -194,13 +197,15 @@ class ShadowPluginTest : BasePluginTest() { run(serverShadowJarTask) - assertThat(outputServerShadowJar).containsEntries( - "server/Server.class", - "junit/framework/Test.class", - ) - assertThat(outputServerShadowJar).doesNotContainEntries( - "client/Client.class", - ) + assertThat(outputServerShadowJar).useAll { + containsEntries( + "server/Server.class", + "junit/framework/Test.class", + ) + doesNotContainEntries( + "client/Client.class", + ) + } } /** @@ -219,10 +224,12 @@ class ShadowPluginTest : BasePluginTest() { run(serverShadowJarTask) - assertThat(outputServerShadowJar).containsEntries( - "client/Client.class", - "server/Server.class", - ) + assertThat(outputServerShadowJar).useAll { + containsEntries( + "client/Client.class", + "server/Server.class", + ) + } } /** @@ -250,11 +257,13 @@ class ShadowPluginTest : BasePluginTest() { run(serverShadowJarTask) - assertThat(outputServerShadowJar).containsEntries( - "client/Client.class", - "server/Server.class", - "junit/framework/TestCase.class", - ) + assertThat(outputServerShadowJar).useAll { + containsEntries( + "client/Client.class", + "server/Server.class", + "junit/framework/TestCase.class", + ) + } path("client/src/main/java/client/Client.java").writeText( """ @@ -266,11 +275,13 @@ class ShadowPluginTest : BasePluginTest() { // TODO: I don't think junit classes should be in the output jar, but it's the test case // from https://github.com/GradleUp/shadow/pull/420, need to investigate more... - assertThat(outputServerShadowJar).containsEntries( - "client/Client.class", - "server/Server.class", - "junit/framework/TestCase.class", - ) + assertThat(outputServerShadowJar).useAll { + containsEntries( + "client/Client.class", + "server/Server.class", + "junit/framework/TestCase.class", + ) + } } /** @@ -284,17 +295,18 @@ class ShadowPluginTest : BasePluginTest() { run(":impl:$SHADOW_JAR_TASK_NAME") - val implOutput = jarPath("impl/build/libs/impl-all.jar") - assertThat(implOutput).containsEntries( - "impl/SimpleEntity.class", - "api/Entity.class", - "api/UnusedEntity.class", - "lib/LibEntity.class", - ) - assertThat(implOutput).doesNotContainEntries( - "junit/framework/Test.class", - "lib/UnusedLibEntity.class", - ) + assertThat(jarPath("impl/build/libs/impl-all.jar")).useAll { + containsEntries( + "impl/SimpleEntity.class", + "api/Entity.class", + "api/UnusedEntity.class", + "lib/LibEntity.class", + ) + doesNotContainEntries( + "junit/framework/Test.class", + "lib/UnusedLibEntity.class", + ) + } } /** @@ -317,17 +329,18 @@ class ShadowPluginTest : BasePluginTest() { run(":impl:$SHADOW_JAR_TASK_NAME") - val implOutput = jarPath("impl/build/libs/impl-all.jar") - assertThat(implOutput).containsEntries( - "impl/SimpleEntity.class", - "api/Entity.class", - "api/UnusedEntity.class", - "lib/LibEntity.class", - "lib/UnusedLibEntity.class", - ) - assertThat(implOutput).doesNotContainEntries( - "junit/framework/Test.class", - ) + assertThat(jarPath("impl/build/libs/impl-all.jar")).useAll { + containsEntries( + "impl/SimpleEntity.class", + "api/Entity.class", + "api/UnusedEntity.class", + "lib/LibEntity.class", + "lib/UnusedLibEntity.class", + ) + doesNotContainEntries( + "junit/framework/Test.class", + ) + } } @Test @@ -336,21 +349,22 @@ class ShadowPluginTest : BasePluginTest() { run(":server:jar") - val serverOutput = jarPath("server/build/libs/server-1.0.jar") - assertThat(serverOutput).containsEntries( - "server/Server.class", - ) - assertThat(serverOutput).doesNotContainEntries( - "client/Client.class", - "junit/framework/Test.class", - "client/junit/framework/Test.class", - ) - - val clientOutput = jarPath("client/build/libs/client-all.jar") - assertThat(clientOutput).containsEntries( - "client/Client.class", - "client/junit/framework/Test.class", - ) + assertThat(jarPath("server/build/libs/server-1.0.jar")).useAll { + containsEntries( + "server/Server.class", + ) + doesNotContainEntries( + "client/Client.class", + "junit/framework/Test.class", + "client/junit/framework/Test.class", + ) + } + assertThat(jarPath("client/build/libs/client-all.jar")).useAll { + containsEntries( + "client/Client.class", + "client/junit/framework/Test.class", + ) + } } @Test @@ -359,20 +373,22 @@ class ShadowPluginTest : BasePluginTest() { run(serverShadowJarTask) - assertThat(outputServerShadowJar).containsEntries( - "client/Client.class", - "client/junit/framework/Test.class", - "server/Server.class", - ) - assertThat(outputServerShadowJar).doesNotContainEntries( - "junit/framework/Test.class", - ) - - val clientOutput = jarPath("client/build/libs/client-all.jar") - assertThat(clientOutput).containsEntries( - "client/Client.class", - "client/junit/framework/Test.class", - ) + assertThat(outputServerShadowJar).useAll { + containsEntries( + "client/Client.class", + "client/junit/framework/Test.class", + "server/Server.class", + ) + doesNotContainEntries( + "junit/framework/Test.class", + ) + } + assertThat(jarPath("client/build/libs/client-all.jar")).useAll { + containsEntries( + "client/Client.class", + "client/junit/framework/Test.class", + ) + } } @Test @@ -405,18 +421,20 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "shadow/Passed.class", - "a.properties", - "META-INF/a.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "META-INF/INDEX.LIST", - "META-INF/a.SF", - "META-INF/a.DSA", - "META-INF/a.RSA", - "module-info.class", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "shadow/Passed.class", + "a.properties", + "META-INF/a.properties", + ) + doesNotContainEntries( + "META-INF/INDEX.LIST", + "META-INF/a.SF", + "META-INF/a.DSA", + "META-INF/a.RSA", + "module-info.class", + ) + } } @Test @@ -432,13 +450,15 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "a.properties", - "a2.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "b.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a.properties", + "a2.properties", + ) + doesNotContainEntries( + "b.properties", + ) + } } @Test @@ -475,12 +495,14 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "api.properties", - "implementation.properties", - "runtimeOnly.properties", - "implementation-dep.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "api.properties", + "implementation.properties", + "runtimeOnly.properties", + "implementation-dep.properties", + ) + } } @Test @@ -496,13 +518,15 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar).containsEntries( - "a.properties", - "a2.properties", - ) - assertThat(outputShadowJar).doesNotContainEntries( - "b.properties", - ) + assertThat(outputShadowJar).useAll { + containsEntries( + "a.properties", + "a2.properties", + ) + doesNotContainEntries( + "b.properties", + ) + } } @Test @@ -528,7 +552,8 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar.entries().toList().size).isEqualTo(2) + val entries = outputShadowJar.use { it.entries().toList() } + assertThat(entries.size).isEqualTo(2) } @Test @@ -543,7 +568,8 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar.manifest.mainAttributes.getValue("Class-Path")).isNull() + val value = outputShadowJar.use { it.manifest.mainAttributes.getValue("Class-Path") } + assertThat(value).isNull() } @Issue( @@ -566,8 +592,8 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar.manifest.mainAttributes.getValue("Class-Path")) - .isEqualTo("/libs/a.jar junit-3.8.2.jar") + val value = outputShadowJar.use { it.manifest.mainAttributes.getValue("Class-Path") } + assertThat(value).isEqualTo("/libs/a.jar junit-3.8.2.jar") } @Issue( @@ -585,8 +611,8 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - assertThat(outputShadowJar.manifest.mainAttributes.getValue("Class-Path")) - .isEqualTo("junit-3.8.2.jar") + val value = outputShadowJar.use { it.manifest.mainAttributes.getValue("Class-Path") } + assertThat(value).isEqualTo("junit-3.8.2.jar") } @Issue( @@ -740,7 +766,7 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - val entries = outputShadowJar.entries().toList() + val entries = outputShadowJar.use { it.entries().toList() } assertThat(entries.count { it.name.endsWith(".class") }).isEqualTo(1) } @@ -769,8 +795,8 @@ class ShadowPluginTest : BasePluginTest() { assertThat(result.task(":$testShadowJarTask")).isNotNull() .transform { it.outcome }.isEqualTo(TaskOutcome.SUCCESS) - val testJar = jarPath("build/libs/shadow-1.0-tests.jar") - assertThat(testJar.getEntry("junit")).isNotNull() + val junitEntry = jarPath("build/libs/shadow-1.0-tests.jar").use { it.getEntry("junit") } + assertThat(junitEntry).isNotNull() } @Test diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt index b12cf69fa..daa3418ab 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt @@ -18,7 +18,7 @@ import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.io.TempDir -abstract class BaseCachingTest : BasePluginTest() { +sealed class BaseCachingTest : BasePluginTest() { @TempDir lateinit var alternateDir: Path diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt index 24e12cd09..04807f7f4 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformerTest.kt @@ -25,8 +25,8 @@ class AppendingTransformerTest : BaseTransformerTest() { run(shadowJarTask) - assertThat(outputShadowJar.getContent(ENTRY_TEST_PROPERTIES).trimIndent()) - .isEqualTo(CONTENT_ONE_TWO) + val content = outputShadowJar.use { it.getContent(ENTRY_TEST_PROPERTIES) }.trimIndent() + assertThat(content).isEqualTo(CONTENT_ONE_TWO) } @Test @@ -48,7 +48,7 @@ class AppendingTransformerTest : BaseTransformerTest() { run(shadowJarTask) - assertThat(outputShadowJar.getContent(ENTRY_TEST_PROPERTIES).trimIndent()) - .isEqualTo(CONTENT_ONE_TWO) + val content = outputShadowJar.use { it.getContent(ENTRY_TEST_PROPERTIES) }.trimIndent() + assertThat(content).isEqualTo(CONTENT_ONE_TWO) } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt index 8838f4cd8..c373895f5 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt @@ -11,7 +11,6 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionMo import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer.Companion.PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer.Companion.PATH_LEGACY_GROOVY_EXTENSION_MODULE_DESCRIPTOR import java.nio.file.Path -import java.util.Properties import kotlin.io.path.appendText import org.junit.jupiter.api.Test @@ -26,8 +25,7 @@ class GroovyExtensionModuleTransformerTest : BaseTransformerTest() { run(shadowJarTask) - val props = outputShadowJar.getContent(PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR).toProperties() - commonAssertions(props) + commonAssertions(PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR) } @Test @@ -43,8 +41,7 @@ class GroovyExtensionModuleTransformerTest : BaseTransformerTest() { run(shadowJarTask) - val props = outputShadowJar.getContent(PATH_LEGACY_GROOVY_EXTENSION_MODULE_DESCRIPTOR).toProperties() - commonAssertions(props) + commonAssertions(PATH_LEGACY_GROOVY_EXTENSION_MODULE_DESCRIPTOR) } @Test @@ -60,15 +57,14 @@ class GroovyExtensionModuleTransformerTest : BaseTransformerTest() { run(shadowJarTask) - val props = outputShadowJar.getContent(PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR).toProperties() - commonAssertions(props) + commonAssertions(PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR) } private fun buildJarFoo( - path: String = PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR, + entry: String = PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR, ): Path = buildJar("foo.jar") { insert( - path, + entry, """ $KEY_MODULE_NAME=foo $KEY_MODULE_VERSION=1.0.5 @@ -79,10 +75,10 @@ class GroovyExtensionModuleTransformerTest : BaseTransformerTest() { } private fun buildJarBar( - path: String = PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR, + entry: String = PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR, ): Path = buildJar("bar.jar") { insert( - path, + entry, """ $KEY_MODULE_NAME=bar $KEY_MODULE_VERSION=2.3.5 @@ -92,19 +88,21 @@ class GroovyExtensionModuleTransformerTest : BaseTransformerTest() { ) } + private fun commonAssertions(entry: String) { + val properties = outputShadowJar.use { it.getContent(entry) }.toProperties() + + assertThat(properties.getProperty(KEY_MODULE_NAME)).isEqualTo(MERGED_MODULE_NAME) + assertThat(properties.getProperty(KEY_MODULE_VERSION)).isEqualTo(MERGED_MODULE_VERSION) + assertThat(properties.getProperty(KEY_EXTENSION_CLASSES)) + .isEqualTo("$EXTENSION_CLASSES_FOO,$EXTENSION_CLASSES_BAR") + assertThat(properties.getProperty(KEY_STATIC_EXTENSION_CLASSES)) + .isEqualTo("$STATIC_EXTENSION_CLASSES_FOO,$STATIC_EXTENSION_CLASSES_BAR") + } + private companion object { const val EXTENSION_CLASSES_FOO = "com.acme.foo.FooExtension,com.acme.foo.BarExtension" const val EXTENSION_CLASSES_BAR = "com.acme.bar.SomeExtension,com.acme.bar.AnotherExtension" const val STATIC_EXTENSION_CLASSES_FOO = "com.acme.foo.FooStaticExtension" const val STATIC_EXTENSION_CLASSES_BAR = "com.acme.bar.SomeStaticExtension" - - fun commonAssertions(properties: Properties) { - assertThat(properties.getProperty(KEY_MODULE_NAME)).isEqualTo(MERGED_MODULE_NAME) - assertThat(properties.getProperty(KEY_MODULE_VERSION)).isEqualTo(MERGED_MODULE_VERSION) - assertThat(properties.getProperty(KEY_EXTENSION_CLASSES)) - .isEqualTo("$EXTENSION_CLASSES_FOO,$EXTENSION_CLASSES_BAR") - assertThat(properties.getProperty(KEY_STATIC_EXTENSION_CLASSES)) - .isEqualTo("$STATIC_EXTENSION_CLASSES_FOO,$STATIC_EXTENSION_CLASSES_BAR") - } } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt index 65923952a..57a8fad89 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt @@ -3,6 +3,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.isEqualTo import com.github.jengelman.gradle.plugins.shadow.util.Issue +import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.writeText import org.junit.jupiter.api.Test @@ -22,8 +23,10 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) - assertThat(outputShadowJar.getContent(ENTRY_SERVICES_SHADE)).isEqualTo(CONTENT_ONE_TWO) - assertThat(outputShadowJar.getContent(ENTRY_SERVICES_FOO)).isEqualTo("one") + assertThat(outputShadowJar).useAll { + transform { it.getContent(ENTRY_SERVICES_SHADE) }.isEqualTo(CONTENT_ONE_TWO) + transform { it.getContent(ENTRY_SERVICES_FOO) }.isEqualTo("one") + } } @Test @@ -45,7 +48,8 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) - assertThat(outputShadowJar.getContent(ENTRY_FOO_SHADE)).isEqualTo(CONTENT_ONE_TWO) + val content = outputShadowJar.use { it.getContent(ENTRY_FOO_SHADE) } + assertThat(content).isEqualTo(CONTENT_ONE_TWO) } @Test @@ -63,8 +67,10 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) - assertThat(outputShadowJar.getContent(ENTRY_SERVICES_SHADE)).isEqualTo(CONTENT_ONE_TWO) - assertThat(outputShadowJar.getContent(ENTRY_SERVICES_FOO)).isEqualTo("one") + assertThat(outputShadowJar).useAll { + transform { it.getContent(ENTRY_SERVICES_SHADE) }.isEqualTo(CONTENT_ONE_TWO) + transform { it.getContent(ENTRY_SERVICES_FOO) }.isEqualTo("one") + } } @Test @@ -119,31 +125,31 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) - val text1 = outputShadowJar.getContent("META-INF/services/java.sql.Driver") - assertThat(text1).isEqualTo( - """ - oracle.jdbc.OracleDriver - myapache.hive.jdbc.HiveDriver - myapache.derby.jdbc.AutoloadedDriver - com.mysql.jdbc.Driver - """.trimIndent(), - ) - - val text2 = outputShadowJar.getContent("META-INF/services/myapache.axis.components.compiler.Compiler") - assertThat(text2).isEqualTo( - """ - myapache.axis.components.compiler.Javac - org.apache.axis.components.compiler.Jikes - """.trimIndent(), - ) - - val text3 = outputShadowJar.getContent("META-INF/services/org.apache.commons.logging.LogFactory") - assertThat(text3).isEqualTo( - """ - myapache.commons.logging.impl.LogFactoryImpl - org.mortbay.log.Factory - """.trimIndent(), - ) + assertThat(outputShadowJar).useAll { + transform { it.getContent("META-INF/services/java.sql.Driver") } + .isEqualTo( + """ + oracle.jdbc.OracleDriver + myapache.hive.jdbc.HiveDriver + myapache.derby.jdbc.AutoloadedDriver + com.mysql.jdbc.Driver + """.trimIndent(), + ) + transform { it.getContent("META-INF/services/myapache.axis.components.compiler.Compiler") } + .isEqualTo( + """ + myapache.axis.components.compiler.Javac + org.apache.axis.components.compiler.Jikes + """.trimIndent(), + ) + transform { it.getContent("META-INF/services/org.apache.commons.logging.LogFactory") } + .isEqualTo( + """ + myapache.commons.logging.impl.LogFactoryImpl + org.mortbay.log.Factory + """.trimIndent(), + ) + } } @Test @@ -165,7 +171,8 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) - assertThat(outputShadowJar.getContent(ENTRY_FOO_SHADE)).isEqualTo(CONTENT_ONE_TWO) + val content = outputShadowJar.use { it.getContent(ENTRY_FOO_SHADE) } + assertThat(content).isEqualTo(CONTENT_ONE_TWO) } @Issue( @@ -199,7 +206,7 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) - assertThat(outputShadowJar.getContent(servicesShadowEntry)) - .isEqualTo(CONTENT_THREE + "\n" + CONTENT_ONE_TWO) + val content = outputShadowJar.use { it.getContent(servicesShadowEntry) } + assertThat(content).isEqualTo(CONTENT_THREE + "\n" + CONTENT_ONE_TWO) } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt index 425828c5d..0190d653c 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt @@ -6,6 +6,7 @@ import assertk.assertions.isNotNull import assertk.assertions.isNull import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.isRegular +import java.util.jar.Attributes import kotlin.io.path.appendText import kotlin.io.path.writeText import kotlin.reflect.KClass @@ -31,10 +32,10 @@ class TransformersTest : BaseTransformerTest() { run(shadowJarTask) - val mf = outputShadowJar.manifest - assertThat(mf).isNotNull() - assertThat(mf.mainAttributes.getValue("Test-Entry")).isEqualTo("PASSED") - assertThat(mf.mainAttributes.getValue("Main-Class")).isEqualTo("shadow.Main") + commonAssertions { + assertThat(getValue("Test-Entry")).isEqualTo("PASSED") + assertThat(getValue("Main-Class")).isEqualTo("shadow.Main") + } } @Test @@ -45,11 +46,7 @@ class TransformersTest : BaseTransformerTest() { run(shadowJarTask) - val mf = outputShadowJar.manifest - assertThat(mf).isNotNull() - assertThat(mf.mainAttributes.getValue("Test-Entry")).isEqualTo("PASSED") - assertThat(mf.mainAttributes.getValue("Main-Class")).isEqualTo("shadow.Main") - assertThat(mf.mainAttributes.getValue("New-Entry")).isEqualTo("NEW") + commonAssertions() } @Test @@ -81,7 +78,8 @@ class TransformersTest : BaseTransformerTest() { run(shadowJarTask) - assertThat(outputShadowJar.getContent(propertiesXml).trimIndent()).isEqualTo( + val content = outputShadowJar.use { it.getContent(propertiesXml) }.trimIndent() + assertThat(content).isEqualTo( """ @@ -101,17 +99,13 @@ class TransformersTest : BaseTransformerTest() { run("jar", shadowJarTask) - val mf1 = outputShadowJar.manifest - assertThat(mf1).isNotNull() - assertThat(mf1.mainAttributes.getValue("Test-Entry")).isEqualTo("PASSED") - assertThat(mf1.mainAttributes.getValue("Main-Class")).isEqualTo("shadow.Main") - assertThat(mf1.mainAttributes.getValue("New-Entry")).isEqualTo("NEW") - - val mf2 = jarPath("build/libs/shadow-1.0.jar").manifest - assertThat(mf2).isNotNull() - assertThat(mf2.mainAttributes.getValue("Test-Entry")).isEqualTo("FAILED") - assertThat(mf2.mainAttributes.getValue("Main-Class")).isEqualTo("shadow.Main") - assertThat(mf2.mainAttributes.getValue("New-Entry")).isNull() + commonAssertions() + + val mf = jarPath("build/libs/shadow-1.0.jar").use { it.manifest } + assertThat(mf).isNotNull() + assertThat(mf.mainAttributes.getValue("Test-Entry")).isEqualTo("FAILED") + assertThat(mf.mainAttributes.getValue("Main-Class")).isEqualTo("shadow.Main") + assertThat(mf.mainAttributes.getValue("New-Entry")).isNull() } @ParameterizedTest @@ -134,6 +128,18 @@ class TransformersTest : BaseTransformerTest() { assertThat(outputShadowJar).isRegular() } + private fun commonAssertions( + mainAttributesBlock: Attributes.() -> Unit = { + assertThat(getValue("Test-Entry")).isEqualTo("PASSED") + assertThat(getValue("Main-Class")).isEqualTo("shadow.Main") + assertThat(getValue("New-Entry")).isEqualTo("NEW") + }, + ) { + val mf = outputShadowJar.use { it.manifest } + assertThat(mf).isNotNull() + mainAttributesBlock(mf.mainAttributes) + } + private companion object { val MANIFEST_ATTRS = """ jar { diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt index d59675a6c..12c362504 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt @@ -12,8 +12,8 @@ class JarBuilder( private val entries = mutableSetOf() private val jos = JarOutputStream(outputPath.outputStream()) - fun insert(path: String, content: String): JarBuilder = apply { - contents[path] = content + fun insert(entry: String, content: String): JarBuilder = apply { + contents[entry] = content } fun write(): Path { diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt index d7f7b3c9f..41bbd0a32 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt @@ -37,7 +37,9 @@ fun Assert.useAll(body: Assert.() -> Unit) = all { /** * Common regular assertions for [JarPath]. */ -fun Assert.isRegular() = transform { it.entries().toList() }.isNotEmpty() +fun Assert.isRegular() = useAll { + transform { it.entries().toList() }.isNotEmpty() +} fun Assert.containsEntries(vararg entries: String) = transform { actual -> entries.forEach { entry -> From 735d8c4a7e6d322db2e1df2fa91dbc2f9fd0fee8 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 16:49:43 +0800 Subject: [PATCH 05/20] Simplify task outcome checks --- .../jengelman/gradle/plugins/shadow/BasePluginTest.kt | 8 ++++++++ .../jengelman/gradle/plugins/shadow/FilteringTest.kt | 10 +++------- .../gradle/plugins/shadow/ShadowPluginTest.kt | 9 ++++----- .../gradle/plugins/shadow/caching/BaseCachingTest.kt | 5 +---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt index 01779d04d..d46179438 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -1,5 +1,8 @@ package com.github.jengelman.gradle.plugins.shadow +import assertk.Assert +import assertk.assertions.isEqualTo +import assertk.assertions.isNotNull import com.github.jengelman.gradle.plugins.shadow.ShadowApplicationPlugin.Companion.SHADOW_RUN_TASK_NAME import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHADOW_JAR_TASK_NAME import com.github.jengelman.gradle.plugins.shadow.tasks.JavaJarExec @@ -24,6 +27,7 @@ import kotlin.io.path.toPath import kotlin.io.path.writeText import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach @@ -363,6 +367,10 @@ abstract class BasePluginTest { } } + fun Assert.taskOutcomeEquals(taskPath: String, expectedOutcome: TaskOutcome) { + return transform { it.task(taskPath)?.outcome }.isNotNull().isEqualTo(expectedOutcome) + } + private fun containsDeprecationWarning(output: String): Boolean { return output.contains("has been deprecated and is scheduled to be removed in Gradle") || output.contains("has been deprecated. This is scheduled to be removed in Gradle") diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt index 32f835ead..219c8e587 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt @@ -1,15 +1,13 @@ package com.github.jengelman.gradle.plugins.shadow import assertk.assertThat -import assertk.assertions.isEqualTo -import assertk.assertions.isNotNull import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText -import org.gradle.testkit.runner.TaskOutcome +import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -111,8 +109,7 @@ class FilteringTest : BasePluginTest() { projectScriptPath.writeText(replaced) val result = run(shadowJarTask) - assertThat(result.task(shadowJarTask)).isNotNull() - .transform { it.outcome }.isEqualTo(TaskOutcome.SUCCESS) + assertThat(result).taskOutcomeEquals(shadowJarTask, SUCCESS) assertThat(outputShadowJar).useAll { containsEntries( "a.properties", @@ -140,8 +137,7 @@ class FilteringTest : BasePluginTest() { val result = run(shadowJarTask) - assertThat(result.task(shadowJarTask)).isNotNull() - .transform { it.outcome }.isEqualTo(TaskOutcome.SUCCESS) + assertThat(result).taskOutcomeEquals(shadowJarTask, SUCCESS) assertThat(outputShadowJar).useAll { containsEntries( "a2.properties", diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt index 1171550da..0ae67d064 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt @@ -19,7 +19,8 @@ import kotlin.io.path.readText import kotlin.io.path.writeText import org.gradle.api.plugins.JavaPlugin import org.gradle.testfixtures.ProjectBuilder -import org.gradle.testkit.runner.TaskOutcome +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.EnabledForJreRange @@ -793,8 +794,7 @@ class ShadowPluginTest : BasePluginTest() { val result = run(testShadowJarTask) - assertThat(result.task(":$testShadowJarTask")).isNotNull() - .transform { it.outcome }.isEqualTo(TaskOutcome.SUCCESS) + assertThat(result).taskOutcomeEquals(":$testShadowJarTask", SUCCESS) val junitEntry = jarPath("build/libs/shadow-1.0-tests.jar").use { it.getEntry("junit") } assertThat(junitEntry).isNotNull() } @@ -830,8 +830,7 @@ class ShadowPluginTest : BasePluginTest() { run(libShadowJarTask) val result = run(libShadowJarTask) - assertThat(result.task(libShadowJarTask)).isNotNull() - .transform { it.outcome }.isEqualTo(TaskOutcome.UP_TO_DATE) + assertThat(result).taskOutcomeEquals(libShadowJarTask, UP_TO_DATE) assertThat(result.output).contains("Reusing configuration cache.") } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt index daa3418ab..dcad6b239 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt @@ -1,8 +1,6 @@ package com.github.jengelman.gradle.plugins.shadow.caching import assertk.assertThat -import assertk.assertions.isEqualTo -import assertk.assertions.isNotNull import com.github.jengelman.gradle.plugins.shadow.BasePluginTest import java.nio.file.Path import kotlin.io.path.ExperimentalPathApi @@ -77,7 +75,6 @@ sealed class BaseCachingTest : BasePluginTest() { runnerBlock: (GradleRunner) -> GradleRunner = { it }, ) { val result = run("--build-cache", shadowJarTask, runnerBlock = runnerBlock) - assertThat(result.task(shadowJarTask)).isNotNull() - .transform { it.outcome }.isEqualTo(expectedOutcome) + assertThat(result).taskOutcomeEquals(shadowJarTask, expectedOutcome) } } From 2bbe80b97badf7995bc1ef22ae2dfe543c54e6ee Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 16:57:03 +0800 Subject: [PATCH 06/20] Cleanups --- .../gradle/plugins/shadow/ApplicationTest.kt | 1 - .../gradle/plugins/shadow/BasePluginTest.kt | 59 +++++++++---------- .../gradle/plugins/shadow/FilteringTest.kt | 13 ++-- .../gradle/plugins/shadow/PublishingTest.kt | 9 ++- .../gradle/plugins/shadow/RelocationTest.kt | 4 +- .../gradle/plugins/shadow/ShadowPluginTest.kt | 2 - .../plugins/shadow/caching/BaseCachingTest.kt | 5 +- .../shadow/caching/MinimizationCachingTest.kt | 1 - .../shadow/caching/RelocationCachingTest.kt | 1 - .../shadow/caching/ShadowJarCachingTest.kt | 1 - .../shadow/caching/TransformCachingTest.kt | 1 - .../transformers/BaseTransformerTest.kt | 3 +- .../ServiceFileTransformerTest.kt | 2 - .../shadow/transformers/TransformersTest.kt | 1 - .../gradle/plugins/shadow/util/JarPath.kt | 9 +-- 15 files changed, 46 insertions(+), 66 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt index c51bbfe40..54d5fd49c 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt @@ -8,7 +8,6 @@ import assertk.assertions.exists import assertk.assertions.isEqualTo import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.isRegular -import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt index d46179438..f1bcaa73c 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -1,6 +1,9 @@ package com.github.jengelman.gradle.plugins.shadow import assertk.Assert +import assertk.all +import assertk.assertThat +import assertk.assertions.doesNotContain import assertk.assertions.isEqualTo import assertk.assertions.isNotNull import com.github.jengelman.gradle.plugins.shadow.ShadowApplicationPlugin.Companion.SHADOW_RUN_TASK_NAME @@ -10,6 +13,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer import com.github.jengelman.gradle.plugins.shadow.util.AppendableMavenRepository import com.github.jengelman.gradle.plugins.shadow.util.JarPath +import java.io.Closeable import java.nio.file.Path import java.util.Properties import kotlin.io.path.ExperimentalPathApi @@ -35,7 +39,7 @@ import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) abstract class BasePluginTest { - lateinit var root: Path + lateinit var projectRoot: Path lateinit var localRepo: AppendableMavenRepository @BeforeAll @@ -60,7 +64,7 @@ abstract class BasePluginTest { @BeforeEach open fun setup() { - root = createTempDirectory() + projectRoot = createTempDirectory() projectScriptPath.writeText(getDefaultProjectBuildScript(withGroup = true, withVersion = true)) settingsScriptPath.writeText(getDefaultSettingsBuildScript()) @@ -71,7 +75,7 @@ abstract class BasePluginTest { fun cleanup() { runCatching { // TODO: workaround for https://github.com/junit-team/junit5/issues/2811. - root.deleteRecursively() + projectRoot.deleteRecursively() } println(projectScriptPath.readText()) @@ -82,6 +86,15 @@ abstract class BasePluginTest { localRepo.root.deleteRecursively() } + open val shadowJarTask = ":$SHADOW_JAR_TASK_NAME" + open val runShadowTask = ":$SHADOW_RUN_TASK_NAME" + val serverShadowJarTask = ":server:$SHADOW_JAR_TASK_NAME" + + val projectScriptPath: Path get() = path("build.gradle") + val settingsScriptPath: Path get() = path("settings.gradle") + open val outputShadowJar: JarPath get() = jarPath("build/libs/shadow-1.0-all.jar") + val outputServerShadowJar: JarPath get() = jarPath("server/build/libs/server-1.0-all.jar") + fun getDefaultProjectBuildScript( javaPlugin: String = "java", withGroup: Boolean = false, @@ -115,24 +128,8 @@ abstract class BasePluginTest { """.trimIndent() + System.lineSeparator() } - open val shadowJarTask = ":$SHADOW_JAR_TASK_NAME" - open val runShadowTask = ":$SHADOW_RUN_TASK_NAME" - val serverShadowJarTask = ":server:$SHADOW_JAR_TASK_NAME" - - val projectScriptPath: Path - get() = path("build.gradle") - - val settingsScriptPath: Path - get() = path("settings.gradle") - - open val outputShadowJar: JarPath - get() = jarPath("build/libs/shadow-1.0-all.jar") - - val outputServerShadowJar: JarPath - get() = jarPath("server/build/libs/server-1.0-all.jar") - fun jarPath(path: String): JarPath { - val realPath = root.resolve(path).also { + val realPath = projectRoot.resolve(path).also { check(it.exists()) { "Path not found: $it" } check(it.isRegularFile()) { "Path is not a regular file: $it" } } @@ -140,7 +137,7 @@ abstract class BasePluginTest { } fun path(path: String): Path { - return root.resolve(path).also { + return projectRoot.resolve(path).also { if (it.exists()) return@also it.parent.createDirectories() // We should create text file only if it doesn't exist. @@ -300,7 +297,7 @@ abstract class BasePluginTest { fun runner( arguments: Iterable = emptyList(), - projectDir: Path? = root, + projectDir: Path? = projectRoot, ): GradleRunner = GradleRunner.create() .forwardOutput() .withPluginClasspath() @@ -362,18 +359,20 @@ abstract class BasePluginTest { } fun BuildResult.assertNoDeprecationWarnings() = apply { - output.lines().forEach { - assert(!containsDeprecationWarning(it)) - } + assertThat(output).doesNotContain( + "has been deprecated and is scheduled to be removed in Gradle", + "has been deprecated. This is scheduled to be removed in Gradle", + ) } - fun Assert.taskOutcomeEquals(taskPath: String, expectedOutcome: TaskOutcome) { - return transform { it.task(taskPath)?.outcome }.isNotNull().isEqualTo(expectedOutcome) + fun Assert.useAll(body: Assert.() -> Unit) = all { + body() + // Close the resource after all assertions are done. + given { it.use(block = {}) } } - private fun containsDeprecationWarning(output: String): Boolean { - return output.contains("has been deprecated and is scheduled to be removed in Gradle") || - output.contains("has been deprecated. This is scheduled to be removed in Gradle") + fun Assert.taskOutcomeEquals(taskPath: String, expectedOutcome: TaskOutcome) { + return transform { it.task(taskPath)?.outcome }.isNotNull().isEqualTo(expectedOutcome) } } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt index 219c8e587..fd7f0fe64 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/FilteringTest.kt @@ -3,7 +3,6 @@ package com.github.jengelman.gradle.plugins.shadow import assertk.assertThat import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries -import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText @@ -201,13 +200,13 @@ class FilteringTest : BasePluginTest() { run(serverShadowJarTask) assertThat(outputServerShadowJar).useAll { - doesNotContainEntries( - "client/Client.class", - ) containsEntries( "server/Server.class", "junit/framework/Test.class", ) + doesNotContainEntries( + "client/Client.class", + ) } } @@ -224,13 +223,13 @@ class FilteringTest : BasePluginTest() { run(serverShadowJarTask) assertThat(outputServerShadowJar).useAll { - doesNotContainEntries( - "junit/framework/Test.class", - ) containsEntries( "client/Client.class", "server/Server.class", ) + doesNotContainEntries( + "junit/framework/Test.class", + ) } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index 575a83130..6b8064ae8 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -9,7 +9,6 @@ import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.JarPath import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries -import com.github.jengelman.gradle.plugins.shadow.util.useAll import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory @@ -33,12 +32,12 @@ class PublishingTest : BasePluginTest() { private val gmmAdapter = moshi.adapter(GradleModuleMetadata::class.java) private val pomReader = MavenXpp3Reader() - private lateinit var remoteRepo: Path + private lateinit var remoteRepoPath: Path @BeforeEach override fun setup() { super.setup() - remoteRepo = root.resolve("remote-maven-repo") + remoteRepoPath = projectRoot.resolve("remote-maven-repo") settingsScriptPath.appendText("rootProject.name = 'maven'" + System.lineSeparator()) } @@ -216,7 +215,7 @@ class PublishingTest : BasePluginTest() { } private fun repoPath(path: String): Path { - return remoteRepo.resolve(path).also { + return remoteRepoPath.resolve(path).also { check(it.exists()) { "Path not found: $it" } check(it.isRegularFile()) { "Path is not a regular file: $it" } } @@ -257,7 +256,7 @@ class PublishingTest : BasePluginTest() { } repositories { maven { - url = '${remoteRepo.toUri()}' + url = '${remoteRepoPath.toUri()}' } } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 61c26aeb2..182e84705 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -4,20 +4,18 @@ import assertk.assertFailure import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf +import assertk.fail import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHADOW_JAR_TASK_NAME import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries -import com.github.jengelman.gradle.plugins.shadow.util.useAll import java.net.URLClassLoader import kotlin.io.path.appendText import kotlin.io.path.writeText import org.junit.jupiter.api.Test -import org.junit.jupiter.api.fail import org.opentest4j.AssertionFailedError class RelocationTest : BasePluginTest() { - @Test fun defaultEnableRelocation() { projectScriptPath.appendText( diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt index 0ae67d064..b71d11bdf 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt @@ -13,7 +13,6 @@ import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries import com.github.jengelman.gradle.plugins.shadow.util.isRegular -import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText @@ -27,7 +26,6 @@ import org.junit.jupiter.api.condition.EnabledForJreRange import org.junit.jupiter.api.condition.JRE class ShadowPluginTest : BasePluginTest() { - @Test fun applyPlugin() { val projectName = "my-shadow" diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt index dcad6b239..d233c5be2 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt @@ -16,8 +16,7 @@ import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.io.TempDir -sealed class BaseCachingTest : BasePluginTest() { - +abstract class BaseCachingTest : BasePluginTest() { @TempDir lateinit var alternateDir: Path @@ -48,7 +47,7 @@ sealed class BaseCachingTest : BasePluginTest() { // ignore if the file does not exist } alternateDir.deleteRecursively() - root.copyToRecursively(alternateDir, followLinks = false, overwrite = false) + projectRoot.copyToRecursively(alternateDir, followLinks = false, overwrite = false) // check that shadowJar pulls from cache in the original directory assertShadowJarHasResult(firstOutcome) // check that shadowJar pulls from cache in a different directory diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt index e1558090c..135176417 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt @@ -4,7 +4,6 @@ import assertk.assertThat import com.github.jengelman.gradle.plugins.shadow.util.JarPath import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries -import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.writeText import org.junit.jupiter.api.Test diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt index a62b217d5..f99d4b0b4 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt @@ -3,7 +3,6 @@ package com.github.jengelman.gradle.plugins.shadow.caching import assertk.assertThat import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries -import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import org.junit.jupiter.api.Test diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt index 3c99cc192..48f9f9028 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt @@ -4,7 +4,6 @@ import assertk.assertThat import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries import com.github.jengelman.gradle.plugins.shadow.util.isRegular -import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt index c749a6582..bec44a76f 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt @@ -7,7 +7,6 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.NoOpTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.XmlAppendingTransformer import com.github.jengelman.gradle.plugins.shadow.util.containsEntries -import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt index 557275a4d..2601c25b4 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt @@ -4,8 +4,7 @@ import com.github.jengelman.gradle.plugins.shadow.BasePluginTest import com.github.jengelman.gradle.plugins.shadow.util.JarBuilder import java.nio.file.Path -sealed class BaseTransformerTest : BasePluginTest() { - +abstract class BaseTransformerTest : BasePluginTest() { fun buildJarOne( builder: JarBuilder.() -> Unit = { insert(ENTRY_SERVICES_SHADE, CONTENT_ONE) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt index 57a8fad89..4c804b844 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt @@ -3,13 +3,11 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.isEqualTo import com.github.jengelman.gradle.plugins.shadow.util.Issue -import com.github.jengelman.gradle.plugins.shadow.util.useAll import kotlin.io.path.appendText import kotlin.io.path.writeText import org.junit.jupiter.api.Test class ServiceFileTransformerTest : BaseTransformerTest() { - @Test fun serviceResourceTransformer() { projectScriptPath.appendText( diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt index 0190d653c..97520efd6 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt @@ -15,7 +15,6 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource class TransformersTest : BaseTransformerTest() { - @Test fun manifestRetained() { writeMainClass() diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt index 41bbd0a32..0212b9982 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt @@ -29,16 +29,13 @@ class JarPath(val path: Path) : } } -fun Assert.useAll(body: Assert.() -> Unit) = all { - body() - given { it.close() } -} - /** * Common regular assertions for [JarPath]. */ -fun Assert.isRegular() = useAll { +fun Assert.isRegular() = all { transform { it.entries().toList() }.isNotEmpty() + // Close the resource after all assertions are done. + given { it.use(block = {}) } } fun Assert.containsEntries(vararg entries: String) = transform { actual -> From c9933c593d882eabafda0e025e36f3986bdb3b5d Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 18:16:14 +0800 Subject: [PATCH 07/20] Simplify assertions for JarPath --- .../gradle/plugins/shadow/ApplicationTest.kt | 3 +- .../gradle/plugins/shadow/RelocationTest.kt | 3 +- .../gradle/plugins/shadow/ShadowPluginTest.kt | 6 +-- .../ServiceFileTransformerTest.kt | 52 +++++++++---------- .../gradle/plugins/shadow/util/JarPath.kt | 12 ++++- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt index 54d5fd49c..04526b24b 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt @@ -7,6 +7,7 @@ import assertk.assertions.containsAtLeast import assertk.assertions.exists import assertk.assertions.isEqualTo import com.github.jengelman.gradle.plugins.shadow.util.containsEntries +import com.github.jengelman.gradle.plugins.shadow.util.getMainAttr import com.github.jengelman.gradle.plugins.shadow.util.isRegular import kotlin.io.path.appendText import kotlin.io.path.readText @@ -48,7 +49,7 @@ class ApplicationTest : BasePluginTest() { "a2.properties", "myapp/Main.class", ) - transform { it.manifest.mainAttributes.getValue("Main-Class") }.isEqualTo("myapp.Main") + getMainAttr("Main-Class").isEqualTo("myapp.Main") } assertThat(path("build/install/myapp-shadow/bin/myapp")).all { diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index 182e84705..b57ba14ad 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -9,6 +9,7 @@ import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHA import com.github.jengelman.gradle.plugins.shadow.util.Issue import com.github.jengelman.gradle.plugins.shadow.util.containsEntries import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries +import com.github.jengelman.gradle.plugins.shadow.util.getMainAttr import java.net.URLClassLoader import kotlin.io.path.appendText import kotlin.io.path.writeText @@ -111,7 +112,7 @@ class RelocationTest : BasePluginTest() { "junit/framework/TestSuite\$1.class", "junit/framework/TestSuite.class", ) - transform { it.manifest.mainAttributes.getValue("TEST-VALUE") }.isEqualTo("FOO") + getMainAttr("TEST-VALUE").isEqualTo("FOO") } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt index b71d11bdf..da50685ef 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt @@ -567,7 +567,7 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - val value = outputShadowJar.use { it.manifest.mainAttributes.getValue("Class-Path") } + val value = outputShadowJar.use { it.getMainAttr("Class-Path") } assertThat(value).isNull() } @@ -591,7 +591,7 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - val value = outputShadowJar.use { it.manifest.mainAttributes.getValue("Class-Path") } + val value = outputShadowJar.use { it.getMainAttr("Class-Path") } assertThat(value).isEqualTo("/libs/a.jar junit-3.8.2.jar") } @@ -610,7 +610,7 @@ class ShadowPluginTest : BasePluginTest() { run(shadowJarTask) - val value = outputShadowJar.use { it.manifest.mainAttributes.getValue("Class-Path") } + val value = outputShadowJar.use { it.getMainAttr("Class-Path") } assertThat(value).isEqualTo("junit-3.8.2.jar") } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt index 4c804b844..8fef8b325 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt @@ -3,6 +3,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.isEqualTo import com.github.jengelman.gradle.plugins.shadow.util.Issue +import com.github.jengelman.gradle.plugins.shadow.util.getContent import kotlin.io.path.appendText import kotlin.io.path.writeText import org.junit.jupiter.api.Test @@ -22,8 +23,8 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) assertThat(outputShadowJar).useAll { - transform { it.getContent(ENTRY_SERVICES_SHADE) }.isEqualTo(CONTENT_ONE_TWO) - transform { it.getContent(ENTRY_SERVICES_FOO) }.isEqualTo("one") + getContent(ENTRY_SERVICES_SHADE).isEqualTo(CONTENT_ONE_TWO) + getContent(ENTRY_SERVICES_FOO).isEqualTo("one") } } @@ -66,8 +67,8 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) assertThat(outputShadowJar).useAll { - transform { it.getContent(ENTRY_SERVICES_SHADE) }.isEqualTo(CONTENT_ONE_TWO) - transform { it.getContent(ENTRY_SERVICES_FOO) }.isEqualTo("one") + getContent(ENTRY_SERVICES_SHADE).isEqualTo(CONTENT_ONE_TWO) + getContent(ENTRY_SERVICES_FOO).isEqualTo("one") } } @@ -124,29 +125,26 @@ class ServiceFileTransformerTest : BaseTransformerTest() { run(shadowJarTask) assertThat(outputShadowJar).useAll { - transform { it.getContent("META-INF/services/java.sql.Driver") } - .isEqualTo( - """ - oracle.jdbc.OracleDriver - myapache.hive.jdbc.HiveDriver - myapache.derby.jdbc.AutoloadedDriver - com.mysql.jdbc.Driver - """.trimIndent(), - ) - transform { it.getContent("META-INF/services/myapache.axis.components.compiler.Compiler") } - .isEqualTo( - """ - myapache.axis.components.compiler.Javac - org.apache.axis.components.compiler.Jikes - """.trimIndent(), - ) - transform { it.getContent("META-INF/services/org.apache.commons.logging.LogFactory") } - .isEqualTo( - """ - myapache.commons.logging.impl.LogFactoryImpl - org.mortbay.log.Factory - """.trimIndent(), - ) + getContent("META-INF/services/java.sql.Driver").isEqualTo( + """ + oracle.jdbc.OracleDriver + myapache.hive.jdbc.HiveDriver + myapache.derby.jdbc.AutoloadedDriver + com.mysql.jdbc.Driver + """.trimIndent(), + ) + getContent("META-INF/services/myapache.axis.components.compiler.Compiler").isEqualTo( + """ + myapache.axis.components.compiler.Javac + org.apache.axis.components.compiler.Jikes + """.trimIndent(), + ) + getContent("META-INF/services/org.apache.commons.logging.LogFactory").isEqualTo( + """ + myapache.commons.logging.impl.LogFactoryImpl + org.mortbay.log.Factory + """.trimIndent(), + ) } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt index 0212b9982..74a1069c2 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt @@ -23,6 +23,10 @@ class JarPath(val path: Path) : path.deleteExisting() } + fun getMainAttr(name: String): String? { + return manifest.mainAttributes.getValue(name) + } + fun getContent(entryName: String): String { val entry = getEntry(entryName) ?: error("Entry not found: $entryName") return getInputStream(entry).bufferedReader().use { it.readText() } @@ -38,13 +42,17 @@ fun Assert.isRegular() = all { given { it.use(block = {}) } } -fun Assert.containsEntries(vararg entries: String) = transform { actual -> +fun Assert.getContent(entryName: String) = transform { it.getContent(entryName) } + +fun Assert.getMainAttr(name: String) = transform { it.getMainAttr(name) } + +fun Assert.containsEntries(vararg entries: String) = given { actual -> entries.forEach { entry -> actual.getEntry(entry) ?: fail("Jar file ${actual.path} does not contain entry $entry") } } -fun Assert.doesNotContainEntries(vararg entries: String) = transform { actual -> +fun Assert.doesNotContainEntries(vararg entries: String) = given { actual -> entries.forEach { entry -> actual.getEntry(entry) ?: return@forEach fail("Jar file ${actual.path} contains entry $entry") From 0caf5acf37772572b2b54d7b04756b71db4dae89 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 19:41:24 +0800 Subject: [PATCH 08/20] Remove the workaround for projectRoot --- .../gradle/plugins/shadow/ApplicationTest.kt | 2 +- .../gradle/plugins/shadow/BasePluginTest.kt | 10 ++-------- .../gradle/plugins/shadow/RelocationTest.kt | 19 +++++++++---------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt index 04526b24b..66cc5f8e8 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt @@ -77,7 +77,7 @@ class ApplicationTest : BasePluginTest() { val zip = path("build/distributions/myapp-shadow-1.0.zip") assertThat(zip).exists() - val entries = ZipFile(zip.toFile()).entries.toList().map { it.name } + val entries = ZipFile(zip.toFile()).use { it.entries }.toList().map { it.name } assertThat(entries).containsAtLeast( "myapp-shadow-1.0/lib/myapp-1.0-all.jar", "myapp-shadow-1.0/lib/a-1.0.jar", diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt index f1bcaa73c..98aefc49e 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -36,9 +36,11 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.io.TempDir @TestInstance(TestInstance.Lifecycle.PER_CLASS) abstract class BasePluginTest { + @TempDir lateinit var projectRoot: Path lateinit var localRepo: AppendableMavenRepository @@ -64,20 +66,12 @@ abstract class BasePluginTest { @BeforeEach open fun setup() { - projectRoot = createTempDirectory() - projectScriptPath.writeText(getDefaultProjectBuildScript(withGroup = true, withVersion = true)) settingsScriptPath.writeText(getDefaultSettingsBuildScript()) } - @ExperimentalPathApi @AfterEach fun cleanup() { - runCatching { - // TODO: workaround for https://github.com/junit-team/junit5/issues/2811. - projectRoot.deleteRecursively() - } - println(projectScriptPath.readText()) } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt index b57ba14ad..fa1f6f380 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt @@ -210,16 +210,15 @@ class RelocationTest : BasePluginTest() { ) } - val classLoader = URLClassLoader( - arrayOf(outputShadowJar.toUri().toURL()), - ClassLoader.getSystemClassLoader().parent, - ) - assertFailure { - // check that the class can be loaded. If the file was not relocated properly, we should get a NoDefClassFound - // Isolated class loader with only the JVM system jars and the output jar from the test project - classLoader.loadClass("shadow.ShadowTest") - fail("Should not reach here.") - }.isInstanceOf(AssertionFailedError::class) + val url = outputShadowJar.use { it.toUri().toURL() } + URLClassLoader(arrayOf(url), ClassLoader.getSystemClassLoader().parent).use { classLoader -> + assertFailure { + // check that the class can be loaded. If the file was not relocated properly, we should get a NoDefClassFound + // Isolated class loader with only the JVM system jars and the output jar from the test project + classLoader.loadClass("shadow.ShadowTest") + fail("Should not reach here.") + }.isInstanceOf(AssertionFailedError::class) + } } @Test From bac0d881983ab78175fc07bb9c71a139f5d08b49 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 20:03:59 +0800 Subject: [PATCH 09/20] Tweak lint configs --- build.gradle.kts | 11 ++--------- gradle/libs.versions.toml | 1 - lint-baseline.xml | 8 ++++---- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index caa0a98da..03f2e1838 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,6 +29,8 @@ kotlin { lint { baseline = file("lint-baseline.xml") + ignoreTestSources = true + warningsAsErrors = true } spotless { @@ -84,7 +86,6 @@ dependencies { funcTestImplementation(libs.moshi.kotlin) lintChecks(libs.androidx.gradlePluginLints) - lintChecks(libs.assertk.lint) } val integrationTest by tasks.registering(Test::class) { @@ -131,14 +132,6 @@ tasks.withType().configureEach { ) } -tasks.whenTaskAdded { - if (name == "lintAnalyzeJvmTest") { - // This task often fails on Windows CI devices. - enabled = !providers.systemProperty("os.name").get().startsWith("Windows") && - !providers.environmentVariable("CI").isPresent - } -} - tasks.clean { val includedBuilds = gradle.includedBuilds dependsOn(includedBuilds.map { it.task(path) }) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d416a9896..c5a755d89 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,6 @@ jetbrains-dokka = "org.jetbrains.dokka:dokka-gradle-plugin:2.0.0" node = "com.github.node-gradle:gradle-node-plugin:7.1.0" androidx-gradlePluginLints = "androidx.lint:lint-gradle:1.0.0-alpha03" -assertk-lint = "com.jzbrooks:assertk-lint:1.4.0" # Dummy to get renovate updates, the version is used in rootProject build.gradle with spotless. ktlint = "com.pinterest.ktlint:ktlint-cli:1.5.0" diff --git a/lint-baseline.xml b/lint-baseline.xml index 6f209b0af..494ab0b7c 100644 --- a/lint-baseline.xml +++ b/lint-baseline.xml @@ -107,7 +107,7 @@ errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -118,7 +118,7 @@ errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -129,7 +129,7 @@ errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -140,7 +140,7 @@ errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> From 6c89a12dc543034a04a0ef16f44a30454453297c Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 21:27:23 +0800 Subject: [PATCH 10/20] Remove TODO for excludeProjectFromMinimizeShallNotExcludeTransitiveDependenciesThatAreUsedInSubproject --- .../github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt index da50685ef..46570901d 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPluginTest.kt @@ -272,8 +272,6 @@ class ShadowPluginTest : BasePluginTest() { ) run(serverShadowJarTask) - // TODO: I don't think junit classes should be in the output jar, but it's the test case - // from https://github.com/GradleUp/shadow/pull/420, need to investigate more... assertThat(outputServerShadowJar).useAll { containsEntries( "client/Client.class", From 8d672ef64f650a123790fc5cde11ab34db2c491d Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 21:41:53 +0800 Subject: [PATCH 11/20] Simplify checks for jarPath --- .../gradle/plugins/shadow/BasePluginTest.kt | 13 ++++--------- .../gradle/plugins/shadow/PublishingTest.kt | 2 +- .../plugins/shadow/caching/BaseCachingTest.kt | 9 ++++----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt index 98aefc49e..a3781b9fb 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -25,7 +25,6 @@ import kotlin.io.path.createFile import kotlin.io.path.createTempDirectory import kotlin.io.path.deleteRecursively import kotlin.io.path.exists -import kotlin.io.path.isRegularFile import kotlin.io.path.readText import kotlin.io.path.toPath import kotlin.io.path.writeText @@ -122,16 +121,12 @@ abstract class BasePluginTest { """.trimIndent() + System.lineSeparator() } - fun jarPath(path: String): JarPath { - val realPath = projectRoot.resolve(path).also { - check(it.exists()) { "Path not found: $it" } - check(it.isRegularFile()) { "Path is not a regular file: $it" } - } - return JarPath(realPath) + fun jarPath(relative: String, parent: Path = projectRoot): JarPath { + return JarPath(parent.resolve(relative)) } - fun path(path: String): Path { - return projectRoot.resolve(path).also { + fun path(relative: String, parent: Path = projectRoot): Path { + return parent.resolve(relative).also { if (it.exists()) return@also it.parent.createDirectories() // We should create text file only if it doesn't exist. diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index 6b8064ae8..455cb4473 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -222,7 +222,7 @@ class PublishingTest : BasePluginTest() { } private fun repoJarPath(path: String): JarPath { - return JarPath(repoPath(path)) + return JarPath(remoteRepoPath.resolve(path)) } private fun publish(): BuildResult = run("publish") diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt index d233c5be2..1501ff7e2 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt @@ -2,6 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow.caching import assertk.assertThat import com.github.jengelman.gradle.plugins.shadow.BasePluginTest +import java.nio.file.NoSuchFileException import java.nio.file.Path import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.appendText @@ -43,11 +44,10 @@ abstract class BaseCachingTest : BasePluginTest() { ) { try { outputShadowJar.deleteExisting() - } catch (ignored: IllegalStateException) { - // ignore if the file does not exist + } catch (ignored: NoSuchFileException) { } alternateDir.deleteRecursively() - projectRoot.copyToRecursively(alternateDir, followLinks = false, overwrite = false) + projectRoot.copyToRecursively(target = alternateDir, followLinks = false) // check that shadowJar pulls from cache in the original directory assertShadowJarHasResult(firstOutcome) // check that shadowJar pulls from cache in a different directory @@ -62,8 +62,7 @@ abstract class BaseCachingTest : BasePluginTest() { fun assertShadowJarExecutes() { try { outputShadowJar.deleteExisting() - } catch (ignored: IllegalStateException) { - // ignore if the file does not exist + } catch (ignored: NoSuchFileException) { } // task was executed and not pulled from cache assertShadowJarHasResult(SUCCESS) From 2d35d75dd77da2efbc2184f38a479f782696e5f1 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 9 Jan 2025 21:58:30 +0800 Subject: [PATCH 12/20] Create jars in temp dir --- .../jengelman/gradle/plugins/shadow/PublishingTest.kt | 8 ++++---- .../plugins/shadow/transformers/BaseTransformerTest.kt | 4 ++-- .../plugins/shadow/util/AppendableMavenRepository.kt | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt index 455cb4473..167ba3626 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt @@ -214,15 +214,15 @@ class PublishingTest : BasePluginTest() { } } - private fun repoPath(path: String): Path { - return remoteRepoPath.resolve(path).also { + private fun repoPath(relative: String): Path { + return remoteRepoPath.resolve(relative).also { check(it.exists()) { "Path not found: $it" } check(it.isRegularFile()) { "Path is not a regular file: $it" } } } - private fun repoJarPath(path: String): JarPath { - return JarPath(remoteRepoPath.resolve(path)) + private fun repoJarPath(relative: String): JarPath { + return JarPath(remoteRepoPath.resolve(relative)) } private fun publish(): BuildResult = run("publish") diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt index 2601c25b4..b4ee8444b 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt @@ -23,8 +23,8 @@ abstract class BaseTransformerTest : BasePluginTest() { return buildJar("two.jar", builder) } - inline fun buildJar(path: String, builder: JarBuilder.() -> Unit): Path { - return JarBuilder(path(path)).apply(builder).write() + inline fun buildJar(relative: String, builder: JarBuilder.() -> Unit): Path { + return JarBuilder(path("temp/$relative")).apply(builder).write() } protected companion object { diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt index 5786ec8bd..a9764473b 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/AppendableMavenRepository.kt @@ -20,7 +20,7 @@ class AppendableMavenRepository( private val modules = mutableListOf() init { - root.createDirectories() + root.resolve("temp").createDirectories() root.resolve("settings.gradle").createFile() .writeText("rootProject.name = '${root.name}'") projectBuildScript = root.resolve("build.gradle").createFile() @@ -109,7 +109,7 @@ class AppendableMavenRepository( fun buildJar(builder: JarBuilder.() -> Unit) { val jarName = coordinate.replace(":", "-") + ".jar" - existingJar = JarBuilder(root.resolve(jarName)).apply(builder).write() + existingJar = JarBuilder(root.resolve("temp/$jarName")).apply(builder).write() } fun addDependency(groupId: String, artifactId: String, version: String, scope: String = "runtime") { From 17d7df6cd361fb369611330e95ca4f7f9777878e Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 10 Jan 2025 12:23:30 +0800 Subject: [PATCH 13/20] Fix deprecations --- .../plugins/shadow/transformers/BaseTransformerTest.kt | 1 + .../transformers/Log4j2PluginsCacheFileTransformerTest.kt | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt index 18b0c4dbc..f1386c9f8 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt @@ -66,6 +66,7 @@ abstract class BaseTransformerTest { * choice to test for improper case-less string comparisons. */ fun setupTurkishLocale() { + @Suppress("DEPRECATION") Locale.setDefault(Locale("tr")) } } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.kt index f411b33a4..d8d5c333e 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.kt @@ -6,7 +6,7 @@ import assertk.assertions.isTrue import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator import com.github.jengelman.gradle.plugins.shadow.util.SimpleRelocator import java.io.File -import java.net.URL +import java.net.URI import java.util.Collections import org.apache.logging.log4j.core.config.plugins.processor.PluginCache import org.apache.tools.zip.ZipOutputStream @@ -39,8 +39,9 @@ class Log4j2PluginsCacheFileTransformerTest : BaseTransformerTest Date: Fri, 10 Jan 2025 13:23:57 +0800 Subject: [PATCH 14/20] Unify classLoader resource usages --- .../gradle/plugins/shadow/BasePluginTest.kt | 14 +++++++++----- .../plugins/shadow/ShadowApplicationPlugin.kt | 9 +++++++-- .../gradle/plugins/shadow/internal/Utils.kt | 10 +++++----- .../gradle/plugins/shadow/tasks/KnowsTask.kt | 2 +- .../shadow/transformers/BaseTransformerTest.kt | 10 +++++----- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt index a3781b9fb..e07e7a829 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -14,6 +14,7 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer import com.github.jengelman.gradle.plugins.shadow.util.AppendableMavenRepository import com.github.jengelman.gradle.plugins.shadow.util.JarPath import java.io.Closeable +import java.nio.file.NoSuchFileException import java.nio.file.Path import java.util.Properties import kotlin.io.path.ExperimentalPathApi @@ -307,11 +308,9 @@ abstract class BasePluginTest { Path(gradleUserHome, "testkit") } - val testJar: Path = requireNotNull(this::class.java.classLoader.getResource("junit-3.8.2.jar")).toURI().toPath() - val artifactJar: Path = - requireNotNull(this::class.java.classLoader.getResource("test-artifact-1.0-SNAPSHOT.jar")).toURI().toPath() - val projectJar: Path = - requireNotNull(this::class.java.classLoader.getResource("test-project-1.0-SNAPSHOT.jar")).toURI().toPath() + val testJar: Path = requireResourceAsPath("junit-3.8.2.jar") + val artifactJar: Path = requireResourceAsPath("test-artifact-1.0-SNAPSHOT.jar") + val projectJar: Path = requireResourceAsPath("test-project-1.0-SNAPSHOT.jar") val shadowJar: String = """ tasks.named('$SHADOW_JAR_TASK_NAME', ${ShadowJar::class.java.name}) @@ -363,5 +362,10 @@ abstract class BasePluginTest { fun Assert.taskOutcomeEquals(taskPath: String, expectedOutcome: TaskOutcome) { return transform { it.task(taskPath)?.outcome }.isNotNull().isEqualTo(expectedOutcome) } + + private fun requireResourceAsPath(name: String): Path { + val resource = this::class.java.classLoader.getResource(name) ?: throw NoSuchFileException("Resource $name not found.") + return resource.toURI().toPath() + } } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt index cdc290c45..5aee54a59 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt @@ -63,10 +63,15 @@ public abstract class ShadowApplicationPlugin : Plugin { protected open fun addCreateScriptsTask() { project.tasks.register(SHADOW_SCRIPTS_TASK_NAME, CreateStartScripts::class.java) { + val unixStartScript = this::class.java.classLoader + .requireResourceAsText("com/github/jengelman/gradle/plugins/shadow/internal/unixStartScript.txt") + val windowsStartScript = this::class.java.classLoader + .requireResourceAsText("com/github/jengelman/gradle/plugins/shadow/internal/windowsStartScript.txt") + (it.unixStartScriptGenerator as TemplateBasedScriptGenerator).template = - project.resources.text.fromString(this::class.java.requireResourceAsText("internal/unixStartScript.txt")) + project.resources.text.fromString(unixStartScript) (it.windowsStartScriptGenerator as TemplateBasedScriptGenerator).template = - project.resources.text.fromString(this::class.java.requireResourceAsText("internal/windowsStartScript.txt")) + project.resources.text.fromString(windowsStartScript) it.description = "Creates OS specific scripts to run the project as a JVM application using the shadow jar" it.group = ApplicationPlugin.APPLICATION_GROUP it.classpath = project.files(shadowJar) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Utils.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Utils.kt index daac5eae8..f0743fc9d 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Utils.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Utils.kt @@ -3,9 +3,9 @@ package com.github.jengelman.gradle.plugins.shadow.internal import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File -import java.io.FileNotFoundException import java.io.InputStream import java.nio.charset.Charset +import java.nio.file.NoSuchFileException import java.util.Properties import org.gradle.api.file.RelativePath import org.gradle.api.internal.file.DefaultFileTreeElement @@ -37,12 +37,12 @@ internal fun Properties.inputStream( return os.toByteArray().inputStream() } -internal fun Class<*>.requireResourceAsText(name: String): String { - return requireResourceAsStream(name).bufferedReader().readText() +internal fun ClassLoader.requireResourceAsText(name: String): String { + return requireResourceAsStream(name).bufferedReader().use { it.readText() } } -private fun Class<*>.requireResourceAsStream(name: String): InputStream { - return getResourceAsStream(name) ?: throw FileNotFoundException("Resource $name not found.") +internal fun ClassLoader.requireResourceAsStream(name: String): InputStream { + return getResourceAsStream(name) ?: throw NoSuchFileException("Resource $name not found.") } private val DummyFile = File("dummy") diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt index fdffe3788..04558455a 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt @@ -12,7 +12,7 @@ public abstract class KnowsTask : DefaultTask() { """ No, The Shadow Knows.... - ${this::class.java.requireResourceAsText("/shadowBanner.txt")} + ${this::class.java.classLoader.requireResourceAsText("shadowBanner.txt")} """.trimIndent(), ) } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt index f1386c9f8..96829e598 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt @@ -2,9 +2,9 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import com.github.jengelman.gradle.plugins.shadow.ShadowStats import com.github.jengelman.gradle.plugins.shadow.internal.createDefaultFileTreeElement +import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsStream import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer.Companion.create import com.github.jengelman.gradle.plugins.shadow.util.testObjectFactory -import java.io.FileNotFoundException import java.io.InputStream import java.lang.reflect.ParameterizedType import java.nio.file.Path @@ -23,10 +23,6 @@ abstract class BaseTransformerTest { protected val manifestTransformerContext: TransformerContext get() = TransformerContext(MANIFEST_NAME, requireResourceAsStream(MANIFEST_NAME)) - protected fun requireResourceAsStream(name: String): InputStream { - return this::class.java.classLoader.getResourceAsStream(name) ?: throw FileNotFoundException("Resource $name not found.") - } - @BeforeEach fun setup() { @Suppress("UNCHECKED_CAST") @@ -69,5 +65,9 @@ abstract class BaseTransformerTest { @Suppress("DEPRECATION") Locale.setDefault(Locale("tr")) } + + fun requireResourceAsStream(name: String): InputStream { + return this::class.java.classLoader.requireResourceAsStream(name) + } } } From e6b8a04175ee7549453d11d3d31512ce766e3cd9 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 10 Jan 2025 13:31:45 +0800 Subject: [PATCH 15/20] Simplify requireResourceAsText and requireResourceAsStream --- .../jengelman/gradle/plugins/shadow/BasePluginTest.kt | 3 ++- .../gradle/plugins/shadow/ShadowApplicationPlugin.kt | 8 ++++---- .../jengelman/gradle/plugins/shadow/internal/Utils.kt | 9 ++++++--- .../jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt | 2 +- .../plugins/shadow/transformers/BaseTransformerTest.kt | 5 ----- .../transformers/ComponentsXmlResourceTransformerTest.kt | 1 + .../Log4j2PluginsCacheFileTransformerTest.kt | 1 + .../transformers/ManifestAppenderTransformerTest.kt | 1 + 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt index e07e7a829..056d974d6 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -364,7 +364,8 @@ abstract class BasePluginTest { } private fun requireResourceAsPath(name: String): Path { - val resource = this::class.java.classLoader.getResource(name) ?: throw NoSuchFileException("Resource $name not found.") + val resource = this::class.java.classLoader.getResource(name) + ?: throw NoSuchFileException("Resource $name not found.") return resource.toURI().toPath() } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt index 5aee54a59..0e0fec3c7 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowApplicationPlugin.kt @@ -63,10 +63,10 @@ public abstract class ShadowApplicationPlugin : Plugin { protected open fun addCreateScriptsTask() { project.tasks.register(SHADOW_SCRIPTS_TASK_NAME, CreateStartScripts::class.java) { - val unixStartScript = this::class.java.classLoader - .requireResourceAsText("com/github/jengelman/gradle/plugins/shadow/internal/unixStartScript.txt") - val windowsStartScript = this::class.java.classLoader - .requireResourceAsText("com/github/jengelman/gradle/plugins/shadow/internal/windowsStartScript.txt") + val unixStartScript = + requireResourceAsText("com/github/jengelman/gradle/plugins/shadow/internal/unixStartScript.txt") + val windowsStartScript = + requireResourceAsText("com/github/jengelman/gradle/plugins/shadow/internal/windowsStartScript.txt") (it.unixStartScriptGenerator as TemplateBasedScriptGenerator).template = project.resources.text.fromString(unixStartScript) diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Utils.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Utils.kt index f0743fc9d..97557c31c 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Utils.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Utils.kt @@ -37,12 +37,13 @@ internal fun Properties.inputStream( return os.toByteArray().inputStream() } -internal fun ClassLoader.requireResourceAsText(name: String): String { +internal fun requireResourceAsText(name: String): String { return requireResourceAsStream(name).bufferedReader().use { it.readText() } } -internal fun ClassLoader.requireResourceAsStream(name: String): InputStream { - return getResourceAsStream(name) ?: throw NoSuchFileException("Resource $name not found.") +internal fun requireResourceAsStream(name: String): InputStream { + return Utils::class.java.classLoader.getResourceAsStream(name) + ?: throw NoSuchFileException("Resource $name not found.") } private val DummyFile = File("dummy") @@ -51,3 +52,5 @@ private val DummyStat = object : Stat { override fun getUnixMode(f: File): Int = error("This is a dummy implementation.") override fun stat(f: File): FileMetadata = error("This is a dummy implementation.") } + +private object Utils diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt index 04558455a..1c5314bba 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/KnowsTask.kt @@ -12,7 +12,7 @@ public abstract class KnowsTask : DefaultTask() { """ No, The Shadow Knows.... - ${this::class.java.classLoader.requireResourceAsText("shadowBanner.txt")} + ${requireResourceAsText("shadowBanner.txt")} """.trimIndent(), ) } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt index 96829e598..6e94fcf97 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/BaseTransformerTest.kt @@ -5,7 +5,6 @@ import com.github.jengelman.gradle.plugins.shadow.internal.createDefaultFileTree import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsStream import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer.Companion.create import com.github.jengelman.gradle.plugins.shadow.util.testObjectFactory -import java.io.InputStream import java.lang.reflect.ParameterizedType import java.nio.file.Path import java.util.Locale @@ -65,9 +64,5 @@ abstract class BaseTransformerTest { @Suppress("DEPRECATION") Locale.setDefault(Locale("tr")) } - - fun requireResourceAsStream(name: String): InputStream { - return this::class.java.classLoader.requireResourceAsStream(name) - } } } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformerTest.kt index 43640d449..73b68d961 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ComponentsXmlResourceTransformerTest.kt @@ -2,6 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.isTrue +import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsStream import org.custommonkey.xmlunit.XMLUnit import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.kt index d8d5c333e..945b9bc8d 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformerTest.kt @@ -3,6 +3,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isTrue +import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsStream import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator import com.github.jengelman.gradle.plugins.shadow.util.SimpleRelocator import java.io.File diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ManifestAppenderTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ManifestAppenderTransformerTest.kt index e699711a7..26d2bc752 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ManifestAppenderTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ManifestAppenderTransformerTest.kt @@ -6,6 +6,7 @@ import assertk.assertions.isFalse import assertk.assertions.isGreaterThan import assertk.assertions.isNotEmpty import assertk.assertions.isTrue +import com.github.jengelman.gradle.plugins.shadow.internal.requireResourceAsStream import org.junit.jupiter.api.Test class ManifestAppenderTransformerTest : BaseTransformerTest() { From ed848e0de6988a3062f047a3b79847e45e6c3886 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 10 Jan 2025 14:29:49 +0800 Subject: [PATCH 16/20] Optimize closable using --- .../github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt | 2 +- .../plugins/shadow/transformers/AppendingTransformer.kt | 1 + .../plugins/shadow/transformers/PropertiesFileTransformer.kt | 4 ++-- .../plugins/shadow/transformers/ServiceFileTransformerTest.kt | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt index 12c362504..6da7edaf8 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarBuilder.kt @@ -25,7 +25,7 @@ class JarBuilder( } if (entries.add(entry)) { jos.putNextEntry(JarEntry(entry)) - content.byteInputStream().copyTo(jos) + content.byteInputStream().use { it.copyTo(jos) } } } } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.kt index 029d2e5b8..182d12f33 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/AppendingTransformer.kt @@ -53,6 +53,7 @@ public open class AppendingTransformer @Inject constructor( entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) os.putNextEntry(entry) + // Closing a ByteArrayOutputStream has no effect, so we don't use a use block here. data.toByteArray().inputStream().copyTo(os) data.reset() } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt index 2950715ae..27560fd88 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/PropertiesFileTransformer.kt @@ -169,7 +169,7 @@ public open class PropertiesFileTransformer @Inject constructor( private fun loadAndTransformKeys(inputStream: InputStream): CleanProperties { val props = CleanProperties() // InputStream closed by caller, so we don't do it here. - props.load(inputStream.reader(charset)) + props.load(inputStream.bufferedReader(charset)) return transformKeys(props) } @@ -225,7 +225,7 @@ public open class PropertiesFileTransformer @Inject constructor( val entry = ZipEntry(path) entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) os.putNextEntry(entry) - props.inputStream(charset).reader(charset).use { + props.inputStream(charset).bufferedReader(charset).use { it.copyTo(zipWriter) } zipWriter.flush() diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt index 55bec8828..debd4001e 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ServiceFileTransformerTest.kt @@ -28,8 +28,8 @@ class ServiceFileTransformerTest : BaseTransformerTest() } assertThat(transformer.hasTransformedResource()).isTrue() - assertThat(transformer.serviceEntries.getValue(path).toInputStream().bufferedReader().readText()) - .isEqualTo(output) + val entry = transformer.serviceEntries.getValue(path).toInputStream().bufferedReader().use { it.readText() } + assertThat(entry).isEqualTo(output) } @Test From 52f50ed8187c23d02a1be0b167dec646496f7397 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 10 Jan 2025 14:34:21 +0800 Subject: [PATCH 17/20] Fix org.junit.jupiter.api.extension.Extension path --- .../services/org.junit.jupiter.api.extension.Extension | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/funcTest/resources/{ => META-INF}/services/org.junit.jupiter.api.extension.Extension (100%) diff --git a/src/funcTest/resources/services/org.junit.jupiter.api.extension.Extension b/src/funcTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension similarity index 100% rename from src/funcTest/resources/services/org.junit.jupiter.api.extension.Extension rename to src/funcTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension From eae6a5f32c9c56bf2c9ed318363f3b8d3df1144d Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 10 Jan 2025 14:42:27 +0800 Subject: [PATCH 18/20] Assert the bat file in integrationWithApplicationPluginAndJavaToolchains --- .../jengelman/gradle/plugins/shadow/ApplicationTest.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt index 66cc5f8e8..8f3ea3e97 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/ApplicationTest.kt @@ -60,6 +60,12 @@ class ApplicationTest : BasePluginTest() { "exec \"\$JAVACMD\" \"\$@\"", ) } + assertThat(path("build/install/myapp-shadow/bin/myapp.bat")).all { + exists() + transform { it.readText() }.contains( + "set CLASSPATH=%APP_HOME%\\lib\\myapp-1.0-all.jar", + ) + } } @Test From dfe9062964392ca98316b3c0f97719c2a180822a Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 10 Jan 2025 15:06:24 +0800 Subject: [PATCH 19/20] Enable --build-cache for all func tests --- .../gradle/plugins/shadow/BasePluginTest.kt | 8 ++++++++ .../plugins/shadow/caching/BaseCachingTest.kt | 20 +------------------ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt index 056d974d6..2564ddfa3 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt @@ -108,6 +108,10 @@ abstract class BasePluginTest { fun getDefaultSettingsBuildScript( startBlock: String = "", + // Use a test-specific build cache directory. This ensures that we'll only use cached outputs generated during + // this test, and we won't accidentally use cached outputs from a different test or a different build. + // https://docs.gradle.org/current/userguide/build_cache.html#sec:build_cache_configure_local + buildCacheBlock: String = "local { directory = file('build-cache') }", endBlock: String = "rootProject.name = 'shadow'", ): String { return """ @@ -118,6 +122,9 @@ abstract class BasePluginTest { mavenCentral() } } + buildCache { + $buildCacheBlock + } $endBlock """.trimIndent() + System.lineSeparator() } @@ -323,6 +330,7 @@ abstract class BasePluginTest { val commonArguments = listOf( "--warning-mode=fail", "--configuration-cache", + "--build-cache", "--stacktrace", ) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt index 1501ff7e2..1492293c4 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt @@ -5,7 +5,6 @@ import com.github.jengelman.gradle.plugins.shadow.BasePluginTest import java.nio.file.NoSuchFileException import java.nio.file.Path import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.appendText import kotlin.io.path.copyToRecursively import kotlin.io.path.deleteRecursively import kotlin.io.path.listDirectoryEntries @@ -14,29 +13,12 @@ import org.gradle.testkit.runner.TaskOutcome import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.io.TempDir abstract class BaseCachingTest : BasePluginTest() { @TempDir lateinit var alternateDir: Path - @BeforeEach - override fun setup() { - super.setup() - // Use a test-specific build cache directory. This ensures that we'll only use cached outputs generated during - // this test, and we won't accidentally use cached outputs from a different test or a different build. - settingsScriptPath.appendText( - """ - buildCache { - local { - directory = file('build-cache') - } - } - """.trimIndent() + System.lineSeparator(), - ) - } - @OptIn(ExperimentalPathApi::class) fun assertShadowJarIsCachedAndRelocatable( firstOutcome: TaskOutcome = FROM_CACHE, @@ -72,7 +54,7 @@ abstract class BaseCachingTest : BasePluginTest() { expectedOutcome: TaskOutcome, runnerBlock: (GradleRunner) -> GradleRunner = { it }, ) { - val result = run("--build-cache", shadowJarTask, runnerBlock = runnerBlock) + val result = run(shadowJarTask, runnerBlock = runnerBlock) assertThat(result).taskOutcomeEquals(shadowJarTask, expectedOutcome) } } From 9861bfd16a5f8b0e91331456ae37035b03dd135e Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 10 Jan 2025 16:05:31 +0800 Subject: [PATCH 20/20] Optimize assertions in BaseCachingTest and remove alternateDir logic --- .../plugins/shadow/caching/BaseCachingTest.kt | 66 ++++++++----------- .../shadow/caching/MinimizationCachingTest.kt | 6 +- .../shadow/caching/RelocationCachingTest.kt | 6 +- .../shadow/caching/ShadowJarCachingTest.kt | 24 ++++--- .../shadow/caching/TransformCachingTest.kt | 60 +++++------------ .../shadow/transformers/TransformersTest.kt | 17 +++++ .../gradle/plugins/shadow/util/JarPath.kt | 6 -- .../gradle/plugins/shadow/tasks/ShadowJar.kt | 1 + 8 files changed, 79 insertions(+), 107 deletions(-) diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt index 1492293c4..077d7b579 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/BaseCachingTest.kt @@ -1,60 +1,46 @@ package com.github.jengelman.gradle.plugins.shadow.caching +import assertk.assertFailure import assertk.assertThat +import assertk.assertions.isEmpty +import assertk.assertions.isInstanceOf import com.github.jengelman.gradle.plugins.shadow.BasePluginTest import java.nio.file.NoSuchFileException -import java.nio.file.Path import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.copyToRecursively -import kotlin.io.path.deleteRecursively -import kotlin.io.path.listDirectoryEntries -import org.gradle.testkit.runner.GradleRunner +import kotlin.io.path.isDirectory +import kotlin.io.path.name +import kotlin.io.path.walk import org.gradle.testkit.runner.TaskOutcome import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE -import org.junit.jupiter.api.io.TempDir abstract class BaseCachingTest : BasePluginTest() { - @TempDir - lateinit var alternateDir: Path + fun assertFirstExecutionSuccess() { + // task was executed and not pulled from cache + assertRunWithOutcome(SUCCESS) + } + + /** + * This should be called after [assertFirstExecutionSuccess] to ensure that the shadowJar task is cached. + */ + fun assertExecutionsAreCachedAndUpToDate() { + run("clean") + // Make sure the output shadow jar has been deleted. + assertFailure { outputShadowJar.close() }.isInstanceOf(NoSuchFileException::class) + @OptIn(ExperimentalPathApi::class) + val buildDirs = projectRoot.walk().filter { it.isDirectory() && it.name == "build" } + // Make sure build folders are deleted by clean task. + assertThat(buildDirs).isEmpty() - @OptIn(ExperimentalPathApi::class) - fun assertShadowJarIsCachedAndRelocatable( - firstOutcome: TaskOutcome = FROM_CACHE, - secondOutcome: TaskOutcome = UP_TO_DATE, - ) { - try { - outputShadowJar.deleteExisting() - } catch (ignored: NoSuchFileException) { - } - alternateDir.deleteRecursively() - projectRoot.copyToRecursively(target = alternateDir, followLinks = false) // check that shadowJar pulls from cache in the original directory - assertShadowJarHasResult(firstOutcome) + assertRunWithOutcome(FROM_CACHE) // check that shadowJar pulls from cache in a different directory - assertShadowJarHasResult(secondOutcome) { - if (alternateDir.listDirectoryEntries().isEmpty()) { - error("Directory was not copied to alternate directory") - } - it.withProjectDir(alternateDir.toFile()) - } - } - - fun assertShadowJarExecutes() { - try { - outputShadowJar.deleteExisting() - } catch (ignored: NoSuchFileException) { - } - // task was executed and not pulled from cache - assertShadowJarHasResult(SUCCESS) + assertRunWithOutcome(UP_TO_DATE) } - private fun assertShadowJarHasResult( - expectedOutcome: TaskOutcome, - runnerBlock: (GradleRunner) -> GradleRunner = { it }, - ) { - val result = run(shadowJarTask, runnerBlock = runnerBlock) + private fun assertRunWithOutcome(expectedOutcome: TaskOutcome) { + val result = run(shadowJarTask) assertThat(result).taskOutcomeEquals(shadowJarTask, expectedOutcome) } } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt index 135176417..3921a87a1 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/MinimizationCachingTest.kt @@ -22,7 +22,7 @@ class MinimizationCachingTest : BaseCachingTest() { """.trimIndent(), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries( "server/Server.class", @@ -41,7 +41,7 @@ class MinimizationCachingTest : BaseCachingTest() { """.trimIndent(), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries( "server/Server.class", @@ -52,7 +52,7 @@ class MinimizationCachingTest : BaseCachingTest() { ) } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries( "server/Server.class", diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt index f99d4b0b4..43a05f563 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/RelocationCachingTest.kt @@ -21,7 +21,7 @@ class RelocationCachingTest : BaseCachingTest() { ) writeMainClass(withImports = true) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries( "shadow/Main.class", @@ -37,7 +37,7 @@ class RelocationCachingTest : BaseCachingTest() { """.trimIndent(), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries( "shadow/Main.class", @@ -48,7 +48,7 @@ class RelocationCachingTest : BaseCachingTest() { ) } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries( "shadow/Main.class", diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt index 48f9f9028..3af4c28ed 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/ShadowJarCachingTest.kt @@ -7,7 +7,6 @@ import com.github.jengelman.gradle.plugins.shadow.util.isRegular import kotlin.io.path.appendText import kotlin.io.path.readText import kotlin.io.path.writeText -import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE import org.junit.jupiter.api.Test class ShadowJarCachingTest : BaseCachingTest() { @@ -24,14 +23,14 @@ class ShadowJarCachingTest : BaseCachingTest() { """.trimIndent(), ) - assertShadowJarExecutes() - assertShadowJarIsCachedAndRelocatable() + assertFirstExecutionSuccess() + assertExecutionsAreCachedAndUpToDate() val replaced = projectScriptPath.readText().lines().filter { it != fromJar(projectJar) }.joinToString(System.lineSeparator()) projectScriptPath.writeText(replaced) - assertShadowJarExecutes() + assertFirstExecutionSuccess() } @Test @@ -44,7 +43,7 @@ class ShadowJarCachingTest : BaseCachingTest() { """.trimIndent() + System.lineSeparator(), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() projectScriptPath.appendText( """ @@ -53,8 +52,7 @@ class ShadowJarCachingTest : BaseCachingTest() { } """.trimIndent(), ) - // TODO: need to investigate why secondOutcome is FROM_CACHE instead of UP_TO_DATE. - assertShadowJarIsCachedAndRelocatable(secondOutcome = FROM_CACHE) + assertExecutionsAreCachedAndUpToDate() assertThat(jarPath("build/libs/foo-1.0-all.jar")).isRegular() } @@ -86,7 +84,7 @@ class ShadowJarCachingTest : BaseCachingTest() { """.trimIndent(), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries( "server/Server.class", @@ -104,7 +102,7 @@ class ShadowJarCachingTest : BaseCachingTest() { } """.trimIndent(), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries( "server/Server.class", @@ -115,7 +113,7 @@ class ShadowJarCachingTest : BaseCachingTest() { ) } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries( "server/Server.class", @@ -139,7 +137,7 @@ class ShadowJarCachingTest : BaseCachingTest() { writeMainClass(withImports = true) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries( "shadow/Main.class", @@ -156,7 +154,7 @@ class ShadowJarCachingTest : BaseCachingTest() { } """.trimIndent(), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries( "shadow/Main.class", @@ -166,7 +164,7 @@ class ShadowJarCachingTest : BaseCachingTest() { ) } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries( "shadow/Main.class", diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt index bec44a76f..63257e886 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/caching/TransformCachingTest.kt @@ -3,7 +3,6 @@ package com.github.jengelman.gradle.plugins.shadow.caching import assertk.assertThat import com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer -import com.github.jengelman.gradle.plugins.shadow.transformers.NoOpTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.XmlAppendingTransformer import com.github.jengelman.gradle.plugins.shadow.util.containsEntries @@ -13,34 +12,11 @@ import kotlin.io.path.writeText import org.junit.jupiter.api.Test class TransformCachingTest : BaseCachingTest() { - @Test - fun shadowJarIsNotCachedWhenCustomTransformsAreUsed() { - writeMainClass() - projectScriptPath.appendText( - """ - $shadowJar { - // Use NoOpTransformer to mock a custom transformer here. - transform(${NoOpTransformer::class.java.name}.INSTANCE) - } - """.trimIndent(), - ) - - assertShadowJarExecutes() - assertThat(outputShadowJar).useAll { - containsEntries("shadow/Main.class") - } - - assertShadowJarExecutes() - assertThat(outputShadowJar).useAll { - containsEntries("shadow/Main.class") - } - } - @Test fun shadowJarIsCachedCorrectlyWhenUsingServiceFileTransformer() { writeMainClass() - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } @@ -53,12 +29,12 @@ class TransformCachingTest : BaseCachingTest() { ), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } @@ -66,12 +42,12 @@ class TransformCachingTest : BaseCachingTest() { val replaced = projectScriptPath.readText().replace("META-INF/foo", "META-INF/bar") projectScriptPath.writeText(replaced) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } @@ -82,7 +58,7 @@ class TransformCachingTest : BaseCachingTest() { path("src/main/resources/foo/bar.properties").writeText("foo=bar") writeMainClass() - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } @@ -95,12 +71,12 @@ class TransformCachingTest : BaseCachingTest() { ), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class", "foo/bar.properties") } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class", "foo/bar.properties") } @@ -110,12 +86,12 @@ class TransformCachingTest : BaseCachingTest() { val replaced = projectScriptPath.readText().replace("foo/bar.properties", "foo/baz.properties") projectScriptPath.writeText(replaced) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class", "foo/baz.properties") } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class", "foo/baz.properties") } @@ -126,7 +102,7 @@ class TransformCachingTest : BaseCachingTest() { path("src/main/resources/foo/bar.xml").writeText("bar") writeMainClass() - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } @@ -139,12 +115,12 @@ class TransformCachingTest : BaseCachingTest() { ), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class", "foo/bar.xml") } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class", "foo/bar.xml") } @@ -154,12 +130,12 @@ class TransformCachingTest : BaseCachingTest() { val replaced = projectScriptPath.readText().replace("foo/bar.xml", "foo/baz.xml") projectScriptPath.writeText(replaced) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class", "foo/baz.xml") } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class", "foo/baz.xml") } @@ -169,7 +145,7 @@ class TransformCachingTest : BaseCachingTest() { fun shadowJarIsCachedCorrectlyWhenUsingGroovyExtensionModuleTransformer() { writeMainClass() - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } @@ -178,12 +154,12 @@ class TransformCachingTest : BaseCachingTest() { transform(), ) - assertShadowJarExecutes() + assertFirstExecutionSuccess() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } - assertShadowJarIsCachedAndRelocatable() + assertExecutionsAreCachedAndUpToDate() assertThat(outputShadowJar).useAll { containsEntries("shadow/Main.class") } diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt index 97520efd6..551578cce 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt @@ -107,6 +107,23 @@ class TransformersTest : BaseTransformerTest() { assertThat(mf.mainAttributes.getValue("New-Entry")).isNull() } + @Test + fun canUseCustomTransformer() { + writeMainClass() + projectScriptPath.appendText( + """ + $shadowJar { + // Use NoOpTransformer to mock a custom transformer here. + transform(${NoOpTransformer::class.java.name}.INSTANCE) + } + """.trimIndent(), + ) + + run(shadowJarTask) + + assertThat(outputShadowJar).isRegular() + } + @ParameterizedTest @MethodSource("transformerConfigurations") fun otherTransformers(pair: Pair>) { diff --git a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt index 74a1069c2..9e10f3395 100644 --- a/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt +++ b/src/funcTest/kotlin/com/github/jengelman/gradle/plugins/shadow/util/JarPath.kt @@ -6,7 +6,6 @@ import assertk.assertions.isNotEmpty import assertk.fail import java.nio.file.Path import java.util.jar.JarFile -import kotlin.io.path.deleteExisting /** * A wrapper for [JarFile] that also implements [Path]. @@ -18,11 +17,6 @@ class JarPath(val path: Path) : JarFile(path.toFile()), Path by path { - fun deleteExisting() { - close() - path.deleteExisting() - } - fun getMainAttr(name: String): String? { return manifest.mainAttributes.getValue(name) } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt index 77fe69a6c..2ef97d17a 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt @@ -61,6 +61,7 @@ public abstract class ShadowJar : manifest = DefaultInheritManifest(services.get(FileResolver::class.java)) outputs.doNotCacheIf("Has one or more transforms or relocators that are not cacheable") { + // TODO: this has been called but the cache is still working fine, need to investigate why. transformers.get().any { !isCacheableTransform(it::class.java) } || relocators.get().any { !isCacheableRelocator(it::class.java) } }