You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ho...@apache.org on 2021/07/09 00:25:31 UTC

[solr] branch main updated: SOLR-14857: Add optional gradle property to run docker tests in parallel

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

hossman pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 5bb9ecd  SOLR-14857: Add optional gradle property to run docker tests in parallel
5bb9ecd is described below

commit 5bb9ecdb87e21dc912c52945fe027ccc63b47e0a
Author: Chris Hostetter <ho...@apache.org>
AuthorDate: Thu Jul 8 17:25:16 2021 -0700

    SOLR-14857: Add optional gradle property to run docker tests in parallel
---
 solr/CHANGES.txt            |   2 +
 solr/docker/build.gradle    | 163 ++++++++++++++++++++++++++++----------------
 solr/docker/gradle-help.txt |   8 +++
 3 files changed, 114 insertions(+), 59 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 2dfcaac..6fcd5da 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -329,6 +329,8 @@ Other Changes
 
 * SOLR-15517: Remove unnecessary no-op implementation of SolrCoreAware in ExpandComponent and TermVectorComponent. (Christine Poerschke)
 
+* SOLR-14857: Add optional gradle property to run docker tests in parallel (hossman, Houston Putman)
+
 Bug Fixes
 ---------------------
 * SOLR-14546: Fix for a relatively hard to hit issue in OverseerTaskProcessor that could lead to out of order execution
diff --git a/solr/docker/build.gradle b/solr/docker/build.gradle
index c9320e0..6719f58 100644
--- a/solr/docker/build.gradle
+++ b/solr/docker/build.gradle
@@ -32,8 +32,6 @@ def githubUrlOrMirror = propertyOrEnvOrDefault("solr.docker.githubUrl", "SOLR_DO
 
 def releaseGpgFingerprint = propertyOrDefault('signing.gnupg.keyName','');
 
-def testCasesDir = "tests/cases"
-
 // Build directory locations
 def imageIdFile = "$buildDir/image-id"
 
@@ -167,54 +165,16 @@ task dockerTag(dependsOn: tasks.dockerBuild) {
   }
 }
 
-
-      
-// Re-usable closure to run tests...
-def testDockerImage = { solrImageId, outputDir, testCasesInclude, testCasesExclude ->
-  // Print information on the image before it is tested
-  logger.lifecycle("Testing Solr Image:")
-  logger.lifecycle("\tID: $solrImageId\n")
-  
-  // Run the tests
-  def sourceDir = file(testCasesDir)
-  sourceDir.eachFile  { file ->
-    def testName = file.getName()
-    def testCaseBuildDir = "${outputDir}/${testName}"
-    
-    // If specific tests are specified, only run those. Otherwise run all that are not ignored.
-    def runTest = !testCasesInclude.isEmpty() ? testCasesInclude.contains(testName) : !testCasesExclude.contains(testName)
-    if (runTest) {
-      exec {
-        environment "TEST_DIR", file
-        environment "BUILD_DIR", testCaseBuildDir
-        commandLine "bash", "$file/test.sh", solrImageId
-      }
-    }
-  }
-}
-
-task testDocker(dependsOn: tasks.dockerBuild) {
+task testDocker(type: TestDockerImageTask, dependsOn: tasks.dockerBuild) {
   group = 'Docker'
   description = 'Test Solr docker image built from Dockerfile.local'
 
-  def iidFile = tasks.dockerBuild.outputs.files.singleFile
+  idFile = tasks.dockerBuild.outputs.files.singleFile
+  outputDir = file("$buildDir/test-results")
 
-  // Ensure that the docker image is re-tested if the image ID changes or the test files change
-  inputs.file(iidFile)
-  inputs.dir(testCasesDir)
-  inputs.properties([
-    // include/exclude options are designed for people who know their customizations will break some tests
-    includeTests: new HashSet(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.include", "SOLR_DOCKER_TESTS_INCLUDE", ",").split(","))),
-    excludeTests: new HashSet(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.exclude", "SOLR_DOCKER_TESTS_EXCLUDE", ",").split(",")))
-  ])
-  
-  def outputDir = "$buildDir/test-results"
-  outputs.dir(outputDir)
-
-  doLast {
-    def solrImageId = iidFile.text
-    testDockerImage(solrImageId, outputDir, inputs.properties.includeTests, inputs.properties.excludeTests)
-  }
+  // include/exclude options are designed for people who know their customizations will break some tests
+  testCasesInclude.value(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.include", "SOLR_DOCKER_TESTS_INCLUDE", ",").split(",")))
+  testCasesExclude.value(Arrays.asList(propertyOrEnvOrDefault("solr.docker.tests.exclude", "SOLR_DOCKER_TESTS_EXCLUDE", ",").split(",")))
 }
 
 task dockerPush(dependsOn: tasks.dockerTag) {
@@ -462,25 +422,110 @@ task testBuildDockerfileOfficial(type: Copy) {
   }
 }
 
-task testDockerfileOfficial(dependsOn: configurations.dockerOfficialSmokeCheckImage) {
+task testDockerfileOfficial(type: TestDockerImageTask, dependsOn: configurations.dockerOfficialSmokeCheckImage) {
   description = 'Smoke Test Solr docker image built from Dockerfile.official'
   
-  def iidFile = file(imageIdFileOfficial)
+  idFile = file(imageIdFileOfficial)
+  outputDir = file("$smokeTestOfficial/test-results")
   
+  // This test does not respect the include/exclude properties that `testDocker` does.
+  // All docker tests will be run, no matter the properties specified.
+  testCasesInclude.empty()
+  testCasesExclude.empty()
+}
+
+// Re-usable class for running tests...
+public abstract class TestDockerImageTask extends DefaultTask {
+
   // Ensure that the docker image is re-tested if the image ID changes or the test files change
-  inputs.file(iidFile)
-  inputs.dir(testCasesDir)
+  @InputFile abstract public RegularFileProperty getIdFile()
+  @InputDirectory final public File sourceDir = project.file("tests/cases")
 
-  // This test does not respect the inputs that `testDocker` does.
-  // All docker tests will be run, no matter the inputs given.
+  @Input final public SetProperty<String> testCasesInclude = project.objects.setProperty(String)
+  @Input final public SetProperty<String> testCasesExclude = project.objects.setProperty(String)
   
-  def outputDir = "$smokeTestOfficial/test-results"
-  outputs.dir(outputDir)
+  @OutputDirectory abstract public DirectoryProperty getOutputDir()
 
-  doLast {
-    def solrImageId = iidFile.text
-    // for smoke testing Dockerfile.official, we always run all tests.
-    // (if there is a test we don't expect to pass, we should delete it)
-    testDockerImage(solrImageId, outputDir, [] as Set, [] as Set)
+  @Inject abstract public WorkerExecutor getWorkerExecutor();
+
+  public static interface SingleTestParameters extends WorkParameters {
+    // NOTE: we explicitly don't use DirectoryProperty here because the way WorkerExecutor serializes the params
+    // causes weird "wrapped" objects to come back that don't work when you try to call `.getAsFile()` or `.getAsFile().getPath()`
+    Property<String> getTestName();
+    Property<String> getTestDir();
+    Property<String> getWorkDir();
+    Property<String> getImageId();
+  }
+  public abstract static class SingleTestAction implements WorkAction<SingleTestParameters> {
+    @Inject
+    abstract public ExecOperations getExec();
+
+    @Override
+    public void execute() {
+      def testCaseName = getParameters().getTestName().get()
+      def testCaseDir = getParameters().getTestDir().get()
+      def testCaseWorkDir = getParameters().getWorkDir().get()
+      def testCaseBuildDir = "${testCaseWorkDir}/build_dir"
+      System.out.println("Starting Solr Docker test: ${testCaseName}")
+
+      def res = getExec().exec {
+        // we'll handle it ourselves so we can report the details of which test failed
+        ignoreExitValue true
+
+        environment "DEBUG", "true"
+        environment "TEST_DIR", testCaseDir
+        environment "BUILD_DIR", testCaseBuildDir
+
+        standardOutput new FileOutputStream("${testCaseWorkDir}/test.std.out")
+        errorOutput new FileOutputStream("${testCaseWorkDir}/test.err.out")
+
+        commandLine "bash", "${testCaseDir}/test.sh", getParameters().getImageId().get()
+      }
+      def ev = res.getExitValue()
+      if (0 != ev) {
+        throw new GradleException("Docker test failure=${ev}: Test: ${testCaseName} Output: ${testCaseWorkDir}");
+      } else {
+        System.out.println("Completed Solr Docker test: ${testCaseName}")
+      }
+    }
+  }
+
+  @TaskAction
+  public void runTests() {
+    // no easy way to control the amount of parallelization of the work queue (independent of `org.gradle.workers.max`)
+    // so the best we can do (simply) is a boolean setting that controls if we `await()` after each `submit()`
+    def workQueue = getWorkerExecutor().noIsolation();
+    def runConcurrentTests = project.propertyOrEnvOrDefault("solr.docker.tests.concurrent", "SOLR_DOCKER_TESTS_CONCURRENT", "false").toBoolean()
+
+    def imageId = idFile.getAsFile().get().text
+    // Print information on the image before it is tested
+    logger.lifecycle("Testing Solr Image ID: $imageId ${ -> runConcurrentTests ? '(concurrently)' : '(sequentially)'}")
+
+    def includes = testCasesInclude.get()
+    def excludes = testCasesExclude.get()
+
+    // "Run" each of the test cases
+    sourceDir.eachFile  { file ->
+      def testName = file.getName()
+      def outDir = outputDir.get()
+      def testCaseWorkDir = outDir.dir(testName)
+
+      // If specific tests are specified, only run those. Otherwise run all that are not ignored.
+      def runTest = !includes.isEmpty() ? includes.contains(testName) : !excludes.contains(testName)
+      if (runTest) {
+        project.mkdir testCaseWorkDir
+
+        def paramSetup = { params ->
+          params.getTestName().set(testName)
+          params.getTestDir().set(file.getPath())
+          params.getWorkDir().set(testCaseWorkDir.getAsFile().getPath())
+          params.getImageId().set(imageId)
+        }
+        workQueue.submit(SingleTestAction.class, paramSetup);
+        if (! runConcurrentTests) {
+          workQueue.await();
+        }
+      }
+    }
   }
 }
diff --git a/solr/docker/gradle-help.txt b/solr/docker/gradle-help.txt
index 8593a69..868a9e6 100644
--- a/solr/docker/gradle-help.txt
+++ b/solr/docker/gradle-help.txt
@@ -79,6 +79,14 @@ Exclude specific tests:
    EnvVar: SOLR_DOCKER_TESTS_EXCLUDE
    Gradle Property: -Psolr.docker.tests.exclude
 
+The docker tests can also be run concurrently, if explicitly specified.
+The parallelization is set by the number of gradle workers you have defined, this cannot be specified separately.
+It is recommended to use at most 3 gradle workers when running the Docker tests concurrently.
+
+Run tests in parallel:
+   EnvVar: SOLR_DOCKER_TESTS_CONCURRENT=true
+   Gradle Property: -Psolr.docker.tests.concurrent=true
+
 -------
 The Official Solr Image
 -------