blob: 65599e4cababd22a5886579c9f9feecee0e094c8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.core.tests.util;
import org.eclipse.jdt.core.tests.runtime.*;
import java.io.*;
import java.net.*;
/**
* Verifies that the .class files resulting from a compilation can be loaded
* in a VM and that they can be run.
*/
public class TestVerifier {
public String failureReason;
boolean reuseVM = true;
String[] classpathCache;
LocalVirtualMachine vm;
StringBuffer outputBuffer;
StringBuffer errorBuffer;
Socket socket;
public TestVerifier(boolean reuseVM) {
this.reuseVM = reuseVM;
}
private boolean checkBuffers(String errorString, String outputString, String sourceFileName, String expectedSuccessOutputString) {
if (errorString.length() > 0) {
this.failureReason =
"Unexpected target error running resulting class file for "
+ sourceFileName
+ ":\n"
+ errorString;
return false;
}
String platformIndependantOutputString = Util.convertToIndependantLineDelimiter(outputString);
if (expectedSuccessOutputString != null && (platformIndependantOutputString.indexOf(expectedSuccessOutputString) == -1
|| expectedSuccessOutputString.length() == 0 && platformIndependantOutputString.length() != 0)) {
System.out.println(Util.displayString(platformIndependantOutputString, 2));
this.failureReason =
"Unexpected output running resulting class file for "
+ sourceFileName
+ ":\n"
+ "--[START]--\n"
+ outputString
+ "---[END]---\n";
return false;
}
return true;
}
private boolean checkBuffersThrowingError(String errorString, String sourceFileName, String expectedSuccessOutputString) {
if (errorString.length() > 0 && errorString.indexOf(expectedSuccessOutputString) != -1) {
return true;
}
this.failureReason =
"Expected error not thrown for "
+ sourceFileName
+ ":\n"
+ expectedSuccessOutputString;
return false;
}
private void compileVerifyTests(String verifierDir) {
String fullyQualifiedName = VerifyTests.class.getName();
int lastDot = fullyQualifiedName.lastIndexOf('.');
String packageName = fullyQualifiedName.substring(0, lastDot);
String simpleName = fullyQualifiedName.substring(lastDot + 1);
String dirName = verifierDir.replace('\\', '/') + "/" + packageName.replace('.', '/');
File dir = new File(dirName.replace('/', File.separatorChar));
if (!dir.exists() && !dir.mkdirs()) {
System.out.println("Could not create " + dir);
return;
}
String fileName = dir + File.separator + simpleName + ".java";
Util.writeToFile(this.getVerifyTestsCode(), fileName);
org.eclipse.jdt.internal.compiler.batch.Main.compile("\"" + fileName + "\" -d \"" + verifierDir + "\" -classpath \"" + Util.getJavaClassLib() + "\"");
}
public void execute(String className, String[] classpaths) {
this.outputBuffer = new StringBuffer();
this.errorBuffer = new StringBuffer();
launchAndRun(className, classpaths, null, null);
}
protected void finalize() throws Throwable {
this.shutDown();
}
public String getExecutionOutput(){
return outputBuffer.toString();
}
public String getExecutionError(){
return errorBuffer.toString();
}
/**
* Returns the code of the VerifyTests class.
*
* To generate:
* - export VerifyTests to c:/temp/test
* - inspect org.eclipse.jdt.core.tests.util.Util.fileContentToDisplayString("c:/temp/test/org/eclipse/jdt/core/tests/util/VerifyTests.java", 2)
*/
private String getVerifyTestsCode() {
return
"package org.eclipse.jdt.core.tests.util;\n" +
"import java.lang.reflect.*;\n" +
"import java.io.*;\n" +
"import java.net.*;\n" +
"import java.util.*;\n" +
"public class VerifyTests {\n" +
" int portNumber;\n" +
" Socket socket;\n" +
"/**\n" +
" * NOTE: Code copied from junit.util.TestCaseClassLoader.\n" +
" *\n" +
" * A custom class loader which enables the reloading\n" +
" * of classes for each test run. The class loader\n" +
" * can be configured with a list of package paths that\n" +
" * should be excluded from loading. The loading\n" +
" * of these packages is delegated to the system class\n" +
" * loader. They will be shared across test runs.\n" +
" * <p>\n" +
" * The list of excluded package paths is specified in\n" +
" * a properties file \"excluded.properties\" that is located in \n" +
" * the same place as the TestCaseClassLoader class.\n" +
" * <p>\n" +
" * <b>Known limitation:</b> the VerifyClassLoader cannot load classes\n" +
" * from jar files.\n" +
" */\n" +
"public class VerifyClassLoader extends ClassLoader {\n" +
" /** scanned class path */\n" +
" private String[] fPathItems;\n" +
" \n" +
" /** excluded paths */\n" +
" private String[] fExcluded= {};\n" +
" /**\n" +
" * Constructs a VerifyClassLoader. It scans the class path\n" +
" * and the excluded package paths\n" +
" */\n" +
" public VerifyClassLoader() {\n" +
" super();\n" +
" String classPath= System.getProperty(\"java.class.path\");\n" +
" String separator= System.getProperty(\"path.separator\");\n" +
" \n" +
" // first pass: count elements\n" +
" StringTokenizer st= new StringTokenizer(classPath, separator);\n" +
" int i= 0;\n" +
" while (st.hasMoreTokens()) {\n" +
" st.nextToken();\n" +
" i++;\n" +
" }\n" +
" // second pass: split\n" +
" fPathItems= new String[i];\n" +
" st= new StringTokenizer(classPath, separator);\n" +
" i= 0;\n" +
" while (st.hasMoreTokens()) {\n" +
" fPathItems[i++]= st.nextToken();\n" +
" }\n" +
" }\n" +
" public java.net.URL getResource(String name) {\n" +
" return ClassLoader.getSystemResource(name);\n" +
" }\n" +
" public InputStream getResourceAsStream(String name) {\n" +
" return ClassLoader.getSystemResourceAsStream(name);\n" +
" }\n" +
" protected boolean isExcluded(String name) {\n" +
" // exclude the \"java\" packages.\n" +
" // They always need to be excluded so that they are loaded by the system class loader\n" +
" if (name.startsWith(\"java\"))\n" +
" return true;\n" +
" \n" +
" // exclude the user defined package paths\n" +
" for (int i= 0; i < fExcluded.length; i++) {\n" +
" if (name.startsWith(fExcluded[i])) {\n" +
" return true;\n" +
" }\n" +
" }\n" +
" return false; \n" +
" }\n" +
" public synchronized Class loadClass(String name, boolean resolve)\n" +
" throws ClassNotFoundException {\n" +
" \n" +
" Class c= findLoadedClass(name);\n" +
" if (c != null)\n" +
" return c;\n" +
" //\n" +
" // Delegate the loading of excluded classes to the\n" +
" // standard class loader.\n" +
" //\n" +
" if (isExcluded(name)) {\n" +
" try {\n" +
" c= findSystemClass(name);\n" +
" return c;\n" +
" } catch (ClassNotFoundException e) {\n" +
" // keep searching\n" +
" }\n" +
" }\n" +
" if (c == null) {\n" +
" File file= locate(name);\n" +
" if (file == null)\n" +
" throw new ClassNotFoundException();\n" +
" byte data[]= loadClassData(file);\n" +
" c= defineClass(name, data, 0, data.length);\n" +
" }\n" +
" if (resolve) \n" +
" resolveClass(c);\n" +
" return c;\n" +
" }\n" +
" private byte[] loadClassData(File f) throws ClassNotFoundException {\n" +
" try {\n" +
" //System.out.println(\"loading: \"+f.getPath());\n" +
" FileInputStream stream= new FileInputStream(f);\n" +
" \n" +
" try {\n" +
" byte[] b= new byte[stream.available()];\n" +
" stream.read(b);\n" +
" stream.close();\n" +
" return b;\n" +
" }\n" +
" catch (IOException e) {\n" +
" throw new ClassNotFoundException();\n" +
" }\n" +
" }\n" +
" catch (FileNotFoundException e) {\n" +
" throw new ClassNotFoundException();\n" +
" }\n" +
" }\n" +
" /**\n" +
" * Locate the given file.\n" +
" * @return Returns null if file couldn\'t be found.\n" +
" */\n" +
" private File locate(String fileName) { \n" +
" fileName= fileName.replace(\'.\', \'/\')+\".class\";\n" +
" File path= null;\n" +
" \n" +
" if (fileName != null) {\n" +
" for (int i= 0; i < fPathItems.length; i++) {\n" +
" path= new File(fPathItems[i], fileName);\n" +
" if (path.exists())\n" +
" return path;\n" +
" }\n" +
" }\n" +
" return null;\n" +
" }\n" +
"}\n" +
" \n" +
"public void loadAndRun(String className) throws Throwable {\n" +
" //System.out.println(\"Loading \" + className + \"...\");\n" +
" Class testClass = new VerifyClassLoader().loadClass(className);\n" +
" //System.out.println(\"Loaded \" + className);\n" +
" try {\n" +
" Method main = testClass.getMethod(\"main\", new Class[] {String[].class});\n" +
" //System.out.println(\"Running \" + className);\n" +
" main.invoke(null, new Object[] {new String[] {}});\n" +
" //System.out.println(\"Finished running \" + className);\n" +
" } catch (NoSuchMethodException e) {\n" +
" return;\n" +
" } catch (InvocationTargetException e) {\n" +
" throw e.getTargetException();\n" +
" }\n" +
"}\n" +
"public static void main(String[] args) throws IOException {\n" +
" VerifyTests verify = new VerifyTests();\n" +
" verify.portNumber = Integer.parseInt(args[0]);\n" +
" verify.run();\n" +
"}\n" +
"public void run() throws IOException {\n" +
" ServerSocket server = new ServerSocket(this.portNumber);\n" +
" this.socket = server.accept();\n" +
" this.socket.setTcpNoDelay(true);\n" +
" server.close();\n" +
" DataInputStream in = new DataInputStream(this.socket.getInputStream());\n" +
" final DataOutputStream out = new DataOutputStream(this.socket.getOutputStream());\n" +
" while (true) {\n" +
" final String className = in.readUTF();\n" +
" Thread thread = new Thread() {\n" +
" public void run() {\n" +
" try {\n" +
" loadAndRun(className);\n" +
" out.writeBoolean(true);\n" +
" System.err.println(VerifyTests.class.getName());\n" +
" System.out.println(VerifyTests.class.getName());\n" +
" } catch (Throwable e) {\n" +
" e.printStackTrace();\n" +
" try {\n" +
" System.err.println(VerifyTests.class.getName());\n" +
" System.out.println(VerifyTests.class.getName());\n" +
" out.writeBoolean(false);\n" +
" } catch (IOException e1) {\n" +
" }\n" +
" }\n" +
" }\n" +
" };\n" +
" thread.start();\n" +
" }\n" +
"}\n" +
"}\n";
}
private void launchAndRun(String className, String[] classpaths, String[] programArguments, String[] vmArguments) {
// we won't reuse the vm, shut the existing one if running
if (this.vm != null) {
try {
vm.shutDown();
} catch (TargetException e) {
}
}
this.classpathCache = null;
// launch a new one
LocalVMLauncher launcher = LocalVMLauncher.getLauncher();
launcher.setClassPath(classpaths);
launcher.setVMPath(Util.getJREDirectory());
if (vmArguments != null) {
String[] completeVmArguments = new String[vmArguments.length + 1];
System.arraycopy(vmArguments, 0, completeVmArguments, 1, vmArguments.length);
completeVmArguments[0] = "-verify";
launcher.setVMArguments(completeVmArguments);
} else {
launcher.setVMArguments(new String[] {"-verify"});
}
launcher.setProgramClass(className);
launcher.setProgramArguments(programArguments);
Thread outputThread;
Thread errorThread;
try {
this.vm = launcher.launch();
final InputStream input = this.vm.getInputStream();
outputThread = new Thread(new Runnable() {
public void run() {
try {
int c = input.read();
while (c != -1) {
outputBuffer.append((char) c);
c = input.read();
}
} catch(IOException ioEx) {
}
}
});
final InputStream errorStream = this.vm.getErrorStream();
errorThread = new Thread(new Runnable() {
public void run() {
try {
int c = errorStream.read();
while (c != -1) {
errorBuffer.append((char) c);
c = errorStream.read();
}
} catch(IOException ioEx) {
}
}
});
outputThread.start();
errorThread.start();
} catch(TargetException e) {
throw new Error(e.getMessage());
}
// wait for vm to shut down by itself
try {
outputThread.join(5000);
errorThread.join(5000);
} catch (InterruptedException e) {
}
}
private void launchVerifyTestsIfNeeded(String[] classpaths, String[] vmArguments) {
// determine if we can reuse the vm
if (this.vm != null && this.vm.isRunning() && this.classpathCache != null) {
boolean sameClasspaths = true;
for (int i = 0; i < classpaths.length; i++) {
if (!this.classpathCache[i].equals(classpaths[i])) {
sameClasspaths = false;
break;
}
}
if (sameClasspaths) {
return;
}
}
// we could not reuse the vm, shut the existing one if running
if (this.vm != null) {
try {
vm.shutDown();
} catch (TargetException e) {
}
}
this.classpathCache = classpaths;
// launch a new one
LocalVMLauncher launcher = LocalVMLauncher.getLauncher();
int length = classpaths.length;
String[] cp = new String[length + 1];
System.arraycopy(classpaths, 0, cp, 0, length);
String verifierDir = Util.getOutputDirectory() + File.separator + "verifier";
this.compileVerifyTests(verifierDir);
cp[length] = verifierDir;
launcher.setClassPath(cp);
launcher.setVMPath(Util.getJREDirectory());
if (vmArguments != null) {
String[] completeVmArguments = new String[vmArguments.length + 1];
System.arraycopy(vmArguments, 0, completeVmArguments, 1, vmArguments.length);
completeVmArguments[0] = "-verify";
launcher.setVMArguments(completeVmArguments);
} else {
launcher.setVMArguments(new String[] {"-verify"});
}
launcher.setProgramClass(VerifyTests.class.getName());
int portNumber = Util.nextAvailablePortNumber();
launcher.setProgramArguments(new String[] {Integer.toString(portNumber)});
try {
this.vm = launcher.launch();
final InputStream input = this.vm.getInputStream();
Thread outputThread = new Thread(new Runnable() {
public void run() {
try {
int c = input.read();
while (c != -1) {
outputBuffer.append((char) c);
c = input.read();
}
} catch(IOException ioEx) {
}
}
});
final InputStream errorStream = this.vm.getErrorStream();
Thread errorThread = new Thread(new Runnable() {
public void run() {
try {
int c = errorStream.read();
while (c != -1) {
errorBuffer.append((char) c);
c = errorStream.read();
}
} catch(IOException ioEx) {
}
}
});
outputThread.start();
errorThread.start();
} catch(TargetException e) {
throw new Error(e.getMessage());
}
// connect to the vm
this.socket = null;
boolean isVMRunning = false;
do {
try {
this.socket = new Socket("localhost", portNumber);
this.socket.setTcpNoDelay(true);
break;
} catch (UnknownHostException e) {
} catch (IOException e) {
}
if (this.socket == null) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
isVMRunning = vm.isRunning();
}
} while (this.socket == null && isVMRunning);
}
/**
* Loads and runs the given class.
* Return whether no exception was thrown while running the class.
*/
private boolean loadAndRun(String className) {
if (this.socket != null) {
try {
DataOutputStream out = new DataOutputStream(this.socket.getOutputStream());
out.writeUTF(className);
DataInputStream in = new DataInputStream(this.socket.getInputStream());
try {
boolean result = in.readBoolean();
this.waitForFullBuffers();
return result;
} catch (SocketException e) {
// connection was reset because target program has exited
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
public void shutDown() {
// Close the socket first so that the OS resource has a chance to be freed.
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// Wait for the vm to shut down by itself for 2 seconds. If not succesfull, force the shut down.
if (this.vm != null) {
try {
int retry = 0;
while (this.vm.isRunning() && (++retry < 20)) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
if (this.vm.isRunning()) {
this.vm.shutDown();
}
} catch (TargetException e) {
e.printStackTrace();
}
}
}
/**
* Verify that the class files created for the given test file can be loaded by
* a virtual machine.
*/
public boolean verifyClassFiles(String sourceFilePath, String className, String expectedSuccessOutputString, String[] classpaths) {
return verifyClassFiles(sourceFilePath, className, expectedSuccessOutputString, classpaths, null, null);
}
/**
* Verify that the class files created for the given test file can be loaded by
* a virtual machine.
*/
public boolean verifyClassFiles(String sourceFilePath, String className, String expectedSuccessOutputString, String[] classpaths, String[] programArguments, String[] vmArguments) {
this.outputBuffer = new StringBuffer();
this.errorBuffer = new StringBuffer();
if (this.reuseVM && programArguments == null) {
this.launchVerifyTestsIfNeeded(classpaths, vmArguments);
this.loadAndRun(className);
} else {
this.launchAndRun(className, classpaths, programArguments, vmArguments);
}
this.failureReason = null;
return this.checkBuffers(this.errorBuffer.toString(), this.outputBuffer.toString(), sourceFilePath, expectedSuccessOutputString);
}
/**
* Verify that the class files created for the given test file can be loaded and run with an expected error contained
* in the expectedSuccessOutputString string.
*/
public boolean verifyClassFilesThrowingError(String sourceFilePath, String className, String expectedSuccessOutputString, String[] classpaths, String[] programArguments, String[] vmArguments) {
this.outputBuffer = new StringBuffer();
this.errorBuffer = new StringBuffer();
if (this.reuseVM && programArguments == null) {
this.launchVerifyTestsIfNeeded(classpaths, vmArguments);
this.loadAndRun(className);
} else {
this.launchAndRun(className, classpaths, programArguments, vmArguments);
}
this.failureReason = null;
return this.checkBuffersThrowingError(this.errorBuffer.toString(), sourceFilePath, expectedSuccessOutputString);
}
/**
* Wait until there is nothing more to read from the stdout or sterr.
*/
private void waitForFullBuffers() {
String endString = VerifyTests.class.getName();
int count = 50;
int errorEndStringStart = this.errorBuffer.toString().indexOf(endString);
int outputEndStringStart = this.outputBuffer.toString().indexOf(endString);
while (errorEndStringStart == -1 || outputEndStringStart == -1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (--count == 0) return;
errorEndStringStart = this.errorBuffer.toString().indexOf(endString);
outputEndStringStart = this.outputBuffer.toString().indexOf(endString);
}
this.errorBuffer.setLength(errorEndStringStart);
this.outputBuffer.setLength(outputEndStringStart);
}
}