diff --git a/src/main/groovy/com/athaydes/spockframework/report/SpockReportExtension.groovy b/src/main/groovy/com/athaydes/spockframework/report/SpockReportExtension.groovy index 0f72750..23b2b73 100644 --- a/src/main/groovy/com/athaydes/spockframework/report/SpockReportExtension.groovy +++ b/src/main/groovy/com/athaydes/spockframework/report/SpockReportExtension.groovy @@ -11,8 +11,11 @@ import com.athaydes.spockframework.report.internal.SpecProblem import com.athaydes.spockframework.report.internal.SpockReportsConfiguration import com.athaydes.spockframework.report.util.Utils import groovy.util.logging.Slf4j +import org.junit.AssumptionViolatedException import org.spockframework.runtime.IRunListener import org.spockframework.runtime.extension.IGlobalExtension +import org.spockframework.runtime.extension.IMethodInterceptor +import org.spockframework.runtime.extension.IMethodInvocation import org.spockframework.runtime.model.ErrorInfo import org.spockframework.runtime.model.FeatureInfo import org.spockframework.runtime.model.IterationInfo @@ -75,7 +78,21 @@ class SpockReportExtension implements IGlobalExtension { @Override void visitSpec( SpecInfo specInfo ) { if ( reportCreator != null ) { - specInfo.addListener createListener() + def listener = createListener() + specInfo.addListener listener + specInfo.allFeatures*.getFeatureMethod()*.addInterceptor(new IMethodInterceptor() { + @Override + void intercept( IMethodInvocation invocation ) throws Throwable { + try { + invocation.proceed() + } catch( Throwable t ) { + if( t in AssumptionViolatedException ) { + listener.error new ErrorInfo( invocation.method, t ) + } + throw t + } + } + }) } else { log.warn "Not creating report for ${ specInfo.name } as reportCreator is null" } @@ -271,5 +288,4 @@ class SpecInfoListener implements IRunListener { private static FeatureInfo dummyFeature() { new FeatureInfo( name: '' ) } - } diff --git a/src/main/groovy/com/athaydes/spockframework/report/internal/HtmlReportCreator.groovy b/src/main/groovy/com/athaydes/spockframework/report/internal/HtmlReportCreator.groovy index 4cbfe01..2836676 100644 --- a/src/main/groovy/com/athaydes/spockframework/report/internal/HtmlReportCreator.groovy +++ b/src/main/groovy/com/athaydes/spockframework/report/internal/HtmlReportCreator.groovy @@ -214,7 +214,7 @@ class HtmlReportCreator extends AbstractHtmlCreator final String name = Utils.featureNameFrom( feature, iteration, index ) final cssClass = problems.any( Utils.&isError ) ? 'error' : problems.any( Utils.&isFailure ) ? 'failure' : - Utils.isSkipped( feature ) ? 'ignored' : 'pass' + problems.any( Utils.&isSkipped ) ? 'ignored' : 'pass' li { a( href: "#${name.hashCode()}", 'class': "feature-toc-$cssClass", name ) } @@ -249,7 +249,7 @@ class HtmlReportCreator extends AbstractHtmlCreator String name = Utils.featureNameFrom( feature, iteration, index ) final cssClass = problems.any( Utils.&isError ) ? 'error' : problems.any( Utils.&isFailure ) ? 'failure' : - Utils.isSkipped( feature ) ? 'ignored' : '' + problems.any( Utils.&isSkipped ) ? 'ignored' : '' writeFeatureDescription( builder, name, cssClass, featureAnnotation( feature, Ignore ), featureAnnotation( feature, PendingFeature ), @@ -490,7 +490,7 @@ class HtmlReportCreator extends AbstractHtmlCreator private void writeIteration( MarkupBuilder builder, IterationInfo iteration, List errors ) { - builder.tr( 'class': errors ? 'ex-fail' : 'ex-pass' ) { + builder.tr( 'class': errors.any( Utils.&isSkipped ) ? 'ex-skip' : errors ? 'ex-fail' : 'ex-pass' ) { for ( value in iteration.dataValues ) { def writableValue = value instanceof Runnable ? "" : @@ -502,7 +502,15 @@ class HtmlReportCreator extends AbstractHtmlCreator } private static String iterationResult( List errors ) { - errors ? 'FAIL' : 'OK' + if( errors.any { Utils.isFailure(it) } ) { + 'FAIL' + } else if( errors.any { Utils.isError(it) } ) { + 'ERROR' + } else if( errors.any { Utils.isSkipped(it) } ) { + 'SKIP' + } else { + 'OK' + } } private void writeFeatureDescription( MarkupBuilder builder, String name, diff --git a/src/main/groovy/com/athaydes/spockframework/report/internal/ProblemBlockWriter.groovy b/src/main/groovy/com/athaydes/spockframework/report/internal/ProblemBlockWriter.groovy index d4de045..8a4b975 100644 --- a/src/main/groovy/com/athaydes/spockframework/report/internal/ProblemBlockWriter.groovy +++ b/src/main/groovy/com/athaydes/spockframework/report/internal/ProblemBlockWriter.groovy @@ -23,9 +23,18 @@ class ProblemBlockWriter { void writeProblemBlockForIteration( MarkupBuilder builder, IterationInfo iteration, List problems, Long time ) { - if ( problems ) { + def failures = problems.findAll { Utils.isError(it) || Utils.isFailure(it) } + def skips = problems.findAll( Utils.&isSkipped ) + if ( failures ) { problemsContainer( builder ) { - def problemsByIteration = problemsByIteration( [ ( iteration ): problems ], [ ( iteration ): time ] ) + def problemsByIteration = problemsByIteration( [ ( iteration ): failures ], [ ( iteration ): time ] ) + problemsByIteration.each { it.dataValues = null } // do not show data values in the report + writeProblems( builder, problemsByIteration ) + } + } + if ( skips ) { + skipsContainer( builder ) { + def problemsByIteration = problemsByIteration( [ ( iteration ): skips ], [ ( iteration ): time ] ) problemsByIteration.each { it.dataValues = null } // do not show data values in the report writeProblems( builder, problemsByIteration ) } @@ -45,6 +54,19 @@ class ProblemBlockWriter { } } + void skipsContainer( MarkupBuilder builder, Runnable createProblemList ) { + builder.tr { + td( colspan: '10' ) { + div( 'class': 'skip-description' ) { + div( 'class': 'skip-header', 'Skip reason:' ) + div( 'class': 'problem-list' ) { + createProblemList.run() + } + } + } + } + } + private void writeProblems( MarkupBuilder builder, List problems ) { problems.each { Map problem -> if ( !problem.messages ) { diff --git a/src/main/groovy/com/athaydes/spockframework/report/internal/SpecData.groovy b/src/main/groovy/com/athaydes/spockframework/report/internal/SpecData.groovy index a881fe2..7260144 100644 --- a/src/main/groovy/com/athaydes/spockframework/report/internal/SpecData.groovy +++ b/src/main/groovy/com/athaydes/spockframework/report/internal/SpecData.groovy @@ -1,6 +1,7 @@ package com.athaydes.spockframework.report.internal import org.junit.ComparisonFailure +import org.junit.internal.AssumptionViolatedException import org.spockframework.runtime.model.ErrorInfo import org.spockframework.runtime.model.FeatureInfo import org.spockframework.runtime.model.IterationInfo @@ -41,11 +42,18 @@ class SpecProblem { } FailureKind getKind() { - failure.exception instanceof AssertionError || failure.exception instanceof ComparisonFailure ? - FailureKind.FAILURE : + switch(failure.exception) { + case AssertionError || ComparisonFailure: + FailureKind.FAILURE + break + case AssumptionViolatedException: + FailureKind.ASSUMPTION_FAILURE + break + default: FailureKind.ERROR - } + } + } } /** @@ -54,5 +62,5 @@ class SpecProblem { * An ERROR means an unexpected {@link Throwable} was thrown, while FAILURE means a test assertion failure. */ enum FailureKind { - FAILURE, ERROR + FAILURE, ASSUMPTION_FAILURE, ERROR } \ No newline at end of file diff --git a/src/main/groovy/com/athaydes/spockframework/report/util/Utils.groovy b/src/main/groovy/com/athaydes/spockframework/report/util/Utils.groovy index b17a76c..a46a0ca 100644 --- a/src/main/groovy/com/athaydes/spockframework/report/util/Utils.groovy +++ b/src/main/groovy/com/athaydes/spockframework/report/util/Utils.groovy @@ -60,7 +60,7 @@ class Utils { static Map stats( SpecData data ) { def failures = countProblems( data.featureRuns, this.&isFailure ) def errors = countProblems( data.featureRuns, this.&isError ) - def skipped = data.info.allFeaturesInExecutionOrder.count { FeatureInfo f -> isSkipped( f ) } + def skipped = countProblems( data.featureRuns, this.&isSkipped ) def total = data.info.allFeatures.size() def totalExecuted = countFeatures( data.featureRuns ) { FeatureRun run -> !isSkipped( run.feature ) } def successRate = successRate( totalExecuted, ( errors + failures ).toInteger() ) @@ -101,6 +101,10 @@ class Utils { problem.kind == FailureKind.ERROR } + static boolean isSkipped( SpecProblem problem ) { + problem.kind == FailureKind.ASSUMPTION_FAILURE + } + static boolean isSkipped( FeatureInfo featureInfo ) { // pending features are not marked as skipped but they are always skipped or fail featureInfo.skipped || featureAnnotation( featureInfo, PendingFeature ) != null diff --git a/src/main/resources/spock-feature-report.css b/src/main/resources/spock-feature-report.css index edc4e5f..f8f546e 100644 --- a/src/main/resources/spock-feature-report.css +++ b/src/main/resources/spock-feature-report.css @@ -140,11 +140,22 @@ div.problem-description { border-radius: 10px; } +div.skip-description { + padding: 10px; + background: lightgray; + border-radius: 10px; +} + div.problem-header { font-weight: bold; color: red; } +div.skip-header { + font-weight: bold; + color: grey; +} + div.problem-list { } @@ -197,6 +208,11 @@ td.ex-result { font-weight: bold; } +.ex-skip { + color: grey; + font-weight: bold; +} + div.block-kind { margin: 2px; font-style: italic;