diff --git a/README.md b/README.md index a4d1f6ca3..e07a20619 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ class ExampleTest { @DisplayName("I can compile a Hello World application") @JavacCompilerTest void canCompileHelloWorld(Compilable compiler) { - var sources = newRamFileSystem("src") + var sources = newRamDirectory("src") .createFile("org/example/Message.java").withContents( """ package org.example; @@ -83,7 +83,7 @@ class ExampleTest { @JavacCompilerTest(modules = true) void canCompileModuleUsingLombok(Compilable compiler) { // Given - var sources = newRamFileSystem("hello.world") + var sources = newRamDirectory("hello.world") .createFile("org/example/Message.java").withContents( """ package org.example; diff --git a/acceptance-tests/acceptance-tests-checkerframework/src/test/groovy/io/github/ascopes/jct/acceptancetests/checkerframework/CheckerNullTest.groovy b/acceptance-tests/acceptance-tests-checkerframework/src/test/groovy/io/github/ascopes/jct/acceptancetests/checkerframework/CheckerNullTest.groovy index e49077381..46d2d911e 100644 --- a/acceptance-tests/acceptance-tests-checkerframework/src/test/groovy/io/github/ascopes/jct/acceptancetests/checkerframework/CheckerNullTest.groovy +++ b/acceptance-tests/acceptance-tests-checkerframework/src/test/groovy/io/github/ascopes/jct/acceptancetests/checkerframework/CheckerNullTest.groovy @@ -15,7 +15,7 @@ */ package io.github.ascopes.jct.acceptancetests.checkerframework -import io.github.ascopes.jct.compilers.Compilable +import io.github.ascopes.jct.compilers.Compiler import io.github.ascopes.jct.junit.JavacCompilerTest import org.checkerframework.checker.nullness.NullnessChecker import org.junit.jupiter.api.BeforeEach @@ -25,7 +25,7 @@ import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory import static org.assertj.core.api.Assumptions.assumeThat @DisplayName("Checkerframework Nullness acceptance tests") @@ -43,9 +43,9 @@ class CheckerNullTest { @DisplayName("Happy paths work as expected") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest - void happyPathsWorkAsExpected(Compilable compiler) { + void happyPathsWorkAsExpected(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .createDirectory("org", "example") .copyContentsFrom("src", "test", "resources", "code", "nullness", "happy") @@ -63,9 +63,9 @@ class CheckerNullTest { @DisplayName("Sad paths fail as expected") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest - void sadPathsFailAsExpected(Compilable compiler) { + void sadPathsFailAsExpected(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .createDirectory("org", "example") .copyContentsFrom("src", "test", "resources", "code", "nullness", "sad") diff --git a/acceptance-tests/acceptance-tests-dagger/src/test/groovy/io/github/ascopes/jct/acceptancetests/dagger/DaggerTest.groovy b/acceptance-tests/acceptance-tests-dagger/src/test/groovy/io/github/ascopes/jct/acceptancetests/dagger/DaggerTest.groovy index 004a5a5d7..368c7b71f 100644 --- a/acceptance-tests/acceptance-tests-dagger/src/test/groovy/io/github/ascopes/jct/acceptancetests/dagger/DaggerTest.groovy +++ b/acceptance-tests/acceptance-tests-dagger/src/test/groovy/io/github/ascopes/jct/acceptancetests/dagger/DaggerTest.groovy @@ -15,20 +15,20 @@ */ package io.github.ascopes.jct.acceptancetests.dagger -import io.github.ascopes.jct.compilers.Compilable +import io.github.ascopes.jct.compilers.Compiler import io.github.ascopes.jct.junit.JavacCompilerTest import org.junit.jupiter.api.DisplayName import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory @DisplayName("Dagger acceptance tests") class DaggerTest { @DisplayName("Dagger DI runs as expected in the annotation processing phase") @JavacCompilerTest - void daggerDiRunsAsExpectedInTheAnnotationProcessingPhase(Compilable compiler) { + void daggerDiRunsAsExpectedInTheAnnotationProcessingPhase(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .createDirectory("org", "example") .copyContentsFrom("src", "test", "resources", "code") diff --git a/acceptance-tests/acceptance-tests-immutables/src/test/groovy/io/github/ascopes/jct/acceptancetests/immutables/ImmutablesIntegrationTest.groovy b/acceptance-tests/acceptance-tests-immutables/src/test/groovy/io/github/ascopes/jct/acceptancetests/immutables/ImmutablesIntegrationTest.groovy index d704bcd26..c53bc711d 100644 --- a/acceptance-tests/acceptance-tests-immutables/src/test/groovy/io/github/ascopes/jct/acceptancetests/immutables/ImmutablesIntegrationTest.groovy +++ b/acceptance-tests/acceptance-tests-immutables/src/test/groovy/io/github/ascopes/jct/acceptancetests/immutables/ImmutablesIntegrationTest.groovy @@ -15,7 +15,7 @@ */ package io.github.ascopes.jct.acceptancetests.immutables -import io.github.ascopes.jct.compilers.Compilable +import io.github.ascopes.jct.compilers.Compiler import io.github.ascopes.jct.junit.JavacCompilerTest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.parallel.Execution @@ -24,7 +24,7 @@ import org.junit.jupiter.api.parallel.ExecutionMode import javax.tools.StandardLocation import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory import static org.assertj.core.api.SoftAssertions.assertSoftly /** @@ -42,9 +42,9 @@ class ImmutablesIntegrationTest { @DisplayName("Immutables @Value produces the expected class") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest - void immutablesValueProducesTheExpectedClass(Compilable compiler) { + void immutablesValueProducesTheExpectedClass(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .rootDirectory() .copyContentsFrom("src", "test", "resources", "code", "flat") @@ -77,9 +77,9 @@ class ImmutablesIntegrationTest { @DisplayName("Immutables @Value produces the expected class for modules") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest(modules = true) - void immutablesValueProducesTheExpectedClassForModules(Compilable compiler) { + void immutablesValueProducesTheExpectedClassForModules(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .rootDirectory() .copyContentsFrom("src", "test", "resources", "code", "jpms") diff --git a/acceptance-tests/acceptance-tests-lombok/src/test/groovy/io/github/ascopes/jct/acceptancetests/lombok/LombokIntegrationTest.groovy b/acceptance-tests/acceptance-tests-lombok/src/test/groovy/io/github/ascopes/jct/acceptancetests/lombok/LombokIntegrationTest.groovy index e6d62c8b9..e26b42416 100644 --- a/acceptance-tests/acceptance-tests-lombok/src/test/groovy/io/github/ascopes/jct/acceptancetests/lombok/LombokIntegrationTest.groovy +++ b/acceptance-tests/acceptance-tests-lombok/src/test/groovy/io/github/ascopes/jct/acceptancetests/lombok/LombokIntegrationTest.groovy @@ -15,7 +15,7 @@ */ package io.github.ascopes.jct.acceptancetests.lombok -import io.github.ascopes.jct.compilers.Compilable +import io.github.ascopes.jct.compilers.Compiler import io.github.ascopes.jct.junit.JavacCompilerTest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.parallel.Execution @@ -25,7 +25,7 @@ import javax.tools.StandardLocation import java.nio.file.Path import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory import static org.assertj.core.api.SoftAssertions.assertSoftly /** @@ -43,9 +43,9 @@ class LombokIntegrationTest { @DisplayName("Lombok @Data compiles the expected data class") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest - void lombokDataCompilesTheExpectedDataClass(Compilable compiler) { + void lombokDataCompilesTheExpectedDataClass(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .rootDirectory() .copyContentsFrom("src", "test", "resources", "code", "flat") @@ -77,9 +77,9 @@ class LombokIntegrationTest { @DisplayName("Lombok @Data compiles the expected data class with module support") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest(modules = true) - void lombokDataCompilesTheExpectedDataClassWithModuleSupport(Compilable compiler) { + void lombokDataCompilesTheExpectedDataClassWithModuleSupport(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .rootDirectory() .copyContentsFrom("src", "test", "resources", "code", "jpms") diff --git a/acceptance-tests/acceptance-tests-mapstruct/src/test/groovy/io/github/ascopes/jct/acceptancetests/mapstruct/MapStructIntegrationTest.groovy b/acceptance-tests/acceptance-tests-mapstruct/src/test/groovy/io/github/ascopes/jct/acceptancetests/mapstruct/MapStructIntegrationTest.groovy index 47b3933be..6a227b1b5 100644 --- a/acceptance-tests/acceptance-tests-mapstruct/src/test/groovy/io/github/ascopes/jct/acceptancetests/mapstruct/MapStructIntegrationTest.groovy +++ b/acceptance-tests/acceptance-tests-mapstruct/src/test/groovy/io/github/ascopes/jct/acceptancetests/mapstruct/MapStructIntegrationTest.groovy @@ -15,7 +15,7 @@ */ package io.github.ascopes.jct.acceptancetests.mapstruct -import io.github.ascopes.jct.compilers.Compilable +import io.github.ascopes.jct.compilers.Compiler import io.github.ascopes.jct.junit.JavacCompilerTest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.parallel.Execution @@ -24,7 +24,7 @@ import org.junit.jupiter.api.parallel.ExecutionMode import javax.tools.StandardLocation import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory import static org.assertj.core.api.SoftAssertions.assertSoftly @DisplayName("MapStruct integration tests") @@ -34,9 +34,9 @@ class MapStructIntegrationTest { @DisplayName("MapStruct generates expected mapping code") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest - void mapStructGeneratesExpectedMappingCode(Compilable compiler) { + void mapStructGeneratesExpectedMappingCode(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .rootDirectory() .copyContentsFrom("src", "test", "resources", "code", "flat") @@ -75,9 +75,9 @@ class MapStructIntegrationTest { @DisplayName("MapStruct generates expected mapping code for modules") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest(modules = true) - void mapStructGeneratesExpectedMappingCodeForModules(Compilable compiler) { + void mapStructGeneratesExpectedMappingCodeForModules(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .rootDirectory() .copyContentsFrom("src", "test", "resources", "code", "jpms") diff --git a/acceptance-tests/acceptance-tests-serviceloader-jpms/src/test/groovy/io/github/ascopes/jct/acceptancetests/serviceloaderjpms/testing/ServiceProcessorTest.groovy b/acceptance-tests/acceptance-tests-serviceloader-jpms/src/test/groovy/io/github/ascopes/jct/acceptancetests/serviceloaderjpms/testing/ServiceProcessorTest.groovy index efc395439..618affc13 100644 --- a/acceptance-tests/acceptance-tests-serviceloader-jpms/src/test/groovy/io/github/ascopes/jct/acceptancetests/serviceloaderjpms/testing/ServiceProcessorTest.groovy +++ b/acceptance-tests/acceptance-tests-serviceloader-jpms/src/test/groovy/io/github/ascopes/jct/acceptancetests/serviceloaderjpms/testing/ServiceProcessorTest.groovy @@ -16,14 +16,14 @@ package io.github.ascopes.jct.acceptancetests.serviceloaderjpms.testing import io.github.ascopes.jct.acceptancetests.serviceloaderjpms.ServiceProcessor -import io.github.ascopes.jct.compilers.Compilable +import io.github.ascopes.jct.compilers.Compiler import io.github.ascopes.jct.junit.JavacCompilerTest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory @DisplayName("ServiceProcessor tests (JPMS)") class ServiceProcessorTest { @@ -31,8 +31,8 @@ class ServiceProcessorTest { @DisplayName("Expected files get created when the processor is run") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest(modules = true) - void expectedFilesGetCreated(Compilable compiler) { - def sources = newRamFileSystem("sources") + void expectedFilesGetCreated(Compiler compiler) { + def sources = newRamDirectory("sources") .createDirectory("org", "example") .copyContentsFrom("src", "test", "resources", "code") diff --git a/acceptance-tests/acceptance-tests-serviceloader/src/test/groovy/io/github/ascopes/jct/acceptancetests/serviceloader/testing/ServiceProcessorTest.groovy b/acceptance-tests/acceptance-tests-serviceloader/src/test/groovy/io/github/ascopes/jct/acceptancetests/serviceloader/testing/ServiceProcessorTest.groovy index f7e4f1bd4..e999e96dc 100644 --- a/acceptance-tests/acceptance-tests-serviceloader/src/test/groovy/io/github/ascopes/jct/acceptancetests/serviceloader/testing/ServiceProcessorTest.groovy +++ b/acceptance-tests/acceptance-tests-serviceloader/src/test/groovy/io/github/ascopes/jct/acceptancetests/serviceloader/testing/ServiceProcessorTest.groovy @@ -16,14 +16,14 @@ package io.github.ascopes.jct.acceptancetests.serviceloader.testing import io.github.ascopes.jct.acceptancetests.serviceloader.ServiceProcessor -import io.github.ascopes.jct.compilers.Compilable +import io.github.ascopes.jct.compilers.Compiler import io.github.ascopes.jct.junit.JavacCompilerTest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory @DisplayName("ServiceProcessor tests (no JPMS)") class ServiceProcessorTest { @@ -31,8 +31,8 @@ class ServiceProcessorTest { @DisplayName("Expected files get created when the processor is run") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest - void expectedFilesGetCreated(Compilable compiler) { - def sources = newRamFileSystem("sources") + void expectedFilesGetCreated(Compiler compiler) { + def sources = newRamDirectory("sources") .createDirectory("org", "example") .copyContentsFrom("src", "test", "resources", "code") diff --git a/acceptance-tests/acceptance-tests-spring-context-indexer/src/test/groovy/io/github/ascopes/jct/acceptancetests/springcontextindexer/SpringContextIndexerTest.groovy b/acceptance-tests/acceptance-tests-spring-context-indexer/src/test/groovy/io/github/ascopes/jct/acceptancetests/springcontextindexer/SpringContextIndexerTest.groovy index e32e7ed5f..9e33a79a7 100644 --- a/acceptance-tests/acceptance-tests-spring-context-indexer/src/test/groovy/io/github/ascopes/jct/acceptancetests/springcontextindexer/SpringContextIndexerTest.groovy +++ b/acceptance-tests/acceptance-tests-spring-context-indexer/src/test/groovy/io/github/ascopes/jct/acceptancetests/springcontextindexer/SpringContextIndexerTest.groovy @@ -15,7 +15,7 @@ */ package io.github.ascopes.jct.acceptancetests.springcontextindexer -import io.github.ascopes.jct.compilers.Compilable +import io.github.ascopes.jct.compilers.Compiler import io.github.ascopes.jct.junit.JavacCompilerTest import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.parallel.Execution @@ -23,7 +23,7 @@ import org.junit.jupiter.api.parallel.ExecutionMode import org.springframework.context.index.processor.CandidateComponentsIndexer import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory @DisplayName("Spring Context Indexer acceptance tests") class SpringContextIndexerTest { @@ -32,9 +32,9 @@ class SpringContextIndexerTest { @DisplayName("Spring will index the application context as expected") @Execution(ExecutionMode.CONCURRENT) @JavacCompilerTest - void springWillIndexTheApplicationContextAsExpected(Compilable compiler) { + void springWillIndexTheApplicationContextAsExpected(Compiler compiler) { // Given - def sources = newRamFileSystem("sources") + def sources = newRamDirectory("sources") .createDirectory("org", "example") .copyContentsFrom("src", "test", "resources", "code") diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/AbstractCompiler.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/AbstractCompiler.java index bc0ab322b..67f585117 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/AbstractCompiler.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/AbstractCompiler.java @@ -20,6 +20,7 @@ import io.github.ascopes.jct.annotations.Nullable; import io.github.ascopes.jct.pathwrappers.PathWrapper; +import io.github.ascopes.jct.pathwrappers.TestDirectoryFactory; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -52,7 +53,7 @@ */ @API(since = "0.0.1", status = Status.EXPERIMENTAL) public abstract class AbstractCompiler> - implements Compilable { + implements Compiler { private final String name; private final JavaCompiler jsr199Compiler; @@ -98,19 +99,19 @@ protected AbstractCompiler( compilerOptions = new ArrayList<>(); runtimeOptions = new ArrayList<>(); - showWarnings = Compilable.DEFAULT_SHOW_WARNINGS; - showDeprecationWarnings = Compilable.DEFAULT_SHOW_DEPRECATION_WARNINGS; - failOnWarnings = Compilable.DEFAULT_FAIL_ON_WARNINGS; - locale = Compilable.DEFAULT_LOCALE; - logCharset = Compilable.DEFAULT_LOG_CHARSET; - previewFeatures = Compilable.DEFAULT_PREVIEW_FEATURES; + showWarnings = Compiler.DEFAULT_SHOW_WARNINGS; + showDeprecationWarnings = Compiler.DEFAULT_SHOW_DEPRECATION_WARNINGS; + failOnWarnings = Compiler.DEFAULT_FAIL_ON_WARNINGS; + locale = Compiler.DEFAULT_LOCALE; + logCharset = Compiler.DEFAULT_LOG_CHARSET; + previewFeatures = Compiler.DEFAULT_PREVIEW_FEATURES; release = null; source = null; target = null; - verbose = Compilable.DEFAULT_VERBOSE; - diagnosticLoggingMode = Compilable.DEFAULT_DIAGNOSTIC_LOGGING_MODE; + verbose = Compiler.DEFAULT_VERBOSE; + diagnosticLoggingMode = Compiler.DEFAULT_DIAGNOSTIC_LOGGING_MODE; } /** @@ -444,6 +445,17 @@ public A annotationProcessorDiscovery(AnnotationProcessorDiscovery annotationPro return myself(); } + @Override + public TestDirectoryFactory getTestDirectoryFactory() { + return fileManagerBuilder.getTestDirectoryFactory(); + } + + @Override + public A testDirectoryFactory(TestDirectoryFactory testDirectoryFactory) { + fileManagerBuilder.setTestDirectoryFactory(testDirectoryFactory); + return myself(); + } + @Override public final String toString() { return name; diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/CompilationFactory.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/CompilationFactory.java index f4b48408d..4395b6ed9 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/CompilationFactory.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/CompilationFactory.java @@ -50,7 +50,7 @@ * @since 0.0.1 */ @API(since = "0.0.1", status = Status.EXPERIMENTAL) -public class CompilationFactory> { +public class CompilationFactory> { private static final Logger LOGGER = LoggerFactory.getLogger(CompilationFactory.class); @@ -211,8 +211,8 @@ protected List buildFlags(A compiler, FlagBuilder flagBuilder) { * Build the {@link JavaFileManager} to use. * *

LoggingMode will be applied to this via - * {@link #applyLoggingToFileManager(Compilable, FileManager)}, which will be handled by - * {@link #compile(Compilable, FileManagerBuilder, JavaCompiler, FlagBuilder)}. + * {@link #applyLoggingToFileManager(Compiler, FileManager)}, which will be handled by + * {@link #compile(Compiler, FileManagerBuilder, JavaCompiler, FlagBuilder)}. * * @param compiler the compiler to use. * @return the file manager to use. @@ -269,10 +269,10 @@ protected List findCompilationUnits( /** * Apply the logging level to the file manager provided by - * {@link #buildFileManager(Compilable, FileManagerBuilder)}. + * {@link #buildFileManager(Compiler, FileManagerBuilder)}. * *

The default implementation will wrap the given {@link JavaFileManager} in a - * {@link LoggingFileManagerProxy} if the {@link Compilable#getFileManagerLoggingMode()} field is + * {@link LoggingFileManagerProxy} if the {@link Compiler#getFileManagerLoggingMode()} field is * not set to {@link LoggingMode#DISABLED}. In the latter scenario, the input * will be returned to the caller with no other modifications. * diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/Compilable.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/Compiler.java similarity index 97% rename from java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/Compilable.java rename to java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/Compiler.java index 9621b1268..5eef889f3 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/Compilable.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/Compiler.java @@ -16,8 +16,10 @@ package io.github.ascopes.jct.compilers; import io.github.ascopes.jct.ex.CompilerException; -import io.github.ascopes.jct.pathwrappers.BasicPathWrapperImpl; import io.github.ascopes.jct.pathwrappers.PathWrapper; +import io.github.ascopes.jct.pathwrappers.RamDirectory; +import io.github.ascopes.jct.pathwrappers.TestDirectoryFactory; +import io.github.ascopes.jct.pathwrappers.impl.BasicPathWrapperImpl; import io.github.ascopes.jct.utils.IterableUtils; import java.io.UncheckedIOException; import java.nio.charset.Charset; @@ -45,7 +47,7 @@ * @since 0.0.1 */ @API(since = "0.0.1", status = Status.EXPERIMENTAL) -public interface Compilable, R extends Compilation> { +public interface Compiler, R extends Compilation> { /** * Default setting for deprecation warnings ({@code true}). @@ -124,6 +126,14 @@ public interface Compilable, R extends Compilation> { */ Charset DEFAULT_LOG_CHARSET = StandardCharsets.UTF_8; + /** + * Default setting for creating temporary directories for use during compilation. + * + *

The default is to use a RAM disk, but this can be swapped out with a temporary + * directory on the root file system if desired, or a custom implementation can be used. + */ + TestDirectoryFactory DEFAULT_TEST_DIRECTORY_FACTORY = RamDirectory::newRamDirectory; + /** * Apply a given configurer to this compiler that can throw a checked exception. * @@ -1171,6 +1181,24 @@ default C target(SourceVersion target) { */ C annotationProcessorDiscovery(AnnotationProcessorDiscovery annotationProcessorDiscovery); + /** + * Get the factory used for creating temporary test directories. + * + *

Unless otherwise changed or specified, implementations should default to + * {@link #DEFAULT_TEST_DIRECTORY_FACTORY}. + * + * @return the test directory factory to use. + */ + TestDirectoryFactory getTestDirectoryFactory(); + + /** + * Set the test directory factory to use. + * + * @param testDirectoryFactory the factory to use. + * @return this compiler for further call chaining. + */ + C testDirectoryFactory(TestDirectoryFactory testDirectoryFactory); + /** * Invoke the compilation and return the compilation result. * diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/CompilerConfigurer.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/CompilerConfigurer.java index ad77b93f8..39ca56959 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/CompilerConfigurer.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/CompilerConfigurer.java @@ -30,7 +30,7 @@ */ @API(since = "0.0.1", status = Status.EXPERIMENTAL) @FunctionalInterface -public interface CompilerConfigurer, T extends Exception> { +public interface CompilerConfigurer, T extends Exception> { /** * Apply configuration logic to the given compiler. diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/FileManagerBuilder.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/FileManagerBuilder.java index 5271abf6b..f3423ba0c 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/FileManagerBuilder.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/FileManagerBuilder.java @@ -15,9 +15,10 @@ */ package io.github.ascopes.jct.compilers; -import io.github.ascopes.jct.pathwrappers.BasicPathWrapperImpl; +import io.github.ascopes.jct.pathwrappers.AbstractTestDirectory; import io.github.ascopes.jct.pathwrappers.PathWrapper; -import io.github.ascopes.jct.pathwrappers.RamFileSystem; +import io.github.ascopes.jct.pathwrappers.TestDirectoryFactory; +import io.github.ascopes.jct.pathwrappers.impl.BasicPathWrapperImpl; import io.github.ascopes.jct.utils.GarbageDisposal; import io.github.ascopes.jct.utils.Lazy; import io.github.ascopes.jct.utils.SpecialLocations; @@ -70,6 +71,7 @@ public final class FileManagerBuilder { private boolean inheritSystemModulePath; private LoggingMode fileManagerLoggingMode; private AnnotationProcessorDiscovery annotationProcessorDiscovery; + private TestDirectoryFactory testDirectoryFactory; /** * Initialize this workspace. @@ -84,13 +86,14 @@ public FileManagerBuilder() { locations = new HashMap<>(); - fixJvmModulePathMismatch = Compilable.DEFAULT_FIX_JVM_MODULEPATH_MISMATCH; - inheritClassPath = Compilable.DEFAULT_INHERIT_CLASS_PATH; - inheritModulePath = Compilable.DEFAULT_INHERIT_MODULE_PATH; - inheritPlatformClassPath = Compilable.DEFAULT_INHERIT_PLATFORM_CLASS_PATH; - inheritSystemModulePath = Compilable.DEFAULT_INHERIT_SYSTEM_MODULE_PATH; - fileManagerLoggingMode = Compilable.DEFAULT_FILE_MANAGER_LOGGING_MODE; - annotationProcessorDiscovery = Compilable.DEFAULT_ANNOTATION_PROCESSOR_DISCOVERY; + fixJvmModulePathMismatch = Compiler.DEFAULT_FIX_JVM_MODULEPATH_MISMATCH; + inheritClassPath = Compiler.DEFAULT_INHERIT_CLASS_PATH; + inheritModulePath = Compiler.DEFAULT_INHERIT_MODULE_PATH; + inheritPlatformClassPath = Compiler.DEFAULT_INHERIT_PLATFORM_CLASS_PATH; + inheritSystemModulePath = Compiler.DEFAULT_INHERIT_SYSTEM_MODULE_PATH; + fileManagerLoggingMode = Compiler.DEFAULT_FILE_MANAGER_LOGGING_MODE; + annotationProcessorDiscovery = Compiler.DEFAULT_ANNOTATION_PROCESSOR_DISCOVERY; + testDirectoryFactory = Compiler.DEFAULT_TEST_DIRECTORY_FACTORY; } @Override @@ -175,7 +178,7 @@ public void addPath(Location location, String module, PathWrapper path) { * enabled, and only applies to the current JVM classpath and module path. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_FIX_JVM_MODULEPATH_MISMATCH}. + * {@link Compiler#DEFAULT_FIX_JVM_MODULEPATH_MISMATCH}. * * @return {@code true} if enabled, or {@code false} if disabled. */ @@ -188,7 +191,7 @@ public boolean isFixJvmModulePathMismatch() { * on the module path. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_FIX_JVM_MODULEPATH_MISMATCH}. + * {@link Compiler#DEFAULT_FIX_JVM_MODULEPATH_MISMATCH}. * * @param fixJvmModulePathMismatch whether to enable the mismatch fixing or not. */ @@ -200,7 +203,7 @@ public void setFixJvmModulePathMismatch(boolean fixJvmModulePathMismatch) { * Get whether the class path is inherited from the caller JVM or not. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_INHERIT_CLASS_PATH}. + * {@link Compiler#DEFAULT_INHERIT_CLASS_PATH}. * * @return whether the current class path is being inherited or not. */ @@ -212,7 +215,7 @@ public boolean isInheritClassPath() { * Set whether the class path is inherited from the caller JVM or not. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_INHERIT_CLASS_PATH}. + * {@link Compiler#DEFAULT_INHERIT_CLASS_PATH}. * * @param inheritClassPath {@code true} to include it, or {@code false} to exclude it. */ @@ -224,7 +227,7 @@ public void inheritClassPath(boolean inheritClassPath) { * Get whether the module path is inherited from the caller JVM or not. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_INHERIT_MODULE_PATH}. + * {@link Compiler#DEFAULT_INHERIT_MODULE_PATH}. * * @return whether the module path is being inherited or not. */ @@ -236,7 +239,7 @@ public boolean isInheritModulePath() { * Set whether the module path is inherited from the caller JVM or not. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_INHERIT_MODULE_PATH}. + * {@link Compiler#DEFAULT_INHERIT_MODULE_PATH}. * * @param inheritModulePath {@code true} to include it, or {@code false} to exclude it. */ @@ -253,7 +256,7 @@ public void inheritModulePath(boolean inheritModulePath) { * ignored. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_INHERIT_PLATFORM_CLASS_PATH}. + * {@link Compiler#DEFAULT_INHERIT_PLATFORM_CLASS_PATH}. * * @return whether the platform class path is being inherited or not. */ @@ -279,7 +282,7 @@ public void inheritPlatformClassPath(boolean inheritPlatformClassPath) { * Get whether the system module path is inherited from the caller JVM or not. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_INHERIT_SYSTEM_MODULE_PATH}. + * {@link Compiler#DEFAULT_INHERIT_SYSTEM_MODULE_PATH}. * * @return whether the system module path is being inherited or not. */ @@ -291,7 +294,7 @@ public boolean isInheritSystemModulePath() { * Set whether the system module path is inherited from the caller JVM or not. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_INHERIT_SYSTEM_MODULE_PATH}. + * {@link Compiler#DEFAULT_INHERIT_SYSTEM_MODULE_PATH}. * * @param inheritSystemModulePath {@code true} to include it, or {@code false} to exclude it. */ @@ -303,7 +306,7 @@ public void inheritSystemModulePath(boolean inheritSystemModulePath) { * Get the current file manager logging mode. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_FILE_MANAGER_LOGGING_MODE}. + * {@link Compiler#DEFAULT_FILE_MANAGER_LOGGING_MODE}. * * @return the current file manager logging mode. */ @@ -315,7 +318,7 @@ public LoggingMode getFileManagerLoggingMode() { * Set how to handle logging calls to underlying file managers. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_FILE_MANAGER_LOGGING_MODE}. + * {@link Compiler#DEFAULT_FILE_MANAGER_LOGGING_MODE}. * * @param fileManagerLoggingMode the mode to use for file manager logging. */ @@ -327,7 +330,7 @@ public void fileManagerLoggingMode(LoggingMode fileManagerLoggingMode) { * Get how to perform annotation processor discovery. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_ANNOTATION_PROCESSOR_DISCOVERY}. + * {@link Compiler#DEFAULT_ANNOTATION_PROCESSOR_DISCOVERY}. * * @return the annotation processor discovery mode. */ @@ -339,15 +342,37 @@ public AnnotationProcessorDiscovery getAnnotationProcessorDiscovery() { * Set how to perform annotation processor discovery. * *

Unless otherwise changed or specified, implementations should default to - * {@link Compilable#DEFAULT_ANNOTATION_PROCESSOR_DISCOVERY}. + * {@link Compiler#DEFAULT_ANNOTATION_PROCESSOR_DISCOVERY}. * * @param annotationProcessorDiscovery the processor discovery mode to use. */ public void annotationProcessorDiscovery( - AnnotationProcessorDiscovery annotationProcessorDiscovery) { + AnnotationProcessorDiscovery annotationProcessorDiscovery + ) { this.annotationProcessorDiscovery = annotationProcessorDiscovery; } + /** + * Get the factory used for creating temporary test directories. + * + *

Unless otherwise changed or specified, implementations should default to + * {@link Compiler#DEFAULT_TEST_DIRECTORY_FACTORY}. + * + * @return the test directory factory to use. + */ + public TestDirectoryFactory getTestDirectoryFactory() { + return testDirectoryFactory; + } + + /** + * Set the test directory factory to use. + * + * @param testDirectoryFactory the factory to use. + */ + public void setTestDirectoryFactory(TestDirectoryFactory testDirectoryFactory) { + this.testDirectoryFactory = testDirectoryFactory; + } + /** * Create a file manager for this workspace. * @@ -383,18 +408,24 @@ public FileManager createFileManager(String effectiveRelease) throws IOException fileManagerLoggingMode == LoggingMode.STACKTRACES); } - private Lazy newFallbackFs(FileManagerImpl fileManager) { + private Lazy> newFallbackFs(FileManagerImpl fileManager) { return new Lazy<>(() -> { - var tempFs = RamFileSystem.newRamFileSystem("temp", false); - var fileManagerName = fileManager.toString(); - GarbageDisposal.onPhantom(fileManager, "tempfs for " + fileManagerName, tempFs::close); + // Make the generated directory belong to the file manager that uses it rather than just + // the reference to the Ram directory itself. This prevents premature closure and also ensures + // the generated directory does not outlive the file manager. + var tempFs = testDirectoryFactory.create("tmp", false); + GarbageDisposal.onPhantom( + fileManager, + "temporary directory for compiler inputs and outputs (" + tempFs + ")", + tempFs::close + ); return tempFs; }); } private void createLocationIfNotPresent( FileManagerImpl fileManager, - Lazy fallbackFs, + Lazy> fallbackFs, Location location ) throws IOException { if (!fileManager.hasLocation(location)) { diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/AbstractPackageContainerGroup.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/AbstractPackageContainerGroup.java index 5ca10bc54..63b2cf5c1 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/AbstractPackageContainerGroup.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/AbstractPackageContainerGroup.java @@ -103,11 +103,13 @@ public void addPackage(Container container) { public void addPackage(PathWrapper path) { var actualPath = path.getPath(); - var archive = ARCHIVE_EXTENSIONS + // Null filename implies the path is the root directory of a file system ( + // like a JIMFS RAM file system we initialize elsewhere). + var isArchive = actualPath.getFileName() != null && ARCHIVE_EXTENSIONS .stream() .anyMatch(actualPath.getFileName().toString().toLowerCase(Locale.ROOT)::endsWith); - var container = archive + var container = isArchive ? new JarContainerImpl(getLocation(), path, release) : new PathWrappingContainerImpl(getLocation(), path); diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/JarContainerImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/JarContainerImpl.java index 3e721796c..648a60a72 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/JarContainerImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/JarContainerImpl.java @@ -23,8 +23,8 @@ import io.github.ascopes.jct.annotations.WillNotClose; import io.github.ascopes.jct.compilers.PathFileObject; import io.github.ascopes.jct.containers.Container; -import io.github.ascopes.jct.pathwrappers.BasicPathWrapperImpl; import io.github.ascopes.jct.pathwrappers.PathWrapper; +import io.github.ascopes.jct.pathwrappers.impl.BasicPathWrapperImpl; import io.github.ascopes.jct.utils.Lazy; import io.github.ascopes.jct.utils.ToStringBuilder; import java.io.IOException; diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/OutputContainerGroupImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/OutputContainerGroupImpl.java index e0727b2ef..00fe1d251 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/OutputContainerGroupImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/containers/impl/OutputContainerGroupImpl.java @@ -25,8 +25,8 @@ import io.github.ascopes.jct.containers.Container; import io.github.ascopes.jct.containers.OutputContainerGroup; import io.github.ascopes.jct.containers.PackageContainerGroup; -import io.github.ascopes.jct.pathwrappers.BasicPathWrapperImpl; import io.github.ascopes.jct.pathwrappers.PathWrapper; +import io.github.ascopes.jct.pathwrappers.impl.BasicPathWrapperImpl; import io.github.ascopes.jct.utils.StringUtils; import java.nio.file.Files; import java.util.HashMap; diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/AbstractCompilersProvider.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/AbstractCompilersProvider.java index 98ff76fb5..24bc833af 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/AbstractCompilersProvider.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/junit/AbstractCompilersProvider.java @@ -15,7 +15,7 @@ */ package io.github.ascopes.jct.junit; -import io.github.ascopes.jct.compilers.Compilable; +import io.github.ascopes.jct.compilers.Compiler; import java.util.function.IntFunction; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -35,7 +35,7 @@ @API(since = "0.0.1", status = Status.EXPERIMENTAL) public abstract class AbstractCompilersProvider implements ArgumentsProvider { - private final IntFunction> compilerFactory; + private final IntFunction> compilerFactory; private final int minCompilerVersionWithoutModules; private final int minCompilerVersionWithModules; private final int maxCompilerVersion; @@ -56,7 +56,7 @@ public abstract class AbstractCompilersProvider implements ArgumentsProvider { * @param maxCompilerVersion the maximum version of the compiler to support. */ protected AbstractCompilersProvider( - IntFunction> compilerFactory, + IntFunction> compilerFactory, int minCompilerVersionWithoutModules, int minCompilerVersionWithModules, int maxCompilerVersion diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamFileSystem.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/AbstractTestDirectory.java similarity index 69% rename from java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamFileSystem.java rename to java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/AbstractTestDirectory.java index 15edfcc11..f05755a2e 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamFileSystem.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/AbstractTestDirectory.java @@ -18,10 +18,6 @@ import static io.github.ascopes.jct.utils.IoExceptionUtils.uncheckedIo; import static java.util.Objects.requireNonNull; -import com.google.common.jimfs.Configuration; -import com.google.common.jimfs.Feature; -import com.google.common.jimfs.Jimfs; -import com.google.common.jimfs.PathType; import io.github.ascopes.jct.annotations.CheckReturnValue; import io.github.ascopes.jct.annotations.Nullable; import io.github.ascopes.jct.annotations.WillClose; @@ -29,6 +25,7 @@ import io.github.ascopes.jct.utils.ToStringBuilder; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; +import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -38,7 +35,6 @@ import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.OpenOption; @@ -54,70 +50,52 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** - * A wrapper around a temporary in-memory file system that exposes the root path of said file - * system. - * - *

This provides a utility for constructing complex and dynamic directory tree structures - * quickly and simply using fluent chained methods. - * - *

These file systems are integrated into the {@link FileSystem} API, and can be configured to - * automatically destroy themselves once this RamPath handle is garbage collected. + * Abstract base for implementing a reusable managed wrapper around a directory of some sort. * - *

In addition, these paths follow POSIX file system semantics, meaning that files are handled - * with case-sensitive names, and use forward slashes to separate paths. - * - *

While this will create a global {@link FileSystem}, it is recommended that you only interact - * with the file system via this class to prevent potentially confusing behaviour elsewhere. + *

This is designed to simplify the creation of file and directory trees, and manage the release + * of resources once no longer needed automatically, helping to keep test logic simple and + * clean. * + * @param the implementation type. * @author Ashley Scopes * @since 0.0.1 */ @API(since = "0.0.1", status = Status.EXPERIMENTAL) -public final class RamFileSystem implements PathWrapper { - - private static final String SEPARATOR = "/"; +public abstract class AbstractTestDirectory> + implements PathWrapper { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private static final Logger LOGGER = LoggerFactory.getLogger(RamFileSystem.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTestDirectory.class); - private final Path path; - private final URI uri; private final String name; + private final Path rootDirectory; + private final String separator; + private final URI uri; + private final Closeable closeHook; - /** - * Initialize this in-memory path. - * - * @param name the name of the file system to create. - * @param closeOnGarbageCollection {@code true} to delegate - */ @SuppressWarnings("ThisEscapedInObjectConstruction") - private RamFileSystem(String name, boolean closeOnGarbageCollection) { + protected AbstractTestDirectory( + String name, + Path rootDirectory, + String separator, + boolean closeOnGc, + Closeable closeHook + ) { this.name = requireNonNull(name, "name"); - - var config = Configuration - .builder(PathType.unix()) - .setSupportedFeatures(Feature.LINKS, Feature.SYMBOLIC_LINKS, Feature.FILE_CHANNEL) - .setAttributeViews("basic", "posix") - .setRoots(SEPARATOR) - .setWorkingDirectory(SEPARATOR) - .setPathEqualityUsesCanonicalForm(true) - .build(); - - var fileSystem = Jimfs.newFileSystem(config); - path = fileSystem.getRootDirectories().iterator().next().resolve(name); - uri = path.toUri(); - - // Ensure the base directory exists. - uncheckedIo(() -> Files.createDirectories(path)); - - if (closeOnGarbageCollection) { - GarbageDisposal.onPhantom(this, name, fileSystem); + this.closeHook = requireNonNull(closeHook, "closeHook"); + this.rootDirectory = requireNonNull(rootDirectory, "rootDirectory"); + this.separator = requireNonNull(separator, "separator"); + uri = this.rootDirectory.toUri(); + + if (closeOnGc) { + LOGGER.trace("Registering {} to be destroyed on garbage collection", uri); + GarbageDisposal.onPhantom(this, name, closeHook); } - - LOGGER.trace("Initialized new in-memory directory {}", path); } + /** * {@inheritDoc} * @@ -133,7 +111,7 @@ public PathWrapper getParent() { @CheckReturnValue @Override public Path getPath() { - return path; + return rootDirectory; } @CheckReturnValue @@ -158,7 +136,7 @@ public String getName() { * @throws UncheckedIOException if an IO error occurs. */ public void close() { - uncheckedIo(path.getFileSystem()::close); + uncheckedIo(closeHook::close); } /** @@ -167,8 +145,8 @@ public void close() { * @return this object. */ @CheckReturnValue - public RamFileSystem and() { - return this; + public I and() { + return thisTestFileSystem(); } /** @@ -224,16 +202,16 @@ public DirectoryBuilder createDirectory(Path path) { */ @CheckReturnValue public DirectoryBuilder rootDirectory() { - return new DirectoryBuilder(path); + return new DirectoryBuilder(rootDirectory); } @Override public boolean equals(Object other) { - if (!(other instanceof RamFileSystem)) { + if (!(other instanceof AbstractTestDirectory)) { return false; } - var that = (RamFileSystem) other; + var that = (AbstractTestDirectory) other; return name.equals(that.name) && uri.equals(that.uri); @@ -253,7 +231,7 @@ public String toString() { } private Path makeRelativeToHere(Path relativePath) { - if (relativePath.isAbsolute() && !relativePath.startsWith(path)) { + if (relativePath.isAbsolute() && !relativePath.startsWith(rootDirectory)) { var fixedPath = relativePath.getRoot().relativize(relativePath); LOGGER.warn( @@ -267,43 +245,9 @@ private Path makeRelativeToHere(Path relativePath) { // ToString is needed as JIMFS will fail on trying to make a relative path from a different // provider. - return relativePath.getFileSystem() == path.getFileSystem() + return relativePath.getFileSystem() == rootDirectory.getFileSystem() ? relativePath.normalize() - : path.resolve(relativePath.toString()).normalize(); - } - - /** - * Create a new in-memory path. - * - *

The underlying in-memory file system will be closed and destroyed when the returned - * object is garbage collected, or when {@link #close()} is called on it manually. - * - * @param name a symbolic name to give the path. This must be a valid POSIX directory name. - * @return the in-memory path. - * @see #newRamFileSystem(String, boolean) - */ - @CheckReturnValue - public static RamFileSystem newRamFileSystem(String name) { - return newRamFileSystem(name, true); - } - - /** - * Create a new in-memory path. - * - * @param name a symbolic name to give the path. This must be a valid POSIX - * directory name. - * @param closeOnGarbageCollection if {@code true}, then the {@link #close()} operation will be - * called on the underlying {@link FileSystem} as soon as the - * returned object from this method is garbage collected. If - * {@code false}, then you must close the underlying file system - * manually using the {@link #close()} method on the returned - * object. Failing to do so will lead to resources being leaked. - * @return the in-memory path. - * @see #newRamFileSystem(String) - */ - @CheckReturnValue - public static RamFileSystem newRamFileSystem(String name, boolean closeOnGarbageCollection) { - return new RamFileSystem(name, closeOnGarbageCollection); + : rootDirectory.resolve(relativePath.toString()).normalize(); } @CheckReturnValue @@ -332,8 +276,8 @@ private static ClassLoader currentCallerClassLoader() { return Thread.currentThread().getContextClassLoader(); } - private static String collapsePath(String first, String... rest) { - var joiner = new StringJoiner(SEPARATOR); + private String collapsePath(String first, String... rest) { + var joiner = new StringJoiner(separator); joiner.add(first); for (var part : rest) { joiner.add(part); @@ -341,14 +285,44 @@ private static String collapsePath(String first, String... rest) { return joiner.toString(); } - private static String collapsePath(Path path) { - var joiner = new StringJoiner(SEPARATOR); + private String collapsePath(Path path) { + var joiner = new StringJoiner(separator); for (var part : path) { joiner.add(part.toString()); } return joiner.toString(); } + @SuppressWarnings("unchecked") + private I thisTestFileSystem() { + return (I) this; + } + + /** + * Assert that the given name is a valid name for a directory, and that it does not contain + * potentially dangerous characters such as double-dots or slashes that could be used to escape + * the directory we are running from. + * + * @param name the directory name to check. + * @throws IllegalArgumentException if the name is invalid. + * @throws NullPointerException if the name is {@code null}. + */ + protected static void assertValidRootName(@Nullable String name) { + Objects.requireNonNull(name, "name"); + + if (name.isBlank()) { + throw new IllegalArgumentException("Directory name cannot be blank"); + } + + if (!name.equals(name.trim())) { + throw new IllegalArgumentException("Directory name cannot begin or end in spaces"); + } + + if (name.contains("/") || name.contains("\\") || name.contains("..")) { + throw new IllegalArgumentException("Invalid file name provided"); + } + } + /** * Chainable builder for creating individual files. * @@ -361,7 +335,7 @@ public final class FileBuilder { private final Path targetPath; private FileBuilder(String first, String... rest) { - this(path.resolve(collapsePath(first, rest))); + this(rootDirectory.resolve(collapsePath(first, rest))); } private FileBuilder(Path targetPath) { @@ -374,7 +348,7 @@ private FileBuilder(Path targetPath) { * @param lines the lines to write using the default charset. * @return the file system for further configuration. */ - public RamFileSystem withContents(String... lines) { + public I withContents(String... lines) { return withContents(DEFAULT_CHARSET, lines); } @@ -385,7 +359,7 @@ public RamFileSystem withContents(String... lines) { * @param lines the lines to write. * @return the file system for further configuration. */ - public RamFileSystem withContents(Charset charset, String... lines) { + public I withContents(Charset charset, String... lines) { return withContents(String.join("\n", lines).getBytes(charset)); } @@ -395,7 +369,7 @@ public RamFileSystem withContents(Charset charset, String... lines) { * @param contents the bytes to write. * @return the file system for further configuration. */ - public RamFileSystem withContents(byte[] contents) { + public I withContents(byte[] contents) { return uncheckedIo(() -> createFile(new ByteArrayInputStream(contents))); } @@ -405,7 +379,7 @@ public RamFileSystem withContents(byte[] contents) { * @param resource the resource to copy. * @return the file system for further configuration. */ - public RamFileSystem copiedFromClassPath(String resource) { + public I copiedFromClassPath(String resource) { return copiedFromClassPath(currentCallerClassLoader(), resource); } @@ -416,7 +390,7 @@ public RamFileSystem copiedFromClassPath(String resource) { * @param resource the resource to copy. * @return the file system for further configuration. */ - public RamFileSystem copiedFromClassPath(ClassLoader classLoader, String resource) { + public I copiedFromClassPath(ClassLoader classLoader, String resource) { return uncheckedIo(() -> { try (var input = classLoader.getResourceAsStream(resource)) { if (input == null) { @@ -434,7 +408,7 @@ public RamFileSystem copiedFromClassPath(ClassLoader classLoader, String resourc * @param file the file to read. * @return the file system for further configuration. */ - public RamFileSystem copiedFromFile(File file) { + public I copiedFromFile(File file) { return copiedFromFile(file.toPath()); } @@ -444,7 +418,7 @@ public RamFileSystem copiedFromFile(File file) { * @param file the file to read. * @return the file system for further configuration. */ - public RamFileSystem copiedFromFile(Path file) { + public I copiedFromFile(Path file) { return uncheckedIo(() -> { try (var input = Files.newInputStream(file)) { return createFile(input); @@ -458,7 +432,7 @@ public RamFileSystem copiedFromFile(Path file) { * @param url the URL to read. * @return the file system for further configuration. */ - public RamFileSystem copiedFromUrl(URL url) { + public I copiedFromUrl(URL url) { return uncheckedIo(() -> createFile(url.openStream())); } @@ -467,7 +441,7 @@ public RamFileSystem copiedFromUrl(URL url) { * * @return the file system for further configuration. */ - public RamFileSystem thatIsEmpty() { + public I thatIsEmpty() { return fromInputStream(InputStream.nullInputStream()); } @@ -479,11 +453,11 @@ public RamFileSystem thatIsEmpty() { * @param inputStream the input stream to read. * @return the file system for further configuration. */ - public RamFileSystem fromInputStream(@WillClose InputStream inputStream) { + public I fromInputStream(@WillClose InputStream inputStream) { return uncheckedIo(() -> createFile(inputStream)); } - private RamFileSystem createFile(InputStream input) throws IOException { + private I createFile(InputStream input) throws IOException { Files.createDirectories(targetPath.getParent()); var opts = new OpenOption[]{ @@ -496,7 +470,7 @@ private RamFileSystem createFile(InputStream input) throws IOException { var bufferedInput = maybeBuffer(input, targetPath.toUri().getScheme()) ) { bufferedInput.transferTo(output); - return RamFileSystem.this; + return thisTestFileSystem(); } } } @@ -513,7 +487,7 @@ public final class DirectoryBuilder { private final Path targetPath; private DirectoryBuilder(String first, String... rest) { - this(path.resolve(collapsePath(first, rest))); + this(rootDirectory.resolve(collapsePath(first, rest))); } private DirectoryBuilder(Path targetPath) { @@ -526,10 +500,10 @@ private DirectoryBuilder(Path targetPath) { *

This uses the default file system. * * @param first the first path fragment of the directory to copy from. - * @param rest any additional path fragements to copy from. + * @param rest any additional path fragments to copy from. * @return the file system for further configuration. */ - public RamFileSystem copyContentsFrom(String first, String... rest) { + public I copyContentsFrom(String first, String... rest) { // Path.of is fine here as it is for the default file system. return copyContentsFrom(Path.of(first, rest)); } @@ -540,7 +514,7 @@ public RamFileSystem copyContentsFrom(String first, String... rest) { * @param dir the directory to copy the contents from. * @return the file system for further configuration. */ - public RamFileSystem copyContentsFrom(File dir) { + public I copyContentsFrom(File dir) { return copyContentsFrom(dir.toPath()); } @@ -550,7 +524,7 @@ public RamFileSystem copyContentsFrom(File dir) { * @param rootDir the directory to copy the contents from. * @return the file system for further configuration. */ - public RamFileSystem copyContentsFrom(Path rootDir) { + public I copyContentsFrom(Path rootDir) { uncheckedIo(() -> { Files.walkFileTree(rootDir, new SimpleFileVisitor<>() { @@ -585,7 +559,7 @@ public FileVisitResult visitFile( }); }); - return RamFileSystem.this; + return thisTestFileSystem(); } /** @@ -593,9 +567,9 @@ public FileVisitResult visitFile( * * @return the file system for further configuration. */ - public RamFileSystem thatIsEmpty() { + public I thatIsEmpty() { uncheckedIo(() -> Files.createDirectories(targetPath)); - return RamFileSystem.this; + return thisTestFileSystem(); } } } diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/PathWrapper.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/PathWrapper.java index cafda6cbb..bdf65975f 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/PathWrapper.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/PathWrapper.java @@ -29,8 +29,8 @@ * we can also use this to enforce that other references related to the internal path are kept alive * for as long as the path-like object itself is kept alive. * - *

This becomes very useful for {@link RamFileSystem}, which keeps a RAM-based - * {@link FileSystem} alive until it is garbage collected, or the {@link RamFileSystem#close()} + *

This becomes very useful for {@link RamDirectory}, which keeps a RAM-based + * {@link FileSystem} alive until it is garbage collected, or the {@link RamDirectory#close()} * operation is called. The mechanism enables cleaning up of resources implicitly without * resource-tidying logic polluting the user's test cases. * diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamDirectory.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamDirectory.java new file mode 100644 index 000000000..8bf412f57 --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/RamDirectory.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 - 2022 Ashley Scopes + * + * 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.pathwrappers; + +import static io.github.ascopes.jct.utils.IoExceptionUtils.uncheckedIo; + +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Feature; +import com.google.common.jimfs.Jimfs; +import com.google.common.jimfs.PathType; +import io.github.ascopes.jct.annotations.CheckReturnValue; +import java.io.Closeable; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A wrapper around a temporary in-memory file system that exposes the root path of said file + * system. + * + *

This provides a utility for constructing complex and dynamic directory tree structures + * quickly and simply using fluent chained methods. + * + *

These file systems are integrated into the {@link FileSystem} API, and can be configured to + * automatically destroy themselves once this RamPath handle is garbage collected. + * + *

In addition, these paths follow POSIX file system semantics, meaning that files are handled + * with case-sensitive names, and use forward slashes to separate paths. + * + *

While this will create a global {@link FileSystem}, it is recommended that you only interact + * with the file system via this class to prevent potentially confusing behaviour elsewhere. + * + * @author Ashley Scopes + * @since 0.0.1 + */ +@API(since = "0.0.1", status = Status.EXPERIMENTAL) +public final class RamDirectory extends AbstractTestDirectory { + + private static final Logger LOGGER = LoggerFactory.getLogger(RamDirectory.class); + + private RamDirectory( + String name, + Path rootDirectory, + String separator, + boolean closeOnGc, + Closeable closeHook + ) { + super(name, rootDirectory, separator, closeOnGc, closeHook); + } + + + /** + * Create a new in-memory path. + * + *

The underlying in-memory file system will be closed and destroyed when the returned + * object is garbage collected, or when {@link #close()} is called on it manually. + * + * @param name a symbolic name to give the path. This must be a valid POSIX directory name. + * @return the in-memory path. + * @see #newRamDirectory(String, boolean) + */ + @CheckReturnValue + public static RamDirectory newRamDirectory(String name) { + return newRamDirectory(name, true); + } + + /** + * Create a new in-memory path. + * + * @param name a symbolic name to give the path. This must be a valid POSIX + * directory name. + * @param closeOnGc if {@code true}, then the {@link #close()} operation will be + * called on the underlying {@link FileSystem} as soon as the + * returned object from this method is garbage collected. If + * {@code false}, then you must close the underlying file system + * manually using the {@link #close()} method on the returned + * object. Failing to do so will lead to resources being leaked. + * @return the in-memory path. + * @see #newRamDirectory(String) + */ + @CheckReturnValue + public static RamDirectory newRamDirectory(String name, boolean closeOnGc) { + assertValidRootName(name); + + var config = Configuration + .builder(PathType.unix()) + .setSupportedFeatures(Feature.LINKS, Feature.SYMBOLIC_LINKS, Feature.FILE_CHANNEL) + .setAttributeViews("basic", "posix") + .setRoots("/") + .setWorkingDirectory("/") + .setPathEqualityUsesCanonicalForm(true) + .build(); + + var fileSystem = Jimfs.newFileSystem(config); + var path = fileSystem.getRootDirectories().iterator().next().resolve(name); + + // Ensure the base directory exists. + uncheckedIo(() -> Files.createDirectories(path)); + + var fs = new RamDirectory( + name, + fileSystem.getRootDirectories().iterator().next(), + fileSystem.getSeparator(), + closeOnGc, + fileSystem + ); + + LOGGER.trace("Initialized new in-memory directory {} - {}", path, fs); + return fs; + } +} diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TempDirectory.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TempDirectory.java new file mode 100644 index 000000000..b0bd2a5e0 --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TempDirectory.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 - 2022 Ashley Scopes + * + * 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.pathwrappers; + +import static io.github.ascopes.jct.utils.IoExceptionUtils.uncheckedIo; + +import io.github.ascopes.jct.annotations.CheckReturnValue; +import io.github.ascopes.jct.utils.RecursiveDeleter; +import java.io.Closeable; +import java.nio.file.Files; +import java.nio.file.Path; +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Temporary file system location that can be created by users to store sources, inputs, and outputs + * for compilation within. + * + *

This implementation uses a temporary directory provided by the default file system on the + * platform you are using, and is thus compatible with older IO-based facilities such as + * {@link java.io.File}, {@link java.io.FileInputStream}, etc. + * + *

The downside to this is that if the JVM crashes, the directory may not be deleted. It may + * also be slower than {@link RamDirectory}, and does not provide isolation from the environment + * that the tests are running in. + * + * @author Ashley Scopes + * @see RamDirectory + * @since 0.0.1 + */ +@API(since = "0.0.1", status = Status.EXPERIMENTAL) +public final class TempDirectory extends AbstractTestDirectory { + + private static final Logger LOGGER = LoggerFactory.getLogger(TempDirectory.class); + + private TempDirectory( + String name, + Path rootDirectory, + String separator, + boolean closeOnGc, + Closeable closeHook + ) { + super(name, rootDirectory, separator, closeOnGc, closeHook); + } + + /** + * Create a new temporary directory on the root file system somewhere. + * + *

The underlying directory will be unlinked when the returned object is garbage collected, + * or when {@link #close()} is called on it manually. + * + * @param name a symbolic name to give the path. This must be a valid directory name for the + * environment you are using. + * @return the temporary directory. + * @see #newTempDirectory(String, boolean) + */ + @CheckReturnValue + public static TempDirectory newTempDirectory(String name) { + return newTempDirectory(name, true); + } + + /** + * Create a new temporary directory on the root file system somewhere. + * + * @param name a symbolic name to give the path. This must be a valid directory name for the + * environment you are using. + * @param closeOnGc if {@code true}, then the temporary resources will be deleted automatically + * when the returned object is garbage collected. If {@code false}, then this + * must be deleted manually. + * @return the temporary directory. + * @see #newTempDirectory(String) + */ + @CheckReturnValue + public static TempDirectory newTempDirectory(String name, boolean closeOnGc) { + assertValidRootName(name); + var tempDir = uncheckedIo(() -> Files.createTempDirectory("jct-")); + var innerDir = uncheckedIo(() -> Files.createDirectory(tempDir.resolve(name))); + + LOGGER.trace("Initialized new temporary directory {}", innerDir); + + // TODO(ascopes): delete on JVM exit recursively if closeOnGc is set. + return new TempDirectory( + name, + innerDir, + tempDir.getFileSystem().getSeparator(), + closeOnGc, + () -> RecursiveDeleter.deleteAll(tempDir) + ); + } +} diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TestDirectoryFactory.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TestDirectoryFactory.java new file mode 100644 index 000000000..f9c25aaf8 --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/TestDirectoryFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 - 2022 Ashley Scopes + * + * 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.pathwrappers; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * Functional interface representing a factory that can create temporary directories for testing. + * + * @param the implementation type. + * @author Ashley Scopes + * @since 0.0.1 + */ +@API(since = "0.0.1", status = Status.INTERNAL) +@FunctionalInterface +public interface TestDirectoryFactory { + + /** + * Create a new temporary directory. + * + * @param name the name of the directory. + * @param closeOnGc whether to clean up resources when the returned object is garbage collected. + * @return the test directory object. + */ + AbstractTestDirectory create(String name, boolean closeOnGc); +} diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/BasicPathWrapperImpl.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/BasicPathWrapperImpl.java similarity index 92% rename from java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/BasicPathWrapperImpl.java rename to java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/BasicPathWrapperImpl.java index 1bd0c57be..b63a7d36c 100644 --- a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/BasicPathWrapperImpl.java +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/BasicPathWrapperImpl.java @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.github.ascopes.jct.pathwrappers; +package io.github.ascopes.jct.pathwrappers.impl; import static java.util.Objects.requireNonNull; import io.github.ascopes.jct.annotations.Nullable; +import io.github.ascopes.jct.pathwrappers.PathWrapper; +import io.github.ascopes.jct.pathwrappers.RamDirectory; import io.github.ascopes.jct.utils.ToStringBuilder; import java.net.URI; import java.nio.file.Path; @@ -30,7 +32,7 @@ * {@link PathWrapper} API. * *

This is not designed to be used by users. The API will handle wrapping paths internally for - * you. You may be interested in using {@link RamFileSystem}, however. + * you. You may be interested in using {@link RamDirectory}, however. * * @author Ashley Scopes * @since 0.0.1 diff --git a/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/package-info.java b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/package-info.java new file mode 100644 index 000000000..582ca56af --- /dev/null +++ b/java-compiler-testing/src/main/java/io/github/ascopes/jct/pathwrappers/impl/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 - 2022 Ashley Scopes + * + * 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. + */ +/** + * Internal objects for handling paths. + */ +package io.github.ascopes.jct.pathwrappers.impl; diff --git a/java-compiler-testing/src/main/java/module-info.java b/java-compiler-testing/src/main/java/module-info.java index c5f27d27f..0f78eb0c1 100644 --- a/java-compiler-testing/src/main/java/module-info.java +++ b/java-compiler-testing/src/main/java/module-info.java @@ -58,4 +58,6 @@ exports io.github.ascopes.jct.annotations to io.github.ascopes.jct.testing; exports io.github.ascopes.jct.containers.impl to io.github.ascopes.jct.testing; exports io.github.ascopes.jct.utils to io.github.ascopes.jct.testing; + exports io.github.ascopes.jct.pathwrappers.impl to io.github.ascopes.jct.testing; + opens io.github.ascopes.jct.pathwrappers.impl to io.github.ascopes.jct.testing; } diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicLegacyCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicLegacyCompilationIntegrationTest.java index 5520d9846..7b1cecb2e 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicLegacyCompilationIntegrationTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicLegacyCompilationIntegrationTest.java @@ -16,9 +16,10 @@ package io.github.ascopes.jct.testing.integration; import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem; +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory; +import static io.github.ascopes.jct.pathwrappers.TempDirectory.newTempDirectory; -import io.github.ascopes.jct.compilers.Compilable; +import io.github.ascopes.jct.compilers.Compiler; import io.github.ascopes.jct.junit.JavacCompilerTest; import org.junit.jupiter.api.DisplayName; @@ -30,10 +31,36 @@ @DisplayName("Basic legacy compilation integration tests") class BasicLegacyCompilationIntegrationTest { - @DisplayName("I can compile a 'Hello, World!' program") + @DisplayName("I can compile a 'Hello, World!' program using a RAM directory") @JavacCompilerTest - void helloWorldJavac(Compilable compiler) { - var sources = newRamFileSystem("sources") + void helloWorldJavacRamDirectory(Compiler compiler) { + var sources = newRamDirectory("sources") + .createFile("com/example/HelloWorld.java").withContents( + "package com.example;", + "public class HelloWorld {", + " public static void main(String[] args) {", + " System.out.println(\"Hello, World\");", + " }", + "}" + ); + + var compilation = compiler + .addSourcePath(sources) + .compile(); + + assertThatCompilation(compilation) + .isSuccessfulWithoutWarnings(); + + assertThatCompilation(compilation) + .classOutput().packages() + .fileExists("com/example/HelloWorld.class") + .isNotEmptyFile(); + } + + @DisplayName("I can compile a 'Hello, World!' program using a temp directory") + @JavacCompilerTest + void helloWorldJavacTempDirectory(Compiler compiler) { + var sources = newTempDirectory("sources") .createFile("com/example/HelloWorld.java").withContents( "package com.example;", "public class HelloWorld {", diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicModuleCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicModuleCompilationIntegrationTest.java index c0330cdce..3b4784276 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicModuleCompilationIntegrationTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicModuleCompilationIntegrationTest.java @@ -16,9 +16,10 @@ package io.github.ascopes.jct.testing.integration; import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem; +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory; +import static io.github.ascopes.jct.pathwrappers.TempDirectory.newTempDirectory; -import io.github.ascopes.jct.compilers.Compilable; +import io.github.ascopes.jct.compilers.Compiler; import io.github.ascopes.jct.junit.JavacCompilerTest; import org.junit.jupiter.api.DisplayName; @@ -30,10 +31,49 @@ @DisplayName("Basic module compilation integration tests") class BasicModuleCompilationIntegrationTest { - @DisplayName("I can compile a 'Hello, World!' module program") + @DisplayName("I can compile a 'Hello, World!' module program using a RAM disk") @JavacCompilerTest(modules = true) - void helloWorld(Compilable compiler) { - var sources = newRamFileSystem("hello.world") + void helloWorldRamDisk(Compiler compiler) { + var sources = newRamDirectory("hello.world") + .createFile("com/example/HelloWorld.java").withContents( + "package com.example;", + "public class HelloWorld {", + " public static void main(String[] args) {", + " System.out.println(\"Hello, World\");", + " }", + "}" + ) + .and().createFile("module-info.java").withContents( + "module hello.world {", + " requires java.base;", + " exports com.example;", + "}" + ); + + var compilation = compiler + .addSourcePath(sources) + .compile(); + + assertThatCompilation(compilation) + .isSuccessfulWithoutWarnings(); + + assertThatCompilation(compilation) + .classOutput() + .packages() + .fileExists("com/example/HelloWorld.class") + .isNotEmptyFile(); + + assertThatCompilation(compilation) + .classOutput() + .packages() + .fileExists("module-info.class") + .isNotEmptyFile(); + } + + @DisplayName("I can compile a 'Hello, World!' module program using a temporary directory") + @JavacCompilerTest(modules = true) + void helloWorldUsingTempDirectory(Compiler compiler) { + var sources = newTempDirectory("hello.world") .createFile("com/example/HelloWorld.java").withContents( "package com.example;", "public class HelloWorld {", diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicMultiModuleCompilationIntegrationTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicMultiModuleCompilationIntegrationTest.java index 44adc23ca..6b8cb1037 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicMultiModuleCompilationIntegrationTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/integration/BasicMultiModuleCompilationIntegrationTest.java @@ -16,9 +16,10 @@ package io.github.ascopes.jct.testing.integration; import static io.github.ascopes.jct.assertions.JctAssertions.assertThatCompilation; -import static io.github.ascopes.jct.pathwrappers.RamFileSystem.newRamFileSystem; +import static io.github.ascopes.jct.pathwrappers.RamDirectory.newRamDirectory; +import static io.github.ascopes.jct.pathwrappers.TempDirectory.newTempDirectory; -import io.github.ascopes.jct.compilers.Compilable; +import io.github.ascopes.jct.compilers.Compiler; import io.github.ascopes.jct.junit.JavacCompilerTest; import javax.tools.StandardLocation; import org.junit.jupiter.api.DisplayName; @@ -31,10 +32,10 @@ @DisplayName("Basic multi-module compilation integration tests") class BasicMultiModuleCompilationIntegrationTest { - @DisplayName("I can compile a single module using multi-module layout") + @DisplayName("I can compile a single module using multi-module layout using a RAM disk") @JavacCompilerTest(modules = true) - void singleModuleInMultiModuleLayout(Compilable compiler) { - var source = newRamFileSystem("hello.world") + void singleModuleInMultiModuleLayoutRamDisk(Compiler compiler) { + var source = newRamDirectory("hello.world") .createFile("com/example/HelloWorld.java").withContents( "package com.example;", "public class HelloWorld {", @@ -68,10 +69,110 @@ void singleModuleInMultiModuleLayout(Compilable compiler) { .fileExists("module-info.class").isNotEmptyFile(); } - @DisplayName("I can compile multiple modules using multi-module layout") + @DisplayName("I can compile a single module using multi-module layout using a temp directory") @JavacCompilerTest(modules = true) - void multipleModulesInMultiModuleLayout(Compilable compiler) { - var helloWorld = newRamFileSystem("hello.world") + void singleModuleInMultiModuleLayoutTempDirectory(Compiler compiler) { + var source = newTempDirectory("hello.world") + .createFile("com/example/HelloWorld.java").withContents( + "package com.example;", + "public class HelloWorld {", + " public static void main(String[] args) {", + " System.out.println(\"Hello, World\");", + " }", + "}" + ) + .and().createFile("module-info.java").withContents( + "module hello.world {", + " exports com.example;", + "}" + ); + + var compilation = compiler + .addPath(StandardLocation.MODULE_SOURCE_PATH, "hello.world", source) + .showDeprecationWarnings(true) + .compile(); + + assertThatCompilation(compilation) + .isSuccessfulWithoutWarnings(); + + assertThatCompilation(compilation) + .classOutput().modules() + .moduleExists("hello.world") + .fileExists("com/example/HelloWorld.class").isNotEmptyFile(); + + assertThatCompilation(compilation) + .classOutput().modules() + .moduleExists("hello.world") + .fileExists("module-info.class").isNotEmptyFile(); + } + + @DisplayName("I can compile multiple modules using multi-module layout using a RAM disk") + @JavacCompilerTest(modules = true) + void multipleModulesInMultiModuleLayoutRamDisk(Compiler compiler) { + var helloWorld = newRamDirectory("hello.world") + .createFile("com/example/HelloWorld.java").withContents( + "package com.example;", + "import com.example.greeter.Greeter;", + "public class HelloWorld {", + " public static void main(String[] args) {", + " System.out.println(Greeter.greet(\"World\"));", + " }", + "}" + ) + .and().createFile("module-info.java").withContents( + "module hello.world {", + " requires greeter;", + " exports com.example;", + "}" + ); + var greeter = newRamDirectory("greeter") + .createFile("com/example/greeter/Greeter.java").withContents( + "package com.example.greeter;", + "public class Greeter {", + " public static String greet(String name) {", + " return \"Hello, \" + name + \"!\";", + " }", + "}" + ) + .and().createFile("module-info.java").withContents( + "module greeter {", + " exports com.example.greeter;", + "}" + ); + + var compilation = compiler + .addModuleSourcePath("hello.world", helloWorld) + .addModuleSourcePath("greeter", greeter) + .compile(); + + assertThatCompilation(compilation) + .isSuccessfulWithoutWarnings(); + + assertThatCompilation(compilation) + .classOutput().modules() + .moduleExists("hello.world") + .fileExists("com/example/HelloWorld.class").isNotEmptyFile(); + + assertThatCompilation(compilation) + .classOutput().modules() + .moduleExists("hello.world") + .fileExists("module-info.class").isNotEmptyFile(); + + assertThatCompilation(compilation) + .classOutput().modules() + .moduleExists("greeter") + .fileExists("com/example/greeter/Greeter.class").isNotEmptyFile(); + + assertThatCompilation(compilation) + .classOutput().modules() + .moduleExists("greeter") + .fileExists("module-info.class").isNotEmptyFile(); + } + + @DisplayName("I can compile multiple modules using multi-module layout using a temp directory") + @JavacCompilerTest(modules = true) + void multipleModulesInMultiModuleLayoutTempDirectory(Compiler compiler) { + var helloWorld = newTempDirectory("hello.world") .createFile("com/example/HelloWorld.java").withContents( "package com.example;", "import com.example.greeter.Greeter;", @@ -87,7 +188,7 @@ void multipleModulesInMultiModuleLayout(Compilable compiler) { " exports com.example;", "}" ); - var greeter = newRamFileSystem("greeter") + var greeter = newRamDirectory("greeter") .createFile("com/example/greeter/Greeter.java").withContents( "package com.example.greeter;", "public class Greeter {", diff --git a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/unit/compilers/CompilableTest.java b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/unit/compilers/CompilableTest.java index 017cf9111..b8803421d 100644 --- a/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/unit/compilers/CompilableTest.java +++ b/java-compiler-testing/src/test/java/io/github/ascopes/jct/testing/unit/compilers/CompilableTest.java @@ -28,7 +28,7 @@ import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.withSettings; -import io.github.ascopes.jct.compilers.Compilable; +import io.github.ascopes.jct.compilers.Compiler; import io.github.ascopes.jct.pathwrappers.PathWrapper; import io.github.ascopes.jct.testing.helpers.TypeRef; import java.nio.file.Path; @@ -49,7 +49,7 @@ import org.mockito.quality.Strictness; /** - * {@link Compilable} tests. + * {@link Compiler} tests. * * @author Ashley Scopes */ @@ -58,15 +58,15 @@ class CompilableTest { @Mock - Compilable compiler; + Compiler compiler; @DisplayName("addClassPath(...) tests") @TestFactory Stream addClassPathTests() { return addPackagePathTestsFor( "addClassPath", - Compilable::addClassPath, - Compilable::addClassPath, + Compiler::addClassPath, + Compiler::addClassPath, StandardLocation.CLASS_PATH ); } @@ -76,8 +76,8 @@ Stream addClassPathTests() { Stream addModulePathTests() { return addModulePathTestsFor( "addModulePath", - Compilable::addModulePath, - Compilable::addModulePath, + Compiler::addModulePath, + Compiler::addModulePath, StandardLocation.MODULE_PATH ); } @@ -87,8 +87,8 @@ Stream addModulePathTests() { Stream addSourcePathTests() { return addPackagePathTestsFor( "addSourcePath", - Compilable::addSourcePath, - Compilable::addSourcePath, + Compiler::addSourcePath, + Compiler::addSourcePath, StandardLocation.SOURCE_PATH ); } @@ -98,8 +98,8 @@ Stream addSourcePathTests() { Stream addModuleSourcePathTests() { return addModulePathTestsFor( "addModuleSourcePath", - Compilable::addModuleSourcePath, - Compilable::addModuleSourcePath, + Compiler::addModuleSourcePath, + Compiler::addModuleSourcePath, StandardLocation.MODULE_SOURCE_PATH ); } @@ -290,7 +290,7 @@ static Stream addPackagePathTestsFor( () -> { // Given var pathLike = stub(PathWrapper.class); - Compilable compiler = mockCast( + Compiler compiler = mockCast( new TypeRef<>() {}, withSettings().strictness(Strictness.LENIENT) ); @@ -313,7 +313,7 @@ static Stream addPackagePathTestsFor( () -> { // Given var path = stub(Path.class); - Compilable compiler = mockCast( + Compiler compiler = mockCast( new TypeRef<>() {}, withSettings().strictness(Strictness.LENIENT) ); @@ -336,7 +336,7 @@ static Stream addPackagePathTestsFor( () -> { // Given var pathLike = stub(PathWrapper.class); - Compilable compiler = mockCast( + Compiler compiler = mockCast( new TypeRef<>() {}, withSettings().strictness(Strictness.LENIENT) ); @@ -356,7 +356,7 @@ static Stream addPackagePathTestsFor( () -> { // Given var path = stub(Path.class); - Compilable compiler = mockCast( + Compiler compiler = mockCast( new TypeRef<>() {}, withSettings().strictness(Strictness.LENIENT) ); @@ -415,7 +415,7 @@ static Stream addModulePathTestsFor( () -> { // Given var pathLike = stub(PathWrapper.class); - Compilable compiler = mockCast( + Compiler compiler = mockCast( new TypeRef<>() {}, withSettings().strictness(Strictness.LENIENT) ); @@ -438,7 +438,7 @@ static Stream addModulePathTestsFor( () -> { // Given var path = stub(Path.class); - Compilable compiler = mockCast( + Compiler compiler = mockCast( new TypeRef<>() {}, withSettings().strictness(Strictness.LENIENT) ); @@ -461,7 +461,7 @@ static Stream addModulePathTestsFor( () -> { // Given var pathLike = stub(PathWrapper.class); - Compilable compiler = mockCast( + Compiler compiler = mockCast( new TypeRef<>() {}, withSettings().strictness(Strictness.LENIENT) ); @@ -481,7 +481,7 @@ static Stream addModulePathTestsFor( () -> { // Given var path = stub(Path.class); - Compilable compiler = mockCast( + Compiler compiler = mockCast( new TypeRef<>() {}, withSettings().strictness(Strictness.LENIENT) ); @@ -509,12 +509,12 @@ static Stream addModulePathTestsFor( @FunctionalInterface interface AddPackagePathAliasMethod

{ - Compilable add(Compilable compiler, P path); + Compiler add(Compiler compiler, P path); } @FunctionalInterface interface AddModulePathAliasMethod

{ - Compilable add(Compilable compiler, String moduleName, P path); + Compiler add(Compiler compiler, String moduleName, P path); } }