/** * Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite * contributors * * This file is part of EvoSuite. * * EvoSuite is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3.0 of the License, or * (at your option) any later version. * * EvoSuite is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>. */ package org.evosuite.statistics.backend; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringEscapeUtils; import org.evosuite.Properties; import org.evosuite.ga.Chromosome; import org.evosuite.statistics.OutputVariable; import org.evosuite.statistics.RuntimeVariable; import org.evosuite.testcase.TestCase; import org.evosuite.testcase.TestChromosome; import org.evosuite.testsuite.TestSuiteChromosome; import org.evosuite.utils.HtmlAnalyzer; import org.evosuite.utils.FileIOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HTMLStatisticsBackend implements StatisticsBackend { protected static final Logger logger = LoggerFactory.getLogger(HTMLStatisticsBackend.class); protected static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss"; protected static final HtmlAnalyzer html_analyzer = new HtmlAnalyzer(); @Override public void writeData(Chromosome result, Map<String, OutputVariable<?>> data) { new File(getReportDir().getAbsolutePath() + "/img").mkdirs(); new File(getReportDir().getAbsolutePath() + "/html/files/").mkdirs(); new File(getReportDir().getAbsolutePath() + "/data/").mkdirs(); new File(getReportDir().getAbsolutePath() + "/files/").mkdirs(); copyFile("prettify.js"); copyFile("prettify.css"); copyFile("style.css"); copyFile("foldButton.js"); copyFile("foldButton.css"); copyFile("jquery.js"); copyFile("detected.png"); copyFile("not_detected.png"); copyFile("img01.jpg"); copyFile("img02.jpg"); copyFile("img03.jpg"); copyFile("img04.png"); copyFile("evosuite.png"); File file = new File(getReportDir(), "report-generation.html"); StringBuffer report = new StringBuffer(); if (file.exists()) { List<String> lines = FileIOUtils.readFile(file); for (String line : lines) { if (line.contains("<!-- EVOSUITE INSERTION POINT -->")) { break; } report.append(line); } } else { writeHTMLHeader(report, Properties.PROJECT_PREFIX); report.append("<div id=\"header\">\n<div id=\"logo\">"); /* if (!Properties.PROJECT_PREFIX.isEmpty()) { report.append("<h1 class=title>EvoSuite: " + Properties.PROJECT_PREFIX + "</h1>\n"); } */ report.append("\n</div><br></div>"); try { report.append("Run on " + java.net.InetAddress.getLocalHost().getHostName() + "\n"); } catch (Exception e) { } report.append("<div id=\"page\">\n"); report.append("<div id=\"page-bgtop\">\n"); report.append("<div id=\"page-bgbtm\">\n"); report.append("<div id=\"content\">\n"); report.append("<div id=\"post\">"); report.append("<h2 class=\"title\">Test generation runs:</h2>\n"); report.append("<div style=\"clear: both;\"> </div><div class=\"entry\">"); report.append("<table cellspacing=0>"); // border=0 cellspacing=0 cellpadding=3>"); report.append("<tr class=\"top bottom\">"); // report.append("<td>Run</td>"); report.append("<td>Date</td>"); report.append("<td>Time</td>"); report.append("<td>Coverage</td>"); report.append("<td>Class</td>"); // report.append("<td></td>"); report.append("</tr>\n"); } writeRunTable((TestSuiteChromosome)result, data, report); report.append("</div></div></div></div></div></div>"); writeHTMLFooter(report); FileIOUtils.writeFile(report.toString(), file); } public static void copyFile(URL src, File dest) { try { InputStream in; in = src.openStream(); OutputStream out = new FileOutputStream(dest); byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } public static void copyFile(String name) { URL systemResource = ClassLoader.getSystemResource("report/" + name); logger.debug("Copying from resource: " + systemResource); copyFile(systemResource, new File(getReportDir(), "files" + File.separator + name)); copyFile(systemResource, new File(getReportDir().getAbsolutePath() + File.separator+ "html" + File.separator + "files" + File.separator + name)); } /** * Return the folder of where reports should be generated. * If the folder does not exist, try to create it * * @return * @throws RuntimeException if folder does not exist, and we cannot create it */ public static File getReportDir() throws RuntimeException{ File dir = new File(Properties.REPORT_DIR); if(!dir.exists()){ boolean created = dir.mkdirs(); if(!created){ String msg = "Cannot create report dir: "+Properties.REPORT_DIR; logger.error(msg); throw new RuntimeException(msg); } } return dir; } /** * HTML header * * @param buffer * a {@link java.lang.StringBuffer} object. * @param title * a {@link java.lang.String} object. */ public static void writeHTMLHeader(StringBuffer buffer, String title) { buffer.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\" \"http://www.w3.org/TR/html4/frameset.dtd\">\n"); buffer.append("<html>\n"); buffer.append("<head>\n"); buffer.append("<title>\n"); buffer.append(title); buffer.append("\n</title>\n"); buffer.append("<link href=\"files/prettify.css\" type=\"text/css\" rel=\"stylesheet\" />\n"); buffer.append("<link href=\"files/style.css\" rel=\"stylesheet\" type=\"text/css\" media=\"screen\" />\n"); buffer.append("<script type=\"text/javascript\" src=\"files/prettify.js\"></script>\n"); buffer.append("<script type=\"text/javascript\" src=\"files/jquery.js\"></script>\n"); buffer.append("<script type=\"text/javascript\" src=\"files/foldButton.js\"></script>\n"); buffer.append("<script type=\"text/javascript\">\n"); buffer.append(" $(document).ready(function() {\n"); //buffer.append(" $('div.tests').foldButton({'closedText':'open TITLE' });\n"); //buffer.append(" $('div.source').foldButton({'closedText':'open TITLE' });\n"); //buffer.append(" $('div.statistics').foldButton({'closedText':'open TITLE' });\n"); buffer.append(" $('H2#tests').foldButton();\n"); buffer.append(" $('H2#source').foldButton();\n"); buffer.append(" $('H2#parameters').foldButton();\n"); buffer.append(" });"); buffer.append("</script>\n"); buffer.append("<link href=\"files/foldButton.css\" rel=\"stylesheet\" type=\"text/css\">\n"); buffer.append("</head>\n"); buffer.append("<body onload=\"prettyPrint()\">\n"); buffer.append("<div id=\"wrapper\">\n"); buffer.append("<img src=\"files/evosuite.png\" height=\"40\"/>\n"); } /** * HTML footer * * @param buffer * a {@link java.lang.StringBuffer} object. */ public static void writeHTMLFooter(StringBuffer buffer) { buffer.append("</div>\n"); buffer.append("</body>\n"); buffer.append("</html>\n"); } /** * The big table of results * * @param buffer * a {@link java.lang.StringBuffer} object. */ protected void writeRunTable(TestSuiteChromosome suite, Map<String, OutputVariable<?>> data, StringBuffer buffer) { SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW); buffer.append("<tr>"); // buffer.append("<td>" + entry.id + "</td>"); buffer.append("<td>"); buffer.append(sdf.format(new Date())); buffer.append("</td>"); buffer.append("<td>"); if (data.containsKey(RuntimeVariable.Total_Time.name())) { long duration = (Long)data.get(RuntimeVariable.Total_Time.name()).getValue() / 1000L; buffer.append(String.format("%d:%02d:%02d", duration / 3600, (duration % 3600) / 60, (duration % 60))); } else buffer.append("UNKNOWN"); buffer.append("</td>"); buffer.append("<td>"); Double coverage = (Double)getOutputVariableValue(data, RuntimeVariable.Coverage.name()); buffer.append((coverage != null) ? NumberFormat.getPercentInstance().format(coverage) : "UNKNOWN"); buffer.append("</td>"); buffer.append("<td><a href=\"html/"); String filename = writeRunPage(suite, data); buffer.append(filename); buffer.append("\">"); buffer.append(data.get("TARGET_CLASS").getValue()); buffer.append("</tr>\n"); buffer.append("<!-- EVOSUITE INSERTION POINT -->\n"); buffer.append("<tr class=\"top\"><td colspan=\"3\"> <td></tr>\n"); buffer.append("</table>"); } /** * Write a file for a particular run * * @param run * a {@link org.evosuite.utils.ReportGenerator.StatisticEntry} * object. * @return a {@link java.lang.String} object. */ @SuppressWarnings("deprecation") protected String writeRunPage(TestSuiteChromosome suite, Map<String, OutputVariable<?>> data) { StringBuffer sb = new StringBuffer(); String className = (String)data.get("TARGET_CLASS").getValue(); writeHTMLHeader(sb, className); sb.append("<br><br><h2 class=title>Summary</h2>\n"); sb.append("<ul><li>Target class: "); sb.append(getOutputVariableValue(data, "TARGET_CLASS")); sb.append(": "); sb.append(suite.getCoverage()); sb.append("</ul>\n"); writeResultTable(suite, sb, data); // writeMutationTable(sb); sb.append("<div id=\"page\">\n"); sb.append("<div id=\"page-bgtop\">\n"); sb.append("<div id=\"page-bgbtm\">\n"); sb.append("<div id=\"content\">\n"); sb.append("<div id=\"post\">\n"); // Resulting test case sb.append("<h2 class=title id=tests>Test suite</h2>\n"); sb.append("<div class=tests>\n"); int num = 0; for (TestChromosome testChromosome : suite.getTestChromosomes()) { TestCase test = testChromosome.getTestCase(); sb.append("<h3>Test case "); sb.append(++num); sb.append("</h3>\n"); /* * if(test.exceptionThrown != null) { sb.append("<p>Raises:"); * sb.append(test.exceptionThrown); sb.append("</p>"); } */ sb.append("<pre class=\"prettyprint\" style=\"border: 1px solid #888;padding: 2px\">\n"); int linecount = 1; String code = null; if (testChromosome.getLastExecutionResult() != null) { code = test.toCode(testChromosome.getLastExecutionResult().exposeExceptionMapping()); } else code = test.toCode(); for (String line : code.split("\n")) { sb.append(String.format("<span class=\"nocode\"><a name=\"%d\">%3d: </a></span>", linecount, linecount)); /* * if(test.exceptionsThrown != null && * test.exception_statement == test_line) * sb.append("<span style=\"background: #FF0000\">"); */ sb.append(StringEscapeUtils.escapeHtml4(line)); /* * if(test.exceptionThrown != null && * test.exception_statement == test_line) * sb.append("</span>"); */ linecount++; sb.append("\n"); } sb.append("</pre>\n"); } sb.append("</div>"); sb.append("<div id=\"post\">\n"); OutputVariable<?> ov_covered_lines = data.get(RuntimeVariable.Covered_Lines.name()); @SuppressWarnings("unchecked") Set<Integer> coveredLines = (ov_covered_lines != null) ? (Set<Integer>) ov_covered_lines.getValue() : new HashSet<Integer>(); // Source code try { Iterable<String> source = html_analyzer.getClassContent(className); sb.append("<h2 class=title id=source>Source Code</h2>\n"); sb.append("<div class=source>\n"); sb.append("<p>"); sb.append("<pre class=\"prettyprint\" style=\"border: 1px solid #888;padding: 2px\">"); int linecount = 1; for (String line : source) { sb.append(String.format("<span class=\"nocode\"><a name=\"%d\">%3d: </a></span>", linecount, linecount)); if (coveredLines.contains(linecount)) { sb.append("<span style=\"background-color: #ffffcc\">"); sb.append(StringEscapeUtils.escapeHtml4(line)); sb.append("</span>"); } else sb.append(StringEscapeUtils.escapeHtml4(line)); sb.append("\n"); linecount++; } sb.append("</pre>\n"); sb.append("</p>\n"); } catch (Exception e) { // Don't display source if there is an error } sb.append("</div>\n"); sb.append("<div id=\"post\">\n"); writeParameterTable(sb, data); sb.append("</div>\n"); sb.append("<p><br><a href=\"../report-generation.html\">Back to Overview</a></p>\n"); writeHTMLFooter(sb); String filename = "report-" + className + "-" + getNumber(className) + ".html"; File file = new File(getReportDir().getAbsolutePath() + "/html/" + filename); FileIOUtils.writeFile(sb.toString(), file); // return file.getAbsolutePath(); return filename; } protected int getNumber(final String className) { int num = 0; FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { // report-ncs.Triangle-0.html return name.startsWith("report-" + className) && (name.endsWith(".html")); } }; List<String> filenames = new ArrayList<String>(); File[] files = (new File(getReportDir().getAbsolutePath() + "/html")).listFiles(filter); if (files != null) { for (File f : files) filenames.add(f.getName()); while (filenames.contains("report-" + className + "-" + num + ".html")) num++; } return num; } protected Object getOutputVariableValue(Map<String, OutputVariable<?>> data, String key) { OutputVariable<?> ov = data.get(key); return (ov != null) ? ov.getValue() : null; } /** * Write some overall stats * * @param buffer * a {@link java.lang.StringBuffer} object. * @param entry * a {@link org.evosuite.utils.ReportGenerator.StatisticEntry} * object. */ protected void writeResultTable(TestSuiteChromosome suite, StringBuffer buffer, Map<String, OutputVariable<?>> data) { //buffer.append("<h2>Statistics</h2>\n"); buffer.append("<ul>\n"); buffer.append("<li>"); buffer.append(suite.getFitness()); buffer.append(" fitness evaluations, "); buffer.append(suite.getAge()); buffer.append(" generations, "); buffer.append(getOutputVariableValue(data, RuntimeVariable.Statements_Executed.name())); buffer.append(" statements, "); buffer.append(suite.size()); buffer.append(" tests.\n"); /* long duration_GA = (entry.end_time - entry.start_time) / 1000; long duration_MI = (entry.minimized_time - entry.end_time) / 1000; long duration_TO = (entry.minimized_time - entry.start_time) / 1000; buffer.append("<li>Time: " + String.format("%d:%02d:%02d", duration_TO / 3600, (duration_TO % 3600) / 60, (duration_TO % 60))); buffer.append("(Search: " + String.format("%d:%02d:%02d", duration_GA / 3600, (duration_GA % 3600) / 60, (duration_GA % 60)) + ", "); buffer.append("minimization: " + String.format("%d:%02d:%02d", duration_MI / 3600, (duration_MI % 3600) / 60, (duration_MI % 60)) + ")\n"); */ buffer.append("<li>Covered " + getOutputVariableValue(data, RuntimeVariable.Covered_Branches.name()) + "/" + getOutputVariableValue(data, RuntimeVariable.Total_Branches.name()) + " branches, "); buffer.append("<li>Covered "+ getOutputVariableValue(data, RuntimeVariable.Covered_Methods.name()) + "/" + getOutputVariableValue(data, RuntimeVariable.Total_Methods.name()) + " methods, "); buffer.append("<li>Covered "+ getOutputVariableValue(data, RuntimeVariable.Covered_Goals.name()) + "/" + getOutputVariableValue(data, RuntimeVariable.Total_Goals.name()) + " total goals\n"); if(data.containsKey(RuntimeVariable.MutationScore.name())) buffer.append("<li>Mutation score: " + NumberFormat.getPercentInstance().format((Double)data.get(RuntimeVariable.MutationScore.name()).getValue()) + "\n"); buffer.append("</ul>\n"); } /** * Write some overall stats * * @param buffer * a {@link java.lang.StringBuffer} object. * @param entry * a {@link org.evosuite.utils.ReportGenerator.StatisticEntry} * object. */ protected void writeParameterTable(StringBuffer buffer, Map<String, OutputVariable<?>> data) { buffer.append("<h2 id=parameters>EvoSuite Parameters</h2>\n"); buffer.append("<div class=statistics><ul>\n"); for (String key : data.keySet()) { buffer.append("<li>" + key + ": " + data.get(key).getValue() + "\n"); } buffer.append("</ul></div>\n"); } }