In Gradle, how to set unique system property befor

2019-06-06 06:53发布

问题:

In the gradle script below, maxParallelForks and forkEvery will start a new process with a new JVM per Junit test class. Is there a way to give each test process/JVM/Junit class its own unique system property?

I tried an experiment where I passed a UUID value as a system property in the beforeSuite method.

apply plugin:'java'
test {
    ignoreFailures = true
    maxParallelForks = 8
    forkEvery = 1
    systemProperty 'some.prop', 'value'

   beforeSuite { descriptor ->
    logger.lifecycle("Before suite: " + descriptor)
    def theuuid = UUID.randomUUID().toString()
    println "beforeSuite uuid =" + theuuid
    systemProperty 'some.uuid', theuuid
   }
   beforeTest { descriptor ->
      logger.lifecycle("Running test: " + descriptor)
   }
}
repositories {
    mavenCentral()
}
dependencies {
    testCompile 'junit:junit:4.12'
}

The JUnit test classes, LoginTest1 and LoginTest2 print out the system property in a test method.

public class LoginTest1 {
    private String userName="Gradle";
@Test
    public void testUserName() throws Exception {
        System.out.println("LoginTest1: Testing Login ");

        System.out.println("LoginTest1: Testing Login some.prop -> " 
           + System.getProperty("some.prop"));
        System.out.println("LoginTest1: Testing Login some.uuid -> " 
           + System.getProperty("some.uuid"));

    assertTrue(userName.equals("Gradle"));
    }
}

public class LoginTest2 {
    private String userName="Gradle";

@Test
    public void testUserName() throws Exception {
        System.out.println("LoginTest2: Testing Login ");

        System.out.println("LoginTest2: Testing Login some.prop -> " 
           + System.getProperty("some.prop"));
        System.out.println("LoginTest2: Testing Login some.uuid -> " 
           + System.getProperty("some.uuid"));

    assertTrue(userName.equals("Gradle"));
        }
}

As you can see from the gradle output below, the beforeSuite method is called before running individual JUnit test methods. Both test classes get the same system property of 6a006689-474f-47d5-9649-a88c92b45095

 $ gradle clean test

> Task :test 
Before suite: Gradle Test Run :test
beforeSuite uuid =6a006689-474f-47d5-9649-a88c92b45095
Before suite: Gradle Test Executor 1
beforeSuite uuid =39edb608-3027-4ad8-bd15-f52f50175582
Before suite: Test class ch6.login.LoginTest1
beforeSuite uuid =c0ce55e0-924c-4319-becb-bc27229c9cf3
Before suite: Gradle Test Executor 2
beforeSuite uuid =c15eaac0-7ea7-46d2-8235-8dc49b3f7a39
Running test: Test testUserNameNotNull(ch6.login.LoginTest1)
Before suite: Test class ch6.login.LoginTest2
beforeSuite uuid =73abbe24-5b78-4935-867a-445ac4ac20ef
Running test: Test testUserName(ch6.login.LoginTest1)
Running test: Test testUserName(ch6.login.LoginTest2)

JUnit test class output:

LoginTest1: Testing Login 
LoginTest1: Testing Login some.prop -> value
LoginTest1: Testing Login some.uuid -> 6a006689-474f-47d5-9649-a88c92b45095

LoginTest2: Testing Login 
LoginTest2: Testing Login some.prop -> value
LoginTest2: Testing Login some.uuid -> 6a006689-474f-47d5-9649-a88c92b45095

It looks like there's an initial call to beforeSuite (seen as ":test") and then once per Test Executor. The first uuid value ("6a006689") is set across all JVMs. Is there anything like a simple beforeClass method I could use?


In the debug log below,it looks like the Test Executors are given the jvm argument -Dsome.uuid=6a006689-474f-47d5-9649-a88c92b45095 and that is used soley to create new JVMs (as per the forkEvery config). This makes me think that what I want to do is impossible in Gradle.

20:23:42.466 [INFO] [org.gradle.process.internal.DefaultExecHandle] Starting process 'Gradle Test Executor 1'. Working directory: /a/b/ Command: /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Djava.security.manager=worker.org.gradle.process.internal.worker.child.BootstrapSecurityManager -Dorg.g
radle.native=false -Dsome.prop=value -Dsome.uuid=6a006689-474f-47d5-9649-a88c92b45095 -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -ea -cp /data/.gradle/caches/4.3.1/workerMain/gradle-worker.jar worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 1'

20:23:42.466 [INFO] [org.gradle.process.internal.DefaultExecHandle] Starting process 'Gradle Test Executor 2'. Working directory: /a/b/ Command: /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Djava.security.manager=worker.org.gradle.process.internal.worker.child.BootstrapSecurityManager -Dorg.g
radle.native=false -Dsome.prop=value -Dsome.uuid=6a006689-474f-47d5-9649-a88c92b45095 -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -ea -cp /data/.gradle/caches/4.3.1/workerMain/gradle-worker.jar worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 2'

Update 8/2018

The following solution does not work in Gradle 4.9 and greater

Instead, there is a org.gradle.test.worker system property which you can use to distinguish between workers, calculate display names, temporary directories, etc. This test.worker is simply an increasing long value. The Gradle team has no plans on making Test Workers configurable. A pity because I had to do some major refactoring in my test code in order to upgrade Gradle. The "@BeforeAllCallback" in JUnit 5 might be useful to you.

Trying to solve by intercepting Test.copyTo(JavaForkOptions target)

This code demonstrates lance-java 's suggestion and succeeds in answering my question. I have the following build.gradle script which uses MyTestTask to extend Test and add a unique value called my_uuid. Note that the build script must have forkEvery = 1 to ensure copyTo is called once per JUnit test:

class MyTestTask extends Test {

 public Test copyTo(JavaForkOptions target) {
        super.copyTo(target);
      String uuid = UUID.randomUUID().toString();
        System.out.println("MyTestTask copyTo:\n my_uuid=" +  uuid);
        System.out.println("\t before getJvmArgs:" +  target.getJvmArgs());
        System.out.println("\t before getSystemProperties:" +  target.getSystemProperties());

        target.systemProperty("my_uuid", uuid);

        System.out.println("\t after getJvmArgs:" +  target.getJvmArgs());
        System.out.println("\t after getSystemProperties:" +  target.getSystemProperties());

        return this;
    }

}
///

subprojects {
    apply plugin: 'java'

    repositories {
        mavenCentral()
    }

    dependencies {
        testCompile 'junit:junit:4.12'
    }


    test {
        enabled false // This doesn't seem to matter.

    }

    task my_test(type: MyTestTask) {

        forkEvery 1 // need this so copyTo called each JUnit test

        testLogging.showStandardStreams = true

        doFirst {
            def uuid = UUID.randomUUID().toString()
            println "TEST:Task name is $name. In doFirst. uuid is " + uuid

            jvmArgs '-Dcommon_uuid='+ uuid

        }
    }
}

The Junit tests that are executed are trivial and simply print out System.getProperty("my_uuid") and System.getProperty("common_uuid").

> Task :CustomCopyToSubProject:my_test 
TEST:Task name is my_test. In doFirst. uuid is eed1ee92-7805-4b8e-a74c-96218d1be6e1
MyTestTask copyTo:
MyTestTask copyTo:
 my_uuid=a7996c20-1085-4397-9e32-fd84467dcb45
 my_uuid=69f6f347-71d0-4e5d-9fad-66bf4468fab3
         before getJvmArgs:[]
         before getJvmArgs:[]
         before getSystemProperties:[common_uuid:eed1ee92-7805-4b8e-a74c-96218d1be6e1]
         before getSystemProperties:[common_uuid:eed1ee92-7805-4b8e-a74c-96218d1be6e1]
         after getJvmArgs:[]
         after getJvmArgs:[]
         after getSystemProperties:[common_uuid:eed1ee92-7805-4b8e-a74c-96218d1be6e1, my_uuid:69f6f347-71d0-4e5d-9fad-66bf4468fab3]
         after getSystemProperties:[common_uuid:eed1ee92-7805-4b8e-a74c-96218d1be6e1, my_uuid:a7996c20-1085-4397-9e32-fd84467dcb45]

ch3.LoginTest2 > testUserName2 STANDARD_OUT
    LoginTest2.java: Testing Login !  >>---> my_uuid=a7996c20-1085-4397-9e32-fd84467dcb45 common_uuid=eed1ee92-7805-4b8e-a74c-96218d1be6e1

ch3.LoginTest1 > testUserName1 STANDARD_OUT
    LoginTest1.java: Testing Login !  >>---> my_uuid=69f6f347-71d0-4e5d-9fad-66bf4468fab3 common_uuid=eed1ee92-7805-4b8e-a74c-96218d1be6e1

The task my_test sets common_uuid in doFirst. The MyTestTask.copyTo method sets its own my_uuid. You can see from the output and MyTestTask is called twice, once per JUnit test task (LoginTest1 and LoginTest2). The two JUnit tests get their own unique system property in my_uuid.

回答1:

I think you want to intercept Test.copyTo(JavaForkOptions target), unfortunately the only way I can see to do this is to extend Test

eg

public class Test2 extends org.gradle.api.tasks.testing.Test {
    public Test copyTo(JavaForkOptions target) {
        super.copyTo(target);
        target.systemProperty("uuid", UUID.randomUUID().toString());
        return this;
    }
}

build.gradle

apply plugin: 'java'
test {
    enabled = false
}
task test2(type: Test2) {
    // guessing you'll need to configure a bit of stuff here
}
check.dependsOn test2


回答2:

Since a new JVM is forked for every test you could just use a static field that is initialized with a UUID.

public class Helper {
    public static final String uuid = UUID.randomUUID().toString();
}

public class LoginTest2 {
    private String userName="Gradle";

    @Test
    public void testUserName() throws Exception {
        System.out.println("LoginTest2: Testing Login ");

        System.out.println("LoginTest2: Testing Login some.uuid -> " 
           + Helper.uuid;

        assertTrue(userName.equals("Gradle"));
    }
}


标签: gradle junit