From 73db260632430da959ab5cf5e19ad3ff6cf334bf Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Fri, 28 Aug 2026 10:12:57 +0200 Subject: [PATCH] build: register all CI hapi test task as real Test task Do no longer have logic based on 'gradle.startParameter.taskNames' checks. Instead, all tasks are registered as 'Test' tasks with the corresponding custom configuration. This has the advantage that (failing) test are associated with the task that runs them. In general, it makes the setup more straight-forward and idiomatic. Signed-off-by: Jendrik Johannes --- hedera-node/test-clients/build.gradle.kts | 335 ++++++++++------------ 1 file changed, 153 insertions(+), 182 deletions(-) diff --git a/hedera-node/test-clients/build.gradle.kts b/hedera-node/test-clients/build.gradle.kts index 68166b133a55..933c031fff41 100644 --- a/hedera-node/test-clients/build.gradle.kts +++ b/hedera-node/test-clients/build.gradle.kts @@ -128,7 +128,7 @@ val prCheckTags = "hapiTestStateThrottling" to "(STATE_THROTTLING&SERIAL)", ) -val remoteCheckTags = +val prRemoteCheckTags = prCheckTags .filterNot { it.key in @@ -224,10 +224,6 @@ val prCheckPlatformOverrides = "platformStatus.observingStatusDelay=10s,reconnect.minimumTimeBetweenReconnects=10s" ) val prCheckPrepareUpgradeOffsets = mapOf("hapiTestAdhoc" to "PT300S") -val prCheckAssertAtLeastOneWraps = - gradle.startParameter.taskNames.any { setOf("hapiTestWraps", "hapiTestCutover").contains(it) } -val prCheckIsSimpleFeesEmbedded = - gradle.startParameter.taskNames.contains("hapiTestSimpleFeesEmbedded") // Path to the extracted WRAPS proving-key artifacts (decider_pp.bin, decider_vp.bin, // nova_pp.bin, nova_vp.bin); blank disables WRAPS proof assertions in the ceremony tests val tssLibWrapsArtifactsPath = System.getenv("TSS_LIB_WRAPS_ARTIFACTS_PATH") ?: "" @@ -271,166 +267,144 @@ val prEmbeddedCheckTags = embeddedBaseTags.mapValues { (_, tags) -> "($tags)" } val repeatableBaseTags = mapOf("hapiTestMiscRepeatable" to "REPEATABLE&!CRYPTO") val prRepeatableCheckTags = repeatableBaseTags.mapValues { (_, tags) -> "($tags)" } -// Choose a different initial port for each test task if running as PR check -val initialPort = - gradle.startParameter.taskNames - .map { prCheckStartPorts[it] ?: "" } - .filter { it.isNotBlank() } - .firstOrNull() ?: "" - -// Gather platform-level overrides (settings.txt) into a single comma-separated list -val platformOverrides = - gradle.startParameter.taskNames - .mapNotNull { prCheckPlatformOverrides[it] } - .joinToString(separator = ",") - -val networkSize = - gradle.startParameter.taskNames - .map { prCheckNetSizeOverrides[it] ?: "" } - .filter { it.isNotBlank() } - .firstOrNull() ?: "4" - -val prepareUpgradeOffsets = - gradle.startParameter.taskNames - .mapNotNull { prCheckPrepareUpgradeOffsets[it] } - .joinToString(",") - -// Gather overrides into a single comma‐separated list -val testOverrides = - gradle.startParameter.taskNames - .mapNotNull { prCheckPropOverrides[it] } - .joinToString(separator = ",") - -tasks { - prCheckTags.forEach { (taskName, _) -> - register(taskName) { - group = "hapi-test" - dependsOn( - if ( - (taskName.contains("Crypto") || - taskName.contains("Token") || - taskName.contains("Misc") || - taskName.contains("TimeConsuming") || - taskName.contains("SimpleFees") || - taskName.contains("AtomicBatch") || - taskName.contains("SmartContract")) && !taskName.contains("Serial") - ) - "testSubprocessConcurrent" - else "testSubprocess" - ) - } - } - remoteCheckTags.forEach { (taskName, _) -> register(taskName) { dependsOn("testRemote") } } - prEmbeddedCheckTags.forEach { (taskName, _) -> - register(taskName) { - group = "hapi-test-embedded" - dependsOn("testEmbedded") - } - } - prRepeatableCheckTags.forEach { (taskName, _) -> - register(taskName) { dependsOn("testRepeatable") } - } -} - // Unlike other tests, these intentionally corrupt embedded state to test FAIL_INVALID // code paths; hence we do not run LOG_VALIDATION after the test suite finishes tasks.registerHapiTest( "test", - emptyMap(), + "", "(INTEGRATION|STREAM_VALIDATION)", embeddedMode = "per-class", junitParallelMode = "same_thread", ) -tasks.registerHapiTest( - "testSubprocess", - prCheckTags, - "none()|!(EMBEDDED|REPEATABLE)", - ciDefaultTags = "|CONCURRENT_SUBPROCESS_VALIDATION)&!(EMBEDDED|REPEATABLE|ISS", - ciDefaultTagsWithoutStreamAndLogValidation = ")&!(EMBEDDED|REPEATABLE", - excludeTags = "CONCURRENT_SUBPROCESS_VALIDATION", - junitParallelMode = "same_thread", - initialPort = initialPort, - // There's nothing special about shard/realm 11.12, except that they are non-zero values. - // We want to run all tests that execute as part of `testSubprocess`–that is to say, - // the majority of the hapi tests - with a nonzero shard/realm - // to maintain confidence that we haven't fallen back into the habit of assuming 0.0 - defaultShard = 11, - defaultRealm = 12, - // Note the 1/4 threshold for the restart check; DabEnabledUpgradeTest is a chaotic - // churn of fast upgrades with heavy use of override networks, and there is a node - // removal step that happens without giving enough time for the next hinTS scheme - // to be completed, meaning a 1/3 threshold in the *actual* roster only accounts for - // 1/4 total weight in the out-of-date hinTS verification key. - hapiSpecHintsThresholdDenominator = - if (gradle.startParameter.taskNames.contains("hapiTestRestart")) "4" else "3", - hapiSpecBlockStateproofVerificationOff = true, -) +registerTestSubprocess("testSubprocess", "") // standard tasks for local dev without tag filter + +registerTestSubprocessConcurrent("testSubprocessConcurrent", "") + +prCheckTags.forEach { (taskName, ciTagExpression) -> + if ( + (taskName.contains("Crypto") || + taskName.contains("Token") || + taskName.contains("Misc") || + taskName.contains("TimeConsuming") || + taskName.contains("SimpleFees") || + taskName.contains("AtomicBatch") || + taskName.contains("SmartContract")) && !taskName.contains("Serial") + ) { + registerTestSubprocessConcurrent(taskName, ciTagExpression) + } else { + registerTestSubprocess(taskName, ciTagExpression) + } +} -tasks.registerHapiTest( - "testSubprocessConcurrent", - prCheckTags, - "none()|!(EMBEDDED|REPEATABLE|ISS)", - ciDefaultTags = "|CONCURRENT_SUBPROCESS_VALIDATION)&!(EMBEDDED|REPEATABLE|ISS", - ciDefaultTagsWithoutStreamAndLogValidation = ")&!(EMBEDDED|REPEATABLE", - excludeTags = "SERIAL&!CONCURRENT_SUBPROCESS_VALIDATION", - junitParallelMode = "concurrent", - junitFixedParallelism = if (networkSize.toInt() <= 3) 3 else 2, - initialPort = initialPort, - // There's nothing special about shard/realm 11.12, except that they are non-zero values. - // We want to run all tests that execute as part of `testSubprocess`–that is to say, - // the majority of the hapi tests - with a nonzero shard/realm - // to maintain confidence that we haven't fallen back into the habit of assuming 0.0 - defaultShard = 11, - defaultRealm = 12, - // Note the 1/4 threshold for the restart check; DabEnabledUpgradeTest is a chaotic - // churn of fast upgrades with heavy use of override networks, and there is a node - // removal step that happens without giving enough time for the next hinTS scheme - // to be completed, meaning a 1/3 threshold in the *actual* roster only accounts for - // 1/4 total weight in the out-of-date hinTS verification key. - hapiSpecSubprocessConcurrent = true, - hapiSpecHintsThresholdDenominator = - if (gradle.startParameter.taskNames.contains("hapiTestRestart")) "4" else "3", - hapiSpecBlockStateproofVerificationOff = true, -) +registerTestRemote("testRemote", "") -tasks.registerHapiTest( - "testRemote", - remoteCheckTags, - "none()|!(EMBEDDED|REPEATABLE)", - ciDefaultTags = "&!(EMBEDDED|REPEATABLE)", - junitParallelMode = "same_thread", - hapiSpecRemote = true, -) +prRemoteCheckTags.forEach { (taskName, ciTagExpression) -> + registerTestRemote(taskName, ciTagExpression) +} + +registerTestEmbedded("testEmbedded", "") + +prEmbeddedCheckTags.forEach { (taskName, ciTagExpression) -> + registerTestEmbedded(taskName, ciTagExpression) +} + +registerTestRepeatable("testRepeatable", "") + +prRepeatableCheckTags.forEach { (taskName, ciTagExpression) -> + registerTestRepeatable(taskName, ciTagExpression) +} + +fun registerTestSubprocess(name: String, ciTagExpression: String) { + tasks.registerHapiTest( + name, + ciTagExpression, + "none()|!(EMBEDDED|REPEATABLE)", + ciDefaultTags = "|CONCURRENT_SUBPROCESS_VALIDATION)&!(EMBEDDED|REPEATABLE|ISS", + ciDefaultTagsWithoutStreamAndLogValidation = ")&!(EMBEDDED|REPEATABLE", + excludeTags = "CONCURRENT_SUBPROCESS_VALIDATION", + junitParallelMode = "same_thread", + // There's nothing special about shard/realm 11.12, except that they are non-zero values. + // We want to run all tests that execute as part of `testSubprocess`–that is to say, + // the majority of the hapi tests - with a nonzero shard/realm + // to maintain confidence that we haven't fallen back into the habit of assuming 0.0 + defaultShard = 11, + defaultRealm = 12, + // Note the 1/4 threshold for the restart check; DabEnabledUpgradeTest is a chaotic + // churn of fast upgrades with heavy use of override networks, and there is a node + // removal step that happens without giving enough time for the next hinTS scheme + // to be completed, meaning a 1/3 threshold in the *actual* roster only accounts for + // 1/4 total weight in the out-of-date hinTS verification key. + hapiSpecHintsThresholdDenominator = if (name == "hapiTestRestart") "4" else "3", + hapiSpecBlockStateproofVerificationOff = true, + ) +} + +fun registerTestSubprocessConcurrent(name: String, ciTagExpression: String) { + tasks.registerHapiTest( + name, + ciTagExpression, + "none()|!(EMBEDDED|REPEATABLE|ISS)", + ciDefaultTags = "|CONCURRENT_SUBPROCESS_VALIDATION)&!(EMBEDDED|REPEATABLE|ISS", + ciDefaultTagsWithoutStreamAndLogValidation = ")&!(EMBEDDED|REPEATABLE", + excludeTags = "SERIAL&!CONCURRENT_SUBPROCESS_VALIDATION", + junitParallelMode = "concurrent", + junitFixedParallelism = true, + // There's nothing special about shard/realm 11.12, except that they are non-zero values. + // We want to run all tests that execute as part of `testSubprocess`–that is to say, + // the majority of the hapi tests - with a nonzero shard/realm + // to maintain confidence that we haven't fallen back into the habit of assuming 0.0 + defaultShard = 11, + defaultRealm = 12, + hapiSpecSubprocessConcurrent = true, + hapiSpecHintsThresholdDenominator = "3", + hapiSpecBlockStateproofVerificationOff = true, + ) +} + +fun registerTestRemote(name: String, ciTagExpression: String) { + tasks.registerHapiTest( + name, + ciTagExpression, + "none()|!(EMBEDDED|REPEATABLE)", + ciDefaultTags = "&!(EMBEDDED|REPEATABLE)", + junitParallelMode = "same_thread", + hapiSpecRemote = true, + ) +} // Runs tests against an embedded network that supports concurrent tests -tasks.registerHapiTest( - "testEmbedded", - prEmbeddedCheckTags, - "none()|!(RESTART|ND_RECONNECT|UPGRADE|REPEATABLE|ONLY_SUBPROCESS|ISS)", - ciDefaultTags = "|STREAM_VALIDATION|LOG_VALIDATION)&!(INTEGRATION|ISS", - // Tell our launcher to target a concurrent embedded network - embeddedMode = "concurrent", - junitParallelMode = "same_thread", - // Running all the tests that are executed in testEmbedded with 0 for shard and realm, - // so we can maintain confidence that there are no regressions in the code. - defaultShard = 0, - defaultRealm = 0, -) +fun registerTestEmbedded(name: String, ciTagExpression: String) { + tasks.registerHapiTest( + name, + ciTagExpression, + "none()|!(RESTART|ND_RECONNECT|UPGRADE|REPEATABLE|ONLY_SUBPROCESS|ISS)", + ciDefaultTags = "|STREAM_VALIDATION|LOG_VALIDATION)&!(INTEGRATION|ISS", + // Tell our launcher to target a concurrent embedded network + embeddedMode = "concurrent", + junitParallelMode = "same_thread", + // Running all the tests that are executed in testEmbedded with 0 for shard and realm, + // so we can maintain confidence that there are no regressions in the code. + defaultShard = 0, + defaultRealm = 0, + ) +} // Runs tests against an embedded network that achieves repeatable results by running tests in a // single thread -tasks.registerHapiTest( - "testRepeatable", - prRepeatableCheckTags, - "none()|!(RESTART|ND_RECONNECT|UPGRADE|EMBEDDED|NOT_REPEATABLE|ONLY_SUBPROCESS|ISS)", - ciDefaultTags = "|STREAM_VALIDATION|LOG_VALIDATION)&!(INTEGRATION|ISS|EMBEDDED", - embeddedMode = "repeatable", -) +fun registerTestRepeatable(name: String, ciTagExpression: String) { + tasks.registerHapiTest( + name, + ciTagExpression, + "none()|!(RESTART|ND_RECONNECT|UPGRADE|EMBEDDED|NOT_REPEATABLE|ONLY_SUBPROCESS|ISS)", + ciDefaultTags = "|STREAM_VALIDATION|LOG_VALIDATION)&!(INTEGRATION|ISS|EMBEDDED", + embeddedMode = "repeatable", + ) +} fun TaskContainer.registerHapiTest( name: String, - ciTags: Map, + ciTagExpression: String, defaultTags: String, ciDefaultTags: String? = null, ciDefaultTagsWithoutStreamAndLogValidation: String? = null, @@ -443,8 +417,7 @@ fun TaskContainer.registerHapiTest( // get a material speed up. See https://github.com/gradle/gradle/issues/6453. // That's why parallel mode is set to 'same_thread' for certain cases junitParallelMode: String? = null, - junitFixedParallelism: Int? = null, - initialPort: String? = null, + junitFixedParallelism: Boolean = false, defaultShard: Int? = null, defaultRealm: Int? = null, hapiSpecSubprocessConcurrent: Boolean = false, @@ -452,17 +425,8 @@ fun TaskContainer.registerHapiTest( hapiSpecBlockStateproofVerificationOff: Boolean = false, hapiSpecRemote: Boolean = false, ) { - (if (name == "test") test else register(name)).configure { - val ciTagExpression = - gradle.startParameter.taskNames - .map { ciTags[it] ?: "" } - .filter { it.isNotBlank() } - .toList() - .joinToString("|") - - val subtaskName = - gradle.startParameter.taskNames.firstOrNull { ciTags.containsKey(it) } ?: "" - + val hapiTest = if (name == "test") test else register(name) + hapiTest { // Shared configuration of all test tasks testClassesDirs = sourceSets.main.get().output.classesDirs classpath = configurations.testRuntimeClasspath.get().plus(files(jar)) @@ -480,25 +444,31 @@ fun TaskContainer.registerHapiTest( "com.hedera.cryptography.libsodium" ) // Isolate each subtask's working directory so logs are not overwritten - if (subtaskName.isNotBlank()) { - systemProperty("hapi.spec.subtask.name", subtaskName) + systemProperty("hapi.spec.subtask.name", name) + + if (prCheckPropOverrides.containsKey(name)) { + systemProperty("hapi.spec.test.overrides", prCheckPropOverrides.getValue(name)) } - if (testOverrides.isNotBlank()) { - systemProperty("hapi.spec.test.overrides", testOverrides) + if (prCheckPlatformOverrides.containsKey(name)) { + systemProperty("hapi.spec.platform.overrides", prCheckPlatformOverrides.getValue(name)) } - if (platformOverrides.isNotBlank()) { - systemProperty("hapi.spec.platform.overrides", platformOverrides) + if (prCheckPrepareUpgradeOffsets.containsKey(name)) { + systemProperty( + "hapi.spec.prepareUpgradeOffsets", + prCheckPrepareUpgradeOffsets.getValue(name), + ) } - if (prepareUpgradeOffsets.isNotBlank()) { - systemProperty("hapi.spec.prepareUpgradeOffsets", prepareUpgradeOffsets) + if (prCheckStartPorts.containsKey(name)) { + systemProperty("hapi.spec.initial.port", prCheckStartPorts.get(name)) } - if (prCheckAssertAtLeastOneWraps) { + if (name in setOf("hapiTestWraps", "hapiTestCutover")) { systemProperty("hapi.spec.assertAtLeastOneWraps", "true") } - if (prCheckIsSimpleFeesEmbedded) { + if (name == "hapiTestSimpleFeesEmbedded") { systemProperty("fees.createSimpleFeeSchedule", "true") systemProperty("fees.simpleFeesEnabled", "true") } + val networkSize = prCheckNetSizeOverrides.getOrElse(name) { "4" } systemProperty("hapi.spec.network.size", networkSize) // Default quiet mode is "false" unless we are running in CI or set it explicitly to "true" systemProperty( @@ -507,14 +477,12 @@ fun TaskContainer.registerHapiTest( .systemProperty("hapi.spec.quiet.mode") .getOrElse(if (ciTagExpression.isNotBlank()) "true" else "false"), ) - gradle.startParameter.taskNames - .firstOrNull(prCheckTssLibWrapsArtifactsPaths::containsKey) - ?.let { - systemProperty( - "hapi.spec.tssLibWrapsArtifactsPath", - prCheckTssLibWrapsArtifactsPaths.getValue(it), - ) - } + if (prCheckTssLibWrapsArtifactsPaths.containsKey(name)) { + systemProperty( + "hapi.spec.tssLibWrapsArtifactsPath", + prCheckTssLibWrapsArtifactsPaths.getValue(name), + ) + } // Pass a system property "KEY=VALUE" to the test JVM via "-PsysProp.KEY=VALUE" providers.gradlePropertiesPrefixedBy("sysProp.").get().forEach { (k, v) -> systemProperty(k.removePrefix("sysProp."), v) @@ -553,19 +521,17 @@ fun TaskContainer.registerHapiTest( "org.junit.jupiter.api.ClassOrderer\$OrderAnnotation", ) } - if (junitFixedParallelism != null) { + if (junitFixedParallelism) { + val parallelismValue = if (networkSize.toInt() <= 3) 3 else 2 systemProperty("junit.jupiter.execution.parallel.config.strategy", "fixed") systemProperty( "junit.jupiter.execution.parallel.config.fixed.parallelism", - "$junitFixedParallelism", + parallelismValue, ) } if (embeddedMode != null) { systemProperty("hapi.spec.embedded.mode", embeddedMode) } - if (initialPort != null) { - systemProperty("hapi.spec.initial.port", initialPort) - } if (defaultShard != null) { systemProperty("hapi.spec.default.shard", defaultShard) } @@ -587,6 +553,11 @@ fun TaskContainer.registerHapiTest( System.getenv("REMOTE_TARGET")?.let { systemProperty("hapi.spec.nodes.remoteYml", it) } } } + // Our convention plugins put all test tasks into the 'build' group for easy access in IntelliJ. + // Tasks that are primarily for CI execution are moved into a separate group here. + if (ciTagExpression.isNotBlank()) { + afterEvaluate { hapiTest { group = "hapi-test" } } + } } application.mainClass = "com.hedera.services.bdd.suites.SuiteRunner"