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

implemented nested steps #17 (+ text report only) #174

Merged
merged 1 commit into from
Dec 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
implemented nested steps #17 (+ text report only)
  • Loading branch information
albertofaci committed Dec 7, 2015
commit be2d8ea9033343fb6200fdce4e0e0c407e3c3fbe
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tngtech.jgiven.annotation;

import java.lang.annotation.*;

@Documented
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.METHOD } )
public @interface NestedSteps {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
import java.lang.reflect.Method;
import java.util.*;

import com.google.common.base.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.CaseFormat;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
Expand Down Expand Up @@ -45,6 +43,8 @@ public class ScenarioModelBuilder implements ScenarioListener {
private ScenarioCaseModel scenarioCaseModel;
private StepModel currentStep;

private Stack<StepAndMethod> stepStack = new Stack<StepAndMethod>();

private Word introWord;

private long scenarioStartedNanos;
Expand Down Expand Up @@ -83,10 +83,27 @@ private static String camelCaseToReadableText(String camelCase) {
return CaseFormat.LOWER_CAMEL.to( CaseFormat.LOWER_UNDERSCORE, camelCase ).replace( '_', ' ' );
}

public void addStepMethod( Method paramMethod, List<NamedArgument> arguments, InvocationMode mode ) {
public void addStepMethod( Method paramMethod, List<NamedArgument> arguments, InvocationMode mode, Method parentMethod) {
StepModel stepModel = createStepModel( paramMethod, arguments, mode );

if( !stepStack.empty() ) {
while( !stepStack.empty() && !stepStack.peek().getMethod().equals( parentMethod ) ) {
stepStack.pop();
}
if( !stepStack.empty() ) {
stepStack.peek().getStepModel().addNestedStep(stepModel);
}
}

if( stepStack.empty() ) {
writeStep( stepModel );
}

if( paramMethod.isAnnotationPresent( NestedSteps.class ) ) {
stepStack.push( new StepAndMethod( paramMethod, stepModel ) );
}

currentStep = stepModel;
writeStep( stepModel );
}

StepModel createStepModel( Method paramMethod, List<NamedArgument> arguments, InvocationMode mode ) {
Expand Down Expand Up @@ -200,7 +217,7 @@ private boolean isHidden( Annotation[] annotations ) {
}

public void writeStep( StepModel stepModel ) {
getCurrentScenarioCase().addStep( stepModel );
getCurrentScenarioCase().addStep(stepModel);
}

private ScenarioCaseModel getCurrentScenarioCase() {
Expand All @@ -211,11 +228,11 @@ private ScenarioCaseModel getCurrentScenarioCase() {
}

@Override
public void stepMethodInvoked( Method method, List<NamedArgument> arguments, InvocationMode mode ) {
public void stepMethodInvoked( Method method, List<NamedArgument> arguments, InvocationMode mode, Method parentMethod ) {
if( method.isAnnotationPresent( IntroWord.class ) ) {
introWordAdded( getDescription( method ) );
} else {
addStepMethod( method, arguments, mode );
addStepMethod(method, arguments, mode, parentMethod);
}
}

Expand Down Expand Up @@ -595,4 +612,23 @@ public ScenarioCaseModel getScenarioCaseModel() {
return scenarioCaseModel;
}

private static class StepAndMethod {

StepModel stepModel;
Method method;

private StepAndMethod( Method method, StepModel stepModel ) {
this.method = method;
this.stepModel = stepModel;
}

public StepModel getStepModel() {
return stepModel;
}

public Method getMethod() {
return method;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void setExtendedDescription( String extendedDescription ) {

class MethodHandler implements StepMethodHandler {
@Override
public void handleMethod( Object stageInstance, Method paramMethod, Object[] arguments, InvocationMode mode )
public void handleMethod( Object stageInstance, Method paramMethod, Object[] arguments, InvocationMode mode, Method parentMethod )
throws Throwable {

if( paramMethod.isSynthetic() && !paramMethod.isBridge() ) {
Expand All @@ -124,7 +124,7 @@ public void handleMethod( Object stageInstance, Method paramMethod, Object[] arg
}

List<NamedArgument> namedArguments = ParameterNameUtil.mapArgumentsWithParameterNames( paramMethod, Arrays.asList( arguments ) );
listener.stepMethodInvoked( paramMethod, namedArguments, mode );
listener.stepMethodInvoked( paramMethod, namedArguments, mode, parentMethod );
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void scenarioStarted( String string ) {}
public void scenarioStarted( Method method, List<NamedArgument> arguments ) {}

@Override
public void stepMethodInvoked( Method method, List<NamedArgument> arguments, InvocationMode mode ) {}
public void stepMethodInvoked( Method method, List<NamedArgument> arguments, InvocationMode mode, Method parentMethod ) {}

@Override
public void introWordAdded( String introWord ) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface ScenarioListener {

void scenarioStarted( Method method, List<NamedArgument> arguments );

void stepMethodInvoked( Method method, List<NamedArgument> arguments, InvocationMode mode );
void stepMethodInvoked( Method method, List<NamedArgument> arguments, InvocationMode mode, Method parentMethod );

void introWordAdded( String introWord );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import java.lang.reflect.Method;

public interface StepMethodHandler {
void handleMethod( Object targetObject, Method paramMethod, Object[] arguments, InvocationMode mode )
void handleMethod( Object targetObject, Method paramMethod, Object[] arguments, InvocationMode mode, Method parentMethod )
throws Throwable;

void handleThrowable( Throwable t ) throws Throwable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import static com.tngtech.jgiven.report.model.InvocationMode.*;

import java.lang.reflect.Method;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;

import com.tngtech.jgiven.annotation.NestedSteps;
import com.tngtech.jgiven.report.model.InvocationMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -20,6 +22,11 @@ public class StepMethodInterceptor {

private AtomicInteger stackDepth;

/**
* stack that represent method call context. @see NestedSteps.java
*/
private Stack<Method> methodCallStack = new Stack<Method>();

/**
* Whether the method handler is called when a step method is invoked
*/
Expand Down Expand Up @@ -51,10 +58,12 @@ public final Object doIntercept( final Object receiver, Method method,
return invoker.proceed();
}

boolean handleMethod = methodHandlingEnabled && stackDepth.get() == 0 && !method.getDeclaringClass().equals( Object.class );
boolean handleMethod = methodHandlingEnabled && shouldHandleMethod( method );

if( handleMethod ) {
scenarioMethodHandler.handleMethod( receiver, method, parameters, mode );
mode = recalculateInvocationMode( mode );
Method parentMethod = ( methodCallStack.empty() )? null : methodCallStack.peek();
scenarioMethodHandler.handleMethod( receiver, method, parameters, mode, parentMethod);
}

if( mode == SKIPPED || mode == PENDING ) {
Expand All @@ -63,19 +72,38 @@ public final Object doIntercept( final Object receiver, Method method,

try {
stackDepth.incrementAndGet();
methodCallStack.push(method);
return invoker.proceed();
} catch( Exception e ) {
return handleThrowable( receiver, method, e, System.nanoTime() - started );
} catch( AssertionError e ) {
return handleThrowable( receiver, method, e, System.nanoTime() - started );
} finally {
methodCallStack.pop();
stackDepth.decrementAndGet();
if( handleMethod ) {
scenarioMethodHandler.handleMethodFinished( System.nanoTime() - started );
}
}
}

private boolean shouldHandleMethod(Method method) {
if(method.getDeclaringClass().equals( Object.class )) {
return false;
}
if(methodCallStack != null && !methodCallStack.empty() && methodCallStack.peek().isAnnotationPresent(NestedSteps.class)) {
return true;
}
return stackDepth.get() == 0;
}

private InvocationMode recalculateInvocationMode( InvocationMode invocationMode ) {
if(methodCallStack.size() > 0) {
return InvocationMode.NESTED;
}
return invocationMode;
}

protected Object handleThrowable( Object receiver, Method method, Throwable t, long durationInNanos ) throws Throwable {
if( methodHandlingEnabled ) {
scenarioMethodHandler.handleThrowable( t );
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.tngtech.jgiven.report.model;

import com.tngtech.jgiven.report.model.StepStatus;

public enum InvocationMode {
NORMAL,
NESTED,
FAILED,
SKIPPED,
PENDING,
Expand All @@ -19,6 +18,8 @@ public StepStatus toStepStatus() {
return StepStatus.PENDING;
case SKIPPED:
return StepStatus.SKIPPED;
case NESTED:
return StepStatus.NESTED;
default:
throw new IllegalArgumentException( this.toString() );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.tngtech.jgiven.report.model;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.tngtech.jgiven.attachment.Attachment;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class StepModel {
/**
* The original name of this step as it appeared in the Java code.
Expand All @@ -19,6 +20,11 @@ public class StepModel {
*/
public List<Word> words = Lists.newArrayList();

/**
* Nested steps
*/
public List<StepModel> nestedSteps = Lists.newArrayList();

/**
* The execution status of this step.
*/
Expand Down Expand Up @@ -97,7 +103,7 @@ public String getExtendedDescription() {
}

public boolean hasExtendedDescription() {
return extendedDescription != null;
return extendedDescription != null || Iterables.size( nestedSteps ) > 0;
}

public void setExtendedDescription( String extendedDescription ) {
Expand Down Expand Up @@ -125,4 +131,12 @@ public void setAttachment( Attachment attachment ) {
public AttachmentModel getAttachment() {
return attachment;
}

public boolean hasNestedSteps() {
return Iterables.size( nestedSteps ) > 0;
}

public void addNestedStep( StepModel stepModel ) {
nestedSteps.add( stepModel );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public enum StepStatus {
PASSED,
NESTED,
FAILED,
SKIPPED,
PENDING;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
package com.tngtech.jgiven.report.text;

import static org.fusesource.jansi.Ansi.Attribute.INTENSITY_BOLD;
import static org.fusesource.jansi.Ansi.Color.MAGENTA;

import java.io.PrintWriter;
import java.util.List;

import org.fusesource.jansi.Ansi.Attribute;
import org.fusesource.jansi.Ansi.Color;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.tngtech.jgiven.impl.params.DefaultCaseDescriptionProvider;
import com.tngtech.jgiven.impl.util.WordUtil;
import com.tngtech.jgiven.report.model.*;
import org.fusesource.jansi.Ansi.Attribute;
import org.fusesource.jansi.Ansi.Color;

import java.io.PrintWriter;
import java.util.List;

import static org.fusesource.jansi.Ansi.Attribute.INTENSITY_BOLD;
import static org.fusesource.jansi.Ansi.Color.MAGENTA;

public class PlainTextScenarioWriter extends PlainTextWriter {
private static final String INDENT = " ";
public static final String NESTED_HEADING = "\\-- ";
public static final String NESTED_INDENT = "| ";

protected ScenarioModel currentScenarioModel;
protected ScenarioCaseModel currentCaseModel;
Expand Down Expand Up @@ -100,6 +102,8 @@ public void visit( StepModel stepModel ) {
intro = INDENT + String.format( "%" + maxFillWordLength + "s ", " " );
}



int restSize = words.size();
boolean printDataTable = false;
if( words.size() > 1 ) {
Expand All @@ -122,12 +126,35 @@ public void visit( StepModel stepModel ) {
}
writer.println( intro + rest );

printNestedSteps( stepModel, 0 );

if( printDataTable ) {
writer.println();
printDataTable( words.get( words.size() - 1 ) );
}
}

private void printNestedSteps(StepModel stepModel, int depth) {
if( stepModel.hasNestedSteps() ) {
for( StepModel nestedStepModel: stepModel.nestedSteps ) {
writer.println(INDENT + INDENT + INDENT + Strings.repeat( NESTED_INDENT , depth ) + NESTED_HEADING + getNestedStepString(nestedStepModel));
printNestedSteps( nestedStepModel, depth + 1) ;
}
}
}

private String getNestedStepString(StepModel nestedStepModel) {
StringBuilder stringBuilder = new StringBuilder();
if( nestedStepModel.words.get(0).isIntroWord() ) {
stringBuilder.append(WordUtil.capitalize(nestedStepModel.words.get(0).getValue()));
stringBuilder.append(" ").append(joinWords(nestedStepModel.words.subList(1, nestedStepModel.words.size())));
}
else {
stringBuilder.append(joinWords(nestedStepModel.words));
}
return stringBuilder.toString();
}

private void printDataTable( Word word ) {
PlainTextTableWriter plainTextTableWriter = new PlainTextTableWriter( writer, withColor );
String indent = INDENT + " ";
Expand Down
Loading