Skip to content

Commit

Permalink
WIP - initial work towards issue junit-pioneer#348
Browse files Browse the repository at this point in the history
  • Loading branch information
jbduncan committed Oct 8, 2021
1 parent 394a983 commit a7fa127
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/main/java/org/junitpioneer/jupiter/New.java
@@ -0,0 +1,25 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junitpioneer.jupiter;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
// TODO: Consider adding and testing ElementType.FIELD
@Target(ElementType.PARAMETER)
public @interface New {

Class<? extends ResourceFactory<?>> value();

}
22 changes: 22 additions & 0 deletions src/main/java/org/junitpioneer/jupiter/Resource.java
@@ -0,0 +1,22 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junitpioneer.jupiter;

public interface Resource<T> extends AutoCloseable {

T get() throws Exception;

@Override
default void close() throws Exception {
// no op by default
}

}
22 changes: 22 additions & 0 deletions src/main/java/org/junitpioneer/jupiter/ResourceFactory.java
@@ -0,0 +1,22 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junitpioneer.jupiter;

public interface ResourceFactory<T> extends AutoCloseable {

Resource<T> create() throws Exception;

@Override
default void close() throws Exception {
// no op by default
}

}
@@ -0,0 +1,65 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junitpioneer.jupiter;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.platform.commons.support.ReflectionSupport;

public class ResourceManagerExtension implements ParameterResolver {

private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace
.create(ResourceManagerExtension.class);

@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
// TODO: Only return true if the parameter is annotated with @New or @Shared
// TODO: Check that we're on a test method, rather than say a constructor or a before-each block?
// return parameterContext.isAnnotated(New.class);
return true;
}

@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
New newResourceAnnotation = parameterContext
.findAnnotation(New.class)
.orElseThrow(() -> {
// TODO
throw new UnsupportedOperationException("TODO");
});
ResourceFactory<?> resourceFactory = ReflectionSupport.newInstance(newResourceAnnotation.value());
// TODO: Put the resourceFactory in the store too?
Resource<?> resource;
try {
resource = resourceFactory.create();
}
catch (Exception e) {
// TODO
throw new UnsupportedOperationException("TODO");
}
// TODO: Check that we're using a custom NAMESPACE
extensionContext
.getStore(ExtensionContext.Namespace.GLOBAL)
.put("theResource", (ExtensionContext.Store.CloseableResource) resource::close);
try {
return resource.get();
}
catch (Exception e) {
// TODO
throw new UnsupportedOperationException("TODO");
}
}

}
76 changes: 76 additions & 0 deletions src/main/java/org/junitpioneer/jupiter/TemporaryDirectory.java
@@ -0,0 +1,76 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junitpioneer.jupiter;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class TemporaryDirectory implements ResourceFactory<Path> {

@Override
public Resource<Path> create() throws Exception {
return new Resource<Path>() {

private Path tempDir;

@Override
public Path get() throws Exception {
return (tempDir = Files.createTempDirectory(""));
}

@Override
public void close() throws Exception {
// TODO: Restore file permissions if needed.
// See: https://github.com/junit-team/junit5/issues/2609

deleteRecursively(tempDir);
}

};
}

@Override
public void close() throws Exception {
// TODO
throw new UnsupportedOperationException("TODO in TemporaryDirectory#close");
}

private void deleteRecursively(Path tempDir) throws IOException {
// TODO: See how JUnit 5 recursively deletes temp directories, and if there's anything
// it does that we don't, write unit tests to reproduce their behaviour.
Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// TODO: Can we unit test that we don't throw an exception if
// the dir being deleted doesn't exist anymore due to
// a race condition?
Files.delete(file);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
// TODO: Can we unit test that we don't throw an exception if
// the dir being deleted doesn't exist anymore due to
// a race condition?
Files.delete(dir);
return FileVisitResult.CONTINUE;
}

});
}

}
@@ -0,0 +1,89 @@
/*
* Copyright 2016-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* http://www.eclipse.org/legal/epl-v20.html
*/

package org.junitpioneer.jupiter;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;

@DisplayName("Resource manager extension")
@ExtendWith(ResourceManagerExtension.class)
// TODO: Do we need a test that checks a test case with LifeCycle.PER_METHOD? Ask maintainers.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class ResourceManagerExtensionTests {

private Path firstRecordedTempDir;
private final List<Path> recordedTempDirs = new CopyOnWriteArrayList<>();

// TODO: Consider adding a constructor with a @New(TemporaryDirectory.class) Path

@DisplayName("should populate a @New(TemporaryDirectory.class)-annotated parameter with a temp dir resource")
@Order(0)
@Test
void shouldPopulateNewAnnotatedParameterWithTempDirResource(@New(TemporaryDirectory.class) Path tempDir) {
assertThat(tempDir).startsWith(Paths.get(System.getProperty("java.io.tmpdir")));

firstRecordedTempDir = tempDir;
recordedTempDirs.add(tempDir);
}

@DisplayName("should tear down the new resource at the end")
@Order(1)
@Test
void shouldTearDownNewResourceAtTheEnd() {
assertThat(firstRecordedTempDir).doesNotExist();
}

@DisplayName("should populate a second @New(TemporaryDirectory.class)-annotated parameter with a temp dir resource")
@Order(2)
@Test
void shouldPopulateSecondNewAnnotatedParameterWithTempDirResource(@New(TemporaryDirectory.class) Path tempDir) {
assertThat(tempDir).startsWith(Paths.get(System.getProperty("java.io.tmpdir")));

recordedTempDirs.add(tempDir);
}

@DisplayName("should populate @New(TemporaryDirectory.class)-annotated parameters with writeable temp dirs")
@Order(2)
@Test
void shouldPopulateNewAnnotatedParametersWithWriteableTempDirs(@New(TemporaryDirectory.class) Path tempDir)
throws Exception {
Files.write(tempDir.resolve("file.txt"), "some random text".getBytes(UTF_8));
assertThat(tempDir.resolve("file.txt")).usingCharset(UTF_8).hasContent("some random text");

recordedTempDirs.add(tempDir);
}

@DisplayName("should have generated new resources each time @New was used")
@Order(3)
@Test
void shouldHaveGeneratedNewResourcesEachTimeNewAnnotationWasUsed() {
int numberOfNewTempDirsInThisClass = 3;
assertThat(recordedTempDirs).hasSize(numberOfNewTempDirsInThisClass);
assertThat(recordedTempDirs.stream().distinct()).hasSize(numberOfNewTempDirsInThisClass);
}

// TODO: Write and test with two custom ResourceFactory implementations: jimfs and OkHttp's MockWebServer
}

0 comments on commit a7fa127

Please sign in to comment.