Java tutorial
/* * Copyright 2006-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.consol.citrus.report; import com.consol.citrus.*; import com.consol.citrus.exceptions.CitrusRuntimeException; import com.consol.citrus.util.FileUtils; import com.consol.citrus.util.PropertyUtils; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; import java.io.*; import java.text.DateFormat; import java.util.*; /** * Basic logging reporter generating a HTML report with detailed test results. * * @author Philipp Komninos, Christoph Deppisch */ public class HtmlReporter extends AbstractTestListener implements TestReporter { /** Logger */ private static Logger log = LoggerFactory.getLogger(HtmlReporter.class); /** Collect test results for test report */ private TestResults testResults = new TestResults(); /** Map holding additional information of test cases */ private Map<String, ResultDetail> details = new HashMap<String, ResultDetail>(); /** Static resource for the HTML test report template */ private static final Resource REPORT_TEMPLATE = new ClassPathResource("test-report.html", HtmlReporter.class); /** Test detail template */ private static final Resource TEST_DETAIL_TEMPLATE = new ClassPathResource("test-detail.html", HtmlReporter.class); /** Output directory */ private static final String OUTPUT_DIRECTORY = "test-output" + File.separator + "citrus-reports"; /** Resulting HTML test report file name */ private static final String REPORT_FILE_NAME = "citrus-test-results.html"; /** Format for creation and update date of TestCases */ private DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM); /** Default logo image resource */ @Autowired(required = false) @Qualifier("HtmlReporter.LOGO") private Resource logo = new ClassPathResource("citrus_logo.png", HtmlReporter.class); /** * @see com.consol.citrus.report.TestReporter#clearTestResults() */ public void clearTestResults() { testResults = new TestResults(); } /** * @see com.consol.citrus.report.TestReporter#generateTestResults() */ public void generateTestResults() { String report = ""; final StringBuilder reportDetails = new StringBuilder(); log.info("Generating HTML test report ..."); try { final String testDetails = FileUtils.readToString(TEST_DETAIL_TEMPLATE); final String unknown = "N/A"; testResults.doWithResults(new TestResults.ResultCallback() { @Override public void doWithResult(TestResult result) { ResultDetail detail = details.get(result.getTestName()); Properties detailProps = new Properties(); detailProps.put("test.style.class", result.getResult().toLowerCase()); detailProps.put("test.case.name", result.getTestName()); detailProps.put("test.author", !StringUtils.hasText(detail.getMetaInfo().getAuthor()) ? unknown : detail.getMetaInfo().getAuthor()); detailProps.put("test.status", detail.getMetaInfo().getStatus().toString()); detailProps.put("test.creation.date", detail.getMetaInfo().getCreationDate() == null ? unknown : dateFormat.format(detail.getMetaInfo().getCreationDate())); detailProps.put("test.updater", !StringUtils.hasText(detail.getMetaInfo().getLastUpdatedBy()) ? unknown : detail.getMetaInfo().getLastUpdatedBy()); detailProps.put("test.update.date", detail.getMetaInfo().getLastUpdatedOn() == null ? unknown : dateFormat.format(detail.getMetaInfo().getLastUpdatedOn())); detailProps.put("test.description", !StringUtils.hasText(detail.getDescription()) ? unknown : detail.getDescription()); detailProps.put("test.result", result.getResult()); reportDetails.append(PropertyUtils.replacePropertiesInString(testDetails, detailProps)); if (result.isFailed() && result.getCause() != null) { reportDetails.append(getStackTraceHtml(result.getCause())); } } }); Properties reportProps = new Properties(); reportProps.put("test.cnt", Integer.toString(testResults.getSize())); reportProps.put("skipped.test.cnt", Integer.toString(testResults.getSkipped())); reportProps.put("skipped.test.pct", testResults.getSkippedPercentage()); reportProps.put("failed.test.cnt", Integer.toString(testResults.getFailed())); reportProps.put("failed.test.pct", testResults.getFailedPercentage()); reportProps.put("success.test.cnt", Integer.toString(testResults.getSuccess())); reportProps.put("success.test.pct", testResults.getSuccessPercentage()); reportProps.put("test.results", reportDetails.toString()); reportProps.put("logo.data", getLogoImageData()); report = PropertyUtils.replacePropertiesInString(FileUtils.readToString(REPORT_TEMPLATE), reportProps); createReportFile(report); } catch (IOException e) { throw new CitrusRuntimeException("Failed to generate HTML test report", e); } } /** * Reads citrus logo png image and converts to base64 encoded string for inline HTML image display. * @return * @throws IOException */ private String getLogoImageData() { ByteArrayOutputStream os = new ByteArrayOutputStream(); BufferedInputStream reader = null; try { reader = new BufferedInputStream(logo.getInputStream()); byte[] contents = new byte[1024]; while (reader.read(contents) != -1) { os.write(contents); } } catch (IOException e) { log.warn("Failed to add logo image data to HTML report", e); } finally { if (reader != null) { try { reader.close(); } catch (IOException ex) { log.warn("Failed to close logo image resource for HTML report", ex); } } try { os.flush(); } catch (IOException ex) { log.warn("Failed to flush logo image stream for HTML report", ex); } } return Base64.encodeBase64String(os.toByteArray()); } /** * Gets the code section from test case XML which is responsible for the * error. * @param cause the error cause. * @return */ private String getCodeSnippetHtml(Throwable cause) { StringBuilder codeSnippet = new StringBuilder(); BufferedReader reader = null; try { if (cause instanceof CitrusRuntimeException) { CitrusRuntimeException ex = (CitrusRuntimeException) cause; if (!ex.getFailureStack().isEmpty()) { FailureStackElement stackElement = ex.getFailureStack().pop(); if (stackElement.getLineNumberStart() > 0) { reader = new BufferedReader(new FileReader( new ClassPathResource(stackElement.getTestFilePath() + ".xml").getFile())); codeSnippet.append("<div class=\"code-snippet\">"); codeSnippet .append("<h2 class=\"code-title\">" + stackElement.getTestFilePath() + ".xml</h2>"); String line; String codeStyle; int lineIndex = 1; int snippetOffset = 5; while ((line = reader.readLine()) != null) { if (lineIndex >= stackElement.getLineNumberStart() - snippetOffset && lineIndex < stackElement.getLineNumberStart() || lineIndex > stackElement.getLineNumberEnd() && lineIndex <= stackElement.getLineNumberEnd() + snippetOffset) { codeStyle = "code"; } else if (lineIndex >= stackElement.getLineNumberStart() && lineIndex <= stackElement.getLineNumberEnd()) { codeStyle = "code-failed"; } else { codeStyle = ""; } if (StringUtils.hasText(codeStyle)) { codeSnippet.append("<pre class=\"" + codeStyle + "\"><span class=\"line-number\">" + lineIndex + ":</span>" + line.replaceAll(">", ">").replaceAll("<", "<") + "</pre>"); } lineIndex++; } codeSnippet.append("</div>"); } } } } catch (IOException e) { log.error("Failed to construct HTML code snippet", e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { log.warn("Failed to close test file", e); } } } return codeSnippet.toString(); } /** * Construct HTML code snippet for stack trace information. * @param cause the causing error. * @return */ private String getStackTraceHtml(Throwable cause) { StringBuilder stackTraceBuilder = new StringBuilder(); stackTraceBuilder.append(cause.getClass().getName() + ": " + cause.getMessage() + "\n "); for (int i = 0; i < cause.getStackTrace().length; i++) { stackTraceBuilder.append("\n\t at "); stackTraceBuilder.append(cause.getStackTrace()[i]); } return "<tr><td colspan=\"2\">" + "<div class=\"error-detail\"><pre>" + stackTraceBuilder.toString() + "</pre>" + getCodeSnippetHtml(cause) + "</div></td></tr>"; } /** * Creates the HTML report file * @param content The String content of the report file */ private void createReportFile(String content) { Writer fileWriter = null; File targetDirectory = new File(OUTPUT_DIRECTORY); if (!targetDirectory.exists()) { boolean success = targetDirectory.mkdirs(); if (!success) { throw new CitrusRuntimeException("Unable to create folder structure for HTML report"); } } try { fileWriter = new FileWriter(OUTPUT_DIRECTORY + File.separator + REPORT_FILE_NAME); fileWriter.append(content); fileWriter.flush(); } catch (IOException e) { log.error("Failed to save HTML test report", e); } finally { if (fileWriter != null) { try { fileWriter.close(); } catch (IOException e) { log.error("Error closing HTML report file", e); } } } } @Override public void onTestSuccess(TestCase test) { details.put(test.getName(), ResultDetail.build(test)); testResults.addResult(TestResult.success(test.getName(), test.getParameters())); } @Override public void onTestFailure(TestCase test, Throwable cause) { details.put(test.getName(), ResultDetail.build(test)); testResults.addResult(TestResult.failed(test.getName(), cause, test.getParameters())); } @Override public void onTestSkipped(TestCase test) { details.put(test.getName(), ResultDetail.build(test)); testResults.addResult(TestResult.skipped(test.getName(), test.getParameters())); } /** * Sets the logo. * @param logo the logo to set */ public void setLogo(Resource logo) { this.logo = logo; } /** * Value object holding test specific data for HTML report generation. */ private static class ResultDetail { /** The meta info of the underlying test */ private TestCaseMetaInfo metaInfo; /** Description of the test */ private String description; /** * Builds a new result detail from test case. * @param test the test case. * @return the result detail. */ public static ResultDetail build(TestCase test) { ResultDetail detail = new ResultDetail(); detail.setDescription(test.getDescription()); detail.setMetaInfo(test.getMetaInfo()); return detail; } /** * Gets the test meta information. * @return the metaInfo */ public TestCaseMetaInfo getMetaInfo() { return metaInfo; } /** * Sets the test meta information. * @param metaInfo the metaInfo to set */ public void setMetaInfo(TestCaseMetaInfo metaInfo) { this.metaInfo = metaInfo; } /** * Gets the test description. * @return the description */ public String getDescription() { return description; } /** * Sets the test description. * @param description the description to set */ public void setDescription(String description) { this.description = description; } } }