/*
* The MIT License
*
* Copyright (c) 2013, Cisco Systems, Inc., a California corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.cucumber.jsontestsupport;
import gherkin.formatter.Argument;
import gherkin.formatter.model.Background;
import gherkin.formatter.model.Comment;
import gherkin.formatter.model.DataTableRow;
import gherkin.formatter.model.DescribedStatement;
import gherkin.formatter.model.Match;
import gherkin.formatter.model.Result;
import gherkin.formatter.model.Step;
import gherkin.formatter.model.Tag;
import gherkin.formatter.model.TagStatement;
import java.util.List;
import java.util.Locale;
public class ScenarioToHTML {
private enum RESULT_TYPE {
/** Step failed as it was not defined */
UNDEFINED("background-color: #ffeeee;"),
/** step passed */
PASSED("background-color: #e6ffcc;"),
/** step failed */
FAILED("background-color: #ffeeee;"),
/** step skipped due to previous failure */
SKIPPED("background-color: #ffffcc;"),
/** line does not have a result */
NO_RESULT(""),
/** glue code is not implemented */
PENDING("background-color: #ffffcc;");
public final String css;
RESULT_TYPE(String css) {
this.css = css;
}
public static RESULT_TYPE typeFromResult(Result r) {
return RESULT_TYPE.valueOf(r.getStatus().toUpperCase(Locale.UK));
}
}
private int indent = 0;
private ScenarioResult scenarioResult;
public ScenarioToHTML(ScenarioResult scenarioResult) {
this.scenarioResult = scenarioResult;
}
public static String getHTML(ScenarioResult scenarioResult) {
return new ScenarioToHTML(scenarioResult).getHTML();
}
/**
* Builds a Gherkin file from the results of the parsing and formats it for HTML. XXX this should be moved
* elsewhere!
*/
public String getHTML() {
// we will be pretty big so start of large to avoild re-allocation.
StringBuilder sb = new StringBuilder(20 * 1024);
sb.append("<table border=\"0\" cellpadding=\"3\" cellspacing=\"0\" bgcolor=\"#ffffff\">\n");
sb.append("<tbody>\n");
// being gherkin output...
addTagStatement(sb, scenarioResult.getParent().getFeature());
for (BeforeAfterResult before : scenarioResult.getBeforeResults()) {
addBeforeAfterResult(sb, "before", before);
}
addBackgroundResult(sb, scenarioResult.getBackgroundResult());
addTagStatement(sb, scenarioResult.getScenario());
for (StepResult stepResult : scenarioResult.getStepResults()) {
addStepResult(sb, stepResult);
}
for (BeforeAfterResult after : scenarioResult.getAfterResults()) {
addBeforeAfterResult(sb, "after", after);
}
// end gherkin output...
sb.append("</tbody></table>");
List<EmbeddedItem> embeddedItems = scenarioResult.getEmbeddedItems();
if (!embeddedItems.isEmpty()) {
sb.append("<h2>Embedded Items</h2>\n");
sb.append("<ul>");
for (EmbeddedItem embeddedItem : embeddedItems) {
addEmbeddedItem(sb, embeddedItem);
}
sb.append("</ul>");
}
return sb.toString();
}
private StringBuilder addEmbeddedItem(StringBuilder stringBuilder, EmbeddedItem embeddedItem) {
stringBuilder.append("<li>");
stringBuilder.append("<a href=\"./embed/").append(embeddedItem.getFilename()).append("\">");
stringBuilder.append(embeddedItem.getFilename());
stringBuilder.append("</a> of type ");
stringBuilder.append(embeddedItem.getMimetype());
stringBuilder.append("</li>\n");
return stringBuilder;
}
private StringBuilder addTagStatement(StringBuilder sb, TagStatement tagStatement) {
for (Comment comment : tagStatement.getComments()) {
addComment(sb, comment);
}
for (Tag tag : tagStatement.getTags()) {
createLine(sb, tag.getLine(), RESULT_TYPE.NO_RESULT);
sb.append(tag.getName());
}
createLine(sb, tagStatement.getLine(), RESULT_TYPE.NO_RESULT);
appendKeyword(sb, tagStatement.getKeyword()).append(' ').append(tagStatement.getName());
String descr = tagStatement.getDescription();
indent++;
if (descr != null && !descr.isEmpty()) {
// may have been run on windows?
descr = descr.replace("\r\n", "\n");
String[] lines = descr.split("\\n");
for (int i=0; i < lines.length; i++){
endLine(sb);
createLine(sb, tagStatement.getLine() + i+1, RESULT_TYPE.NO_RESULT);
sb.append("<span style=\"font-style:italic\">");
sb.append(lines[i]);
sb.append("</span>");
}
}
endLine(sb);
return sb;
}
public StringBuilder addDescribedStatement(StringBuilder sb, DescribedStatement ds) {
for (Comment comment : ds.getComments()) {
addComment(sb, comment);
}
createLine(sb, ds.getLine(), RESULT_TYPE.NO_RESULT);
appendKeyword(sb, ds.getKeyword());
sb.append(' ');
sb.append(ds.getName());
endLine(sb);
return sb;
}
private StringBuilder createLine(StringBuilder sb, Integer line, RESULT_TYPE type) {
String lineStr = String.format("%03d", line);
return createLine(sb, lineStr, type);
}
private StringBuilder createLine(StringBuilder sb, String str, RESULT_TYPE type) {
sb.append("\n<tr><td valign=\"top\" align=\"right\"><a style=\"color:#808080\" name=\"").append(str).append("\">");
sb.append(str);
sb.append("</a></td>");
sb.append("<td nowrap=\"nowrap\" valign=\"top\" align=\"left\" style=\"").append(type.css).append("\">");
sb.append("<div style=\"padding-left: ").append(indent).append("em;");
sb.append(type.css);
sb.append("\">");
return sb;
}
private StringBuilder endLine(StringBuilder sb) {
return sb.append("</div></td>");
}
public StringBuilder addComment(StringBuilder sb, Comment comment) {
createLine(sb, comment.getLine(), RESULT_TYPE.NO_RESULT);
sb.append("<span style=\"font-style:italic; color: #666666\">");
sb.append(comment.getValue());
sb.append("</span>");
endLine(sb);
return sb;
}
public StringBuilder appendKeyword(StringBuilder sb, String keyword) {
sb.append("<span style=\"font-weight: bold; color: ##4D0080\">").append(keyword).append("</span>");
return sb;
}
public StringBuilder addBeforeAfterResult(StringBuilder sb,
String beforeOrAfter,
BeforeAfterResult beforeAfter) {
Match m = beforeAfter.getMatch();
Result r = beforeAfter.getResult();
createLine(sb, beforeOrAfter, RESULT_TYPE.typeFromResult(r));
sb.append(m.getLocation()).append(' ');
addFailure(sb, r);
// XXX add argument formatting
// List<Argument> args = m.getArguments();
endLine(sb);
return sb;
}
public StringBuilder addFailure(StringBuilder sb, Result result) {
if (Result.FAILED.equals(result.getStatus())) {
createLine(sb, "Failure", RESULT_TYPE.FAILED);
String[] stack = result.getErrorMessage().split("\n");
sb.append(stack[0]).append("<br>");
for (int i = 1; i < stack.length; i++) {
sb.append(stack[i].replaceAll("\t", " "));
sb.append("<br>");
}
// Error is always null (only non null when invoked direct as part of the test).
/*
* Throwable t = result.getError(); if (t != null) { StackTraceElement stack[] = t.getStackTrace();
* for (StackTraceElement ste : stack) { sb.append(ste.toString()).append("<br>"); } }
*/
}
else if ("undefined".equals(result.getStatus())) {
createLine(sb, "Undefined", RESULT_TYPE.UNDEFINED);
sb.append("Step is undefined");
// We have no error message.
}
endLine(sb);
return sb;
}
public StringBuilder addBackgroundResult(StringBuilder sb, BackgroundResult backgroundResult) {
if (backgroundResult != null) {
Background background = backgroundResult.getBackground();
addDescribedStatement(sb, background);
for (StepResult step : backgroundResult.getStepResults()) {
addStepResult(sb, step);
}
}
return sb;
}
public StringBuilder addStepResult(StringBuilder sb, StepResult stepResult) {
Step step = stepResult.getStep();
{
List<Comment> comments = step.getComments();
if (comments != null) {
for (Comment c : comments) {
addComment(sb, c);
}
}
}
createLine(sb, step.getLine(), RESULT_TYPE.typeFromResult(stepResult.getResult()));
appendKeyword(sb, step.getKeyword());
sb.append(' ');
sb.append(step.getName());
if (step.getRows() != null) {
indent++;
boolean firstRow = true;
for (DataTableRow dtr : step.getRows()) {
List<Comment> comments = dtr.getComments();
if (comments != null) {
for (Comment comment : comments) {
addComment(sb, comment);
}
}
createLine(sb, dtr.getLine(), RESULT_TYPE.NO_RESULT);
int colwidth = 100 / (dtr.getCells().size());
// these span multiple lines and divs don't wrap if the argument is too long
// so use a table per row with the same sizes for each column. ugly but works...
// having a large colspan would be nice but then we need to compute all the possibilities up
// front.
sb.append("<table width=\"80%\">");
sb.append("<tr>");
for (String cell : dtr.getCells()) {
if (firstRow) {
sb.append("<th width=\"").append(colwidth).append("%\">");
sb.append(cell);
sb.append("</th>");
continue;
}
sb.append("<td width=\"").append(colwidth).append("%\">");
sb.append(cell);
sb.append("</td>");
}
sb.append("</tr></table>");
firstRow = false;
endLine(sb);
}
indent--;
}
endLine(sb);
// TODO add support for table rows...
addFailure(sb, stepResult.getResult());
return sb;
}
}