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