run gradle test task twice for 2 java versions - java

I want to run gradle tests as part of my build but twice, once using java 8 and then using java 11.
i have tried following,but get issues when I run test using gradle command
test {
dependsOn ['testsOn8','testsOn11']
}
task('testsOn8', type: Test) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
}
}
task('testsOn11', type: Test) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
}

Related

Replacing a string within a .class file on build with Gradle Kotlin

I've got a Java application with the following variable:
static final String MAGIC_VERSION = "SET_BY_MAGIC";
Using gradle, I want to replace this on runtime with my gradle project version, however I am having issues replacing the string.
This is my gradle file:
tasks {
processResources {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
from(sourceSets["main"].java) {
filter {
it.replace("SET_BY_MAGIC", version as String)
}
}
}
}
What am I doing wrong?

How can I use Gradle to configure different environments using a shared source set(main)?

I am trying to use Gradle Source Sets to configure different files for different environments, which is inspired by Gradles integration testing setup documentation.
My goal is to have both fake and prod source sets use interfaces defined in main and provide their own implementations. This is similar if not exactly what you can accomplish with the Android Gradle Plugin's Product Flavors.
For example, here is my build.gradle.kts file:
...
sourceSets {
create("prod") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
java.srcDirs("src/main/kotlin", "src/prod/kotlin")
}
create("fake") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
java.srcDirs("src/main/kotlin", "src/fake/kotlin")
}
}
val fakeImplementation by configurations.getting {
extendsFrom(configurations.implementation.get())
}
val prodImplementation by configurations.getting {
extendsFrom(configurations.implementation.get())
}
configurations["fakeRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())
configurations["prodRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())
val prodTest = task<Test>("prodTest") {
description = "Runs prod tests."
group = "verification"
testClassesDirs = sourceSets["prod"].output.classesDirs
classpath = sourceSets["prod"].runtimeClasspath
shouldRunAfter("test")
}
val fakeTest = task<Test>("fakeTest") {
description = "Runs fake tests."
group = "verification"
testClassesDirs = sourceSets["fake"].output.classesDirs
classpath = sourceSets["fake"].runtimeClasspath
shouldRunAfter("test")
}
tasks.check { dependsOn(prodTest) }
tasks.check { dependsOn(fakeTest) }
...
compileFakeKotlin doesn't recognize files in the fake source set, particularly on the kaptKotlin task which is defined like so in my dependencies block:
dependencies {
// Dagger
implementation("com.google.dagger:dagger:2.41")
kapt("com.google.dagger:dagger-compiler:2.41")
"kaptProd"("com.google.dagger:dagger-compiler:2.41")
"kaptFake"("com.google.dagger:dagger-compiler:2.41")
}
And here is the error:
> Task :server:compileKotlinCheckIncrementalCompilationAnvil UP-TO-DATE
> Task :server:kaptGenerateStubsKotlin UP-TO-DATE
> Task :server:processResources UP-TO-DATE
> Task :server:compileFakeKotlinCheckIncrementalCompilationAnvil UP-TO-DATE
> Task :server:kaptKotlin FAILED
...[Dagger/MissingBinding] my.package.name.RecipeController cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent { ...
When I put the binding class(FakeRecipeController.kt) in the main source set, it works perfectly. But when it's in src/fake/kotlin, I get this error which leads me to believe the code in that source set isn't being used and I'm unsure how to proceed from here. Thank you.

how to set the environment variables in junit 5 for test case

i am working on Junit5 . My java code uses the System.getenv("demoVar") to access environment variable . so how do i set up this environment variable in the jUnit5 test class , so that my code can access the value of this environment variable during the test.
From this other SO answer https://stackoverflow.com/a/59635733/2185719:
There is JUnit Pioneer, a "JUnit 5 extension pack".
jUnit Pioneer offers an annotation that sets environment variables for a test. For example:
#Test
#SetEnvironmentVariable(key = "PATH", value = "")
void testPath_isEmpty() {
assertThat(System.getenv("PATH")).isEmpty();
}
You can't within the actual java process because these environmental values using getenv are immutable.
One way would be to start another vm or another process where you could introduce your new environment value.
Another way would be to switch to System.getProperty, but be sure you understand the differences.
https://www.baeldung.com/java-system-get-property-vs-system-getenv
Here is a little testcode:
public class EnvironmentVarsTest {
private static int counter = 0;
#BeforeEach
public void setUp() {
counter = counter + 1;
System.setProperty("simple_test_env_property", String.valueOf(counter));
}
#Test
public void testFirst() {
printOutValues();
}
#Test
public void testSecond() {
printOutValues();
}
private void printOutValues() {
System.out.println("--------------------");
System.out.println("val=" + counter);
System.out.println("envval=" + System.getProperty("simple_test_env_property"));
}
}
This can be achieved with https://github.com/webcompere/system-stubs/tree/master/system-stubs-jupiter
#ExtendWith(SystemStubsExtension.class)
class TestClass {
#SystemStub
private EnvironmentVariables environmentVariables =
new EnvironmentVariables("demoVar", "val");
#Test
void test() {
// can use the environment
assertThat(System.getenv("demoVar")).isEqualTo("val");
// can also change the environment
environmentVariables.set("foo", "bar");
// environment variables restored to previous state when
// test ends
}
}
The common practice is to use System properties instead of environment variables.
In this case you will run your java/maven/gradle command or whatever you use to run your tests with option -D demoVar="{your_value}".
for maven goal:
maven clean install -DdemoVar="test"
for java jar:
java -jar xxx.jar -DdemoVar="test"
You will be able to get it from code with System.getProperty("demoVar").
If you really need to use environment variable, use OS functionality.
For linux:
demoVar="test" mvn clean install
For windows PowerShell:
$env:demoVar = 'test'; mvn clean install
Simple solution if you use Gradle, you can add following to your build.gradle
test {
environment "ENV_VAR_NAME", "ENV_VAR_VALUE"
}
Link to Gradle doc: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:environment

Is it possible to swap out the JavaExecAction Gradle is using to run Java?

Java fails to launch when the classpath is too long. The length limit is particularly short on Windows.
Gradle seem uninterested in fixing the issue on their side (even though it's sort of their responsibility since they're the ones launching Java), so we ended up substituting the JavaExec task out with our own alternative.
The alternative works like this:
public class WorkingJavaExec extends JavaExec {
private static final String MATCH_CHUNKS_OF_70_CHARACTERS =
"(?<=\\G.{70})";
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void exec() {
FileCollection oldClasspath = getClasspath();
File jarFile = null;
try {
if (!oldClasspath.isEmpty()) {
try {
jarFile =
toJarWithClasspath(oldClasspath.getFiles());
setClasspath(getProject().files(jarFile));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
super.exec();
} finally {
setClasspath(oldClasspath);
if (jarFile != null) {
try {
Files.delete(jarFile.toPath());
} catch (Exception e) {
logger.warn("Couldn't delete: " + jarFile, e);
}
}
}
}
public static File toJarWithClasspath(Set<File> files)
throws IOException {
File jarFile = File.createTempFile("long-classpath", ".jar");
try (ZipOutputStream zip =
new ZipOutputStream(new FileOutputStream(jarFile))) {
zip.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
try (PrintWriter writer =
new PrintWriter(
new OutputStreamWriter(
zip, StandardCharsets.UTF_8))) {
writer.println("Manifest-Version: 1.0");
String classPath = files.stream().map(
file -> file.toURI().toString())
.collect(Collectors.joining(" "));
String classPathEntry = "Class-Path: " + classPath;
writer.println(Arrays.stream(
classPathEntry.split(MATCH_CHUNKS_OF_70_CHARACTERS))
.collect(Collectors.joining("\n ")));
}
}
return jarFile;
}
}
Using this is cumbersome, though, because everywhere someone might run JavaExec, I have to replace it with WorkingJavaExec. New developers also don't know that there is this pitfall in Gradle in the first place, so they don't even know it's something they have to work around.
In reading the internals of Gradle, I saw that JavaExec internally uses a JavaExecAction to do the actual exec.
I thought that maybe by replacing this, we could fix the problem as if Gradle had fixed it themselves, and maybe it would then also apply to other tasks, such as Test. But I haven't been able to find any examples anywhere. (Even in other large projects, which you would expect to have hit the same issue!)
Is it possible to substitute JavaExecAction, and if so, how?
I'm not sure you can "substitute" JavaExecAction because it is set during JavaExec task instanciation, but I think you can solve this problem in a nicer way, using a custom Plugin as follow:
class FixClasspathLimitPlugin implements Plugin<Project> {
#Override
void apply(Project project) {
// after project has been evaluated, hack into all tasks of type JavaExec declared.
project.afterEvaluate {
project.tasks.stream().filter { task -> task instanceof JavaExec }.forEach {
println "Reconfiguring classpath for : $it"
JavaExec javaExec = (JavaExec) it;
FileCollection oldClasspath = javaExec.getClasspath()
// insert an Action at first position, that will change classpath
javaExec.doFirst { task ->
((JavaExec) task).setClasspath(getProject().files(toJarWithClasspath(oldClasspath.getFiles())));
}
// optional - reset old classpath
javaExec.doLast { task ->
((JavaExec) task).setClasspath(oldClasspath)
}
}
}
}
public static File toJarWithClasspath(Set<File> files)
throws Exception {
// same method implementation as given in your question
}
This way, you won't have to replace JavaExec in all build scripts written by your team, you will only have to ensure that these scripts apply your plugin.
And if you use a custom distribution of Gradle and use wrapper in you enterprise, you can even include this plugin in this distribution as an Init Script, as explained here: https://docs.gradle.org/current/userguide/init_scripts.html#sec:using_an_init_script
Put a file that ends with .gradle in the GRADLE_HOME/init.d/ directory, in the Gradle distribution. This allows you to package up a custom Gradle distribution containing some custom build logic and plugins. You can combine this with the Gradle wrapper as a way to make custom logic available to all builds in your enterprise.
This way, the plugin will be applied in a "transparent" way.
Concerning the Test task: it does not use JavaExecAction, I think, but a similar solution could be applied, using a similar plugin.
You can use the jar task to add the class path to the manifest for you:
jar {
baseName = "my-app"
version = "1.0.0"
manifest {
attributes("Class-Path": configurations.compile.collect { it.getName() }.join(' '))
}
}
And then you can reference that jar when launching:
task run(type:JavaExec) {
classpath = jar.outputs.files
main = "myapp.MainClass"
}
That works around the command line path limit. You might also want to copy the dependency JARs to the output folder, so they will be available at runtime.
task copyDependencies(type: Copy, dependsOn: [ "build" ]) {
from configurations.runtime
into "./build/libs"
}
build.finalizedBy(copyDependencies)
Helpful?

gradle :compilePlayBinaryPlayRoutes is not generating all imports

I am switching a play app from SBT to gradle and the routes and reverse routes scala files are not being generated with
import _root_.play.libs.F
which is causing
build/src/play/binary/routesScalaSources/controllers/ReverseRoutes.scala:260: not found: value F
def validate(accountId:F.Option[java.lang.Long]): Call = {
I am using gradle 3.5, play: '2.4.8', scala: '2.11' and java: '1.8'. Does anyone know if there is a compatibility issue with or some other known issue that is stopping the import from being added to the generated scala file?
** EDIT **
I found this class RoutesCompile with a method additionalImports but I can't find how to use it in the build.gradle file. (I am super new to gradle, more of a maven guy)
** EDIT 2 **
Based on the Javadoc in the RoutesCompile class it seems like I should be adding it to the model like this:
model {
components {
play {
platform play: '2.4.8', scala: '2.11', java: '1.8'
injectedRoutesGenerator = true
additionalImports = ['play.libs.F']
sources {
twirlTemplates {
defaultImports = TwirlImports.JAVA
source.srcDir "assets/views"
source.exclude "assets/stylesheets"
}
}
}
}
}
But I get the following error:
> Exception thrown while executing model rule: play { ... } # build.gradle line 147, column 9
> No such property: additionalImports for class: org.gradle.play.PlayApplicationSpec
I have finally found the answer to my issue. I needed to get the task and add the additional import that way.
model {
components {
play {
platform play: '2.4.8', scala: '2.11', java: '1.8'
injectedRoutesGenerator = true
tasks.withType(RoutesCompile) {
additionalImports = ['play.libs.F']
}
sources {
twirlTemplates {
defaultImports = TwirlImports.JAVA
source.srcDir "assets/views"
source.exclude "assets/stylesheets"
}
}
}
}
}

Categories