Skip to content

Commit 3df9122

Browse files
committed
java9-modularity#72: added test-project-mixed
tests the "modularity" extension (incl. verifying generated class file formats)
1 parent dd341b2 commit 3df9122

File tree

40 files changed

+461
-0
lines changed

40 files changed

+461
-0
lines changed

src/test/java/org/javamodularity/moduleplugin/ModulePluginSmokeTest.java

+39
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.gradle.testkit.runner.BuildResult;
66
import org.gradle.testkit.runner.GradleRunner;
77
import org.junit.jupiter.api.BeforeEach;
8+
import org.junit.jupiter.api.Test;
89
import org.junit.jupiter.params.ParameterizedTest;
910
import org.junit.jupiter.params.provider.ValueSource;
1011

@@ -49,6 +50,44 @@ void smokeTest(String projectName) {
4950
assertTasksSuccessful(result, "greeter.runner", "build", "run");
5051
}
5152

53+
@Test
54+
void smokeTestMixed() throws IOException {
55+
var result = GradleRunner.create()
56+
.withProjectDir(new File("test-project-mixed"))
57+
.withPluginClasspath(pluginClasspath)
58+
.withGradleVersion(GRADLE_VERSION)
59+
.withArguments("-c", "smoke_test_settings.gradle", "clean", "build", "--stacktrace")
60+
.forwardOutput()
61+
.build();
62+
63+
verifyMixedTestResult(result, "greeter.api-jdk8", 8, 9);
64+
65+
verifyMixedTestResult(result, "greeter.provider-jdk8", 8, 9);
66+
verifyMixedTestResult(result, "greeter.provider-jdk8.test-jdk8", 8, 9);
67+
verifyMixedTestResult(result, "greeter.provider-jdk8.test-jdk11", 11, 11);
68+
69+
verifyMixedTestResult(result, "greeter.provider-jdk11", 11, 11);
70+
verifyMixedTestResult(result, "greeter.provider-jdk11.test-jdk11", 11, 11);
71+
}
72+
73+
private static void verifyMixedTestResult(
74+
BuildResult result, String subprojectName,
75+
int mainJavaRelease, int moduleInfoJavaRelease) throws IOException {
76+
assertTasksSuccessful(result, subprojectName, "build");
77+
assertExpectedClassFileFormats(subprojectName, mainJavaRelease, moduleInfoJavaRelease);
78+
}
79+
80+
private static void assertExpectedClassFileFormats(
81+
String subprojectName, int mainJavaRelease, int moduleInfoJavaRelease) throws IOException {
82+
Path classesDir = Path.of("test-project-mixed").resolve(subprojectName).resolve("build/classes/java/main");
83+
84+
Path moduleInfoClassPath = classesDir.resolve("module-info.class");
85+
SmokeTestHelper.assertClassFileJavaVersion(moduleInfoJavaRelease, moduleInfoClassPath);
86+
87+
Path nonModuleInfoClassPath = SmokeTestHelper.anyNonModuleInfoClassFilePath(classesDir);
88+
SmokeTestHelper.assertClassFileJavaVersion(mainJavaRelease, nonModuleInfoClassPath);
89+
}
90+
5291
@ParameterizedTest
5392
@ValueSource(strings = "test-project")
5493
void smokeTestDist(String projectName) {

src/test/java/org/javamodularity/moduleplugin/SmokeTestHelper.java

+37
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import org.gradle.testkit.runner.TaskOutcome;
66

77
import java.io.*;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
810
import java.util.Objects;
911
import java.util.concurrent.TimeUnit;
1012
import java.util.stream.Collectors;
@@ -19,6 +21,41 @@ static void assertTaskSuccessful(BuildResult result, String subprojectName, Stri
1921
assertEquals(TaskOutcome.SUCCESS, task.getOutcome(), () -> fullTaskName + " failed!");
2022
}
2123

24+
//region CLASS FILE FORMAT
25+
static void assertClassFileJavaVersion(int expectedJavaVersion, Path classFilePath) throws IOException {
26+
int actualJavaVersion = classFileJavaVersion(classFilePath);
27+
assertEquals(expectedJavaVersion, actualJavaVersion, classFilePath::toString);
28+
}
29+
30+
static Path anyNonModuleInfoClassFilePath(Path classesDir) throws IOException {
31+
return Files.walk(classesDir)
32+
.filter(SmokeTestHelper::isClassFile)
33+
.filter(path -> !path.endsWith("module-info.class"))
34+
.findAny()
35+
.orElseThrow(() -> new IllegalStateException("Main class file not found in " + classesDir));
36+
}
37+
38+
private static int classFileJavaVersion(Path classFilePath) throws IOException {
39+
// https://en.wikipedia.org/wiki/Java_class_file#General_layout
40+
return classFileFormat(classFilePath) - 44;
41+
}
42+
43+
private static int classFileFormat(Path classFilePath) throws IOException {
44+
if (!isClassFile(classFilePath)) {
45+
throw new IllegalArgumentException(classFilePath.toString());
46+
}
47+
48+
try (InputStream inputStream = Files.newInputStream(classFilePath)) {
49+
// https://en.wikipedia.org/wiki/Java_class_file#General_layout
50+
return inputStream.readNBytes(8)[7]; // 8th byte: major version number
51+
}
52+
}
53+
54+
private static boolean isClassFile(Path classFilePath) {
55+
return classFilePath.toString().endsWith(".class");
56+
}
57+
//endregion
58+
2259
//region APP OUTPUT
2360
static String getAppOutput(String binDirPath, String appName) {
2461
boolean windows = System.getProperty("os.name").toLowerCase().contains("windows");

test-project-mixed/README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
Introduction
2+
===
3+
4+
This mixed test project can be used as a standalone test project to verify the published plugin.
5+
It is also used as an internal test project for testing unpublished plugin changes.
6+
7+
This project is "mixed" in two ways:
8+
9+
1. It produces classes targeting mixed JDKs (JDK 8 and JDK 11). The project makes use of the
10+
[`ModularityExtension`](../src/main/java/org/javamodularity/moduleplugin/extensions/ModularityExtension.java),
11+
which in turn makes use of the Java compiler
12+
[`--release`](https://docs.oracle.com/en/java/javase/11/tools/javac.html) option.
13+
2. It contains mixed build files: `build.gradle` (Groovy DSL) and `build.gradle.kts` (Kotlin DSL).
14+
15+
Standalone test product
16+
===
17+
To run this product as a standalone test product use this command (launched from `test-project-mixed` directory):
18+
```
19+
../gradlew clean build
20+
```
21+
22+
It will use the most recent plugin version from Gradle maven repository to compile the test project with
23+
modules and run the unit tests.
24+
25+
Testing locally published plugin
26+
===
27+
28+
You can publish the plugin locally by running this command from the root directory:
29+
30+
`./gradlew publishToMavenLocal`
31+
32+
You can test the locally published plugin by running the following command from `test-project-mixed` directory.
33+
34+
`../gradlew -c local_maven_settings.gradle clean build`
35+
36+
It will use the latest locally published version of the plugin to compile the test project with
37+
modules and run the unit tests.
38+
39+
40+
Internal test project
41+
===
42+
43+
This mode is enabled in `ModulePluginSmokeTest` by passing an extra parameter (`-c smoke_test_settings.gradle`).
44+
`smoke_test_settings.gradle` script configures plugin management so that the plugin cannot be resolved from
45+
a Gradle plugin repository. Instead, it relies on the smoke test to make the plugin under development available
46+
to the test project by sharing a classpath (using Gradle TestKit).
47+
48+
__CAUTION:__ This approach won't work outside of the smoke test, it will break the build because the plugin jar won't be resolved.

test-project-mixed/build.gradle

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
plugins {
2+
id 'org.javamodularity.moduleplugin' version '1.4.1' apply false
3+
}
4+
5+
subprojects {
6+
apply plugin: 'java'
7+
apply plugin: 'org.javamodularity.moduleplugin'
8+
9+
repositories {
10+
mavenCentral()
11+
}
12+
13+
test {
14+
useJUnitPlatform()
15+
16+
testLogging {
17+
events 'PASSED', 'FAILED', 'SKIPPED'
18+
}
19+
}
20+
21+
dependencies {
22+
testImplementation "org.junit.jupiter:junit-jupiter-api:$jUnitVersion"
23+
testImplementation "org.junit.jupiter:junit-jupiter-params:$jUnitVersion"
24+
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$jUnitVersion"
25+
}
26+
27+
build.dependsOn javadoc
28+
}

test-project-mixed/gradle.properties

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jUnitVersion = 5.3.1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
dependencies {
2+
implementation project(':greeter.api-jdk8')
3+
testImplementation('org.hamcrest:hamcrest:2.1+')
4+
5+
compile "javax.annotation:javax.annotation-api:1.3.2"
6+
compile "com.google.code.findbugs:jsr305:3.0.2"
7+
}
8+
9+
patchModules.config = [
10+
"java.annotation=jsr305-3.0.2.jar"
11+
]
12+
13+
javadoc {
14+
moduleOptions {
15+
addModules = ['java.sql']
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
dependencies {
2+
implementation project(':greeter.api-jdk8')
3+
}
4+
5+
patchModules.config = [
6+
"java.annotation=jsr305-3.0.2.jar"
7+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
modularity.mixedJavaRelease(8)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package examples.greeter.api;
2+
3+
public interface Greeter {
4+
String hello();
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module greeter.api {
2+
exports examples.greeter.api;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
modularity.standardJavaRelease(11)
2+
3+
dependencies {
4+
runtimeOnly(project(":greeter.provider-jdk11"))
5+
}
6+
7+
apply(from = "$rootDir/gradle/shared/greeter.provider.test.gradle")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import examples.greeter.api.Greeter;
2+
3+
module greeter.provider.test {
4+
requires greeter.api;
5+
6+
uses Greeter;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package tests;
2+
3+
import examples.greeter.api.Greeter;
4+
5+
import java.util.ServiceLoader;
6+
7+
public class GreeterLocator {
8+
public Greeter findGreeter() {
9+
return ServiceLoader.load(Greeter.class).findFirst().orElseThrow(() -> new RuntimeException("No Greeter found"));
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package tests;
2+
3+
import examples.greeter.api.Greeter;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.assertFalse;
7+
8+
class GreeterTest {
9+
@Test
10+
void testLocate() {
11+
Greeter greeter = new GreeterLocator().findGreeter();
12+
assertFalse(greeter.hello().isBlank());
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
modularity.standardJavaRelease(11)
2+
3+
apply(from = "$rootDir/gradle/shared/greeter.provider.gradle")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package examples.greeter;
2+
3+
import examples.greeter.api.Greeter;
4+
import java.io.*;
5+
import java.util.*;
6+
import javax.annotation.Nonnull;
7+
8+
public class Friendly implements Greeter {
9+
@Override @Nonnull
10+
public String hello() {
11+
var stream = this.getClass().getResourceAsStream("/greeting.txt");
12+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "utf-8"))) {
13+
return reader.readLine();
14+
} catch (Exception e) {
15+
throw new RuntimeException(e);
16+
}
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import examples.greeter.api.Greeter;
2+
3+
module greeter.provider {
4+
requires greeter.api;
5+
requires java.annotation;
6+
7+
provides Greeter with examples.greeter.Friendly;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
welcome
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package examples.greeter;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.junit.jupiter.api.Assertions.*;
6+
7+
class FriendlyTest {
8+
@Test
9+
void testGreeting() {
10+
String greeting = new Friendly().hello();
11+
assertTrue(greeting.contains("welcome"));
12+
}
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package examples.greeter;
2+
3+
import javax.script.*;
4+
import org.junit.jupiter.api.*;
5+
import static org.hamcrest.MatcherAssert.assertThat;
6+
import static org.hamcrest.Matchers.*;
7+
8+
class ScriptingTest {
9+
10+
@Test
11+
void testScripting() {
12+
ScriptEngineManager manager = new ScriptEngineManager();
13+
assertThat(manager.getEngineFactories(), not(nullValue()));
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// make module visible
2+
--add-modules
3+
java.scripting,org.hamcrest
4+
5+
// "requires java.scripting"
6+
--add-reads
7+
greeter.provider=java.scripting,org.hamcrest
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
modularity.standardJavaRelease 11
2+
3+
dependencies {
4+
runtimeOnly project(':greeter.provider-jdk8')
5+
}
6+
7+
apply from: "$rootDir/gradle/shared/greeter.provider.test.gradle"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import examples.greeter.api.Greeter;
2+
3+
module greeter.provider.test {
4+
requires greeter.api;
5+
6+
uses Greeter;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package tests;
2+
3+
import examples.greeter.api.Greeter;
4+
5+
import java.util.ServiceLoader;
6+
7+
public class GreeterLocator {
8+
public Greeter findGreeter() {
9+
return ServiceLoader.load(Greeter.class).findFirst().orElseThrow(() -> new RuntimeException("No Greeter found"));
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package tests;
2+
3+
import examples.greeter.api.Greeter;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.assertFalse;
7+
8+
class GreeterTest {
9+
@Test
10+
void testLocate() {
11+
Greeter greeter = new GreeterLocator().findGreeter();
12+
assertFalse(greeter.hello().isBlank());
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
modularity.mixedJavaRelease 8
2+
3+
dependencies {
4+
runtimeOnly project(':greeter.provider-jdk8')
5+
}
6+
7+
apply from: "$rootDir/gradle/shared/greeter.provider.test.gradle"

0 commit comments

Comments
 (0)