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 extends JavaFileObject> 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 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 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 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 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 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 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 {
- 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);
}
}