| /* |
| * Copyright 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.android.build.gradle.internal.test.report; |
| |
| import com.google.common.io.Closeables; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.math.BigDecimal; |
| import java.text.DecimalFormat; |
| import java.text.DecimalFormatSymbols; |
| import java.text.ParseException; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.xpath.XPath; |
| import javax.xml.xpath.XPathFactory; |
| import org.gradle.api.GradleException; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| |
| /** |
| * Custom test reporter based on Gradle's DefaultTestReport |
| */ |
| public class TestReport { |
| private final HtmlReportRenderer htmlRenderer = new HtmlReportRenderer(); |
| private final ReportType reportType; |
| private final File resultDir; |
| private final File reportDir; |
| |
| public TestReport(ReportType reportType, File resultDir, File reportDir) { |
| this.reportType = reportType; |
| this.resultDir = resultDir; |
| this.reportDir = reportDir; |
| htmlRenderer.requireResource(getClass().getResource("report.js")); |
| htmlRenderer.requireResource(getClass().getResource("base-style.css")); |
| htmlRenderer.requireResource(getClass().getResource("style.css")); |
| } |
| |
| public CompositeTestResults generateReport() { |
| AllTestResults model = loadModel(); |
| generateFiles(model); |
| return model; |
| } |
| |
| private AllTestResults loadModel() { |
| AllTestResults model = new AllTestResults(); |
| if (resultDir.exists()) { |
| File[] files = resultDir.listFiles(); |
| if (files != null) { |
| for (File file : files) { |
| if (file.getName().startsWith("TEST-") && file.getName().endsWith(".xml")) { |
| mergeFromFile(file, model); |
| } |
| } |
| } |
| } |
| return model; |
| } |
| |
| private void mergeFromFile(File file, AllTestResults model) { |
| InputStream inputStream = null; |
| try { |
| //noinspection IOResourceOpenedButNotSafelyClosed |
| inputStream = new FileInputStream(file); |
| Document document; |
| try { |
| document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( |
| new InputSource(inputStream)); |
| } finally { |
| inputStream.close(); |
| } |
| |
| String deviceName = null; |
| String projectName = null; |
| String flavorName = null; |
| NodeList propertiesList = document.getElementsByTagName("properties"); |
| for (int i = 0; i < propertiesList.getLength(); i++) { |
| Element properties = (Element) propertiesList.item(i); |
| XPath xPath = XPathFactory.newInstance().newXPath(); |
| deviceName = xPath.evaluate("property[@name='device']/@value",properties); |
| projectName = xPath.evaluate("property[@name='project']/@value",properties); |
| flavorName = xPath.evaluate("property[@name='flavor']/@value",properties); |
| } |
| |
| NodeList testCases = document.getElementsByTagName("testcase"); |
| for (int i = 0; i < testCases.getLength(); i++) { |
| Element testCase = (Element) testCases.item(i); |
| String className = testCase.getAttribute("classname"); |
| String testName = testCase.getAttribute("name"); |
| BigDecimal duration = parse(testCase.getAttribute("time")); |
| duration = duration.multiply(BigDecimal.valueOf(1000)); |
| NodeList failures = testCase.getElementsByTagName("failure"); |
| TestResult testResult = model.addTest(className, testName, duration.longValue(), |
| deviceName, projectName, flavorName); |
| for (int j = 0; j < failures.getLength(); j++) { |
| Element failure = (Element) failures.item(j); |
| testResult.addFailure( |
| failure.getAttribute("message"), failure.getTextContent(), |
| deviceName, projectName, flavorName); |
| } |
| } |
| NodeList ignoredTestCases = document.getElementsByTagName("ignored-testcase"); |
| for (int i = 0; i < ignoredTestCases.getLength(); i++) { |
| Element testCase = (Element) ignoredTestCases.item(i); |
| String className = testCase.getAttribute("classname"); |
| String testName = testCase.getAttribute("name"); |
| model.addTest(className, testName, 0, deviceName, projectName, flavorName).ignored(); |
| } |
| String suiteClassName = document.getDocumentElement().getAttribute("name"); |
| ClassTestResults suiteResults = model.addTestClass(suiteClassName); |
| NodeList stdOutElements = document.getElementsByTagName("system-out"); |
| for (int i = 0; i < stdOutElements.getLength(); i++) { |
| suiteResults.addStandardOutput(stdOutElements.item(i).getTextContent()); |
| } |
| NodeList stdErrElements = document.getElementsByTagName("system-err"); |
| for (int i = 0; i < stdErrElements.getLength(); i++) { |
| suiteResults.addStandardError(stdErrElements.item(i).getTextContent()); |
| } |
| } catch (Exception e) { |
| throw new GradleException(String.format("Could not load test results from '%s'.", file), e); |
| } finally { |
| try { |
| Closeables.close(inputStream, true /* swallowIOException */); |
| } catch (IOException e) { |
| // cannot happen |
| } |
| } |
| } |
| |
| private void generateFiles(AllTestResults model) { |
| try { |
| generatePage(model, new OverviewPageRenderer(reportType), new File(reportDir, "index.html")); |
| for (PackageTestResults packageResults : model.getPackages()) { |
| generatePage(packageResults, new PackagePageRenderer(reportType), |
| new File(reportDir, packageResults.getFilename(reportType) + ".html")); |
| for (ClassTestResults classResults : packageResults.getClasses()) { |
| generatePage(classResults, new ClassPageRenderer(reportType), |
| new File(reportDir, classResults.getFilename(reportType) + ".html")); |
| } |
| } |
| } catch (Exception e) { |
| throw new GradleException( |
| String.format("Could not generate test report to '%s'.", reportDir), e); |
| } |
| } |
| |
| private <T extends CompositeTestResults> void generatePage(T model, PageRenderer<T> renderer, |
| File outputFile) throws Exception { |
| htmlRenderer.renderer(renderer).writeTo(model, outputFile); |
| } |
| |
| /** |
| * Regardless of the default locale, comma ('.') is used as decimal separator |
| * |
| * @param source |
| * @return |
| * @throws java.text.ParseException |
| */ |
| public BigDecimal parse(String source) throws ParseException { |
| DecimalFormatSymbols symbols = new DecimalFormatSymbols(); |
| symbols.setDecimalSeparator('.'); |
| DecimalFormat format = new DecimalFormat("#.#", symbols); |
| format.setParseBigDecimal(true); |
| return (BigDecimal) format.parse(source); |
| } |
| } |