From bb7a486e5876d9bd3c4bf8a18f45a30baf5f8fe5 Mon Sep 17 00:00:00 2001 From: Jesse Hallett Date: Tue, 7 Jan 2014 14:10:05 -0800 Subject: Displays table of errors and warnings by rule - Splits "failures" column into "errors" and "warnings" - Links go to detailed report instead of to source page --- .../src/main/java/com/galois/fiveui/Reporter.java | 216 +++++++++++++++------ 1 file changed, 155 insertions(+), 61 deletions(-) diff --git a/src/batchtools/headless/src/main/java/com/galois/fiveui/Reporter.java b/src/batchtools/headless/src/main/java/com/galois/fiveui/Reporter.java index 344e46d..486e5c3 100644 --- a/src/batchtools/headless/src/main/java/com/galois/fiveui/Reporter.java +++ b/src/batchtools/headless/src/main/java/com/galois/fiveui/Reporter.java @@ -24,6 +24,8 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -32,6 +34,7 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.google.common.base.Function; import com.google.common.collect.Lists; import com.googlecode.jatl.Html; @@ -50,16 +53,19 @@ public class Reporter { /** see sortBy* and computeSummaryStats methods for map semantics */ private final Map> _byURLMap; private final Map> _byRuleMap; - private final Map _passFailMap; + private final Map _statsByUrl; + private final Map _statsByRule; private Map _ruleNameToDesc; private static String GLOBAL_CSS = "h1 { font-size: 150%; }" + "h2 { font-size: 110%; }" + "li { margin: 5 0 0 0; }" - + "table { border: 1px solid grey; cellpadding: 5%; width: 200px; }\n" - + "td.pass-number{ text-align: right;color: green; }\n" - + "td.fail-number{ text-align: right;color: red; }\n" + + "table { border: 1px solid grey; cellpadding: 5%; width: 100%; table-layout: fixed; }\n" + + "th.numeric { width: 4em; }" + + "td.pass-number{ text-align: right; color: green; }\n" + + "td.error-number{ text-align: right; color: red; }\n" + + "td.warning-number{ text-align: right; color: darkorange; }\n" + "td.text{ text-align: left; }\n" + "th { font-weight: bold; }\n" - + "td, th { border: 1px solid grey; }\n" + + "td, th { border: 1px solid grey; overflow: hidden; text-overflow: ellipsis; }\n" + ".hlRow { background: #EEEEEE; }\n" + ".regRow { background: #FFFFFF; }\n"; @@ -73,78 +79,105 @@ public class Reporter { public Reporter(List results) { this._byURLMap = sortByURL(results); this._byRuleMap = sortByRule(results); - this._passFailMap = computeSummaryStats(results); + this._statsByUrl = computeSummaryStatsByUrl(results); + this._statsByRule = computeSummaryStatsByRule(results); this._ruleNameToDesc = extractRuleDesc(results); } /** * Build the HTML markup for a summary page based on the precomputed map - * this._passFailMap. + * this._statsByUrl. * * @return String containing human-readable HTML representing a summary page */ public String getSummary() { StringWriter summaryPage = new StringWriter(); - final Map scopedMap = this._passFailMap; + final Map scopedMapByUrl = this._statsByUrl; + final Map scopedMapByRule = this._statsByRule; Html page = new Html(summaryPage); page = makeHead(page, "Summary of Results"); page = new Html(page) { { h1().text("Headless Run Summary").end(); p(); - ul(); - li().a().href("byURL.html").text("Results organized by URL") + ul(); + li().a().href("byURL.html") + .text("Results organized by URL") .end().end(); - li().a().href("byRule.html").text("Results organized by Rule") + li().a().href("byRule.html") + .text("Results organized by Rule") .end().end(); - end(); + end(); end(); p(); - div().id("stats"); - makeSummaryStats(scopedMap); + div().id("stats"); + makeSummaryStatsByUrl(scopedMapByUrl); + end(); end(); + p(); + div().id("stats"); + makeSummaryStatsByRule(scopedMapByRule); + end(); end(); endAll(); done(); } + Html makeSummaryStatsByUrl(Map stats) { + return makeSummaryStats("Unique URLs", "byURL.html", stats); + } + + Html makeSummaryStatsByRule(Map stats) { + return makeSummaryStats("Rules", "byRule.html", stats); + } + /** * Report statistics on the headless run. Note, "pass" means the URL * passed all tests in the ruleset, but "fail" can be reported for * the same test on multiple offenders in the page. */ - Html makeSummaryStats(Map passFailMap) { - int uniqueURLs = passFailMap.size(); - int[] passFailList; + Html makeSummaryStats(String title, String detailUrl, Map stats) { + int keycount = stats.size(); + int[] statsList; p(); - h3().text("Unique URLs: "); - span().classAttr("number").text(String.valueOf(uniqueURLs)) + h3().text(title +": "); + span().classAttr("number") + .text(String.valueOf(keycount)) .end(); end().end(); p(); - table().id("stats-table"); - tr(); - th().text("URL").end(); - th().text("Pass").end(); - th().text("Fail").end(); - end(); + table().id("stats-table"); + thead(); + tr(); + th() .text("URL") .end(); + th().classAttr("numeric").text("Pass") .end(); + th().classAttr("numeric").text("Error") .end(); + th().classAttr("numeric").text("Warning").end(); + end(); + end(); + tbody(); int i = 0; // index for **alternate row highlighting** List sortedKeys = Lists.newArrayList(); - sortedKeys.addAll(passFailMap.keySet()); + sortedKeys.addAll(stats.keySet()); Collections.sort(sortedKeys); for (String key : sortedKeys) { - passFailList = passFailMap.get(key); + statsList = stats.get(key); tr().classAttr(i % 2 == 0 ? "hlRow" : "regRow"); - td().classAttr("text").a().href(key).text(key).end().end(); - td().classAttr("pass-number") - .text(String.valueOf(passFailList[0])).end(); - td().classAttr("fail-number") - .text(String.valueOf(passFailList[1])).end(); + td().classAttr("text") + .a().href(detailUrl+"#"+encode(key)).text(key).end() + .end(); + td().classAttr("pass-number") + .text(String.valueOf(statsList[0])).end(); + td().classAttr("error-number") + .text(String.valueOf(statsList[1])).end(); + td().classAttr("warning-number") + .text(String.valueOf(statsList[2])).end(); end(); i++; } + end(); // end end(); // end return end(); // end

} @@ -171,8 +204,9 @@ public class Reporter { sortedKeys.addAll(scopedMap.keySet()); Collections.sort(sortedKeys); for (String url : sortedKeys) { - li().h2().a().href(url).text(url).end().end(); - ul(); + li().id(formatId(url)) + .h2().a().href(url).text(url).end().end(); + ul(); int i = 0; for (Result r : scopedMap.get(url)) { String cAttr = i % 2 == 0 ? "hlRow" : "regRow"; @@ -215,7 +249,7 @@ public class Reporter { sortedKeys.addAll(scopedMap.keySet()); Collections.sort(sortedKeys); for (String rule : sortedKeys) { - li(); + li().id(formatId(rule)); b().text(rule).end() .text(": " + scopedRuleNameToDesc.get(rule)); ul(); @@ -377,44 +411,104 @@ public class Reporter { /** * Compute summary statistics from the results list. This includes number of * passes and fails for each URL checked. + * + * @param results + * a list of results + * @return a map representing the results by URL. + */ + private Map computeSummaryStatsByUrl(List results) { + return computeSummaryStats(results, new Function() { + public String apply(Result result) { + return result.getURL(); + } + }); + } + + /** + * Compute summary statistics from the results list. This includes number of + * passes and fails for each rule checked. * * @param results * a list of results - * @return a map representing the results sorted by rule name. + * @return a map representing the results by URL. */ - private Map computeSummaryStats(List results) { - /** passFailMap semantics: Map */ - Map passFailMap = new HashMap(); - String url; - int pass, fail; - int[] passFailList; - Pattern numberPassedPattern = Pattern.compile("passed ([0-9]+) tests"); - Matcher matcher; + private Map computeSummaryStatsByRule(List results) { + return computeSummaryStats(results, new Function() { + public String apply(Result result) { + return result.getRuleName(); + } + }); + } + + /** + * Compute summary statistics from the results list. + * + * @param results + * a list of results + * @param getKey + * a function that maps a result to a map key + * @return a map representing the results counted according to an + * arbitrary result property + */ + private Map computeSummaryStats( + List results, + Function getKey) { + /** statsMap semantics: Map */ + Map statsMap = new HashMap(); + String key; + int pass, error, warn; + int[] statsList; for (Result result : results) { - pass = fail = 0; - url = result.getURL(); + pass = error = warn = 0; + key = getKey.apply(result); if (result.getType() == ResType.Pass) { - // now we have to parse out how many tests passed - matcher = numberPassedPattern.matcher(result.getMsg()); - if (matcher.find()) - pass = Integer.parseInt(matcher.group(1)); // throws - // exception if - // parse fails - } else if (result.getType() == ResType.Error) { + pass = numberPassed(result); + } + else if (result.getType() == ResType.Error) { // each error result corresponds to one test - fail = 1; + error = 1; + } + else if (result.getType() == ResType.Warning) { + warn = 1; } - if (passFailMap.containsKey(url)) { - passFailList = passFailMap.get(url); + if (statsMap.containsKey(key)) { + statsList = statsMap.get(key); } else { - passFailList = new int[] { 0, 0 }; - passFailMap.put(url, passFailList); + statsList = new int[] { 0, 0, 0 }; + statsMap.put(key, statsList); } - passFailList[0] += pass; - passFailList[1] += fail; + statsList[0] += pass; + statsList[1] += error; + statsList[2] += warn; + } + return statsMap; + } + + private Pattern numberPassedPattern = Pattern.compile("passed ([0-9]+) tests"); + + private int numberPassed(Result result) { + int pass = 0; + Matcher m = numberPassedPattern.matcher(result.getMsg()); + if (m.find()) { + // parseInt throws exception if parse fails + pass = Integer.parseInt(m.group(1)); + } + return pass; + } + + private String formatId(String label) { + // java.net.URLEncoder.encode encodes spaces as "+" instead of "%20". + return label.replaceAll("\\s", "+"); + } + + private String encode(String in) { + try { + return URLEncoder.encode(in, "utf-8"); + } + catch(UnsupportedEncodingException e) { + return ""; } - return passFailMap; } } -- cgit v1.2.3