package net.masterthought.jenkins; import com.google.gson.Gson; import net.masterthought.jenkins.json.Element; import net.masterthought.jenkins.json.Feature; import net.masterthought.jenkins.json.Step; import net.masterthought.jenkins.json.Util; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class FeatureReportGenerator { private Map> jsonResultFiles; private File reportDirectory; private String buildNumber; private String buildProject; private List totalSteps; private String pluginUrlPath; private List allFeatures; private List allTags; private static final String charEncoding = "UTF-8"; public FeatureReportGenerator(List jsonResultFiles, File reportDirectory, String pluginUrlPath, String buildNumber, String buildProject, boolean skippedFails, boolean undefinedFails) throws IOException { ConfigurationOptions.setSkippedFailsBuild(skippedFails); ConfigurationOptions.setUndefinedFailsBuild(undefinedFails); this.jsonResultFiles = parseJsonResults(jsonResultFiles); this.allFeatures = listAllFeatures(); this.totalSteps = getAllStepStatuses(); this.reportDirectory = reportDirectory; this.buildNumber = buildNumber; this.buildProject = buildProject; this.pluginUrlPath = getPluginUrlPath(pluginUrlPath); this.allTags = findTagsInFeatures(); } public boolean getBuildStatus() { return !(getTotalFails() > 0); } private Map> parseJsonResults(List jsonResultFiles) throws IOException { Map> featureResults = new HashMap>(); for (String jsonFile : jsonResultFiles) { String fileContent = U2U(Util.readFileAsString(jsonFile)); Feature[] features = new Gson().fromJson(fileContent, Feature[].class); featureResults.put(jsonFile, Arrays.asList(features)); } return featureResults; } public void generateReports() throws Exception { generateFeatureReports(); generateFeatureOverview(); generateTagReports(); generateTagOverview(); } public void generateFeatureOverview() throws Exception { VelocityEngine ve = new VelocityEngine(); ve.init(getProperties()); Template featureOverview = ve.getTemplate("templates/featureOverview.vm"); VelocityContext context = new VelocityContext(); context.put("build_project", buildProject); context.put("build_number", buildNumber); context.put("features", allFeatures); context.put("total_features", getTotalFeatures()); context.put("total_scenarios", getTotalScenarios()); context.put("total_steps", getTotalSteps()); context.put("total_passes", getTotalPasses()); context.put("total_fails", getTotalFails()); context.put("total_skipped", getTotalSkipped()); context.put("total_pending", getTotalPending()); context.put("chart_data", XmlChartBuilder.donutChart(getTotalPasses(), getTotalFails(), getTotalSkipped(), getTotalPending())); context.put("time_stamp", timeStamp()); context.put("total_duration", getTotalDuration()); context.put("jenkins_base", pluginUrlPath); generateReport("feature-overview.html", featureOverview, context); } public void generateFeatureReports() throws Exception { Iterator it = jsonResultFiles.entrySet().iterator(); while (it.hasNext()) { Map.Entry pairs = (Map.Entry) it.next(); List featureList = (List) pairs.getValue(); for (Feature feature : featureList) { VelocityEngine ve = new VelocityEngine(); ve.init(getProperties()); Template featureResult = ve.getTemplate("templates/featureReport.vm"); VelocityContext context = new VelocityContext(); context.put("feature", feature); context.put("report_status_colour", getReportStatusColour(feature)); context.put("build_project", buildProject); context.put("build_number", buildNumber); context.put("scenarios", feature.getElements()); context.put("time_stamp", timeStamp()); context.put("jenkins_base", pluginUrlPath); generateReport(feature.getFileName(), featureResult, context); } } } public void generateTagReports() throws Exception { for (TagObject tagObject : allTags) { VelocityEngine ve = new VelocityEngine(); ve.init(getProperties()); Template featureResult = ve.getTemplate("templates/tagReport.vm"); VelocityContext context = new VelocityContext(); context.put("tag", tagObject); context.put("time_stamp", timeStamp()); context.put("jenkins_base", pluginUrlPath); context.put("build_project", buildProject); context.put("build_number", buildNumber); context.put("report_status_colour", getTagReportStatusColour(tagObject)); generateReport(tagObject.getTagName().replace("@", "").trim() + ".html", featureResult, context); } } public void generateTagOverview() throws Exception { VelocityEngine ve = new VelocityEngine(); ve.init(getProperties()); Template featureOverview = ve.getTemplate("templates/tagOverview.vm"); VelocityContext context = new VelocityContext(); context.put("build_project", buildProject); context.put("build_number", buildNumber); context.put("tags", allTags); context.put("total_tags", getTotalTags()); context.put("total_scenarios", getTotalTagScenarios()); context.put("total_steps", getTotalTagSteps()); context.put("total_passes", getTotalTagPasses()); context.put("total_fails", getTotalTagFails()); context.put("total_skipped", getTotalTagSkipped()); context.put("total_pending", getTotalTagPending()); context.put("chart_data", XmlChartBuilder.StackedColumnChart(allTags)); context.put("total_duration", getTotalTagDuration()); context.put("time_stamp", timeStamp()); context.put("jenkins_base", pluginUrlPath); generateReport("tag-overview.html", featureOverview, context); } private List findTagsInFeatures() { List tagMap = new ArrayList(); for (Feature feature : allFeatures) { List scenarioList = new ArrayList(); if (feature.hasTags()) { for (Element scenario : feature.getElements()) { scenarioList.add(new ScenarioTag(scenario, feature.getFileName())); tagMap = createOrAppendToTagMap(tagMap, feature.getTagList(), scenarioList); } } if (Util.hasScenarios(feature)) { for (Element scenario : feature.getElements()) { if (scenario.hasTags()) { scenarioList = addScenarioUnlessExists(scenarioList, new ScenarioTag(scenario, feature.getFileName())); } tagMap = createOrAppendToTagMap(tagMap, scenario.getTagList(), scenarioList); } } } return tagMap; } private List addScenarioUnlessExists(List scenarioList, ScenarioTag scenarioTag) { boolean exists = false; for (ScenarioTag scenario : scenarioList) { if (scenario.getParentFeatureUri().equalsIgnoreCase(scenarioTag.getParentFeatureUri()) && scenario.getScenario().getName().equalsIgnoreCase(scenarioTag.getScenario().getName())) { exists = true; break; } } if (!exists) { scenarioList.add(scenarioTag); } return scenarioList; } private List createOrAppendToTagMap(List tagMap, List tagList, List scenarioList) { for (String tag : tagList) { boolean exists = false; TagObject tagObj = null; for (TagObject tagObject : tagMap) { if (tagObject.getTagName().equalsIgnoreCase(tag)) { exists = true; tagObj = tagObject; break; } } if (exists) { List existingTagList = tagObj.getScenarios(); for (ScenarioTag scenarioTag : scenarioList) { existingTagList = addScenarioUnlessExists(existingTagList, scenarioTag); } tagMap.remove(tagObj); tagObj.setScenarios(existingTagList); tagMap.add(tagObj); } else { tagObj = new TagObject(tag, scenarioList); tagMap.add(tagObj); } } return tagMap; } private List listAllFeatures() { List allFeatures = new ArrayList(); Iterator it = jsonResultFiles.entrySet().iterator(); while (it.hasNext()) { Map.Entry pairs = (Map.Entry) it.next(); List featureList = (List) pairs.getValue(); allFeatures.addAll(featureList); } return allFeatures; } private static final Pattern p = Pattern.compile("\\\\u\\s*([0-9(A-F|a-f)]{4})", Pattern.MULTILINE); public static String U2U(String s) { String res = s; Matcher m = p.matcher(res); while (m.find()) { res = res.replaceAll("\\" + m.group(0), Character.toString((char) Integer.parseInt(m.group(1), 16))); } return res; } private String getPluginUrlPath(String path) { return path.isEmpty() ? "/" : path; } private int getTotalSteps() { return totalSteps.size(); } private int getTotalTagSteps() { int steps = 0; for (TagObject tag : allTags) { for (ScenarioTag scenarioTag : tag.getScenarios()) { Step[] stepList = scenarioTag.getScenario().getSteps(); if (stepList != null && stepList.length != 0) { steps += stepList.length; } } } return steps; } private String getTotalDuration() { Long duration = 0L; for (Feature feature : allFeatures) { if (Util.hasScenarios(feature)) { for (Element scenario : feature.getElements()) { if (Util.hasSteps(scenario)) { for (Step step : scenario.getSteps()) { duration = duration + step.getDuration(); } } } } } return Util.formatDuration(duration); } private String getTotalTagDuration() { Long duration = 0L; for (TagObject tagObject : allTags) { for (ScenarioTag scenario : tagObject.getScenarios()) { if (Util.hasSteps(scenario)) { for (Step step : scenario.getScenario().getSteps()) { duration = duration + step.getDuration(); } } } } return Util.formatDuration(duration); } private int getTotalPasses() { return Util.findStatusCount(totalSteps, Util.Status.PASSED); } private int getTotalFails() { return Util.findStatusCount(totalSteps, Util.Status.FAILED); } private int getTotalSkipped() { return Util.findStatusCount(totalSteps, Util.Status.SKIPPED); } private int getTotalPending() { return Util.findStatusCount(totalSteps, Util.Status.UNDEFINED); } private int getTotalTagPasses() { int passes = 0; for (TagObject tag : allTags) { passes += tag.getNumberOfPasses(); } return passes; } private int getTotalTagFails() { int failed = 0; for (TagObject tag : allTags) { failed += tag.getNumberOfFailures(); } return failed; } private int getTotalTagSkipped() { int skipped = 0; for (TagObject tag : allTags) { skipped += tag.getNumberOfSkipped(); } return skipped; } private int getTotalTagPending() { int pending = 0; for (TagObject tag : allTags) { pending += tag.getNumberOfPending(); } return pending; } private List getAllStepStatuses() { List steps = new ArrayList(); for (Feature feature : allFeatures) { if (Util.hasScenarios(feature)) { for (Element scenario : feature.getElements()) { if (Util.hasSteps(scenario)) { for (Step step : scenario.getSteps()) { steps.add(step.getStatus()); } } } } } return steps; } private int getTotalFeatures() { return allFeatures.size(); } private int getTotalTags() { return allTags.size(); } private int getTotalScenarios() { int scenarios = 0; for (Feature feature : allFeatures) { scenarios = scenarios + feature.getNumberOfScenarios(); } return scenarios; } private int getTotalTagScenarios() { int scenarios = 0; for (TagObject tag : allTags) { scenarios = scenarios + tag.getScenarios().size(); } return scenarios; } private void generateReport(String fileName, Template featureResult, VelocityContext context) throws Exception { Writer writer = new FileWriter(new File(reportDirectory, fileName)); featureResult.merge(context, writer); writer.flush(); writer.close(); } private Properties getProperties() { Properties props = new Properties(); props.setProperty("resource.loader", "class"); props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); return props; } private String getReportStatusColour(Feature feature) { return feature.getStatus() == Util.Status.PASSED ? "#C5D88A" : "#D88A8A"; } private String getTagReportStatusColour(TagObject tag) { return tag.getStatus() == Util.Status.PASSED ? "#C5D88A" : "#D88A8A"; } private String timeStamp() { return new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date()); } }