db85090553
Co-authored-by: Sean Mullan <mullan@openjdk.org> Co-authored-by: Alan Bateman <alanb@openjdk.org> Co-authored-by: Weijun Wang <weijun@openjdk.org> Co-authored-by: Aleksei Efimov <aefimov@openjdk.org> Co-authored-by: Brian Burkhalter <bpb@openjdk.org> Co-authored-by: Daniel Fuchs <dfuchs@openjdk.org> Co-authored-by: Harshitha Onkar <honkar@openjdk.org> Co-authored-by: Joe Wang <joehw@openjdk.org> Co-authored-by: Jorn Vernee <jvernee@openjdk.org> Co-authored-by: Justin Lu <jlu@openjdk.org> Co-authored-by: Kevin Walls <kevinw@openjdk.org> Co-authored-by: Lance Andersen <lancea@openjdk.org> Co-authored-by: Naoto Sato <naoto@openjdk.org> Co-authored-by: Roger Riggs <rriggs@openjdk.org> Co-authored-by: Brent Christian <bchristi@openjdk.org> Co-authored-by: Stuart Marks <smarks@openjdk.org> Co-authored-by: Ian Graves <igraves@openjdk.org> Co-authored-by: Phil Race <prr@openjdk.org> Co-authored-by: Erik Gahlin <egahlin@openjdk.org> Co-authored-by: Jaikiran Pai <jpai@openjdk.org> Reviewed-by: kevinw, aivanov, rriggs, lancea, coffeys, dfuchs, ihse, erikj, cjplummer, coleenp, naoto, mchung, prr, weijun, joehw, azvegint, psadhukhan, bchristi, sundar, attila
533 lines
18 KiB
Java
533 lines
18 KiB
Java
/*
|
|
* Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/**
|
|
*
|
|
*
|
|
* @author Adrian Colley
|
|
* @author Laird Dornin
|
|
* @author Peter Jones
|
|
* @author Ann Wollrath
|
|
*
|
|
* The rmi library directory contains a set of simple utiltity classes
|
|
* for use in rmi regression tests.
|
|
*
|
|
* NOTE: The JavaTest group has recommended that regression tests do
|
|
* not make use of packages.
|
|
*/
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.PrintStream;
|
|
import java.net.MalformedURLException;
|
|
import java.net.ServerSocket;
|
|
import java.net.URL;
|
|
import java.rmi.NoSuchObjectException;
|
|
import java.rmi.Remote;
|
|
import java.rmi.RemoteException;
|
|
import java.rmi.registry.LocateRegistry;
|
|
import java.rmi.registry.Registry;
|
|
import java.rmi.server.RemoteRef;
|
|
import java.rmi.server.UnicastRemoteObject;
|
|
import java.util.Enumeration;
|
|
import java.util.Properties;
|
|
|
|
import sun.rmi.registry.RegistryImpl;
|
|
import sun.rmi.server.UnicastServerRef;
|
|
import sun.rmi.transport.Endpoint;
|
|
import sun.rmi.transport.LiveRef;
|
|
import sun.rmi.transport.tcp.TCPEndpoint;
|
|
|
|
/**
|
|
* Class of utility/library methods (i.e. procedures) that assist with
|
|
* the writing and maintainance of rmi regression tests.
|
|
*/
|
|
public class TestLibrary {
|
|
/**
|
|
* IMPORTANT!
|
|
*
|
|
* RMI tests are run concurrently and port conflicts result when a single
|
|
* port number is used by multiple tests. When needing a port, use
|
|
* getUnusedRandomPort() wherever possible. If getUnusedRandomPort() cannot
|
|
* be used, reserve and specify a port to use for your test here. This
|
|
* will ensure there are no port conflicts amongst the RMI tests. The
|
|
* port numbers specified here may also be specified in the respective
|
|
* tests. Do not change the reserved port numbers here without also
|
|
* changing the port numbers in the respective tests.
|
|
*
|
|
* When needing an instance of the RMIRegistry, use
|
|
* createRegistryOnUnusedPort wherever possible to prevent port conflicts.
|
|
*
|
|
* Reserved port range: FIXED_PORT_MIN to FIXED_PORT_MAX (inclusive) for
|
|
* tests which cannot use a random port. If new fixed ports are added below
|
|
* FIXED_PORT_MIN or above FIXED_PORT_MAX, then adjust
|
|
* FIXED_PORT_MIN/MAX appropriately.
|
|
*/
|
|
public final static int FIXED_PORT_MIN = 60001;
|
|
public final static int FIXED_PORT_MAX = 60010;
|
|
public final static int INHERITEDCHANNELNOTSERVERSOCKET_ACTIVATION_PORT = 60003;
|
|
public final static int INHERITEDCHANNELNOTSERVERSOCKET_REGISTRY_PORT = 60004;
|
|
public final static int READTEST_REGISTRY_PORT = 60005;
|
|
private final static int MAX_SERVER_SOCKET_TRIES = 2*(FIXED_PORT_MAX-FIXED_PORT_MIN+1);
|
|
|
|
static void mesg(Object mesg) {
|
|
System.err.println("TEST_LIBRARY: " + mesg.toString());
|
|
}
|
|
|
|
/**
|
|
* Routines that enable rmi tests to fail in a uniformly
|
|
* informative fashion.
|
|
*/
|
|
public static void bomb(String message, Exception e) {
|
|
String testFailed = "TEST FAILED: ";
|
|
|
|
if ((message == null) && (e == null)) {
|
|
testFailed += " No relevant information";
|
|
} else if (e == null) {
|
|
testFailed += message;
|
|
}
|
|
|
|
System.err.println(testFailed);
|
|
if (e != null) {
|
|
System.err.println("Test failed with: " +
|
|
e.getMessage());
|
|
e.printStackTrace(System.err);
|
|
}
|
|
throw new TestFailedException(testFailed, e);
|
|
}
|
|
public static void bomb(String message) {
|
|
bomb(message, null);
|
|
}
|
|
public static void bomb(Exception e) {
|
|
bomb(null, e);
|
|
}
|
|
|
|
/**
|
|
* Helper method to determine if registry has started
|
|
*
|
|
* @param port The port number to check
|
|
* @param msTimeout The amount of milliseconds to spend checking
|
|
*/
|
|
|
|
public static boolean checkIfRegistryRunning(int port, int msTimeout) {
|
|
final long POLLTIME_MS = 100L;
|
|
long stopTime = computeDeadline(System.currentTimeMillis(), msTimeout);
|
|
do {
|
|
try {
|
|
Registry r = LocateRegistry.getRegistry(port);
|
|
String[] s = r.list();
|
|
// no exception. We're now happy that registry is running
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
// problem - not ready ? Try again
|
|
try {
|
|
Thread.sleep(POLLTIME_MS);
|
|
} catch (InterruptedException ie) {
|
|
// not expected
|
|
}
|
|
}
|
|
} while (System.currentTimeMillis() < stopTime);
|
|
return false;
|
|
}
|
|
|
|
public static String getProperty(final String property,
|
|
final String defaultVal) {
|
|
try {
|
|
return java.security.AccessController.doPrivileged(
|
|
new java.security.PrivilegedAction<String>() {
|
|
public String run() {
|
|
return System.getProperty(property, defaultVal);
|
|
}
|
|
});
|
|
} catch (Exception ex) {
|
|
bomb("Exception getting property " + property, ex);
|
|
throw new AssertionError("this should be unreachable");
|
|
}
|
|
}
|
|
|
|
public static double getTimeoutFactor() {
|
|
String prop = getProperty("test.timeout.factor", "1.0");
|
|
double timeoutFactor = 1.0;
|
|
|
|
try {
|
|
timeoutFactor = Double.parseDouble(prop);
|
|
} catch (NumberFormatException ignore) { }
|
|
|
|
return timeoutFactor;
|
|
}
|
|
|
|
/**
|
|
* Computes a deadline from a timestamp and a timeout value.
|
|
* Maximum timeout (before multipliers are applied) is one hour.
|
|
*/
|
|
public static long computeDeadline(long timestamp, long timeout) {
|
|
if (timeout < 0L) {
|
|
throw new IllegalArgumentException("timeout " + timeout + "ms out of range");
|
|
}
|
|
|
|
return timestamp + (long)(timeout * getTimeoutFactor());
|
|
}
|
|
|
|
/**
|
|
* Property mutators
|
|
*/
|
|
public static void setBoolean(String property, boolean value) {
|
|
setProperty(property, (new Boolean(value)).toString());
|
|
}
|
|
public static void setInteger(String property, int value) {
|
|
setProperty(property, Integer.toString(value));
|
|
}
|
|
public static void setProperty(String property, String value) {
|
|
final String prop = property;
|
|
final String val = value;
|
|
java.security.AccessController.doPrivileged(
|
|
new java.security.PrivilegedAction<Void>() {
|
|
public Void run() {
|
|
System.setProperty(prop, val);
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Routines to print out a test's properties environment.
|
|
*/
|
|
public static void printEnvironment() {
|
|
printEnvironment(System.err);
|
|
}
|
|
public static void printEnvironment(PrintStream out) {
|
|
out.println("-------------------Test environment----------" +
|
|
"---------");
|
|
|
|
for(Enumeration<?> keys = System.getProperties().keys();
|
|
keys.hasMoreElements();) {
|
|
|
|
String property = (String) keys.nextElement();
|
|
out.println(property + " = " + getProperty(property, null));
|
|
}
|
|
out.println("---------------------------------------------" +
|
|
"---------");
|
|
}
|
|
|
|
/**
|
|
* Routine that "works-around" a limitation in jtreg.
|
|
* Currently it is not possible for a test to specify that the
|
|
* test harness should build a given source file and install the
|
|
* resulting class in a location that is not accessible from the
|
|
* test's classpath. This method enables a test to move a
|
|
* compiled test class file from the test's class directory into a
|
|
* given "codebase" directory. As a result the test can only
|
|
* access the class file for <code>className</code>if the test loads
|
|
* it from a classloader (e.g. RMIClassLoader).
|
|
*
|
|
* Tests that use this routine must have the following permissions
|
|
* granted to them:
|
|
*
|
|
* getProperty user.dir
|
|
* getProperty etc.
|
|
*/
|
|
public static URL installClassInCodebase(String className,
|
|
String codebase)
|
|
throws MalformedURLException
|
|
{
|
|
return installClassInCodebase(className, codebase, true);
|
|
}
|
|
|
|
public static URL installClassInCodebase(String className,
|
|
String codebase,
|
|
boolean delete)
|
|
throws MalformedURLException
|
|
{
|
|
/*
|
|
* NOTES/LIMITATIONS: The class must not be in a named package,
|
|
* and the codebase must be a relative path (it's created relative
|
|
* to the working directory).
|
|
*/
|
|
String classFileName = className + ".class";
|
|
|
|
/*
|
|
* Specify the file to contain the class definition. Make sure
|
|
* that the codebase directory exists (underneath the working
|
|
* directory).
|
|
*/
|
|
File dstDir = (new File(getProperty("user.dir", "."), codebase));
|
|
|
|
if (!dstDir.exists()) {
|
|
if (!dstDir.mkdir()) {
|
|
throw new RuntimeException(
|
|
"could not create codebase directory");
|
|
}
|
|
}
|
|
File dstFile = new File(dstDir, classFileName);
|
|
|
|
/*
|
|
* Obtain the URL for the codebase.
|
|
*/
|
|
URL codebaseURL = dstDir.toURI().toURL();
|
|
|
|
/*
|
|
* Specify where we will copy the class definition from, if
|
|
* necessary. After the test is built, the class file can be
|
|
* found in the "test.classes" directory.
|
|
*/
|
|
File srcDir = new File(getProperty("test.classes", "."));
|
|
File srcFile = new File(srcDir, classFileName);
|
|
|
|
mesg(srcFile);
|
|
mesg(dstFile);
|
|
|
|
/*
|
|
* If the class definition is not already located at the codebase,
|
|
* copy it there from the test build area.
|
|
*/
|
|
if (!dstFile.exists()) {
|
|
if (!srcFile.exists()) {
|
|
throw new RuntimeException(
|
|
"could not find class file to install in codebase " +
|
|
"(try rebuilding the test): " + srcFile);
|
|
}
|
|
|
|
try {
|
|
copyFile(srcFile, dstFile);
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(
|
|
"could not install class file in codebase");
|
|
}
|
|
|
|
mesg("Installed class \"" + className +
|
|
"\" in codebase " + codebaseURL);
|
|
}
|
|
|
|
/*
|
|
* After the class definition is successfully installed at the
|
|
* codebase, delete it from the test's CLASSPATH, so that it will
|
|
* not be found there first before the codebase is searched.
|
|
*/
|
|
if (srcFile.exists()) {
|
|
if (delete && !srcFile.delete()) {
|
|
throw new RuntimeException(
|
|
"could not delete duplicate class file in CLASSPATH");
|
|
}
|
|
}
|
|
|
|
return codebaseURL;
|
|
}
|
|
|
|
public static void copyFile(File srcFile, File dstFile)
|
|
throws IOException
|
|
{
|
|
FileInputStream src = new FileInputStream(srcFile);
|
|
FileOutputStream dst = new FileOutputStream(dstFile);
|
|
|
|
byte[] buf = new byte[32768];
|
|
while (true) {
|
|
int count = src.read(buf);
|
|
if (count < 0) {
|
|
break;
|
|
}
|
|
dst.write(buf, 0, count);
|
|
}
|
|
|
|
dst.close();
|
|
src.close();
|
|
}
|
|
|
|
/** routine to unexport an object */
|
|
public static void unexport(Remote obj) {
|
|
if (obj != null) {
|
|
try {
|
|
mesg("unexporting object...");
|
|
UnicastRemoteObject.unexportObject(obj, true);
|
|
} catch (NoSuchObjectException munch) {
|
|
} catch (Exception e) {
|
|
e.getMessage();
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an RMI {@link Registry} on a random, un-reserved port.
|
|
*
|
|
* @return an RMI Registry, using a random port.
|
|
* @throws RemoteException if there was a problem creating a Registry.
|
|
*/
|
|
public static Registry createRegistryOnUnusedPort() throws RemoteException {
|
|
return LocateRegistry.createRegistry(getUnusedRandomPort());
|
|
}
|
|
|
|
/**
|
|
* Creates an RMI {@link Registry} on an ephemeral port.
|
|
*
|
|
* @return an RMI Registry
|
|
* @throws RemoteException if there was a problem creating a Registry.
|
|
*/
|
|
public static Registry createRegistryOnEphemeralPort() throws RemoteException {
|
|
return LocateRegistry.createRegistry(0);
|
|
}
|
|
|
|
/**
|
|
* Returns the port number the RMI {@link Registry} is running on.
|
|
*
|
|
* @param registry the registry to find the port of.
|
|
* @return the port number the registry is using.
|
|
* @throws RuntimeException if there was a problem getting the port number.
|
|
*/
|
|
public static int getRegistryPort(Registry registry) {
|
|
int port = -1;
|
|
|
|
try {
|
|
RemoteRef remoteRef = ((RegistryImpl)registry).getRef();
|
|
LiveRef liveRef = ((UnicastServerRef)remoteRef).getLiveRef();
|
|
Endpoint endpoint = liveRef.getChannel().getEndpoint();
|
|
TCPEndpoint tcpEndpoint = (TCPEndpoint) endpoint;
|
|
port = tcpEndpoint.getPort();
|
|
} catch (Exception ex) {
|
|
throw new RuntimeException("Error getting registry port.", ex);
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
/**
|
|
* Returns an unused random port number which is not a reserved port. Will
|
|
* try up to 10 times to get a random port before giving up and throwing a
|
|
* RuntimeException.
|
|
*
|
|
* @return an unused random port number.
|
|
* @throws RuntimeException if there was a problem getting a port.
|
|
*/
|
|
public static int getUnusedRandomPort() {
|
|
int numTries = 0;
|
|
IOException ex = null;
|
|
|
|
while (numTries++ < MAX_SERVER_SOCKET_TRIES) {
|
|
int unusedRandomPort = -1;
|
|
ex = null; //reset
|
|
|
|
try (ServerSocket ss = new ServerSocket(0)) {
|
|
unusedRandomPort = ss.getLocalPort();
|
|
} catch (IOException e) {
|
|
ex = e;
|
|
// temporarily print stack trace here until we find out why
|
|
// tests are failing.
|
|
System.err.println("TestLibrary.getUnusedRandomPort() caught "
|
|
+ "exception on iteration " + numTries
|
|
+ (numTries==MAX_SERVER_SOCKET_TRIES ? " (the final try)."
|
|
: "."));
|
|
ex.printStackTrace();
|
|
}
|
|
|
|
if (unusedRandomPort >= 0) {
|
|
if (isReservedPort(unusedRandomPort)) {
|
|
System.out.println("INFO: On try # " + numTries
|
|
+ (numTries==MAX_SERVER_SOCKET_TRIES ? ", the final try, ": ",")
|
|
+ " ServerSocket(0) returned the reserved port "
|
|
+ unusedRandomPort
|
|
+ " in TestLibrary.getUnusedRandomPort() ");
|
|
} else {
|
|
return unusedRandomPort;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're here, then either an exception was thrown or the port is
|
|
// a reserved port.
|
|
if (ex==null) {
|
|
throw new RuntimeException("Error getting unused random port. The"
|
|
+" last port returned by ServerSocket(0) was a reserved port");
|
|
} else {
|
|
throw new RuntimeException("Error getting unused random port.", ex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines if a port is one of the reserved port numbers.
|
|
*
|
|
* @param port the port to test.
|
|
* @return {@code true} if the port is a reserved port, otherwise
|
|
* {@code false}.
|
|
*/
|
|
public static boolean isReservedPort(int port) {
|
|
return ((port >= FIXED_PORT_MIN) && (port <= FIXED_PORT_MAX) ||
|
|
(port == 1099));
|
|
}
|
|
|
|
/**
|
|
* Method to capture the stack trace of an exception and return it
|
|
* as a string.
|
|
*/
|
|
public String stackTraceToString(Exception e) {
|
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
PrintStream ps = new PrintStream(bos);
|
|
|
|
e.printStackTrace(ps);
|
|
return bos.toString();
|
|
}
|
|
|
|
/** extra properties */
|
|
private static Properties props;
|
|
|
|
/**
|
|
* Returns extra test properties. Looks for the file "../../test.props"
|
|
* and reads it in as a Properties file. Assuming the working directory
|
|
* is "<path>/JTwork/scratch", this will find "<path>/test.props".
|
|
*/
|
|
private static synchronized Properties getExtraProperties() {
|
|
if (props != null) {
|
|
return props;
|
|
}
|
|
props = new Properties();
|
|
File f = new File(".." + File.separator + ".." + File.separator +
|
|
"test.props");
|
|
if (!f.exists()) {
|
|
return props;
|
|
}
|
|
try {
|
|
FileInputStream in = new FileInputStream(f);
|
|
try {
|
|
props.load(in);
|
|
} finally {
|
|
in.close();
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
throw new RuntimeException("extra property setup failed", e);
|
|
}
|
|
return props;
|
|
}
|
|
|
|
/**
|
|
* Returns an extra test property. Looks for the file "../../test.props"
|
|
* and reads it in as a Properties file. Assuming the working directory
|
|
* is "<path>/JTwork/scratch", this will find "<path>/test.props".
|
|
* If the property isn't found, defaultVal is returned.
|
|
*/
|
|
public static String getExtraProperty(String property, String defaultVal) {
|
|
return getExtraProperties().getProperty(property, defaultVal);
|
|
}
|
|
}
|