Skip to content

Commit

Permalink
Further improved handling the handling of derived parameter (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Schäfer committed Sep 19, 2014
1 parent 57f4870 commit b2ed91d
Show file tree
Hide file tree
Showing 30 changed files with 454 additions and 199 deletions.
Expand Up @@ -9,7 +9,9 @@ public static void close( Closeable... closeables ) {
Exception t = null;
for( Closeable c : closeables ) {
try {
c.close();
if( c != null ) {
c.close();
}
} catch( Exception e ) {
t = e;
}
Expand Down
Expand Up @@ -33,7 +33,7 @@ public void visitEnd( ScenarioCaseModel scenarioCase ) {
writer.println( "<table class='data-table'>" );
writer.println( "<tr>" );
writer.print( "<th>#</th>" );
for( String param : scenarioModel.parameterNames ) {
for( String param : scenarioModel.getDerivedParameters() ) {
writer.print( "<th>" + param + "</th>" );
}
writer.print( "<th>Status</th>" );
Expand All @@ -42,7 +42,7 @@ public void visitEnd( ScenarioCaseModel scenarioCase ) {

writer.println( "<tr>" );
writer.print( "<td>" + scenarioCase.caseNr + "</td>" );
for( String arg : scenarioCase.arguments ) {
for( String arg : scenarioCase.getDerivedArguments() ) {
writer.print( "<td>" + arg + "</td>" );
}
writer.print( "<td>" );
Expand Down
Expand Up @@ -17,6 +17,7 @@
import com.google.common.base.Throwables;
import com.google.common.io.Files;
import com.tngtech.jgiven.impl.util.ResourceUtil;
import com.tngtech.jgiven.impl.util.Version;
import com.tngtech.jgiven.report.impl.CommonReportHelper;
import com.tngtech.jgiven.report.model.ReportModel;
import com.tngtech.jgiven.report.model.ReportModelVisitor;
Expand Down Expand Up @@ -48,8 +49,9 @@ public void writeHtmlFooter() {
}

private void writeJGivenFooter() {
writer.print( "<div id='footer'>Generated by <a href='http://github.com/TNG/JGiven'>JGiven</a> - on " );
writer.print( DateFormat.getDateTimeInstance().format( new Date() ) );
writer.print( "<div id='footer'>Generated by <a href='http://jgiven.org'>JGiven</a> " );
writer.print( Version.VERSION );
writer.print( " - on " + DateFormat.getDateTimeInstance().format( new Date() ) );
closeDiv();
}

Expand Down
Expand Up @@ -97,7 +97,7 @@ public void visitEnd( ScenarioModel scenarioModel ) {
public void visit( ScenarioCaseModel scenarioCase ) {
this.scenarioCase = scenarioCase;
printCaseHeader( scenarioCase );
String collapsed = scenarioCase.arguments.isEmpty() || scenarioModel.isCasesAsTable() ? "" : " collapsed";
String collapsed = scenarioCase.getExplicitArguments().isEmpty() || scenarioModel.isCasesAsTable() ? "" : " collapsed";
writer.println( "<ul class='steps" + collapsed + "' id='" + getCaseId() + "'>" );
}

Expand All @@ -107,19 +107,19 @@ private String getCaseId() {

void printCaseHeader( ScenarioCaseModel scenarioCase ) {
writer.println( format( "<div class='case %sCase'>", scenarioCase.success ? "passed" : "failed" ) );
if( !scenarioCase.arguments.isEmpty() ) {
if( !scenarioCase.getExplicitArguments().isEmpty() ) {
writer.print( format( "<h4 onclick='toggle(\"%s\")'>", getCaseId() ) );
writeStatusIcon( scenarioCase.success );
writer.print( format( " Case %d: ", scenarioCase.caseNr ) );

for( int i = 0; i < scenarioCase.arguments.size(); i++ ) {
if( scenarioModel.parameterNames.size() > i ) {
writer.print( scenarioModel.parameterNames.get( i ) + " = " );
for( int i = 0; i < scenarioCase.getExplicitArguments().size(); i++ ) {
if( scenarioModel.getExplicitParameters().size() > i ) {
writer.print( scenarioModel.getExplicitParameters().get( i ) + " = " );
}

writer.print( scenarioCase.arguments.get( i ) );
writer.print( scenarioCase.getExplicitArguments().get( i ) );

if( i < scenarioCase.arguments.size() - 1 ) {
if( i < scenarioCase.getExplicitArguments().size() - 1 ) {
writer.print( ", " );
}
}
Expand Down Expand Up @@ -147,9 +147,9 @@ public void visit( StepModel stepModel ) {
if( !firstWord ) {
writer.print( ' ' );
}
String text = HtmlEscapers.htmlEscaper().escape( word.value );
String text = HtmlEscapers.htmlEscaper().escape( word.getValue() );

if( firstWord && word.isIntroWord ) {
if( firstWord && word.isIntroWord() ) {
writer.print( format( "<span class='introWord'>%s</span>", WordUtil.capitalize( text ) ) );
} else if( word.isArg() ) {
printArg( word );
Expand All @@ -171,7 +171,8 @@ public void visit( StepModel stepModel ) {
}

private void printArg( Word word ) {
String value = word.getArgumentInfo().isParameter() ? formatCaseArgument( word ) : HtmlEscapers.htmlEscaper().escape( word.value );
String value = word.getArgumentInfo().isParameter() ? formatCaseArgument( word ) : HtmlEscapers.htmlEscaper().escape(
word.getFormattedValue() );
value = escapeToHtml( value );
String multiLine = value.contains( "<br />" ) ? "multiline" : "";
String caseClass = word.getArgumentInfo().isParameter() ? "caseArgument" : "argument";
Expand All @@ -183,6 +184,6 @@ private String escapeToHtml( String value ) {
}

String formatCaseArgument( Word word ) {
return HtmlEscapers.htmlEscaper().escape( word.value );
return HtmlEscapers.htmlEscaper().escape( word.getValue() );
}
}
@@ -1,15 +1,18 @@
package com.tngtech.jgiven.report.impl;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.tngtech.jgiven.report.model.ArgumentInfo;
import com.tngtech.jgiven.report.model.ReportModel;
import com.tngtech.jgiven.report.model.ReportModelVisitor;
import com.tngtech.jgiven.report.model.ScenarioCaseModel;
Expand All @@ -21,7 +24,9 @@
* Analyzes a report model and tries to infer which step method arguments match to which case argument.
*
* This is done by comparing all cases of a scenario and find out which method arguments
* match in all cases to the same parameter
* match in all cases to the same parameter.
*
* The algorithm is rather complex, but I could not find an easier one yet.
*
*/
public class CaseArgumentAnalyser {
Expand Down Expand Up @@ -66,10 +71,13 @@ private boolean wordsAreEqual( List<Word> firstWords, List<Word> words ) {
return false;
}
for( int j = 0; j < words.size(); j++ ) {
if( firstWords.get( j ).isArg() && firstWords.get( j ).getArgumentInfo().isParameter() ) {
Word firstWord = firstWords.get( j );
Word word = words.get( j );
if( firstWord.isArg() && word.isArg()
&& Objects.equal( firstWord.getArgumentInfo().getArgumentName(), word.getArgumentInfo().getArgumentName() ) ) {
continue;
}
if( !firstWords.get( j ).equals( words.get( j ) ) ) {
if( !firstWord.equals( word ) ) {
return false;
}
}
Expand All @@ -90,60 +98,124 @@ public ArgumentHolder get( int i ) {
}
}

static class ParameterReplacement {
List<Word> arguments;
ParameterMatch match;
String replacementName;
boolean isStepParameterName;

public void updateToStepParameterName() {
replacementName = arguments.get( 0 ).getArgumentInfo().getArgumentName();
isStepParameterName = true;
}
}

private void reduceMatrix( ScenarioModel scenarioModel, List<CaseArguments> argumentMatrix ) {
int nArguments = argumentMatrix.get( 0 ).arguments.size();
List<String> derivedParams = Lists.newArrayList();
Map<String, ParameterReplacement> usedParameters = Maps.newLinkedHashMap();
List<ParameterReplacement> parameterReplacements = Lists.newArrayList();

for( int iArg = 0; iArg < nArguments; iArg++ ) {
Set<String> currentSet = Sets.newLinkedHashSet();
currentSet.addAll( argumentMatrix.get( 0 ).get( iArg ).params );
for( int iCase = 1; iCase < argumentMatrix.size(); iCase++ ) {
currentSet.retainAll( argumentMatrix.get( iCase ).get( iArg ).params );
List<Word> arguments = getArgumentsOfAllCases( argumentMatrix, iArg );

if( allArgumentsAreEqual( arguments ) ) {
continue;
}
if( currentSet.size() > 1 ) {
log.warn( "Could not disambiguate case arguments for argument " + iArg + ". Values: " + currentSet );
} else if( currentSet.isEmpty() ) {
log.warn( "Could not identify parameter index for argument " + iArg );

if( !allArgumentsAreEqual( argumentMatrix, iArg ) ) {
String parameterName = argumentMatrix.get( 0 ).get( iArg ).word.getArgumentInfo().getArgumentName();
derivedParams.add( parameterName );
for( int iCase = 0; iCase < argumentMatrix.size(); iCase++ ) {
CaseArguments caseArguments = argumentMatrix.get( iCase );
Word word = caseArguments.get( iArg ).word;
ArgumentInfo argumentInfo = word.getArgumentInfo();
argumentInfo.setParameterName( parameterName );
argumentInfo.setDerivedParameter( true );
caseArguments.caseModel.addArguments( word.value );

ParameterReplacement replacement = new ParameterReplacement();
replacement.arguments = arguments;
parameterReplacements.add( replacement );

Collection<ParameterMatch> parameterMatches = getPossibleParameterNames( argumentMatrix, iArg );

if( !parameterMatches.isEmpty() ) {
Iterator<ParameterMatch> iterator = parameterMatches.iterator();
ParameterMatch match = iterator.next();
replacement.match = match;
replacement.replacementName = match.parameter;

if( usedParameters.containsKey( match.parameter ) ) {
ParameterReplacement usedReplacement = usedParameters.get( match.parameter );
if( match.formattedValueMatches && !usedReplacement.match.formattedValueMatches ) {
usedReplacement.updateToStepParameterName();
}
scenarioModel.addDerivedParameter( parameterName );
}

continue;
usedParameters.put( match.parameter, replacement );

if( iterator.hasNext() ) {
log.debug( "Multiple parameter matches found for argument " + iArg + ": " + parameterMatches
+ ". Took the first one." );
}
} else {
replacement.updateToStepParameterName();
}
for( int iCase = 0; iCase < argumentMatrix.size(); iCase++ ) {
Word word = argumentMatrix.get( iCase ).get( iArg ).word;
word.getArgumentInfo().setParameterName( currentSet.iterator().next() );
}

for( ParameterReplacement replacement : parameterReplacements ) {
scenarioModel.addDerivedParameter( replacement.replacementName );
for( int i = 0; i < replacement.arguments.size(); i++ ) {
Word word = replacement.arguments.get( i );
word.getArgumentInfo().setParameterName( replacement.replacementName );
word.getArgumentInfo().setDerivedParameter( replacement.isStepParameterName );
scenarioModel.getCase( i ).addDerivedArguments( word.getFormattedValue() );
}
}

}

private boolean allArgumentsAreEqual( List<CaseArguments> argumentMatrix, int iArg ) {
Word word = argumentMatrix.get( 0 ).get( iArg ).word;
boolean allWordsAreEqual = true;
for( int iCase = 1; iCase < argumentMatrix.size(); iCase++ ) {
Word word2 = argumentMatrix.get( iCase ).get( iArg ).word;
if( !word.equals( word2 ) ) {
allWordsAreEqual = false;
break;
private Collection<ParameterMatch> getPossibleParameterNames( List<CaseArguments> argumentMatrix, int iArg ) {
Map<String, ParameterMatch> result = toMap( argumentMatrix.get( 0 ).arguments.get( iArg ).params );

for( int i = 1; i < argumentMatrix.size(); i++ ) {
Map<String, ParameterMatch> map = toMap( argumentMatrix.get( i ).arguments.get( iArg ).params );
for( String key : Lists.newArrayList( result.keySet() ) ) {
if( !map.containsKey( key ) ) {
result.remove( key );
} else {
result.get( key ).formattedValueMatches &= map.get( key ).formattedValueMatches;
}
}
}
return result.values();
}

private Map<String, ParameterMatch> toMap( Set<ParameterMatch> params ) {
Map<String, ParameterMatch> result = Maps.newLinkedHashMap();
for( ParameterMatch match : params ) {
result.put( match.parameter, match );
}
return result;
}

private List<Word> getArgumentsOfAllCases( List<CaseArguments> argumentMatrix, int iArg ) {
List<Word> result = Lists.newArrayList();
for( int iCase = 0; iCase < argumentMatrix.size(); iCase++ ) {
result.add( argumentMatrix.get( iCase ).get( iArg ).word );
}
return result;
}

private boolean allArgumentsAreEqual( List<Word> arguments ) {
Word firstWord = arguments.get( 0 );
for( int i = 1; i < arguments.size(); i++ ) {
Word word = arguments.get( i );
if( !firstWord.equals( word ) ) {
return false;
}
}
return allWordsAreEqual;
return true;
}

static class ParameterMatch {
String parameter;
int index;
boolean formattedValueMatches;
}

static class ArgumentHolder {
Word word;
Set<String> params;
Set<ParameterMatch> params;
}

/**
Expand Down Expand Up @@ -184,12 +256,17 @@ public void visit( StepModel methodModel ) {
}
}

private Set<String> getMatchingParameters( Word word ) {
Set<String> matchingParameters = Sets.newLinkedHashSet();
for( int i = 0; i < currentCase.arguments.size(); i++ ) {
if( Objects.equal( word.value, currentCase.arguments.get( i ) ) ) {
if( i < scenarioModel.parameterNames.size() ) {
matchingParameters.add( scenarioModel.parameterNames.get( i ) );
private Set<ParameterMatch> getMatchingParameters( Word word ) {
Set<ParameterMatch> matchingParameters = Sets.newLinkedHashSet();
for( int i = 0; i < currentCase.getExplicitArguments().size(); i++ ) {
String argumentValue = currentCase.getExplicitArguments().get( i );
if( Objects.equal( word.getValue(), argumentValue ) ) {
if( i < scenarioModel.getExplicitParameters().size() ) {
ParameterMatch match = new ParameterMatch();
match.index = i;
match.parameter = scenarioModel.getExplicitParameters().get( i );
match.formattedValueMatches = Objects.equal( word.getFormattedValue(), argumentValue );
matchingParameters.add( match );
}
}
}
Expand Down
Expand Up @@ -25,6 +25,12 @@ public class ArgumentInfo {
*/
private String argumentName;

/**
* The value of the argument after formatting has been applied.
* Can be {@code null}
*/
private String formattedValue;

public void setParameterName( String parameterName ) {
this.parameterName = parameterName;
}
Expand Down Expand Up @@ -63,6 +69,14 @@ public boolean isDerivedParameter() {
return isDerivedParameter;
}

public void setFormattedValue( String formattedValue ) {
this.formattedValue = formattedValue;
}

public String getFormattedValue() {
return formattedValue;
}

@Override
public int hashCode() {
return Objects.hashCode( parameterName, argumentName, isDerivedParameter );
Expand Down

0 comments on commit b2ed91d

Please sign in to comment.