View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.failsafe.util;
20  
21  import javax.xml.xpath.XPath;
22  import javax.xml.xpath.XPathFactory;
23  
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.io.InputStreamReader;
29  import java.io.Reader;
30  import java.util.Locale;
31  
32  import org.apache.maven.surefire.api.suite.RunResult;
33  import org.apache.maven.surefire.shared.io.IOUtils;
34  import org.w3c.dom.Node;
35  import org.xml.sax.InputSource;
36  
37  import static java.lang.Boolean.parseBoolean;
38  import static java.lang.Integer.parseInt;
39  import static java.lang.String.format;
40  import static java.nio.charset.StandardCharsets.UTF_8;
41  import static javax.xml.xpath.XPathConstants.NODE;
42  import static org.apache.maven.surefire.shared.lang3.StringEscapeUtils.escapeXml10;
43  import static org.apache.maven.surefire.shared.lang3.StringEscapeUtils.unescapeXml;
44  import static org.apache.maven.surefire.shared.utils.StringUtils.isBlank;
45  
46  /**
47   * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
48   * @since 2.20
49   */
50  public final class FailsafeSummaryXmlUtils {
51      private static final String FAILSAFE_SUMMARY_XML_SCHEMA_LOCATION =
52              "https://maven.apache.org/surefire/maven-surefire-plugin/xsd/failsafe-summary.xsd";
53  
54      private static final String MESSAGE_NIL_ELEMENT =
55              "<failureMessage xsi:nil=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"/>";
56  
57      private static final String MESSAGE_ELEMENT = "<failureMessage>%s</failureMessage>";
58  
59      private static final String FAILSAFE_SUMMARY_XML_TEMPLATE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
60              + "<failsafe-summary xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
61              + " xsi:noNamespaceSchemaLocation=\"" + FAILSAFE_SUMMARY_XML_SCHEMA_LOCATION + "\""
62              + " result=\"%s\" timeout=\"%s\">\n"
63              + "    <completed>%d</completed>\n"
64              + "    <errors>%d</errors>\n"
65              + "    <failures>%d</failures>\n"
66              + "    <skipped>%d</skipped>\n"
67              + "    %s\n"
68              + "</failsafe-summary>";
69  
70      private FailsafeSummaryXmlUtils() {
71          throw new IllegalStateException("No instantiable constructor.");
72      }
73  
74      public static RunResult toRunResult(File failsafeSummaryXml) throws Exception {
75          XPathFactory xpathFactory = XPathFactory.newInstance();
76          XPath xpath = xpathFactory.newXPath();
77  
78          try (Reader reader = new InputStreamReader(new FileInputStream(failsafeSummaryXml), UTF_8)) {
79              Node root = (Node) xpath.evaluate("/", new InputSource(reader), NODE);
80  
81              String completed = xpath.evaluate("/failsafe-summary/completed", root);
82              String errors = xpath.evaluate("/failsafe-summary/errors", root);
83              String failures = xpath.evaluate("/failsafe-summary/failures", root);
84              String skipped = xpath.evaluate("/failsafe-summary/skipped", root);
85              String failureMessage = xpath.evaluate("/failsafe-summary/failureMessage", root);
86              String timeout = xpath.evaluate("/failsafe-summary/@timeout", root);
87  
88              return new RunResult(
89                      parseInt(completed),
90                      parseInt(errors),
91                      parseInt(failures),
92                      parseInt(skipped),
93                      isBlank(failureMessage) ? null : unescapeXml(failureMessage),
94                      parseBoolean(timeout));
95          }
96      }
97  
98      public static void fromRunResultToFile(RunResult fromRunResult, File toFailsafeSummaryXml) throws IOException {
99          String failure = fromRunResult.getFailure();
100         String msg = isBlank(failure) ? MESSAGE_NIL_ELEMENT : format(MESSAGE_ELEMENT, escapeXml10(failure));
101         String xml = format(
102                 Locale.ROOT,
103                 FAILSAFE_SUMMARY_XML_TEMPLATE,
104                 fromRunResult.getFailsafeCode(),
105                 String.valueOf(fromRunResult.isTimeout()),
106                 fromRunResult.getCompletedCount(),
107                 fromRunResult.getErrors(),
108                 fromRunResult.getFailures(),
109                 fromRunResult.getSkipped(),
110                 msg);
111 
112         try (FileOutputStream os = new FileOutputStream(toFailsafeSummaryXml)) {
113             IOUtils.write(xml, os, UTF_8);
114         }
115     }
116 
117     public static void writeSummary(RunResult mergedSummary, File mergedSummaryFile, boolean inProgress)
118             throws Exception {
119         if (!mergedSummaryFile.getParentFile().isDirectory()) {
120             //noinspection ResultOfMethodCallIgnored
121             mergedSummaryFile.getParentFile().mkdirs();
122         }
123 
124         if (mergedSummaryFile.exists() && inProgress) {
125             RunResult runResult = toRunResult(mergedSummaryFile);
126             mergedSummary = mergedSummary.aggregate(runResult);
127         }
128 
129         fromRunResultToFile(mergedSummary, mergedSummaryFile);
130     }
131 }