lucene/gradle/testing/randomization.gradle

254 lines
11 KiB
Groovy

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// Configure test randomization seeds and derived test properties.
//
import java.nio.file.*
import com.carrotsearch.randomizedtesting.SeedUtils
import com.carrotsearch.randomizedtesting.generators.RandomPicks
import org.apache.tools.ant.types.Commandline
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.7.2'
}
}
def resources = scriptResources(buildscript)
// Pick the "root" seed from which everything else is derived.
configure(rootProject) {
ext {
rootSeed = propertyOrDefault('tests.seed', String.format("%08X", new Random().nextLong()))
rootSeedUserProvided = (propertyOrDefault('tests.seed', null) != null)
rootSeedLong = SeedUtils.parseSeedChain(rootSeed)[0]
projectSeedLong = rootSeedLong ^ project.path.hashCode()
}
task randomizationInfo() {
doFirst {
logger.lifecycle("Running tests with randomization seed: tests.seed=${rootSeed}")
}
}
}
// Any test task will trigger display of randomization settings.
allprojects {
tasks.withType(Test) { task ->
task.dependsOn rootProject.randomizationInfo
}
}
// Configure test property defaults and their descriptions.
allprojects {
plugins.withType(JavaPlugin) {
ext {
testOptions += [
// seed, repetition and amplification.
[propName: 'tests.seed', value: { -> rootSeed }, description: "Sets the master randomization seed."],
[propName: 'tests.iters', value: null, description: "Duplicate (re-run) each test case N times."],
[propName: 'tests.multiplier', value: null, description: "Value multiplier for randomized tests."],
[propName: 'tests.maxfailures', value: null, description: "Skip tests after a given number of failures."],
[propName: 'tests.timeoutSuite', value: null, description: "Timeout (in millis) for an entire suite."],
[propName: 'tests.failfast', value: "false", description: "Stop the build early on failure.", buildOnly: true],
// asserts, debug output.
[propName: 'tests.asserts', value: "true", description: "Enables or disables assertions mode."],
[propName: 'tests.infostream', value: false, description: "Enables or disables infostream logs."],
[propName: 'tests.leaveTemporary', value: false, description: "Leave temporary directories after tests complete."],
[propName: 'tests.useSecurityManager', value: true, description: "Control security manager in tests.", buildOnly: true],
// component randomization
[propName: 'tests.codec', value: "random", description: "Sets the codec tests should run with."],
[propName: 'tests.directory', value: "random", description: "Sets the Directory implementation tests should run with."],
[propName: 'tests.postingsformat', value: "random", description: "Sets the postings format tests should run with."],
[propName: 'tests.docvaluesformat', value: "random", description: "Sets the doc values format tests should run with."],
[propName: 'tests.locale', value: "random", description: "Sets the default locale tests should run with."],
[propName: 'tests.timezone', value: "random", description: "Sets the default time zone tests should run with."],
// filtering
[propName: 'tests.filter', value: null, description: "Applies a test filter (see :helpTests)."],
[propName: 'tests.nightly', value: false, description: "Enables or disables @Nightly tests."],
[propName: 'tests.weekly', value: false, description: "Enables or disables @Weekly tests."],
[propName: 'tests.monster', value: false, description: "Enables or disables @Monster tests."],
[propName: 'tests.awaitsfix', value: null, description: "Enables or disables @AwaitsFix tests."],
[propName: 'tests.gui',
value: { ->
return rootProject.ext.isCIBuild
},
description: "Enables or disables @RequiresGUI tests."],
[propName: 'tests.file.encoding',
value: { ->
RandomPicks.randomFrom(new Random(projectSeedLong), ["US-ASCII", "ISO-8859-1", "UTF-8"])
},
description: "Sets the default file.encoding on test JVM.", buildOnly: true],
// Test data file used.
[propName: 'tests.linedocsfile', value: 'europarl.lines.txt.gz', description: "Test data file path."],
// miscellaneous; some of them very weird.
[propName: 'tests.LUCENE_VERSION', value: baseVersion, description: "Base Lucene version."],
[propName: 'tests.bwcdir', value: null, description: "Data for backward-compatibility indexes."],
// vectorization related
[propName: 'tests.vectorsize',
value: { ->
RandomPicks.randomFrom(new Random(projectSeedLong), ["128", "256", "512"])
},
description: "Sets preferred vector size in bits."],
[propName: 'tests.forceintegervectors', value: "true", description: "Forces use of integer vectors even when slow."],
]
}
}
}
// Resolve test option values after all evaluation is complete.
allprojects {
plugins.withType(JavaPlugin) {
afterEvaluate {
ext.testOptionsResolved = testOptions.findAll { opt ->
propertyOrDefault(opt.propName, opt.value) != null
}.collectEntries { opt ->
[(opt.propName): Objects.toString(resolvedTestOption(opt.propName))]
}
// Compute the "reproduce with" string.
ext.testOptionsForReproduceLine = testOptions.findAll { opt ->
if (opt["includeInReproLine"] == false) {
return false
}
def defValue = Objects.toString(opt.value, null)
def value = testOptionsResolved[opt.propName]
return defValue != value
}.collect { opt ->
Commandline.quoteArgument("-P" + opt.propName + "=" + testOptionsResolved[opt.propName])
}.join(" ")
// leaving temporary folder option has multiple aliases...
if ([
"tests.leaveTemporary",
"tests.leavetemporary",
"tests.leavetmpdir",
].find { prop ->
def v = Boolean.parseBoolean(propertyOrDefault(prop, "false"))
if (v) {
logger.lifecycle("Update your code to use the official 'tests.leaveTemporary' option (you used '${prop}').")
}
return v
}) {
testOptionsResolved['tests.leaveTemporary'] = "true"
}
// Append resolved test properties to the test task.
tasks.withType(Test) { task ->
// TODO: we could remove some options that are only relevant to the build environment
// and not the test JVM itself.
systemProperties testOptionsResolved
if (Boolean.parseBoolean(testOptionsResolved['tests.asserts'])) {
jvmArgs("-ea", "-esa")
} else {
enableAssertions = false
}
if (Boolean.parseBoolean(testOptionsResolved["tests.failfast"])) {
failFast true
}
// Enable security manager, if requested. We could move the selection of security manager and security policy
// to each project's build/ configuration but it seems compact enough to keep it here for now.
def securityArgumentProvider = new SecurityArgumentProvider(commonDir: project(":lucene").layout.projectDirectory)
if (Boolean.parseBoolean(testOptionsResolved["tests.useSecurityManager"])) {
if (project.path.endsWith(".tests")) {
// LUCENE-10301: for now, do not use the security manager for modular tests (test framework is not available).
} else if (project.path == ":lucene:replicator") {
systemProperty 'java.security.manager', "org.apache.lucene.tests.util.TestSecurityManager"
securityArgumentProvider.javaSecurityPolicy = layout.projectDirectory.file("${resources}/policies/replicator-tests.policy")
} else if (project.path.startsWith(":lucene")) {
systemProperty 'java.security.manager', "org.apache.lucene.tests.util.TestSecurityManager"
securityArgumentProvider.javaSecurityPolicy = layout.projectDirectory.file("${resources}/policies/tests.policy")
}
jvmArgumentProviders.add(securityArgumentProvider)
def gradleUserHome = project.gradle.getGradleUserHomeDir()
systemProperty 'gradle.lib.dir', Paths.get(project.class.location.toURI()).parent.toAbsolutePath().toString().replace('\\', '/')
systemProperty 'gradle.worker.jar', Paths.get("${gradleUserHome}/caches/${gradle.gradleVersion}/workerMain/gradle-worker.jar").toAbsolutePath().toString()
systemProperty 'gradle.user.home', gradleUserHome.toPath().toAbsolutePath().toString()
}
doFirst {
logger.debug("Will use test opts:\n" + testOptionsResolved.collect {k,v -> "${k}: ${v}"}.sort().join("\n"))
}
}
}
}
}
// Add a helper task to display resolved test property values with their defaults
// and descriptions.
allprojects {
plugins.withType(JavaPlugin) {
task testOpts() {
group = 'Help (developer guides and hints)'
description = "Display values of randomization settings for a given seed"
doFirst {
println "Test options for project ${project.path} and seed \"${rootSeed}\":"
testOptions.sort { a, b -> a.propName.compareTo(b.propName) }.each { opt ->
def defValue
def computedValue = false
if (opt.value instanceof Closure) {
defValue = Objects.toString(opt.value(), null)
computedValue = true
} else {
defValue = Objects.toString(opt.value, null)
}
def value = testOptionsResolved[opt.propName]
println String.format(Locale.ROOT,
"%s%-24s = %-8s # %s",
(defValue != value ? "! " : computedValue ? "C " : " "),
opt.propName,
value,
(computedValue ? "(!= default: computed) " : (defValue != value ? "(!= default: ${defValue}) " : "")) + opt.description)
}
}
}
}
}
class SecurityArgumentProvider implements CommandLineArgumentProvider {
@Internal
Directory commonDir
@InputFile
@Optional
@PathSensitive(PathSensitivity.RELATIVE)
RegularFile javaSecurityPolicy
@Override
Iterable<String> asArguments() {
def args = ["-Dcommon.dir=${commonDir.getAsFile()}"]
if (javaSecurityPolicy) {
args.add("-Djava.security.policy=${javaSecurityPolicy.getAsFile()}")
}
return args
}
}