/*******************************************************************************
* Copyright (c) 2013, 2014 Sylvain Lézier.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sylvain Lézier - initial implementation
*******************************************************************************/
package sft.report;
import sft.*;
import sft.decorators.*;
import sft.environment.TargetFolder;
import sft.report.decorators.*;
import sft.result.*;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
public class HtmlReport extends Report {
public static final String HTML_DEPENDENCIES_FOLDER = "sft-html-default";
private final RelativePathResolver pathResolver = new RelativePathResolver();
public String useCaseTemplate =
"<html>\n" +
" <head>\n" +
" <title>@@@useCase.name@@@</title>\n" +
"@@@useCase.css@@@" +
"@@@useCase.js@@@" +
" </head>\n" +
" <body class=\"useCase @@@useCase.issue@@@\">\n" +
" <div class=\"container\">\n" +
" <div class=\"page-header\">\n" +
" <div class=\"text-center\">\n" +
" <h1><span class=\"useCaseName\">@@@useCase.name@@@</span></h1>\n" +
" </div>\n" +
"@@@useCaseCommentTemplate@@@" +
" </div>\n" +
"@@@beforeUseCaseTemplate@@@" +
"@@@scenarioTemplates@@@" +
"@@@afterUseCaseTemplate@@@" +
"@@@relatedUseCasesTemplates@@@" +
" </div>\n" +
" </body>\n" +
"</html>";
public String useCaseCommentTemplate =
" <div class=\"comment\">\n" +
"@@@comment.text@@@" +
" </div>\n";
public String beforeUseCaseTemplate =
" <div class=\"panel panel-default beforeUseCase @@@beforeUseCase.issue@@@\">\n" +
" <div class=\"panel-body\">\n" +
"@@@contextInstructionTemplates@@@" +
" </div>\n" +
"@@@exceptionTemplate@@@" +
" </div>\n";
public String scenarioTemplate =
" <div class=\"scenario @@@scenario.issue@@@ panel panel-default\">\n" +
" <div class=\"panel-heading\">\n" +
" <h3><span class=\"scenarioName\">@@@scenario.name@@@</span></h3>\n" +
" </div>\n" +
"@@@scenarioCommentTemplate@@@" +
"@@@beforeScenarioTemplate@@@" +
" <div class=\"panel-body\">\n" +
"@@@scenarioInstructionTemplates@@@" +
" </div>\n" +
"@@@afterScenarioTemplate@@@" +
"@@@displayedContextsTemplates@@@" +
"@@@exceptionTemplate@@@" +
" </div>\n";
public String scenarioCommentTemplate =
" <div class=\"comment\">\n" +
"@@@comment.text@@@" +
" </div>\n";
public String exceptionTemplate =
" <div class=\"panel-body\">\n" +
" <div class=\"exception\">\n" +
" <a onClick=\"$(this).next().toggle()\" >@@@failure.className@@@: @@@failure.message@@@</a>\n" +
" <pre class=\"stacktrace pre-scrollable\" >@@@failure.stacktrace@@@</pre>\n" +
" </div>\n" +
" </div>\n";
public String beforeScenarioTemplate =
" <div class=\"beforeScenario panel-body\">\n" +
"@@@contextInstructionTemplates@@@" +
" <hr/>\n" +
" </div>";
public String scenarioInstructionTemplate =
" @@@instruction.emptyLines@@@" +
" <div class=\"instruction @@@instruction.issue@@@\">\n" +
" <span>@@@instruction.text@@@</span>" +
" </div>\n";
public String afterScenarioTemplate =
" <div class=\"afterScenario panel-body\">\n" +
" <hr/>\n " +
"@@@contextInstructionTemplates@@@" +
" </div>";
public String displayedContextsTemplate =
" <div class=\"displayableContext panel-body\">\n" +
"@@@displayedContextTemplates@@@" +
" </div>\n";
public String displayedContextTemplate =
" <div>\n" +
"@@@displayedContext.text@@@" +
" </div>\n";
public String afterUseCaseTemplate =
" <div class=\"panel panel-default afterUseCase @@@afterUseCase.issue@@@\">\n" +
" <div class=\"panel-body\">\n" +
"@@@contextInstructionTemplates@@@" +
" </div>\n" +
"@@@exceptionTemplate@@@" +
" </div>\n";
public String contextInstructionTemplate =
" <div>\n" +
" <span>@@@instruction.text@@@</span>\n" +
" </div>\n";
public String relatedUseCasesTitleTemplate =
" <div class=\"panel-heading\">\n" +
" <h3>@@@relatedUseCasesTitle@@@</h3>\n" +
" </div>\n";
public String relatedUseCasesTemplate =
" <div class=\"panel panel-default relatedUseCases\">\n" +
"@@@relatedUseCasesTitleTemplates@@@" +
" <div class=\"panel-body\">\n" +
" <ul>\n" +
"@@@relatedUseCaseTemplates@@@" +
" </ul>\n" +
" </div>\n" +
" </div>\n";
public String relatedUseCaseTemplate =
" <li class=\"relatedUseCase @@@relatedUseCase.issue@@@\">\n" +
" <a href=\"@@@relatedUseCase.link@@@\"><span>@@@relatedUseCase.name@@@</span></a>\n" +
" </li>\n";
public String parameterTemplate = "<i class=\"value\">@@@parameter.value@@@</i>";
public String ignoredClass = "ignored";
public String failedClass = "failed";
public String successClass = "succeeded";
private HtmlResources htmlResources;
private Map<Class<? extends Decorator>, DecoratorReportImplementation> decorators= new HashMap<Class<? extends Decorator>, DecoratorReportImplementation>();
private TargetFolder reportFolder;
private static final String TARGET_SFT_RESULT = "target/sft-result/";
public HtmlReport(DefaultConfiguration configuration) {
super(configuration);
reportFolder = configuration.getProjectFolder().getTargetFolder(TARGET_SFT_RESULT);
setResourcePath(HTML_DEPENDENCIES_FOLDER);
decorators.put(Style.class,new HtmlStyle(configuration));
decorators.put(Breadcrumb.class,new HtmlBreadcrumb(configuration));
decorators.put(Group.class,new HtmlGroup(configuration));
decorators.put(Table.class,new HtmlTable(configuration));
decorators.put(TableOfContent.class,new HtmlTableOfContent(configuration));
decorators.put(Synthesis.class,new HtmlSynthesis(configuration));
decorators.put(NullDecorator.class,new HtmlNullDecorator(configuration));
}
@Override
public void addDecorator(Class<? extends Decorator> decoratorClass, DecoratorReportImplementation decoratorImplementation){
decorators.put(decoratorClass, decoratorImplementation);
}
public void setReportPath(String reportPath) {
if( ! reportPath.equals( reportFolder.path)){
reportFolder = new TargetFolder(reportFolder.path,reportPath);
}
}
@Override
public DecoratorReportImplementation getDecoratorImplementation(Decorator decorator) {
if( decorators.containsKey(decorator.getClass())){
return decorators.get(decorator.getClass());
}
System.out.println("Decorator " + decorator.getClass().getCanonicalName() + " not Managed by " + this.getClass().getCanonicalName() + " using default decorator");
return new HtmlNullDecorator(configuration);
}
public void setResourcePath(String resourcePath) {
htmlResources = new HtmlResources(configuration, this,resourcePath);
}
@Override
public void report(UseCaseResult useCaseResult) throws Exception {
final Decorator decorator = useCaseResult.useCase.useCaseDecorator;
String useCaseReport = getDecoratorImplementation(decorator).applyOnUseCase(useCaseResult, decorator.parameters);
File htmlFile = reportFolder.createFileFromClass(useCaseResult.useCase.classUnderTest, ".html");
Writer html = new OutputStreamWriter(new FileOutputStream(htmlFile));
html.write(useCaseReport);
html.close();
System.out.println("Report wrote: " + htmlFile.getCanonicalPath());
}
public String applyOnUseCase(UseCaseResult useCaseResult) {
Class<?> classUnderTest = useCaseResult.useCase.classUnderTest;
try {
return new TemplateString(useCaseTemplate)
.replace("@@@useCase.name@@@", useCaseResult.useCase.getName())
.replace("@@@useCase.css@@@", htmlResources.getIncludeCssDirectives(classUnderTest))
.replace("@@@useCase.js@@@", htmlResources.getIncludeJsDirectives(classUnderTest))
.replace("@@@useCase.issue@@@", htmlResources.convertIssue(useCaseResult.getIssue()))
.replace("@@@useCaseCommentTemplate@@@", getUseCaseComment(useCaseResult.useCase))
.replace("@@@beforeUseCaseTemplate@@@", getBeforeUseCase(useCaseResult))
.replace("@@@scenarioTemplates@@@", getScenarios(useCaseResult))
.replace("@@@afterUseCaseTemplate@@@", getAfterUseCase(useCaseResult))
.replace("@@@relatedUseCasesTemplates@@@", getRelatedUseCases(useCaseResult))
.getText();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String applyOnScenario(ScenarioResult scenarioResult) {
return new TemplateString(scenarioTemplate)
.replace("@@@scenario.issue@@@", htmlResources.convertIssue(scenarioResult.issue))
.replace("@@@scenario.name@@@", scenarioResult.scenario.getName())
.replace("@@@scenarioCommentTemplate@@@", getScenarioComment(scenarioResult))
.replace("@@@beforeScenarioTemplate@@@", getBeforeScenario(scenarioResult))
.replace("@@@scenarioInstructionTemplates@@@", getScenarioInstructions(scenarioResult))
.replace("@@@afterScenarioTemplate@@@", getAfterScenario(scenarioResult))
.replace("@@@displayedContextsTemplates@@@", extractDisplayedContexts(scenarioResult))
.replace("@@@exceptionTemplate@@@", getStackTrace(scenarioResult.failure))
.getText();
}
public String generateFixtureCall(FixtureCallResult fixtureCallResult) {
return new TemplateString(scenarioInstructionTemplate)
.replace("@@@instruction.emptyLines@@@", generateEmptyLines(fixtureCallResult.fixtureCall.emptyLine))
.replace("@@@instruction.issue@@@", htmlResources.convertIssue(fixtureCallResult.issue))
.replace("@@@instruction.text@@@", generateInstructionWithParameter(fixtureCallResult.fixtureCall))
.getText();
}
private String generateEmptyLines(int emptyLine) {
String result = "";
for (int i = 0; i < emptyLine; i++) {
result += "<br/>";
}
return result;
}
private String getRelatedUseCases(UseCaseResult useCaseResult) {
if (!useCaseResult.subUseCaseResults.isEmpty()) {
return getRelatedUseCase(useCaseResult);
}
return "";
}
private String getRelatedUseCase(UseCaseResult useCaseResult) {
String result = "";
Decorator decorator = null;
ArrayList<SubUseCaseResult> subUseCaseResults = new ArrayList<SubUseCaseResult>();
for (SubUseCaseResult subUseCaseResult : useCaseResult.subUseCaseResults) {
if (decorator == null) {
decorator = subUseCaseResult.subUseCase.decorator;
} else if (!decorator.comply(subUseCaseResult.subUseCase.decorator)) {
result += getDecoratorImplementation(decorator).applyOnSubUseCases(subUseCaseResults, decorator.parameters);
decorator = subUseCaseResult.subUseCase.decorator;
subUseCaseResults = new ArrayList<SubUseCaseResult>();
}
subUseCaseResults.add(subUseCaseResult);
}
if (!subUseCaseResults.isEmpty()) {
result += getDecoratorImplementation(decorator).applyOnSubUseCases(subUseCaseResults, decorator.parameters);
}
return result;
}
public String applyOnSubUseCases(String title, List<SubUseCaseResult> subUseCaseResults) {
String relatedUseCase = "";
for (SubUseCaseResult subUseCaseResult : subUseCaseResults) {
relatedUseCase += new TemplateString(relatedUseCaseTemplate)
.replace("@@@relatedUseCase.issue@@@", htmlResources.convertIssue(subUseCaseResult.useCaseResult.getIssue()))
.replace("@@@relatedUseCase.link@@@", pathResolver.getRelativePathAsFile(subUseCaseResult.subUseCase.parentUseCase.classUnderTest, subUseCaseResult.useCaseResult.useCase.classUnderTest, ".html"))
.replace("@@@relatedUseCase.name@@@", subUseCaseResult.useCaseResult.useCase.getName())
.getText();
}
final TemplateString replace = new TemplateString(relatedUseCasesTemplate)
.replace("@@@relatedUseCaseTemplates@@@", relatedUseCase);
if (title != null) {
return replace.replace("@@@relatedUseCasesTitleTemplates@@@", new TemplateString(relatedUseCasesTitleTemplate).replace("@@@relatedUseCasesTitle@@@", title).getText()).getText();
} else {
return replace.replace("@@@relatedUseCasesTitleTemplates@@@", "").getText();
}
}
private String getScenarios(UseCaseResult useCaseResult) {
String result = "";
Decorator decorator = new NullDecorator(configuration);
final ArrayList<ScenarioResult> scenarioResults= new ArrayList<ScenarioResult>();
for( ScenarioResult scenarioResult : useCaseResult.scenarioResults ){
final Scenario scenario = scenarioResult.scenario;
if(decorator != null && !decorator.comply(scenario.decorator)) {
result += getDecoratorImplementation(decorator).applyOnScenarios(scenarioResults, decorator.parameters);
scenarioResults.clear();
}
decorator = scenario.decorator;
scenarioResults.add(scenarioResult);
}
result += getDecoratorImplementation(decorator).applyOnScenarios(scenarioResults, decorator.parameters);
return result;
}
private String getScenarioInstructions(ScenarioResult scenarioResult) {
String result = "";
Decorator decorator = null;
final ArrayList<FixtureCallResult> fixtureCallResults = new ArrayList<FixtureCallResult>();
for (FixtureCallResult fixtureCallResult : scenarioResult.fixtureCallResults) {
final Fixture fixture = fixtureCallResult.fixtureCall.fixture;
if (decorator != null && !decorator.comply(fixture.decorator)) {
result += getDecoratorImplementation(decorator).applyOnFixtures(fixtureCallResults, decorator.parameters);
fixtureCallResults.clear();
}
decorator = fixture.decorator;
fixtureCallResults.add(fixtureCallResult);
}
result += getDecoratorImplementation(decorator).applyOnFixtures(fixtureCallResults, decorator.parameters);
return result;
}
private String getBeforeScenario(ScenarioResult scenarioResult) {
if (scenarioResult.scenario.useCase.beforeScenario != null) {
String instructions = getContextInstructions(scenarioResult.scenario.useCase.beforeScenario);
return new TemplateString(beforeScenarioTemplate)
.replace("@@@contextInstructionTemplates@@@", instructions)
.getText();
}
return "";
}
private String getAfterScenario(ScenarioResult scenarioResult) {
if (scenarioResult.scenario.useCase.afterScenario != null) {
String instructions = getContextInstructions(scenarioResult.scenario.useCase.afterScenario);
return new TemplateString(afterScenarioTemplate)
.replace("@@@contextInstructionTemplates@@@", instructions)
.getText();
}
return "";
}
private String getScenarioComment(ScenarioResult scenarioResult) {
String comment = "";
if (scenarioResult.scenario.getComment() != null) {
comment = new TemplateString(scenarioCommentTemplate)
.replace("@@@comment.text@@@", scenarioResult.scenario.getComment())
.getText();
}
return comment;
}
private String extractDisplayedContexts(ScenarioResult scenarioResult) {
List<String> values = scenarioResult.contextToDisplay;
if (!values.isEmpty()) {
String htmlText = "";
for (String value : values) {
htmlText += extractDisplayedContext(value);
}
return new TemplateString(displayedContextsTemplate)
.replace("@@@displayedContextTemplates@@@", htmlText)
.getText();
}
return "";
}
private String getStackTrace(Throwable failure) {
if (failure != null) {
StringWriter stringWriter = new StringWriter();
failure.printStackTrace(new PrintWriter(stringWriter));
return new TemplateString(exceptionTemplate)
.replace("@@@failure.className@@@", failure.getClass().getSimpleName())
.replace("@@@failure.message@@@", failure.getMessage())
.replace("@@@failure.stacktrace@@@", stringWriter.toString())
.getText();
}
return "";
}
private String extractDisplayedContext(String value) {
return new TemplateString(displayedContextTemplate)
.replace("@@@displayedContext.text@@@", value)
.getText();
}
private String getBeforeUseCase(UseCaseResult useCaseResult) {
UseCase useCase = useCaseResult.useCase;
if (useCase.beforeUseCase != null) {
return new TemplateString(beforeUseCaseTemplate)
.replace("@@@beforeUseCase.issue@@@", htmlResources.convertIssue(useCaseResult.beforeResult.issue))
.replace("@@@contextInstructionTemplates@@@", getContextInstructions(useCase.beforeUseCase))
.replace("@@@exceptionTemplate@@@", getStackTrace(useCaseResult.beforeResult))
.getText();
} else {
return "";
}
}
private String getAfterUseCase(UseCaseResult useCaseResult) throws IOException {
UseCase useCase = useCaseResult.useCase;
if (useCase.afterUseCase != null) {
return new TemplateString(afterUseCaseTemplate)
.replace("@@@afterUseCase.issue@@@", htmlResources.convertIssue(useCaseResult.afterResult.issue))
.replace("@@@contextInstructionTemplates@@@", getContextInstructions(useCase.afterUseCase))
.replace("@@@exceptionTemplate@@@", getStackTrace(useCaseResult.afterResult))
.getText();
} else {
return "";
}
}
private String getStackTrace(ContextResult contextResult) {
if (contextResult.isSuccessful()) {
return "";
} else {
return getStackTrace(contextResult.exception);
}
}
private String getContextInstructions(ContextHandler context) {
String instructions = "";
for (FixtureCall fixtureCall : context.fixtureCalls) {
instructions += new TemplateString(contextInstructionTemplate)
.replace("@@@instruction.text@@@", generateInstructionWithParameter(fixtureCall))
.getText();
}
return instructions;
}
public String generateInstructionWithParameter(FixtureCall testFixture) {
String instruction = testFixture.fixture.getText();
for (Map.Entry<String, String> parameter : testFixture.getParameters().entrySet()) {
String value = Matcher.quoteReplacement(getParameter(parameter.getValue()));
instruction = instruction.replaceAll("\\$\\{" + parameter.getKey() + "\\}", value);
}
return instruction;
}
private String getParameter(String value) {
return new TemplateString(parameterTemplate)
.replace("@@@parameter.value@@@", value)
.getText();
}
private String getUseCaseComment(UseCase useCase) {
String comment = "";
if (useCase.haveComment()) {
return new TemplateString(useCaseCommentTemplate)
.replace("@@@comment.text@@@", useCase.getComment())
.getText();
}
return comment;
}
public HtmlResources getHtmlResources() {
return htmlResources;
}
public String getIssueConverter(Issue issue) {
switch (issue) {
case IGNORED:
return ignoredClass;
case FAILED:
return failedClass;
default:
case SUCCEEDED:
return successClass;
}
}
public String applyOnFixtures(List<FixtureCallResult> fixtureCallResults) {
String result = "";
for (FixtureCallResult fixture : fixtureCallResults) {
result += generateFixtureCall(fixture);
}
return result;
}
public String applyOnScenarios(List<ScenarioResult> scenarioResults) {
String scenarioTxt = "";
for (ScenarioResult scenarioResult : scenarioResults) {
scenarioTxt += applyOnScenario(scenarioResult);
}
return scenarioTxt;
}
public void setReportFolder(String targetPath) {
reportFolder = configuration.getProjectFolder().getTargetFolder(targetPath);
}
public TargetFolder getReportFolder() {
return reportFolder;
}
}