Skip to content

Commit

Permalink
Rework Ares IOTester registration, make custom implementation possible
Browse files Browse the repository at this point in the history
This fixes #105 and should now be a completely satisfactory solution. We
could now also add a system property to set the default IOManager on
startup, if requested.
  • Loading branch information
MaisiKoleni committed Mar 24, 2022
1 parent 582371d commit e032d0e
Show file tree
Hide file tree
Showing 32 changed files with 533 additions and 101 deletions.
15 changes: 11 additions & 4 deletions src/main/java/de/tum/in/test/api/MirrorOutput.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;

import de.tum.in.test.api.io.IOManager;
import de.tum.in.test.api.io.IOTester;

/**
* This annotation can be applied to a class or method and tells the
* {@link IOTester}, whether to pipe the output to the original standard output
* as well (mirroring everything received). It does not affect the test result
* (unless the standard output throws an exception).
* {@link IOTester} (or an alternative implementation provided by
* {@link IOManager}), whether to pipe the output to the original standard
* output as well (mirroring everything received). It does not affect the test
* result (unless the standard output throws an exception).
* <p>
* A {@link MirrorOutput} annotation on a method always overrides the one on the
* class level.
Expand All @@ -27,7 +29,8 @@
*
* @author Christian Femers
* @since 0.1.0
* @version 1.2.0
* @version 1.2.1
* @see IOManager
*/
@API(status = Status.STABLE)
@Inherited
Expand All @@ -38,6 +41,10 @@

long DEFAULT_MAX_STD_OUT = 100_000_000L;

/**
* If the output should be mirrored and printed to the original standard output
* in addition to being recorded.
*/
MirrorOutputPolicy value() default MirrorOutputPolicy.ENABLED;

/**
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/de/tum/in/test/api/WithIOManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package de.tum.in.test.api;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;

import de.tum.in.test.api.io.AresIOContext;
import de.tum.in.test.api.io.IOManager;
import de.tum.in.test.api.io.IOTester;

/**
* Allows to overwrite the default IO test implementation of Ares with is using
* {@link IOTester}.
* <p>
* A custom {@link IOManager} class must have a constructor that takes no
* arguments and be accessible to Ares. All classes used for that purpose should
* be trusted/whitelisted.
*
* @author Christian Femers
* @since 1.9.1
* @version 1.0.0
* @see IOManager
*/
@API(status = Status.EXPERIMENTAL)
@Inherited
@Documented
@Retention(RUNTIME)
@Target({ TYPE, METHOD, ANNOTATION_TYPE })
public @interface WithIOManager {

/**
* The {@link IOManager} implementation to use for testing in the annotated
* element.
*/
Class<? extends IOManager<?>> value();

/**
* Effectively no {@link IOManager}. {@link System#out}, {@link System#err} and
* {@link System#in} are unchanged. Not recommended. Consider a custom but
* functional {@link IOManager} implementation first.
*/
public final class None implements IOManager<Void> {

@Override
public void beforeTestExecution(AresIOContext context) {
// do nothing
}

@Override
public void afterTestExecution(AresIOContext context) {
// do nothing
}

@Override
public Void getControllerInstance(AresIOContext context) {
return null;
}

@Override
public Class<Void> getControllerClass() {
return null;
}
}
}
26 changes: 26 additions & 0 deletions src/main/java/de/tum/in/test/api/context/AresContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package de.tum.in.test.api.context;

import java.util.Objects;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;

@API(status = Status.INTERNAL)
public abstract class AresContext {

private final TestContext testContext;

protected AresContext(TestContext testContext) {
this.testContext = Objects.requireNonNull(testContext);
}

/**
* Returns the current test context.
*
* @return the {@link TestContext}, never null.
* @author Christian Femers
*/
public final TestContext testContext() {
return testContext;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.tum.in.test.api.internal;
package de.tum.in.test.api.context;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
Expand All @@ -10,10 +10,15 @@
/**
* Adapter for different JUnit 5 test runner contexts. This interface provides
* the common properties of a test context.
* <p>
* All properties are optional as different test types and phases in the test
* life cycle of different test engines have different execution environments.
* While you can expect most properties to be present most of the time, use them
* defensively.
*
* @author Christian Femers
*/
@API(status = Status.INTERNAL)
@API(status = Status.MAINTAINED)
public abstract class TestContext {

public abstract Optional<Method> testMethod();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.tum.in.test.api.internal;
package de.tum.in.test.api.context;

import static org.junit.platform.commons.support.AnnotationSupport.*;

Expand All @@ -19,7 +19,7 @@
*
* @author Christian Femers
*/
@API(status = Status.INTERNAL)
@API(status = Status.MAINTAINED)
public final class TestContextUtils {

private TestContextUtils() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.tum.in.test.api.internal;
package de.tum.in.test.api.context;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;
Expand All @@ -17,9 +17,9 @@
* @see PublicTest
* @author Christian Femers
* @since 0.2.0
* @version 1.0.1
* @version 1.1.0
*/
@API(status = Status.INTERNAL)
@API(status = Status.MAINTAINED)
public enum TestType {
PUBLIC,
HIDDEN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import de.tum.in.test.api.WhitelistClass;
import de.tum.in.test.api.WhitelistPackage;
import de.tum.in.test.api.WhitelistPath;
import de.tum.in.test.api.context.TestContext;
import de.tum.in.test.api.context.TestContextUtils;
import de.tum.in.test.api.security.AresSecurityConfiguration;
import de.tum.in.test.api.security.AresSecurityConfigurationBuilder;
import de.tum.in.test.api.util.PackageRule;
Expand Down
87 changes: 87 additions & 0 deletions src/main/java/de/tum/in/test/api/internal/IOExtensionUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package de.tum.in.test.api.internal;

import static java.lang.invoke.MethodType.methodType;

import java.lang.annotation.AnnotationFormatError;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Objects;
import java.util.function.Supplier;

import org.apiguardian.api.API;
import org.apiguardian.api.API.Status;

import de.tum.in.test.api.WithIOManager;
import de.tum.in.test.api.context.TestContext;
import de.tum.in.test.api.context.TestContextUtils;
import de.tum.in.test.api.io.AresIOContext;
import de.tum.in.test.api.io.IOManager;
import de.tum.in.test.api.io.IOTesterManager;
import de.tum.in.test.api.security.ArtemisSecurityManager;

@API(status = Status.INTERNAL)
public final class IOExtensionUtils {

private static final Class<IOTesterManager> DEFAULT_IO_MANAGER = IOTesterManager.class;

static {
/*
* Initialize SecurityManager when we are still in the main thread
*/
ArtemisSecurityManager.isInstalled();
}

private static final HashMap<Class<? extends IOManager<?>>, Supplier<? extends IOManager<?>>> ioManagerCache = new HashMap<>();

private final AresIOContext context;
private final IOManager<?> ioManager;
private final Class<?> controllerClass;

public IOExtensionUtils(TestContext testContext) {
context = AresIOContext.from(testContext);
ioManager = createIOManagerFor(testContext);
controllerClass = ioManager.getControllerClass();
}

public void beforeTestExecution() {
ioManager.beforeTestExecution(context);
}

public void afterTestExecution() {
ioManager.afterTestExecution(context);
}

public boolean providesController() {
return controllerClass != null;
}

public Object getControllerInstance() {
return providesController() ? ioManager.getControllerInstance(context) : null;
}

public boolean canProvideControllerFor(Class<?> targetType) {
return providesController() && Objects.class != targetType && targetType.isAssignableFrom(controllerClass);
}

private static IOManager<?> createIOManagerFor(TestContext testContext) {
var ioManagerClass = TestContextUtils.findAnnotationIn(testContext, WithIOManager.class)
.<Class<? extends IOManager<?>>>map(WithIOManager::value).orElse(DEFAULT_IO_MANAGER);
return ioManagerCache.computeIfAbsent(ioManagerClass, IOExtensionUtils::generateIOManagerSupplier).get();
}

private static Supplier<IOManager<?>> generateIOManagerSupplier(Class<? extends IOManager<?>> ioManagerClass) {
try {
var lookup = MethodHandles.lookup();
var contructor = lookup.findConstructor(ioManagerClass, methodType(void.class));
var factory = LambdaMetafactory
.metafactory(lookup, "get", methodType(Supplier.class), contructor.type().generic(), contructor, //$NON-NLS-1$
contructor.type())
.getTarget();
return (Supplier<IOManager<?>>) factory.invokeExact();
} catch (Throwable e) {
throw new AnnotationFormatError("Could not create IOManager Supplier for type " //$NON-NLS-1$
+ ioManagerClass.getCanonicalName() + ". Make sure a public no-args constructor is available.", e); //$NON-NLS-1$
}
}
}
40 changes: 0 additions & 40 deletions src/main/java/de/tum/in/test/api/internal/IOTesterManager.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.tum.in.test.api.context.TestContext;
import de.tum.in.test.api.internal.sanitization.MessageTransformer;
import de.tum.in.test.api.internal.sanitization.ThrowableInfo;
import de.tum.in.test.api.internal.sanitization.ThrowableSanitizer;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/de/tum/in/test/api/internal/TestGuardUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import de.tum.in.test.api.ActivateHiddenBefore;
import de.tum.in.test.api.Deadline;
import de.tum.in.test.api.ExtendedDeadline;
import de.tum.in.test.api.context.TestContext;
import de.tum.in.test.api.context.TestType;

/**
* This class handles public/hidden tests and deadline evaluation.
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/de/tum/in/test/api/internal/TimeoutUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

import de.tum.in.test.api.PrivilegedExceptionsOnly;
import de.tum.in.test.api.StrictTimeout;
import de.tum.in.test.api.context.TestContext;
import de.tum.in.test.api.context.TestContextUtils;
import de.tum.in.test.api.security.ArtemisSecurityManager;

@API(status = Status.INTERNAL)
Expand Down
Loading

0 comments on commit e032d0e

Please sign in to comment.