From 96d5245af3a1fe13f25298d090585f40d4228159 Mon Sep 17 00:00:00 2001 From: david_williams <david_williams> Date: Sat, 24 Feb 2007 04:59:42 +0000 Subject: [PATCH] create wtp tools project --- .../org.eclipse.wtp.releng.tools/.classpath | 7 + .../org.eclipse.wtp.releng.tools/.cvsignore | 4 + .../org.eclipse.wtp.releng.tools/.project | 28 + .../META-INF/MANIFEST.MF | 8 + .../build.properties | 5 + .../plugin.properties | 2 + .../org.eclipse.wtp.releng.tools/plugin.xml | 27 + .../wtp/releng/tools/ErrorTracker.java | 224 +++ .../eclipse/wtp/releng/tools/FileCounter.java | 146 ++ .../wtp/releng/tools/PlatformStatus.java | 59 + .../releng/tools/TestResultsGenerator.java | 1250 +++++++++++++++++ .../wtpRelengTools.jar | Bin 0 -> 19421 bytes 12 files changed, 1760 insertions(+) create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.classpath create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.cvsignore create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.project create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/META-INF/MANIFEST.MF create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/build.properties create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.properties create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.xml create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/ErrorTracker.java create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/FileCounter.java create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/PlatformStatus.java create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/TestResultsGenerator.java create mode 100644 archive/releng.builder/tools/org.eclipse.wtp.releng.tools/wtpRelengTools.jar diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.classpath b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.classpath new file mode 100644 index 000000000..751c8f2e5 --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.cvsignore b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.cvsignore new file mode 100644 index 000000000..5022c63e8 --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.cvsignore @@ -0,0 +1,4 @@ +javaCompiler.wtpRelengTools.jar.args +build.xml +bin +temp.folder diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.project b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.project new file mode 100644 index 000000000..99c64d284 --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.wtp.releng.tools</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/META-INF/MANIFEST.MF b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/META-INF/MANIFEST.MF new file mode 100644 index 000000000..048d2df1c --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %Bundle-Name.0 +Bundle-SymbolicName: org.eclipse.wtp.releng.tools;singleton:=true +Bundle-Version: 1.0.0 +Require-Bundle: org.apache.ant +Bundle-Localization: plugin +Bundle-ClassPath: wtpRelengTools.jar diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/build.properties b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/build.properties new file mode 100644 index 000000000..73f4109b7 --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/build.properties @@ -0,0 +1,5 @@ +bin.includes = META-INF/,\ + plugin.xml,\ + wtpRelengTools.jar,\ + plugin.properties +source.wtpRelengTools.jar = src/ diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.properties b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.properties new file mode 100644 index 000000000..be037b9d4 --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.properties @@ -0,0 +1,2 @@ +# properties file for org.eclipse.wtp.releng.tools +Bundle-Name.0 = Tools \ No newline at end of file diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.xml b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.xml new file mode 100644 index 000000000..0dee10e32 --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/plugin.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="3.2"?> +<plugin> + + <extension + point="org.eclipse.ant.core.extraClasspathEntries"> + <extraClasspathEntry + library="wtpRelengTools.jar"> + </extraClasspathEntry> + </extension> + <!-- Tasks --> + <extension point="org.eclipse.ant.core.antTasks"> + + <antTask + library="wtpRelengTools.jar" + name="indexResults" + class="org.eclipse.wtp.releng.tools.TestResultsGenerator"> + </antTask> + <antTask + library="wtpRelengTools.jar" + name="indexResults" + class="org.eclipse.wtp.releng.tools.FileCounter"> + </antTask> + + </extension> + +</plugin> diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/ErrorTracker.java b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/ErrorTracker.java new file mode 100644 index 000000000..37efce70a --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/ErrorTracker.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.wtp.releng.tools; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.io.File; + +import java.util.Vector; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * @version 1.0 + * @author + */ +public class ErrorTracker { + + // List of test logs expected at end of build + private Vector testLogs = new Vector(); + + + // Platforms keyed on + private Hashtable platforms = new Hashtable(); + private Hashtable logFiles = new Hashtable(); + private Hashtable typesMap = new Hashtable(); + private Vector typesList = new Vector(); + + public static void main(String[] args) { + + // For testing only. Should not be invoked + + ErrorTracker anInstance = new ErrorTracker(); + anInstance.loadFile("C:\\junk\\testManifest.xml"); + String[] theTypes = anInstance.getTypes(); + for (int i=0; i < theTypes.length; i++) { + // System.out.println("Type: " + theTypes[i]); + PlatformStatus[] thePlatforms = anInstance.getPlatforms(theTypes[i]); + for (int j=0; j < thePlatforms.length; j++) { + // System.out.println("Out ID: " + thePlatforms[j].getId()); + } + } + } + + public void loadFile(String fileName) { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder parser=null; + try { + parser = docBuilderFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e1) { + e1.printStackTrace(); + } + try { + + Document document = parser.parse(fileName); + NodeList elements = document.getElementsByTagName("platform"); + int elementCount = elements.getLength(); + for (int i = 0; i < elementCount; i++) { + PlatformStatus aPlatform = new PlatformStatus((Element) elements.item(i)); + //System.out.println("ID: " + aPlatform.getId()); + platforms.put(aPlatform.getId(), aPlatform); + + Node zipType = elements.item(i).getParentNode(); + String zipTypeName = (String) zipType.getAttributes().getNamedItem("name").getNodeValue(); + + Vector aVector = (Vector) typesMap.get(zipTypeName); + if (aVector == null) { + typesList.add(zipTypeName); + aVector = new Vector(); + typesMap.put(zipTypeName, aVector); + } + aVector.add(aPlatform.getId()); + + } + + NodeList effectedFiles = document.getElementsByTagName("effectedFile"); + int effectedFilesCount = effectedFiles.getLength(); + for (int i = 0; i < effectedFilesCount; i++) { + Node anEffectedFile = effectedFiles.item(i); + Node logFile = anEffectedFile.getParentNode(); + String logFileName = (String) logFile.getAttributes().getNamedItem("name").getNodeValue(); + logFileName=convertPathDelimiters(logFileName); + String effectedFileID = (String) anEffectedFile.getAttributes().getNamedItem("id").getNodeValue(); + //System.out.println(logFileName); + Vector aVector = (Vector) logFiles.get(logFileName); + if (aVector == null) { + aVector = new Vector(); + logFiles.put(logFileName, aVector); + + } + PlatformStatus ps=(PlatformStatus) platforms.get(effectedFileID); + if (ps!=null) + aVector.addElement(ps); + } + + // store a list of the test logs expected after testing + NodeList testLogList = document.getElementsByTagName("logFile"); + int testLogCount = testLogList.getLength(); + for (int i = 0; i < testLogCount; i++) { + + Node testLog = testLogList.item(i); + String testLogName = (String) testLog.getAttributes().getNamedItem("name").getNodeValue(); + Node typeNode=testLog.getAttributes().getNamedItem("type"); + String type="test"; + if (typeNode!=null){ + type = typeNode.getNodeValue(); + } + if (testLogName.endsWith(".xml")&&type.equals("test")){ + testLogs.add(testLogName); + //System.out.println(testLogName); + } + + } + + +// // Test this mess. +// Object[] results = platforms.values().toArray(); +// for (int i=0; i < results.length; i++) { +// PlatformStatus ap = (PlatformStatus) results[i]; +// System.out.println("ID: " + ap.getId() + " passed: " + ap.getPassed()); +// } +// +// Enumeration anEnumeration = logFiles.keys(); +// while (anEnumeration.hasMoreElements()) { +// String aKey = (String) anEnumeration.nextElement(); +// System.out.println("Whack a key: " + aKey); +// ((PlatformStatus) logFiles.get(aKey)).setPassed(false); +// } +// +// results = platforms.values().toArray(); +// for (int i=0; i < results.length; i++) { +// PlatformStatus ap = (PlatformStatus) results[i]; +// System.out.println("ID: " + ap.getId() + " passed: " + ap.getPassed()); +// } + + + + + + } catch (IOException e) { + System.out.println("IOException: " + fileName); + // e.printStackTrace(); + + } catch (SAXException e) { + System.out.println("SAXException: " + fileName); + e.printStackTrace(); + + } + } + + public void registerError(String fileName) { + // System.out.println("Found an error in: " + fileName); + if (logFiles.containsKey(fileName)) { + Vector aVector = (Vector) logFiles.get(fileName); + for (int i = 0; i < aVector.size(); i++) { + ((PlatformStatus) aVector.elementAt(i)).registerError(); + } + } else { + + // If a log file is not specified explicitly it effects + // all "platforms" except JDT + + Enumeration values = platforms.elements(); + while (values.hasMoreElements()) { + PlatformStatus aValue = (PlatformStatus) values.nextElement(); + if (!aValue.getId().equals("JA") && + !aValue.getId().equals("EW") && + !aValue.getId().equals("EA")) { + aValue.registerError(); + } + } + } + } + + public boolean hasErrors(String id) { + return ((PlatformStatus) platforms.get(id)).hasErrors(); + } + + // Answer a string array of the zip type names in the order they appear in + // the .xml file. + public String[] getTypes() { + return (String[]) typesList.toArray(new String[typesList.size()]); + } + + // Answer an array of PlatformStatus objects for a given type. + + public PlatformStatus[] getPlatforms(String type) { + Vector platformIDs = (Vector) typesMap.get(type); + PlatformStatus[] result = new PlatformStatus[platformIDs.size()]; + for (int i = 0; i < platformIDs.size(); i++) { + result[i] = (PlatformStatus) platforms.get((String) platformIDs.elementAt(i)); + } + return result; + } + + /** + * Returns the testLogs. + * @return Vector + */ + public Vector getTestLogs() { + return testLogs; + } + + private String convertPathDelimiters(String path){ + return new File(path).getPath(); + } + +} diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/FileCounter.java b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/FileCounter.java new file mode 100644 index 000000000..f61c4474f --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/FileCounter.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.wtp.releng.tools; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * This task will count the number of fils in a given directory + * that match a given filter. The number of fils will be output + * to a given output file. The output file will be overwritten + * if it already exists. + * + * Note: Filter comparison is NOT case sensitive. Do not use wild cards. + * ie .zip counts all files with .zip anywere in the name. + */ +public class FileCounter extends Task { + + private String sourceDirectory = ""; + private String filterString = ".zip"; + private String outputFile = ""; + + public static void main(String args[]) { + // For testing only. + FileCounter aFileCounter = new FileCounter(); + aFileCounter.setSourceDirectory("c:\\RelEng\\dean"); + aFileCounter.setOutputFile("c:\\RelEng\\dean\\files.count"); + aFileCounter.setFilterString(".zip"); + aFileCounter.execute(); + } + + public void execute() throws BuildException { + // Do the work. + + int count = 0; + + System.out.println("Source Directory: " + this.getSourceDirectory()); + System.out.println("Output File: " + this.getOutputFile()); + System.out.println("Filter String: " + this.getFilterString()); + + File aDirectory = new File(this.getSourceDirectory()); + if (aDirectory == null) { + throw new BuildException("Directory " + this.getSourceDirectory() + " not found."); + } + + String[] names = aDirectory.list(); + if (names == null) { + throw new BuildException("Directory " + this.getSourceDirectory() + " not found."); + } + + System.out.println("List size: " + names.length); + + for (int i = 0; i < names.length; i++) { + System.out.println("Name: " + names[i]); + + int index = -1; + StringTokenizer types = getFileTypes(); + + while (types.hasMoreTokens()){ + index = names[i].toLowerCase().indexOf(types.nextToken().toLowerCase()); + if (index != -1) { + count++; + } + } + + } + + try { + FileOutputStream anOutputStream = new FileOutputStream(this.getOutputFile()); + anOutputStream.write(String.valueOf(count).getBytes()); + anOutputStream.close(); + } catch (FileNotFoundException e) { + throw new BuildException("Can not create file.count file"); + } catch (IOException e) { + throw new BuildException("Can not create file.count file"); + } + + } + + private StringTokenizer getFileTypes(){ + return new StringTokenizer(getFilterString(),","); + } + + /** + * Gets the sourceDirectory. + * @return Returns a String + */ + public String getSourceDirectory() { + return sourceDirectory; + } + + /** + * Sets the sourceDirectory. + * @param sourceDirectory The sourceDirectory to set + */ + public void setSourceDirectory(String sourceDirectory) { + this.sourceDirectory = sourceDirectory; + } + + /** + * Gets the filterString. + * @return Returns a String + */ + public String getFilterString() { + return filterString; + } + + /** + * Sets the filterString. + * @param filterString The filterString to set + */ + public void setFilterString(String filterString) { + this.filterString = filterString; + } + + /** + * Gets the outputFile. + * @return Returns a String + */ + public String getOutputFile() { + return outputFile; + } + + /** + * Sets the outputFile. + * @param outputFile The outputFile to set + */ + public void setOutputFile(String outputFile) { + this.outputFile = outputFile; + } + +} diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/PlatformStatus.java b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/PlatformStatus.java new file mode 100644 index 000000000..5da345143 --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/PlatformStatus.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.wtp.releng.tools; + +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; + +/** + * @version 1.0 + * @author + */ +public class PlatformStatus { + + private String id; + private String name; + private String fileName; + private boolean hasErrors = false; + + PlatformStatus(Element anElement) { + super(); + NamedNodeMap attributes = anElement.getAttributes(); + this.id = (String) attributes.getNamedItem("id").getNodeValue(); + this.name = (String) attributes.getNamedItem("name").getNodeValue(); + this.fileName = (String) attributes.getNamedItem("fileName").getNodeValue(); + + } + + /** + * Gets the id. + * @return Returns a String + */ + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getFileName() { + return fileName; + } + + public void registerError() { + this.hasErrors = true; + } + + public boolean hasErrors() { + return this.hasErrors; + } +} diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/TestResultsGenerator.java b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/TestResultsGenerator.java new file mode 100644 index 000000000..4a6ed6de8 --- /dev/null +++ b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/src/org/eclipse/wtp/releng/tools/TestResultsGenerator.java @@ -0,0 +1,1250 @@ +package org.eclipse.wtp.releng.tools; + + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.Enumeration; + +import org.apache.tools.ant.Task; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + + +/** + * @version 1.0 + * @author Dean Roberts + */ +public class TestResultsGenerator extends Task { + private static final String WARNING_SEVERITY = "WARNING"; + private static final String ERROR_SEVERITY = "ERROR"; + private static final String ForbiddenReferenceID = "ForbiddenReference"; + private static final String DiscouragedReferenceID = "DiscouragedReference"; + + private static final int DEFAULT_READING_SIZE = 8192; + + static final String elementName = "testsuite"; + static final String testResultsToken = "%testresults%"; + static final String compileLogsToken = "%compilelogs%"; + public Vector dropTokens; + public Vector platformSpecs; + public Vector differentPlatforms; + public String testResultsWithProblems = "\n"; + + private DocumentBuilder parser = null; + public ErrorTracker anErrorTracker; + public String testResultsTemplateString = ""; + public String dropTemplateString = ""; + + public Vector platformDescription; + public Vector platformTemplateString; + public Vector platformDropFileName; + + // Status of tests results (pending, successful, failed), used to specify + // the color + // of the test Results link on the build pages (standard, green, red), + // once failures + // are encountered, this is set to failed + protected String testResultsStatus = "successful"; + // assume tests ran. If no html files are found, this is set to false + private boolean testsRan = true; + + // Parameters + // build runs JUnit automated tests + private boolean isBuildTested; + + // buildType, I, N + public String buildType; + + // Comma separated list of drop tokens + public String dropTokenList; + + // Token in platform.php.template to be replaced by the desired platform + // ID + public String platformIdentifierToken; + + // Location of the xml files + public String xmlDirectoryName; + + // Location of the html files + public String htmlDirectoryName; + + // Location of the resulting index.php file. + public String dropDirectoryName; + + // Location and name of the template index.php file. + public String testResultsTemplateFileName; + + // Platform specific template and output list (colon separated) in the + // following format: + // <descriptor, ie. OS name>,path to template file, path to output file + public String platformSpecificTemplateList = ""; + + // Location and name of the template drop index.php file. + public String dropTemplateFileName; + + // Name of the generated index php file. + public String testResultsHtmlFileName; + + // Name of the generated drop index php file; + public String dropHtmlFileName; + + // Arbitrary path used in the index.php page to href the + // generated .html files. + public String hrefTestResultsTargetPath; + + // Aritrary path used in the index.php page to reference the compileLogs + public String hrefCompileLogsTargetPath; + + // Location of compile logs base directory + public String compileLogsDirectoryName; + + // Location and name of test manifest file + public String testManifestFileName; + + // Initialize the prefix to a default string + private String prefix = "default"; + private String testShortName = ""; + private int counter = 0; + // The four configurations, add new configurations to test results here + + // update + // testResults.php.template for changes + private String[] testsConfig = {"linux.gtk.x86.xml", "linux.gtk.x86_5.0.xml", "macosx.carbon.ppc.xml", "win32.win32.x86.xml", "win32.win32.x86_5.0.xml"}; + + + public static void main(String[] args) { + TestResultsGenerator test = new TestResultsGenerator(); + test.setDropTokenList("%sdk%,%tests%,%example%,%rcpruntime%,%rcpsdk%,%icubase%,%runtime%,%platformsdk%,%jdt%,%jdtsdk%,%pde%,%pdesdk%,%cvs%,%cvssdk%,%teamextras%,%swt%,%relengtools%"); + test.setPlatformIdentifierToken("%platform%"); + test.getDropTokensFromList(test.dropTokenList); + test.setIsBuildTested(true); + test.setXmlDirectoryName("C:\\junk\\testresults\\xml"); + test.setHtmlDirectoryName("C:\\junk\\testresults"); + test.setDropDirectoryName("C:\\junk"); + test.setTestResultsTemplateFileName("C:\\junk\\templateFiles\\testResults.php.template"); + test.setPlatformSpecificTemplateList("Windows,C:\\junk\\templateFiles\\platform.php.template,winPlatform.php;Linux,C:\\junk\\templateFiles\\platform.php.template,linPlatform.php;Solaris,C:\\junk\\templateFiles\\platform.php.template,solPlatform.php;AIX,C:\\junk\\templateFiles\\platform.php.template,aixPlatform.php;Macintosh,C:\\junk\\templateFiles\\platform.php.template,macPlatform.php;Source Build,C:\\junk\\templateFiles\\sourceBuilds.php.template,sourceBuilds.php"); + test.setDropTemplateFileName("C:\\junk\\templateFiles\\index.php.template"); + test.setTestResultsHtmlFileName("testResults.php"); + // test.setDropHtmlFileName("index.php"); + test.setDropHtmlFileName("index.html"); + + test.setHrefTestResultsTargetPath("testresults"); + test.setCompileLogsDirectoryName("C:\\junk\\compilelogs\\plugins"); + test.setHrefCompileLogsTargetPath("compilelogs"); + test.setTestManifestFileName("C:\\junk\\testManifest.xml"); + test.execute(); + } + + public void execute() { + + anErrorTracker = new ErrorTracker(); + platformDescription = new Vector(); + platformTemplateString = new Vector(); + platformDropFileName = new Vector(); + anErrorTracker.loadFile(testManifestFileName); + getDropTokensFromList(dropTokenList); + testResultsTemplateString = readFile(testResultsTemplateFileName); + dropTemplateString = readFile(dropTemplateFileName); + + // Specific to the platform build-page + if (platformSpecificTemplateList != "") { + String description, platformTemplateFile, platformDropFile; + // Retrieve the different platforms and their info + getDifferentPlatformsFromList(platformSpecificTemplateList); + // Parses the platform info and retrieves the platform name, + // template file, and drop file + for (int i = 0; i < differentPlatforms.size(); i++) { + getPlatformSpecsFromList(differentPlatforms.get(i).toString()); + description = platformSpecs.get(0).toString(); + platformTemplateFile = platformSpecs.get(1).toString(); + platformDropFile = platformSpecs.get(2).toString(); + platformDescription.add(description); + platformTemplateString.add(readFile(platformTemplateFile)); + platformDropFileName.add(platformDropFile); + + } + + } + + System.out.println("Begin: Generating test results index page"); + System.out.println("Parsing XML files"); + parseXml(); + System.out.println("Parsing compile logs"); + parseCompileLogs(); + System.out.println("End: Generating test results index page"); + writeTestResultsFile(); + // For the platform build-page, write platform files, in addition to + // the index file + if (platformSpecificTemplateList != "") { + writeDropFiles(); + } + else { + writeDropIndexFile(); + } + } + + public void parseCompileLogs() { + + StringBuffer replaceString = new StringBuffer(); + processCompileLogsDirectory(compileLogsDirectoryName, replaceString); + if (replaceString.length() == 0) { + replaceString.append("None"); + } + testResultsTemplateString = replace(testResultsTemplateString, compileLogsToken, String.valueOf(replaceString)); + + } + + private void processCompileLogsDirectory(String directoryName, StringBuffer buffer) { + File sourceDirectory = new File(directoryName); + if (sourceDirectory.isFile()) { + if (sourceDirectory.getName().endsWith(".log")) + readCompileLog(sourceDirectory.getAbsolutePath(), buffer); + if (sourceDirectory.getName().endsWith(".xml")) + parseCompileLog(sourceDirectory.getAbsolutePath(), buffer); + } + if (sourceDirectory.isDirectory()) { + File[] logFiles = sourceDirectory.listFiles(); + Arrays.sort(logFiles); + for (int j = 0; j < logFiles.length; j++) { + processCompileLogsDirectory(logFiles[j].getAbsolutePath(), buffer); + } + } + } + + private void readCompileLog(String log, StringBuffer buffer) { + String fileContents = readFile(log); + + int errorCount = countCompileErrors(fileContents); + int warningCount = countCompileWarnings(fileContents); + int forbiddenWarningCount = countForbiddenWarnings(fileContents); + int discouragedWarningCount = countDiscouragedWarnings(fileContents); + if (errorCount != 0) { + // use wildcard in place of version number on directory names + String logName = log.substring(getCompileLogsDirectoryName().length() + 1); + StringBuffer stringBuffer = new StringBuffer(logName); + stringBuffer.replace(logName.indexOf("_") + 1, logName.indexOf(File.separator, logName.indexOf("_") + 1), "*"); + logName = new String(stringBuffer); + + anErrorTracker.registerError(logName); + } + formatCompileErrorRow(log, errorCount, warningCount, forbiddenWarningCount, discouragedWarningCount, buffer); + } + + private void parseCompileLog(String log, StringBuffer stringBuffer) { + int errorCount = 0; + int warningCount = 0; + int forbiddenWarningCount = 0; + int discouragedWarningCount = 0; + + File file = new File(log); + Document aDocument = null; + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(file)); + InputSource inputSource = new InputSource(reader); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + aDocument = builder.parse(inputSource); + } + catch (SAXException e) { + e.printStackTrace(); + } + catch (IOException e) { + e.printStackTrace(); + } + catch (ParserConfigurationException e) { + e.printStackTrace(); + } + finally { + if (reader != null) { + try { + reader.close(); + } + catch (IOException e) { + // ignore + } + } + } + + if (aDocument == null) + return; + // Get summary of problems + NodeList nodeList = aDocument.getElementsByTagName("problem"); + if (nodeList == null || nodeList.getLength() == 0) + return; + + int length = nodeList.getLength(); + for (int i = 0; i < length; i++) { + Node problemNode = nodeList.item(i); + NamedNodeMap aNamedNodeMap = problemNode.getAttributes(); + Node severityNode = aNamedNodeMap.getNamedItem("severity"); + Node idNode = aNamedNodeMap.getNamedItem("id"); + if (severityNode != null) { + String severityNodeValue = severityNode.getNodeValue(); + if (WARNING_SEVERITY.equals(severityNodeValue)) { + // this is a warning + // need to check the id + String nodeValue = idNode.getNodeValue(); + if (ForbiddenReferenceID.equals(nodeValue)) { + forbiddenWarningCount++; + } + else if (DiscouragedReferenceID.equals(nodeValue)) { + discouragedWarningCount++; + } + else { + warningCount++; + } + } + else if (ERROR_SEVERITY.equals(severityNodeValue)) { + // this is an error + errorCount++; + } + } + } + if (errorCount != 0) { + // use wildcard in place of version number on directory names + // System.out.println(log + "/n"); + String logName = log.substring(getCompileLogsDirectoryName().length() + 1); + StringBuffer buffer = new StringBuffer(logName); + buffer.replace(logName.indexOf("_") + 1, logName.indexOf(File.separator, logName.indexOf("_") + 1), "*"); + logName = new String(buffer); + + anErrorTracker.registerError(logName); + } + formatCompileErrorRow(log.replaceAll(".xml", ".html"), errorCount, warningCount, forbiddenWarningCount, discouragedWarningCount, stringBuffer); + } + + public static byte[] getFileByteContent(String fileName) throws IOException { + InputStream stream = null; + try { + File file = new File(fileName); + stream = new FileInputStream(file); + return getInputStreamAsByteArray(stream, (int) file.length()); + } + finally { + if (stream != null) { + try { + stream.close(); + } + catch (IOException e) { + // ignore + } + } + } + } + + /** + * Returns the given input stream's contents as a byte array. If a length + * is specified (ie. if length != -1), only length bytes are returned. + * Otherwise all bytes in the stream are returned. Note this doesn't close + * the stream. + * + * @throws IOException + * if a problem occured reading the stream. + */ + public static byte[] getInputStreamAsByteArray(InputStream stream, int length) throws IOException { + byte[] contents; + if (length == -1) { + contents = new byte[0]; + int contentsLength = 0; + int amountRead = -1; + do { + int amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE); // read + // at + // least + // 8K + + // resize contents if needed + if (contentsLength + amountRequested > contents.length) { + System.arraycopy(contents, 0, contents = new byte[contentsLength + amountRequested], 0, contentsLength); + } + + // read as many bytes as possible + amountRead = stream.read(contents, contentsLength, amountRequested); + + if (amountRead > 0) { + // remember length of contents + contentsLength += amountRead; + } + } + while (amountRead != -1); + + // resize contents if necessary + if (contentsLength < contents.length) { + System.arraycopy(contents, 0, contents = new byte[contentsLength], 0, contentsLength); + } + } + else { + contents = new byte[length]; + int len = 0; + int readSize = 0; + while ((readSize != -1) && (len != length)) { + // See PR 1FMS89U + // We record first the read size. In this case len is the + // actual read size. + len += readSize; + readSize = stream.read(contents, len, length - len); + } + } + + return contents; + } + + public String readFile(String fileName) { + byte[] aByteArray = null; + try { + aByteArray = getFileByteContent(fileName); + } + catch (IOException e) { + e.printStackTrace(); + } + if (aByteArray == null) { + return ""; + } + return new String(aByteArray); + } + + private int countCompileErrors(String aString) { + return extractNumber(aString, "error"); + } + + private int countCompileWarnings(String aString) { + return extractNumber(aString, "warning"); + } + + private int countForbiddenWarnings(String aString) { + return extractNumber(aString, "Access restriction:"); + } + + private int countDiscouragedWarnings(String aString) { + return extractNumber(aString, "Discouraged access:"); + } + + private int extractNumber(String aString, String endToken) { + int endIndex = aString.lastIndexOf(endToken); + if (endIndex == -1) { + return 0; + } + + int startIndex = endIndex; + while (startIndex >= 0 && aString.charAt(startIndex) != '(' && aString.charAt(startIndex) != ',') { + startIndex--; + } + + String count = aString.substring(startIndex + 1, endIndex).trim(); + try { + return Integer.parseInt(count); + } + catch (NumberFormatException e) { + return 0; + } + + } + + private int missingCount = 0; + private boolean includeAll; + + private String verifyAllTestsRan(String directory) { + Enumeration enumeration = (anErrorTracker.getTestLogs()).elements(); + + String replaceString = ""; + while (enumeration.hasMoreElements()) { + String testLogName = enumeration.nextElement().toString(); + + if (new File(directory + File.separator + testLogName).exists()) + continue; + + anErrorTracker.registerError(testLogName); + String tmp = ((platformSpecificTemplateList == "") ? formatRow(testLogName, -1, false) : formatRowReleng(testLogName, -1, false)); + if (missingCount == 0) { + replaceString = replaceString + "</table></br>" + "\n" + "<table width=\"65%\" border=\"1\" bgcolor=\"#EEEEEE\" rules=\"groups\" align=\"center\">" + "<tr bgcolor=\"#9999CC\"> <th width=\"80%\" align=\"center\"> Missing Files </th><th align=\"center\"> Status </th></tr>"; + } + replaceString = replaceString + tmp; + testResultsWithProblems = testResultsWithProblems.concat("\n" + testLogName.substring(0, testLogName.length() - 4) + " (file missing)"); + missingCount++; + } + return replaceString; + } + + public void parseXml() { + + File sourceDirectory = new File(xmlDirectoryName); + + if (sourceDirectory.exists()) { + + String replaceString = ""; + + File[] xmlFileNames = sourceDirectory.listFiles(); + Arrays.sort(xmlFileNames); + + for (int i = 0; i < xmlFileNames.length; i++) { + if (xmlFileNames[i].getPath().endsWith(".xml")) { + String fullName = xmlFileNames[i].getPath(); + int errorCount = countErrors(fullName); + if (errorCount != 0) { + String testName = xmlFileNames[i].getName().substring(0, xmlFileNames[i].getName().length() - 4); + testResultsWithProblems = testResultsWithProblems.concat("\n" + testName); + anErrorTracker.registerError(fullName.substring(getXmlDirectoryName().length() + 1)); + } + + + String tmp = ((platformSpecificTemplateList == "") ? formatRow(xmlFileNames[i].getPath(), errorCount, true) : formatRowReleng(xmlFileNames[i].getPath(), errorCount, true)); + replaceString = replaceString + tmp; + + + } + } + // check for missing test logs + replaceString = replaceString + verifyAllTestsRan(xmlDirectoryName); + + testResultsTemplateString = replace(testResultsTemplateString, testResultsToken, replaceString); + testsRan = true; + + } + else { + testsRan = false; + System.out.println("Test results not found in " + sourceDirectory.getAbsolutePath()); + } + + } + + private String replace(String source, String original, String replacement) { + + int replaceIndex = source.indexOf(original); + if (replaceIndex > -1) { + String resultString = source.substring(0, replaceIndex); + resultString = resultString + replacement; + resultString = resultString + source.substring(replaceIndex + original.length()); + return resultString; + } + else { + System.out.println("Could not find token: " + original); + return source; + } + + } + + protected void writeDropFiles() { + writeDropIndexFile(); + // Write all the platform files + for (int i = 0; i < platformDescription.size(); i++) { + writePlatformFile(platformDescription.get(i).toString(), platformTemplateString.get(i).toString(), platformDropFileName.get(i).toString()); + } + } + + protected void writeDropIndexFile() { + + String[] types = anErrorTracker.getTypes(); + for (int i = 0; i < types.length; i++) { + PlatformStatus[] platforms = anErrorTracker.getPlatforms(types[i]); + String replaceString = processDropRows(platforms); + dropTemplateString = replace(dropTemplateString, dropTokens.get(i).toString(), replaceString); + } + // Replace the token %testsStatus% with the status of the test results + dropTemplateString = replace(dropTemplateString, "%testsStatus%", testResultsStatus); + String outputFileName = dropDirectoryName + File.separator + dropHtmlFileName; + writeFile(outputFileName, dropTemplateString); + } + + // Writes the platform file (dropFileName) specific to "desiredPlatform" + protected void writePlatformFile(String desiredPlatform, String templateString, String dropFileName) { + + String[] types = anErrorTracker.getTypes(); + for (int i = 0; i < types.length; i++) { + PlatformStatus[] platforms = anErrorTracker.getPlatforms(types[i]); + // Call processPlatformDropRows passing the platform's name + String replaceString = processPlatformDropRows(platforms, desiredPlatform); + templateString = replace(templateString, dropTokens.get(i).toString(), replaceString); + } + // Replace the platformIdentifierToken with the platform's name and + // the testsStatus + // token with the status of the test results + templateString = replace(templateString, platformIdentifierToken, desiredPlatform); + templateString = replace(templateString, "%testsStatus%", testResultsStatus); + String outputFileName = dropDirectoryName + File.separator + dropFileName; + writeFile(outputFileName, templateString); + } + + // Process drop rows specific to each of the platforms + protected String processPlatformDropRows(PlatformStatus[] platforms, String name) { + + String result = ""; + boolean found = false; + for (int i = 0; i < platforms.length; i++) { + // If the platform description indicates the platform's name, or + // "All", + // call processDropRow + if (platforms[i].getName().startsWith(name.substring(0, 3)) || platforms[i].getName().equals("All")) { + result = result + processDropRow(platforms[i]); + found = true; + } + // If the platform description indicates "All Other Platforms", + // process + // the row locally + else if (platforms[i].getName().equals("All Other Platforms") && !found) { + String imageName = ""; + + if (platforms[i].hasErrors()) { + imageName = "<a href=\"" + getTestResultsHtmlFileName() + "\"><img src = \"FAIL.gif\" width=19 height=23></a>"; + } + else { + if (testsRan) { + imageName = "<img src = \"OK.gif\" width=19 height=23>"; + } + else { + if (isBuildTested) { + imageName = "<font size=\"-1\" color=\"#FF0000\">pending</font>"; + } + else { + imageName = "<img src = \"OK.gif\" width=19 height=23>"; + } + } + } + + result = result + "<tr>"; + result = result + "<td><div align=left>" + imageName + "</div></td>\n"; + result = result + "<td>All " + name + "</td>"; + // generate ftp, http, md5 and sha1 links by calling php + // functions in the template + result = result + "<td><?php genLinks($_SERVER[\"SERVER_NAME\"],\"@buildlabel@\",\"" + platforms[i].getFileName() + "\"); ?></td>\n"; + result = result + "</tr>\n"; + } + } + + return result; + } + + protected String processDropRows(PlatformStatus[] platforms) { + + String result = ""; + for (int i = 0; i < platforms.length; i++) { + result = result + processDropRow(platforms[i]); + } + + return result; + } + + protected String processDropRow(PlatformStatus aPlatform) { + + String imageName = ""; + + if (aPlatform.hasErrors()) { + imageName = "<a href=\"" + getTestResultsHtmlFileName() + "\"><img src = \"FAIL.gif\" width=19 height=23></a>"; + // Failure in tests + testResultsStatus = "failed"; + } + else { + if (testsRan) { + imageName = "<img src = \"OK.gif\" width=19 height=23>"; + } + else { + if (isBuildTested) { + imageName = "<font size=\"-1\" color=\"#FF0000\">pending</font>"; + // Tests are pending + testResultsStatus = "pending"; + } + else { + imageName = "<img src = \"OK.gif\" width=19 height=23>"; + } + } + } + + String result = "<tr>"; + + result = result + "<td><div align=left>" + imageName + "</div></td>\n"; + result = result + "<td>" + aPlatform.getName() + "</td>"; + result = result + "<td>" + aPlatform.getFileName() + "</td>\n"; + result = result + "</tr>\n"; + + return result; + } + + public void writeTestResultsFile() { + + String outputFileName = dropDirectoryName + File.separator + testResultsHtmlFileName; + writeFile(outputFileName, testResultsTemplateString); + } + + private void writeFile(String outputFileName, String contents) { + FileOutputStream outputStream = null; + try { + outputStream = new FileOutputStream(outputFileName); + outputStream.write(contents.getBytes()); + } + catch (FileNotFoundException e) { + System.out.println("File not found exception writing: " + outputFileName); + } + catch (IOException e) { + System.out.println("IOException writing: " + outputFileName); + } + finally { + if (outputStream != null) { + try { + outputStream.close(); + } + catch (IOException e) { + // ignore + } + } + } + } + + public void setTestResultsHtmlFileName(String aString) { + testResultsHtmlFileName = aString; + } + + public String getTestResultsHtmlFileName() { + return testResultsHtmlFileName; + } + + public void setTestResultsTemplateFileName(String aString) { + testResultsTemplateFileName = aString; + } + + public String getTestResultsTemplateFileName() { + return testResultsTemplateFileName; + } + + public void setXmlDirectoryName(String aString) { + xmlDirectoryName = aString; + } + + public String getXmlDirectoryName() { + return xmlDirectoryName; + } + + public void setHtmlDirectoryName(String aString) { + htmlDirectoryName = aString; + } + + public String getHtmlDirectoryName() { + return htmlDirectoryName; + } + + public void setDropDirectoryName(String aString) { + dropDirectoryName = aString; + } + + public String getDropDirectoryName() { + return dropDirectoryName; + } + + private void formatCompileErrorRow(String fileName, int errorCount, int warningCount, int forbiddenAccessWarningCount, int discouragedAccessWarningCount, StringBuffer buffer) { + + int accessRuleWarningCount = forbiddenAccessWarningCount + discouragedAccessWarningCount; + if (!includeAll) { + if (errorCount == 0 && warningCount == 0 && accessRuleWarningCount == 0) { + return; + } + } + + int i = fileName.indexOf(getHrefCompileLogsTargetPath()); + + String shortName = fileName.substring(i + getHrefCompileLogsTargetPath().length()); + + buffer.append("<tr>\n<td>\n").append("<a href=").append("\"").append(getHrefCompileLogsTargetPath()).append(shortName).append("\">").append(shortName).append("</a>").append("</td><td align=\"center\">").append("<a href=").append("\"").append(getHrefCompileLogsTargetPath()).append(shortName).append("#ERRORS").append("\">").append(errorCount).append("</a>").append("</td><td align=\"center\">").append("<a href=").append("\"").append(getHrefCompileLogsTargetPath()).append(shortName).append("#ACCESSRULES_WARNINGS").append("\">").append(accessRuleWarningCount).append("</a>").append("(").append(forbiddenAccessWarningCount).append("/").append(discouragedAccessWarningCount).append(")").append("</td><td align=\"center\">").append("<a href=").append("\"").append(getHrefCompileLogsTargetPath()).append(shortName).append("#OTHER_WARNINGS").append("\">").append(warningCount).append("</a>").append("</td>\n</tr>\n"); + } + + private String formatRow(String fileName, int errorCount, boolean link) { + + // replace .xml with .html + + String aString = ""; + if (!link) { + return "<tr><td>" + fileName + " (missing)" + "</td><td>" + "DNF"; + } + + if (fileName.endsWith(".xml")) { + + int begin = fileName.lastIndexOf(File.separatorChar); + int end = fileName.lastIndexOf(".xml"); + + String shortName = fileName.substring(begin + 1, end); + String displayName = shortName; + if (errorCount != 0) + aString = aString + "<tr><td><b>"; + else + aString = aString + "<tr><td>"; + + + if (errorCount != 0) { + displayName = "<font color=\"#ff0000\">" + displayName + "</font>"; + } + if (errorCount == -1) { + aString = aString.concat(displayName); + } + else { + aString = aString + "<a href=" + "\"" + hrefTestResultsTargetPath + "/" + shortName + ".html" + "\">" + displayName + "</a>"; + } + if (errorCount > 0) + aString = aString + "</td><td><b>"; + else + aString = aString + "</td><td>"; + + if (errorCount == -1) + aString = aString + "<font color=\"#ff0000\">DNF"; + + else if (errorCount > 0) + aString = aString + "<font color=\"#ff0000\">" + String.valueOf(errorCount); + else + aString = aString + String.valueOf(errorCount); + + if (errorCount != 0) + aString = aString + "</font></b></td></tr>"; + else + aString = aString + "</td></tr>"; + } + + return aString; + + } + + // Specific to the RelEng test results page + private String formatRowReleng(String fileName, int errorCount, boolean link) { + + // If the file name doesn't end with any of the set test + // configurations, do nothing + boolean endsWithConfig = false; + int card = testsConfig.length; + for (int i = 0; i < card; i++) { + if (fileName.endsWith(testsConfig[i])) + endsWithConfig = true; + } + if (!endsWithConfig) + return ""; + + String aString = ""; + if (!link) { + return "<tr><td>" + fileName + "</td><td align=\"center\">" + "DNF </tr>"; + } + + if (fileName.endsWith(".xml")) { + + int begin = fileName.lastIndexOf(File.separatorChar); + + // Get org.eclipse. out of the component name + String shortName = fileName.substring(begin + 13, fileName.indexOf('_')); + String displayName = shortName; + + // If the short name does not start with this prefix + if (!shortName.startsWith(prefix)) { + // If the prefix is not yet set + if (prefix == "default") { + // Set the testShortName variable to the current short + // name + testShortName = shortName; + counter = 0; + // Set new prefix + prefix = shortName.substring(0, shortName.indexOf(".tests") + 6); + aString = aString + "<tbody><tr><td><b>" + prefix + ".*" + "</b><td><td><td><td>"; + aString = aString + "<tr><td><P>" + shortName; + + // Loop until the matching string postfix(test config.) is + // found + while (counter < card && !fileName.endsWith(testsConfig[counter])) { + aString = aString + "<td align=\"center\">-</td>"; + counter++; + } + } + else { + // Set new prefix + prefix = shortName.substring(0, shortName.indexOf(".tests") + 6); + + // Loop until the matching string postfix(test config.) is + // found + while (counter < card && !fileName.endsWith(testsConfig[counter])) { + aString = aString + "<td align=\"center\">-</td>"; + counter++; + } + + // In this case, the new prefix should be set with the + // short name under it, + // since this would mean that the team has more than one + // component test + if (!shortName.endsWith("tests")) { + aString = aString + "<tbody><tr><td><b>" + prefix + ".*" + "</b><td><td><td><td>"; + aString = aString + "<tr><td><P>" + shortName; + } + // The team has only one component test + else + aString = aString + "<tbody><tr><td><b>" + shortName; + testShortName = shortName; + + counter = 0; + } + } + // If the file's short name starts with the current prefix + if (shortName.startsWith(prefix)) { + // If the new file has a different short name than the current + // one + if (!shortName.equals(testShortName)) { + // Fill the remaining cells with '-'. These files will + // later be listed as + // missing + while (counter < card) { + aString = aString + "<td align=\"center\">-</td>"; + counter++; + } + counter = 0; + // Print the component name + aString = aString + "<tr><td><P>" + shortName; + // Loop until the matching string postfix(test config.) is + // found + while (counter < card && !fileName.endsWith(testsConfig[counter])) { + aString = aString + "<td align=\"center\">-</td>"; + counter++; + } + } + else { + // Loop until the matching string postfix(test config.) is + // found + while (counter < card && !fileName.endsWith(testsConfig[counter])) { + aString = aString + "<td align=\"center\">-</td>"; + counter++; + } + // If the previous component has no more test files left + if (counter == card) { + counter = 0; + // Print the new component name + aString = aString + "<tr><td><P>" + shortName; + // Loop until the matching string postfix(test + // config.) is found + while (counter < card && !fileName.endsWith(testsConfig[counter])) { + aString = aString + "<td align=\"center\">-</td>"; + counter++; + } + } + } + + testShortName = shortName; + + if (errorCount != 0) + aString = aString + "<td align=\"center\"><b>"; + else + aString = aString + "<td align=\"center\">"; + + // Print number of errors + if (errorCount != 0) { + displayName = "<font color=\"#ff0000\">" + "(" + String.valueOf(errorCount) + ")" + "</font>"; + } + else { + displayName = "(0)"; + } + + // Reference + if (errorCount == -1) { + aString = aString.concat(displayName); + } + else { + aString = aString + "<a href=" + "\"" + hrefTestResultsTargetPath + "/" + fileName.substring(begin + 1, fileName.length() - 4) + ".html" + "\">" + displayName + "</a>"; + } + + if (errorCount == -1) + aString = aString + "<font color=\"#ff0000\">DNF"; + + if (errorCount != 0) + aString = aString + "</font></b></td>"; + else + aString = aString + "</td>"; + counter++; + } + } + + return aString; + } + + private int countErrors(String fileName) { + int errorCount = 0; + + if (new File(fileName).length() == 0) + return -1; + + try { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + parser = docBuilderFactory.newDocumentBuilder(); + + Document document = parser.parse(fileName); + NodeList elements = document.getElementsByTagName(elementName); + + int elementCount = elements.getLength(); + if (elementCount == 0) + return -1; + for (int i = 0; i < elementCount; i++) { + Element element = (Element) elements.item(i); + NamedNodeMap attributes = element.getAttributes(); + Node aNode = attributes.getNamedItem("errors"); + errorCount = errorCount + Integer.parseInt(aNode.getNodeValue()); + aNode = attributes.getNamedItem("failures"); + errorCount = errorCount + Integer.parseInt(aNode.getNodeValue()); + + } + + } + catch (IOException e) { + System.out.println("IOException: " + fileName); + // e.printStackTrace(); + return 0; + } + catch (SAXException e) { + System.out.println("SAXException: " + fileName); + // e.printStackTrace(); + return 0; + } + catch (ParserConfigurationException e) { + e.printStackTrace(); + } + return errorCount; + } + + + + /** + * Gets the hrefTestResultsTargetPath. + * + * @return Returns a String + */ + public String getHrefTestResultsTargetPath() { + return hrefTestResultsTargetPath; + } + + /** + * Sets the hrefTestResultsTargetPath. + * + * @param hrefTestResultsTargetPath + * The hrefTestResultsTargetPath to set + */ + public void setHrefTestResultsTargetPath(String htmlTargetPath) { + this.hrefTestResultsTargetPath = htmlTargetPath; + } + + /** + * Gets the compileLogsDirectoryName. + * + * @return Returns a String + */ + public String getCompileLogsDirectoryName() { + return compileLogsDirectoryName; + } + + /** + * Sets the compileLogsDirectoryName. + * + * @param compileLogsDirectoryName + * The compileLogsDirectoryName to set + */ + public void setCompileLogsDirectoryName(String compileLogsDirectoryName) { + this.compileLogsDirectoryName = compileLogsDirectoryName; + } + + /** + * Gets the hrefCompileLogsTargetPath. + * + * @return Returns a String + */ + public String getHrefCompileLogsTargetPath() { + return hrefCompileLogsTargetPath; + } + + /** + * Sets the hrefCompileLogsTargetPath. + * + * @param hrefCompileLogsTargetPath + * The hrefCompileLogsTargetPath to set + */ + public void setHrefCompileLogsTargetPath(String hrefCompileLogsTargetPath) { + this.hrefCompileLogsTargetPath = hrefCompileLogsTargetPath; + } + + /** + * Gets the testManifestFileName. + * + * @return Returns a String + */ + public String getTestManifestFileName() { + return testManifestFileName; + } + + /** + * Sets the testManifestFileName. + * + * @param testManifestFileName + * The testManifestFileName to set + */ + public void setTestManifestFileName(String testManifestFileName) { + this.testManifestFileName = testManifestFileName; + } + + /** + * Gets the dropHtmlFileName. + * + * @return Returns a String + */ + public String getDropHtmlFileName() { + return dropHtmlFileName; + } + + /** + * Sets the dropHtmlFileName. + * + * @param dropHtmlFileName + * The dropHtmlFileName to set + */ + public void setDropHtmlFileName(String dropHtmlFileName) { + this.dropHtmlFileName = dropHtmlFileName; + } + + /** + * Gets the dropTemplateFileName. + * + * @return Returns a String + */ + public String getDropTemplateFileName() { + return dropTemplateFileName; + } + + /** + * Sets the dropTemplateFileName. + * + * @param dropTemplateFileName + * The dropTemplateFileName to set + */ + public void setDropTemplateFileName(String dropTemplateFileName) { + this.dropTemplateFileName = dropTemplateFileName; + } + + protected void getDropTokensFromList(String list) { + StringTokenizer tokenizer = new StringTokenizer(list, ","); + dropTokens = new Vector(); + + while (tokenizer.hasMoreTokens()) { + dropTokens.add(tokenizer.nextToken()); + } + } + + protected void getDifferentPlatformsFromList(String list) { + StringTokenizer tokenizer = new StringTokenizer(list, ";"); + differentPlatforms = new Vector(); + + while (tokenizer.hasMoreTokens()) { + differentPlatforms.add(tokenizer.nextToken()); + } + } + + protected void getPlatformSpecsFromList(String list) { + StringTokenizer tokenizer = new StringTokenizer(list, ","); + platformSpecs = new Vector(); + + while (tokenizer.hasMoreTokens()) { + platformSpecs.add(tokenizer.nextToken()); + } + } + + public String getDropTokenList() { + return dropTokenList; + } + + public void setDropTokenList(String dropTokenList) { + this.dropTokenList = dropTokenList; + } + + public boolean isBuildTested() { + return isBuildTested; + } + + public void setIsBuildTested(boolean isBuildTested) { + this.isBuildTested = isBuildTested; + } + + + /** + * @return + */ + public boolean testsRan() { + return testsRan; + } + + /** + * @param b + */ + public void setTestsRan(boolean b) { + testsRan = b; + } + + /** + * @return + */ + public Vector getDropTokens() { + return dropTokens; + } + + /** + * @param vector + */ + public void setDropTokens(Vector vector) { + dropTokens = vector; + } + + /** + * @return + */ + public String getTestResultsWithProblems() { + return testResultsWithProblems; + } + + /** + * @param string + */ + public void setTestResultsWithProblems(String string) { + testResultsWithProblems = string; + } + + public String getBuildType() { + return buildType; + } + + public void setBuildType(String buildType) { + this.buildType = buildType; + } + + public String getPlatformSpecificTemplateList() { + return platformSpecificTemplateList; + } + + public void setPlatformSpecificTemplateList(String platformSpecificTemplateList) { + this.platformSpecificTemplateList = platformSpecificTemplateList; + } + + public void setPlatformIdentifierToken(String platformIdentifierToken) { + this.platformIdentifierToken = platformIdentifierToken; + } + + public String getPlatformIdentifierToken() { + return platformIdentifierToken; + } + + public boolean isIncludeAll() { + return includeAll; + } + + public void setIncludeAll(boolean includeAll) { + this.includeAll = includeAll; + } + +} diff --git a/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/wtpRelengTools.jar b/archive/releng.builder/tools/org.eclipse.wtp.releng.tools/wtpRelengTools.jar new file mode 100644 index 0000000000000000000000000000000000000000..623e66dbde5a9b822295894dee3f27e8d95c5706 GIT binary patch literal 19421 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 E51FVZMgRZ+ literal 0 HcmV?d00001 -- GitLab