You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by th...@apache.org on 2020/09/14 16:38:28 UTC

[lucene-solr] 10/39: LUCENE-9491: Consolidate test options and improve support for dynamic… (#1807)

This is an automated email from the ASF dual-hosted git repository.

thelabdude pushed a commit to branch reference_impl_gradle_updates
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit 72bf564e35270fb4ebdaf1cc1e2d14fae1e4e74c
Author: Dawid Weiss <dw...@apache.org>
AuthorDate: Mon Aug 31 12:20:30 2020 +0200

    LUCENE-9491: Consolidate test options and improve support for dynamic… (#1807)
    
    * LUCENE-9491: Consolidate test options and improve support for dynamically computed values.
---
 build.gradle                                       |  1 +
 gradle/defaults.gradle                             | 12 ++++
 .../hashmapAssertions.gradle}                      | 40 ++++-------
 gradle/testing/beasting.gradle                     | 12 +++-
 gradle/testing/defaults-tests.gradle               | 48 +++++++++++--
 gradle/testing/profiling.gradle                    | 50 ++++++++------
 gradle/testing/randomization.gradle                | 80 ++++++++++------------
 gradle/validation/config-file-sanity.gradle        |  2 +-
 8 files changed, 147 insertions(+), 98 deletions(-)

diff --git a/build.gradle b/build.gradle
index 9801e06..bbdc7bc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -175,3 +175,4 @@ apply from: file('gradle/documentation/render-javadoc.gradle')
 
 apply from: file('gradle/hacks/findbugs.gradle')
 apply from: file('gradle/hacks/gradle.gradle')
+apply from: file('gradle/hacks/hashmapAssertions.gradle')
diff --git a/gradle/defaults.gradle b/gradle/defaults.gradle
index bf64968..5f11803 100644
--- a/gradle/defaults.gradle
+++ b/gradle/defaults.gradle
@@ -40,10 +40,22 @@ allprojects {
         result = project.getProperty(propName)
       } else if (System.properties.containsKey(propName)) {
         result = System.properties.get(propName)
+      } else if (defValue instanceof Closure) {
+        result = defValue.call()
       } else {
         result = defValue
       }
       return result
     }
+
+    // System environment variable or default.
+    envOrDefault = { envName, defValue ->
+      return Objects.requireNonNullElse(System.getenv(envName), defValue);
+    }
+
+    // Either a project, system property, environment variable or default value.
+    propertyOrEnvOrDefault = { propName, envName, defValue ->
+      return propertyOrDefault(propName, envOrDefault(envName, defValue));
+    }
   }
 }
diff --git a/gradle/defaults.gradle b/gradle/hacks/hashmapAssertions.gradle
similarity index 50%
copy from gradle/defaults.gradle
copy to gradle/hacks/hashmapAssertions.gradle
index bf64968..095726c 100644
--- a/gradle/defaults.gradle
+++ b/gradle/hacks/hashmapAssertions.gradle
@@ -15,35 +15,19 @@
  * limitations under the License.
  */
 
-allprojects {
-  apply plugin: 'base'
-
-  group "org.apache"
-
-  // Repositories to fetch dependencies from.
-  repositories {
-    mavenCentral()
-    maven {
-      url "https://maven.restlet.com"
-    }
-  }
-
-  // Artifacts will have names after full gradle project path
-  // so :solr:core will have solr-core.jar, etc.
-  project.archivesBaseName = project.path.replaceAll("^:", "").replace(':', '-')
-
-  ext {
-    // Utility method to support passing overrides via -P or -D.
-    propertyOrDefault = { propName, defValue ->
-      def result
-      if (project.hasProperty(propName)) {
-        result = project.getProperty(propName)
-      } else if (System.properties.containsKey(propName)) {
-        result = System.properties.get(propName)
-      } else {
-        result = defValue
+// Disable assertions for HashMap due to: LUCENE-8991 / JDK-8205399
+def vmName = System.getProperty("java.vm.name")
+def spec = System.getProperty("java.specification.version")
+if (vmName =~ /(?i)(hotspot|openjdk|jrockit)/ &&
+    spec =~ /^(1\.8|9|10|11)$/ &&
+    !Boolean.parseBoolean(propertyOrDefault('tests.asserts.hashmap', 'false'))) {
+  logger.info("Enabling HashMap assertions.")
+  allprojects {
+    plugins.withType(JavaPlugin) {
+      tasks.withType(Test) { task ->
+        jvmArgs("-da:java.util.HashMap")
       }
-      return result
     }
   }
 }
+
diff --git a/gradle/testing/beasting.gradle b/gradle/testing/beasting.gradle
index 1604cdb..c4b5303 100644
--- a/gradle/testing/beasting.gradle
+++ b/gradle/testing/beasting.gradle
@@ -25,6 +25,16 @@
 
 def beastingMode = gradle.startParameter.taskNames.contains("beast");
 
+allprojects {
+  plugins.withType(JavaPlugin) {
+    ext {
+      testOptions += [
+          [propName: 'tests.dups', value: 0, description: "Reiterate runs of entire test suites ('beast' task)."]
+      ]
+    }
+  }
+}
+
 if (beastingMode) {
   if (rootProject.rootSeedUserProvided) {
     logger.warn("Root randomization seed is externally provided, all duplicated runs will use the same starting seed.")
@@ -37,7 +47,7 @@ if (beastingMode) {
         group "Verification"
       }
 
-      def dups = Integer.parseInt(propertyOrDefault("tests.dups", "0"))
+      def dups = Integer.parseInt(resolvedTestOption("tests.dups"))
       if (dups <= 0) {
         throw new GradleException("Specify -Ptests.dups=[count] for beast task.")
       }
diff --git a/gradle/testing/defaults-tests.gradle b/gradle/testing/defaults-tests.gradle
index c4316d5..57b0c14 100644
--- a/gradle/testing/defaults-tests.gradle
+++ b/gradle/testing/defaults-tests.gradle
@@ -24,15 +24,49 @@ def verboseModeHookInstalled = false
 
 allprojects {
   plugins.withType(JavaPlugin) {
-    def verboseMode = Boolean.parseBoolean(propertyOrDefault("tests.verbose", "false"))
-
     project.ext {
+      // This array will collect all test options, including default values and option description.
+      // The actual values of these properties (defaults, project properties) are resolved lazily after evaluation
+      // completes.
+      // [propName: 'tests.foo', value: "bar", description: "Sets foo in tests."],
+      testOptions = [
+          // asserts, debug output.
+          [propName: 'tests.verbose', value: false, description: "Enables verbose mode (emits full test outputs immediately)."],
+          [propName: 'tests.workDir',
+           value: { -> file("${buildDir}/tmp/tests-tmp") },
+           description: "Working directory for forked test JVMs",
+           includeInReproLine: false
+          ],
+          // JVM settings
+          [propName: 'tests.minheapsize', value: "256m", description: "Minimum heap size for test JVMs"],
+          [propName: 'tests.heapsize', value: "512m", description: "Heap size for test JVMs"],
+          // Test forks
+          [propName: 'tests.jvms',
+           value: { -> ((int) Math.max(1, Math.min(Runtime.runtime.availableProcessors() / 2.0, 4.0))) },
+           description: "Number of forked test JVMs"],
+          [propName: 'tests.haltonfailure', value: true, description: "Halt processing on test failure."],
+          [propName: 'tests.jvmargs',
+           value: { -> propertyOrEnvOrDefault("tests.jvmargs", "TEST_JVM_ARGS", "-XX:TieredStopAtLevel=1") },
+           description: "Arguments passed to each forked JVM."],
+      ]
+
+      // Resolves test option's value.
+      resolvedTestOption = { propName ->
+        def option = testOptions.find { entry -> entry.propName == propName }
+        if (option == null) {
+          throw new GradleException("No such test option: " + propName)
+        }
+        return propertyOrDefault(option.propName, option.value)
+      }
+
       testsCwd = file("${buildDir}/tmp/tests-cwd")
-      testsTmpDir = file(propertyOrDefault("tests.workDir", "${buildDir}/tmp/tests-tmp"))
+      testsTmpDir = file(resolvedTestOption("tests.workDir"))
       commonDir = project(":lucene").projectDir
       commonSolrDir = project(":solr").projectDir
     }
 
+    def verboseMode = resolvedTestOption("tests.verbose").toBoolean()
+
     // If we're running in verbose mode and:
     // 1) worker count > 1
     // 2) number of 'test' tasks in the build is > 1
@@ -58,10 +92,10 @@ allprojects {
       }
       binaryResultsDirectory = file(propertyOrDefault("binaryResultsDirectory", binaryResultsDirectory))
 
-      if (verboseMode) {
+      maxParallelForks = resolvedTestOption("tests.jvms") as Integer
+      if (verboseMode && maxParallelForks != 1) {
+        logger.lifecycle("tests.jvm forced to 1 in verbose mode.")
         maxParallelForks = 1
-      } else {
-        maxParallelForks = propertyOrDefault("tests.jvms", (int) Math.max(1, Math.min(Runtime.runtime.availableProcessors() / 2.0, 4.0))) as Integer
       }
 
       workingDir testsCwd
@@ -72,6 +106,8 @@ allprojects {
 
       jvmArgs Commandline.translateCommandline(propertyOrDefault("tests.jvmargs", "-XX:TieredStopAtLevel=1 -XX:+UseParallelGC -XX:-UseBiasedLocking -DconfigurationFile=log4j2.xml -Dorg.apache.xml.dtm.DTMManager=org.apache.xml.dtm.ref.DTMManagerDefault"));
 
+      ignoreFailures = resolvedTestOption("tests.haltonfailure").toBoolean() == false
+
       systemProperty 'java.util.logging.config.file', rootProject.file("gradle/testing/defaults-tests/logging.properties")
       systemProperty 'java.awt.headless', 'true'
       systemProperty 'jdk.map.althashing.threshold', '0'
diff --git a/gradle/testing/profiling.gradle b/gradle/testing/profiling.gradle
index 01938f9..6655df7 100644
--- a/gradle/testing/profiling.gradle
+++ b/gradle/testing/profiling.gradle
@@ -19,29 +19,39 @@ import org.apache.lucene.gradle.ProfileResults;
 
 def recordings = files()
 
-if (Boolean.parseBoolean(propertyOrDefault("tests.profile", "false"))) {
-  allprojects {
-    tasks.withType(Test) {
-      jvmArgs("-XX:StartFlightRecording=dumponexit=true,maxsize=250M,settings=" + rootProject.file("gradle/testing/profiling.jfc"),
+allprojects {
+  plugins.withType(JavaPlugin) {
+    ext {
+      testOptions += [
+          [propName: 'tests.profile', value: false, description: "Enable java flight recorder profiling."]
+      ]
+    }
+
+    if (resolvedTestOption("tests.profile").toBoolean()) {
+      allprojects {
+        tasks.withType(Test) {
+          jvmArgs("-XX:StartFlightRecording=dumponexit=true,maxsize=250M,settings=" + rootProject.file("gradle/testing/profiling.jfc"),
               "-XX:+UnlockDiagnosticVMOptions",
               "-XX:+DebugNonSafepoints")
-      // delete any previous profile results
-      doFirst {
-        project.delete fileTree(dir: workingDir, include: '*.jfr')
-      }
-      doLast {
-        recordings = recordings.plus fileTree(dir: workingDir, include: '*.jfr')
+          // delete any previous profile results
+          doFirst {
+            project.delete fileTree(dir: workingDir, include: '*.jfr')
+          }
+          doLast {
+            recordings = recordings.plus fileTree(dir: workingDir, include: '*.jfr')
+          }
+        }
       }
-    }
-  }
 
-  gradle.buildFinished {
-    if (!recordings.isEmpty()) {
-      ProfileResults.printReport(recordings.getFiles().collect { it.toString() },
-                                 propertyOrDefault(ProfileResults.MODE_KEY, ProfileResults.MODE_DEFAULT) as String,
-                                 Integer.parseInt(propertyOrDefault(ProfileResults.STACKSIZE_KEY, ProfileResults.STACKSIZE_DEFAULT)),
-                                 Integer.parseInt(propertyOrDefault(ProfileResults.COUNT_KEY, ProfileResults.COUNT_DEFAULT)),
-                                 Boolean.parseBoolean(propertyOrDefault(ProfileResults.LINENUMBERS_KEY, ProfileResults.LINENUMBERS_DEFAULT)))
+      gradle.buildFinished {
+        if (!recordings.isEmpty()) {
+          ProfileResults.printReport(recordings.getFiles().collect { it.toString() },
+              propertyOrDefault(ProfileResults.MODE_KEY, ProfileResults.MODE_DEFAULT) as String,
+              Integer.parseInt(propertyOrDefault(ProfileResults.STACKSIZE_KEY, ProfileResults.STACKSIZE_DEFAULT)),
+              Integer.parseInt(propertyOrDefault(ProfileResults.COUNT_KEY, ProfileResults.COUNT_DEFAULT)),
+              Boolean.parseBoolean(propertyOrDefault(ProfileResults.LINENUMBERS_KEY, ProfileResults.LINENUMBERS_DEFAULT)))
+        }
+      }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/gradle/testing/randomization.gradle b/gradle/testing/randomization.gradle
index eb539c7..570ff6a 100644
--- a/gradle/testing/randomization.gradle
+++ b/gradle/testing/randomization.gradle
@@ -60,9 +60,9 @@ allprojects {
 allprojects {
   plugins.withType(JavaPlugin) {
     ext {
-      testOptions = [
+      testOptions += [
           // seed, repetition and amplification.
-          [propName: 'tests.seed', value: "random", description: "Sets the master randomization seed."],
+          [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: 1, description: "Value multiplier for randomized tests."],
           [propName: 'tests.maxfailures', value: null, description: "Skip tests after a given number of failures."],
@@ -70,9 +70,8 @@ allprojects {
           [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.verbose', value: false, description: "Emit verbose debug information from tests."],
           [propName: 'tests.infostream', value: false, description: "Enables or disables infostream logs."],
-          [propName: 'tests.leaveTemporary', value: null, description: "Leave temporary directories after tests complete."],
+          [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."],
@@ -88,7 +87,12 @@ allprojects {
           [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.file.encoding', value: "random", description: "Sets the default file.encoding on test JVM.", buildOnly: true],
+          [propName: 'tests.badapples', value: null, description: "Enables or disables @BadApple 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
           [propName: 'tests.linedocsfile', value: 'europarl.lines.txt.gz', description: "Test data file path."],
           // miscellaneous; some of them very weird.
@@ -105,7 +109,11 @@ configure(allprojects.findAll {project -> project.path.startsWith(":solr") }) {
     ext {
       testOptions += [
           [propName: 'tests.luceneMatchVersion', value: baseVersion, description: "Base Lucene version."],
-          [propName: 'common-solr.dir', value: file("${commonDir}/../solr").path, description: "Solr base dir."],
+          [propName: 'common-solr.dir',
+           value: { -> file("${commonDir}/../solr").path },
+           description: "Solr base dir.",
+           includeInReproLine: false
+          ],
           [propName: 'solr.directoryFactory', value: "org.apache.solr.core.MockDirectoryFactory", description: "Solr directory factory."],
           [propName: 'tests.src.home', value: null, description: "See SOLR-14023."],
           [propName: 'solr.tests.use.numeric.points', value: null, description: "Point implementation to use (true=numerics, false=trie)."],
@@ -121,23 +129,15 @@ allprojects {
       ext.testOptionsResolved = testOptions.findAll { opt ->
         propertyOrDefault(opt.propName, opt.value) != null
       }.collectEntries { opt ->
-        [(opt.propName): Objects.toString(propertyOrDefault(opt.propName, opt.value))]
-      }
-
-      // These are not official options or dynamically seed-derived options.
-      if (testOptionsResolved['tests.file.encoding'] == 'random') {
-        testOptionsResolved['tests.file.encoding'] = RandomPicks.randomFrom(
-            new Random(projectSeedLong), [
-                "US-ASCII", "ISO-8859-1", "UTF-8"
-            ])
-      }
-
-      if (testOptionsResolved['tests.seed'] == 'random') {
-        testOptionsResolved['tests.seed'] = rootSeed
+        [(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
@@ -152,14 +152,19 @@ allprojects {
          "tests.leavetmpdir",
          "solr.test.leavetmpdir",
       ].find { prop ->
-        Boolean.parseBoolean(propertyOrDefault(prop, "false"))
+        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 opts with "buildOnly: true" (?)
+        // 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'])) {
@@ -215,33 +220,24 @@ allprojects {
         println "Test options for project ${project.path} and seed \"${rootSeed}\":"
 
         testOptions.sort { a, b -> a.propName.compareTo(b.propName) }.each { opt ->
-          def defValue = Objects.toString(opt.value, null)
+          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%-23s = %-8s # %s",
-              (defValue != value ? "! " : "  "),
+              "%s%-24s = %-8s # %s",
+              (defValue != value ? "! " : computedValue ? "C " : "  "),
               opt.propName,
               value,
-              (defValue != value ? "(!= default: ${defValue}) " : "") + opt.description)
+              (computedValue ? "(!= default: computed) " : (defValue != value ? "(!= default: ${defValue}) " : "")) + opt.description)
         }
       }
     }
   }
 }
-
-// Disable assertions for HashMap due to: LUCENE-8991 / JDK-8205399
-def vmName = System.getProperty("java.vm.name")
-def spec = System.getProperty("java.specification.version")
-if (vmName =~ /(?i)(hotspot|openjdk|jrockit)/ &&
-    spec =~ /^(1\.8|9|10|11)$/ &&
-    !Boolean.parseBoolean(propertyOrDefault('tests.asserts.hashmap', 'false'))) {
-  logger.debug("Enabling HashMap assertions.")
-  allprojects {
-    plugins.withType(JavaPlugin) {
-      tasks.withType(Test) { task ->  
-        jvmArgs("-da:java.util.HashMap")
-      }
-    }
-  }
-}
-
diff --git a/gradle/validation/config-file-sanity.gradle b/gradle/validation/config-file-sanity.gradle
index e7ae048..30f6391 100644
--- a/gradle/validation/config-file-sanity.gradle
+++ b/gradle/validation/config-file-sanity.gradle
@@ -20,7 +20,7 @@
 configure(project(":solr")) {
   task validateConfigFileSanity() {
     doFirst {
-      def matchVersion = project(":solr:core").testOptionsResolved['tests.luceneMatchVersion']
+      def matchVersion = project(":solr:core").resolvedTestOption('tests.luceneMatchVersion')
       if (!matchVersion) {
         throw new GradleException("tests.luceneMatchVersion not defined?")
       }