| /******************************************************************************* |
| * Copyright (c) 2000, 2007 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.wst.jsdt.core.tests.util; |
| |
| import org.eclipse.wst.jsdt.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.trim().length() > 0) { |
| this.failureReason = |
| "Unexpected target error running resulting class file for " |
| + sourceFileName |
| + ":\n" |
| + errorString; |
| return false; |
| } |
| String platformIndependantOutputString = Util.convertToIndependantLineDelimiter(outputString.trim()); |
| if (expectedSuccessOutputString != null && !Util.convertToIndependantLineDelimiter(expectedSuccessOutputString).equals(platformIndependantOutputString)) { |
| 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.wst.jsdt.internal.compiler.batch.Main.compile("\"" + fileName + "\" -d \"" + verifierDir + "\" -classpath \"" + Util.getJavaClassLibsAsString() + "\""); |
| } |
| 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. |
| * |
| * IMPORTANT NOTE: DO NOTE EDIT BUT GENERATE INSTEAD (see below) |
| * |
| * To generate: |
| * - export VerifyTests.java to d:/temp |
| * - inspect org.eclipse.wst.jsdt.core.tests.util.Util.fileContentToDisplayString("d:/temp/VerifyTests.java", 2, true) |
| */ |
| private String getVerifyTestsCode() { |
| return |
| "/*******************************************************************************" + |
| " * Copyright (c) 2000, 2005 IBM Corporation and others." + |
| " * All rights reserved. This program and the accompanying materials" + |
| " * are made available under the terms of the Eclipse Public License v1.0" + |
| " * which accompanies this distribution, and is available at" + |
| " * http://www.eclipse.org/legal/epl-v10.html" + |
| " *" + |
| " * Contributors:" + |
| " * IBM Corporation - initial API and implementation" + |
| " *******************************************************************************/" + |
| "package org.eclipse.wst.jsdt.core.tests.util;\n" + |
| "\n" + |
| "import java.lang.reflect.*;\n" + |
| "import java.io.*;\n" + |
| "import java.net.*;\n" + |
| "import java.util.*;\n" + |
| "\n" + |
| "/******************************************************\n" + |
| " * \n" + |
| " * IMPORTANT NOTE: If modifying this class, copy the source to TestVerifier#getVerifyTestsCode()\n" + |
| " * (see this method for details)\n" + |
| " * \n" + |
| " ******************************************************/\n" + |
| "\n" + |
| "public class VerifyTests {\n" + |
| " int portNumber;\n" + |
| " Socket socket;\n" + |
| "\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" + |
| "\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" + |
| " /**\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" + |
| " }\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" + |
| " 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" + |
| " 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" + |
| "\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" + |
| " // ignore\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) { |
| if (classpaths.length == classpathCache.length) { |
| 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.getFreePort(); |
| 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); |
| } |
| } |