build() {
+ // Immutable copy.
+ return List.copyOf(craftedFlags);
+ }
+
+ private EcjJctFlagBuilderImpl addFlagIfTrue(boolean condition, String flag) {
+ if (condition) {
+ craftedFlags.add(flag);
+ }
+
+ return this;
+ }
+
+ private EcjJctFlagBuilderImpl addVersionIfPresent(String flagPrefix, @Nullable String version) {
+ if (version != null) {
+ craftedFlags.add(flagPrefix);
+ craftedFlags.add(version);
+ }
+
+ return this;
+ }
+}
diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilerTest.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilerTest.java
new file mode 100644
index 000000000..62532cc0e
--- /dev/null
+++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 - 2025, the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.github.ascopes.jct.junit;
+
+import io.github.ascopes.jct.compilers.JctCompilerConfigurer;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Tags;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.condition.DisabledInNativeImage;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+
+/**
+ * Annotation that can be applied to a JUnit parameterized test to invoke that test case across
+ * multiple ECJ compilers, each configured to a specific version in a range of Java language
+ * versions.
+ *
+ * This will also add the {@code "java-compiler-testing-test"} tag and {@code "ecj-test"}
+ * tags to your test method, meaning you can instruct your IDE or build system to optionally only
+ * run tests annotated with this method for development purposes. As an example, Maven Surefire
+ * could be instructed to only run these tests by passing {@code -Dgroup="ecj-test"} to Maven.
+ *
+ * @author Ashley Scopes
+ * @since TBC
+ */
+@ArgumentsSource(EcjCompilersProvider.class)
+@DisabledInNativeImage
+@Documented
+@ParameterizedTest(name = "for compiler \"{0}\"")
+@Retention(RetentionPolicy.RUNTIME)
+@Tags({
+ @Tag("java-compiler-testing-test"),
+ @Tag("ecj-test")
+})
+@Target({
+ ElementType.ANNOTATION_TYPE,
+ ElementType.METHOD,
+})
+@TestTemplate
+public @interface EcjCompilerTest {
+
+ /**
+ * Minimum version to use (inclusive).
+ *
+ *
By default, it will use the lowest possible version supported by the compiler. This
+ * varies between versions of the JDK that are in use.
+ *
+ *
If the version is lower than the minimum supported version, then the minimum supported
+ * version of the compiler will be used instead. This enables writing tests that will work on a
+ * range of JDKs during builds without needing to duplicate the test to satisfy different JDK
+ * supported version ranges.
+ *
+ * @return the minimum version.
+ */
+ int minVersion() default Integer.MIN_VALUE;
+
+ /**
+ * Maximum version to use (inclusive).
+ *
+ *
By default, it will use the highest possible version supported by the compiler. This
+ * varies between versions of the JDK that are in use.
+ *
+ *
If the version is higher than the maximum supported version, then the maximum supported
+ * version of the compiler will be used instead. This enables writing tests that will work on a
+ * range of JDKs during builds without needing to duplicate the test to satisfy different JDK
+ * supported version ranges.
+ *
+ * @return the maximum version.
+ */
+ int maxVersion() default Integer.MAX_VALUE;
+
+ /**
+ * Get an array of compiler configurer classes to apply in-order before starting the test.
+ *
+ *
Each configurer must have a public no-args constructor, and their package must be
+ * open to this module if JPMS modules are in-use, for example:
+ *
+ *
+ * module mytests {
+ * requires io.github.ascopes.jct;
+ * requires org.junit.jupiter.api;
+ *
+ * opens org.example.mytests to io.github.ascopes.jct;
+ * }
+ *
+ *
+ * An example of usage:
+ *
+ *
+ * public class WerrorConfigurer implements JctCompilerConfigurer<RuntimeException> {
+ * {@literal @Override}
+ * public void configure(JctCompiler compiler) {
+ * compiler.failOnWarnings(true);
+ * }
+ * }
+ *
+ * // ...
+ *
+ * class SomeTest {
+ * {@literal @EcjCompilerTest(configurers = WerrorConfigurer.class)}
+ * void someTest(JctCompiler compiler) {
+ * // ...
+ * }
+ * }
+ *
+ *
+ * @return an array of classes to run to configure the compiler. These run in the given order.
+ */
+ Class extends JctCompilerConfigurer>>[] configurers() default {};
+
+ /**
+ * The version strategy to use.
+ *
+ * This determines whether the version number being iterated across specifies the
+ * release, source, target, or source and target versions.
+ *
+ *
The default is to specify the release.
+ *
+ * @return the version strategy to use.
+ */
+ VersionStrategy versionStrategy() default VersionStrategy.RELEASE;
+}
diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilersProvider.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilersProvider.java
new file mode 100644
index 000000000..4df27e4dd
--- /dev/null
+++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/EcjCompilersProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 - 2025, the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.github.ascopes.jct.junit;
+
+import io.github.ascopes.jct.compilers.JctCompiler;
+import io.github.ascopes.jct.compilers.impl.EcjJctCompilerImpl;
+import org.junit.jupiter.params.support.AnnotationConsumer;
+
+/**
+ * Argument provider for the {@link EcjCompilerTest} annotation.
+ *
+ * @author Ashley Scopes
+ * @since 5.0.0
+ */
+public final class EcjCompilersProvider extends AbstractCompilersProvider
+ implements AnnotationConsumer {
+
+ /**
+ * Initialise the provider.
+ *
+ * This is only visible for testing purposes, users should have no need to
+ * initialise this class directly.
+ */
+ EcjCompilersProvider() {
+ // Visible for testing only.
+ }
+
+ @Override
+ protected JctCompiler initializeNewCompiler() {
+ return new EcjJctCompilerImpl();
+ }
+
+ @Override
+ protected int minSupportedVersion() {
+ return EcjJctCompilerImpl.getEarliestSupportedVersionInt();
+ }
+
+ @Override
+ protected int maxSupportedVersion() {
+ return EcjJctCompilerImpl.getLatestSupportedVersionInt();
+ }
+
+ @Override
+ public void accept(EcjCompilerTest annotation) {
+ var min = annotation.minVersion();
+ var max = annotation.maxVersion();
+ var configurers = annotation.configurers();
+ var versioning = annotation.versionStrategy();
+ configure(min, max, configurers, versioning);
+ }
+}
diff --git a/java-compiler-testing/src/main/java/module-info.java b/java-compiler-testing/src/main/java/module-info.java
index ce510b28c..d6a9ff905 100644
--- a/java-compiler-testing/src/main/java/module-info.java
+++ b/java-compiler-testing/src/main/java/module-info.java
@@ -75,7 +75,8 @@
*
* class JsonSchemaAnnotationProcessorTest {
*
- * {@literal @JavacCompilerTest(minVersion=11, maxVersion=19)}
+ * {@literal @EcjCompilerTest(minVersion=17)}
+ * {@literal @JavacCompilerTest(minVersion=17)}
* void theJsonSchemaIsCreatedFromTheInputCode(JctCompiler compiler) {
*
* try (var workspace = Workspaces.newWorkspace()) {
@@ -125,6 +126,7 @@
requires java.management;
requires me.xdrop.fuzzywuzzy;
requires org.assertj.core;
+ requires org.eclipse.jdt.core.compiler.batch;
requires static org.jspecify;
requires static org.junit.jupiter.api;
requires static org.junit.jupiter.params;
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/JctCompilersTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/JctCompilersTest.java
index c89636de4..3bd0c3c17 100644
--- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/JctCompilersTest.java
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/JctCompilersTest.java
@@ -17,6 +17,7 @@
import static org.assertj.core.api.Assertions.assertThat;
+import io.github.ascopes.jct.compilers.impl.EcjJctCompilerImpl;
import io.github.ascopes.jct.compilers.impl.JavacJctCompilerImpl;
import io.github.ascopes.jct.fixtures.UtilityClassTestTemplate;
import org.junit.jupiter.api.DisplayName;
@@ -53,4 +54,22 @@ void newPlatformCompilerReturnsTheExpectedInstance() {
.satisfies(constructed -> assertThat(compiler).isSameAs(constructed));
}
}
+
+ @DisplayName(".newEcjCompiler() creates an EcjJctCompilerImpl instance")
+ @Test
+ void newEcjCompilerReturnsTheExpectedInstance() {
+ try (var ecjJctCompilerImplMock = Mockito.mockConstruction(EcjJctCompilerImpl.class)) {
+ // When
+ var compiler = JctCompilers.newEcjCompiler();
+
+ // Then
+ assertThat(compiler)
+ .isInstanceOf(EcjJctCompilerImpl.class);
+
+ assertThat(ecjJctCompilerImplMock.constructed())
+ .singleElement()
+ // Nested assertion to swap expected/actual args.
+ .satisfies(constructed -> assertThat(compiler).isSameAs(constructed));
+ }
+ }
}
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImplTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImplTest.java
new file mode 100644
index 000000000..002833292
--- /dev/null
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctCompilerImplTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 - 2025, the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.github.ascopes.jct.compilers.impl;
+
+import static io.github.ascopes.jct.fixtures.Fixtures.someInt;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * {@link EcjJctCompilerImpl} tests.
+ *
+ * @author Ashley Scopes
+ */
+@DisplayName("EcjJctCompilerImpl tests")
+class EcjJctCompilerImplTest {
+
+ EcjJctCompilerImpl compiler;
+
+ @BeforeEach
+ void setUp() {
+ compiler = new EcjJctCompilerImpl();
+ }
+
+ @DisplayName("Compilers have the expected JSR-199 compiler factory")
+ @Test
+ void compilersHaveTheExpectedCompilerFactory() {
+ // When
+ var actualCompiler = compiler.getCompilerFactory().createCompiler();
+
+ // Then
+ assertThat(actualCompiler).isInstanceOf(EclipseCompiler.class);
+ }
+
+ @DisplayName("Compilers have the expected flag builder factory")
+ @Test
+ void compilersHaveTheExpectedFlagBuilderFactory() {
+ // Given
+ try (var flagBuilderMock = mockConstruction(EcjJctFlagBuilderImpl.class)) {
+ // When
+ var flagBuilder = compiler.getFlagBuilderFactory().createFlagBuilder();
+
+ // Then
+ assertThat(flagBuilderMock.constructed()).hasSize(1);
+ assertThat(flagBuilder).isSameAs(flagBuilderMock.constructed().get(0));
+ }
+ }
+
+ @DisplayName("Compilers have the expected default release string")
+ @Test
+ void compilersHaveTheExpectedDefaultRelease() {
+ // Given
+ try (var compilerClassMock = mockStatic(EcjJctCompilerImpl.class)) {
+ var latestSupportedInt = someInt(17, 21);
+ compilerClassMock
+ .when(EcjJctCompilerImpl::getLatestSupportedVersionInt)
+ .thenReturn(latestSupportedInt);
+
+ // When
+ var defaultRelease = compiler.getDefaultRelease();
+
+ // Then
+ compilerClassMock
+ .verify(EcjJctCompilerImpl::getLatestSupportedVersionInt);
+
+ assertThat(defaultRelease)
+ .isEqualTo("%d", latestSupportedInt);
+ }
+ }
+
+ @DisplayName("Compilers have the expected default name")
+ @Test
+ void compilersHaveTheExpectedDefaultName() {
+ // Then
+ assertThat(compiler.getName()).isEqualTo("ECJ");
+ }
+
+ @DisplayName("Compilers have no default compiler flags set")
+ @Test
+ void compilersHaveNoDefaultCompilerFlagsSet() {
+ // Then
+ assertThat(compiler.getCompilerOptions()).isEmpty();
+ }
+
+ @DisplayName("Compilers have no default annotation processor flags set")
+ @Test
+ void compilersHaveNoDefaultAnnotationProcessorFlagsSet() {
+ // Then
+ assertThat(compiler.getAnnotationProcessorOptions()).isEmpty();
+ }
+
+ @DisplayName("Compilers have no default annotation processors set")
+ @Test
+ void compilersHaveNoDefaultAnnotationProcessorsSet() {
+ // Then
+ assertThat(compiler.getAnnotationProcessors()).isEmpty();
+ }
+
+ @DisplayName("Compilers have the expected latest release")
+ @Test
+ void latestSupportedVersionReturnsTheExpectedValue() {
+ // Given
+ var expected = (int) ((ClassFileConstants.getLatestJDKLevel() >> 16L)
+ - ClassFileConstants.MAJOR_VERSION_0);
+
+ // When
+ var actual = EcjJctCompilerImpl.getLatestSupportedVersionInt();
+
+ // Then
+ assertThat(expected).isEqualTo(actual);
+ }
+
+ @DisplayName("Compilers have the expected earliest release")
+ @Test
+ void earliestSupportedVersionReturnsTheExpectedValue() {
+ // Given
+ var expected = (int) ((ClassFileConstants.JDK1_8 >> 16L)
+ - ClassFileConstants.MAJOR_VERSION_0);
+
+ // When
+ var actual = EcjJctCompilerImpl.getEarliestSupportedVersionInt();
+
+ // Then
+ assertThat(expected).isEqualTo(actual);
+ }
+}
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImplTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImplTest.java
new file mode 100644
index 000000000..ea4995d74
--- /dev/null
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/compilers/impl/EcjJctFlagBuilderImplTest.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2022 - 2025, the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.github.ascopes.jct.compilers.impl;
+
+import static io.github.ascopes.jct.fixtures.Fixtures.someBoolean;
+import static io.github.ascopes.jct.fixtures.Fixtures.someRelease;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import io.github.ascopes.jct.compilers.CompilationMode;
+import io.github.ascopes.jct.compilers.DebuggingInfo;
+import io.github.ascopes.jct.fixtures.Fixtures;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * {@link EcjJctFlagBuilderImpl} tests.
+ *
+ * @author Ashley Scopes
+ */
+@DisplayName("EcjJctFlagBuilderImpl tests")
+@TestMethodOrder(OrderAnnotation.class)
+class EcjJctFlagBuilderImplTest {
+
+ EcjJctFlagBuilderImpl flagBuilder;
+
+ @BeforeEach
+ void setUp() {
+ flagBuilder = new EcjJctFlagBuilderImpl();
+ }
+
+ @DisplayName(".verbose(boolean) tests")
+ @Nested
+ class VerboseFlagTest {
+
+ @DisplayName("Setting .verbose(true) adds the '-verbose' flag")
+ @Test
+ void addsFlagIfTrue() {
+ // When
+ flagBuilder.verbose(true);
+
+ // Then
+ assertThat(flagBuilder.build()).contains("-verbose");
+ }
+
+ @DisplayName("Setting .verbose(false) does not add the '-verbose' flag")
+ @Test
+ void doesNotAddFlagIfFalse() {
+ // When
+ flagBuilder.verbose(false);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("-verbose");
+ }
+
+ @DisplayName(".verbose(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Then
+ assertThat(flagBuilder.verbose(someBoolean()))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".previewFeatures(boolean) tests")
+ @Nested
+ class PreviewFeaturesFlagTest {
+
+ @DisplayName("Setting .previewFeatures(true) adds the '--enable-preview' flag")
+ @Test
+ void addsFlagIfTrue() {
+ // When
+ flagBuilder.previewFeatures(true);
+
+ // Then
+ assertThat(flagBuilder.build()).contains("--enable-preview");
+ }
+
+ @DisplayName("Setting .previewFeatures(false) does not add the '--enable-preview' flag")
+ @Test
+ void doesNotAddFlagIfFalse() {
+ // When
+ flagBuilder.previewFeatures(false);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("--enable-preview");
+ }
+
+ @DisplayName(".previewFeatures(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Then
+ assertThat(flagBuilder.previewFeatures(someBoolean()))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".showWarnings(boolean) tests")
+ @Nested
+ class ShowWarningsFlagTest {
+
+ @DisplayName("Setting .showWarnings(true) does not add the '-nowarn' flag")
+ @Test
+ void doesNotAddFlagIfTrue() {
+ // When
+ flagBuilder.showWarnings(true);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("-nowarn");
+ }
+
+ @DisplayName("Setting .showWarnings(false) adds the '-nowarn' flag")
+ @Test
+ void addsFlagIfFalse() {
+ // When
+ flagBuilder.showWarnings(false);
+
+ // Then
+ assertThat(flagBuilder.build()).contains("-nowarn");
+ }
+
+ @DisplayName(".showWarnings(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Then
+ assertThat(flagBuilder.showWarnings(someBoolean()))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".failOnWarnings(boolean) tests")
+ @Nested
+ class FailOnWarningsFlagTest {
+
+ @DisplayName("Setting .failOnWarnings(true) adds the '--failOnWarning' flag")
+ @Test
+ void addsFlagIfTrue() {
+ // When
+ flagBuilder.failOnWarnings(true);
+
+ // Then
+ assertThat(flagBuilder.build()).contains("--failOnWarning");
+ }
+
+ @DisplayName("Setting .failOnWarnings(false) does not add the '-Werror' flag")
+ @Test
+ void doesNotAddFlagIfFalse() {
+ // When
+ flagBuilder.failOnWarnings(false);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("-Werror");
+ }
+
+ @DisplayName(".failOnWarnings(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Then
+ assertThat(flagBuilder.failOnWarnings(someBoolean()))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".compilationMode(CompilationMode) tests")
+ @Nested
+ class CompilationModeFlagTest {
+
+ @DisplayName(".compilationMode(COMPILATION_ONLY) adds -proc:none")
+ @Test
+ void compilationOnlyAddsProcNone() {
+ // When
+ flagBuilder.compilationMode(CompilationMode.COMPILATION_ONLY);
+
+ // Then
+ assertThat(flagBuilder.build()).containsExactly("-proc:none");
+ }
+
+ @DisplayName(".compilationMode(ANNOTATION_PROCESSING_ONLY) adds -proc:only")
+ @Test
+ void annotationProcessingOnlyAddsProcOnly() {
+ // When
+ flagBuilder.compilationMode(CompilationMode.ANNOTATION_PROCESSING_ONLY);
+
+ // Then
+ assertThat(flagBuilder.build()).containsExactly("-proc:only");
+ }
+
+ @DisplayName(".compilationMode(COMPILATION_AND_ANNOTATION_PROCESSING) adds nothing")
+ @Test
+ void compilationAndAnnotationProcessingAddsNothing() {
+ // When
+ flagBuilder.compilationMode(CompilationMode.COMPILATION_AND_ANNOTATION_PROCESSING);
+
+ // Then
+ assertThat(flagBuilder.build()).isEmpty();
+ }
+
+ @DisplayName(".compilationMode(...) returns the flag builder")
+ @EnumSource(CompilationMode.class)
+ @ParameterizedTest(name = "for compilationMode = {0}")
+ void returnsFlagBuilder(CompilationMode mode) {
+ // Then
+ assertThat(flagBuilder.compilationMode(mode))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".showDeprecationWarnings(boolean) tests")
+ @Nested
+ class ShowDeprecationWarningsFlagTest {
+
+ @DisplayName("Setting .showDeprecationWarnings(true) adds the '-deprecation' flag")
+ @Test
+ void addsFlagIfTrue() {
+ // When
+ flagBuilder.showDeprecationWarnings(true);
+
+ // Then
+ assertThat(flagBuilder.build()).contains("-deprecation");
+ }
+
+ @DisplayName("Setting .showDeprecationWarnings(false) does not add the '-deprecation' flag")
+ @Test
+ void doesNotAddFlagIfFalse() {
+ // When
+ flagBuilder.showDeprecationWarnings(false);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("-deprecation");
+ }
+
+ @DisplayName(".showDeprecationWarnings(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Then
+ assertThat(flagBuilder.showDeprecationWarnings(someBoolean()))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".release(String) tests")
+ @Nested
+ class ReleaseFlagTest {
+
+ @DisplayName("Setting .release(String) adds the '--release ' flag")
+ @ValueSource(strings = {"8", "11", "17"})
+ @ParameterizedTest(name = "Setting .release(String) adds the \"--release {0}\" flag")
+ void addsFlagIfPresent(String version) {
+ // When
+ flagBuilder.release(version);
+
+ // Then
+ assertThat(flagBuilder.build()).containsSequence("--release", version);
+ }
+
+ @DisplayName("Setting .release(null) does not add the '--release' flag")
+ @Test
+ void doesNotAddFlagIfNotPresent() {
+ // When
+ flagBuilder.release(null);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("--release");
+ }
+
+ @DisplayName(".release(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Then
+ assertThat(flagBuilder.release(someRelease()))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".source(String) tests")
+ @Nested
+ class SourceFlagTest {
+
+ @DisplayName("Setting .source(String) adds the '-source ' flag")
+ @ValueSource(strings = {"8", "11", "17"})
+ @ParameterizedTest(name = "Setting .source(String) adds the \"-source {0}\" flag")
+ void addsFlagIfPresent(String version) {
+ // When
+ flagBuilder.source(version);
+
+ // Then
+ assertThat(flagBuilder.build()).containsSequence("-source", version);
+ }
+
+ @DisplayName("Setting .source(null) does not add the '-source' flag")
+ @Test
+ void doesNotAddFlagIfNotPresent() {
+ // When
+ flagBuilder.source(null);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("-source");
+ }
+
+
+ @DisplayName(".source(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Then
+ assertThat(flagBuilder.source(someRelease()))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".target(String) tests")
+ @Nested
+ class TargetFlagTest {
+
+ @DisplayName("Setting .target(String) adds the '-target ' flag")
+ @ValueSource(strings = {"8", "11", "17"})
+ @ParameterizedTest(name = "Setting .target(String) adds the \"-target {0}\" flag")
+ void addsFlagIfPresent(String version) {
+ // When
+ flagBuilder.target(version);
+
+ // Then
+ assertThat(flagBuilder.build()).containsSequence("-target", version);
+ }
+
+ @DisplayName("Setting .target(null) does not add the '-target' flag")
+ @Test
+ void doesNotAddFlagIfNotPresent() {
+ // When
+ flagBuilder.target(null);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("-target");
+ }
+
+ @DisplayName(".target(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Then
+ assertThat(flagBuilder.target(someRelease()))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".debuggingInfo(Set) tests")
+ @Nested
+ class DebuggingInfoTest {
+
+ @DisplayName("Setting .debuggingInfo with an empty set adds the '-g:none' flag")
+ @Test
+ void emptySetAddsGnoneFlag() {
+ // When
+ flagBuilder.debuggingInfo(DebuggingInfo.none());
+
+ // Then
+ assertThat(flagBuilder.build()).containsOnlyOnce("-g:none");
+ }
+
+ @DisplayName("Setting .debuggingInfo with some values set adds the '-g:xxx' flags")
+ @CsvSource({
+ " LINES, -g:lines",
+ "SOURCE, -g:source",
+ " VARS, -g:vars",
+ })
+ @ParameterizedTest(name = "expect {0} to set flag {1}")
+ void correctFlagsAreSet(DebuggingInfo flag, String flagString) {
+ // When
+ flagBuilder.debuggingInfo(DebuggingInfo.just(flag));
+
+ // Then
+ assertThat(flagBuilder.build()).containsExactly(flagString);
+ }
+
+ @DisplayName("Setting .debuggingInfo with all values set adds the '-g:xxx' flags")
+ @Test
+ void allAddsValues() {
+ // When
+ flagBuilder.debuggingInfo(DebuggingInfo.all());
+
+ // Then
+ assertThat(flagBuilder.build())
+ .doesNotContain("-g", "-g:none")
+ .containsOnlyOnce("-g:lines", "-g:source", "-g:vars");
+ }
+ }
+
+ @DisplayName(".parameterInfoEnabled(boolean) tests")
+ @Nested
+ class ParameterInfoEnabledTest {
+
+ @DisplayName("Setting .parameterInfoEnabled(true) adds the '-parameters' flag")
+ @Test
+ void trueAddsFlag() {
+ // When
+ flagBuilder.parameterInfoEnabled(true);
+
+ // Then
+ assertThat(flagBuilder.build()).containsOnlyOnce("-parameters");
+ }
+
+ @DisplayName("Setting .parameterInfoEnabled(false) does not add the '-parameters' flag")
+ @Test
+ void falseDoesNotAddFlag() {
+ // When
+ flagBuilder.parameterInfoEnabled(false);
+
+ // Then
+ assertThat(flagBuilder.build()).doesNotContain("-parameters");
+ }
+ }
+
+ @DisplayName(".addAnnotationProcessorOptions(List) tests")
+ @Nested
+ class AnnotationProcessorOptionsTest {
+
+ @DisplayName("Setting .annotationProcessorOptions(List) adds the options")
+ @Test
+ void addsAnnotationProcessorOptions() {
+ // Given
+ var options = Stream
+ .generate(Fixtures::someText)
+ .limit(5)
+ .collect(Collectors.toList());
+
+ // When
+ flagBuilder.annotationProcessorOptions(options);
+
+ // Then
+ assertThat(flagBuilder.build())
+ .containsSequence(options.stream()
+ .map("-A"::concat)
+ .collect(Collectors.toList()));
+ }
+
+ @DisplayName(".annotationProcessorOptions(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Given
+ var options = Stream
+ .generate(Fixtures::someText)
+ .limit(5)
+ .collect(Collectors.toList());
+
+ // Then
+ assertThat(flagBuilder.annotationProcessorOptions(options))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @DisplayName(".compilerOptions(List) tests")
+ @Nested
+ class CompilerOptionsTest {
+
+ @DisplayName("Setting .compilerOptions(List) adds the options")
+ @Test
+ void addsCompilerOptions() {
+ // Given
+ var options = Stream
+ .generate(Fixtures::someText)
+ .limit(5)
+ .collect(Collectors.toList());
+
+ // When
+ flagBuilder.compilerOptions(options);
+
+ // Then
+ assertThat(flagBuilder.build())
+ .containsSequence(options);
+ }
+
+ @DisplayName(".compilerOptions(...) returns the flag builder")
+ @Test
+ void returnsFlagBuilder() {
+ // Given
+ var options = Stream
+ .generate(Fixtures::someText)
+ .limit(5)
+ .collect(Collectors.toList());
+
+ // Then
+ assertThat(flagBuilder.compilerOptions(options))
+ .isSameAs(flagBuilder);
+ }
+ }
+
+ @Order(Integer.MAX_VALUE - 1)
+ @DisplayName("The flag builder adds multiple flags correctly")
+ @Test
+ void addsMultipleFlagsCorrectly() {
+ // When
+ var flags = flagBuilder
+ .compilerOptions(List.of("--foo", "--bar"))
+ .release("15")
+ .annotationProcessorOptions(List.of("--baz", "--bork"))
+ .build();
+
+ // Then
+ assertThat(flags)
+ .containsExactly("--foo", "--bar", "--release", "15", "-A--baz", "-A--bork");
+ }
+
+ @Order(Integer.MAX_VALUE)
+ @DisplayName("The flag builder produces an immutable list as the result")
+ @Test
+ void resultIsImmutable() {
+ // When
+ var flags = flagBuilder
+ .compilerOptions(List.of("--foo", "--bar"))
+ .release("15")
+ .annotationProcessorOptions(List.of("--baz", "--bork"))
+ .build();
+
+ // Then
+ assertThatThrownBy(() -> flags.add("something"))
+ .isInstanceOf(UnsupportedOperationException.class);
+ }
+}
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicLegacyCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicLegacyCompilationIntegrationTest.java
index 7a4d47fb7..6c343a3c3 100644
--- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicLegacyCompilationIntegrationTest.java
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicLegacyCompilationIntegrationTest.java
@@ -19,6 +19,7 @@
import io.github.ascopes.jct.compilers.JctCompiler;
import io.github.ascopes.jct.integration.AbstractIntegrationTest;
+import io.github.ascopes.jct.junit.EcjCompilerTest;
import io.github.ascopes.jct.junit.JavacCompilerTest;
import io.github.ascopes.jct.workspaces.PathStrategy;
import io.github.ascopes.jct.workspaces.Workspaces;
@@ -34,6 +35,7 @@
class BasicLegacyCompilationIntegrationTest extends AbstractIntegrationTest {
@DisplayName("I can compile a 'Hello, World!' program using a RAM directory")
+ @EcjCompilerTest
@JavacCompilerTest
void helloWorldJavacRamDirectory(JctCompiler compiler) {
try (var workspace = Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES)) {
@@ -54,6 +56,7 @@ void helloWorldJavacRamDirectory(JctCompiler compiler) {
}
@DisplayName("I can compile a 'Hello, World!' program using a temp directory")
+ @EcjCompilerTest
@JavacCompilerTest
void helloWorldJavacTempDirectory(JctCompiler compiler) {
try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) {
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicModuleCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicModuleCompilationIntegrationTest.java
index 06cd8bf1f..26c7b2a73 100644
--- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicModuleCompilationIntegrationTest.java
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicModuleCompilationIntegrationTest.java
@@ -19,6 +19,7 @@
import io.github.ascopes.jct.compilers.JctCompiler;
import io.github.ascopes.jct.integration.AbstractIntegrationTest;
+import io.github.ascopes.jct.junit.EcjCompilerTest;
import io.github.ascopes.jct.junit.JavacCompilerTest;
import io.github.ascopes.jct.workspaces.PathStrategy;
import io.github.ascopes.jct.workspaces.Workspaces;
@@ -33,6 +34,7 @@
class BasicModuleCompilationIntegrationTest extends AbstractIntegrationTest {
@DisplayName("I can compile a 'Hello, World!' module program using a RAM disk")
+ @EcjCompilerTest(minVersion = 9)
@JavacCompilerTest(minVersion = 9)
void helloWorldRamDisk(JctCompiler compiler) {
try (var workspace = Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES)) {
@@ -61,6 +63,7 @@ void helloWorldRamDisk(JctCompiler compiler) {
}
@DisplayName("I can compile a 'Hello, World!' module program using a temporary directory")
+ @EcjCompilerTest(minVersion = 9)
@JavacCompilerTest(minVersion = 9)
void helloWorldUsingTempDirectory(JctCompiler compiler) {
try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) {
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicMultiModuleCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicMultiModuleCompilationIntegrationTest.java
index 9dbf34106..22e611598 100644
--- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicMultiModuleCompilationIntegrationTest.java
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/BasicMultiModuleCompilationIntegrationTest.java
@@ -20,6 +20,7 @@
import io.github.ascopes.jct.compilers.JctCompiler;
import io.github.ascopes.jct.integration.AbstractIntegrationTest;
+import io.github.ascopes.jct.junit.EcjCompilerTest;
import io.github.ascopes.jct.junit.JavacCompilerTest;
import io.github.ascopes.jct.workspaces.PathStrategy;
import io.github.ascopes.jct.workspaces.Workspaces;
@@ -34,6 +35,7 @@
class BasicMultiModuleCompilationIntegrationTest extends AbstractIntegrationTest {
@DisplayName("I can compile a single module using multi-module layout using a RAM disk")
+ @EcjCompilerTest(minVersion = 9)
@JavacCompilerTest(minVersion = 9)
void singleModuleInMultiModuleLayoutRamDisk(JctCompiler compiler) {
try (var workspace = Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES)) {
@@ -62,6 +64,7 @@ void singleModuleInMultiModuleLayoutRamDisk(JctCompiler compiler) {
}
@DisplayName("I can compile a single module using multi-module layout using a temp directory")
+ @EcjCompilerTest(minVersion = 9)
@JavacCompilerTest(minVersion = 9)
void singleModuleInMultiModuleLayoutTempDirectory(JctCompiler compiler) {
try (var workspace = Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES)) {
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/CompilingSpecificClassesIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/CompilingSpecificClassesIntegrationTest.java
index c73f7f61e..dffae5409 100644
--- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/CompilingSpecificClassesIntegrationTest.java
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/CompilingSpecificClassesIntegrationTest.java
@@ -19,6 +19,7 @@
import io.github.ascopes.jct.compilers.JctCompiler;
import io.github.ascopes.jct.integration.AbstractIntegrationTest;
+import io.github.ascopes.jct.junit.EcjCompilerTest;
import io.github.ascopes.jct.junit.JavacCompilerTest;
import io.github.ascopes.jct.workspaces.Workspaces;
import org.junit.jupiter.api.DisplayName;
@@ -32,6 +33,7 @@
class CompilingSpecificClassesIntegrationTest extends AbstractIntegrationTest {
@DisplayName("Only the classes that I specify get compiled")
+ @EcjCompilerTest
@JavacCompilerTest
void onlyTheClassesSpecifiedGetCompiled(JctCompiler compiler) {
try (var workspace = Workspaces.newWorkspace()) {
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/MultiTieredCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/MultiTieredCompilationIntegrationTest.java
index d46aa0bcf..86c1bbc06 100644
--- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/MultiTieredCompilationIntegrationTest.java
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/integration/compilation/MultiTieredCompilationIntegrationTest.java
@@ -19,6 +19,7 @@
import io.github.ascopes.jct.compilers.JctCompiler;
import io.github.ascopes.jct.integration.AbstractIntegrationTest;
+import io.github.ascopes.jct.junit.EcjCompilerTest;
import io.github.ascopes.jct.junit.JavacCompilerTest;
import io.github.ascopes.jct.workspaces.Workspaces;
import org.junit.jupiter.api.DisplayName;
@@ -35,6 +36,7 @@ class MultiTieredCompilationIntegrationTest extends AbstractIntegrationTest {
@DisplayName(
"I can compile sources to classes and provide them in the classpath to a second compilation"
)
+ @EcjCompilerTest
@JavacCompilerTest
void compileSourcesToClassesAndProvideThemInClassPathToSecondCompilation(
JctCompiler compiler
@@ -81,6 +83,7 @@ void compileSourcesToClassesAndProvideThemInClassPathToSecondCompilation(
"I can compile sources to classes and provide them in the classpath to a second "
+ "compilation within a JAR"
)
+ @EcjCompilerTest
@JavacCompilerTest
void compileSourcesToClassesAndProvideThemInClassPathToSecondCompilationWithinJar(
JctCompiler compiler
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/junit/EcjCompilersProviderTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/junit/EcjCompilersProviderTest.java
new file mode 100644
index 000000000..ca3b5a2ec
--- /dev/null
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/junit/EcjCompilersProviderTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2022 - 2025, the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.github.ascopes.jct.junit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+import static org.junit.jupiter.params.support.AnnotationConsumerInitializer.initialize;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+import io.github.ascopes.jct.compilers.JctCompilerConfigurer;
+import io.github.ascopes.jct.compilers.impl.EcjJctCompilerImpl;
+import java.lang.reflect.AnnotatedElement;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+/**
+ * {@link EcjCompilersProvider} tests.
+ */
+@DisplayName("EcjCompilersProvider tests")
+class EcjCompilersProviderTest {
+
+ @DisplayName("Provider uses the user-provided compiler version bounds when valid")
+ @Test
+ void providerUsesTheUserProvidedVersionRangesWhenValid() {
+ // Given
+ try (var ecjMock = mockStatic(EcjJctCompilerImpl.class)) {
+ ecjMock.when(EcjJctCompilerImpl::getEarliestSupportedVersionInt).thenReturn(8);
+ ecjMock.when(EcjJctCompilerImpl::getLatestSupportedVersionInt).thenReturn(17);
+ var annotation = someAnnotation(10, 15);
+ var test = someAnnotatedElement(annotation);
+ var context = mock(ExtensionContext.class);
+
+ // When
+ var consumer = initialize(test, new EcjCompilersProvider());
+ var compilers = consumer.provideArguments(context)
+ .map(args -> (EcjJctCompilerImpl) args.get()[0])
+ .toList();
+
+ // Then
+ assertThat(compilers)
+ .as("compilers that were initialised (%s)", compilers)
+ .hasSize(6);
+
+ assertSoftly(softly -> {
+ for (var i = 0; i < compilers.size(); ++i) {
+ var compiler = compilers.get(i);
+ softly.assertThat(compiler.getName())
+ .as("compilers[%d].getName()", i)
+ .isEqualTo("ECJ (release = Java %d)", 10 + i);
+ softly.assertThat(compiler.getRelease())
+ .as("compilers[%d].getRelease()", i)
+ .isEqualTo("%d", 10 + i);
+ }
+ });
+ }
+ }
+
+ @DisplayName("Provider uses the minimum compiler version that is allowed if exceeded")
+ @Test
+ void providerUsesTheMinCompilerVersionAllowedIfExceeded() {
+ // Given
+ try (var ecjMock = mockStatic(EcjJctCompilerImpl.class)) {
+ ecjMock.when(EcjJctCompilerImpl::getEarliestSupportedVersionInt).thenReturn(8);
+ ecjMock.when(EcjJctCompilerImpl::getLatestSupportedVersionInt).thenReturn(17);
+ var annotation = someAnnotation(1, 15);
+ var test = someAnnotatedElement(annotation);
+ var context = mock(ExtensionContext.class);
+
+ // When
+ var consumer = initialize(test, new EcjCompilersProvider());
+ var compilers = consumer.provideArguments(context)
+ .map(args -> (EcjJctCompilerImpl) args.get()[0])
+ .toList();
+
+ // Then
+ assertThat(compilers)
+ .as("compilers that were initialised (%s)", compilers)
+ .hasSize(8);
+
+ assertSoftly(softly -> {
+ for (var i = 0; i < compilers.size(); ++i) {
+ var compiler = compilers.get(i);
+ softly.assertThat(compiler.getName())
+ .as("compilers[%d].getName()", i)
+ .isEqualTo("ECJ (release = Java %d)", 8 + i);
+ softly.assertThat(compiler.getRelease())
+ .as("compilers[%d].getRelease()", i)
+ .isEqualTo("%d", 8 + i);
+ }
+ });
+ }
+ }
+
+ @DisplayName("Provider uses the maximum compiler version that is allowed if exceeded")
+ @Test
+ void providerUsesTheMaxCompilerVersionAllowedIfExceeded() {
+ // Given
+ try (var ecjMock = mockStatic(EcjJctCompilerImpl.class)) {
+ ecjMock.when(EcjJctCompilerImpl::getEarliestSupportedVersionInt).thenReturn(8);
+ ecjMock.when(EcjJctCompilerImpl::getLatestSupportedVersionInt).thenReturn(17);
+ var annotation = someAnnotation(10, 17);
+ var test = someAnnotatedElement(annotation);
+ var context = mock(ExtensionContext.class);
+
+ // When
+ var consumer = initialize(test, new EcjCompilersProvider());
+ var compilers = consumer.provideArguments(context)
+ .map(args -> (EcjJctCompilerImpl) args.get()[0])
+ .toList();
+
+ // Then
+ assertThat(compilers)
+ .as("compilers that were initialised (%s)", compilers)
+ .hasSize(8);
+
+ assertSoftly(softly -> {
+ for (var i = 0; i < compilers.size(); ++i) {
+ var compiler = compilers.get(i);
+ softly.assertThat(compiler.getName())
+ .as("compilers[%d].getName()", i)
+ .isEqualTo("ECJ (release = Java %d)", 10 + i);
+ softly.assertThat(compiler.getRelease())
+ .as("compilers[%d].getRelease()", i)
+ .isEqualTo("%d", 10 + i);
+ }
+ });
+ }
+ }
+
+ @SafeVarargs
+ final EcjCompilerTest someAnnotation(
+ int min,
+ int max,
+ Class extends JctCompilerConfigurer>>... configurers
+ ) {
+ var annotation = mock(EcjCompilerTest.class);
+ when(annotation.minVersion()).thenReturn(min);
+ when(annotation.maxVersion()).thenReturn(max);
+ when(annotation.configurers()).thenReturn(configurers);
+ when(annotation.versionStrategy()).thenReturn(VersionStrategy.RELEASE);
+ when(annotation.annotationType()).thenAnswer(ctx -> EcjCompilerTest.class);
+ return annotation;
+ }
+
+ AnnotatedElement someAnnotatedElement(EcjCompilerTest annotation) {
+ var element = mock(AnnotatedElement.class);
+ when(element.getDeclaredAnnotation(EcjCompilerTest.class)).thenReturn(annotation);
+ return element;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 64c43148c..625c9084b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,14 @@
https://github.com/ascopes
+
+
+ eclipse-snapshots
+ Eclipse Snapshots
+ https://repo.eclipse.org/content/repositories/eclipse-snapshots
+
+
+
https://github.com/ascopes/java-compiler-testing
scm:git:https://github.com/ascopes/java-compiler-testing
@@ -96,6 +104,7 @@
4.0.0-M1
4.3.0
+ 3.41.0-SNAPSHOT
1.4.0
1.0.0
5.12.2
@@ -126,6 +135,7 @@
true
+ INFO
+ org.eclipse.jdt
+ ecj
+ ${ecj.version}
+