Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grand unified theory of hooks #77

Merged
merged 19 commits into from
Dec 6, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would do both let and junitTestClass

/**
* 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();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a minimum, clear() should be in a finally block. Depending on the outcome of other discussions, after() may also need to be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

@Override
public T get() {
assertSpectrumInTestMode();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Borrowed from let not a bad thing to have around.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And by borrowed, I mean stolen, given that let is now a hook!


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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted from #56 - this is necessary for the Atomic to help us correctly.

/**
* 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.
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checkstyle forced me to write Javadoc as items stopped being internal when they switched packages.

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> {
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really want to modify aroundXXX to use this rather than ThrowingConsumer - we probably could, but for now I've left an adapter in place.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't done a release with ThrowingConsumer, so I wouldn't be too worried about protecting that API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing Consumer can die then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ThrowingSupplier on the other hand, was the item in question and I've removed it.. then restored it.

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> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept for posterity...

}


Expand Down