diff --git a/el/pom.xml b/el/pom.xml index 680cc88b49..ae69632404 100644 --- a/el/pom.xml +++ b/el/pom.xml @@ -53,11 +53,6 @@ common ${project.parent.version} - - ${project.groupId} - signaturetest - ${project.parent.version} - jakarta.el jakarta.el-api diff --git a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/ELSigTestIT.java b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/ELSigTestIT.java index a5cfc22886..7cb43f6110 100644 --- a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/ELSigTestIT.java +++ b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/ELSigTestIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2023 Oracle and/or its affiliates and others. + * Copyright (c) 2007, 2024 Oracle and/or its affiliates and others. * All rights reserved. * * This program and the accompanying materials are made available under the @@ -24,22 +24,13 @@ import org.junit.jupiter.api.Test; -import com.sun.javatest.Status; -import com.sun.ts.tests.signaturetest.SigTest; -import com.sun.ts.tests.signaturetest.SignatureTestDriver; -import com.sun.ts.tests.signaturetest.SignatureTestDriverFactory; - import java.util.Properties; -import com.sun.ts.lib.util.TestUtil; import java.lang.System.Logger; /* * This class is a simple example of a signature test that extends the * SigTest framework class. This signature test is run outside of the - * Java EE containers. This class also contains the boilerplate - * code necessary to create a signature test using the test framework. - * To see a complete TCK example see the javaee directory for the Java EE - * TCK signature test class. + * EE containers. */ public class ELSigTestIT extends SigTest { @@ -79,10 +70,6 @@ protected String[] getPackages() { // comment or the one below it depending on which properties your // signature tests need. Please do not use both comments. - /* - * @class.setup_props: ts_home, The base path of this TCK; sigTestClasspath; - */ - /* * @testName: signatureTest * @@ -100,7 +87,6 @@ public void signatureTest() throws Exception { logger.log(Logger.Level.INFO, "$$$ SigTestIT.signatureTest() called"); String mapFile = null; String packageFile = null; - String repositoryDir = null; Properties mapFileAsProps = null; String[] packages = getPackages(); String apiPackage = "jakarta.el"; diff --git a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/PackageList.java b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/PackageList.java new file mode 100644 index 0000000000..1d3890e914 --- /dev/null +++ b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/PackageList.java @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2007, 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.ts.tests.signaturetest.el; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/** + * This class represents a package list file. A package list file is used in + * conjunction with a set of signature files to execute API signature tests. + * Users specify which set of package APIs are verified. Each package's + * signature is verified independently. As such all valid sub-packages must be + * excluded while a package's signature is being verified. This allows API check + * to determine incompatible additional packages included in a distribution. + *

+ * This class builds a package list file when signatures are recorded and + * provides an API to provide valid sub-package lists when signatures are played + * back (verified). + *

+ * In record mode, this class reads the existing package list file, if one + * exists, and removes the package (as well as sub-packages) that are currently + * being recorded. All package names read from the existing package list file + * are held in a tree set which sorts the package names and keeps duplicate + * package names from ocurring. The user can then instruct this class to write + * out the package list file. At this point this class reads the currently + * recorded signature file and extracts each package names and adds it to the + * tree set. After this step the previous package list file is saved as a backup + * and the new package list file is written to disk. + *

+ * In playback mode, this class reads the contents of the package list file and + * stores each package name in a tree set. Users can then invoke the + * getSubPackages method to retrieve the valid sub-packages for any given + * package. This is done by simply examining the package names in the tree set + * and returning any package name that starts with the parent package name and a + * trailing period character. + */ +class PackageList { + + // Any line in the packageFile starting with this character is a comment + private static final char COMMENT_CHAR = '#'; + + private static final String BACKUP_EXT = ".bak"; + + // File containing the list of packages and sub-packages + private File packageFile; + + // Signature file where the package signatures were recorded + private File sigFile; + + // Name of the package being recorded + private String additionalPackageName; + + // Name of packages and sub-packages in the + private Set packageNames = new TreeSet(); + + /** + * Creates an instance of the PackageList class. The PackageList instance + * reads the specified package file and populates it's internal state with the + * package names found in this file. Users should use this c'tor when playing + * back signature files. Users can init the PackageList instance then use the + * "String[] getSubPackages(String packageName)" method to get the list of + * valid sub-packages for every package who's signature is being verified. + * + * @param packageFileName + * The name of the file that contains the package list. This file + * contains the names of all the packages that exist across all the + * signature files that makeup this deliverable. This file is used to + * generate a list of valid sub-packages that must be exclued when + * testing theor parent package's signature. + * + * @throws Exception + * when the packageFileName does not exist. + */ + public PackageList(String packageFileName) throws Exception { + packageFile = new File(packageFileName); + if (packageFile.exists() && packageFile.isFile()) { + extractExistingPackageNames(); + } else { + throw new FileNotFoundException(packageFileName); + } + } + + /** + * Creates an instance of the PackageList class. The PackageList instance + * reads the contents of the packageFileName and stores it in it's internal + * state. Next, any packages whos name starts with the specified packageName + * are removed from the internal package list. This is done because this is + * the package being recorded and we need to remove any previously recorded + * package names in case any sub-packages have been removed since the last + * time the signatures were recorded. Users should use this c'tor when they + * are recording signature files never during playback. + * + * @param packageName + * The name of the package whos signatures are being recorded (along + * with sub-packages). + * @param sigFileName + * The name of the file that contains the recored signatures. + * @param packageFileName + * The name of the file that contains the package list. This file + * contains the names of all the packages that exist across all the + * signature files that makeup this deliverable. This file is used to + * generate a list of valid sub-packages that must be exclued when + * testing their parent package's signature. + * + * @throws Exception + * when an error occurs reading the packageFileName or the + * sigFileName does not exist. + */ + public PackageList(String packageName, String sigFileName, + String packageFileName) throws Exception { + this.additionalPackageName = packageName; + sigFile = new File(sigFileName); + if (!sigFile.exists() || !sigFile.isFile()) { + throw new FileNotFoundException(sigFileName); + } + packageFile = new File(packageFileName); + if (packageFile.exists() && packageFile.isFile()) { + extractExistingPackageNames(); + removeExistingPackage(); + } + } + + /** + * Read the package names stored in the package list file. Each package name + * found in the package list file is added to the internal tree set. + * + * @throws Exception + * if there is an error opening or reading the package list file. + */ + private void extractExistingPackageNames() throws Exception { + BufferedReader in = new BufferedReader(new FileReader(packageFile)); + String line; + String trimLine; + try { + while ((line = in.readLine()) != null) { + trimLine = line.trim(); + if (isComment(trimLine) || "".equals(trimLine)) { + continue; + } + packageNames.add(trimLine); + } + } finally { + try { + in.close(); + } catch (Exception e) { + } + } + } + + /** + * Returns true if the specified string starts with a comment character as + * denoted by the COMMENT_CHAR constant. + * + * @param line + * Determins of this line is a comment line + * + * @return boolean True if the specified line is a comment line else false. + */ + private boolean isComment(String line) { + if (line == null) { + return false; + } + String theLine = line.trim(); + if (theLine.length() > 0) { + return (theLine.charAt(0) == COMMENT_CHAR); + } + + return false; + } + + /** + * Removes package names from the package list file. The packages that are + * removed are the ones currently being recorded. The packages being recorded + * is denoted by this.additionalPackageName. This includes any sub-packages of + * the additionalPackageName. This step is necessary in the cases where a + * sub-package has been removed from a parent package in between signature + * recordings. + */ + private void removeExistingPackage() { + String delPackage = this.additionalPackageName; + String packageName; + List delPkgs = new ArrayList(); + // iterate over package set and find package names to remove + for (Iterator i = packageNames.iterator(); i.hasNext();) { + packageName = (String) i.next(); + if (packageName.startsWith(delPackage)) { + delPkgs.add(packageName); + } + } + // actually remove the package names from the set + for (int i = 0; i < delPkgs.size(); i++) { + packageName = (String) (delPkgs.get(i)); + packageNames.remove(packageName); + System.out.println( + "PackageList.removeExistingPackage() \"" + packageName + "\""); + } + } + + /** + * Extract the package name from the specified string. The specified string + * should have the form: "package jakarta.ejb;" + * + * @param packageLine + * The string containing the package name. + * + * @return String The extracted package name. + * + * @throws Exception + * if the specified string does not conform to the expected format. + */ + private String parsePackageName(String packageLine) throws Exception { + + // sig test framework doesn't have the concept of package entries + // as the ApiCheck signature format does. + // Instead, we need to parse an entry similar to this: + // CLSS public jakarta.some.package.SomeClass + + return packageLine.substring(packageLine.lastIndexOf(' ') + 1, + packageLine.lastIndexOf('.')); + } + + /** + * Reads the package names from the signature file. Each package name that is + * read is added to this classes internal tree set. + * + * @throws Exception + * if there is an error opening or reading the signature file. + */ + private void readPkgsFromSigFile() throws Exception { + BufferedReader in = new BufferedReader(new FileReader(sigFile)); + String line; + String trimLine; + try { + while ((line = in.readLine()) != null) { + trimLine = line.trim(); + if (trimLine.startsWith("CLSS")) { + packageNames.add(parsePackageName(trimLine)); + } + } + } finally { + try { + in.close(); + } catch (Exception e) { + } + } + } + + /** + * Removes the existing package list file. The package list file is actually + * moved to a backup file if it exists. The old backup is lost. + * + * @throws Exception + * if there is an error moving the current package list file to a + * backup file. + */ + private void removePkgFile() throws Exception { + File backupPkgFile = new File(packageFile.getPath() + BACKUP_EXT); + if (backupPkgFile.exists() && backupPkgFile.isFile()) { + backupPkgFile.delete(); + } + if (packageFile.isFile() && packageFile.exists()) { + File copyPackageFile = new File(packageFile.getPath()); + copyPackageFile.renameTo(backupPkgFile); + } + } + + /** + * Write a simple header to the package list file to explain what the file is. + * + * @param out + * The BufferedWriter to dump the header to. + * + * @throws Exception + * if there is any errors writing the header to the specified + * BufferedWriter. + */ + private void writeHeader(BufferedWriter out) throws Exception { + out.write(COMMENT_CHAR); + out.write(COMMENT_CHAR); + out.newLine(); + out.write(COMMENT_CHAR + " This file contains a list of all the packages"); + out.newLine(); + out.write(COMMENT_CHAR + " contained in the signature files for this"); + out.newLine(); + out.write( + COMMENT_CHAR + " deliverable. This file is used to exclude valid"); + out.newLine(); + out.write(COMMENT_CHAR + " sub-packages from being verified when their"); + out.newLine(); + out.write(COMMENT_CHAR + " parent package's signature is checked."); + out.newLine(); + out.write(COMMENT_CHAR); + out.write(COMMENT_CHAR); + out.newLine(); + out.newLine(); + } + + /** + * Write the list of package names out to a package list file. + * + * @throws Exception + * if there is an error creating and writting the package list file. + */ + private void writePkgFile() throws Exception { + BufferedWriter out = null; + try { + out = new BufferedWriter(new FileWriter(packageFile)); + writeHeader(out); + for (Iterator i = packageNames.iterator(); i.hasNext();) { + String packageName = (String) i.next(); + out.write(packageName); + out.newLine(); + System.out + .println("PackageList.writePkgFile() \"" + packageName + "\""); + } + } finally { + if (out != null) { + out.close(); + } + } + } + + /** + * Returns the list of sub-packages that exist in the specified package name. + * + * @param pkgName + * The name of the package we want the sub-package list for. + * + * @return String[] The sub-packages that live under the specified parent + * package. + */ + public String[] getSubPackages(String pkgName) { + List result = new ArrayList(); + String subPackageName = pkgName + "."; + for (Iterator i = packageNames.iterator(); i.hasNext();) { + String packageName = (String) i.next(); + if (packageName.startsWith(subPackageName)) { + result.add(packageName); + } + } + return (String[]) (result.toArray(new String[result.size()])); + } + + /** + * Returns the list of sub-packages that exist in the specified package name. + * The returned string matches the API check format of specifying multiple + * packages with a single string. Each package name is separated with the "+" + * character. + * + * @param pkgName + * The name of the package we want the sub-package list for. + * + * @return String The sub-packages that live under the specified parent + * package. + */ + public String getSubPackagesFormatted(String pkgName) { + StringBuffer formattedResult = new StringBuffer(); + String[] result = getSubPackages(pkgName); + for (int i = 0; i < result.length; i++) { + formattedResult.append(result[i]); + if (i < (result.length - 1)) { + formattedResult.append("+"); + } + } + return formattedResult.toString(); + } + +} // end class PackageList diff --git a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTest.java b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTest.java new file mode 100644 index 0000000000..677123b4de --- /dev/null +++ b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2007, 2024 Oracle and/or its affiliates and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.ts.tests.signaturetest.el; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import java.util.ArrayList; +import java.util.Properties; + +import java.lang.System.Logger; + +/** + * This class should be extended by TCK developers that wish to create a set of + * signature tests that run outside of any Java EE container. Developers must + * implement the getPackages method to specify which packages are to be tested + * by the signature test framework. + */ +public abstract class SigTest { + + private static final Logger logger = System.getLogger(SigTest.class.getName()); + + protected SignatureTestDriver driver; + + /** + *

+ * Returns a {@link SignatureTestDriver} appropriate for the particular TCK + * (using API check or the Signature Test Framework). + *

+ * + *

+ * The default implementation of this method will return a + * {@link SignatureTestDriver} that will use API Check. TCK developers can + * override this to return the desired {@link SignatureTestDriver} for their + * TCK. + */ + protected SignatureTestDriver getSigTestDriver() { + + if (driver == null) { + driver = SignatureTestDriverFactory.getInstance(SignatureTestDriverFactory.SIG_TEST); + } + + return driver; + + } // END getSigTestDriver + + + /** + * Returns the list of packages that must be tested by the siganture test + * framework. TCK developers must implement this method in their signature + * test sub-class. + * + * @return String A list of packages that the developer wishes to test using + * the signature test framework. + */ + protected abstract String[] getPackages(); + + /** + * Returns an array of individual classes that must be tested by the signature + * test framwork. TCK developers may override this method when this + * functionality is needed. Most will only need package level granularity. + * + * @return an Array of Strings containing the individual classes the framework + * should test. The default implementation of this method returns a + * zero-length array. + */ + protected String[] getClasses() { + + return new String[] {}; + + } // END getClasses + + protected SigTestData testInfo; + + /** + * Called by the test framework to initialize this test. The method simply + * retrieves some state information that is necessary to run the test when + * when the test framework invokes the run method (actually the test1 method). + * + * @param args + * List of arguments passed to this test. + * @param p + * Properties specified by the test user and passed to this test via + * the test framework. + */ + public void setup() { + try { + logger.log(Logger.Level.TRACE, "$$$ SigTest.setup() called"); + this.testInfo = new SigTestData(); + logger.log(Logger.Level.TRACE, "$$$ SigTest.setup() complete"); + } catch (Exception e) { + logger.log(Logger.Level.ERROR, "Unexpected exception " + e.getMessage()); + } + } + + /** + * Called by the test framework to run this test. This method utilizes the + * state information set in the setup method to run the signature tests. All + * signature test code resides in the utility class so it can be reused by the + * signature test framework base classes. + * + * @throws Exception + * When an error occurs executing the signature tests. + */ + public void signatureTest(String mapFile, String packageFile, Properties mapFileAsProps, String[] packages) + throws Exception { + + SigTestResult results = null; + String repositoryDir = System.getProperty("java.io.tmpdir"); + String[] classes = getClasses(); + String testClasspath = testInfo.getTestClasspath(); + + // If testing with Java 9+, extract the JDK's modules so they can be used + // on the testcase's classpath. + + String jimageDir = testInfo.getJImageDir(); + File f = new File(jimageDir); + f.mkdirs(); + + String javaHome = System.getProperty("java.home"); + logger.log(Logger.Level.INFO, "Executing JImage"); + + try { + ProcessBuilder pb = new ProcessBuilder(javaHome + "/bin/jimage", "extract", "--dir=" + jimageDir, javaHome + "/lib/modules"); + logger.log(Logger.Level.INFO, javaHome + "/bin/jimage extract --dir=" + jimageDir + " " + javaHome + "/lib/modules"); + pb.redirectErrorStream(true); + Process proc = pb.start(); + BufferedReader out = new BufferedReader(new InputStreamReader(proc.getInputStream())); + String line = null; + while ((line = out.readLine()) != null) { + logger.log(Logger.Level.INFO, line); + } + + int rc = proc.waitFor(); + logger.log(Logger.Level.INFO,"JImage RC = " + rc); + out.close(); + } catch (Exception e) { + logger.log(Logger.Level.INFO, "Exception while executing JImage! Some tests may fail."); + e.printStackTrace(); + } + + + try { + results = getSigTestDriver().executeSigTest(packageFile, mapFile, + repositoryDir, packages, classes, testClasspath); + logger.log(Logger.Level.INFO,results.toString()); + if (!results.passed()) { + logger.log(Logger.Level.TRACE, "results.passed() returned false"); + throw new Exception(); + } + logger.log(Logger.Level.TRACE, "$$$ SigTest.test1() returning"); + } catch (Exception e) { + if (results != null && !results.passed()) { + throw new Exception("SigTest.test1() failed!, diffs found"); + } else { + logger.log(Logger.Level.ERROR, "Unexpected exception " + e.getMessage()); + throw new Exception("test failed with an unexpected exception", e); + } + } + } + + public File writeStreamToTempFile(InputStream inputStream, String tempFilePrefix, String tempFileSuffix) throws IOException { + FileOutputStream outputStream = null; + try { + File file = File.createTempFile(tempFilePrefix, tempFileSuffix); + file.deleteOnExit(); + outputStream = new FileOutputStream(file); + byte[] buffer = new byte[1024]; + while (true) { + int bytesRead = inputStream.read(buffer); + if (bytesRead == -1) { + break; + } + outputStream.write(buffer, 0, bytesRead); + } + return file; + } + finally { + if (outputStream != null) { + outputStream.close(); + } + } + } + + public File writeStreamToSigFile(InputStream inputStream, String apiPackage, String packageVersion) throws IOException { + FileOutputStream outputStream = null; + String tmpdir = System.getProperty("java.io.tmpdir"); + try { + File sigfile = new File(tmpdir+ File.separator + apiPackage + ".sig_"+packageVersion); + if(sigfile.exists()){ + sigfile.delete(); + logger.log(Logger.Level.INFO, "Existing signature file deleted to create new one"); + } + if(!sigfile.createNewFile()){ + logger.log(Logger.Level.ERROR, "signature file is not created"); + } + outputStream = new FileOutputStream(sigfile); + byte[] buffer = new byte[1024]; + while (true) { + int bytesRead = inputStream.read(buffer); + if (bytesRead == -1) { + break; + } + outputStream.write(buffer, 0, bytesRead); + } + return sigfile; + } + + finally { + if (outputStream != null) { + outputStream.close(); + } + } + } + + /** + * Called by the test framework to cleanup any outstanding state. + * + */ + public void cleanup() { + logger.log(Logger.Level.TRACE, "$$$ SigTest.cleanup()"); + } + +} // end class SigTest diff --git a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestData.java b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestData.java new file mode 100644 index 0000000000..ff19c22fbb --- /dev/null +++ b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestData.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2007, 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.ts.tests.signaturetest.el; + +import java.util.Properties; + +/** + * This class holds the data passed to a signature test invocation during the + * setup phase. This allows us to keep the passed data separate and reuse the + * data between the signature test framework base classes. + */ +public class SigTestData { + + private Properties props; + + public SigTestData() { + this.props = System.getProperties(); + } + + public String getTestClasspath() { + return props.getProperty("sigTestClasspath", ""); + } + + public String getProperty(String prop) { + return props.getProperty(prop); + } + + public String getJImageDir() { + return props.getProperty("jimage.dir", ""); + } +} // end class SigTestData diff --git a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestDriver.java b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestDriver.java new file mode 100644 index 0000000000..7880ce98ce --- /dev/null +++ b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestDriver.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2007, 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.ts.tests.signaturetest.el; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.lang.System.Logger; + + +/** + *

+ * Wrapper for the Sig Test framework. + *

+ */ +public class SigTestDriver extends SignatureTestDriver { + + private static final Logger logger = System.getLogger(SigTestDriver.class.getName()); + + private static final String CLASSPATH_FLAG = "-Classpath"; + + private static final String FILENAME_FLAG = "-FileName"; + + private static final String PACKAGE_FLAG = "-Package"; + + private static final String PACKAGE_NO_SUBS_FLAG = "-PackageWithoutSubpackages"; + + private static final String API_VERSION_FLAG = "-ApiVersion"; + + private static final String EXCLUDE_FLAG = "-Exclude"; + + private static final String STATIC_FLAG = "-Static"; + + private static final String CHECKVALUE_FLAG = "-CheckValue"; // only valid w/ + // -static + + private static final String NO_CHECKVALUE_FLAG = "-NoCheckValue"; + + private static final String SMODE_FLAG = "-mode"; // requires arg of bin or + // src + + private static final String DEBUG_FLAG = "-Debug"; + + private static final String FORMATPLAIN_FLAG = "-FormatPlain"; + + private static final String EXCLUDE_JDK_CLASS_FLAG = "-IgnoreJDKClass"; + + private static String[] excludeJdkClasses = { + "java.util.Map", + "java.lang.Object", + "java.io.ByteArrayInputStream", + "java.io.InputStream", + "java.lang.Deprecated", + "java.io.Writer", + "java.io.OutputStream", + "java.util.List", + "java.util.Collection", + "java.lang.instrument.IllegalClassFormatException", + "javax.transaction.xa.XAException", + "java.lang.annotation.Repeatable", + "java.lang.InterruptedException", + "java.lang.CloneNotSupportedException", + "java.lang.Throwable", + "java.lang.Thread", + "java.lang.Enum" + }; + + + @Override + protected String[] createTestArguments(String packageListFile, String mapFile, + String signatureRepositoryDir, String packageOrClassUnderTest, + String classpath, boolean bStaticMode) throws Exception { + + SignatureFileInfo info = getSigFileInfo(packageOrClassUnderTest, mapFile, + signatureRepositoryDir); + + PackageList packageList = new PackageList(packageListFile); + String[] subPackages = packageList.getSubPackages(packageOrClassUnderTest); + + List command = new ArrayList(); + + if (bStaticMode) { + // static mode allows finer level of constants checking + // -CheckValue says to check the actual const values + logger.log(Logger.Level.TRACE, "Setting static mode flag to allow constant checking."); + command.add(STATIC_FLAG); + command.add(CHECKVALUE_FLAG); + + // specifying "-mode src" allows stricter 2 way verification of constant + // vals + // (note that using "-mode bin" mode is less strict) + command.add(SMODE_FLAG); + // command.add("bin"); + command.add("src"); + } else { + logger.log(Logger.Level.TRACE, "Not Setting static mode flag to allow constant checking."); + } + + // if (TestUtil.harnessDebug) { + // command.add(DEBUG_FLAG); + // } + command.add("-Verbose"); + + command.add(FILENAME_FLAG); + command.add(info.getFile()); + + command.add(CLASSPATH_FLAG); + command.add(classpath); + + command.add(PACKAGE_FLAG); + command.add(packageOrClassUnderTest); + + for (int i = 0; i < subPackages.length; i++) { + command.add(EXCLUDE_FLAG); + command.add(subPackages[i]); + } + + for(String jdkClassName:excludeJdkClasses) { + command.add(EXCLUDE_JDK_CLASS_FLAG); + command.add(jdkClassName); + } + + + command.add(API_VERSION_FLAG); + command.add(info.getVersion()); + + return ((String[]) command.toArray(new String[command.size()])); + + } // END createTestArguments + + @Override + protected boolean runSignatureTest(String packageOrClassName, + String[] testArguments) throws Exception { + + Class sigTestClass = Class + .forName("com.sun.tdk.signaturetest.SignatureTest"); + Object sigTestInstance = sigTestClass.newInstance(); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + // do some logging to help with troubleshooting + logger.log(Logger.Level.TRACE, + "\nCalling: com.sun.tdk.signaturetest.SignatureTest() with following args:"); + for (int ii = 0; ii < testArguments.length; ii++) { + logger.log(Logger.Level.TRACE, " testArguments[" + ii + "] = " + testArguments[ii]); + } + + @SuppressWarnings("unchecked") + Method runMethod = sigTestClass.getDeclaredMethod("run", + new Class[] { String[].class, PrintWriter.class, PrintWriter.class }); + runMethod.invoke(sigTestInstance, + new Object[] { testArguments, new PrintWriter(output, true), null }); + + String rawMessages = output.toString(); + + // currently, there is no way to determine if there are error msgs in + // the rawmessages, so we will always dump this and call it a status. + logger.log(Logger.Level.INFO, "********** Status Report '" + packageOrClassName + "' **********\n"); + logger.log(Logger.Level.INFO, rawMessages); + + return sigTestInstance.toString().substring(7).startsWith("Passed."); + } // END runSignatureTest + + +} diff --git a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestResult.java b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestResult.java new file mode 100644 index 0000000000..d5fd0e17e7 --- /dev/null +++ b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SigTestResult.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2007, 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.ts.tests.signaturetest.el; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class SigTestResult implements Serializable { + + private static final String NL = System.getProperty("line.separator", "\n"); + + private List failedPkgs = new ArrayList(); + + private List passedPkgs = new ArrayList(); + + private List failedClasses = new ArrayList(); + + private List passedClasses = new ArrayList(); + + // ---------------------------------------------------------- Public Methods + + public synchronized boolean passed() { + + return (failedPkgs.size() == 0 && failedClasses.size() == 0); + + } // end passed + + public synchronized void addFailedPkg(String pkg) { + + failedPkgs.add(pkg); + + } // END addFailedPkg + + public synchronized void addPassedPkg(String pkg) { + + passedPkgs.add(pkg); + + } // END addPassedPkg + + public synchronized void addFailedClass(String className) { + + failedClasses.add(className); + + } // END addFailedClass + + public synchronized void addPassedClass(String className) { + + passedClasses.add(className); + + } // END addPassedClass + + public String toString() { + + String delim = "******************************************************" + + NL; + if (!pkgsTested() && !classesTested()) { + return (delim + "******** No packages or classes were tested **********" + + NL + delim); + } + StringBuffer buf = new StringBuffer(); + buf.append(delim); + buf.append(delim); + if (passed()) { + buf.append("All package signatures passed.").append(NL); + } else { + buf.append("Some signatures failed.").append(NL); + if (failedPkgs.size() > 0) { + buf.append("\tFailed packages listed below: ").append(NL); + formatList(failedPkgs, buf); + } + if (failedClasses.size() > 0) { + buf.append("\tFailed classes listed below: ").append(NL); + formatList(failedClasses, buf); + } + } + if (passedPkgs.size() > 0) { + buf.append("\tPassed packages listed below: ").append(NL); + formatList(passedPkgs, buf); + } + if (passedClasses.size() > 0) { + buf.append("\tPassed classes listed below: ").append(NL); + formatList(passedClasses, buf); + } + buf.append("\t"); + buf.append(delim); + buf.append(delim); + return buf.toString(); + + } // END toString + + // --------------------------------------------------------- Private Methods + + private synchronized void formatList(List list, StringBuffer buf) { + + synchronized (this) { + for (int i = 0; i < list.size(); i++) { + String pkg = (String) (list.get(i)); + buf.append("\t\t").append(pkg).append(NL); + } + } + + } // END formatList + + private synchronized boolean pkgsTested() { + + return (failedPkgs.size() != 0 || passedPkgs.size() != 0); + + } // END pkgsTested + + private synchronized boolean classesTested() { + + return (failedClasses.size() != 0 || passedClasses.size() != 0); + + } // END classesTested + +} // end class SigTestResult diff --git a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SignatureTestDriver.java b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SignatureTestDriver.java new file mode 100644 index 0000000000..788a0808f6 --- /dev/null +++ b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SignatureTestDriver.java @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2007, 2024 Oracle and/or its affiliates and others. + * All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.ts.tests.signaturetest.el; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Properties; + +import java.lang.System.Logger; + +/** + * Allows the sigtest framework to be extended using different signature test + * implementations (e.g. ApiCheck, or SigTest) + */ +public abstract class SignatureTestDriver { + + private static final Logger logger = System.getLogger(SignatureTestDriver.class.getName()); + + private static final String SIG_FILE_EXT = ".sig"; + + private static final String SIG_FILE_VER_SEP = "_"; + + // ---------------------------------------------------------- Public Methods + + /** + *

+ * Execute the signature test. By default, this method passes the result of + * {@link #createTestArguments(String, String, String, String, String)} and + * passes the result to {@link #runSignatureTest(String, String[])}. + * + * @param packageListFile + * - file containing the packages/classes that are to be verified + * @param mapFile + * sig-test.map file + * @param signatureRepositoryDir + * directory containing the recorded signatures + * @param packagesUnderTest + * packages, defined by the test client, that should be tested + * @param classesUnderTest + * classes, defined by the test client, that should be tested + * @param classpath + * The location of the API being verified. Normally the checked API + * will be available in the test environment and testClasspath will + * be null. In some rare cases the tested API may not be part of the + * test environment and will have to specified using this parameter. + * + * @return a {@link SigTestResult} containing the result of the test execution + */ + public SigTestResult executeSigTest(String packageListFile, String mapFile, + String signatureRepositoryDir, String[] packagesUnderTest, + String[] classesUnderTest, String classpath) + throws Exception { + + SigTestResult result = new SigTestResult(); + + if (packagesUnderTest != null && packagesUnderTest.length > 0) { + logger.log(Logger.Level.INFO, "********** BEGIN PACKAGE LEVEL SIGNATURE " + + "VALIDATION **********\n\n"); + for (int i = 0; i < packagesUnderTest.length; i++) { + + String packageName = packagesUnderTest[i]; + + logger.log(Logger.Level.INFO, "********** BEGIN VALIDATE PACKAGE '" + + packagesUnderTest[i] + "' **********\n"); + + logger.log(Logger.Level.INFO, + "********** VALIDATE IN STATIC MODE - TO CHECK CONSANT VALUES ****"); + logger.log(Logger.Level.INFO, "Static mode supports checks of static constants values "); + + String[] args = createTestArguments(packageListFile, mapFile, + signatureRepositoryDir, packageName, classpath, true); + dumpTestArguments(args); + + if (runSignatureTest(packageName, args)) { + logger.log(Logger.Level.INFO, "********** Package '" + packageName + + "' - PASSED (STATIC MODE) **********"); + result.addPassedPkg(packageName + "(static mode)"); + } else { + result.addFailedPkg(packageName + "(static mode)"); + logger.log(Logger.Level.INFO, "********** Package '" + packageName + + "' - FAILED (STATIC MODE) **********"); + } + + logger.log(Logger.Level.INFO, "\n\n"); + logger.log(Logger.Level.INFO, "********** VALIDATE IN REFLECTIVE MODE ****"); + logger.log(Logger.Level.INFO, + "Reflective mode supports verification within containers (ie ejb, servlet, etc)"); + + String[] args2 = createTestArguments(packageListFile, mapFile, + signatureRepositoryDir, packageName, classpath, false); + dumpTestArguments(args2); + + if (runSignatureTest(packageName, args2)) { + logger.log(Logger.Level.INFO, "********** Package '" + packageName + + "' - PASSED (REFLECTION MODE) **********"); + result.addPassedPkg(packageName + "(reflection mode)"); + } else { + result.addFailedPkg(packageName + "(reflection mode)"); + logger.log(Logger.Level.INFO, "********** Package '" + packageName + + "' - FAILED (REFLECTION MODE) **********"); + } + + logger.log(Logger.Level.INFO, "********** END VALIDATE PACKAGE '" + + packagesUnderTest[i] + "' **********\n"); + + logger.log(Logger.Level.INFO, "\n"); + logger.log(Logger.Level.INFO, "\n"); + + } + } + + if (classesUnderTest != null && classesUnderTest.length > 0) { + logger.log(Logger.Level.INFO, "********** BEGIN CLASS LEVEL SIGNATURE " + + "VALIDATION **********\n\n"); + + for (int i = 0; i < classesUnderTest.length; i++) { + + String className = classesUnderTest[i]; + + logger.log(Logger.Level.INFO, "********** BEGIN VALIDATE CLASS '" + + classesUnderTest[i] + "' **********\n"); + + logger.log(Logger.Level.INFO, + "********** VALIDATE IN STATIC MODE - TO CHECK CONSANT VALUES ****"); + logger.log(Logger.Level.INFO, "Static mode supports checks of static constants values "); + + String[] args = createTestArguments(packageListFile, mapFile, + signatureRepositoryDir, className, classpath, true); + dumpTestArguments(args); + + if (runSignatureTest(className, args)) { + logger.log(Logger.Level.INFO, "********** Class '" + className + + "' - PASSED (STATIC MODE) **********"); + result.addPassedClass(className + "(static mode)"); + } else { + logger.log(Logger.Level.INFO, "********** Class '" + className + + "' - FAILED (STATIC MODE) **********"); + result.addFailedClass(className + "(static mode)"); + } + + logger.log(Logger.Level.INFO, "\n\n"); + logger.log(Logger.Level.INFO, "********** VALIDATE IN REFLECTIVE MODE ****"); + logger.log(Logger.Level.INFO, + "Reflective mode supports verification within containers (ie ejb, servlet, etc)"); + + String[] args2 = createTestArguments(packageListFile, mapFile, + signatureRepositoryDir, className, classpath, false); + dumpTestArguments(args2); + + if (runSignatureTest(className, args2)) { + logger.log(Logger.Level.INFO, "********** Class '" + className + + "' - PASSED (REFLECTION MODE) **********"); + result.addPassedClass(className + "(reflection mode)"); + } else { + logger.log(Logger.Level.INFO, "********** Class '" + className + + "' - FAILED (REFLECTION MODE) **********"); + result.addFailedClass(className + "(reflection mode)"); + } + + logger.log(Logger.Level.INFO, "********** END VALIDATE CLASS '" + classesUnderTest[i] + + "' **********\n"); + + logger.log(Logger.Level.INFO, "\n"); + logger.log(Logger.Level.INFO, "\n"); + + } + } + + return result; + + } // END executeSigTest + + // ------------------------------------------------------- Protected Methods + + /** + * Using a common set of information, create arguments that are appropriate to + * be used with the underlying signature test framework. + * + * @param packageListFile + * - file containing the packages/classes that are to be verified + * @param mapFile + * sig-test.map file + * @param signatureRepositoryDir + * directory containing the recorded signatures + * @param packageOrClassUnderTest + * the class or package + * @param classpath + * The location of the API being verified. Normally the checked API + * will be available in the test environment and testClasspath will + * be null. In some rare cases the tested API may not be part of the + * test environment and will have to specified using this parameter. + */ + protected abstract String[] createTestArguments(String packageListFile, + String mapFile, String signatureRepositoryDir, + String packageOrClassUnderTest, String classpath, boolean bStaticMode) + throws Exception; + + /** + * Invoke the underlying signature test framework for the specified package or + * class. + * + * @param packageOrClassName + * the package or class to be validated + * @param testArguments + * the arguments necessary to invoke the signature test framework + * + * @return true if the test passed, otherwise false + */ + protected abstract boolean runSignatureTest(String packageOrClassName, + String[] testArguments) throws Exception; + + + /** + * Loads the specified file into a Properties object provided the specified + * file exists and is a regular file. The call to new FileInputStream verifies + * that the specfied file is a regular file and exists. + * + * @param mapFile + * the path and name of the map file to be loaded + * + * @return Properties The Properties object initialized with the contents of + * the specified file + * + * @throws java.io.IOException + * If the specified map file does not exist or is not a regular + * file, can also be thrown if there is an error creating an input + * stream from the specified file. + */ + public Properties loadMapFile(String mapFile) + throws IOException, FileNotFoundException { + + FileInputStream in = null; + try { + File map = new File(mapFile); + Properties props = new Properties(); + in = new FileInputStream(map); + props.load(in); + return props; + } finally { + try { + if (in != null) { + in.close(); + } + } catch (Throwable t) { + // do nothing + } + } + + } // END loadMapFile + + /** + * This method will attempt to build a fully-qualified filename in the format + * of respositoryDir + baseName + + * .sig_ + version. + * + * @param baseName + * the base portion of the signature filename + * @param repositoryDir + * the directory in which the signatures are stored + * @param version + * the version of the signature file + * @throws FileNotFoundException + * if the file cannot be validated as existing and is in fact a file + * @return a valid, fully qualified filename, appropriate for the system the + * test is being run on + */ + protected String getSigFileName(String baseName, String repositoryDir, + String version) throws FileNotFoundException { + + String sigFile; + if (repositoryDir.endsWith(File.separator)) { + sigFile = repositoryDir + baseName + SIG_FILE_EXT + SIG_FILE_VER_SEP + + version; + } else { + sigFile = repositoryDir + File.separator + baseName + SIG_FILE_EXT + + SIG_FILE_VER_SEP + version; + } + + File testFile = new File(sigFile); + + if (!testFile.exists() && !testFile.isFile()) { + throw new FileNotFoundException( + "Signature file \"" + sigFile + "\" does not exist."); + } + + // we are actually requiring this normalizeFileName call to get + // things working on Windows. Without this, if we just return the + // testFile; we will fail on windows. (Solaris works either way) + // IMPORTANT UPDATE!! (4/5/2011) + // in sigtest 2.2: they stopped supporting the normalized version which + // created a string filename = + // "file://com/sun/ts/tests/signaturetest/foo.sig" + // so now use file path and name only. + // return normalizeFileName(testFile); + return testFile.toString(); + + } // END getSigFileName + + + /** + * Returns the name and path to the signature file that contains the specified + * package's signatures. + * + * @param packageName + * The package under test + * @param mapFile + * The name of the file that maps package names to versions + * @param repositoryDir + * The directory that conatisn all signature files + * + * @return String The path and name of the siganture file that contains the + * specified package's signatures + * + * @throws Exception + * if the determined signature file is not a regular file or does + * not exist + */ + protected SignatureFileInfo getSigFileInfo(String packageName, String mapFile, + String repositoryDir) throws Exception { + + String originalPackage = packageName; + String name = null; + String version = null; + Properties props = loadMapFile(mapFile); + + while (true) { + boolean packageFound = false; + for (Enumeration e = props.propertyNames(); e.hasMoreElements();) { + name = (String) (e.nextElement()); + if (name.equals(packageName)) { + version = props.getProperty(name); + packageFound = true; + break; + } // end if + } // end for + + if (packageFound) { + break; + } + + /* + * If we get here we did not find a package name in the properties file + * that matches the package name under test. So we look for a package name + * in the properties file that could be the parent package for the package + * under test. We do this by removing the specified packages last package + * name section. So jakarta.ejb.spi would become jakarta.ejb + */ + int index = packageName.lastIndexOf("."); + if (index <= 0) { + throw new Exception("Package \"" + originalPackage + + "\" not specified in mapping file \"" + mapFile + "\"."); + } + packageName = packageName.substring(0, index); + } // end while + + /* Return the expected name of the signature file */ + + return new SignatureFileInfo(getSigFileName(name, repositoryDir, version), + version); + + } // END getSigFileInfo + + // --------------------------------------------------------- Private Methods + + /** + * Prints the specified list of parameters to the message log. Used for + * debugging purposes only. + * + * @param params + * The list of parameters to dump. + */ + private static void dumpTestArguments(String[] params) { + + if (params != null && params.length > 0) { + logger.log(Logger.Level.TRACE, "----------------- BEGIN SIG PARAM DUMP -----------------"); + for (int i = 0; i < params.length; i++) { + logger.log(Logger.Level.TRACE, " Param[" + i + "]: " + params[i]); + } + logger.log(Logger.Level.TRACE, "------------------ END SIG PARAM DUMP ------------------"); + } + + } // END dumpTestArguments + + // ----------------------------------------------------------- Inner Classes + + /** + * A simple data structure containing the fully qualified path to the + * signature file as well as the version being tested. + */ + protected static class SignatureFileInfo { + + private String file; + + private String version; + + // -------------------------------------------------------- Constructors + + public SignatureFileInfo(String file, String version) { + + if (file == null) { + throw new IllegalArgumentException("'file' argument cannot be null"); + } + + if (version == null) { + throw new IllegalArgumentException("'version' argument cannot be null"); + } + + this.file = file; + this.version = version; + + } // END SignatureFileInfo + + // ------------------------------------------------------ Public Methods + + public String getFile() { + + return file; + + } // END getFileIncludingPath + + public String getVersion() { + + return version; + + } // END getVersion + + } + +} // END SigTestDriver diff --git a/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SignatureTestDriverFactory.java b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SignatureTestDriverFactory.java new file mode 100644 index 0000000000..aa22502de7 --- /dev/null +++ b/el/src/main/java/com/sun/ts/tests/el/signaturetest/el/SignatureTestDriverFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2007, 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.ts.tests.signaturetest.el; + +/** + *

+ * Factory to obtain SignatureTestDriver implementations. + *

+ */ +public class SignatureTestDriverFactory { + + /** + *

+ * Identifier for the driver that uses the Signature Test framwork for + * signature validation. + *

+ */ + public static final String SIG_TEST = "sigtest"; + + // ------------------------------------------------------------ Constructors + + // Access via factory method + private SignatureTestDriverFactory() { + } // END SignatureTestDriverFactory + + // ---------------------------------------------------------- Public Methods + + /** + *

+ * Obtain a {@link SignatureTestDriver} instance based on the + * type argument. + * + * @param type + * the driver type to create + * @return a {@link SignatureTestDriver} implementation + */ + public static SignatureTestDriver getInstance(String type) { + + if (type == null || type.length() == 0) { + throw new IllegalArgumentException("Type was null or empty"); + } + + if (SIG_TEST.equals(type)) { + return new SigTestDriver(); + } else { + throw new IllegalArgumentException("Unknown Type: '" + type + '\''); + } + + } // END getInstance + +} // END SignatureTestDriverFactory