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 }