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 }