import com.r3.testing.DistributeTestsBy import com.r3.testing.PodLogLevel import net.corda.plugins.apiscanner.GenerateApi import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import static org.gradle.api.JavaVersion.VERSION_17 import static org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 import static org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9 buildscript { // For sharing constants between builds Properties constants = new Properties() file("$projectDir/constants.properties").withInputStream { constants.load(it) } // Our version: bump this on release. ext.baseVersion = constants.getProperty("cordaVersion") ext.versionSuffix = constants.getProperty("versionSuffix") ext.corda_build_edition = System.getenv("CORDA_BUILD_EDITION")?.trim() ?: "Corda Open Source" ext.corda_platform_version = constants.getProperty("platformVersion") ext.corda_shell_version = constants.getProperty("cordaShellVersion") ext.gradle_plugins_version = constants.getProperty("gradlePluginsVersion") // Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things. // // TODO: Sort this alphabetically. ext.warnings_as_errors = project.hasProperty("compilation.warningsAsErrors") ? project.property("compilation.warningsAsErrors").toBoolean() : false ext.quasar_group = 'co.paralleluniverse' // Set version of Quasar according to version of Java used: ext.quasar_version = constants.getProperty("quasarVersion") ext.quasar_classifier = constants.getProperty("quasarClassifier") ext.quasar_exclusions = [ 'co.paralleluniverse**', 'groovy**', 'com.esotericsoftware.**', 'jdk**', 'junit**', 'kotlin**', 'net.rubygrapefruit.**', 'org.gradle.**', 'org.apache.**', 'org.jacoco.**', 'org.junit**', 'org.slf4j**', 'worker.org.gradle.**', 'org.mockito.kotlin**', 'org.assertj**', 'org.hamcrest**', 'org.mockito**', 'org.opentest4j**' ] ext.capsule_version = constants.getProperty("capsuleVersion") ext.open_telemetry_version = constants.getProperty("openTelemetryVersion") ext.open_telemetry_sem_conv_version = constants.getProperty("openTelemetrySemConvVersion") ext.asm_version = constants.getProperty("asmVersion") ext.artemis_version = constants.getProperty("artemisVersion") ext.jackson_version = constants.getProperty("jacksonVersion") ext.jackson_kotlin_version = constants.getProperty("jacksonKotlinVersion") ext.jetty_version = constants.getProperty("jettyVersion") ext.jersey_version = constants.getProperty("jerseyVersion") ext.servlet_version = constants.getProperty("servletVersion") ext.assertj_version = constants.getProperty("assertjVersion") ext.slf4j_version = constants.getProperty("slf4JVersion") ext.log4j_version = constants.getProperty("log4JVersion") ext.bouncycastle_version = constants.getProperty("bouncycastleVersion") ext.guava_version = constants.getProperty("guavaVersion") ext.caffeine_version = constants.getProperty("caffeineVersion") ext.disruptor_version = constants.getProperty("disruptorVersion") ext.metrics_version = constants.getProperty("metricsVersion") ext.metrics_new_relic_version = constants.getProperty("metricsNewRelicVersion") ext.okhttp_version = constants.getProperty("okhttpVersion") ext.netty_version = constants.getProperty("nettyVersion") ext.tcnative_version = constants.getProperty("tcnativeVersion") ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion") ext.fileupload_version = constants.getProperty("fileuploadVersion") ext.kryo_version = constants.getProperty("kryoVersion") ext.kryo_serializer_version = constants.getProperty("kryoSerializerVersion") ext.junit_version = constants.getProperty("junitVersion") ext.junit_vintage_version = constants.getProperty("junitVintageVersion") ext.junit_jupiter_version = constants.getProperty("junitJupiterVersion") ext.junit_platform_version = constants.getProperty("junitPlatformVersion") ext.mockito_version = constants.getProperty("mockitoVersion") ext.mockito_kotlin_version = constants.getProperty("mockitoKotlinVersion") ext.hamkrest_version = constants.getProperty("hamkrestVersion") ext.jopt_simple_version = constants.getProperty("joptSimpleVersion") ext.jansi_version = constants.getProperty("jansiVersion") ext.hibernate_version = constants.getProperty("hibernateVersion") ext.h2_version = constants.getProperty("h2Version") ext.rxjava_version = constants.getProperty("rxjavaVersion") ext.dokka_version = constants.getProperty("dokkaVersion") ext.eddsa_version = constants.getProperty("eddsaVersion") ext.dependency_checker_version = constants.getProperty("dependencyCheckerVersion") ext.commons_collections_version = constants.getProperty("commonsCollectionsVersion") ext.beanutils_version = constants.getProperty("beanutilsVersion") ext.jsr305_version = constants.getProperty("jsr305Version") ext.shiro_version = constants.getProperty("shiroVersion") ext.artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion') ext.hikari_version = constants.getProperty("hikariVersion") ext.liquibase_version = constants.getProperty("liquibaseVersion") ext.artifactory_contextUrl = 'https://software.r3.com/artifactory' ext.publicArtifactURL = 'https://download.corda.net/maven' ext.docker_compose_rule_version = constants.getProperty("dockerComposeRuleVersion") ext.selenium_version = constants.getProperty("seleniumVersion") ext.ghostdriver_version = constants.getProperty("ghostdriverVersion") ext.proguard_version = constants.getProperty('proguardVersion') ext.jsch_version = constants.getProperty("jschVersion") ext.protonj_version = constants.getProperty("protonjVersion") ext.snappy_version = constants.getProperty("snappyVersion") ext.class_graph_version = constants.getProperty('classgraphVersion') ext.jcabi_manifests_version = constants.getProperty("jcabiManifestsVersion") ext.picocli_version = constants.getProperty("picocliVersion") ext.commons_io_version = constants.getProperty("commonsIoVersion") ext.controlsfx_version = constants.getProperty("controlsfxVersion") ext.detekt_version = constants.getProperty('detektVersion') ext.docker_java_version = constants.getProperty("dockerJavaVersion") ext.commons_configuration2_version = constants.getProperty("commonsConfiguration2Version") ext.commons_text_version = constants.getProperty("commonsTextVersion") ext.snake_yaml_version = constants.getProperty("snakeYamlVersion") ext.fontawesomefx_commons_version = constants.getProperty("fontawesomefxCommonsVersion") ext.fontawesomefx_fontawesome_version = constants.getProperty("fontawesomefxFontawesomeVersion") ext.javaassist_version = constants.getProperty("javaassistVersion") ext.corda_revision = { try { "git rev-parse HEAD".execute().text.trim() } catch (Exception ignored) { logger.warn("git is unavailable in build environment") "unknown" } }() ext.corda_docs_link = "https://docs.corda.net/docs/corda-os/$baseVersion" repositories { mavenLocal() // Use system environment to activate caching with Artifactory, // because it is actually easier to pass that during parallel build. // NOTE: it has to be a name of a virtual repository with all // required remote or local repositories! if (System.getenv("CORDA_USE_CACHE")) { maven { name "R3 Maven remote repositories" url "${artifactory_contextUrl}/${System.getenv("CORDA_USE_CACHE")}" authentication { basic(BasicAuthentication) } credentials { username = System.getenv('CORDA_ARTIFACTORY_USERNAME') password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') } } } else { maven { url "${publicArtifactURL}/corda-dependencies-dev" content { includeGroupByRegex 'net\\.corda(\\..*)?' includeGroupByRegex 'com\\.r3(\\..*)?' includeGroup 'co.paralleluniverse' } } maven { url "${publicArtifactURL}/corda-releases" content { includeGroupByRegex 'net\\.corda(\\..*)?' includeGroupByRegex 'com\\.r3(\\..*)?' } } mavenCentral() jcenter() } } dependencies { classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version" classpath "net.corda.plugins:cordformation:$gradle_plugins_version" classpath "net.corda.plugins:cordapp:$gradle_plugins_version" classpath "net.corda.plugins:api-scanner:$gradle_plugins_version" classpath "net.corda.plugins:jar-filter:$gradle_plugins_version" classpath "com.guardsquare:proguard-gradle:$proguard_version" classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0' classpath "org.jetbrains.dokka:dokka-base:$dokka_version" classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment. classpath "org.owasp:dependency-check-gradle:$dependency_checker_version" classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version" // Capsule gradle plugin forked and maintained locally to support Gradle 5.x // See https://github.com/corda/gradle-capsule-plugin classpath "us.kirchmeier:gradle-capsule-plugin:1.0.5_r3" classpath group: "com.r3.testing", name: "gradle-distributed-testing-plugin", version: '1.3.0' classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8" } configurations.classpath { // FORCE Gradle to use latest SNAPSHOT plugins. resolutionStrategy.cacheChangingModulesFor 0, 'seconds' } } plugins { id 'org.jetbrains.kotlin.jvm' apply false id 'org.jetbrains.kotlin.plugin.allopen' apply false id 'org.jetbrains.kotlin.plugin.jpa' apply false id 'com.github.johnrengelman.shadow' version '7.1.2' apply false id "org.ajoberstar.grgit" version "4.0.0" id 'corda.root-publish' id "org.jetbrains.dokka" version "1.8.20" } apply plugin: 'project-report' apply plugin: 'com.github.ben-manes.versions' apply plugin: 'com.r3.testing.distributed-testing' // If the command line project option -PversionFromGit is added to the gradle invocation, we'll resolve // the latest git commit hash and timestamp and create a version postfix from that if (project.hasProperty("versionFromGit")){ ext.versionSuffix = "${grgit.head().dateTime.format("yyyyMMdd_HHmmss")}-${grgit.head().abbreviatedId}" } // Need the `toString()` call on these, because they need to be converted from GStringImpl to Java Strings. if (ext.versionSuffix != ""){ ext.corda_release_version = "${ext.baseVersion}-${ext.versionSuffix}".toString() } else { ext.corda_release_version = "${ext.baseVersion}".toString() } logger.lifecycle("JDK: {}", System.getProperty("java.home")) logger.lifecycle("Quasar version: {}", quasar_version) logger.lifecycle("Quasar classifier: {}", quasar_classifier.toString()) logger.lifecycle("Building Corda version: {}", corda_release_version) logger.lifecycle("User home: {}", System.getProperty('user.home')) allprojects { apply plugin: 'java' apply plugin: 'kotlin-allopen' apply plugin: 'jacoco' apply plugin: 'org.owasp.dependencycheck' apply plugin: 'org.sonarqube' allOpen { annotations( "javax.persistence.Entity", "javax.persistence.Embeddable", "javax.persistence.MappedSuperclass" ) } dependencyCheck { suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml' cveValidForHours = 1 format = 'ALL' failOnError = project.property('owasp.failOnError') // by default CVSS is '11' which passes everything. Set between 0-10 to catch vulnerable deps failBuildOnCVSS = project.property('owasp.failBuildOnCVSS').toFloat() analyzers { assemblyEnabled = false nuspecEnabled = false nugetconfEnabled = false } } sourceCompatibility = VERSION_17 targetCompatibility = VERSION_17 jacoco { // JDK11 official support (https://github.com/jacoco/jacoco/releases/tag/v0.8.3) toolVersion = "0.8.7" } java { withSourcesJar() withJavadocJar() } tasks.withType(JavaCompile).configureEach { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" << "-Xlint:-options" << "-parameters" options.compilerArgs << '-XDenableSunApiLintControl' if (warnings_as_errors) { // We cannot fail the build on compiler warnings because we have java warnings that you cannot disable: // Signal is internal proprietary API and may be removed in a future release // otherwise we should have the following line here: // options.compilerArgs << "-Werror" } options.encoding = 'UTF-8' } tasks.withType(KotlinCompile).configureEach { compilerOptions { languageVersion = KOTLIN_1_9 apiVersion = KOTLIN_1_9 jvmTarget = JVM_17 javaParameters = true // Useful for reflection. freeCompilerArgs = ['-Xjvm-default=all-compatibility'] allWarningsAsErrors = warnings_as_errors } } tasks.register('compileAll') { task -> task.dependsOn tasks.withType(AbstractCompile) } tasks.withType(Jar).configureEach { task -> // Includes War and Ear manifest { attributes('Corda-Release-Version': corda_release_version) attributes('Corda-Platform-Version': corda_platform_version) attributes('Corda-Revision': corda_revision) attributes('Corda-Vendor': corda_build_edition) attributes('Automatic-Module-Name': "net.corda.${task.project.name.replaceAll('-', '.')}") attributes('Corda-Docs-Link': corda_docs_link) } } tasks.withType(Test).configureEach { jvmArgs += project(":node:capsule").file("src/main/resources/node-jvm-args.txt").readLines() jvmArgs += "--add-modules=jdk.incubator.foreign" // For the SharedMemoryIncremental forkEvery = 20 ignoreFailures = project.hasProperty('tests.ignoreFailures') ? project.property('tests.ignoreFailures').toBoolean() : false failFast = project.hasProperty('tests.failFast') ? project.property('tests.failFast').toBoolean() : false maxHeapSize = "1g" if (project.path.startsWith(':experimental') && System.getProperty("experimental.test.enable") == null) { enabled = false } // Required to use Gradle build cache (until Gradle 5.0 is released with default value of "append" set to false) // See https://github.com/gradle/gradle/issues/5269 and https://github.com/gradle/gradle/pull/6419 extensions.configure(TypeOf.typeOf(JacocoTaskExtension)) { ex -> // ex.append = false } if (name.contains("integrationTest")) { maxParallelForks = (System.env.CORDA_INT_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_INT_TESTING_FORKS".toInteger() } else { maxParallelForks = (System.env.CORDA_TESTING_FORKS == null) ? 1 : "$System.env.CORDA_TESTING_FORKS".toInteger() } // Prevent the project from creating temporary files outside of the build directory. systemProperty 'java.io.tmpdir', buildDir.absolutePath systemProperty 'java.security.egd', 'file:/dev/./urandom' } group 'net.corda' version "$corda_release_version" repositories { mavenLocal() // Prevents cache giving use the wrong artemis mavenCentral { content { includeGroup 'org.apache.activemq' } } // Use system environment to activate caching with Artifactory, // because it is actually easier to pass that during parallel build. // NOTE: it has to be a name of a virtual repository with all // required remote or local repositories! if (System.getenv("CORDA_USE_CACHE")) { maven { name "R3 Maven remote repositories" url "${artifactory_contextUrl}/${System.getenv("CORDA_USE_CACHE")}" authentication { basic(BasicAuthentication) } credentials { username = System.getenv('CORDA_ARTIFACTORY_USERNAME') password = System.getenv('CORDA_ARTIFACTORY_PASSWORD') } } } else { maven { url "${publicArtifactURL}/corda-dependencies" content { includeGroupByRegex 'net\\.corda(\\..*)?' includeGroupByRegex 'com\\.r3(\\..*)?' includeGroup 'co.paralleluniverse' includeGroup 'org.crashub' includeGroup 'com.github.bft-smart' includeGroup 'com.github.detro' } metadataSources { mavenPom() artifact() } } maven { url "${publicArtifactURL}/corda-dependencies-dev" content { includeGroup 'co.paralleluniverse' } } maven { url "${publicArtifactURL}/corda-dev" content { includeGroupByRegex 'net\\.corda(\\..*)?' includeGroupByRegex 'com\\.r3(\\..*)?' } } maven { url 'https://repo.gradle.org/gradle/libs-releases' content { includeGroup 'org.gradle' includeGroup 'com.github.detro' } } maven { url "${publicArtifactURL}/corda-releases" content { includeModule('net.corda', 'corda-shell') } } mavenCentral() jcenter() } } configurations { configureEach { resolutionStrategy { if (pluginManager.hasPlugin("org.jetbrains.kotlin.jvm")) { // Force dependencies to use the same version of Kotlin as Corda. force "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } // Force dependencies to use the same version of Guava as Corda. force "com.google.guava:guava:$guava_version" // Demand that everything uses our given versions of: // * Netty // * Apache commons-configuration2 eachDependency { details -> if (details.requested.group == 'io.netty' && details.requested.name.startsWith('netty-')) { if (details.requested.name.startsWith('netty-tcnative')) { details.useVersion tcnative_version } else { details.useVersion netty_version } } if (details.requested.group == 'org.apache.commons') { if (details.requested.name == "commons-configuration2") { details.useVersion commons_configuration2_version } else if (details.requested.name == "commons-text") { details.useVersion commons_text_version } } if (details.requested.group == 'org.yaml' && details.requested.name == 'snakeyaml') { details.useVersion snake_yaml_version } } dependencySubstitution { // We want to use SLF4J's version of these bindings: jcl-over-slf4j // Remove any transitive dependency on Apache's version. substitute module('commons-logging:commons-logging') with module("org.slf4j:jcl-over-slf4j:$slf4j_version") // Remove any transitive dependency on Logback (e.g. Liquibase 3.6 introduces this dependency) substitute module('ch.qos.logback:logback-classic') with module("org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version") // Netty-All is an uber-jar which contains every Netty module. // Exclude it to force us to use the individual Netty modules instead. substitute module('io.netty:netty-all') with module("io.netty:netty-common:$netty_version") // Force dependencies to use the same version of Guava as Corda. substitute module('com.google.guava:guava') with module("com.google.guava:guava:$guava_version") // Effectively delete this unused and unwanted transitive dependency of Artemis. substitute module('org.jgroups:jgroups') with module("org.apache.activemq:artemis-commons:$artemis_version") } // FORCE Gradle to use latest SNAPSHOT dependencies. cacheChangingModulesFor 0, 'seconds' } } if (pluginManager.hasPlugin("org.jetbrains.kotlin.jvm")) { // Select all of the compileClasspath and runtimeClasspath etc configurations, // but NOT the "classpath" configuration, as that is used by the Gradle plugins. matching { it.name.endsWith("Classpath") }.configureEach { cfg -> cfg.resolutionStrategy { dependencySubstitution { // Force dependencies to use the same version of Kotlin as Corda. substitute module('org.jetbrains.kotlin:kotlin-stdlib-common') with module("org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version") substitute module('org.jetbrains.kotlin:kotlin-stdlib') with module("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") substitute module('org.jetbrains.kotlin:kotlin-reflect') with module("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") } } } } } } sonarqube { properties { property "sonar.projectName", "Corda" property "sonar.projectKey", "corda" property 'sonar.tests', '**/src/test/**,**/src/smoke-test/**,**/src/integration-test/**,**/src/integration-test-slow/**' property 'sonar.coverage.jacoco.xmlReportPaths', "${rootDir.path}/build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml" property 'detekt.sonar.kotlin.baseline.path', "${rootDir.path}/detekt-baseline.xml" property 'detekt.sonar.kotlin.config.path', "${rootDir.path}/detekt-config.yml" } } configurations { detekt } // Required for building out the fat JAR. dependencies { implementation project(':node') implementation "com.google.guava:guava:$guava_version" // Set to corda implementation to ensure it exists now deploy nodes no longer relies on build implementation project(path: ":node:capsule", configuration: 'runtimeArtifacts') implementation project(path: ":testing:testserver:testcapsule:", configuration: 'runtimeArtifacts') // For the buildCordappDependenciesJar task runtimeOnly project(':client:jfx') runtimeOnly project(':client:mock') runtimeOnly project(':client:rpc') runtimeOnly project(':core') runtimeOnly project(':confidential-identities') runtimeOnly project(':finance:workflows') runtimeOnly project(':finance:contracts') runtimeOnly project(':testing:testserver') testImplementation project(':test-utils') detekt 'io.gitlab.arturbosch.detekt:detekt-cli:1.0.1' } jar { // Prevent the root project from building an unwanted dummy CorDapp. enabled = false } tasks.register('jacocoRootReport', JacocoReport) { dependsOn = subprojects.test // additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs) // sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs) // classDirectories = files(subprojects.sourceSets.main.output) // executionData = files(subprojects.jacocoTestReport.executionData) reports { html.required = true xml.required = true csv.required = false } onlyIf = { true } doFirst { executionData = files(executionData.findAll { it.exists() }) } } tasks.register('detekt', JavaExec) { def input = "$projectDir" def config = "$projectDir/detekt-config.yml" def baseline = "$projectDir/detekt-baseline.xml" def detektPluginsJar = project(':detekt-plugins').tasks.jar def plugins = detektPluginsJar.outputs.files.singleFile def params = ['-i', input, '-c', config, '-b', baseline, '--plugins', plugins] inputs.files(detektPluginsJar, config, baseline) mainClass = "io.gitlab.arturbosch.detekt.cli.Main" classpath = configurations.detekt args(params) } tasks.register('detektBaseline', JavaExec) { mainClass = "io.gitlab.arturbosch.detekt.cli.Main" classpath = configurations.detekt def input = "$projectDir" def config = "$projectDir/detekt-config.yml, $projectDir/detekt-baseline-config.yml" def baseline = "$projectDir/detekt-baseline.xml" def params = ['-i', input, '-c', config, '-b', baseline, '--create-baseline'] args(params) } tasks.withType(Test).configureEach { reports.html.outputLocation.set(file("${reporting.baseDir}/${name}")) } tasks.register('testReport', TestReport) { destinationDir = file("$buildDir/reports/allTests") // Include the results from the `test` task in all subprojects reportOn subprojects*.test } // Note: corda.jar is used at runtime so no runtime ZIP is necessary. // Resulting ZIP can be found in "build/distributions" tasks.register('buildCordappDependenciesZip', Zip) { baseName 'corda-deps' from configurations.runtimeOnly from configurations.implementation from configurations.testImplementation from buildscript.configurations.classpath from 'node/capsule/NOTICE' // CDDL notice duplicatesStrategy = DuplicatesStrategy.EXCLUDE } tasks.register('generateApi', GenerateApi) { baseName = "api-corda" } // This exists to reduce CI build time when the envvar is set (can save up to 40 minutes) if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) { if (file('corda-docs-only-build').exists()) { logger.info("Tests are disabled due to presence of file 'corda-docs-only-build' in the project root") } else { logger.info("Tests are disabled due to the presence of envvar CORDA_DOCS_ONLY_BUILD") } allprojects { test { exclude '*/**' } it.afterEvaluate { if (it.tasks.findByName("integrationTest") != null) { integrationTest { exclude '*/**' } } } it.afterEvaluate { if (it.tasks.findByName("smokeTest") != null) { smokeTest { exclude '*/**' } } } } } wrapper { gradleVersion = '5.6.4' distributionType = Wrapper.DistributionType.ALL } distributedTesting { profilesURL = 'https://raw.githubusercontent.com/corda/infrastructure-profiles/master' parallelTestGroups { allParallelIntegrationTest { testGroups 'integrationTest' profile 'generalPurpose.yml' podLogLevel PodLogLevel.INFO distribution DistributeTestsBy.METHOD } allParallelUnitTest { podLogLevel PodLogLevel.INFO testGroups 'test' profile 'generalPurpose.yml' distribution DistributeTestsBy.CLASS } allParallelUnitAndIntegrationTest { testGroups 'test', 'integrationTest' profile 'generalPurpose.yml' distribution DistributeTestsBy.METHOD } allParallelSmokeTest { testGroups 'smokeTest' profile 'regression.yml' distribution DistributeTestsBy.METHOD } allParallelSlowIntegrationTest { testGroups 'slowIntegrationTest' profile 'regression.yml' distribution DistributeTestsBy.METHOD } } }