SignatureTestDriver.java
/*
* Copyright (c) 2021, 2022 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 ee.jakarta.tck.jsonp.signaturetest;
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.util.logging.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 = Logger.getLogger(SignatureTestDriver.class.getName());
private static final String SIG_FILE_EXT = ".sig";
private static final String SIG_FILE_VER_SEP = "_";
// ---------------------------------------------------------- Public Methods
/**
* Implementation of the getPackageFile method defined in both the SigTest and
* SigTestEE class.
*/
public String getPackageFileImpl(String binDir) {
String thePkgListFile = "sig-test-pkg-list.txt";
LOGGER.info(
"Using the following as the SigTest Package file: " + thePkgListFile);
String theFile = binDir + File.separator + thePkgListFile;
File ff = new File(theFile);
if (!ff.exists()) {
// we could not find the map file that coresponded to our SE version so
// lets
// try to default to use the sig-test-pkg-list.txt
LOGGER.warning("The SigTest Package file does not exist: " + thePkgListFile);
theFile = binDir + File.separator + "sig-test-pkg-list.txt";
File ff2 = new File(theFile);
if (!ff2.exists()) {
LOGGER.warning("The Default SigTest Package file does not exist either: "
+ theFile);
} else {
LOGGER.info("Defaulting to using SigTest Package file: " + theFile);
}
}
return (theFile);
} // END getPackageFileImpl
/**
* Implementation of the getMapFile method defined in both the SigTest and
* SigTestEE class.
*/
public String getMapFileImpl(String binDir) {
String theMapFile = "sig-test.map";
LOGGER.info("Using the following as the sig-Test map file: " + theMapFile);
String theFile = binDir + File.separator + theMapFile;
File ff = new File(theFile);
if (!ff.exists()) {
// we could not find the map file that coresponded to our SE version so
// lets
// try to default to use the sig-test.map
LOGGER.warning("The SigTest Map file does not exist: " + theMapFile);
theFile = binDir + File.separator + "sig-test.map";
File ff2 = new File(theFile);
if (!ff2.exists()) {
LOGGER.warning("The SigTest Map file does not exist either: " + theFile);
} else {
LOGGER.info("Defaulting to using SigTest Map file: " + theFile);
}
}
return (theFile);
} // END getMapFileImpl
/**
* Returns true if the passed in version matches the current Java version
* being used.
*
*/
public Boolean isJavaSEVersion(String ver) {
String strOSVersion = System.getProperty("java.version");
if (strOSVersion.startsWith(ver)) {
return true;
} else {
return false;
}
}
/**
* Implementation of the getRepositoryDir method defined in both the SigTest
* and SigTestEE class.
*/
public String getRepositoryDirImpl(String tsHome) {
return (tsHome + File.separator + "src" + File.separator + "com"
+ File.separator + "sun" + File.separator + "ts" + File.separator
+ "tests" + File.separator + "signaturetest" + File.separator
+ "signature-repository" + File.separator);
} // END getRepositoryDirImpl
/**
* Implementation of the cleanup method defined in both the SigTest and
* SigTestEE class.
*/
public void cleanupImpl() throws Exception {
try {
LOGGER.info("cleanup");
} catch (Exception e) {
LOGGER.warning("Exception in cleanup method" + e);
throw e;
}
} // END cleanupImpl
/**
* <p>
* 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.
* @param unaccountedTechPkgs
* packages that should not exist within the technology under test.
* These will be searched for and if found, will be flagged as error
* since they were not explicitly declared as being under test. Their
* existence requires explicit testing.
*
* @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,
ArrayList<String> unaccountedTechPkgs, String optionalPkgToIgnore)
throws Exception {
SigTestResult result = new SigTestResult();
LOGGER.info("optionalPkgToIgnore = " + optionalPkgToIgnore);
String[] arrayOptionalPkgsToIgnore = null;
if (optionalPkgToIgnore != null) {
arrayOptionalPkgsToIgnore = optionalPkgToIgnore.split(",");
}
if (packagesUnderTest != null && packagesUnderTest.length > 0) {
LOGGER.info("********** BEGIN PACKAGE LEVEL SIGNATURE "
+ "VALIDATION **********\n\n");
for (int i = 0; i < packagesUnderTest.length; i++) {
String packageName = packagesUnderTest[i];
LOGGER.info("********** BEGIN VALIDATE PACKAGE '"
+ packagesUnderTest[i] + "' **********\n");
LOGGER.info(
"********** VALIDATE IN STATIC MODE - TO CHECK CONSANT VALUES ****");
LOGGER.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.info("********** Package '" + packageName
+ "' - PASSED (STATIC MODE) **********");
result.addPassedPkg(packageName + "(static mode)");
} else {
result.addFailedPkg(packageName + "(static mode)");
LOGGER.info("********** Package '" + packageName
+ "' - FAILED (STATIC MODE) **********");
}
LOGGER.info("\n\n");
LOGGER.info("********** VALIDATE IN REFLECTIVE MODE ****");
LOGGER.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.info("********** Package '" + packageName
+ "' - PASSED (REFLECTION MODE) **********");
result.addPassedPkg(packageName + "(reflection mode)");
} else {
result.addFailedPkg(packageName + "(reflection mode)");
LOGGER.info("********** Package '" + packageName
+ "' - FAILED (REFLECTION MODE) **********");
}
LOGGER.info("********** END VALIDATE PACKAGE '"
+ packagesUnderTest[i] + "' **********\n");
LOGGER.info("\n");
LOGGER.info("\n");
}
}
if (classesUnderTest != null && classesUnderTest.length > 0) {
LOGGER.info("********** BEGIN CLASS LEVEL SIGNATURE "
+ "VALIDATION **********\n\n");
for (int i = 0; i < classesUnderTest.length; i++) {
String className = classesUnderTest[i];
LOGGER.info("********** BEGIN VALIDATE CLASS '"
+ classesUnderTest[i] + "' **********\n");
LOGGER.info(
"********** VALIDATE IN STATIC MODE - TO CHECK CONSANT VALUES ****");
LOGGER.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.info("********** Class '" + className
+ "' - PASSED (STATIC MODE) **********");
result.addPassedClass(className + "(static mode)");
} else {
LOGGER.info("********** Class '" + className
+ "' - FAILED (STATIC MODE) **********");
result.addFailedClass(className + "(static mode)");
}
LOGGER.info("\n\n");
LOGGER.info("********** VALIDATE IN REFLECTIVE MODE ****");
LOGGER.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.info("********** Class '" + className
+ "' - PASSED (REFLECTION MODE) **********");
result.addPassedClass(className + "(reflection mode)");
} else {
LOGGER.info("********** Class '" + className
+ "' - FAILED (REFLECTION MODE) **********");
result.addFailedClass(className + "(reflection mode)");
}
LOGGER.info("********** END VALIDATE CLASS '" + classesUnderTest[i]
+ "' **********\n");
LOGGER.info("\n");
LOGGER.info("\n");
}
}
/*
* The following will check if there are Optional Technologies being
* implemented but not explicitly defined thru (ts.jte) javaee.level
* property. This is a problem because if an optional technolgy is defined
* (either whole or partially) than the TCK tests (and sig tests) for those
* Optional Technology(s) MUST be run according to related specs.
*/
if (unaccountedTechPkgs != null) {
for (int ii = 0; ii < unaccountedTechPkgs.size(); ii++) {
// 'unaccountedTechPkgs' are t hose packages which do not beling to
// base technology nor one of the *declared* optionalal technologies.
// 'unaccountedTechPkgs' refers to packages for Optional Technologies
// which were not defined thru (ts.jte) javaee.level property.
// So, make sure there are no whole or partial implementations of
// undeclared optional technologies in the implementation
String packageName = unaccountedTechPkgs.get(ii);
// this is a special case exception to our validation of Optional
// Technologies. Normally any partial technology implementations
// would be a compatibility failure. HOWEVER, EE 7 Spec (see section
// EE 6.1.2 of the Platform spec in the footnote on p. 156.)
// requires us to add special handling to avoid testing 'certain' pkgs
// within an optional technology.
if (isIgnorePackageUnderTest(packageName, arrayOptionalPkgsToIgnore)) {
LOGGER.info(
"Ignoring special optional technology package: " + packageName);
continue;
}
LOGGER.info("\n\n");
LOGGER.info(
"********** CHECK IF OPTIONAL TECHNOLOGIES EXIST IN REFLECTIVE MODE ****");
LOGGER.info(
"Reflective mode supports verification within containers (ie ejb, servlet, etc)");
String[] args3 = createTestArguments(packageListFile, mapFile,
signatureRepositoryDir, packageName, classpath, false);
dumpTestArguments(args3);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - -
// NOTE: this is the opposite of above in that *if* we find that an
// undeclared
// optional technology package exists - then we want to raise a red
// flag.
// The user would have to either remove the technology from the impl if
// they do not want to include it in their impl -OR- they must
// explicitly
// set javaee.level (in ts.jte) to include that Optional Technology AND
// after setting this property, they have to pass all related TCK tests.
if (runPackageSearch(packageName, args3)) {
// if this passed we have an issue because it should not exist - thus
// should NOT pass.
LOGGER.info("********** Package '" + packageName
+ "' - WAS FOUND BUT SHOULD NOT BE (REFLECTION MODE) **********");
String err = "ERROR: An area of concern has been identified. ";
err += "You must run sigtests with (ts.jte) javaee.level set to ";
err += "include all optional technology keywords. Whole and/or ";
err += "partial implementations of Optional Technologies ";
err += "must be implemented according to the specs AND must pass ";
err += "all related TCK tests. To properly pass the ";
err += "signature tests - you must identify all Optional Technology ";
err += "areas (via javaee.level) that you wish to pass signature tests for.";
LOGGER.info(err);
result.addFailedPkg(packageName
+ " (Undeclared Optional Technology package found in reflection mode)");
} else {
LOGGER.info("********** Undeclared Optional Technology package '"
+ packageName + "' - PASSED (REFLECTION MODE) **********");
}
}
}
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 <code>true</code> if the test passed, otherwise <code>false</code>
*/
protected abstract boolean runSignatureTest(String packageOrClassName,
String[] testArguments) throws Exception;
/**
* This checks if a class exists or not within the impl.
*
* @param packageOrClassName
* the package or class to be validated
*
* @return <code>true</code> if the package was found to exist, otherwise
* <code>false</code>
*/
protected abstract boolean runPackageSearch(String packageOrClassName,
String[] testArguments) throws Exception;
/**
* This method checks whether JTA API jar contains classes from
* javax.transaction.xa package
*
* @param classpath
* the classpath, pointing JTA API jar
* @param repositoryDir
* the directory containing an empty signature file
*
* @return <code>true</code> if the package javax.transaction.xa is not
* found in the JTA API jar, otherwise <code>false</code>
*/
protected abstract boolean verifyJTAJarForNoXA(String classpath,
String repositoryDir) 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 <code>respositoryDir</code> + </code>baseName</code> +
* <code>.sig_</code> + </code>version</code>.
*
* @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
protected abstract String normalizeFileName(File f);
/**
* 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
/*
* This returns true is the passed in packageName matches one of the packages
* that are listed in the arrayOptionalPkgsToIgnore. arrayOptionalPkgsToIgnore
* is ultimately defined in the ts.jte property
* 'optional.tech.packages.to.ignore' If one of the entries in
* arrayOptionalPkgsToIgnore matches the packageName then that means we return
* TRUE to indicate we should ignore and NOT TEST that particular package.
*/
private static boolean isIgnorePackageUnderTest(String packageName,
String[] arrayOptionalPkgsToIgnore) {
// if anything is null - consider no match
if ((packageName == null) || (arrayOptionalPkgsToIgnore == null)) {
return false;
}
for (int ii = 0; ii < arrayOptionalPkgsToIgnore.length; ii++) {
if (packageName.equals(arrayOptionalPkgsToIgnore[ii])) {
// we found a match -
return true;
}
}
return false;
}
/**
* 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.fine("----------------- BEGIN SIG PARAM DUMP -----------------");
for (int i = 0; i < params.length; i++) {
LOGGER.fine(" Param[" + i + "]: " + params[i]);
}
LOGGER.fine("------------------ 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