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.IOException;
27  import java.io.InputStreamReader;
28  import java.io.Reader;
29  import java.nio.file.Files;
30  import java.nio.file.StandardOpenOption;
31  import java.util.Locale;
32  
33  import org.apache.maven.surefire.api.suite.RunResult;
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              + "    <flakes>%d</flakes>\n"
68              + "    %s\n"
69              + "</failsafe-summary>";
70  
71      private FailsafeSummaryXmlUtils() {
72          throw new IllegalStateException("No instantiable constructor.");
73      }
74  
75      public static RunResult toRunResult(File failsafeSummaryXml) throws Exception {
76          XPathFactory xpathFactory = XPathFactory.newInstance();
77          XPath xpath = xpathFactory.newXPath();
78  
79          try (Reader reader = new InputStreamReader(new FileInputStream(failsafeSummaryXml), UTF_8)) {
80              Node root = (Node) xpath.evaluate("/", new InputSource(reader), NODE);
81  
82              String completed = xpath.evaluate("/failsafe-summary/completed", root);
83              String errors = xpath.evaluate("/failsafe-summary/errors", root);
84              String failures = xpath.evaluate("/failsafe-summary/failures", root);
85              String skipped = xpath.evaluate("/failsafe-summary/skipped", root);
86              String failureMessage = xpath.evaluate("/failsafe-summary/failureMessage", root);
87              String timeout = xpath.evaluate("/failsafe-summary/@timeout", root);
88              String flakes = xpath.evaluate("/failsafe-summary/flakes", root);
89  
90              return new RunResult(
91                      parseInt(completed),
92                      parseInt(errors),
93                      parseInt(failures),
94                      parseInt(skipped),
95                      // FIXME Backwards compatability: to be replaced with parseInt in a future release
96                      // synchronize with maven-surefire-plugin/src/site/resources/xsd/failsafe-summary.xsd
97                      isBlank(flakes) ? 0 : parseInt(flakes),
98                      isBlank(failureMessage) ? null : unescapeXml(failureMessage),
99                      parseBoolean(timeout));
100         }
101     }
102 
103     public static void fromRunResultToFile(RunResult fromRunResult, File toFailsafeSummaryXml) throws IOException {
104         String failure = fromRunResult.getFailure();
105         String msg = isBlank(failure) ? MESSAGE_NIL_ELEMENT : format(MESSAGE_ELEMENT, escapeXml10(failure));
106         String xml = format(
107                 Locale.ROOT,
108                 FAILSAFE_SUMMARY_XML_TEMPLATE,
109                 fromRunResult.getFailsafeCode(),
110                 fromRunResult.isTimeout(),
111                 fromRunResult.getCompletedCount(),
112                 fromRunResult.getErrors(),
113                 fromRunResult.getFailures(),
114                 fromRunResult.getSkipped(),
115                 fromRunResult.getFlakes(),
116                 msg);
117 
118         Files.write(
119                 toFailsafeSummaryXml.toPath(),
120                 xml.getBytes(UTF_8),
121                 StandardOpenOption.CREATE,
122                 StandardOpenOption.TRUNCATE_EXISTING,
123                 StandardOpenOption.WRITE);
124     }
125 
126     public static void writeSummary(RunResult mergedSummary, File mergedSummaryFile, boolean inProgress)
127             throws Exception {
128         if (!mergedSummaryFile.getParentFile().isDirectory()) {
129             //noinspection ResultOfMethodCallIgnored
130             mergedSummaryFile.getParentFile().mkdirs();
131         }
132 
133         if (mergedSummaryFile.exists() && inProgress) {
134             RunResult runResult = toRunResult(mergedSummaryFile);
135             mergedSummary = mergedSummary.aggregate(runResult);
136         }
137 
138         fromRunResultToFile(mergedSummary, mergedSummaryFile);
139     }
140 }