();
+
+ while (clazz != null) {
+ for (var field : clazz.getDeclaredFields()) {
+ if (isWorkspaceField(field) && !Modifier.isStatic(field.getModifiers())) {
+ fields.add(field);
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+
+ return fields;
+ }
+
+ private boolean isWorkspaceField(Field field) {
+ return field.getType().equals(Workspace.class)
+ && field.isAnnotationPresent(Managed.class);
+ }
+
+ private void initWorkspaceForField(Field field, @Nullable Object instance) throws Exception {
+ LOGGER
+ .atTrace()
+ .setMessage("Initialising workspace for field in {}: {} {} on instance {}")
+ .addArgument(() -> field.getDeclaringClass().getSimpleName())
+ .addArgument(() -> field.getType().getSimpleName())
+ .addArgument(field::getName)
+ .addArgument(instance)
+ .log();
+
+ field.setAccessible(true);
+ var managedWorkspace = field.getAnnotation(Managed.class);
+ var workspace = Workspaces.newWorkspace(managedWorkspace.pathStrategy());
+ field.set(instance, workspace);
+ }
+
+ private void closeWorkspaceForField(Field field, @Nullable Object instance) throws Exception {
+ LOGGER
+ .atTrace()
+ .setMessage("Closing workspace for field in {}: {} {} on instance {}")
+ .addArgument(() -> field.getDeclaringClass().getSimpleName())
+ .addArgument(() -> field.getType().getSimpleName())
+ .addArgument(field::getName)
+ .addArgument(instance)
+ .log();
+
+ field.setAccessible(true);
+ ((Workspace) field.get(instance)).close();
+ }
+}
diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/Managed.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/Managed.java
new file mode 100644
index 000000000..c47428805
--- /dev/null
+++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/Managed.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 - 2023, 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.workspaces.PathStrategy;
+import io.github.ascopes.jct.workspaces.Workspace;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.apiguardian.api.API;
+import org.apiguardian.api.API.Status;
+
+/**
+ * Annotation for a {@link Workspace} field in a test class. This will ensure it gets
+ * initialised and closed correctly between tests.
+ *
+ * Use static-fields to keep a workspace object alive for the duration of all the tests
+ * (providing the same semantics as initialising and closing resources using the
+ * {@link org.junit.jupiter.api.BeforeAll} and {@link org.junit.jupiter.api.AfterAll}
+ * annotations).
+ *
+ * You must extend your test class with the {@link JctExtension} extension for this annotation
+ * to be detected and handled.
+ *
+ *
Example usage:
+ *
+ *
+ * {@literal @ExtendWith(JctExtension.class)}
+ * class MyTest {
+ * {@literal @Managed}
+ * Workspace workspace;
+ *
+ * {@literal @JavacCompilerTest}
+ * void myTest(JctCompiler<?, ?> compiler) {
+ * ...
+ * var compilation = compiler.compile(workspace);
+ * ...
+ * }
+ * }
+ *
+ *
+ * @author Ashley Scopes
+ * @since 0.4.0
+ */
+@API(since = "0.4.0", status = Status.STABLE)
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Managed {
+
+ /**
+ * Get the path strategy to use for the workspace.
+ *
+ * @return the path strategy to use.
+ */
+ PathStrategy pathStrategy() default PathStrategy.RAM_DIRECTORIES;
+}
diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/workspaces/Workspace.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/workspaces/Workspace.java
index b23967fa8..7b2f8a619 100644
--- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/workspaces/Workspace.java
+++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/workspaces/Workspace.java
@@ -103,6 +103,15 @@ public interface Workspace extends AutoCloseable {
@Override
void close();
+ /**
+ * Determine if the workspace is closed or not.
+ *
+ * @return {@code true} if closed, {@code false} if open.
+ * @since 0.4.0
+ */
+ @API(since = "0.4.0", status = Status.STABLE)
+ boolean isClosed();
+
///
/// Accessor operations
///
diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/workspaces/impl/WorkspaceImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/workspaces/impl/WorkspaceImpl.java
index b2e7d04f1..952bd8336 100644
--- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/workspaces/impl/WorkspaceImpl.java
+++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/workspaces/impl/WorkspaceImpl.java
@@ -47,39 +47,50 @@
@API(since = "0.0.1", status = Status.INTERNAL)
public final class WorkspaceImpl implements Workspace {
+ private volatile boolean closed;
private final PathStrategy pathStrategy;
private final Map> paths;
public WorkspaceImpl(PathStrategy pathStrategy) {
+ closed = false;
this.pathStrategy = requireNonNull(pathStrategy, "pathStrategy");
paths = new HashMap<>();
}
@Override
public void close() {
- // Close everything in a best-effort fashion.
- var exceptions = new ArrayList();
-
- for (var list : paths.values()) {
- for (var path : list) {
- if (path instanceof AbstractManagedDirectory) {
- try {
- ((AbstractManagedDirectory) path).close();
-
- } catch (Exception ex) {
- exceptions.add(ex);
+ try {
+ // Close everything in a best-effort fashion.
+ var exceptions = new ArrayList();
+
+ for (var list : paths.values()) {
+ for (var path : list) {
+ if (path instanceof AbstractManagedDirectory) {
+ try {
+ ((AbstractManagedDirectory) path).close();
+
+ } catch (Exception ex) {
+ exceptions.add(ex);
+ }
}
}
}
- }
- if (exceptions.size() > 0) {
- var newEx = new IllegalStateException("One or more components failed to close");
- exceptions.forEach(newEx::addSuppressed);
- throw newEx;
+ if (exceptions.size() > 0) {
+ var newEx = new IllegalStateException("One or more components failed to close");
+ exceptions.forEach(newEx::addSuppressed);
+ throw newEx;
+ }
+ } finally {
+ closed = true;
}
}
+ @Override
+ public boolean isClosed() {
+ return closed;
+ }
+
@Override
public void addPackage(Location location, Path path) {
requireNonNull(location, "location");
diff --git a/java-compiler-testing/src/main/java/module-info.java b/java-compiler-testing/src/main/java/module-info.java
index b70cdd32e..6fa9566c4 100644
--- a/java-compiler-testing/src/main/java/module-info.java
+++ b/java-compiler-testing/src/main/java/module-info.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
+import io.github.ascopes.jct.junit.JctExtension;
import io.github.ascopes.jct.workspaces.RamFileSystemProvider;
+import org.junit.jupiter.api.extension.Extension;
/**
* A framework for performing exhaustive integration testing against Java compilers in modern Java
@@ -101,6 +103,7 @@
requires static transitive org.apiguardian.api;
requires org.assertj.core;
requires static org.jspecify;
+ requires static transitive org.junit.jupiter.api;
requires static transitive org.junit.jupiter.params;
requires org.slf4j;
@@ -123,6 +126,7 @@
/// SERVICE PROVIDER INTERFACES ///
///////////////////////////////////
+ provides Extension with JctExtension;
uses RamFileSystemProvider;
//////////////////////////////////////////////////////
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/integration/JctExtensionIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/integration/JctExtensionIntegrationTest.java
new file mode 100644
index 000000000..c1baf698f
--- /dev/null
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/integration/JctExtensionIntegrationTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2022 - 2023, 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.tests.integration;
+
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import io.github.ascopes.jct.junit.JctExtension;
+import io.github.ascopes.jct.junit.Managed;
+import io.github.ascopes.jct.workspaces.PathStrategy;
+import io.github.ascopes.jct.workspaces.Workspace;
+import io.github.ascopes.jct.workspaces.Workspaces;
+import java.util.ArrayList;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.platform.testkit.engine.EngineTestKit;
+
+/**
+ * Integration tests for {@link JctExtension}.
+ *
+ * @author Ashley Scopes
+ */
+@DisplayName("JctExtension integration tests")
+class JctExtensionIntegrationTest {
+
+ @DisplayName("Static workspaces are initialized and closed for all tests")
+ @Test
+ void staticWorkspacesAreInitializedAndClosedOnceForAllTests() {
+ var workspace = mock(Workspace.class);
+
+ try (var workspacesMock = mockStatic(Workspaces.class)) {
+ workspacesMock.when(() -> Workspaces.newWorkspace(any())).thenReturn(workspace);
+
+ var results = testKit()
+ .selectors(selectClass(StaticLifecycleTestCase.class))
+ .execute();
+
+ assertThat(results.testEvents().succeeded().count()).isEqualTo(7);
+ workspacesMock.verify(() -> Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES));
+ verify(workspace).close();
+ workspacesMock.verifyNoMoreInteractions();
+ verifyNoMoreInteractions(workspace);
+ }
+ }
+
+ @Disabled("This is just test data")
+ @ExtendWith(JctExtension.class)
+ static class StaticLifecycleTestCase {
+
+ @Managed
+ static Workspace workspace;
+
+ @Test
+ void testWorkspaceIsInitialised() {
+ assertThat(workspace).isNotNull();
+ }
+
+ @Test
+ void testWorkspaceIsInitialisedAgain() {
+ assertThat(workspace).isNotNull();
+ }
+
+ @RepeatedTest(5)
+ void testWorkspaceIsInitialisedRepeatedly() {
+ assertThat(workspace).isNotNull();
+ }
+ }
+
+ @DisplayName("Instance workspaces are initialized and closed for each test case")
+ @Test
+ void instanceWorkspacesAreInitializedAndClosedForEachTest() {
+ var workspaces = new ArrayList();
+
+ try (var workspacesMock = mockStatic(Workspaces.class)) {
+ workspacesMock.when(() -> Workspaces.newWorkspace(any()))
+ .then(ctx -> {
+ var workspace = mock(Workspace.class);
+ workspaces.add(workspace);
+ return workspace;
+ });
+
+ var results = testKit()
+ .selectors(selectClass(InstanceLifecycleTestCase.class))
+ .execute();
+
+ assertThat(results.testEvents().succeeded().count()).isEqualTo(7);
+ workspacesMock.verify(() -> Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES), times(21));
+ workspacesMock.verifyNoMoreInteractions();
+
+ assertThat(workspaces)
+ .hasSize(21)
+ .allSatisfy(ws -> verify(ws).close())
+ .allSatisfy(ws -> verifyNoMoreInteractions(ws));
+ }
+ }
+
+ @Disabled("This is just test data")
+ @ExtendWith(JctExtension.class)
+ static class InstanceLifecycleTestCase {
+
+ @Managed
+ Workspace workspace1;
+
+ @Managed
+ Workspace workspace2;
+
+ @Managed
+ Workspace workspace3;
+
+ @Test
+ void testWorkspaceIsInitialised() {
+ assertThat(workspace1).isNotNull();
+ assertThat(workspace2).isNotNull();
+ assertThat(workspace3).isNotNull();
+ }
+
+ @Test
+ void testWorkspaceIsInitialisedAgain() {
+ assertThat(workspace1).isNotNull();
+ assertThat(workspace2).isNotNull();
+ assertThat(workspace3).isNotNull();
+ }
+
+ @RepeatedTest(5)
+ void testWorkspaceIsInitialisedRepeatedly() {
+ assertThat(workspace1).isNotNull();
+ assertThat(workspace2).isNotNull();
+ assertThat(workspace3).isNotNull();
+ }
+ }
+
+ @DisplayName("Instance workspaces are initialized and closed for each parameterized test case")
+ @Test
+ void instanceWorkspacesAreInitializedAndClosedForEachParameterizedTest() {
+ var workspaces = new ArrayList();
+
+ try (var workspacesMock = mockStatic(Workspaces.class)) {
+ workspacesMock.when(() -> Workspaces.newWorkspace(any()))
+ .then(ctx -> {
+ var workspace = mock(Workspace.class);
+ workspaces.add(workspace);
+ return workspace;
+ });
+
+ var results = testKit()
+ .selectors(selectClass(ParameterizedLifecycleTestCase.class))
+ .execute();
+
+ assertThat(results.testEvents().succeeded().count()).isEqualTo(10);
+ workspacesMock.verify(() -> Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES), times(10));
+ workspacesMock.verifyNoMoreInteractions();
+
+ assertThat(workspaces)
+ .hasSize(10)
+ .allSatisfy(ws -> verify(ws).close())
+ .allSatisfy(ws -> verifyNoMoreInteractions(ws));
+ }
+ }
+
+ @Disabled("This is just test data")
+ @ExtendWith(JctExtension.class)
+ static class ParameterizedLifecycleTestCase {
+
+ @Managed
+ Workspace workspace;
+
+ @ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
+ @ParameterizedTest
+ void testWorkspaceIsInitialised(int iteration) {
+ assertThat(workspace)
+ .as("workspace " + workspace + " for iteration " + iteration)
+ .isNotNull();
+ }
+ }
+
+ @DisplayName("Instance workspaces are initialized and closed once for test factories")
+ @Test
+ void instanceWorkspacesAreInitializedAndClosedOnceForTestFactories() {
+ var workspaces = new ArrayList();
+
+ try (var workspacesMock = mockStatic(Workspaces.class)) {
+ workspacesMock.when(() -> Workspaces.newWorkspace(any()))
+ .then(ctx -> {
+ var workspace = mock(Workspace.class);
+ workspaces.add(workspace);
+ return workspace;
+ });
+
+ var results = testKit()
+ .selectors(selectClass(DynamicLifecycleTestCase.class))
+ .execute();
+
+ assertThat(results.testEvents().succeeded().count()).isEqualTo(10);
+ workspacesMock.verify(() -> Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES), times(1));
+ workspacesMock.verifyNoMoreInteractions();
+
+ assertThat(workspaces)
+ .hasSize(1)
+ .allSatisfy(ws -> verify(ws).close())
+ .allSatisfy(ws -> verifyNoMoreInteractions(ws));
+ }
+ }
+
+ @Disabled("This is just test data")
+ @ExtendWith(JctExtension.class)
+ static class DynamicLifecycleTestCase {
+
+ @Managed
+ Workspace workspace;
+
+ @TestFactory
+ Stream testWorkspaceIsInitialised() {
+ return IntStream
+ .rangeClosed(1, 10)
+ .mapToObj(iteration -> dynamicTest("for iteration " + iteration, () -> {
+ assertThat(workspace)
+ .as("workspace " + workspace + " for iteration " + iteration)
+ .isNotNull();
+ }));
+ }
+ }
+
+ @DisplayName("Explicit path strategies for workspaces are handled")
+ @Test
+ void explicitPathStrategiesAreHandled() {
+ try (var workspacesMock = mockStatic(Workspaces.class)) {
+ workspacesMock.when(() -> Workspaces.newWorkspace(any())).thenCallRealMethod();
+
+ var results = testKit()
+ .selectors(selectClass(CustomPathStrategyTestCase.class))
+ .execute();
+
+ assertThat(results.testEvents().succeeded().count()).isEqualTo(1);
+ workspacesMock
+ .verify(() -> Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES), times(2));
+ workspacesMock
+ .verify(() -> Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES), times(2));
+ workspacesMock.verifyNoMoreInteractions();
+ }
+ }
+
+ @Disabled("This is just test data")
+ @ExtendWith(JctExtension.class)
+ static class CustomPathStrategyTestCase {
+
+ @Managed(pathStrategy = PathStrategy.RAM_DIRECTORIES)
+ static Workspace staticRamDirectoriesWorkspace;
+
+ @Managed(pathStrategy = PathStrategy.TEMP_DIRECTORIES)
+ static Workspace staticTempDirectoriesWorkspace;
+
+ @Managed(pathStrategy = PathStrategy.RAM_DIRECTORIES)
+ Workspace ramDirectoriesWorkspace;
+
+ @Managed(pathStrategy = PathStrategy.TEMP_DIRECTORIES)
+ Workspace tempDirectoriesWorkspace;
+
+ @Test
+ void testWorkspaceIsInitialisedWithCorrectPathStrategy() {
+ assertThat(staticRamDirectoriesWorkspace.getPathStrategy())
+ .isSameAs(PathStrategy.RAM_DIRECTORIES);
+ assertThat(staticTempDirectoriesWorkspace.getPathStrategy())
+ .isSameAs(PathStrategy.TEMP_DIRECTORIES);
+ assertThat(ramDirectoriesWorkspace.getPathStrategy())
+ .isSameAs(PathStrategy.RAM_DIRECTORIES);
+ assertThat(tempDirectoriesWorkspace.getPathStrategy())
+ .isSameAs(PathStrategy.TEMP_DIRECTORIES);
+ }
+ }
+
+ private static EngineTestKit.Builder testKit() {
+ return EngineTestKit.engine("junit-jupiter")
+ .configurationParameter("junit.jupiter.conditions.deactivate", "*DisabledCondition");
+ }
+}
diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/junit/JctExtensionTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/junit/JctExtensionTest.java
new file mode 100644
index 000000000..bd3dc6ff2
--- /dev/null
+++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/junit/JctExtensionTest.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2022 - 2023, 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.tests.unit.junit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import io.github.ascopes.jct.junit.JctExtension;
+import io.github.ascopes.jct.junit.Managed;
+import io.github.ascopes.jct.workspaces.PathStrategy;
+import io.github.ascopes.jct.workspaces.Workspace;
+import io.github.ascopes.jct.workspaces.Workspaces;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.TestInstances;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.junit.jupiter.api.parallel.Isolated;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * {@link JctExtension} tests.
+ *
+ * @author Ashley Scopes
+ */
+@DisplayName("JctExtension tests")
+@Execution(ExecutionMode.SAME_THREAD)
+@ExtendWith(MockitoExtension.class)
+@Isolated("This modifies global state in some test cases")
+class JctExtensionTest {
+ @Mock
+ ExtensionContext extensionContext;
+
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ MockedStatic workspacesMock;
+
+ JctExtension extension;
+
+ @BeforeEach
+ void setUp() {
+ extension = new JctExtension();
+ }
+
+ @DisplayName("The beforeAll hook initialises annotated static workspace fields")
+ @Test
+ void beforeAllHookInitialisesAnnotatedStaticWorkspaceFields() {
+ // Given
+ StaticWorkspaceTestCase.staticWorkspace1 = null;
+ StaticWorkspaceTestCase.staticWorkspace2 = null;
+ StaticWorkspaceTestCase.staticWorkspace3 = null;
+ StaticWorkspaceTestCase.someInvalidStaticWorkspace = null;
+ StaticWorkspaceTestCase.someIgnoredStaticWorkspace = null;
+
+ var expectedWorkspace1 = mock(Workspace.class);
+ var expectedWorkspace2 = mock(Workspace.class);
+ var expectedWorkspace3 = mock(Workspace.class);
+ workspacesMock.when(() -> Workspaces.newWorkspace(any()))
+ .thenReturn(expectedWorkspace1, expectedWorkspace2, expectedWorkspace3);
+ when(extensionContext.getRequiredTestClass()).thenAnswer(ctx -> StaticWorkspaceTestCase.class);
+
+ // When
+ assertThatCode(() -> extension.beforeAll(extensionContext))
+ .doesNotThrowAnyException();
+
+ // Then
+ workspacesMock.verify(() -> Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES), times(2));
+ workspacesMock.verify(() -> Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES));
+ workspacesMock.verifyNoMoreInteractions();
+
+ assertThat(List.of(
+ StaticWorkspaceTestCase.staticWorkspace1,
+ StaticWorkspaceTestCase.staticWorkspace2,
+ StaticWorkspaceTestCase.staticWorkspace3
+ )).containsExactlyInAnyOrder(expectedWorkspace1, expectedWorkspace2, expectedWorkspace3);
+
+ assertThat(StaticWorkspaceTestCase.someInvalidStaticWorkspace).isNull();
+ assertThat(StaticWorkspaceTestCase.someIgnoredStaticWorkspace).isNull();
+ }
+
+ @DisplayName("The afterAll hook closes annotated static workspace fields")
+ @Test
+ void afterAllHookClosesAnnotatedStaticWorkspaceFields() {
+ // Given
+ StaticWorkspaceTestCase.staticWorkspace1 = mock(Workspace.class);
+ StaticWorkspaceTestCase.staticWorkspace2 = mock(Workspace.class);
+ StaticWorkspaceTestCase.staticWorkspace3 = mock(Workspace.class);
+ StaticWorkspaceTestCase.someInvalidStaticWorkspace = mock(Workspace.class);
+ StaticWorkspaceTestCase.someIgnoredStaticWorkspace = mock();
+
+ when(extensionContext.getRequiredTestClass()).thenAnswer(ctx -> StaticWorkspaceTestCase.class);
+
+ // When
+ assertThatCode(() -> extension.afterAll(extensionContext))
+ .doesNotThrowAnyException();
+
+ // Then
+ verify(StaticWorkspaceTestCase.staticWorkspace1).close();
+ verifyNoMoreInteractions(StaticWorkspaceTestCase.staticWorkspace1);
+ verify(StaticWorkspaceTestCase.staticWorkspace2).close();
+ verifyNoMoreInteractions(StaticWorkspaceTestCase.staticWorkspace2);
+ verify(StaticWorkspaceTestCase.staticWorkspace3).close();
+ verifyNoMoreInteractions(StaticWorkspaceTestCase.staticWorkspace3);
+ verifyNoInteractions(StaticWorkspaceTestCase.someInvalidStaticWorkspace);
+ verifyNoInteractions(StaticWorkspaceTestCase.someIgnoredStaticWorkspace);
+ }
+
+ @Disabled("This is just test data")
+ static class StaticWorkspaceTestCase {
+ @Managed
+ static Workspace staticWorkspace1;
+
+ @Managed(pathStrategy = PathStrategy.RAM_DIRECTORIES)
+ static Workspace staticWorkspace2;
+
+ @Managed(pathStrategy = PathStrategy.TEMP_DIRECTORIES)
+ static Workspace staticWorkspace3;
+
+ // These all get ignored because they don't match the typing/annotation criteria.
+ static Workspace someIgnoredStaticWorkspace;
+
+ @Managed
+ static Object someInvalidStaticWorkspace;
+
+ // These should be ignored because they are not static, so no instance exists to apply them on.
+ @Managed
+ Object someInvalidInstanceWorkspace;
+
+ @Managed
+ Workspace someIgnoredInstanceWorkspace;
+
+ @Test
+ void testSomething() {
+ // ...
+ }
+
+ @Test
+ void testSomethingAgain() {
+ // ...
+ }
+ }
+
+ @DisplayName("The beforeEach hook initialises annotated instance workspace fields")
+ @Test
+ void beforeEachHookInitialisesAnnotatedInstanceWorkspaceFields() {
+ // Given
+ var instance1 = new InstanceWorkspaceTestCase();
+ var instance2 = new InstanceWorkspaceTestCase();
+ var instance3 = new InstanceWorkspaceTestCase();
+ var testInstances = mock(TestInstances.class);
+ when(testInstances.getAllInstances()).thenReturn(List.of(instance1, instance2, instance3));
+
+ var expectedWorkspace1 = mock(Workspace.class);
+ var expectedWorkspace2 = mock(Workspace.class);
+ var expectedWorkspace3 = mock(Workspace.class);
+ var expectedWorkspace4 = mock(Workspace.class);
+ var expectedWorkspace5 = mock(Workspace.class);
+ var expectedWorkspace6 = mock(Workspace.class);
+ var expectedWorkspace7 = mock(Workspace.class);
+ var expectedWorkspace8 = mock(Workspace.class);
+ var expectedWorkspace9 = mock(Workspace.class);
+ workspacesMock.when(() -> Workspaces.newWorkspace(any()))
+ .thenReturn(
+ expectedWorkspace1, expectedWorkspace2, expectedWorkspace3, expectedWorkspace4,
+ expectedWorkspace5, expectedWorkspace6, expectedWorkspace7, expectedWorkspace8,
+ expectedWorkspace9
+ );
+ when(extensionContext.getRequiredTestInstances())
+ .thenReturn(testInstances);
+
+ // When
+ assertThatCode(() -> extension.beforeEach(extensionContext))
+ .doesNotThrowAnyException();
+
+ // Then
+ workspacesMock.verify(() -> Workspaces.newWorkspace(PathStrategy.RAM_DIRECTORIES), times(6));
+ workspacesMock.verify(() -> Workspaces.newWorkspace(PathStrategy.TEMP_DIRECTORIES), times(3));
+ workspacesMock.verifyNoMoreInteractions();
+
+ assertThat(List.of(
+ instance1.workspace1,
+ instance1.workspace2,
+ instance1.workspace3
+ )).containsExactlyInAnyOrder(expectedWorkspace1, expectedWorkspace2, expectedWorkspace3);
+
+ assertThat(List.of(
+ instance2.workspace1,
+ instance2.workspace2,
+ instance2.workspace3
+ )).containsExactlyInAnyOrder(expectedWorkspace4, expectedWorkspace5, expectedWorkspace6);
+
+ assertThat(List.of(
+ instance3.workspace1,
+ instance3.workspace2,
+ instance3.workspace3
+ )).containsExactlyInAnyOrder(expectedWorkspace7, expectedWorkspace8, expectedWorkspace9);
+
+ assertThat(InstanceWorkspaceTestCase.someIgnoredStaticWorkspace).isNull();
+ assertThat(InstanceWorkspaceTestCase.someInvalidStaticWorkspace).isNull();
+ }
+
+ @DisplayName("The afterEach hook closes annotated instance workspace fields")
+ @Test
+ void afterEachHookClosesAnnotatedInstanceWorkspaceFields() {
+ // Given
+ InstanceWorkspaceTestCase.someIgnoredStaticWorkspace = mock();
+ InstanceWorkspaceTestCase.someInvalidStaticWorkspace = mock();
+ var instance1 = new InstanceWorkspaceTestCase();
+ var instance2 = new InstanceWorkspaceTestCase();
+ var instance3 = new InstanceWorkspaceTestCase();
+ var testInstances = mock(TestInstances.class);
+ when(testInstances.getAllInstances()).thenReturn(List.of(instance1, instance2, instance3));
+
+ instance1.workspace1 = mock();
+ instance1.workspace2 = mock();
+ instance1.workspace3 = mock();
+ instance1.someIgnoredInstanceWorkspace = mock();
+ instance1.someInvalidInstanceWorkspace = mock();
+ instance2.workspace1 = mock();
+ instance2.workspace2 = mock();
+ instance2.workspace3 = mock();
+ instance2.someIgnoredInstanceWorkspace = mock();
+ instance2.someInvalidInstanceWorkspace = mock();
+ instance3.workspace1 = mock();
+ instance3.workspace2 = mock();
+ instance3.workspace3 = mock();
+ instance3.someIgnoredInstanceWorkspace = mock();
+ instance3.someInvalidInstanceWorkspace = mock();
+
+ when(extensionContext.getRequiredTestInstances())
+ .thenReturn(testInstances);
+
+ // When
+ assertThatCode(() -> extension.afterEach(extensionContext))
+ .doesNotThrowAnyException();
+
+ // Then
+ verify(instance1.workspace1).close();
+ verifyNoMoreInteractions(instance1.workspace1);
+ verify(instance1.workspace2).close();
+ verifyNoMoreInteractions(instance1.workspace2);
+ verify(instance1.workspace3).close();
+ verifyNoMoreInteractions(instance1.workspace3);
+ verify(instance2.workspace1).close();
+ verifyNoMoreInteractions(instance2.workspace1);
+ verify(instance2.workspace2).close();
+ verifyNoMoreInteractions(instance2.workspace2);
+ verify(instance2.workspace3).close();
+ verifyNoMoreInteractions(instance2.workspace3);
+ verify(instance3.workspace1).close();
+ verifyNoMoreInteractions(instance3.workspace1);
+ verify(instance3.workspace2).close();
+ verifyNoMoreInteractions(instance3.workspace2);
+ verify(instance3.workspace3).close();
+ verifyNoMoreInteractions(instance3.workspace3);
+
+ verifyNoInteractions(instance1.someIgnoredInstanceWorkspace);
+ verifyNoInteractions(instance1.someInvalidInstanceWorkspace);
+ verifyNoInteractions(instance2.someIgnoredInstanceWorkspace);
+ verifyNoInteractions(instance2.someInvalidInstanceWorkspace);
+ verifyNoInteractions(instance3.someIgnoredInstanceWorkspace);
+ verifyNoInteractions(instance3.someInvalidInstanceWorkspace);
+ verifyNoInteractions(InstanceWorkspaceTestCase.someIgnoredStaticWorkspace);
+ verifyNoInteractions(InstanceWorkspaceTestCase.someInvalidStaticWorkspace);
+ verifyNoInteractions(InstanceWorkspaceTestCase.someIgnoredStaticWorkspace);
+ }
+
+ @Disabled(value = "This is just test data")
+ static class InstanceWorkspaceTestCase {
+ @Managed
+ Workspace workspace1;
+
+ @Managed(pathStrategy = PathStrategy.RAM_DIRECTORIES)
+ Workspace workspace2;
+
+ @Managed(pathStrategy = PathStrategy.TEMP_DIRECTORIES)
+ Workspace workspace3;
+
+ // These all get ignored because they don't match the typing/annotation criteria.
+ Workspace someIgnoredInstanceWorkspace;
+
+ @Managed
+ Object someInvalidInstanceWorkspace;
+
+ // These should be ignored because they are static.
+ @Managed
+ static Object someInvalidStaticWorkspace;
+
+ @Managed
+ static Workspace someIgnoredStaticWorkspace;
+
+ @Test
+ void testSomething() {
+ // ...
+ }
+
+ @Test
+ void testSomethingAgain() {
+ // ...
+ }
+ }
+
+ @DisplayName("The beforeEach hook will initialise workspaces in any superclasses")
+ @Test
+ void beforeEachHookWillInitialiseWorkspacesInAnySuperClasses() {
+ // Given
+ var instance = new TestCaseImpl();
+ var testInstances = mock(TestInstances.class);
+ when(testInstances.getAllInstances()).thenReturn(List.of(instance));
+ when(extensionContext.getRequiredTestInstances()).thenReturn(testInstances);
+
+ var expectedWorkspace1 = mock(Workspace.class);
+ var expectedWorkspace2 = mock(Workspace.class);
+ var expectedWorkspace3 = mock(Workspace.class);
+ var expectedWorkspace4 = mock(Workspace.class);
+
+ workspacesMock.when(() -> Workspaces.newWorkspace(any()))
+ .thenReturn(expectedWorkspace1, expectedWorkspace2, expectedWorkspace3, expectedWorkspace4);
+
+ // When
+ assertThatCode(() -> extension.beforeEach(extensionContext))
+ .doesNotThrowAnyException();
+
+ // Then
+ assertThat(instance.testCaseImplWorkspace).isSameAs(expectedWorkspace1);
+ assertThat(instance.testCaseBase3Workspace).isSameAs(expectedWorkspace2);
+ assertThat(instance.testCaseBase2Workspace).isSameAs(expectedWorkspace3);
+ assertThat(instance.testCaseBase1Workspace).isSameAs(expectedWorkspace4);
+ }
+
+ @DisplayName("The afterEach hook will close workspaces in any superclasses")
+ @Test
+ void afterEachHookWillCloseWorkspacesInAnySuperClasses() {
+ // Given
+ var instance = new TestCaseImpl();
+ var testInstances = mock(TestInstances.class);
+ when(testInstances.getAllInstances()).thenReturn(List.of(instance));
+ when(extensionContext.getRequiredTestInstances()).thenReturn(testInstances);
+
+ instance.testCaseImplWorkspace = mock();
+ instance.testCaseBase3Workspace = mock();
+ instance.testCaseBase2Workspace = mock();
+ instance.testCaseBase1Workspace = mock();
+
+ // When
+ assertThatCode(() -> extension.afterEach(extensionContext))
+ .doesNotThrowAnyException();
+
+ // Then
+ verify(instance.testCaseImplWorkspace).close();
+ verifyNoMoreInteractions(instance.testCaseImplWorkspace);
+ verify(instance.testCaseBase1Workspace).close();
+ verifyNoMoreInteractions(instance.testCaseBase1Workspace);
+ verify(instance.testCaseBase2Workspace).close();
+ verifyNoMoreInteractions(instance.testCaseBase2Workspace);
+ verify(instance.testCaseBase3Workspace).close();
+ verifyNoMoreInteractions(instance.testCaseBase3Workspace);
+ }
+
+ static class TestCaseBase1 {
+ @Managed
+ Workspace testCaseBase1Workspace;
+ }
+
+ static class TestCaseBase2 extends TestCaseBase1 {
+ @Managed
+ Workspace testCaseBase2Workspace;
+ }
+
+ static class TestCaseBase3 extends TestCaseBase2 {
+ @Managed
+ Workspace testCaseBase3Workspace;
+ }
+
+ static class TestCaseImpl extends TestCaseBase3 {
+ @Managed
+ Workspace testCaseImplWorkspace;
+ }
+}
diff --git a/java-compiler-testing/src/test/java/module-info.java b/java-compiler-testing/src/test/java/module-info.java
index 3dc1f89d3..93cb7fae7 100644
--- a/java-compiler-testing/src/test/java/module-info.java
+++ b/java-compiler-testing/src/test/java/module-info.java
@@ -27,8 +27,9 @@
requires transitive org.junit.jupiter.api;
requires transitive org.junit.jupiter.engine;
requires transitive org.junit.jupiter.params;
- requires transitive org.junit.platform.commons; // required to make IntelliJ happy.
- requires transitive org.junit.platform.engine; // required to make IntelliJ happy.
+ requires transitive org.junit.platform.commons;
+ requires transitive org.junit.platform.engine;
+ requires transitive org.junit.platform.testkit;
requires org.mockito;
requires org.mockito.junit.jupiter;
requires org.slf4j;
From 1957b7a48981dc4ab971c7311141f40c098eded3 Mon Sep 17 00:00:00 2001
From: Ash <73482956+ascopes@users.noreply.github.com>
Date: Sun, 19 Mar 2023 08:23:01 +0000
Subject: [PATCH 2/2] Fix doc typo in Managed.java
Signed-off-by: Ash <73482956+ascopes@users.noreply.github.com>
---
.../src/main/java/io/github/ascopes/jct/junit/Managed.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/Managed.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/Managed.java
index c47428805..095501d36 100644
--- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/Managed.java
+++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/Managed.java
@@ -45,7 +45,7 @@
* Workspace workspace;
*
* {@literal @JavacCompilerTest}
- * void myTest(JctCompiler<?, ?> compiler) {
+ * void myTest(JctCompiler<?, ?> compiler) {
* ...
* var compilation = compiler.compile(workspace);
* ...