From 8647f2bcadf02f2624f3d2bf69b6f4cca1e52ed3 Mon Sep 17 00:00:00 2001 From: Ian Moore Date: Wed, 12 Oct 2016 23:17:57 +0100 Subject: [PATCH] more updates to the usage tree and reporting in general, WIP - non critical failures not clear --- .../runner/SubstepsExecutionConfig.java | 26 +++- .../substeps/runner/ExecutionNodeRunner.java | 22 ++- .../org/substeps/report/ReportingUtil.java | 12 +- .../org/substeps/report/ReportBuilder.scala | 142 +++++++++--------- .../org/substeps/report/ReportFrame.scala | 44 +++++- .../runner/ParsingFromSourceTests.scala | 2 + .../substeps/runner/ExecutionConfig.java | 26 ++++ .../substeps/runner/SubstepsRunnerMojo.java | 8 +- 8 files changed, 192 insertions(+), 90 deletions(-) diff --git a/api/src/main/java/com/technophobia/substeps/runner/SubstepsExecutionConfig.java b/api/src/main/java/com/technophobia/substeps/runner/SubstepsExecutionConfig.java index caf28351..934c88ce 100644 --- a/api/src/main/java/com/technophobia/substeps/runner/SubstepsExecutionConfig.java +++ b/api/src/main/java/com/technophobia/substeps/runner/SubstepsExecutionConfig.java @@ -18,6 +18,7 @@ */ package com.technophobia.substeps.runner; +import java.io.File; import java.io.Serializable; import java.util.Arrays; import java.util.List; @@ -59,6 +60,11 @@ public class SubstepsExecutionConfig implements Serializable { private String[] executionListeners; + private File dataOutputDirectory; + + private boolean checkForUncalledAndUnused = false; + + public String getDescription() { return description; } @@ -180,6 +186,24 @@ public void setScenarioName(String scenarioName) { } + public File getDataOutputDirectory() { + return dataOutputDirectory; + } + + public void setDataOutputDirectory(File dataOutputDirectory) { + this.dataOutputDirectory = dataOutputDirectory; + } + + public boolean isCheckForUncalledAndUnused() { + return checkForUncalledAndUnused; + } + + public void setCheckForUncalledAndUnused(boolean checkForUncalledAndUnused) { + this.checkForUncalledAndUnused = checkForUncalledAndUnused; + } + + + public String printParameters() { return "SubstepExecutionConfig [description=" + getDescription() + ", tags=" + getTags() + ", nonFatalTags=" + getNonFatalTags() + ", featureFile=" + getFeatureFile() + ", subStepsFileName=" @@ -189,6 +213,6 @@ public String printParameters() { + ", initialisationClass=" + Arrays.toString(getInitialisationClass()) + ", stepImplementationClasses=" + getStepImplementationClasses() + ", initialisationClasses=" + Arrays.toString(getInitialisationClasses()) + ", executionListeners=" - + Arrays.toString(getExecutionListeners()) + "]"; + + Arrays.toString(getExecutionListeners()) + "], dataOutputDirectory=" + getDataOutputDirectory(); } } \ No newline at end of file diff --git a/core/src/main/java/com/technophobia/substeps/runner/ExecutionNodeRunner.java b/core/src/main/java/com/technophobia/substeps/runner/ExecutionNodeRunner.java index 523886f3..b2727890 100644 --- a/core/src/main/java/com/technophobia/substeps/runner/ExecutionNodeRunner.java +++ b/core/src/main/java/com/technophobia/substeps/runner/ExecutionNodeRunner.java @@ -68,7 +68,7 @@ public class ExecutionNodeRunner implements SubstepsRunner { private List failures; - private final ReportingUtil reportingUtil = new ReportingUtil(new File("target")); + private final ReportingUtil reportingUtil = new ReportingUtil(); // map of nodes to each of the parents, where this node is used private final Map> callerHierarchy = new HashMap>(); @@ -92,9 +92,11 @@ public RootNode prepareExecutionConfig(final ExecutionConfigWrapper configWrappe setupExecutionListeners(configWrapper); - processUncalledAndUnused(syntax); + if (configWrapper.getExecutionConfig().isCheckForUncalledAndUnused()) { + processUncalledAndUnused(syntax, configWrapper.getExecutionConfig().getDataOutputDirectory()); + } - UsageTreeBuilder.buildUsageTree(this.rootNode); + // UsageTreeBuilder.buildUsageTree(this.rootNode); ExecutionContext.put(Scope.SUITE, INotificationDistributor.NOTIFIER_DISTRIBUTOR_KEY, this.notificationDistributor); @@ -200,21 +202,25 @@ private void setupExecutionListeners(ExecutionConfigWrapper configWrapper) { /** * @param syntax */ - private void processUncalledAndUnused(final Syntax syntax) { + private void processUncalledAndUnused(final Syntax syntax, final File dataOutputDir) { final List uncalledStepImplementations = syntax.getUncalledStepImplementations(); - reportingUtil.writeUncalledStepImpls(uncalledStepImplementations); + if (!dataOutputDir.exists()){ + dataOutputDir.mkdir(); + } + + reportingUtil.writeUncalledStepImpls(uncalledStepImplementations, dataOutputDir); buildCallHierarchy(); - checkForUncalledParentSteps(syntax); + checkForUncalledParentSteps(syntax, dataOutputDir); } /** * @param syntax */ - private void checkForUncalledParentSteps(final Syntax syntax) { + private void checkForUncalledParentSteps(final Syntax syntax, File outputDir) { final Set calledExecutionNodes = callerHierarchy.keySet(); @@ -230,7 +236,7 @@ private void checkForUncalledParentSteps(final Syntax syntax) { uncalledSubstepDefs.add(parent); } } - reportingUtil.writeUncalledStepDefs(uncalledSubstepDefs); + reportingUtil.writeUncalledStepDefs(uncalledSubstepDefs, outputDir); } private boolean thereIsNotAStepThatMatchesThisPattern(final String stepPattern, final Set calledExecutionNodes) { diff --git a/core/src/main/java/org/substeps/report/ReportingUtil.java b/core/src/main/java/org/substeps/report/ReportingUtil.java index 58a1a097..eee92bb7 100644 --- a/core/src/main/java/org/substeps/report/ReportingUtil.java +++ b/core/src/main/java/org/substeps/report/ReportingUtil.java @@ -21,11 +21,11 @@ */ public class ReportingUtil { - private final File outputDir; +// private final File outputDir; - public ReportingUtil(final File outputDir){ - this.outputDir = outputDir; - } +// public ReportingUtil(final File outputDir){ +// this.outputDir = outputDir; +// } private static Logger log = LoggerFactory.getLogger(ReportingUtil.class); @@ -77,7 +77,7 @@ public static Gson gson() { return gson.create(); } - public void writeUncalledStepDefs(List uncalledSubstepDefs) { + public void writeUncalledStepDefs(List uncalledSubstepDefs, File outputDir) { final StringBuilder buf = new StringBuilder(); @@ -114,7 +114,7 @@ public void writeUncalledStepDefs(List uncalledSubstepDefs) { } - public void writeUncalledStepImpls(List uncalledStepImplementations){ + public void writeUncalledStepImpls(List uncalledStepImplementations, File outputDir){ if (!uncalledStepImplementations.isEmpty()) { diff --git a/core/src/main/scala/org/substeps/report/ReportBuilder.scala b/core/src/main/scala/org/substeps/report/ReportBuilder.scala index 9864023c..697033a4 100644 --- a/core/src/main/scala/org/substeps/report/ReportBuilder.scala +++ b/core/src/main/scala/org/substeps/report/ReportBuilder.scala @@ -87,6 +87,7 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr } } + def buildFromDirectory(sourceDataDir: File): Unit = { reportDir.mkdir() @@ -95,8 +96,8 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr FileUtils.copyDirectory(new File(sourceDataDir.getPath), dataDir) - safeCopy(new File(sourceDataDir, "../uncalled.stepdefs.js"), new File(reportDir, "uncalled.stepdefs.js")) - safeCopy(new File(sourceDataDir, "../uncalled.stepimpls.js"), new File(reportDir, "uncalled.stepimpls.js")) + safeCopy(new File(sourceDataDir, "uncalled.stepdefs.js"), new File(reportDir, "uncalled.stepdefs.js")) + safeCopy(new File(sourceDataDir, "uncalled.stepimpls.js"), new File(reportDir, "uncalled.stepimpls.js")) val detailData = createFile( "detail_data.js") @@ -114,20 +115,12 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr val stats : ExecutionStats = buildExecutionStats(srcData) - val description = Option(srcData._1.description).getOrElse("Substeps Test Report") - val localDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(srcData._1.timestamp), ZoneId.systemDefault()); - val dateTimeString = localDate.format(DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss")) + val reportFrameHtml = buildReportFrame(srcData._1, stats + ) - val reportFrameHtml = buildReportFrame(description, dateTimeString, stats, - buildStatsBlock("Features", stats.featuresCounter), - buildStatsBlock("Scenarios", stats.scenarioCounters), - buildStatsBlock("Scenario steps", stats.stepCounters)) - val writer = Files.newWriter(reportFrameHTML, Charset.defaultCharset) - writer.append(reportFrameHtml) - writer.flush() - writer.close() + withWriter(reportFrameHTML, writer => writer.append(reportFrameHtml)) copyStaticResources() @@ -144,10 +137,9 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr val usgaeTreeHTMLFile = createFile( "usage-tree.html") val usageTreeHtml = buildUsageTree() - val writer2 = Files.newWriter(usgaeTreeHTMLFile, Charset.defaultCharset) - writer2.append(usageTreeHtml) - writer2.flush() - writer2.close() + + withWriter(usgaeTreeHTMLFile, writer => writer.append(usageTreeHtml) ) + } @@ -155,18 +147,16 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr def writeStatsJs(statsJsFile: File, stats: (List[Counters], List[Counters])) = { - val writer = Files.newWriter(statsJsFile, Charset.defaultCharset) - - implicit val formats = Serialization.formats(NoTypeHints) + withWriter(statsJsFile, writer => { - writer.append("var featureStatsData = ") - writer.append(writePretty(stats._1)) - writer.append(";\nvar scenarioStatsData = ") - writer.append(writePretty(stats._2)) - writer.append(";\n") + implicit val formats = Serialization.formats(NoTypeHints) - writer.flush() - writer.close() + writer.append("var featureStatsData = ") + writer.append(writePretty(stats._1)) + writer.append(";\nvar scenarioStatsData = ") + writer.append(writePretty(stats._2)) + writer.append(";\n") + }) } def buildExecutionStats(srcData: (RootNodeSummary, List[(FeatureSummary, List[NodeDetail])])): ExecutionStats = { @@ -320,21 +310,20 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr val substepDefNodes = getJSTreeCallHierarchyForSubstepDefs( allNodeDetails) - val writer = Files.newWriter(usageTreeDataFile, Charset.defaultCharset) - writer.append("var stepImplUsageTreeData=") - implicit val formats = Serialization.formats(NoTypeHints) + withWriter(usageTreeDataFile, writer => { + writer.append("var stepImplUsageTreeData=") - writer.append(writePretty(methodNodes)) - writer.append(";\nvar substepDefUsageTreeData=") + implicit val formats = Serialization.formats(NoTypeHints) - writer.append(writePretty(substepDefNodes)) + writer.append(writePretty(methodNodes)) + writer.append(";\nvar substepDefUsageTreeData=") - writer.append(";") - writer.flush() - writer.close() + writer.append(writePretty(substepDefNodes)) + writer.append(";") + }) } def getJSTreeCallHierarchyForSubstepDefs(allNodeDetails : List[NodeDetail]): Iterable[JsTreeNode] = { @@ -342,9 +331,7 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr val substepNodeDetails = allNodeDetails.filter(nd => nd.nodeType == "SubstepNode") val substepDefsByUniqueMethood = substepNodeDetails.groupBy(_.source.get) - var nextId = allNodeDetails.map(n => n.id).max + 1 - - println("x") + val nextId = allNodeDetails.map(n => n.id).max + 1 substepDefsByUniqueMethood.map(e => { @@ -367,16 +354,8 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr }) lastChildOption.get.head.copy(li_attr = Some(Map("data-substep-def-call" -> substep.description))) - // substep.description is what we want - applied to the lastChildOption..? -// JsTreeNode(ReportBuilder.uniqueId(substep.id), substep.description, substep.nodeType, lastChildOption, State(true)) }) - // this jstree node represents the substepdef - -// val jstreeNode = JsTreeNode(ReportBuilder.uniqueId(nextId), -// StringEscapeUtils.ESCAPE_HTML4.translate(exemplarNodeDetail.source.get), "method", Some(stepImplJsTreeNodes), State(true), -// Some(Map("data-stepimpl-method" -> s"${exemplarNodeDetail.method.get}", "data-stepimpl-passpc" -> s"${passPC}", -// "data-stepimpl-failpc" -> s"${failPC}", "data-stepimpl-notrunpc" -> s"${notRunPC}"))) // calculate the pass / fail / not run % @@ -396,11 +375,6 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr JsTreeNode(ReportBuilder.uniqueId(nextId), StringEscapeUtils.ESCAPE_HTML4.translate(substepDef), "SubstepDefinition", Some(substepJsTreeNodes), State(true), Some(Map("data-substep-def" -> "true", "data-substepdef-passpc" -> s"${passPC}", "data-substepdef-failpc" -> s"${failPC}", "data-substepdef-notrunpc" -> s"${notRunPC}"))) -// -// nextId = nextId + 1 -// -// jstreeNode - }) } @@ -419,9 +393,7 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr stepImplsbyUniqueMethood.toList.map(e => { // convert the method reference to an examplar instance of such a node detail - (allNodeDetails.filter(n => n.method == Some(e._1)), e._2) - - //(allNodeDetails.find(n => n.method == Some(e._1)).get, e._2) + (allNodeDetails.filter(n => n.method.contains(e._1)), e._2) }).sortBy(_._1.head.source.get) @@ -498,6 +470,28 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr f } + def writeResultSummary(resultsFile: RootNodeSummary) = { + + val file = createFile("results-summary.js") + + withWriter(file, writer => { + writer.append("var resultsSummary=") + + val map = Map("description" -> resultsFile.description, + "timestamp" -> resultsFile.timestamp, + "result" -> resultsFile.result, + "environment" -> resultsFile.environment, + "nonFatalTags" -> resultsFile.nonFatalTags, + "tags" -> resultsFile.tags) + + implicit val formats = Serialization.formats(NoTypeHints) + + writer.append(writePretty(map)).append(";") + + }) + + } + def readModel(srcDir : File) = { implicit val formats = Serialization.formats(NoTypeHints) @@ -514,6 +508,8 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr resultsFileOption match { case Some(resultsFile) => { + writeResultSummary(resultsFile) + resultsFile.features.flatMap(featureSummary => { val featureFileResultsDir = new File(srcDir, featureSummary.resultsDir) @@ -580,12 +576,20 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr val rootNode = JsTreeNode(srcData._1.id.toString, srcData._1.description, icon, childrenOption, state) - val writer = Files.newWriter(file, Charset.defaultCharset) - writer.append("var treeData = ") + withWriter(file, writer =>{ + writer.append("var treeData = ") - implicit val formats = Serialization.formats(NoTypeHints) + implicit val formats = Serialization.formats(NoTypeHints) + + writer.append(writePretty(rootNode)) + }) - writer.append(writePretty(rootNode)) + } + + + def withWriter(file: File, op: BufferedWriter => Any) = { + val writer = Files.newWriter(file, Charset.defaultCharset) + op(writer) writer.flush() writer.close() } @@ -634,21 +638,21 @@ class ReportBuilder extends IReportBuilder with ReportFrameTemplate with UsageTr def createDetailData(file : File, srcData : (RootNodeSummary, List[(FeatureSummary, List[NodeDetail])])) = { - val writer = Files.newWriter(file, Charset.defaultCharset) - writer.append("var detail = new Array();\n") - val featureSummaries = srcData._2.map( _._1) + withWriter(file, writer => { - val nodeDetailList = srcData._2.flatMap(_._2) + writer.append("var detail = new Array();\n") - writeRootNode(writer, srcData._1, featureSummaries) + val featureSummaries = srcData._2.map(_._1) - featureSummaries.foreach(f => { - writeFeatureNode(writer, f, nodeDetailList) - }) + val nodeDetailList = srcData._2.flatMap(_._2) - writer.flush() - writer.close() + writeRootNode(writer, srcData._1, featureSummaries) + + featureSummaries.foreach(f => { + writeFeatureNode(writer, f, nodeDetailList) + }) + }) } diff --git a/core/src/main/scala/org/substeps/report/ReportFrame.scala b/core/src/main/scala/org/substeps/report/ReportFrame.scala index 2abe6833..7963f18b 100644 --- a/core/src/main/scala/org/substeps/report/ReportFrame.scala +++ b/core/src/main/scala/org/substeps/report/ReportFrame.scala @@ -1,5 +1,8 @@ package org.substeps.report +import java.time.{Instant, LocalDateTime, ZoneId} +import java.time.format.DateTimeFormatter + /** * Created by ian on 18/08/16. */ @@ -13,9 +16,9 @@ trait ReportFrameTemplate { s""" |
| - |
${name}  ${counters.total}
+ |
${name}  ${counters.total}
| - |
+ |
| |
|
${counters.successPC} Success (${counters.passed})
@@ -31,7 +34,32 @@ trait ReportFrameTemplate { } - def buildReportFrame(reportTitle: String, dateTimeStr : String, stats : ExecutionStats, featureProgressBlock : String, scenarioProgressBlock : String, scenarioStepProgressBlock : String) = { + def buildReportFrame(rootNodeSummary: RootNodeSummary, stats : ExecutionStats) = { + + val featureProgressBlock = buildStatsBlock("Features", stats.featuresCounter) + val scenarioProgressBlock = buildStatsBlock("Scenarios", stats.scenarioCounters) + val scenarioStepProgressBlock = buildStatsBlock("Scenario steps", stats.stepCounters) + val stepImplBlock = buildStatsBlock("Step Impls", stats.stepImplCounters) + + val reportTitle = Option(rootNodeSummary.description).getOrElse("Substeps Test Report") + + val localDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(rootNodeSummary.timestamp), ZoneId.systemDefault()); + val dateTimeString = localDate.format(DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss")) + + + // TODO pull out some of the other things from the node summary - tags, nonfatal tags and environment + + val env = rootNodeSummary.environment + + + val nonFatalTagsOption = + rootNodeSummary.nonFatalTags.map(t => s"""""" ) + + val nonFatalTags = nonFatalTagsOption.getOrElse("") + + val tags = rootNodeSummary.tags.getOrElse("") + + s""" | @@ -46,6 +74,7 @@ trait ReportFrameTemplate { | | | + | | |