Skip to content

Commit

Permalink
First cut - demonstrates the hooks mechanism.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ashley Frieze committed Dec 4, 2016
1 parent 903d5a6 commit d947193
Show file tree
Hide file tree
Showing 31 changed files with 581 additions and 139 deletions.
47 changes: 47 additions & 0 deletions src/main/java/com/greghaskins/spectrum/AbstractSupplyingHook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.greghaskins.spectrum;

import static com.greghaskins.spectrum.Spectrum.assertSpectrumInTestMode;

import com.greghaskins.spectrum.model.Singleton;

/**
* A base class for supplying hooks to use. Override before or after. Return the singleton
* value from the before method.
* You can use this to write any plugin which needs to make a value visible to the specs.
* This is not the only way to achieve that - you can also build from {@link SupplyingHook}
* but this captures the template for a complex hook.
*/
public class AbstractSupplyingHook<T> extends Singleton<T> implements SupplyingHook<T> {
/**
* Override this to supply behaviour for before the block is run.
* @return the value that the singleton will store to supply
*/
protected T before() {
return null;
}

/**
* Override this to supply behaviour for after the block is run.
*/
protected void after() {}

/**
* Template method for a hook which supplies.
* @param block the inner block that will be run
* @throws Throwable on error
*/
@Override
public void acceptOrThrow(Block block) throws Throwable {
set(before());
block.run();
after();
clear();
}

@Override
public T get() {
assertSpectrumInTestMode();

return super.get();
}
}
41 changes: 41 additions & 0 deletions src/main/java/com/greghaskins/spectrum/CompositeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.greghaskins.spectrum;

import com.greghaskins.spectrum.internal.Atomic;
import com.greghaskins.spectrum.internal.Child;
import com.greghaskins.spectrum.internal.FailureDetectingRunListener;
import com.greghaskins.spectrum.internal.Parent;
import com.greghaskins.spectrum.model.TaggingState;

import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;

/**
* Subclass of {@link Suite} that represent the fact that some tests are composed
* of interrelated steps which add up to a single test.
*/
final class CompositeTest extends Suite implements Atomic {
/**
* Constructs a Composite Test, which is a suite run as an atomic test.
* @param description of the test
* @param parent parent suite
* @param tagging tagging state to inherit from parent
*/
CompositeTest(final Description description, final Parent parent, final TaggingState tagging) {
super(description, parent, CompositeTest::abortOnFailureChildRunner, tagging);
}

private static void abortOnFailureChildRunner(final Suite suite, final RunNotifier runNotifier) {
FailureDetectingRunListener listener = new FailureDetectingRunListener();
runNotifier.addListener(listener);
try {
for (Child child : suite.children) {
if (listener.hasFailedYet()) {
child.ignore();
}
suite.runChild(child, runNotifier);
}
} finally {
runNotifier.removeListener(listener);
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/greghaskins/spectrum/Configuration.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.greghaskins.spectrum;

/**
* Allows the injection of suite configuration during test definition. A wrapper for the
* suite which exposes configurables.
*/
public class Configuration {

private final Suite suite;
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/greghaskins/spectrum/Hook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.greghaskins.spectrum;

/**
* A hook allows you to inject functionality before and/or after a {@link Block}.
* Just implement the {@link ThrowingConsumer#acceptOrThrow(Object)} method and
* call {@link Block#run()} within your implementation.
* If your hook is going to provide an object to the running test, then implement
* {@link SupplyingHook} or subclass {@link AbstractSupplyingHook}.
*/
public interface Hook extends ThrowingConsumer<Block> {
}
26 changes: 0 additions & 26 deletions src/main/java/com/greghaskins/spectrum/NotifyingBlock.java

This file was deleted.

18 changes: 0 additions & 18 deletions src/main/java/com/greghaskins/spectrum/Parent.java

This file was deleted.

7 changes: 6 additions & 1 deletion src/main/java/com/greghaskins/spectrum/Spec.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.greghaskins.spectrum;

import com.greghaskins.spectrum.internal.Atomic;
import com.greghaskins.spectrum.internal.Child;
import com.greghaskins.spectrum.internal.NotifyingBlock;
import com.greghaskins.spectrum.internal.Parent;

import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;

final class Spec implements Child {
final class Spec implements Child, Atomic {

private final NotifyingBlock block;
private final Description description;
Expand Down
59 changes: 26 additions & 33 deletions src/main/java/com/greghaskins/spectrum/Spectrum.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.greghaskins.spectrum;

import static com.greghaskins.spectrum.PreConditionBlock.with;
import static com.greghaskins.spectrum.PreConditions.Factory.focus;
import static com.greghaskins.spectrum.PreConditions.Factory.ignore;
import static com.greghaskins.spectrum.internal.PreConditionBlock.with;
import static com.greghaskins.spectrum.model.PreConditions.Factory.focus;
import static com.greghaskins.spectrum.model.PreConditions.Factory.ignore;

import com.greghaskins.spectrum.internal.LetHook;
import com.greghaskins.spectrum.model.ConstructorBlock;
import com.greghaskins.spectrum.model.HookContext;

import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
Expand Down Expand Up @@ -249,17 +253,24 @@ public static void afterAll(final com.greghaskins.spectrum.Block block) {
* `Throwable`
* @return memoized supplier
*/
public static <T> Supplier<T> let(final ThrowingSupplier<T> supplier) {
final ConcurrentHashMap<Supplier<T>, T> cache = new ConcurrentHashMap<>(1);
afterEach(cache::clear);

return () -> {
if (getCurrentSuiteBeingDeclared() == null) {
return cache.computeIfAbsent(supplier, Supplier::get);
}
throw new IllegalStateException("Cannot use the value from let() in a suite declaration. "
public static <T> Supplier<T> let(final com.greghaskins.spectrum.ThrowingSupplier<T> supplier) {
LetHook<T> letHook = new LetHook<>(supplier);
HookContext hookContext = new HookContext(letHook, false, false, true);
getCurrentSuiteBeingDeclared().addHook(hookContext);

return letHook;
}

/**
* Will throw an exception if this method happens to be called while Spectrum is still defining
* tests, rather than executing them. Useful to see if a hook is being accidentally used
* during definition.
*/
public static void assertSpectrumInTestMode() {
if (getCurrentSuiteBeingDeclared() != null) {
throw new IllegalStateException("Cannot use this statement in a suite declaration. "
+ "It may only be used in the context of a running spec.");
};
}
}

/**
Expand All @@ -271,27 +282,9 @@ public static <T> Supplier<T> let(final ThrowingSupplier<T> supplier) {
*
* @param <T> The type of result that will be supplied
*/
@Deprecated
@FunctionalInterface
public interface ThrowingSupplier<T> extends Supplier<T> {

/**
* Get a result.
*
* @return a result
* @throws Throwable any uncaught Error or Exception
*/
T getOrThrow() throws Throwable;

@Override
default T get() {
try {
return getOrThrow();
} catch (final RuntimeException | Error unchecked) {
throw unchecked;
} catch (final Throwable checked) {
throw new RuntimeException(checked);
}
}
public interface ThrowingSupplier<T> extends com.greghaskins.spectrum.ThrowingSupplier<T> {
}


Expand Down

0 comments on commit d947193

Please sign in to comment.