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 import org.apache.tools.ant.taskdefs.optional.ssh.SSHExec;
065
066 /**
067 * Download the ResultsSummary.html file from the site url.
068 * Update it with success rate (in percentage) of the results from each of the top level testsuites.
069 * Upload the file back again.
070 *
071 * @goal summarize
072 *
073 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
074 */
075 public class ResultsSummaryMojo
076 extends MojoSupport {
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 private SSHExec ssh;
158
159 protected MavenProject getProject()
160 {
161 return project;
162 }
163
164 protected void init() throws MojoExecutionException, MojoFailureException {
165 super.init();
166
167 ant.setProject(getProject());
168
169 String siteId = project.getDistributionManagement().getSite().getId();
170 server = settings.getServer(siteId);
171
172 scp = (Scp)ant.createTask("scp");
173 scp.setKeyfile(getKeyFile());
174 scp.setPassword(getPassword());
175 scp.setPassphrase(getPassphrase());
176 scp.setTrust(true);
177
178 ssh = (SSHExec)ant.createTask("sshexec");
179 ssh.setKeyfile(getKeyFile());
180 ssh.setPassword(getPassword());
181 ssh.setPassphrase(getPassphrase());
182 ssh.setTrust(true);
183
184 }
185
186 private String getKeyFile() {
187 if ( keyFile != null ) {
188 return keyFile;
189 }
190 else if ( server != null && server.getPrivateKey() != null ) {
191 return server.getPrivateKey();
192 }
193
194 return "/home/" + getUsername() + "/.ssh/id_dsa";
195 }
196
197 private String getUsername() {
198 if ( username != null ) {
199 return username;
200 }
201 else if ( server != null && server.getUsername() != null ) {
202 return server.getUsername();
203 }
204
205 return System.getProperty("user.name");
206 }
207
208 private String getPassword() {
209 if ( password != null ) {
210 return password;
211 }
212 else if ( server != null && server.getPassword() != null ) {
213 return server.getPassword();
214 }
215
216 return " ";
217 }
218
219 private String getPassphrase() {
220 if ( passphrase != null ) {
221 return passphrase;
222 }
223 else if ( server != null && server.getPassphrase() != null ) {
224 return server.getPassphrase();
225 }
226
227 return " ";
228 }
229
230 /**
231 * called by execute from super
232 */
233 protected void doExecute() throws Exception {
234 if ( buildNumber == null ) {
235 log.warn("No build number specified; returning");
236 return;
237 }
238
239 File currentSiteDirectory = new File(targetDirectory, "/site");
240 if ( !currentSiteDirectory.exists() ) {
241 log.warn("No site directory here; returning");
242 return;
243 }
244
245 // Download ResultsSummary.html and parse it.
246 File resultsFile = null;
247 try {
248 downloadHTML();
249 resultsFile = new File(targetDirectory, resultsFileName);
250 }
251 catch ( Exception e ) {
252 log.warn("Download failed. " + e.getMessage());
253 }
254
255 Tidy tidy = new Tidy();
256 tidy.setQuiet(true);
257 tidy.setShowWarnings(false);
258
259 if ( resultsFile == null || !resultsFile.exists() ) {
260 log.info( resultsFileName + " could not be downloaded. Using the template to create anew");
261 resultsFile = new File(project.getBasedir(), "src/main/resources/" + resultsFileName);
262 }
263
264 FileInputStream is = new FileInputStream( resultsFile );
265 Document document = tidy.parseDOM(is, null);
266 is.close();
267
268 File reportsDir = new File(targetDirectory, "surefire-reports");
269 if ( !reportsDir.exists() ) {
270 log.warn("No surefire-reports directory here");
271 return;
272 }
273
274 ArrayList files = (ArrayList) FileUtils.getFiles(reportsDir, "TEST-*.xml", null, true);
275 if ( files.size() > 0 ) {
276 document = insertNewColumn(document);
277 if ( document == null ) {
278 throw new MojoFailureException("Main table cannot be found in the " + resultsFileName + ". The file may be corrupted");
279 }
280 }
281
282 for ( Iterator itr=files.iterator(); itr.hasNext(); ) {
283 File file = (File) itr.next();
284 log.debug("working on " + file.getAbsolutePath() );
285 document = processFile(document, file);
286 }
287
288 // Use a Transformer for output
289 TransformerFactory tFactory = TransformerFactory.newInstance();
290 Transformer transformer = tFactory.newTransformer();
291
292 // write the document back into a temporary file.
293 File tempFile = new File(targetDirectory, "ResultsSummary-2.html");
294 FileOutputStream os = new FileOutputStream( tempFile );
295 DOMSource source = new DOMSource(document);
296 StreamResult result = new StreamResult(os);
297 transformer.transform(source, result);
298
299 os.flush();
300 os.close();
301
302 // tidy the document and create/replace ResultsSummary.html in the target directory
303 resultsFile = new File(targetDirectory, resultsFileName);
304 is = new FileInputStream( tempFile );
305 os = new FileOutputStream( resultsFile );
306 tidy.parse(is, os);
307 is.close();
308 os.close();
309
310 // delete the temp file.
311 tempFile.delete();
312
313 try {
314 uploadHTML(resultsFile);
315 }
316 catch ( Exception e ) {
317 log.warn("Upload failed. " + e.getMessage());
318 }
319 }
320
321
322 private String getRemoteUri() {
323 String siteUri = project.getDistributionManagement().getSite().getUrl();
324
325 // chop off the protocol
326 int index = siteUri.indexOf("://");
327 siteUri = siteUri.substring(index + 3);
328 log.debug("siteUri uri is " + siteUri);
329
330 // chop off the buildNumber directory at the end. This is used to deploy site files.
331 index = siteUri.lastIndexOf("/");
332 siteUri = siteUri.substring(0, index);
333 log.debug("siteUri uri is " + siteUri);
334
335 // insert : between the host and path
336 index = siteUri.indexOf("/");
337 String remoteUri = siteUri.substring(0, index) + ":" + siteUri.substring(index);
338 log.debug("siteUri uri is " + remoteUri);
339
340
341 // construct the uri using username
342 remoteUri = getUsername() + ":" + getPassword() + "@" + remoteUri;
343 log.info("Remote uri is " + remoteUri);
344
345 return remoteUri;
346
347 }
348
349
350 /**
351 * Download the html from the remote site where it is has been deployed.
352 */
353 private void downloadHTML()
354 {
355 String remoteUri = getRemoteUri() + "/" + resultsFileName;
356
357 scp.setFile(remoteUri);
358 scp.setTodir(targetDirectory.getAbsolutePath());
359 scp.execute();
360 }
361
362
363 /**
364 * Upload the html to the remote site where it will be deployed.
365 */
366 private void uploadHTML(File resultsFile)
367 {
368 String remoteUri = getRemoteUri();
369 scp.setFile( resultsFile.getAbsolutePath() );
370 scp.setTodir(remoteUri);
371 scp.execute();
372
373 // Use the following block to set 664 perms on the uploaded html file; else synch will fail.
374 // ssh is setting the right perms but blocking. setTimeout doesn't seem to work.
375 /*
376 remoteUri = remoteUri + "/" + resultsFileName;
377 int atindex = remoteUri.lastIndexOf("@");
378 int index = remoteUri.lastIndexOf(":");
379 ssh.setHost(remoteUri.substring(atindex+1, index));
380 ssh.setUsername(getUsername());
381 ssh.setCommand("chmod 664 " + remoteUri.substring(index+1));
382 ssh.setTimeout(30 * 1000);
383 ssh.execute();
384 */
385 }
386
387
388 /**
389 * Append a new column for the latest build. Put the build number in the column header
390 */
391 private Document insertNewColumn(Document document)
392 {
393 Element table = getElementById(document.getDocumentElement(), "table", "mainTable");
394 if ( table == null ) {
395 log.info("table is null");
396 return null;
397 }
398
399 Element thead = getElementById(table, "thead", "mainTableHead");
400 Element tr = (Element) thead.getFirstChild();
401
402 Element td= document.createElement("TD");
403 td.setAttribute("class", "servers");
404
405 Element anchor = document.createElement("a");
406 anchor.setAttribute("href", "./" + buildNumber + "/surefire-report.html");
407 Text text = document.createTextNode(buildNumber);
408 anchor.appendChild(text);
409
410 td.appendChild(anchor);
411 tr.appendChild(td);
412
413 // increment the cols attribute for the table
414 int cols = tr.getChildNodes().getLength();
415
416 // check for number of columns to be shown.
417 // Don't take the suite names column into count.
418 if ( cols > (numberShown + 1) ) {
419 cols = cleanup(table);
420 }
421
422 table.setAttribute("cols", String.valueOf(cols) );
423
424
425 return document;
426 }
427
428 private Document processFile(Document document, File file)
429 {
430 String pcent = getResultsFromFile(file);
431
432 // strip off TEST- and .xml from the filename to get the suitename
433 String fileName = FileUtils.basename(file.getName());
434 fileName = fileName.substring(fileName.indexOf("-") + 1);
435 fileName = fileName.substring(0, fileName.length()-1);
436 document = insertColumn(document, pcent, fileName);
437
438 return document;
439 }
440
441 /**
442 * Load the surefire-report xml file as an ANT xml property and get the values of the results
443 * compute percentage
444 */
445 private String getResultsFromFile(File xmlFile)
446 {
447 String prefix = String.valueOf(System.currentTimeMillis());
448 loadXMLProperty(xmlFile, prefix);
449
450 String tests = ant.getAnt().getProperty(prefix + ".testsuite.tests");
451 String errors = ant.getAnt().getProperty(prefix + ".testsuite.errors");
452 String failures = ant.getAnt().getProperty(prefix + ".testsuite.failures");
453 String skipped = ant.getAnt().getProperty(prefix + ".testsuite.skipped");
454
455 log.debug("tests: " + tests + "; errors:" + errors + "; failures:" + failures + "; skipped:" + skipped);
456
457 int testsNum = Integer.parseInt(tests);
458 int errorsNum = Integer.parseInt(errors);
459 int failuresNum = Integer.parseInt(failures);
460 int skippedNum = Integer.parseInt(skipped);
461
462 //String pcent = tests + "/" + errors + "/" + failures + " (" + computePercentage(testsNum, errorsNum, failuresNum, skippedNum) + "%)";
463 String pcent = tests + "/" + errors + "/" + failures;
464 return pcent;
465 }
466
467 /**
468 * http://ant.apache.org/manual/CoreTasks/xmlproperty.html
469 */
470 private void loadXMLProperty(File src, String prefix)
471 {
472 XmlProperty xmlProperty = (XmlProperty)ant.createTask("xmlproperty");
473 xmlProperty.setFile(src);
474 if ( prefix != null ) {
475 xmlProperty.setPrefix(prefix);
476 }
477 xmlProperty.setCollapseAttributes(true);
478 xmlProperty.execute();
479 log.debug("Loaded xml file as ant property with prefix " + prefix);
480 }
481
482 /**
483 * compute percentage
484 */
485 public String computePercentage( int tests, int errors, int failures, int skipped )
486 {
487 float percentage;
488 if ( tests == 0 ) {
489 percentage = 0;
490 }
491 else {
492 percentage = ( (float) ( tests - errors - failures - skipped ) / (float) tests ) * PCENT;
493 }
494
495 return numberFormat.format( percentage );
496 }
497
498 /**
499 * Insert the rest of the column. If there is no matching row for the suite name, create a new row.
500 */
501 private Document insertColumn(Document document, String pcent, String suiteName)
502 {
503 log.debug("inserting column");
504
505 Element table = getElementById(document.getDocumentElement(), "table", "mainTable");
506 int cols = Integer.parseInt( table.getAttribute("cols") );
507
508 Element tr = getElementById(table, "tr", suiteName);
509
510 if ( tr != null ) {
511 // creating empty cells in the cols for the previous failed builds.
512 NodeList nodeList = tr.getElementsByTagName("td");
513 Element td;
514 for ( int i=nodeList.getLength()+1; i<cols; i++ ) {
515 td = document.createElement("TD");
516 td.setAttribute("class", "cell");
517 tr.appendChild(td);
518 }
519
520 td = document.createElement("TD");
521 td.setAttribute("class", "cell");
522
523 Element anchor = document.createElement("a");
524 anchor.setAttribute("href", "./" + buildNumber + "/" + suiteName + "/surefire-report.html");
525 Text text = document.createTextNode(pcent);
526 anchor.appendChild(text);
527
528 td.appendChild(anchor);
529 tr.appendChild(td);
530 }
531 else {
532 log.debug("Creating a new row for a new suite");
533 tr = document.createElement("TR");
534 tr.setAttribute("id", suiteName);
535
536 Element td = document.createElement("TD");
537 td.setAttribute("class", "suite");
538 Text text = document.createTextNode(suiteName);
539 td.appendChild(text);
540 tr.appendChild(td);
541
542 // creating empty cells in the cols for the previous builds.
543 for ( int i=1; i<cols; i++ ) {
544 td = document.createElement("TD");
545 td.setAttribute("class", "cell");
546 tr.appendChild(td);
547 }
548
549 Element anchor = document.createElement("a");
550 anchor.setAttribute("href", "./" + buildNumber + "/" + suiteName + "/surefire-report.html");
551 text = document.createTextNode(pcent);
552 anchor.appendChild(text);
553 td.appendChild(anchor);
554
555 table.appendChild(tr);
556 }
557
558 log.debug("inserted column");
559
560 return document;
561 }
562
563 /**
564 * Get a child element identified by an ID
565 */
566 private Element getElementById(Element element, String tagName, String id)
567 {
568 log.debug("Searching for tag " + tagName + " with id=" + id);
569
570 Element foundElement = null;
571
572 NodeList nodeList = element.getElementsByTagName(tagName);
573
574 for ( int i=0; i<nodeList.getLength(); i++ ) {
575 foundElement = (Element) nodeList.item(i);
576 log.debug("Element is " + foundElement.getTagName() + " " + foundElement.getAttribute("id") );
577 if ( id.trim().equals(foundElement.getAttribute("id").trim()) ) {
578 break;
579 }
580 else {
581 foundElement = null;
582 }
583 }
584
585 return foundElement;
586 }
587
588 /**
589 * Removes the oldest test column(s) from the table based on the value set in 'numberShown' variable
590 */
591 private int cleanup(Element table)
592 {
593 log.info("Removing oldest column");
594
595 NodeList nodeList = table.getElementsByTagName("tr");
596
597 int new_cols = 0;
598 for ( int i=0; i<nodeList.getLength(); i++ ) {
599 Element tr = (Element) nodeList.item(i);
600 Element suiteColumn = (Element) tr.getFirstChild();
601 Element removeMe = (Element) suiteColumn.getNextSibling();
602 tr.removeChild(removeMe);
603
604 // get the count from just the header row since only the header has been added yet
605 if ( i==0 ) {
606 new_cols = tr.getChildNodes().getLength();
607 }
608 }
609
610 if ( new_cols > (numberShown + 1) ) {
611 new_cols = cleanup(table);
612 }
613
614 log.debug( String.valueOf("Returning cols: " + new_cols) );
615
616 return new_cols;
617 }
618 }