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    }