001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020 package org.apache.geronimo.mavenplugins.testsuite;
021
022 import java.io.File;
023 import java.io.FileInputStream;
024 import java.io.FileOutputStream;
025 import java.io.IOException;
026 import java.text.NumberFormat;
027 import java.util.ArrayList;
028 import java.util.Iterator;
029
030 import org.w3c.dom.Document;
031 import org.w3c.dom.Element;
032 import org.w3c.dom.Node;
033 import org.w3c.dom.NodeList;
034 import org.w3c.dom.Text;
035 import org.w3c.tidy.Tidy;
036 import org.xml.sax.ErrorHandler;
037 import org.xml.sax.InputSource;
038 import org.xml.sax.SAXException;
039 import org.xml.sax.SAXParseException;
040
041 import javax.xml.transform.Transformer;
042 import javax.xml.transform.TransformerFactory;
043 import javax.xml.transform.TransformerException;
044 import javax.xml.transform.TransformerConfigurationException;
045 import javax.xml.transform.dom.DOMSource;
046 import javax.xml.transform.stream.StreamResult;
047
048 import org.codehaus.mojo.pluginsupport.MojoSupport;
049 import org.codehaus.mojo.pluginsupport.ant.AntHelper;
050
051 import org.apache.maven.model.DistributionManagement;
052 import org.apache.maven.project.MavenProject;
053 import org.apache.maven.plugin.MojoExecutionException;
054 import org.apache.maven.plugin.MojoFailureException;
055 import org.apache.maven.settings.Settings;
056 import org.apache.maven.settings.Server;
057
058 import org.codehaus.plexus.util.FileUtils;
059
060 import org.apache.tools.ant.Project;
061 import org.apache.tools.ant.taskdefs.Property;
062 import org.apache.tools.ant.taskdefs.XmlProperty;
063 import org.apache.tools.ant.taskdefs.optional.ssh.Scp;
064
065 /**
066 * Download the ResultsSummary.html file from the site url.
067 * Update it with success rate (in percentage) of the results from each of the top level testsuites.
068 * Upload the file back again.
069 *
070 * @goal summarize
071 *
072 * @version $Rev: 524717 $ $Date: 2007-04-01 23:39:19 -0400 (Sun, 01 Apr 2007) $
073 */
074 public class ResultsSummaryMojo
075 extends MojoSupport
076 {
077 /**
078 * @component
079 */
080 protected AntHelper ant;
081
082 /**
083 * @parameter default-value="${project.build.directory}"
084 * @read-only
085 */
086 private File targetDirectory;
087
088 /**
089 * The maven project.
090 *
091 * @parameter expression="${project}"
092 * @required
093 * @readonly
094 */
095 protected MavenProject project = null;
096
097 /**
098 * The build settings.
099 *
100 * @parameter expression="${settings}" default-value="${settings}
101 * @required
102 * @readonly
103 */
104 protected Settings settings;
105
106 /**
107 * The username
108 *
109 * @parameter expression="${username}"
110 */
111 private String username;
112
113 /**
114 * The password
115 *
116 * @parameter expression="${password}"
117 */
118 private String password;
119
120 /**
121 * The passphrase
122 *
123 * @parameter expression="${passphrase}"
124 */
125 private String passphrase;
126
127 /**
128 * The keyfile
129 *
130 * @parameter expression="${keyfile}"
131 */
132 private String keyFile;
133
134 /**
135 * The passphrase
136 *
137 * @parameter expression="${buildNumber}"
138 */
139 private String buildNumber;
140
141 /**
142 * show results for only these many tests.
143 *
144 * @parameter expression="${numberShown}" default-value="8"
145 */
146 private int numberShown;
147
148 private NumberFormat numberFormat = NumberFormat.getInstance();
149
150 private static final int PCENT = 100;
151
152 private final String resultsFileName = "ResultsSummary.html";
153
154 private Server server = null;
155
156 private Scp scp;
157
158 protected MavenProject getProject()
159 {
160 return project;
161 }
162
163 protected void init() throws MojoExecutionException, MojoFailureException {
164 super.init();
165
166 ant.setProject(getProject());
167
168 scp = (Scp)ant.createTask("scp");
169
170 String siteId = project.getDistributionManagement().getSite().getId();
171 server = settings.getServer(siteId);
172
173 scp.setKeyfile(getKeyFile());
174
175 scp.setPassword(getPassword());
176 scp.setPassphrase(getPassphrase());
177 scp.setTrust(true);
178 }
179
180 private String getKeyFile() {
181 if (keyFile != null) {
182 return keyFile;
183 }
184 else if (server != null && server.getPrivateKey() != null) {
185 return server.getPrivateKey();
186 }
187
188 return "/home/" + getUsername() + "/.ssh/id_dsa";
189 }
190
191 private String getUsername() {
192 if (username != null) {
193 return username;
194 }
195 else if (server != null && server.getUsername() != null) {
196 return server.getUsername();
197 }
198
199 return System.getProperty("user.name");
200 }
201
202 private String getPassword() {
203 if (password != null) {
204 return password;
205 }
206 else if (server != null && server.getPassword() != null) {
207 return server.getPassword();
208 }
209
210 return " ";
211 }
212
213 private String getPassphrase() {
214 if (passphrase != null) {
215 return passphrase;
216 }
217 else if (server != null && server.getPassphrase() != null) {
218 return server.getPassphrase();
219 }
220
221 return " ";
222 }
223
224 /**
225 * called by execute from super
226 */
227 protected void doExecute() throws Exception {
228 if (buildNumber == null) {
229 log.warn("No build number specified; returning");
230 return;
231 }
232
233 File currentSiteDirectory = new File(targetDirectory, "/site");
234 if (!currentSiteDirectory.exists()) {
235 log.warn("No site directory here; returning");
236 return;
237 }
238
239 // Download ResultsSummary.html and parse it.
240 File resultsFile = null;
241 try {
242 downloadHTML();
243 resultsFile = new File(targetDirectory, resultsFileName);
244 }
245 catch (Exception e) {
246 log.warn("Download failed. " + e.getMessage());
247 }
248
249 Tidy tidy = new Tidy();
250 tidy.setQuiet(true);
251 tidy.setShowWarnings(false);
252
253 if ( resultsFile == null || !resultsFile.exists() ) {
254 log.info( resultsFileName + " could not be downloaded. Using the template to create anew");
255 resultsFile = new File(project.getBasedir(), "src/main/resources/" + resultsFileName);
256 }
257
258 FileInputStream is = new FileInputStream( resultsFile );
259 Document document = tidy.parseDOM(is, null);
260 is.close();
261
262 File reportsDir = new File(targetDirectory, "surefire-reports");
263 if ( !reportsDir.exists() ) {
264 log.warn("No surefire-reports directory here");
265 return;
266 }
267
268 ArrayList files = (ArrayList) FileUtils.getFiles(reportsDir, "TEST-*.xml", null, true);
269 if ( files.size() > 0 ) {
270 document = insertNewColumn(document);
271 if ( document == null ) {
272 throw new MojoFailureException("Main table cannot be found in the " + resultsFileName + ". The file may be corrupted");
273 }
274 }
275
276 for ( Iterator itr=files.iterator(); itr.hasNext(); ) {
277 File file = (File) itr.next();
278 log.debug("working on " + file.getAbsolutePath() );
279 document = processFile(document, file);
280 }
281
282 // Use a Transformer for output
283 TransformerFactory tFactory = TransformerFactory.newInstance();
284 Transformer transformer = tFactory.newTransformer();
285
286 // write the document back into a temporary file.
287 File tempFile = new File(targetDirectory, "ResultsSummary-2.html");
288 FileOutputStream os = new FileOutputStream( tempFile );
289 DOMSource source = new DOMSource(document);
290 StreamResult result = new StreamResult(os);
291 transformer.transform(source, result);
292
293 os.flush();
294 os.close();
295
296 // tidy the document and create/replace ResultsSummary.html in the target directory
297 resultsFile = new File(targetDirectory, resultsFileName);
298 is = new FileInputStream( tempFile );
299 os = new FileOutputStream( resultsFile );
300 tidy.parse(is, os);
301 is.close();
302 os.close();
303
304 // delete the temp file.
305 tempFile.delete();
306
307 try {
308 uploadHTML(resultsFile);
309 }
310 catch ( Exception e ) {
311 log.warn("Upload failed. " + e.getMessage());
312 }
313 }
314
315
316 private String getRemoteUri() {
317 String siteUri = project.getDistributionManagement().getSite().getUrl();
318
319 // chop off the protocol
320 int index = siteUri.indexOf("://");
321 siteUri = siteUri.substring(index + 3);
322 log.debug("siteUri uri is " + siteUri);
323
324 // chop off the buildNumber directory at the end. This is used to deploy site files.
325 index = siteUri.lastIndexOf("/");
326 siteUri = siteUri.substring(0, index);
327 log.debug("siteUri uri is " + siteUri);
328
329 // insert : between the host and path
330 index = siteUri.indexOf("/");
331 String remoteUri = siteUri.substring(0, index) + ":" + siteUri.substring(index);
332 log.debug("siteUri uri is " + remoteUri);
333
334
335 // construct the uri using username
336 remoteUri = getUsername() + ":" + getPassword() + "@" + remoteUri;
337 log.info("Remote uri is " + remoteUri);
338
339 return remoteUri;
340
341 }
342
343
344 /**
345 * Download the html from the remote site where it is has been deployed.
346 */
347 private void downloadHTML()
348 {
349 String remoteUri = getRemoteUri() + "/" + resultsFileName;
350
351 scp.setFile(remoteUri);
352 scp.setTodir(targetDirectory.getAbsolutePath());
353
354 scp.execute();
355 }
356
357
358 /**
359 * Upload the html to the remote site where it will be deployed.
360 */
361 private void uploadHTML(File resultsFile)
362 {
363 String remoteUri = getRemoteUri();
364
365 scp.setFile( resultsFile.getAbsolutePath() );
366 scp.setTodir(remoteUri);
367
368 scp.execute();
369 }
370
371
372 /**
373 * Append a new column for the latest build. Put the build number in the column header
374 */
375 private Document insertNewColumn(Document document)
376 {
377 Element table = getElementById(document.getDocumentElement(), "table", "mainTable");
378 if ( table == null )
379 {
380 log.info("table is null");
381 return null;
382 }
383
384 Element thead = getElementById(table, "thead", "mainTableHead");
385 Element tr = (Element) thead.getFirstChild();
386
387 Element td= document.createElement("TD");
388 td.setAttribute("class", "servers");
389
390 Element anchor = document.createElement("a");
391 anchor.setAttribute("href", "./" + buildNumber + "/surefire-report.html");
392 Text text = document.createTextNode(buildNumber);
393 anchor.appendChild(text);
394
395 td.appendChild(anchor);
396 tr.appendChild(td);
397
398 // increment the cols attribute for the table
399 int cols = tr.getChildNodes().getLength();
400
401 // check for number of columns to be shown.
402 // Don't take the suite names column into count.
403 if ( cols > (numberShown + 1) )
404 {
405 cols = cleanup(table);
406 }
407
408 table.setAttribute("cols", String.valueOf(cols) );
409
410
411 return document;
412 }
413
414 private Document processFile(Document document, File file)
415 {
416 String pcent = getResultsFromFile(file);
417
418 // strip off TEST- and .xml from the filename to get the suitename
419 String fileName = FileUtils.basename(file.getName());
420 fileName = fileName.substring(fileName.indexOf("-") + 1);
421 fileName = fileName.substring(0, fileName.length()-1);
422 document = insertColumn(document, pcent, fileName);
423
424 return document;
425 }
426
427 /**
428 * Load the surefire-report xml file as an ANT xml property and get the values of the results
429 * compute percentage
430 */
431 private String getResultsFromFile(File xmlFile)
432 {
433 String prefix = String.valueOf(System.currentTimeMillis());
434 loadXMLProperty(xmlFile, prefix);
435
436 String tests = ant.getAnt().getProperty(prefix + ".testsuite.tests");
437 String errors = ant.getAnt().getProperty(prefix + ".testsuite.errors");
438 String failures = ant.getAnt().getProperty(prefix + ".testsuite.failures");
439 String skipped = ant.getAnt().getProperty(prefix + ".testsuite.skipped");
440
441 log.debug("tests: " + tests + "; errors:" + errors + "; failures:" + failures + "; skipped:" + skipped);
442
443 int testsNum = Integer.parseInt(tests);
444 int errorsNum = Integer.parseInt(errors);
445 int failuresNum = Integer.parseInt(failures);
446 int skippedNum = Integer.parseInt(skipped);
447
448 String pcent = computePercentage(testsNum, errorsNum, failuresNum, skippedNum);
449 return pcent;
450 }
451
452 /**
453 * http://ant.apache.org/manual/CoreTasks/xmlproperty.html
454 */
455 private void loadXMLProperty(File src, String prefix)
456 {
457 XmlProperty xmlProperty = (XmlProperty)ant.createTask("xmlproperty");
458 xmlProperty.setFile(src);
459 if ( prefix != null )
460 {
461 xmlProperty.setPrefix(prefix);
462 }
463 xmlProperty.setCollapseAttributes(true);
464 xmlProperty.execute();
465 log.debug("Loaded xml file as ant property with prefix " + prefix);
466 }
467
468 /**
469 * compute percentage
470 */
471 public String computePercentage( int tests, int errors, int failures, int skipped )
472 {
473 float percentage;
474 if ( tests == 0 )
475 {
476 percentage = 0;
477 }
478 else
479 {
480 percentage = ( (float) ( tests - errors - failures - skipped ) / (float) tests ) * PCENT;
481 }
482
483 return numberFormat.format( percentage );
484 }
485
486 /**
487 * Insert the rest of the column. If there is no matching row for the suite name, create a new row.
488 */
489 private Document insertColumn(Document document, String pcent, String suiteName)
490 {
491 log.debug("inserting column");
492
493 Element table = getElementById(document.getDocumentElement(), "table", "mainTable");
494 int cols = Integer.parseInt( table.getAttribute("cols") );
495
496 Element tr = getElementById(table, "tr", suiteName);
497
498 if ( tr != null )
499 {
500 Element td = document.createElement("TD");
501 td.setAttribute("class", "cell");
502
503 Element anchor = document.createElement("a");
504 anchor.setAttribute("href", "./" + buildNumber + "/" + suiteName + "/surefire-report.html");
505 Text text = document.createTextNode(pcent + "%");
506 anchor.appendChild(text);
507
508 td.appendChild(anchor);
509 tr.appendChild(td);
510 }
511 else
512 {
513 log.debug("Creating a new row for a new suite");
514 tr = document.createElement("TR");
515 tr.setAttribute("id", suiteName);
516
517 Element td = document.createElement("TD");
518 td.setAttribute("class", "suite");
519 Text text = document.createTextNode(suiteName);
520 td.appendChild(text);
521 tr.appendChild(td);
522
523 // creating empty cells in the cols for the previous builds.
524 for ( int i=1; i<cols; i++ )
525 {
526 td = document.createElement("TD");
527 td.setAttribute("class", "cell");
528 tr.appendChild(td);
529 }
530
531 Element anchor = document.createElement("a");
532 anchor.setAttribute("href", "./" + buildNumber + "/" + suiteName + "/surefire-report.html");
533 text = document.createTextNode(pcent + "%");
534 anchor.appendChild(text);
535 td.appendChild(anchor);
536
537 table.appendChild(tr);
538 }
539
540 log.debug("inserted column");
541
542 return document;
543 }
544
545 /**
546 * Get a child element identified by an ID
547 */
548 private Element getElementById(Element element, String tagName, String id)
549 {
550 log.debug("Searching for tag " + tagName + " with id=" + id);
551
552 Element foundElement = null;
553
554 NodeList nodeList = element.getElementsByTagName(tagName);
555
556 for ( int i=0; i<nodeList.getLength(); i++ )
557 {
558 foundElement = (Element) nodeList.item(i);
559 log.debug("Element is " + foundElement.getTagName() + " " + foundElement.getAttribute("id") );
560 if ( id.trim().equals(foundElement.getAttribute("id").trim()) )
561 {
562 break;
563 }
564 else
565 {
566 foundElement = null;
567 }
568 }
569
570 return foundElement;
571 }
572
573 /**
574 * Removes the oldest test column(s) from the table based on the value set in 'numberShown' variable
575 */
576 private int cleanup(Element table)
577 {
578 log.info("Removing oldest column");
579
580 NodeList nodeList = table.getElementsByTagName("tr");
581
582 int new_cols = 0;
583 for ( int i=0; i<nodeList.getLength(); i++ )
584 {
585 Element tr = (Element) nodeList.item(i);
586 Element suiteColumn = (Element) tr.getFirstChild();
587 Element removeMe = (Element) suiteColumn.getNextSibling();
588 tr.removeChild(removeMe);
589
590 // get the count from just the header row since only the header has been added yet
591 if ( i==0 )
592 {
593 new_cols = tr.getChildNodes().getLength();
594 }
595 }
596
597 if ( new_cols > (numberShown + 1) )
598 {
599 new_cols = cleanup(table);
600 }
601
602 log.debug( String.valueOf("Returning cols: " + new_cols) );
603
604 return new_cols;
605 }
606 }