From aa3dd3212b134bb99d445ed678109acf6dd98418 Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sat, 30 Jan 2016 12:02:01 -0500 Subject: [PATCH 01/12] Beginnings of new new approach --- .../java/com/greghaskins/spectrum/Spec.java | 33 +++++++++++ .../java/com/greghaskins/spectrum/Suite.java | 56 +++++++++++++++++++ .../com/greghaskins/spectrum/Trilogy.java | 11 ++++ 3 files changed, 100 insertions(+) create mode 100644 src/main/java/com/greghaskins/spectrum/Spec.java create mode 100644 src/main/java/com/greghaskins/spectrum/Suite.java create mode 100644 src/main/java/com/greghaskins/spectrum/Trilogy.java diff --git a/src/main/java/com/greghaskins/spectrum/Spec.java b/src/main/java/com/greghaskins/spectrum/Spec.java new file mode 100644 index 0000000..4ef4217 --- /dev/null +++ b/src/main/java/com/greghaskins/spectrum/Spec.java @@ -0,0 +1,33 @@ +package com.greghaskins.spectrum; + +import com.greghaskins.spectrum.Spectrum.Block; + +class Spec implements Trilogy { + + private final Block setup; + private final Block teardown; + private final Block test; + + public Spec(final Block setup, final Block test, final Block teardown) { + this.setup = setup; + this.test = test; + this.teardown = teardown; + } + + @Override + public void setUp() throws Throwable { + this.setup.run(); + } + + @Override + public void run() throws Throwable { + this.test.run(); + } + + @Override + public void tearDown() throws Throwable { + this.teardown.run(); + } + + +} diff --git a/src/main/java/com/greghaskins/spectrum/Suite.java b/src/main/java/com/greghaskins/spectrum/Suite.java new file mode 100644 index 0000000..7e4e070 --- /dev/null +++ b/src/main/java/com/greghaskins/spectrum/Suite.java @@ -0,0 +1,56 @@ +package com.greghaskins.spectrum; + +import java.util.ArrayList; +import java.util.List; + +import com.greghaskins.spectrum.Spectrum.Block; + +class Suite implements Trilogy { + + private final List beforeAll = new ArrayList<>(); + private final List afterAll = new ArrayList<>(); + + private final List beforeEach = new ArrayList<>(); + private final List afterEach = new ArrayList<>(); + + private final List children = new ArrayList<>(); + + @Override + public void setUp() throws Throwable { + for (final Block block : this.beforeAll) { + block.run(); + } + } + + @Override + public void run() throws Throwable { + for (final Trilogy trilogy : this.children) { + trilogy.setUp(); + try { + trilogy.run(); + } finally { + trilogy.tearDown(); + } + } + } + + @Override + public void tearDown() throws Throwable { + for (final Block block : this.afterAll) { + block.run(); + } + } + + public void addSuite(final Suite child) { + this.children.add(child); + } + + public void addSpec(final String behavior, final Block block) { + this.children.add(new Spec( + new CompositeBlock(this.beforeEach), + block, + new CompositeBlock(this.afterEach) + )); + } + +} diff --git a/src/main/java/com/greghaskins/spectrum/Trilogy.java b/src/main/java/com/greghaskins/spectrum/Trilogy.java new file mode 100644 index 0000000..03b21f8 --- /dev/null +++ b/src/main/java/com/greghaskins/spectrum/Trilogy.java @@ -0,0 +1,11 @@ +package com.greghaskins.spectrum; + +interface Trilogy { + + void setUp() throws Throwable; + + void run() throws Throwable; + + void tearDown() throws Throwable; + +} From a41a8b8b612720db74c11323cba55ada1d222fad Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 08:28:19 -0500 Subject: [PATCH 02/12] Create Suite + Spec classes as Runners Using the terms suite/spec to match Jasmine --- .../greghaskins/spectrum/CompositeBlock.java | 19 ++-- .../com/greghaskins/spectrum/Context.java | 51 ++++----- .../java/com/greghaskins/spectrum/Spec.java | 40 ++++--- .../java/com/greghaskins/spectrum/Suite.java | 102 ++++++++++++------ 4 files changed, 135 insertions(+), 77 deletions(-) diff --git a/src/main/java/com/greghaskins/spectrum/CompositeBlock.java b/src/main/java/com/greghaskins/spectrum/CompositeBlock.java index 334dfd8..bd89cda 100644 --- a/src/main/java/com/greghaskins/spectrum/CompositeBlock.java +++ b/src/main/java/com/greghaskins/spectrum/CompositeBlock.java @@ -1,26 +1,31 @@ package com.greghaskins.spectrum; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import com.greghaskins.spectrum.Spectrum.Block; class CompositeBlock implements Block { - private final Iterable blocks; + private final List blocks; - public CompositeBlock(final Iterable blocks) { + public CompositeBlock(final List blocks) { this.blocks = blocks; } - public CompositeBlock(final Block... blocks) { - this(Arrays.asList(blocks)); - } + public CompositeBlock() { + this(new ArrayList<>()); + } @Override public void run() throws Throwable { - for (final Block block : blocks) { + for (final Block block : this.blocks) { block.run(); } } + public void addBlock(final Block block) { + this.blocks.add(block); + } + } diff --git a/src/main/java/com/greghaskins/spectrum/Context.java b/src/main/java/com/greghaskins/spectrum/Context.java index 6fa5368..247d404 100644 --- a/src/main/java/com/greghaskins/spectrum/Context.java +++ b/src/main/java/com/greghaskins/spectrum/Context.java @@ -1,5 +1,7 @@ package com.greghaskins.spectrum; +import static java.util.Arrays.asList; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -15,28 +17,28 @@ class Context implements Executable { private final Description description; private final List setupBlocks; private final List teardownBlocks; - private final List contextSetupBlocks; + private final List contextSetupBlocks; private final List contextTeardownBlocks; private final Deque executables; public Context(final Description description) { this.description = description; - setupBlocks = new ArrayList(); - teardownBlocks = new ArrayList(); - contextSetupBlocks = new ArrayList(); - contextTeardownBlocks = new ArrayList(); + this.setupBlocks = new ArrayList(); + this.teardownBlocks = new ArrayList(); + this.contextSetupBlocks = new ArrayList(); + this.contextTeardownBlocks = new ArrayList(); - executables = new ArrayDeque(); + this.executables = new ArrayDeque(); } @Override public void execute(final RunNotifier notifier) { - if (descriptionHasAnyTests(description)) { + if (descriptionHasAnyTests(this.description)) { addExecutablesForFixtureLevelSetupAndTeardown(); } else { - notifier.fireTestIgnored(description); + notifier.fireTestIgnored(this.description); } - for (final Executable child : executables) { + for (final Executable child : this.executables) { child.execute(notifier); } } @@ -55,45 +57,44 @@ private boolean isTest(final Description child) { } private void addExecutablesForFixtureLevelSetupAndTeardown() { - executables.addFirst(new BlockExecutable(description, new CompositeBlock(contextSetupBlocks))); - executables.addLast(new BlockExecutable(description, new CompositeBlock(contextTeardownBlocks))); + this.executables.addFirst(new BlockExecutable(this.description, new CompositeBlock(this.contextSetupBlocks))); + this.executables.addLast(new BlockExecutable(this.description, new CompositeBlock(this.contextTeardownBlocks))); } public void addTestSetup(final Block block) { - setupBlocks.add(block); + this.setupBlocks.add(block); } public void addTestTeardown(final Block block) { - teardownBlocks.add(block); + this.teardownBlocks.add(block); } public void addContextSetup(final Block block) { - contextSetupBlocks.add(new RunOnceBlock(block)); + this.contextSetupBlocks.add(new RunOnceBlock(block)); } public void addContextTeardown(final Block block) { - contextTeardownBlocks.add(block); + this.contextTeardownBlocks.add(block); } public void addTest(final String behavior, final Block block) { - final Description testDescription = Description.createTestDescription(description.getClassName(), behavior); + final Description testDescription = Description.createTestDescription(this.description.getClassName(), behavior); final CompositeBlock testBlock = putTestBlockInContext(block); final Test test = new Test(testDescription, testBlock); - description.addChild(testDescription); - executables.add(test); + this.description.addChild(testDescription); + this.executables.add(test); } private CompositeBlock putTestBlockInContext(final Block testBlock) { - return new CompositeBlock(new CompositeBlock(contextSetupBlocks), new CompositeBlock(setupBlocks), testBlock, - new CompositeBlock(teardownBlocks)); + return new CompositeBlock(asList(new CompositeBlock(this.contextSetupBlocks), new CompositeBlock(this.setupBlocks), testBlock, new CompositeBlock(this.teardownBlocks))); } public void addChild(final Context childContext) { - description.addChild(childContext.description); - childContext.addContextSetup(new CompositeBlock(contextSetupBlocks)); - childContext.addTestSetup(new CompositeBlock(setupBlocks)); - childContext.addTestTeardown(new CompositeBlock(teardownBlocks)); - executables.add(childContext); + this.description.addChild(childContext.description); + childContext.addContextSetup(new CompositeBlock(this.contextSetupBlocks)); + childContext.addTestSetup(new CompositeBlock(this.setupBlocks)); + childContext.addTestTeardown(new CompositeBlock(this.teardownBlocks)); + this.executables.add(childContext); } diff --git a/src/main/java/com/greghaskins/spectrum/Spec.java b/src/main/java/com/greghaskins/spectrum/Spec.java index 4ef4217..b3a965a 100644 --- a/src/main/java/com/greghaskins/spectrum/Spec.java +++ b/src/main/java/com/greghaskins/spectrum/Spec.java @@ -1,33 +1,43 @@ package com.greghaskins.spectrum; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; + import com.greghaskins.spectrum.Spectrum.Block; -class Spec implements Trilogy { +class Spec extends Runner { + + private final Block block; + private final Description description; - private final Block setup; - private final Block teardown; - private final Block test; - public Spec(final Block setup, final Block test, final Block teardown) { - this.setup = setup; - this.test = test; - this.teardown = teardown; + public Spec(final Description description, final Block block) { + this.description = description; + this.block = block; } @Override - public void setUp() throws Throwable { - this.setup.run(); + public Description getDescription() { + return this.description; } @Override - public void run() throws Throwable { - this.test.run(); + public void run(final RunNotifier notifier) { + notifier.fireTestStarted(this.description); + try { + this.block.run(); + } catch (final Throwable e) { + notifier.fireTestFailure(new Failure(this.description, e)); + } + notifier.fireTestFinished(this.description); } + @Override - public void tearDown() throws Throwable { - this.teardown.run(); + public int testCount() { + return 1; } - } diff --git a/src/main/java/com/greghaskins/spectrum/Suite.java b/src/main/java/com/greghaskins/spectrum/Suite.java index 7e4e070..c01a0de 100644 --- a/src/main/java/com/greghaskins/spectrum/Suite.java +++ b/src/main/java/com/greghaskins/spectrum/Suite.java @@ -1,56 +1,98 @@ package com.greghaskins.spectrum; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; + import com.greghaskins.spectrum.Spectrum.Block; -class Suite implements Trilogy { +class Suite extends Runner { - private final List beforeAll = new ArrayList<>(); - private final List afterAll = new ArrayList<>(); + private final CompositeBlock beforeAll = new CompositeBlock(); + private final CompositeBlock afterAll = new CompositeBlock(); - private final List beforeEach = new ArrayList<>(); - private final List afterEach = new ArrayList<>(); + private final CompositeBlock beforeEach = new CompositeBlock(); + private final CompositeBlock afterEach = new CompositeBlock(); - private final List children = new ArrayList<>(); + private final List children = new ArrayList<>(); - @Override - public void setUp() throws Throwable { - for (final Block block : this.beforeAll) { - block.run(); - } + private final Description description; + + public Suite(final Description description) { + this.description = description; + } + + public Suite addSuite(final String name, final Block block) { + final Suite suite = new Suite(Description.createSuiteDescription(name)); + addChild(suite); + return suite; + } + + public Spec addSpec(final String name, final Block block) { + final CompositeBlock specBlockInContext = new CompositeBlock(Arrays.asList(this.beforeEach, block, this.afterEach)); + final Spec spec = new Spec(Description.createTestDescription(this.description.getClassName(), name), specBlockInContext); + addChild(spec); + return spec; + } + + private void addChild(final Runner runner) { + this.description.addChild(runner.getDescription()); + this.children.add(runner); + } + + public void beforeAll(final Block block) { + this.beforeAll.addBlock(block); + } + + public void afterAll(final Block block) { + this.afterAll.addBlock(block); + } + + public void beforeEach(final Block block) { + this.beforeEach.addBlock(block); + } + + public void afterEach(final Block block) { + this.afterEach.addBlock(block); } @Override - public void run() throws Throwable { - for (final Trilogy trilogy : this.children) { - trilogy.setUp(); - try { - trilogy.run(); - } finally { - trilogy.tearDown(); - } + public void run(final RunNotifier notifier) { + if (this.testCount() == 0) { + notifier.fireTestIgnored(this.description); + return; } + + runOrFail(this.beforeAll, notifier, "error in beforeAll"); + this.children.stream().forEach((child) -> child.run(notifier)); + runOrFail(this.afterAll, notifier, "error in afterAll"); } - @Override - public void tearDown() throws Throwable { - for (final Block block : this.afterAll) { + private void runOrFail(final Block block, final RunNotifier notifier, final String failureMessage) { + try { block.run(); + } catch (final Throwable e) { + final Description failureDescription = Description.createTestDescription(this.description.getClassName(), failureMessage); + this.description.addChild(failureDescription); + notifier.fireTestFailure(new Failure(failureDescription, e)); } } - public void addSuite(final Suite child) { - this.children.add(child); + @Override + public Description getDescription() { + return this.description; } - public void addSpec(final String behavior, final Block block) { - this.children.add(new Spec( - new CompositeBlock(this.beforeEach), - block, - new CompositeBlock(this.afterEach) - )); + @Override + public int testCount() { + return this.children.stream().mapToInt((child) -> { return child.testCount(); }).sum(); } + + } From bdffc3082bcb9ebd6cd64a8165a18a5890a73b87 Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 10:25:46 -0500 Subject: [PATCH 03/12] Properly handle beforeAll in Suite Needs to mark all descendent specs as failures, not just direct children. --- .../java/com/greghaskins/spectrum/Suite.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/greghaskins/spectrum/Suite.java b/src/main/java/com/greghaskins/spectrum/Suite.java index c01a0de..dd4e194 100644 --- a/src/main/java/com/greghaskins/spectrum/Suite.java +++ b/src/main/java/com/greghaskins/spectrum/Suite.java @@ -25,17 +25,25 @@ class Suite extends Runner { public Suite(final Description description) { this.description = description; + beforeEach(new RunOnceBlock(this.beforeAll)); } - public Suite addSuite(final String name, final Block block) { - final Suite suite = new Suite(Description.createSuiteDescription(name)); + public Suite addSuite(final String name) { + return addSuite(Description.createSuiteDescription(name)); + } + + public Suite addSuite(final Description description) { + final Suite suite = new Suite(description); + suite.beforeEach(this.beforeEach); + suite.afterEach(this.afterEach); addChild(suite); return suite; } public Spec addSpec(final String name, final Block block) { final CompositeBlock specBlockInContext = new CompositeBlock(Arrays.asList(this.beforeEach, block, this.afterEach)); - final Spec spec = new Spec(Description.createTestDescription(this.description.getClassName(), name), specBlockInContext); + final Description specDescription = Description.createTestDescription(this.description.getClassName(), name); + final Spec spec = new Spec(specDescription, specBlockInContext); addChild(spec); return spec; } @@ -65,19 +73,22 @@ public void afterEach(final Block block) { public void run(final RunNotifier notifier) { if (this.testCount() == 0) { notifier.fireTestIgnored(this.description); - return; + runChildren(notifier); + } else { + runChildren(notifier); + runAfterAll(notifier); } + } - runOrFail(this.beforeAll, notifier, "error in beforeAll"); + private void runChildren(final RunNotifier notifier) { this.children.stream().forEach((child) -> child.run(notifier)); - runOrFail(this.afterAll, notifier, "error in afterAll"); } - private void runOrFail(final Block block, final RunNotifier notifier, final String failureMessage) { + private void runAfterAll(final RunNotifier notifier) { try { - block.run(); + this.afterAll.run(); } catch (final Throwable e) { - final Description failureDescription = Description.createTestDescription(this.description.getClassName(), failureMessage); + final Description failureDescription = Description.createTestDescription(this.description.getClassName(), "error in afterAll"); this.description.addChild(failureDescription); notifier.fireTestFailure(new Failure(failureDescription, e)); } @@ -93,6 +104,4 @@ public int testCount() { return this.children.stream().mapToInt((child) -> { return child.testCount(); }).sum(); } - - } From 43592f1120c87931c7654b99ad87d0dfaeb4968b Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 10:32:14 -0500 Subject: [PATCH 04/12] Switch to suite implementation Works similarly to before, but has a simpler structure. Needed up update one failing test with incorrect expectation (should only fail once per spec, not per suite too) --- .../com/greghaskins/spectrum/Spectrum.java | 43 +++++++++---------- src/test/java/specs/FixturesSpec.java | 21 ++++++--- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/greghaskins/spectrum/Spectrum.java b/src/main/java/com/greghaskins/spectrum/Spectrum.java index 6fafd39..74088be 100644 --- a/src/main/java/com/greghaskins/spectrum/Spectrum.java +++ b/src/main/java/com/greghaskins/spectrum/Spectrum.java @@ -33,8 +33,8 @@ public static interface Block { * */ public static void describe(final String context, final Block block) { - final Context newContext = new Context(Description.createSuiteDescription(context)); - enterContext(newContext, block); + final Suite suite = getCurrentContext().addSuite(context); + enterContext(suite, block); } /** @@ -47,7 +47,7 @@ public static void describe(final String context, final Block block) { * if that expectation is not met. */ public static void it(final String behavior, final Block block) { - getCurrentContext().addTest(behavior, block); + getCurrentContext().addSpec(behavior, block); } /** @@ -62,7 +62,7 @@ public static void it(final String behavior, final Block block) { * {@link Block} to run before each test */ public static void beforeEach(final Block block) { - getCurrentContext().addTestSetup(block); + getCurrentContext().beforeEach(block); } /** @@ -77,7 +77,7 @@ public static void beforeEach(final Block block) { * {@link Block} to run after each test */ public static void afterEach(final Block block) { - getCurrentContext().addTestTeardown(block); + getCurrentContext().afterEach(block); } /** @@ -92,7 +92,7 @@ public static void afterEach(final Block block) { * {@link Block} to run once before all tests */ public static void beforeAll(final Block block) { - getCurrentContext().addContextSetup(block); + getCurrentContext().beforeAll(block); } /** @@ -107,7 +107,7 @@ public static void beforeAll(final Block block) { * {@link Block} to run once after all tests */ public static void afterAll(final Block block) { - getCurrentContext().addContextTeardown(block); + getCurrentContext().afterAll(block); } public static Value value(@SuppressWarnings("unused") final Class type) { @@ -126,44 +126,41 @@ private Value(final T value) { } } - private static final Deque globalContexts = new ArrayDeque(); + private static final Deque globalSuites = new ArrayDeque(); static { - globalContexts.push(new Context(Description.createSuiteDescription("Spectrum tests"))); + globalSuites.push(new Suite(Description.createSuiteDescription("Spectrum tests"))); } - private final Description description; - private final Context rootContext; + private final Suite rootContext; public Spectrum(final Class testClass) { - description = Description.createSuiteDescription(testClass); - rootContext = new Context(description); - enterContext(rootContext, new ConstructorBlock(testClass)); + final Description description = Description.createSuiteDescription(testClass); + this.rootContext = getCurrentContext().addSuite(description); + enterContext(this.rootContext, new ConstructorBlock(testClass)); } @Override public Description getDescription() { - return description; + return this.rootContext.getDescription(); } @Override public void run(final RunNotifier notifier) { - rootContext.execute(notifier); + this.rootContext.run(notifier); } - private static void enterContext(final Context context, final Block block) { - getCurrentContext().addChild(context); - - globalContexts.push(context); + private static void enterContext(final Suite suite, final Block block) { + globalSuites.push(suite); try { block.run(); } catch (final Throwable e) { it("encountered an error", new FailingBlock(e)); } - globalContexts.pop(); + globalSuites.pop(); } - private static Context getCurrentContext() { - return globalContexts.peek(); + private static Suite getCurrentContext() { + return globalSuites.peek(); } } diff --git a/src/test/java/specs/FixturesSpec.java b/src/test/java/specs/FixturesSpec.java index e98e7e9..8c92255 100644 --- a/src/test/java/specs/FixturesSpec.java +++ b/src/test/java/specs/FixturesSpec.java @@ -6,12 +6,12 @@ import static com.greghaskins.spectrum.Spectrum.beforeEach; import static com.greghaskins.spectrum.Spectrum.describe; import static com.greghaskins.spectrum.Spectrum.it; +import static com.greghaskins.spectrum.Spectrum.value; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; -import helpers.SpectrumRunner; import java.util.ArrayList; import java.util.List; @@ -22,6 +22,9 @@ import com.greghaskins.spectrum.Spectrum; import com.greghaskins.spectrum.Spectrum.Block; +import com.greghaskins.spectrum.Spectrum.Value; + +import helpers.SpectrumRunner; @RunWith(Spectrum.class) public class FixturesSpec {{ @@ -205,9 +208,7 @@ public class FixturesSpec {{ it("cause all tests in that context and its children to fail", () -> { final Result result = SpectrumRunner.run(getSpecWithExplodingBeforeAll()); - final int numberOfFailingContexts = 2; - final int numbmerOfTestsWithinThoseContexts = 3; - assertThat(result.getFailureCount(), is(numberOfFailingContexts + numbmerOfTestsWithinThoseContexts)); + assertThat(result.getFailureCount(), is(3)); }); }); @@ -292,6 +293,8 @@ class Spec {{ private static Class getSpecWithExplodingBeforeAll(){ class Spec {{ + final Value executedSpecs = value(0); + describe("failing context", () ->{ beforeAll(() -> { @@ -303,22 +306,26 @@ class Spec {{ }); it("should fail once", () -> { - + executedSpecs.value++; }); it("should also fail", () -> { - + executedSpecs.value++; }); describe("failing child", () -> { it("fails too", () -> { - + executedSpecs.value++; }); }); }); + it("should not execute any specs", () -> { + assertThat(executedSpecs.value, is(0)); + }); + }} return Spec.class; } From 77e1bb9eddc42c1f5016eb92744dc722db963302 Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 11:08:31 -0500 Subject: [PATCH 05/12] Fix execution order of nested beforeAll --- .../java/com/greghaskins/spectrum/Suite.java | 6 ++--- src/test/java/specs/FixturesSpec.java | 24 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/greghaskins/spectrum/Suite.java b/src/main/java/com/greghaskins/spectrum/Suite.java index dd4e194..e632193 100644 --- a/src/main/java/com/greghaskins/spectrum/Suite.java +++ b/src/main/java/com/greghaskins/spectrum/Suite.java @@ -25,7 +25,6 @@ class Suite extends Runner { public Suite(final Description description) { this.description = description; - beforeEach(new RunOnceBlock(this.beforeAll)); } public Suite addSuite(final String name) { @@ -34,6 +33,7 @@ public Suite addSuite(final String name) { public Suite addSuite(final Description description) { final Suite suite = new Suite(description); + suite.beforeAll(this.beforeAll); suite.beforeEach(this.beforeEach); suite.afterEach(this.afterEach); addChild(suite); @@ -41,7 +41,7 @@ public Suite addSuite(final Description description) { } public Spec addSpec(final String name, final Block block) { - final CompositeBlock specBlockInContext = new CompositeBlock(Arrays.asList(this.beforeEach, block, this.afterEach)); + final CompositeBlock specBlockInContext = new CompositeBlock(Arrays.asList(this.beforeAll, this.beforeEach, block, this.afterEach)); final Description specDescription = Description.createTestDescription(this.description.getClassName(), name); final Spec spec = new Spec(specDescription, specBlockInContext); addChild(spec); @@ -54,7 +54,7 @@ private void addChild(final Runner runner) { } public void beforeAll(final Block block) { - this.beforeAll.addBlock(block); + this.beforeAll.addBlock(new RunOnceBlock(block)); } public void afterAll(final Block block) { diff --git a/src/test/java/specs/FixturesSpec.java b/src/test/java/specs/FixturesSpec.java index 8c92255..d2412ec 100644 --- a/src/test/java/specs/FixturesSpec.java +++ b/src/test/java/specs/FixturesSpec.java @@ -129,7 +129,7 @@ public class FixturesSpec {{ }); - describe("A spec using beforeAll", () -> { + describe("A suite using beforeAll", () -> { final List items = new ArrayList(); @@ -150,6 +150,28 @@ public class FixturesSpec {{ assertThat(items, contains("foo", "bar", "baz")); }); + describe("with nested children", () -> { + + final ArrayList numbers = new ArrayList(); + + beforeAll(() -> { + numbers.add(1); + }); + + describe("inside suites without their own tests", () -> { + + beforeAll(() -> { + numbers.add(2); + }); + + it("runs the beforeAll blocks from outer scope first", () -> { + assertThat(numbers, contains(1, 2)); + }); + + }); + + }); + }); describe("A spec using afterAll", () -> { From 3098f043ea34a99710502aea3a7c033807ff16a9 Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 11:10:51 -0500 Subject: [PATCH 06/12] Remove old implementation files --- .../greghaskins/spectrum/BlockExecutable.java | 28 ----- .../com/greghaskins/spectrum/Context.java | 101 ------------------ .../com/greghaskins/spectrum/Executable.java | 7 -- .../java/com/greghaskins/spectrum/Test.java | 29 ----- .../com/greghaskins/spectrum/Trilogy.java | 11 -- 5 files changed, 176 deletions(-) delete mode 100644 src/main/java/com/greghaskins/spectrum/BlockExecutable.java delete mode 100644 src/main/java/com/greghaskins/spectrum/Context.java delete mode 100644 src/main/java/com/greghaskins/spectrum/Executable.java delete mode 100644 src/main/java/com/greghaskins/spectrum/Test.java delete mode 100644 src/main/java/com/greghaskins/spectrum/Trilogy.java diff --git a/src/main/java/com/greghaskins/spectrum/BlockExecutable.java b/src/main/java/com/greghaskins/spectrum/BlockExecutable.java deleted file mode 100644 index 7cb03da..0000000 --- a/src/main/java/com/greghaskins/spectrum/BlockExecutable.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.greghaskins.spectrum; - -import org.junit.runner.Description; -import org.junit.runner.notification.Failure; -import org.junit.runner.notification.RunNotifier; - -import com.greghaskins.spectrum.Spectrum.Block; - -class BlockExecutable implements Executable { - - private final Block block; - private final Description description; - - public BlockExecutable(final Description description, final Block block) { - this.description = description; - this.block = block; - } - - @Override - public void execute(final RunNotifier notifier) { - try { - block.run(); - } catch (final Throwable e) { - notifier.fireTestFailure(new Failure(description, e)); - } - } - -} diff --git a/src/main/java/com/greghaskins/spectrum/Context.java b/src/main/java/com/greghaskins/spectrum/Context.java deleted file mode 100644 index 247d404..0000000 --- a/src/main/java/com/greghaskins/spectrum/Context.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.greghaskins.spectrum; - -import static java.util.Arrays.asList; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.List; - -import org.junit.runner.Description; -import org.junit.runner.notification.RunNotifier; - -import com.greghaskins.spectrum.Spectrum.Block; - -class Context implements Executable { - - private final Description description; - private final List setupBlocks; - private final List teardownBlocks; - private final List contextSetupBlocks; - private final List contextTeardownBlocks; - private final Deque executables; - - public Context(final Description description) { - this.description = description; - this.setupBlocks = new ArrayList(); - this.teardownBlocks = new ArrayList(); - this.contextSetupBlocks = new ArrayList(); - this.contextTeardownBlocks = new ArrayList(); - - this.executables = new ArrayDeque(); - } - - @Override - public void execute(final RunNotifier notifier) { - if (descriptionHasAnyTests(this.description)) { - addExecutablesForFixtureLevelSetupAndTeardown(); - } else { - notifier.fireTestIgnored(this.description); - } - for (final Executable child : this.executables) { - child.execute(notifier); - } - } - - private boolean descriptionHasAnyTests(final Description currentDescription) { - for (final Description child : currentDescription.getChildren()) { - if (isTest(child) || descriptionHasAnyTests(child)) { - return true; - } - } - return false; - } - - private boolean isTest(final Description child) { - return child.getMethodName() != null; - } - - private void addExecutablesForFixtureLevelSetupAndTeardown() { - this.executables.addFirst(new BlockExecutable(this.description, new CompositeBlock(this.contextSetupBlocks))); - this.executables.addLast(new BlockExecutable(this.description, new CompositeBlock(this.contextTeardownBlocks))); - } - - public void addTestSetup(final Block block) { - this.setupBlocks.add(block); - } - - public void addTestTeardown(final Block block) { - this.teardownBlocks.add(block); - } - - public void addContextSetup(final Block block) { - this.contextSetupBlocks.add(new RunOnceBlock(block)); - } - - public void addContextTeardown(final Block block) { - this.contextTeardownBlocks.add(block); - } - - public void addTest(final String behavior, final Block block) { - final Description testDescription = Description.createTestDescription(this.description.getClassName(), behavior); - final CompositeBlock testBlock = putTestBlockInContext(block); - final Test test = new Test(testDescription, testBlock); - this.description.addChild(testDescription); - this.executables.add(test); - } - - private CompositeBlock putTestBlockInContext(final Block testBlock) { - return new CompositeBlock(asList(new CompositeBlock(this.contextSetupBlocks), new CompositeBlock(this.setupBlocks), testBlock, new CompositeBlock(this.teardownBlocks))); - } - - public void addChild(final Context childContext) { - this.description.addChild(childContext.description); - childContext.addContextSetup(new CompositeBlock(this.contextSetupBlocks)); - childContext.addTestSetup(new CompositeBlock(this.setupBlocks)); - childContext.addTestTeardown(new CompositeBlock(this.teardownBlocks)); - this.executables.add(childContext); - } - - -} diff --git a/src/main/java/com/greghaskins/spectrum/Executable.java b/src/main/java/com/greghaskins/spectrum/Executable.java deleted file mode 100644 index c5f4828..0000000 --- a/src/main/java/com/greghaskins/spectrum/Executable.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.greghaskins.spectrum; - -import org.junit.runner.notification.RunNotifier; - -interface Executable { - void execute(final RunNotifier notifier); -} diff --git a/src/main/java/com/greghaskins/spectrum/Test.java b/src/main/java/com/greghaskins/spectrum/Test.java deleted file mode 100644 index 2ed5738..0000000 --- a/src/main/java/com/greghaskins/spectrum/Test.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.greghaskins.spectrum; - -import org.junit.runner.Description; -import org.junit.runner.notification.Failure; -import org.junit.runner.notification.RunNotifier; - -import com.greghaskins.spectrum.Spectrum.Block; - -class Test implements Executable { - private final Description description; - private final Block block; - - public Test(final Description description, final Block block) { - this.description = description; - this.block = block; - } - - @Override - public void execute(final RunNotifier notifier) { - notifier.fireTestStarted(description); - try { - block.run(); - } catch (final Throwable e) { - notifier.fireTestFailure(new Failure(description, e)); - } - notifier.fireTestFinished(description); - } - -} diff --git a/src/main/java/com/greghaskins/spectrum/Trilogy.java b/src/main/java/com/greghaskins/spectrum/Trilogy.java deleted file mode 100644 index 03b21f8..0000000 --- a/src/main/java/com/greghaskins/spectrum/Trilogy.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.greghaskins.spectrum; - -interface Trilogy { - - void setUp() throws Throwable; - - void run() throws Throwable; - - void tearDown() throws Throwable; - -} From af781fa0eecb6668201636bde573e0e7e64af4ee Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 11:19:54 -0500 Subject: [PATCH 07/12] Clean up run-once idempotent block --- .../com/greghaskins/spectrum/EmptyBlock.java | 9 ----- .../greghaskins/spectrum/IdempotentBlock.java | 38 +++++++++++++++++++ .../greghaskins/spectrum/RunOnceBlock.java | 24 ------------ .../java/com/greghaskins/spectrum/Suite.java | 2 +- 4 files changed, 39 insertions(+), 34 deletions(-) delete mode 100644 src/main/java/com/greghaskins/spectrum/EmptyBlock.java create mode 100644 src/main/java/com/greghaskins/spectrum/IdempotentBlock.java delete mode 100644 src/main/java/com/greghaskins/spectrum/RunOnceBlock.java diff --git a/src/main/java/com/greghaskins/spectrum/EmptyBlock.java b/src/main/java/com/greghaskins/spectrum/EmptyBlock.java deleted file mode 100644 index b9da7df..0000000 --- a/src/main/java/com/greghaskins/spectrum/EmptyBlock.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.greghaskins.spectrum; - -import com.greghaskins.spectrum.Spectrum.Block; - -final class EmptyBlock implements Block { - @Override - public void run() { - } -} diff --git a/src/main/java/com/greghaskins/spectrum/IdempotentBlock.java b/src/main/java/com/greghaskins/spectrum/IdempotentBlock.java new file mode 100644 index 0000000..568d613 --- /dev/null +++ b/src/main/java/com/greghaskins/spectrum/IdempotentBlock.java @@ -0,0 +1,38 @@ +package com.greghaskins.spectrum; + +import com.greghaskins.spectrum.Spectrum.Block; + +class IdempotentBlock implements Block { + + private final Block block; + private Block result; + + + public IdempotentBlock(final Block block) { + this.block = block; + } + + @Override + public void run() throws Throwable { + if (this.result == null) { + this.result = runBlockOnce(this.block); + } + this.result.run(); + } + + private static Block runBlockOnce(final Block block) { + try { + block.run(); + return new EmptyBlock(); + } catch (final Throwable e) { + return new FailingBlock(e); + } + } + + private static class EmptyBlock implements Block { + @Override + public void run() { + } + } + +} diff --git a/src/main/java/com/greghaskins/spectrum/RunOnceBlock.java b/src/main/java/com/greghaskins/spectrum/RunOnceBlock.java deleted file mode 100644 index 4a93bf3..0000000 --- a/src/main/java/com/greghaskins/spectrum/RunOnceBlock.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.greghaskins.spectrum; - -import com.greghaskins.spectrum.Spectrum.Block; - -class RunOnceBlock implements Block { - - private Block block; - - public RunOnceBlock(final Block block) { - this.block = block; - } - - @Override - public void run() throws Throwable { - try { - block.run(); - } catch (final Throwable e) { - block = new FailingBlock(e); - throw e; - } - block = new EmptyBlock(); - } - -} diff --git a/src/main/java/com/greghaskins/spectrum/Suite.java b/src/main/java/com/greghaskins/spectrum/Suite.java index e632193..1fcdbca 100644 --- a/src/main/java/com/greghaskins/spectrum/Suite.java +++ b/src/main/java/com/greghaskins/spectrum/Suite.java @@ -54,7 +54,7 @@ private void addChild(final Runner runner) { } public void beforeAll(final Block block) { - this.beforeAll.addBlock(new RunOnceBlock(block)); + this.beforeAll.addBlock(new IdempotentBlock(block)); } public void afterAll(final Block block) { From f934404cd98fdd05f186f1c0f32c8eb18e8227fc Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 16:35:50 -0500 Subject: [PATCH 08/12] Restore java6 compatibility for runner Will upgrade and use latest features in future. For now, the runner itself is java6-compatible. --- .../com/greghaskins/spectrum/CompositeBlock.java | 2 +- src/main/java/com/greghaskins/spectrum/Suite.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/greghaskins/spectrum/CompositeBlock.java b/src/main/java/com/greghaskins/spectrum/CompositeBlock.java index bd89cda..4d54bfa 100644 --- a/src/main/java/com/greghaskins/spectrum/CompositeBlock.java +++ b/src/main/java/com/greghaskins/spectrum/CompositeBlock.java @@ -14,7 +14,7 @@ public CompositeBlock(final List blocks) { } public CompositeBlock() { - this(new ArrayList<>()); + this(new ArrayList()); } @Override diff --git a/src/main/java/com/greghaskins/spectrum/Suite.java b/src/main/java/com/greghaskins/spectrum/Suite.java index 1fcdbca..6c3271f 100644 --- a/src/main/java/com/greghaskins/spectrum/Suite.java +++ b/src/main/java/com/greghaskins/spectrum/Suite.java @@ -19,7 +19,7 @@ class Suite extends Runner { private final CompositeBlock beforeEach = new CompositeBlock(); private final CompositeBlock afterEach = new CompositeBlock(); - private final List children = new ArrayList<>(); + private final List children = new ArrayList(); private final Description description; @@ -81,7 +81,9 @@ public void run(final RunNotifier notifier) { } private void runChildren(final RunNotifier notifier) { - this.children.stream().forEach((child) -> child.run(notifier)); + for (final Runner child : this.children) { + child.run(notifier); + } } private void runAfterAll(final RunNotifier notifier) { @@ -101,7 +103,11 @@ public Description getDescription() { @Override public int testCount() { - return this.children.stream().mapToInt((child) -> { return child.testCount(); }).sum(); + int count = 0; + for (final Runner child : this.children) { + count += child.testCount(); + } + return count; } } From 067b7515b5838548868084fd158f0e80f3e543a9 Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 16:39:39 -0500 Subject: [PATCH 09/12] Upgrade to latest Gradle (2.10) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 69985a9..4a6b653 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ jacocoTestReport { } task wrapper(type: Wrapper) { - gradleVersion = '2.3' + gradleVersion = '2.10' } task sourcesJar(type: Jar, dependsOn: classes) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 52eeef0..a9a7bd8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Mar 28 13:22:51 EDT 2015 +#Sun Jan 31 16:37:51 EST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip From 9006571cf2dbcfe7b49f0e0f86de5d4abfa3f2bf Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 16:45:42 -0500 Subject: [PATCH 10/12] Upgrade to latest dependencies --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4a6b653..46150f4 100644 --- a/build.gradle +++ b/build.gradle @@ -12,9 +12,9 @@ repositories { } dependencies { - compile group: 'junit', name: 'junit', version: '4.11' + compile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3' - testCompile group: 'org.mockito', name: 'mockito-core', version: '1.10.8' + testCompile group: 'org.mockito', name: 'mockito-core', version: '1.10.19' } compileJava { From b0a3b44d3c34f7c69642a341fc0313036492bff9 Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 23:30:34 -0500 Subject: [PATCH 11/12] Clarify terminology in javadocs --- .../com/greghaskins/spectrum/Spectrum.java | 67 +++++++++---------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/greghaskins/spectrum/Spectrum.java b/src/main/java/com/greghaskins/spectrum/Spectrum.java index 74088be..45a9542 100644 --- a/src/main/java/com/greghaskins/spectrum/Spectrum.java +++ b/src/main/java/com/greghaskins/spectrum/Spectrum.java @@ -24,21 +24,21 @@ public static interface Block { } /** - * Declare a test suite to describe the expected behaviors of the system in a given context. + * Declare a test suite that describes the expected behavior of the system in a given context. * * @param context - * Description of the test context + * Description of the context for this suite * @param block * {@link Block} with one or more calls to {@link #it(String, Block) it} that define each expected behavior * */ public static void describe(final String context, final Block block) { - final Suite suite = getCurrentContext().addSuite(context); - enterContext(suite, block); + final Suite suite = getCurrentSuite().addSuite(context); + beginDefintion(suite, block); } /** - * Declare a test for an expected behavior of the system. + * Declare a spec, or test, for an expected behavior of the system in this suite context. * * @param behavior * Description of the expected behavior @@ -47,11 +47,11 @@ public static void describe(final String context, final Block block) { * if that expectation is not met. */ public static void it(final String behavior, final Block block) { - getCurrentContext().addSpec(behavior, block); + getCurrentSuite().addSpec(behavior, block); } /** - * Declare a {@link Block} to be run before each test in the current context. + * Declare a {@link Block} to be run before each spec in the suite. * *

* Use this to perform setup actions that are common across tests in the context. If multiple {@code beforeEach} blocks are @@ -59,44 +59,44 @@ public static void it(final String behavior, final Block block) { *

* * @param block - * {@link Block} to run before each test + * {@link Block} to run once before each spec */ public static void beforeEach(final Block block) { - getCurrentContext().beforeEach(block); + getCurrentSuite().beforeEach(block); } /** - * Declare a {@link Block} to be run after each test in the current context. + * Declare a {@link Block} to be run after each spec in the current suite. * *

- * Use this to perform teardown or cleanup actions that are common across tests in this context. If multiple + * Use this to perform teardown or cleanup actions that are common across specs in this suite. If multiple * {@code afterEach} blocks are declared, they will run in declaration order. *

* * @param block - * {@link Block} to run after each test + * {@link Block} to run once after each spec */ public static void afterEach(final Block block) { - getCurrentContext().afterEach(block); + getCurrentSuite().afterEach(block); } /** - * Declare a {@link Block} to be run once before all the tests in the current context. + * Declare a {@link Block} to be run once before all the specs in the current suite begin. * *

* Use {@code beforeAll} and {@link #afterAll(Block) afterAll} blocks with caution: since they only run once, shared state - * will leak across tests. + * will leak across specs. *

* * @param block - * {@link Block} to run once before all tests + * {@link Block} to run once before all specs in this suite */ public static void beforeAll(final Block block) { - getCurrentContext().beforeAll(block); + getCurrentSuite().beforeAll(block); } /** - * Declare a {@link Block} to be run once after all the tests in the current context. + * Declare a {@link Block} to be run once after all the specs in the current suite have run. * *

* Use {@link #beforeAll(Block) beforeAll} and {@code afterAll} blocks with caution: since they only run once, shared state @@ -104,10 +104,10 @@ public static void beforeAll(final Block block) { *

* * @param block - * {@link Block} to run once after all tests + * {@link Block} to run once after all specs in this suite */ public static void afterAll(final Block block) { - getCurrentContext().afterAll(block); + getCurrentSuite().afterAll(block); } public static Value value(@SuppressWarnings("unused") final Class type) { @@ -126,41 +126,38 @@ private Value(final T value) { } } - private static final Deque globalSuites = new ArrayDeque(); - static { - globalSuites.push(new Suite(Description.createSuiteDescription("Spectrum tests"))); - } + private static final Deque suiteStack = new ArrayDeque(); - private final Suite rootContext; + private final Suite rootSuite; public Spectrum(final Class testClass) { final Description description = Description.createSuiteDescription(testClass); - this.rootContext = getCurrentContext().addSuite(description); - enterContext(this.rootContext, new ConstructorBlock(testClass)); + this.rootSuite = new Suite(description); + beginDefintion(this.rootSuite, new ConstructorBlock(testClass)); } @Override public Description getDescription() { - return this.rootContext.getDescription(); + return this.rootSuite.getDescription(); } @Override public void run(final RunNotifier notifier) { - this.rootContext.run(notifier); + this.rootSuite.run(notifier); } - private static void enterContext(final Suite suite, final Block block) { - globalSuites.push(suite); + private static void beginDefintion(final Suite suite, final Block definitionBlock) { + suiteStack.push(suite); try { - block.run(); + definitionBlock.run(); } catch (final Throwable e) { it("encountered an error", new FailingBlock(e)); } - globalSuites.pop(); + suiteStack.pop(); } - private static Suite getCurrentContext() { - return globalSuites.peek(); + private static Suite getCurrentSuite() { + return suiteStack.peek(); } } From 0e880336c4ac833b54e48ad84834164f8cdce2df Mon Sep 17 00:00:00 2001 From: Gregory Haskins Date: Sun, 31 Jan 2016 23:34:42 -0500 Subject: [PATCH 12/12] Add some thread safety defense MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To make sure only one definition block can run at a time so that global `describe` and `it` functions don’t get mis-attached. --- src/main/java/com/greghaskins/spectrum/Spectrum.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/greghaskins/spectrum/Spectrum.java b/src/main/java/com/greghaskins/spectrum/Spectrum.java index 45a9542..d79dfcf 100644 --- a/src/main/java/com/greghaskins/spectrum/Spectrum.java +++ b/src/main/java/com/greghaskins/spectrum/Spectrum.java @@ -146,7 +146,7 @@ public void run(final RunNotifier notifier) { this.rootSuite.run(notifier); } - private static void beginDefintion(final Suite suite, final Block definitionBlock) { + synchronized private static void beginDefintion(final Suite suite, final Block definitionBlock) { suiteStack.push(suite); try { definitionBlock.run(); @@ -156,7 +156,7 @@ private static void beginDefintion(final Suite suite, final Block definitionBloc suiteStack.pop(); } - private static Suite getCurrentSuite() { + synchronized private static Suite getCurrentSuite() { return suiteStack.peek(); }