Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,65 @@ class ElasticsearchTestBasePluginFuncTest extends AbstractGradleFuncTest {
then:
result.task(':test').outcome == TaskOutcome.UP_TO_DATE
}

def "uses new test seed for every invocation"() {
given:
file("src/test/java/acme/SomeTests.java").text = """

public class SomeTests {
@org.junit.Test
public void printTestSeed() {
System.out.println("TESTSEED=[" + System.getProperty("tests.seed") + "]");
}
}

"""
buildFile.text = """
plugins {
id 'java'
id 'elasticsearch.test-base'
}

tasks.named('test').configure {
testLogging {
showStandardStreams = true
}
}

tasks.register('test2', Test) {
classpath = sourceSets.test.runtimeClasspath
testClassesDirs = sourceSets.test.output.classesDirs
testLogging {
showStandardStreams = true
}
}

repositories {
mavenCentral()
}

dependencies {
testImplementation 'junit:junit:4.12'
}

"""

when:
def result1 = gradleRunner("cleanTest", "cleanTest2", "test", "test2").build()
def result2 = gradleRunner("cleanTest", "cleanTest2", "test", "test2").build()

then:
def seeds1 = result1.output.findAll(/(?m)TESTSEED=\[([^\]]+)\]/) { it[1] }
def seeds2 = result2.output.findAll(/(?m)TESTSEED=\[([^\]]+)\]/) { it[1] }

seeds1.unique().size() == 1
seeds2.unique().size() == 1

verifyAll {
seeds1[0] != null
seeds2[0] != null
seeds1[0] != seeds2[0]
}
Comment on lines +165 to +169
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against empty seed lists before indexing

If neither test nor test2 emits the TESTSEED=[…] line, seeds1[0]/seeds2[0] will throw IndexOutOfBoundsException. Please assert the lists are non-empty before indexing, e.g. !seeds1.isEmpty() / !seeds2.isEmpty().

🤖 Prompt for AI Agents
In
build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/ElasticsearchTestBasePluginFuncTest.groovy
around lines 165 to 169, guard against empty seed lists before indexing by first
asserting both lists are non-empty (e.g. !seeds1.isEmpty() and
!seeds2.isEmpty()) and only then index into [0] for null checks and comparison;
update the verifyAll block to check !seeds1.isEmpty() && !seeds2.isEmpty()
before evaluating seeds1[0] != null, seeds2[0] != null and seeds1[0] !=
seeds2[0].

result2.output.contains("Configuration cache entry reused.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.configuration.BuildFeatures;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.ProviderFactory;
Expand Down Expand Up @@ -56,6 +57,9 @@ public abstract class ElasticsearchTestBasePlugin implements Plugin<Project> {
@Inject
protected abstract ProviderFactory getProviderFactory();

@Inject
protected abstract BuildFeatures getBuildFeatures();

@Override
public void apply(Project project) {
project.getRootProject().getPlugins().apply(GlobalBuildInfoPlugin.class);
Expand Down Expand Up @@ -164,9 +168,11 @@ public void execute(Task t) {
);
test.systemProperties(sysprops);

// ignore changing test seed when build is passed -Dignore.tests.seed for cacheability experimentation
if (System.getProperty("ignore.tests.seed") != null) {
nonInputProperties.systemProperty("tests.seed", buildParams.get().getTestSeed());
// ignore changing test seed when build is passed -Dignore.tests.seed for cacheability
// also ignore when configuration cache is on since the test seed as task input would break
// configuration cache reuse.
if (System.getProperty("ignore.tests.seed") != null || getBuildFeatures().getConfigurationCache().getActive().get()) {
nonInputProperties.systemProperty("tests.seed", buildParams.get().getTestSeedProvider());
} else {
test.systemProperty("tests.seed", buildParams.get().getTestSeed());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public interface BuildParameterExtension {

String getTestSeed();

Provider<String> getTestSeedProvider();

Boolean getCi();

Integer getDefaultParallel();
Expand All @@ -66,7 +68,7 @@ public interface BuildParameterExtension {

Provider<BwcVersions> getBwcVersionsProvider();

Random getRandom();
Provider<Random> getRandom();

Boolean getGraalVmRuntime();
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public abstract class DefaultBuildParameterExtension implements BuildParameterEx
private final Provider<String> gitRevision;

private transient AtomicReference<ZonedDateTime> buildDate = new AtomicReference<>();
private final String testSeed;
private final Provider<String> testSeed;
private final Boolean isCi;
private final Integer defaultParallel;
private final Boolean snapshotBuild;
Expand All @@ -58,7 +58,7 @@ public DefaultBuildParameterExtension(
JavaVersion gradleJavaVersion,
Provider<String> gitRevision,
Provider<String> gitOrigin,
String testSeed,
Provider<String> testSeed,
boolean isCi,
int defaultParallel,
final boolean isSnapshotBuild,
Expand Down Expand Up @@ -181,6 +181,11 @@ public ZonedDateTime getBuildDate() {

@Override
public String getTestSeed() {
return testSeed.get();
}

@Override
public Provider<String> getTestSeedProvider() {
return testSeed;
}

Expand All @@ -205,8 +210,8 @@ public BwcVersions getBwcVersions() {
}

@Override
public Random getRandom() {
return new Random(Long.parseUnsignedLong(testSeed.split(":")[0], 16));
public Provider<Random> getRandom() {
return getTestSeedProvider().map(seed -> new Random(Long.parseUnsignedLong(seed.split(":")[0], 16)));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.configuration.BuildFeatures;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.model.ObjectFactory;
Expand Down Expand Up @@ -60,7 +61,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -87,7 +87,7 @@ public class GlobalBuildInfoPlugin implements Plugin<Project> {
private final JavaInstallationRegistry javaInstallationRegistry;
private final JvmMetadataDetector metadataDetector;
private final ProviderFactory providers;
private final BranchesFileParser branchesFileParser;
private final BuildFeatures buildFeatures;
private JavaToolchainService toolChainService;
private Project project;

Expand All @@ -96,13 +96,14 @@ public GlobalBuildInfoPlugin(
ObjectFactory objectFactory,
JavaInstallationRegistry javaInstallationRegistry,
JvmMetadataDetector metadataDetector,
ProviderFactory providers
ProviderFactory providers,
BuildFeatures buildFeatures
) {
this.objectFactory = objectFactory;
this.javaInstallationRegistry = javaInstallationRegistry;
this.metadataDetector = new ErrorTraceMetadataDetector(metadataDetector);
this.providers = providers;
this.branchesFileParser = new BranchesFileParser(new ObjectMapper());
this.buildFeatures = buildFeatures;
}

@Override
Expand All @@ -113,6 +114,7 @@ public void apply(Project project) {
this.project = project;
project.getPlugins().apply(JvmToolchainsPlugin.class);
project.getPlugins().apply(JdkDownloadPlugin.class);
project.getPlugins().apply(VersionPropertiesPlugin.class);
Provider<GitInfo> gitInfo = project.getPlugins().apply(GitInfoPlugin.class).getGitInfo();

toolChainService = project.getExtensions().getByType(JavaToolchainService.class);
Expand All @@ -121,11 +123,9 @@ public void apply(Project project) {
throw new GradleException("Gradle " + minimumGradleVersion.getVersion() + "+ is required");
}

project.getPlugins().apply(VersionPropertiesPlugin.class);
Properties versionProperties = (Properties) project.getExtensions().getByName(VERSIONS_EXT);
JavaVersion minimumCompilerVersion = JavaVersion.toVersion(versionProperties.get("minimumCompilerJava"));
JavaVersion minimumRuntimeVersion = JavaVersion.toVersion(versionProperties.get("minimumRuntimeJava"));

Version elasticsearchVersionProperty = Version.fromString(versionProperties.getProperty("elasticsearch"));

RuntimeJava runtimeJavaHome = findRuntimeJavaHome();
Expand Down Expand Up @@ -233,6 +233,7 @@ private List<DevelopmentBranch> getDevelopmentBranches() {
}
}

var branchesFileParser = new BranchesFileParser(new ObjectMapper());
return branchesFileParser.parse(branchesBytes);
}

Expand Down Expand Up @@ -266,7 +267,11 @@ private void logGlobalBuildInfo(BuildParameterExtension buildParams) {
if (javaToolchainHome != null) {
LOGGER.quiet(" JAVA_TOOLCHAIN_HOME : " + javaToolchainHome);
}
LOGGER.quiet(" Random Testing Seed : " + buildParams.getTestSeed());

if (buildFeatures.getConfigurationCache().getActive().get() == false) {
// if configuration cache is enabled, resolving the test seed early breaks configuration cache reuse
LOGGER.quiet(" Random Testing Seed : " + buildParams.getTestSeed());
}
LOGGER.quiet(" In FIPS 140 mode : " + buildParams.getInFipsJvm());
LOGGER.quiet("=======================================");
}
Expand Down Expand Up @@ -321,16 +326,8 @@ private Stream<InstallationLocation> getAvailableJavaInstallationLocationSteam()
);
}

private static String getTestSeed() {
String testSeedProperty = System.getProperty("tests.seed");
final String testSeed;
if (testSeedProperty == null) {
long seed = new Random(System.currentTimeMillis()).nextLong();
testSeed = Long.toUnsignedString(seed, 16).toUpperCase(Locale.ROOT);
} else {
testSeed = testSeedProperty;
}
return testSeed;
private Provider<String> getTestSeed() {
return project.getProviders().of(TestSeedValueSource.class, spec -> {});
}

private static void throwInvalidJavaHomeException(String description, File javaHome, int expectedVersion, int actualVersion) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.gradle.internal.info;

import org.gradle.api.provider.ValueSource;
import org.gradle.api.provider.ValueSourceParameters;

import java.util.Locale;
import java.util.Random;

public abstract class TestSeedValueSource implements ValueSource<String, ValueSourceParameters.None> {

@Override
public ValueSourceParameters.None getParameters() {
return null;
}
Comment on lines +20 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Return the ValueSourceParameters.None singleton instead of null.

Using the provided singleton clarifies intent and avoids any accidental null-handling edge cases inside Gradle’s value source plumbing.

     @Override
     public ValueSourceParameters.None getParameters() {
-        return null;
+        return ValueSourceParameters.None.INSTANCE;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public ValueSourceParameters.None getParameters() {
return null;
}
@Override
public ValueSourceParameters.None getParameters() {
return ValueSourceParameters.None.INSTANCE;
}
🤖 Prompt for AI Agents
In
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/TestSeedValueSource.java
around lines 20 to 23, the getParameters() method currently returns null;
replace that null with the provided singleton by returning
ValueSourceParameters.None.INSTANCE instead so the method returns the
appropriate non-null singleton value and avoids null-handling issues in Gradle's
value source plumbing.


@Override
public String obtain() {
String testSeedProperty = System.getProperty("tests.seed");
final String testSeed;
if (testSeedProperty == null) {
long seed = new Random(System.currentTimeMillis()).nextLong();
testSeed = Long.toUnsignedString(seed, 16).toUpperCase(Locale.ROOT);
} else {
testSeed = testSeedProperty;
}
return testSeed;
}
}
4 changes: 1 addition & 3 deletions x-pack/plugin/security/qa/profile/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ dependencies {
javaRestTestImplementation project(':x-pack:plugin:security')
}

boolean literalUsername = buildParams.random.nextBoolean()

tasks.named("javaRestTest").configure {
usesDefaultDistribution("to be triaged")
systemProperty 'test.literalUsername', literalUsername
systemProperty 'test.literalUsername', buildParams.random.map{r -> r.nextBoolean()}.get()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't eagerly resolve the random provider.

Calling .get() here forces the provider to resolve during configuration, which locks the boolean into the configuration cache and still reuses the same value on subsequent invocations. Use the provider directly (via systemProperty’s provider overload or systemPropertyProviders) so the boolean is generated when the task runs.

-  systemProperty 'test.literalUsername', buildParams.random.map{ r -> r.nextBoolean() }.get()
+  systemPropertyProviders.put('test.literalUsername', buildParams.random.map { r -> Boolean.toString(r.nextBoolean()) })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
systemProperty 'test.literalUsername', buildParams.random.map{r -> r.nextBoolean()}.get()
systemPropertyProviders.put('test.literalUsername', buildParams.random.map { r -> Boolean.toString(r.nextBoolean()) })
🤖 Prompt for AI Agents
In x-pack/plugin/security/qa/profile/build.gradle around line 17, the call to
buildParams.random.map{r -> r.nextBoolean()}.get() eagerly resolves the provider
at configuration time; change it to pass the Provider itself to the system
property (use the systemProperty overload that accepts a Provider or use
systemPropertyProviders) so the boolean is produced at execution time rather
than calling .get() during configuration.

}
4 changes: 0 additions & 4 deletions x-pack/plugin/sql/qa/jdbc/security/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import org.elasticsearch.gradle.internal.test.RestIntegTestTask
import org.elasticsearch.gradle.testclusters.TestClusterValueSource
import org.elasticsearch.gradle.testclusters.TestClustersPlugin
import org.elasticsearch.gradle.testclusters.TestClustersRegistry
import org.elasticsearch.gradle.util.GradleUtils

apply plugin: 'elasticsearch.internal-test-artifact'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ restResources {
}

// randomise between sniff and proxy modes
boolean proxyMode = buildParams.random.nextBoolean()
var proxyModeProvider = buildParams.random.map{r -> r.nextBoolean()}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix Groovy variable declaration

var is not valid in Groovy build scripts and causes unable to resolve class var. Declare the provider with def (or an explicit Provider<Boolean>).

-var proxyModeProvider = buildParams.random.map{r -> r.nextBoolean()}
+def proxyModeProvider = buildParams.random.map { r -> r.nextBoolean() }
🤖 Prompt for AI Agents
In
x-pack/qa/multi-cluster-search-security/legacy-with-basic-license/build.gradle
around line 22, the script uses the invalid Groovy keyword "var" causing "unable
to resolve class var"; replace the declaration with a Groovy-compatible one such
as "def proxyModeProvider = buildParams.random.map { r -> r.nextBoolean() }" or
an explicit typed provider like "Provider<Boolean> proxyModeProvider =
buildParams.random.map { r -> r.nextBoolean() }" so the variable is declared
correctly and the build script resolves.


def fulfillingCluster = testClusters.register('fulfilling-cluster') {
setting 'xpack.security.enabled', 'true'
Expand Down Expand Up @@ -57,7 +57,7 @@ def queryingCluster = testClusters.register('querying-cluster') {
user username: "test_user", password: "x-pack-test-password"
setting 'xpack.ml.enabled', 'false'
setting 'cluster.remote.my_remote_cluster.skip_unavailable', 'false'
if (proxyMode) {
if (proxyModeProvider.get()) {
setting 'cluster.remote.my_remote_cluster.mode', 'proxy'
setting 'cluster.remote.my_remote_cluster.proxy_address', {
"\"${fulfillingCluster.get().getAllTransportPortURI().get(0)}\""
Expand All @@ -79,7 +79,7 @@ tasks.register('querying-cluster', RestIntegTestTask) {
useCluster fulfillingCluster
useCluster queryingCluster
systemProperty 'tests.rest.suite', 'querying_cluster'
if (proxyMode) {
if (proxyModeProvider.get()) {
systemProperty 'tests.rest.blacklist', [
'querying_cluster/10_basic/Add persistent remote cluster based on the preset cluster',
'querying_cluster/20_info/Add persistent remote cluster based on the preset cluster and check remote info',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ restResources {
}

// randomise between sniff and proxy modes
boolean proxyMode = buildParams.random.nextBoolean()
var proxyModeProvider = buildParams.random.map{r -> r.nextBoolean()}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Replace var with a Groovy-compatible declaration

build.gradle uses the Groovy DSL, which does not recognize var as a type. This line will fail with unable to resolve class var. Declare the provider with def (or an explicit Provider<Boolean> type) instead.

-var proxyModeProvider = buildParams.random.map{r -> r.nextBoolean()}
+def proxyModeProvider = buildParams.random.map { r -> r.nextBoolean() }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var proxyModeProvider = buildParams.random.map{r -> r.nextBoolean()}
def proxyModeProvider = buildParams.random.map { r -> r.nextBoolean() }
🤖 Prompt for AI Agents
In x-pack/qa/multi-cluster-search-security/legacy-with-full-license/build.gradle
around line 22, the declaration uses the Java/Kotlin-style keyword "var" which
Groovy DSL doesn't recognize; replace "var" with a Groovy-compatible declaration
such as "def proxyModeProvider = buildParams.random.map { r -> r.nextBoolean()
}" or declare an explicit Provider<Boolean> type so the build resolves
correctly.


def fulfillingCluster = testClusters.register('fulfilling-cluster') {
setting 'xpack.security.enabled', 'true'
Expand All @@ -43,7 +43,7 @@ def queryingCluster = testClusters.register('querying-cluster') {
setting 'cluster.remote.connections_per_cluster', "1"
user username: "test_user", password: "x-pack-test-password"

if (proxyMode) {
if (proxyModeProvider.get()) {
setting 'cluster.remote.my_remote_cluster.mode', 'proxy'
setting 'cluster.remote.my_remote_cluster.proxy_address', {
"\"${fulfillingCluster.get().getAllTransportPortURI().get(0)}\""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ tasks.register("copyCerts", Sync) {
}

// randomise between sniff and proxy modes
boolean proxyMode = buildParams.random.nextBoolean()
var proxyModeProvider = buildParams.random.map{r -> r.nextBoolean()}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use def instead of var in Groovy build scripts

Groovy DSL scripts cannot declare variables with var; this triggers unable to resolve class var at configuration time. Switch to def (or an explicit Provider<Boolean> type).

-var proxyModeProvider = buildParams.random.map{r -> r.nextBoolean()}
+def proxyModeProvider = buildParams.random.map { r -> r.nextBoolean() }
🤖 Prompt for AI Agents
In
x-pack/qa/multi-cluster-search-security/legacy-with-restricted-trust/build.gradle
around line 32, the Groovy build script incorrectly uses the JavaScript/Java
'var' keyword which causes "unable to resolve class var" at configuration time;
replace 'var' with 'def' (or an explicit Provider<Boolean> type) so the
declaration becomes valid Groovy and the expression buildParams.random.map{ r ->
r.nextBoolean() } is assigned correctly.


def fulfillingCluster = testClusters.register('fulfilling-cluster') {
setting 'xpack.security.enabled', 'true'
Expand Down Expand Up @@ -66,7 +66,7 @@ def queryingCluster = testClusters.register('querying-cluster') {
setting 'cluster.remote.connections_per_cluster', "1"
user username: "test_user", password: "x-pack-test-password"

if (proxyMode) {
if (proxyModeProvider.get()) {
setting 'cluster.remote.my_remote_cluster.mode', 'proxy'
setting 'cluster.remote.my_remote_cluster.proxy_address', {
"\"${fulfillingCluster.get().getAllTransportPortURI().get(0)}\""
Expand Down