Skip to content

Commit

Permalink
WIP refactor to CleanupMode
Browse files Browse the repository at this point in the history
  • Loading branch information
leonard84 committed Jan 24, 2024
1 parent f40650b commit 23c6c25
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.codehaus.groovy.runtime.MetaClassHelper;
import org.spockframework.runtime.GroovyRuntimeUtil;
import org.spockframework.util.CollectionUtil;

public class BuilderHelper {
public static Object createInstance(Class clazz, Object... args) {
Expand All @@ -31,6 +32,14 @@ public static Object createInstance(Class clazz, Object... args) {

// IDEA: could support creation of collection types here

if (clazz.isEnum()) {
if (args.length == 1 && args[0] instanceof String) {
return Enum.valueOf(clazz, (String) args[0]);
} else {
throw new IllegalArgumentException("Cannot create enum " + clazz.getName() + " with arguments " + args);
}
}

if ((clazz.getModifiers() & Modifier.ABSTRACT) != 0) {
String kind = clazz.isPrimitive() ? "primitive" : clazz.isInterface() ? "interface" : "abstract";
throw new RuntimeException(String.format( "Cannot instantiate %s type %s", kind, clazz.getName()));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://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 org.spockframework.runtime.extension.builtin;

import org.spockframework.runtime.extension.IAnnotationDrivenExtension;
import org.spockframework.runtime.extension.IMethodInterceptor;
import org.spockframework.runtime.model.*;
import org.spockframework.tempdir.TempDirConfiguration;
import org.spockframework.util.*;
import org.spockframework.util.Beta;
import org.spockframework.util.Checks;
import org.spockframework.util.UnreachableCodeError;
import spock.lang.TempDir;

import java.util.EnumSet;
Expand All @@ -25,21 +43,47 @@ public TempDirExtension(TempDirConfiguration configuration) {

@Override
public void visitFieldAnnotation(TempDir annotation, FieldInfo field) {
TempDirInterceptor interceptor = TempDirInterceptor.forField(field, configuration.baseDir, !annotation.cleanup() || configuration.keep);
TempDir.CleanupMode cleanupMode = annotation.cleanup();
cleanupMode = cleanupMode == TempDir.CleanupMode.DEFAULT ? configuration.cleanup : cleanupMode;
TempDirInterceptor interceptor = TempDirInterceptor.forField(field, configuration.baseDir, annotation, cleanupMode);

// attach interceptor
SpecInfo specInfo = field.getParent();
if (field.isShared()) {
specInfo.getBottomSpec().addSharedInitializerInterceptor(interceptor);
} else {
specInfo.addInitializerInterceptor(interceptor);
}
if (cleanupMode == TempDir.CleanupMode.ON_SUCCESS) {
registerForAllFeatures(specInfo, new TempDirInterceptor.FailureTracker(annotation));
}
}

private static void registerForAllFeatures(SpecInfo specInfo, IMethodInterceptor interceptor) {
specInfo.getBottomSpec().getAllFeatures().forEach(featureInfo -> featureInfo.getFeatureMethod().addInterceptor(interceptor));
}

@Override
public void visitParameterAnnotation(TempDir annotation, ParameterInfo parameter) {
Checks.checkArgument(VALID_METHOD_KINDS.contains(parameter.getParent().getKind()), () -> "@TempDir can only be used on setup, setupSpec or feature method parameters.");
TempDirInterceptor interceptor = TempDirInterceptor.forParameter(parameter, configuration.baseDir, configuration.keep);
parameter.getParent().addInterceptor(interceptor);
TempDir.CleanupMode cleanupMode = annotation.cleanup();
cleanupMode = cleanupMode == TempDir.CleanupMode.DEFAULT ? configuration.cleanup : cleanupMode;
MethodInfo methodInfo = parameter.getParent();
Checks.checkArgument(VALID_METHOD_KINDS.contains(methodInfo.getKind()), () -> "@TempDir can only be used on setup, setupSpec or feature method parameters.");
TempDirInterceptor interceptor = TempDirInterceptor.forParameter(parameter, configuration.baseDir, annotation, cleanupMode);
methodInfo.addInterceptor(interceptor);

if (cleanupMode == TempDir.CleanupMode.ON_SUCCESS) {
TempDirInterceptor.FailureTracker failureTracker = new TempDirInterceptor.FailureTracker(annotation);
switch (methodInfo.getKind()) {
case SETUP:
case SETUP_SPEC:
registerForAllFeatures(methodInfo.getParent(), failureTracker);
break;
case FEATURE:
methodInfo.addInterceptor(failureTracker);
break;
default:
throw new UnreachableCodeError();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://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 org.spockframework.runtime.extension.builtin;

import org.codehaus.groovy.runtime.ResourceGroovyMethods;
Expand All @@ -8,6 +23,7 @@
import org.spockframework.runtime.model.FieldInfo;
import org.spockframework.runtime.model.ParameterInfo;
import org.spockframework.util.*;
import spock.lang.TempDir;

import java.io.File;
import java.io.IOException;
Expand All @@ -17,10 +33,10 @@
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;
import java.util.regex.Pattern;

import static java.nio.file.FileVisitResult.CONTINUE;
import static org.spockframework.runtime.model.MethodInfo.MISSING_ARGUMENT;

/**
* @author dqyuan
Expand All @@ -36,18 +52,21 @@ public class TempDirInterceptor implements IMethodInterceptor {
private final IThrowableBiConsumer<IMethodInvocation, Path, Exception> valueSetter;
private final String name;
private final Path parentDir;
private final boolean keep;
private final TempDir annotation;
private final TempDir.CleanupMode cleanupMode;

private TempDirInterceptor(
IThrowableBiConsumer<IMethodInvocation, Path, Exception> valueSetter,
String name,
Path parentDir,
boolean keep) {
TempDir annotation,
TempDir.CleanupMode cleanupMode) {

this.valueSetter = valueSetter;
this.name = name;
this.parentDir = parentDir;
this.keep = keep;
this.annotation = annotation;
this.cleanupMode = cleanupMode;
}

private String dirPrefix(IMethodInvocation invocation) {
Expand Down Expand Up @@ -88,7 +107,9 @@ protected Path setUp(IMethodInvocation invocation) throws Exception {
@Override
public void intercept(IMethodInvocation invocation) throws Throwable {
Path path = setUp(invocation);
invocation.getStore(NAMESPACE).put(path, new TempDirContainer(path, keep));
// not super thrilled about identityHashCode, but apparently annotations use field equality and not identity
TempDirContainer old = invocation.getStore(NAMESPACE).put(System.identityHashCode(annotation), new TempDirContainer(path, cleanupMode));
Checks.checkState(old == null, () -> "Replaced other value: " + old.path);
invocation.proceed();
}

Expand Down Expand Up @@ -149,44 +170,87 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
}
}

static TempDirInterceptor forField(FieldInfo fieldInfo, Path parentDir, boolean keep) {
static TempDirInterceptor forField(FieldInfo fieldInfo, Path parentDir, TempDir annotation, TempDir.CleanupMode cleanupMode) {
IThrowableFunction<Path, ?, Exception> typeMapper = createPathToTypeMapper(fieldInfo.getType());
return new TempDirInterceptor(
(invocation, path) -> fieldInfo.writeValue(invocation.getInstance(), typeMapper.apply(path)),
fieldInfo.getName(),
parentDir,
keep);
annotation,
cleanupMode);
}

static TempDirInterceptor forParameter(ParameterInfo parameterInfo, Path parentDir, boolean keep) {
static TempDirInterceptor forParameter(ParameterInfo parameterInfo, Path parentDir, TempDir annotation, TempDir.CleanupMode cleanupMode) {
IThrowableFunction<Path, ?, Exception> typeMapper = createPathToTypeMapper(parameterInfo.getReflection().getType());
return new TempDirInterceptor(
(IMethodInvocation invocation, Path path) -> {
invocation.resolveArgument(parameterInfo.getIndex(), typeMapper.apply(path));
},
parameterInfo.getName(),
parentDir,
keep);
annotation,
cleanupMode);
}

static class FailureTracker implements IMethodInterceptor {
private final TempDir annotation;

FailureTracker(TempDir annotation) {
this.annotation = annotation;
}

@Override
public void intercept(IMethodInvocation invocation) throws Throwable {
TempDirContainer tempDirContainer = invocation.getStore(NAMESPACE).get(System.identityHashCode(annotation), TempDirContainer.class);
try {
invocation.proceed();
} catch (Throwable t) {
tempDirContainer.markFailed();
throw t;
}
}
}

static class TempDirContainer implements AutoCloseable {
private final Path path;
private final boolean keep;
private final TempDir.CleanupMode cleanupMode;
private volatile boolean failed = false;

TempDirContainer(Path path, boolean keep) {
TempDirContainer(Path path, TempDir.CleanupMode cleanupMode) {
this.path = path;
this.keep = keep;
this.cleanupMode = cleanupMode;
}

void markFailed() {
failed = true;
}

private void destroy(Path path) throws IOException {
switch (cleanupMode) {
case ON_SUCCESS:
if (failed) {
System.err.printf("TempDir '%s' not deleted because the test failed, please delete it manually after investigation.%n",
path.toAbsolutePath());
return;
}
case ALWAYS:
case DEFAULT:
deleteTempDir(path);
break;
case NEVER:
break;
default:
throw new IllegalStateException("Unknown cleanup mode: " + cleanupMode);
}
}

@Override
public void close() {
if (!keep) {
try {
deleteTempDir(path);
destroy(path);
} catch (IOException e) {
ExceptionUtil.sneakyThrow(e);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
/*
* Copyright 2009 the original author or authors.
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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
* https://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.
*
* https://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 org.spockframework.runtime.model;
Expand Down Expand Up @@ -54,4 +53,8 @@ public boolean isFeatureScopedFixtureMethod() {
public boolean isSpecScopedFixtureMethod() {
return this == SETUP_SPEC || this == CLEANUP_SPEC;
}

public boolean isInitializerMethod() {
return this == INITIALIZER || this == SHARED_INITIALIZER;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.spockframework.util.Beta;
import spock.config.ConfigurationObject;
import spock.lang.TempDir;

import java.nio.file.Path;
import java.util.Optional;

/**
*
Expand Down Expand Up @@ -32,8 +34,15 @@ public class TempDirConfiguration {
public Path baseDir = null;

/**
* Whether to keep the temp directory or not after test,
* default is system property {@code spock.tempDir.keep} or false if it is not set.
* Whether to keep the temp directory or not after test if it failed,
* default is system property {@link TempDir#TEMP_DIR_CLEANUP_PROPERTY} or {@link TempDir.CleanupMode#ALWAYS} if it is not set.
*
* @see TempDir#cleanup()
* @see TempDir#TEMP_DIR_CLEANUP_PROPERTY
*
* @since 2.3
*/
public boolean keep = Boolean.getBoolean("spock.tempDir.keep");
public TempDir.CleanupMode cleanup = Optional.ofNullable(System.getProperty(TempDir.TEMP_DIR_CLEANUP_PROPERTY))
.map(TempDir.CleanupMode::valueOf)
.orElse(TempDir.CleanupMode.ALWAYS);
}
25 changes: 24 additions & 1 deletion spock-core/src/main/java/spock/lang/TempDir.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,31 @@
@Target({ElementType.FIELD, ElementType.PARAMETER})
@ExtensionAnnotation(TempDirExtension.class)
public @interface TempDir {
String TEMP_DIR_CLEANUP_PROPERTY = "spock.tempdir.cleanup";

/**
* Whether to cleanup the directory after the test.
*
* @since 2.3
*/
boolean cleanup() default true;
CleanupMode cleanup() default CleanupMode.DEFAULT;

enum CleanupMode {
/**
* Use the default cleanup mode, configured via {@link #TEMP_DIR_CLEANUP_PROPERTY} or {@link org.spockframework.tempdir.TempDirConfiguration#cleanup}.
*/
DEFAULT,
/**
* Always cleanup the directory after the test.
*/
ALWAYS,
/**
* Cleanup the directory only if the test has succeeded.
*/
ON_SUCCESS,
/**
* Never cleanup the directory after the test.
*/
NEVER
}
}
Loading

0 comments on commit 23c6c25

Please sign in to comment.