diff --git a/airbyte-cdk/java/airbyte-cdk/airbyte-commons/src/main/resources/log4j2.xml b/airbyte-cdk/java/airbyte-cdk/airbyte-commons/src/main/resources/log4j2-test.xml similarity index 100% rename from airbyte-cdk/java/airbyte-cdk/airbyte-commons/src/main/resources/log4j2.xml rename to airbyte-cdk/java/airbyte-cdk/airbyte-commons/src/main/resources/log4j2-test.xml diff --git a/airbyte-cdk/java/airbyte-cdk/config-models-oss/build.gradle b/airbyte-cdk/java/airbyte-cdk/config-models-oss/build.gradle index 9d695e5227b..b64c0a8c7b4 100644 --- a/airbyte-cdk/java/airbyte-cdk/config-models-oss/build.gradle +++ b/airbyte-cdk/java/airbyte-cdk/config-models-oss/build.gradle @@ -37,30 +37,3 @@ jsonSchema2Pojo { tasks.register('generate').configure { dependsOn tasks.named('generateJsonSchema2Pojo') } - -test { - useJUnitPlatform { - excludeTags 'log4j2-config', 'logger-client' - } - testLogging { - events "passed", "skipped", "failed" - } -} - -tasks.register('log4j2IntegrationTest', Test) { - useJUnitPlatform { - includeTags 'log4j2-config' - } - testLogging { - events "passed", "skipped", "failed" - } -} - -tasks.register('logClientsIntegrationTest', Test) { - useJUnitPlatform { - includeTags 'logger-client' - } - testLogging { - events "passed", "skipped", "failed" - } -} diff --git a/airbyte-cdk/java/airbyte-cdk/core/src/test/java/io/airbyte/cdk/integrations/base/AirbyteLogMessageTemplateTest.java b/airbyte-cdk/java/airbyte-cdk/core/src/test/java/io/airbyte/cdk/integrations/base/AirbyteLogMessageTemplateTest.java index 8cdb8b907f1..39795319dbf 100644 --- a/airbyte-cdk/java/airbyte-cdk/core/src/test/java/io/airbyte/cdk/integrations/base/AirbyteLogMessageTemplateTest.java +++ b/airbyte-cdk/java/airbyte-cdk/core/src/test/java/io/airbyte/cdk/integrations/base/AirbyteLogMessageTemplateTest.java @@ -20,10 +20,10 @@ import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.OutputStreamAppender; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; import org.apache.logging.log4j.core.config.LoggerConfig; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -43,6 +43,7 @@ public class AirbyteLogMessageTemplateTest { public static final String CONSOLE_JSON_APPENDER = "ConsoleJSONAppender"; private static OutputStreamAppender outputStreamAppender; private static LoggerConfig rootLoggerConfig; + private static LoggerContext loggerContext; @BeforeAll static void init() { @@ -50,7 +51,7 @@ public class AirbyteLogMessageTemplateTest { // as the console json appender defined in this project's log4j2.xml file. // We then attach this log appender with the LOGGER instance so that we can validate the logs // produced by code and assert that it matches the expected format. - final LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + loggerContext = Configurator.initialize(null, "log4j2.xml"); final Configuration configuration = loggerContext.getConfiguration(); rootLoggerConfig = configuration.getLoggerConfig(""); @@ -71,6 +72,7 @@ public class AirbyteLogMessageTemplateTest { static void cleanUp() { outputStreamAppender.stop(); rootLoggerConfig.removeAppender(OUTPUT_STREAM_APPENDER); + loggerContext.close(); } @Test diff --git a/airbyte-cdk/java/airbyte-cdk/db-sources/build.gradle b/airbyte-cdk/java/airbyte-cdk/db-sources/build.gradle index 3db5221b72a..e69c6c45224 100644 --- a/airbyte-cdk/java/airbyte-cdk/db-sources/build.gradle +++ b/airbyte-cdk/java/airbyte-cdk/db-sources/build.gradle @@ -23,14 +23,6 @@ java { } } - -test { - testLogging { - // TODO: Remove this after debugging - showStandardStreams = true - } -} - project.configurations { // From `base-debezium`: testFixturesImplementation.extendsFrom implementation diff --git a/airbyte-integrations/connectors/destination-mssql-strict-encrypt/gradle.properties b/airbyte-integrations/connectors/destination-mssql-strict-encrypt/gradle.properties index 5d1adb3b55c..2b147dcf717 100644 --- a/airbyte-integrations/connectors/destination-mssql-strict-encrypt/gradle.properties +++ b/airbyte-integrations/connectors/destination-mssql-strict-encrypt/gradle.properties @@ -1,3 +1,3 @@ # currently limit the number of parallel threads until further investigation into the issues \ # where integration tests run into race conditions -numberThreads=1 +testExecutionConcurrency=1 diff --git a/airbyte-integrations/connectors/destination-mssql/gradle.properties b/airbyte-integrations/connectors/destination-mssql/gradle.properties index 5d1adb3b55c..2b147dcf717 100644 --- a/airbyte-integrations/connectors/destination-mssql/gradle.properties +++ b/airbyte-integrations/connectors/destination-mssql/gradle.properties @@ -1,3 +1,3 @@ # currently limit the number of parallel threads until further investigation into the issues \ # where integration tests run into race conditions -numberThreads=1 +testExecutionConcurrency=1 diff --git a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/gradle.properties b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/gradle.properties index 5d1adb3b55c..2b147dcf717 100644 --- a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/gradle.properties +++ b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/gradle.properties @@ -1,3 +1,3 @@ # currently limit the number of parallel threads until further investigation into the issues \ # where integration tests run into race conditions -numberThreads=1 +testExecutionConcurrency=1 diff --git a/airbyte-integrations/connectors/destination-postgres/gradle.properties b/airbyte-integrations/connectors/destination-postgres/gradle.properties index 5d1adb3b55c..2b147dcf717 100644 --- a/airbyte-integrations/connectors/destination-postgres/gradle.properties +++ b/airbyte-integrations/connectors/destination-postgres/gradle.properties @@ -1,3 +1,3 @@ # currently limit the number of parallel threads until further investigation into the issues \ # where integration tests run into race conditions -numberThreads=1 +testExecutionConcurrency=1 diff --git a/airbyte-integrations/connectors/destination-snowflake/gradle.properties b/airbyte-integrations/connectors/destination-snowflake/gradle.properties index b677e2e4341..3ce49dd31e2 100644 --- a/airbyte-integrations/connectors/destination-snowflake/gradle.properties +++ b/airbyte-integrations/connectors/destination-snowflake/gradle.properties @@ -1,4 +1,3 @@ # currently limit the number of parallel threads until further investigation into the issues \ # where Snowflake will fail to login using config credentials -numberThreads=4 -parallelExecutionsPerThread=6 +testExecutionConcurrency=4 diff --git a/build.gradle b/build.gradle index 119ec338cd1..24ef9ea9b21 100644 --- a/build.gradle +++ b/build.gradle @@ -505,27 +505,51 @@ subprojects { subproj -> } test { - maxHeapSize = '3g' - maxParallelForks = Runtime.runtime.availableProcessors() - // This allows to set up a `gradle.properties` file inside the connector folder to reduce parallelization. - // This is especially useful for connectors that share resources, like Redshift or Snowflake. - // This limits the number of test classes that execute in parallel. - // See also usages of parallelExecutionsPerThread. - if (subproj.hasProperty('numberThreads')) { - int numberThreads = 0 - String numberThreadsString = subproj.property('numberThreads').toString() - if (numberThreadsString.isInteger()) { - numberThreads = numberThreadsString as int - } - if (numberThreads > 0 && numberThreads < maxParallelForks) { - maxParallelForks = numberThreads + useJUnitPlatform() + testLogging() { + events 'skipped', 'started', 'passed', 'failed' + exceptionFormat 'full' + showStandardStreams = true + } + + // Set the timezone to UTC instead of picking up the host machine's timezone, + // which on a developer's laptop is more likely to be PST. + systemProperty 'user.timezone', 'UTC' + + // Enable parallel test execution in JUnit by default. + // This is to support @Execution(ExecutionMode.CONCURRENT) annotations + // See https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution for details. + systemProperty 'junit.jupiter.execution.parallel.enabled', 'true' + // Concurrency takes place at the class level. + systemProperty 'junit.jupiter.execution.parallel.mode.classes.default', 'concurrent' + // Within a class, the test methods are still run serially on the same thread. + systemProperty 'junit.jupiter.execution.parallel.mode.default', 'same_thread' + // Effectively disable JUnit concurrency by running tests in only one thread by default. + systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'fixed' + systemProperty 'junit.jupiter.execution.parallel.config.fixed.parallelism', 1 + + if (!subproj.hasProperty('testExecutionConcurrency')) { + // By default, let gradle spawn as many independent workers as it wants. + maxParallelForks = Runtime.runtime.availableProcessors() + maxHeapSize = '3G' + } else { + // Otherwise, run tests within the same JVM. + // Let gradle spawn only one worker. + maxParallelForks = 1 + maxHeapSize = '8G' + // Manage test execution concurrency in JUnit. + String concurrency = subproj.property('testExecutionConcurrency').toString() + if (concurrency.isInteger() && (concurrency as int) > 0) { + // Define a fixed number of threads when the property is set to a positive integer. + systemProperty 'junit.jupiter.execution.parallel.config.fixed.parallelism', concurrency + } else { + // Otherwise let JUnit manage the concurrency dynamically. + systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'dynamic' } } - systemProperty 'user.timezone', 'UTC' - + // Exclude all connector unit tests upon request. if (rootProject.ext.skipSlowTests) { - // Exclude all connector unit tests exclude '**/io/airbyte/integrations/source/**' exclude '**/io/airbyte/integrations/destination/**' } @@ -534,15 +558,6 @@ subprojects { subproj -> enabled = !rootProject.ext.skipSlowTests excludes = ['**/*Test*', '**/generated*'] } - useJUnitPlatform { - excludeTags('cloud-storage') - } - testLogging() { - events "passed", "skipped", "failed" - exceptionFormat 'full' - // uncomment to get the full log output - // showStandardStreams = true - } finalizedBy jacocoTestReportTask } @@ -587,8 +602,6 @@ subprojects { subproj -> implementation libs.airbyte.protocol } - - tasks.withType(SpotBugsTask).configureEach { // Reports can be found under each subproject in build/spotbugs/ reports { @@ -598,9 +611,6 @@ subprojects { subproj -> } javadoc.options.addStringOption('Xdoclint:none', '-quiet') - tasks.named('check').configure { - dependsOn tasks.named('jacocoTestCoverageVerification') - } } // integration and performance test tasks per project @@ -610,7 +620,6 @@ allprojects { [ 'integrationTestJava', 'integrationTestPython', - 'standardSourceTestFile', ].contains(it.name) } } diff --git a/buildSrc/src/main/groovy/airbyte-integration-test-java.gradle b/buildSrc/src/main/groovy/airbyte-integration-test-java.gradle index 0490abfb36e..3c824bfb853 100644 --- a/buildSrc/src/main/groovy/airbyte-integration-test-java.gradle +++ b/buildSrc/src/main/groovy/airbyte-integration-test-java.gradle @@ -28,41 +28,26 @@ class AirbyteIntegrationTestJavaPlugin implements Plugin { testClassesDirs = project.sourceSets.integrationTestJava.output.classesDirs classpath += project.sourceSets.integrationTestJava.runtimeClasspath - useJUnitPlatform { - // todo (cgardens) - figure out how to de-dupe this exclusion with the one in build.gradle. - excludeTags 'log4j2-config', 'logger-client', 'cloud-storage' - } - + useJUnitPlatform() testLogging() { - events "passed", "failed", "started" - exceptionFormat "full" - // uncomment to get the full log output - // showStandardStreams = true + events 'skipped', 'started', 'passed', 'failed' + exceptionFormat 'full' + showStandardStreams = true } - outputs.upToDateWhen { false } - + systemProperties = project.test.systemProperties maxParallelForks = project.test.maxParallelForks maxHeapSize = project.test.maxHeapSize - systemProperties = [ - // Allow tests to set @Execution(ExecutionMode.CONCURRENT) - 'junit.jupiter.execution.parallel.enabled': 'true', - ] - - // Limit the number of concurrent tests within a single test class. - // See also usages of numberThreads. - if (project.hasProperty('parallelExecutionsPerThread')) { - int parallelExecutionsPerThread = project.property('parallelExecutionsPerThread').toString() as int - systemProperties = systemProperties + [ - 'junit.jupiter.execution.parallel.config.strategy': 'fixed', - 'junit.jupiter.execution.parallel.config.fixed.parallelism': parallelExecutionsPerThread, - ] - } + // Always re-run integration tests no matter what. + outputs.upToDateWhen { false } } integrationTestJava.configure { mustRunAfter project.tasks.named('check') dependsOn project.tasks.matching { it.name == 'assemble' } } + project.tasks.named('build').configure { + dependsOn integrationTestJava + } } } diff --git a/buildSrc/src/main/groovy/airbyte-python.gradle b/buildSrc/src/main/groovy/airbyte-python.gradle index 2511a27ba4a..e7913ca0a16 100644 --- a/buildSrc/src/main/groovy/airbyte-python.gradle +++ b/buildSrc/src/main/groovy/airbyte-python.gradle @@ -173,6 +173,14 @@ class AirbytePythonPlugin implements Plugin { } Helpers.addTestTaskIfTestFilesFound(project, 'integration_tests', 'integrationTestPython', installTestReqs) + def integrationTestTasks = project.tasks.matching { it.name == 'integrationTestPython' } + integrationTestTasks.configureEach { + dependsOn project.tasks.named('assemble') + mustRunAfter project.tasks.named('check') + } + project.tasks.named('build').configure { + dependsOn integrationTestTasks + } } }