Skip to content

Commit

Permalink
Merge pull request #87 from leonard84/pending-feature
Browse files Browse the repository at this point in the history
Add @PendingFeature
  • Loading branch information
robfletcher committed Aug 18, 2015
2 parents 97fcd00 + e4df2b6 commit 689cf19
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 3 deletions.
32 changes: 29 additions & 3 deletions docs/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ meta-annotation.

=== Ignore

To temporarily prevent a feature method from getting executed, annotate it with +spock.lang.Ignore+:
To temporarily prevent a feature method from getting executed, annotate it with `spock.lang.Ignore`:

[source,groovy]
----
Expand Down Expand Up @@ -40,7 +40,7 @@ In most execution environments, ignored feature methods and specs will be report

=== IgnoreRest

To ignore all but a (typically) small subset of methods, annotate the latter with +spock.lang.IgnoreRest+:
To ignore all but a (typically) small subset of methods, annotate the latter with `spock.lang.IgnoreRest`:

[source,groovy]
----
Expand All @@ -56,7 +56,7 @@ def "I'll also be ignored"() { ... }

=== IgnoreIf

To ignore a feature method under certain conditions, annotate it with +spock.lang.IgnoreIf+,
To ignore a feature method under certain conditions, annotate it with `spock.lang.IgnoreIf`,
followed by a predicate:

[source,groovy]
Expand Down Expand Up @@ -95,6 +95,32 @@ def "I'll only run on Windows"() { ... }
to state the conditions under which a method gets executed, rather than the conditions under which it gets ignored.


=== PendingFeature

To indicate that the feature is not fully implemented yet and should not be reported as error, annotate it with `spock.lang.PendingFeature`.

The use case is to annotate tests that can not yet run but should already be committed.
The main difference to `Ignore` is that the test are executed, but test failures are ignored.
If the test passes without an error, then it will be reported as failure since the `PendingFeature` annotation should be removed.
This way the tests will become part of the normal tests instead of being ignored forever.

Groovy has the `groovy.transform.NotYetImplemented` annotation which is similar but behaves a differently.

* it will mark failing tests as passed
* if at least one iteration of a data-driven test passes it will be reported as error

`PendingFeature`:

* it will mark failing tests as skipped
* if at least one iteration of a data-driven test fails it will be reported as skipped
* if every iteration of a data-driven test passes it will be reported as error

[source,groovy]
----
@PendingFeature
def "not implemented yet"() { ... }
----

=== Stepwise

To execute features in the order that they are declared, annotate a spec class with `spock.lang.Stepwise`:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.spockframework.runtime.extension.builtin;

import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension;
import org.spockframework.runtime.model.FeatureInfo;
import spock.lang.PendingFeature;

/**
* @author Leonard Brünings
*/
public class PendingFeatureExtension extends AbstractAnnotationDrivenExtension<PendingFeature> {
@Override
public void visitFeatureAnnotation(PendingFeature annotation, FeatureInfo feature) {
if (feature.isParameterized()) {
feature.addInterceptor(new PendingFeatureIterationInterceptor());
} else {
feature.getFeatureMethod().addInterceptor(new PendingFeatureInterceptor());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.spockframework.runtime.extension.builtin;

import org.junit.AssumptionViolatedException;
import org.spockframework.runtime.extension.IMethodInterceptor;
import org.spockframework.runtime.extension.IMethodInvocation;

/**
* @author Leonard Brünings
*/
class PendingFeatureInterceptor implements IMethodInterceptor {
@Override
public void intercept(IMethodInvocation invocation) throws Throwable {
try {
invocation.proceed();
} catch (AssertionError e) {
throw new AssumptionViolatedException("Feature not yet implemented correctly.");
}
throw new AssertionError("Feature is marked with @PendingFeature but passes unexpectedly");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.spockframework.runtime.extension.builtin;

import org.junit.AssumptionViolatedException;
import org.spockframework.runtime.extension.IMethodInterceptor;
import org.spockframework.runtime.extension.IMethodInvocation;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* @author Leonard Brünings
*/
class PendingFeatureIterationInterceptor implements IMethodInterceptor {
@Override
public void intercept(IMethodInvocation invocation) throws Throwable {

AtomicBoolean pass = new AtomicBoolean(false);
invocation.getFeature().getFeatureMethod().addInterceptor(new InnerIterationInterceptor(pass));
invocation.proceed();
if (pass.get()) {
throw new AssumptionViolatedException("Feature not yet implemented correctly.");
} else {
throw new AssertionError("Feature is marked with @PendingFeature but passes unexpectedly");
}
}

private static class InnerIterationInterceptor implements IMethodInterceptor {
private final AtomicBoolean pass;

public InnerIterationInterceptor(AtomicBoolean pass) {
this.pass = pass;
}

@Override
public void intercept(IMethodInvocation invocation) throws Throwable {
try {
invocation.proceed();
} catch (AssertionError e) {
pass.set(true);
}
}
}
}
43 changes: 43 additions & 0 deletions spock-core/src/main/java/spock/lang/PendingFeature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package spock.lang;


import org.spockframework.runtime.extension.ExtensionAnnotation;
import org.spockframework.runtime.extension.builtin.IgnoreExtension;
import org.spockframework.runtime.extension.builtin.PendingFeatureExtension;
import org.spockframework.util.Beta;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that the feature is not fully implemented yet and should not be reported as error.
* <p>
* The use case is to annotate tests that can not yet run but should already be committed.
* The main difference to {@link Ignore} is that the test are executed, but test failures are ignored.
* If the test passes without an error, then it will be reported as failure since the {@link PendingFeature}
* annotation should be removed. This way the tests will become part of the normal tests
* instead of being ignored forever.
* </p>
* <p>
* Groovy has the {@link groovy.transform.NotYetImplemented} annotation which is similar but behaves a differently.
* <ul>
* <li>it will mark failing tests as passed</li>
* <li>if at least one iteration of a data-driven test passes it will be reported as error</li>
* </ul>
* {@link PendingFeature}:
* <ul>
* <li>it will mark failing tests as skipped</li>
* <li>if at least one iteration of a data-driven test fails it will be reported as skipped</li>
* <li>if every iteration of a data-driven test passes it will be reported as error</li>
* </ul>
*</p>
* @author Leonard Brünings
*/
@Beta
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@ExtensionAnnotation(PendingFeatureExtension.class)
public @interface PendingFeature {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package org.spockframework.smoke.extension

import org.spockframework.EmbeddedSpecification

class PendingFeatureExtensionSpec extends EmbeddedSpecification {

def "@PendingFeature marks failing feature as skipped"() {
when:
def result = runner.runWithImports("""
class Foo extends Specification {
@PendingFeature
def bar() {
expect: false
}
}
""")

then:
notThrown(AssertionError)
result.runCount == 1
result.failureCount == 0
result.ignoreCount == 0
}

def "@PendingFeature marks passing feature as failed"() {
when:
runner.runWithImports("""
class Foo extends Specification {
@PendingFeature
def bar() {
expect: true
}
}
""")

then:
AssertionError e = thrown(AssertionError)
e.message == "Feature is marked with @PendingFeature but passes unexpectedly"
}

def "@PendingFeature marks data driven feature where every iteration fails as skipped"() {
when:
def result = runner.runWithImports("""
class Foo extends Specification {
@PendingFeature
def bar() {
expect: test
where:
test << [false, false, false]
}
}
""")

then:
notThrown(AssertionError)
result.runCount == 1
result.failureCount == 0
result.ignoreCount == 0
}

def "@PendingFeature marks @Unroll'ed data driven feature where every iteration fails as skipped"() {
when:
def result = runner.runWithImports("""
class Foo extends Specification {
@Unroll
@PendingFeature
def bar() {
expect: test
where:
test << [false, false, false]
}
}
""")

then:
notThrown(AssertionError)
result.runCount == 3
result.failureCount == 0
result.ignoreCount == 0
}

def "@PendingFeature marks data driven feature where at least one iteration fails as skipped"() {
when:
def result = runner.runWithImports("""
class Foo extends Specification {
@PendingFeature
def bar() {
expect: test
where:
test << [true, false, true]
}
}
""")

then:
notThrown(AssertionError)
result.runCount == 1
result.failureCount == 0
result.ignoreCount == 0
}


def "@PendingFeature marks @Unroll'ed data driven feature where at least one iteration fails as skipped"() {
when:
def result = runner.runWithImports("""
class Foo extends Specification {
@Unroll
@PendingFeature
def bar() {
expect: test
where:
test << [true, false, true]
}
}
""")

then:
notThrown(AssertionError)
result.runCount == 3
result.failureCount == 0
result.ignoreCount == 0
}

def "@PendingFeature marks data driven feature where all iterations pass as failed"() {
when:
runner.runWithImports("""
class Foo extends Specification {
@PendingFeature
def bar() {
expect: test
where:
test << [true, true, true]
}
}
""")

then:
AssertionError e = thrown(AssertionError)
e.message == "Feature is marked with @PendingFeature but passes unexpectedly"
}


def "@PendingFeature marks @Unroll'ed data driven feature where all iterations pass as failed"() {
when:
runner.runWithImports("""
class Foo extends Specification {
@Unroll
@PendingFeature
def bar() {
expect: test
where:
test << [true, true, true]
}
}
""")

then:
AssertionError e = thrown(AssertionError)
e.message == "Feature is marked with @PendingFeature but passes unexpectedly"
}
}

0 comments on commit 689cf19

Please sign in to comment.