Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java5
-rw-r--r--org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java5
-rw-r--r--org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java379
-rw-r--r--org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/VerifyTests.java215
4 files changed, 250 insertions, 354 deletions
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java
index bffc9f25c3..c7cf6b18c4 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LambdaExpressionsTest.java
@@ -1046,7 +1046,10 @@ public void test039() {
" try {\n" +
" X x = (X & I & J) o;\n" +
" } catch (ClassCastException e) {\n" +
- " System.out.println(e.getMessage());\n" +
+ // Make assertion more robust by producing predictable output for Java 11+:
+ // - Omit stack trace
+ // - Cut off class loader name (e.g. 'java.net.URLClassLoader @f3f9f4b') for easier matching
+ " System.out.println(e.getMessage().replaceFirst(\"(unnamed module of loader).*\", \"$1\"));\n" +
" }\n" +
" }\n" +
"}\n",
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java
index 3cf5ff80e3..c0697c8d9e 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/MethodVerifyTest.java
@@ -14474,7 +14474,10 @@ public void testBug536978_comment5() {
" try {\n" +
" OtherResult result = demo.test(new OtherResult());\n" +
" } catch (ClassCastException e) {\n" +
- " System.out.println(e.getMessage());\n" + // omit the stack trace for test robustness
+ // Make assertion more robust by producing predictable output for Java 11+:
+ // - Omit stack trace
+ // - Cut off class loader name (e.g. 'java.net.URLClassLoader @f3f9f4b') for easier matching
+ " System.out.println(e.getMessage().replaceFirst(\"(unnamed module of loader).*\", \"$1\"));\n" +
" }\n" +
" }\n" +
"}\n"
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java
index 645a3612ff..ffb85ec993 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/TestVerifier.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,8 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Alexander Kriegisch - bug 286316: Set classpath for forked test JVM via
+ * DataOutputStream instead of JVM parameter; improve file deletion logic
*******************************************************************************/
package org.eclipse.jdt.core.tests.util;
@@ -17,6 +19,10 @@ import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
import org.eclipse.jdt.core.tests.runtime.*;
import java.io.*;
import java.net.*;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
/**
* Verifies that the .class files resulting from a compilation can be loaded
* in a VM and that they can be run.
@@ -125,208 +131,95 @@ public String getExecutionOutput(){
public String getExecutionError(){
return this.errorBuffer.toString();
}
+
/**
- * Returns the code of the VerifyTests class.
- *
+ * Default value for {@link VerifyTests} source code, copied and regularly refreshed from original source code
+ * <p>
* IMPORTANT NOTE: DO NOTE EDIT BUT GENERATE INSTEAD (see below)
- *
- * To generate:
- * - export VerifyTests.java to d:/temp
- * - inspect org.eclipse.jdt.core.tests.util.Util.fileContentToDisplayString("d:/temp/VerifyTests.java", 2, true)
+ * <p>
+ * To generate:<ul>
+ * <li>export VerifyTests.java to d:/temp</li>
+ * <li>inspect org.eclipse.jdt.core.tests.util.Util.fileContentToDisplayString("d:/temp/VerifyTests.java", 2, true)</li>
+ * </ul><p>
*/
-private String getVerifyTestsCode() {
- return
+static final String VERIFY_TEST_CODE_DEFAULT;
+
+static {
+ // Use static initialiser block instead of direct field initialisation, because it permits for code folding in IDEs,
+ // i.e. this huge string can easily be folded away, which minimises scrolling.
+ VERIFY_TEST_CODE_DEFAULT =
"/*******************************************************************************\n" +
- " * Copyright (c) 2000, 2017 IBM Corporation and others.\n" +
- " * All rights reserved. This program and the accompanying materials\n" +
- " * are made available under the terms of the Eclipse Public License v1.0\n" +
+ " * Copyright (c) 2000, 2021 IBM Corporation and others.\n" +
+ " *\n" +
+ " * This program and the accompanying materials\n" +
+ " * are made available under the terms of the Eclipse Public License 2.0\n" +
" * which accompanies this distribution, and is available at\n" +
- " * http://www.eclipse.org/legal/epl-v10.html\n" +
+ " * https://www.eclipse.org/legal/epl-2.0/\n" +
+ " *\n" +
+ " * SPDX-License-Identifier: EPL-2.0\n" +
" *\n" +
" * Contributors:\n" +
" * IBM Corporation - initial API and implementation\n" +
+ " * Alexander Kriegisch - bug 286316: Get classpath via DataInputStream and\n" +
+ " * use it in an isolated URLClassLoader, enabling formerly locked\n" +
+ " * classpath JARs to be closed on Windows\n" +
" *******************************************************************************/\n" +
"package org.eclipse.jdt.core.tests.util;\n" +
"\n" +
"import java.io.DataInputStream;\n" +
"import java.io.DataOutputStream;\n" +
"import java.io.File;\n" +
- "import java.io.FileInputStream;\n" +
- "import java.io.FileNotFoundException;\n" +
"import java.io.IOException;\n" +
- "import java.io.InputStream;\n" +
"import java.lang.reflect.InvocationTargetException;\n" +
"import java.lang.reflect.Method;\n" +
+ "import java.net.MalformedURLException;\n" +
"import java.net.Socket;\n" +
- "import java.util.StringTokenizer;\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" +
+ "import java.net.URL;\n" +
+ "import java.net.URLClassLoader;\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" +
+ " * <b>IMPORTANT NOTE:</b> When modifying this class, please copy the source into the static initialiser block for field\n" +
+ " * {@link TestVerifier#VERIFY_TEST_CODE_DEFAULT}. See also {@link TestVerifier#READ_VERIFY_TEST_FROM_FILE}, if you want\n" +
+ " * to dynamically load the source code directly from this file when running tests, which is a convenient way to test if\n" +
+ " * changes in this class work as expected, without the need to update the hard-coded default value every single time\n" +
+ " * during an ongoing refactoring.\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" +
+ " * In order to make the copying job easier, keep this class compatible with Java 5 language level. You may however use\n" +
+ " * things like {@code @Override} for interfaces, {@code assert} (if in a single line), {@code @SuppressWarnings},\n" +
+ " * because {@link TestVerifier#getVerifyTestsCode()} can filter them out dynamically. You should however avoid things\n" +
+ " * like diamonds, multi-catch, catch-with-resources and more recent Java features.\n" +
" */\n" +
+ "@SuppressWarnings({ \"unchecked\", \"rawtypes\" })\n" +
+ "public class VerifyTests {\n" +
+ " int portNumber;\n" +
+ " Socket socket;\n" +
"\n" +
- "\n" +
- "public class VerifyClassLoader extends ClassLoader {\n" +
- " /** scanned class path */\n" +
- " private String[] pathItems;\n" +
- "\n" +
- " /** excluded paths */\n" +
- " private String[] excluded= {};\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" +
- " this.pathItems= new String[i];\n" +
- " st= new StringTokenizer(classPath, separator);\n" +
- " i= 0;\n" +
- " while (st.hasMoreTokens()) {\n" +
- " this.pathItems[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\") || name.startsWith(\"[Ljava\"))\n" +
- " return true;\n" +
- "\n" +
- " // exclude the user defined package paths\n" +
- " for (int i= 0; i < this.excluded.length; i++) {\n" +
- " if (name.startsWith(this.excluded[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" +
- " FileInputStream stream = null;\n" +
- " try {\n" +
- " //System.out.println(\"loading: \"+f.getPath());\n" +
- " stream = new FileInputStream(f);\n" +
- "\n" +
- " try {\n" +
- " byte[] b= new byte[stream.available()];\n" +
- " stream.read(b);\n" +
- " return b;\n" +
- " }\n" +
- " catch (IOException e) {\n" +
- " throw new ClassNotFoundException();\n" +
- " }\n" +
- " }\n" +
- " catch (FileNotFoundException e) {\n" +
- " throw new ClassNotFoundException();\n" +
- " } finally {\n" +
- " if (stream != null) {\n" +
- " try {\n" +
- " stream.close();\n" +
- " } catch (IOException e) {\n" +
- " /* ignore */\n" +
- " }\n" +
- " }\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" +
- " if (fileName != null) {\n" +
- " fileName= fileName.replace('.', '/')+\".class\";\n" +
- " File path= null;\n" +
- " for (int i= 0; i < this.pathItems.length; i++) {\n" +
- " path= new File(this.pathItems[i], fileName);\n" +
- " if (path.exists())\n" +
- " return path;\n" +
- " }\n" +
- " }\n" +
- " return null;\n" +
+ "private static URL[] classPathToURLs(String[] classPath) throws MalformedURLException {\n" +
+ " URL[] urls = new URL[classPath.length];\n" +
+ " for (int i = 0; i < classPath.length; i++) {\n" +
+ " urls[i] = new File(classPath[i]).toURI().toURL();\n" +
" }\n" +
+ " return urls;\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" +
+ "public void loadAndRun(String className, String[] classPath) throws Throwable {\n" +
+ " URLClassLoader urlClassLoader = new URLClassLoader(classPathToURLs(classPath));\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" +
+ " //System.out.println(\"Loading \" + className + \"...\");\n" +
+ " Class testClass = urlClassLoader.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" +
+ " } finally {\n" +
+ " urlClassLoader.close();\n" +
" }\n" +
"}\n" +
"public static void main(String[] args) throws IOException {\n" +
@@ -342,35 +235,147 @@ private String getVerifyTestsCode() {
" final DataOutputStream out = new DataOutputStream(this.socket.getOutputStream());\n" +
" while (true) {\n" +
" final String className = in.readUTF();\n" +
+ " final int length = in.readInt();\n" +
+ " final String[] classPath = new String[length];\n" +
+ " for (int i = 0; i < length; i++) {\n" +
+ " classPath[i] = in.readUTF();\n" +
+ " }\n" +
" Thread thread = new Thread() {\n" +
+ " @Override\n" +
" public void run() {\n" +
" try {\n" +
- " loadAndRun(className);\n" +
+ " loadAndRun(className, classPath);\n" +
" out.writeBoolean(true);\n" +
- " System.err.println(VerifyTests.class.getName());\n" +
" System.out.println(VerifyTests.class.getName());\n" +
+ " System.err.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" +
+ " System.out.println(VerifyTests.class.getName());\n" +
+ " System.err.println(VerifyTests.class.getName());\n" +
" } catch (IOException e1) {\n" +
" e1.printStackTrace();\n" +
" }\n" +
" }\n" +
+ " // Flush all streams, in case the test executor VM is shut down before\n" +
+ " // the controlling VM receives the responses it depends on\n" +
" try {\n" +
" out.flush();\n" +
" } catch (IOException e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
+ " System.out.flush();\n" +
+ " System.err.flush();\n" +
" }\n" +
" };\n" +
" thread.start();\n" +
" }\n" +
"}\n" +
- "}";
+ "}\n";
+}
+
+/**
+ * Activate, if you want to read the {@link VerifyTests} source code directly from the file system in
+ * {@link #getVerifyTestsCode()}, e.g. during development while refactoring the source code.
+ */
+public static boolean READ_VERIFY_TEST_FROM_FILE = false;
+/**
+ * Adjust, if in {@link #READ_VERIFY_TEST_FROM_FILE} mode method {@link #getVerifyTestsCode()} cannot find
+ * the source file based on the current directory. In that case, set the correct JDT Core project base
+ * directory as PROJECT_BASE_DIR environment variable, so that the 'org.eclipse.jdt.core.tests.compiler/src'
+ * sub-directory can be found from there.
+ */
+public static String PROJECT_BASE_DIR = System.getenv("PROJECT_BASE_DIR");
+
+// Cached value for VerifyTests.java source code, read only once, either directly from the source code directory or
+// from VERIFY_TEST_CODE_DEFAULT
+private static String verifyTestCode;
+
+// Helper object for guarding 'verifyTestCode' with 'synchronized (verifyTestCodeLock)', in case tests are to be run in
+// parallel
+private static final Object verifyTestCodeLock = new Object();
+
+/**
+ * Returns {@link VerifyTests} source code, to be used as a boot-strapping class in forked test JVMs
+ * <p>
+ * Optionally, you can use {@link #READ_VERIFY_TEST_FROM_FILE} in order to read the source code from the project's
+ * source directory. If it is not found automatically, you may also adjust {@link #PROJECT_BASE_DIR}. Both values are
+ * public and writable during runtime.
+ * <p>
+ * <b>Caveat:</b> The return value is only lazily initialised once, then cached. If you change
+ * {@link #READ_VERIFY_TEST_FROM_FILE} after calling this method for the first time, the return value will not change
+ * anymore.
+ *
+ * @return {@link VerifyTests} source code, filtered by {@link #filterSourceCode(Stream)}
+ */
+String getVerifyTestsCode() {
+ synchronized (verifyTestCodeLock) {
+ if (verifyTestCode == null) {
+ if (READ_VERIFY_TEST_FROM_FILE) {
+ String sourceFile = "src/org/eclipse/jdt/core/tests/util/VerifyTests.java";
+ if (!new File(sourceFile).exists()) {
+ sourceFile = PROJECT_BASE_DIR + "/org.eclipse.jdt.core.tests.compiler/" + sourceFile;
+ }
+ try (BufferedReader reader = new BufferedReader(new FileReader(sourceFile))) {
+ verifyTestCode = filterSourceCode(reader.lines());
+ }
+ catch (IOException e) {
+ System.out.println("WARNING: Cannot read & filter VerifyTests source code from file, using default value");
+ System.out.println(" - exception: " + e);
+ }
+ }
+ }
+ if (verifyTestCode == null) {
+ try (BufferedReader reader = new BufferedReader(new StringReader(VERIFY_TEST_CODE_DEFAULT))) {
+ verifyTestCode = filterSourceCode(reader.lines());
+ }
+ catch (IOException e) {
+ System.out.println("WARNING: Cannot filter VerifyTests source code default value, using unfiltered value");
+ System.out.println(" - exception: " + e);
+ verifyTestCode = VERIFY_TEST_CODE_DEFAULT;
+ }
+ }
+ return verifyTestCode;
+ }
}
+
+/**
+ * Filter some elements incompatible with Java source level 1.5 from source code
+ * <p>
+ * This method cannot convert things like catch-with-resources or other language elements back to Java 1.5, you have to
+ * take care of keeping the source code backward compatible by yourself. But a few things you can still use in the
+ * source code, such as {@code @SuppressWarnings}, {@code @Override} in interfaces or single-line {@code assert}.
+ *
+ * @param sourceCodeLines stream of source code lines
+ * @return filtered source code file as a string
+ */
+private String filterSourceCode(Stream<String> sourceCodeLines) {
+ return sourceCodeLines
+ .filter(s -> !(s.contains("@SuppressWarnings") || s.contains("@Override") || s.contains("assert ")))
+ .collect(Collectors.joining("\n"));
+}
+
+/**
+ * Remove non-essential parts of the test JVM classpath
+ * <p>
+ * The classpath for the forked test JVM should only contain JDK paths and the 'verifier' subdirectory where the
+ * {@link VerifyTests} class boot-strapping the test resides, because those need to be present during JVM start-up.
+ * Other parts of the classpath are stripped off, because they are to be communicated to the forked JVM via direct
+ * socket communication.
+ *
+ * @param classPath full classpath
+ * @return minimal classpath necessary for forked test JVM boot-strapping
+ */
+private String[] getMinimalClassPath(String[] classPath) {
+return Arrays.stream(classPath)
+ .filter(s -> {
+ String path = s.replace('\\', '/');
+ return !path.contains("/comptest/") || path.endsWith("/verifier");
+ })
+ .toArray(String[]::new);
+}
+
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) {
@@ -475,7 +480,7 @@ private void launchVerifyTestsIfNeeded(String[] classpaths, String[] vmArguments
String verifierDir = Util.getOutputDirectory() + File.separator + "verifier";
compileVerifyTests(verifierDir);
cp[length] = verifierDir;
- launcher.setClassPath(cp);
+ launcher.setClassPath(getMinimalClassPath(cp));
launcher.setVMPath(Util.getJREDirectory());
if (vmArguments != null) {
String[] completeVmArguments = new String[vmArguments.length + 1];
@@ -565,11 +570,17 @@ private void launchVerifyTestsIfNeeded(String[] classpaths, String[] vmArguments
* Loads and runs the given class.
* Return whether no exception was thrown while running the class.
*/
-private boolean loadAndRun(String className) {
+private boolean loadAndRun(String className, String[] classPath) {
if (this.socket != null) {
try {
DataOutputStream out = new DataOutputStream(this.socket.getOutputStream());
out.writeUTF(className);
+ if (classPath == null)
+ classPath = new String[0];
+ out.writeInt(classPath.length);
+ for (String classpath : classPath) {
+ out.writeUTF(classpath);
+ }
DataInputStream in = new DataInputStream(this.socket.getInputStream());
try {
boolean result = in.readBoolean();
@@ -632,7 +643,7 @@ public boolean verifyClassFiles(String sourceFilePath, String className, String
this.errorBuffer = new StringBuffer();
if (this.reuseVM && programArguments == null) {
launchVerifyTestsIfNeeded(classpaths, vmArguments);
- loadAndRun(className);
+ loadAndRun(className, classpaths);
} else {
launchAndRun(className, classpaths, programArguments, vmArguments);
}
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/VerifyTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/VerifyTests.java
index 67a3d172ae..6ec6faa0a2 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/VerifyTests.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/util/VerifyTests.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,192 +10,66 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Alexander Kriegisch - bug 286316: Get classpath via DataInputStream and
+ * use it in an isolated URLClassLoader, enabling formerly locked
+ * classpath JARs to be closed on Windows
*******************************************************************************/
package org.eclipse.jdt.core.tests.util;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.net.MalformedURLException;
import java.net.Socket;
-import java.util.StringTokenizer;
-
-/******************************************************
- *
- * IMPORTANT NOTE: If modifying this class, copy the source to TestVerifier#getVerifyTestsCode()
- * (see this method for details)
- *
- ******************************************************/
+import java.net.URL;
+import java.net.URLClassLoader;
+/**
+ * <b>IMPORTANT NOTE:</b> When modifying this class, please copy the source into the static initialiser block for field
+ * {@link TestVerifier#VERIFY_TEST_CODE_DEFAULT}. See also {@link TestVerifier#READ_VERIFY_TEST_FROM_FILE}, if you want
+ * to dynamically load the source code directly from this file when running tests, which is a convenient way to test if
+ * changes in this class work as expected, without the need to update the hard-coded default value every single time
+ * during an ongoing refactoring.
+ * <p>
+ * In order to make the copying job easier, keep this class compatible with Java 5 language level. You may however use
+ * things like {@code @Override} for interfaces, {@code assert} (if in a single line), {@code @SuppressWarnings},
+ * because {@link TestVerifier#getVerifyTestsCode()} can filter them out dynamically. You should however avoid things
+ * like diamonds, multi-catch, catch-with-resources and more recent Java features.
+ */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class VerifyTests {
int portNumber;
Socket socket;
-/**
- * NOTE: Code copied from junit.util.TestCaseClassLoader.
- *
- * A custom class loader which enables the reloading
- * of classes for each test run. The class loader
- * can be configured with a list of package paths that
- * should be excluded from loading. The loading
- * of these packages is delegated to the system class
- * loader. They will be shared across test runs.
- * <p>
- * The list of excluded package paths is specified in
- * a properties file "excluded.properties" that is located in
- * the same place as the TestCaseClassLoader class.
- * <p>
- * <b>Known limitation:</b> the VerifyClassLoader cannot load classes
- * from jar files.
- */
-
-
-public class VerifyClassLoader extends ClassLoader {
- /** scanned class path */
- private String[] pathItems;
-
- /** excluded paths */
- private String[] excluded= {};
-
- /**
- * Constructs a VerifyClassLoader. It scans the class path
- * and the excluded package paths
- */
- public VerifyClassLoader() {
- super();
- String classPath= System.getProperty("java.class.path");
- String separator= System.getProperty("path.separator");
-
- // first pass: count elements
- StringTokenizer st= new StringTokenizer(classPath, separator);
- int i= 0;
- while (st.hasMoreTokens()) {
- st.nextToken();
- i++;
- }
- // second pass: split
- this.pathItems= new String[i];
- st= new StringTokenizer(classPath, separator);
- i= 0;
- while (st.hasMoreTokens()) {
- this.pathItems[i++]= st.nextToken();
- }
-
- }
- @Override
- public java.net.URL getResource(String name) {
- return ClassLoader.getSystemResource(name);
- }
- @Override
- public InputStream getResourceAsStream(String name) {
- return ClassLoader.getSystemResourceAsStream(name);
- }
- protected boolean isExcluded(String name) {
- // exclude the "java" packages.
- // They always need to be excluded so that they are loaded by the system class loader
- if (name.startsWith("java") || name.startsWith("[Ljava"))
- return true;
-
- // exclude the user defined package paths
- for (int i= 0; i < this.excluded.length; i++) {
- if (name.startsWith(this.excluded[i])) {
- return true;
- }
- }
- return false;
- }
- @Override
- public synchronized Class loadClass(String name, boolean resolve)
- throws ClassNotFoundException {
-
- Class c= findLoadedClass(name);
- if (c != null)
- return c;
- //
- // Delegate the loading of excluded classes to the
- // standard class loader.
- //
- if (isExcluded(name)) {
- try {
- c= findSystemClass(name);
- return c;
- } catch (ClassNotFoundException e) {
- // keep searching
- }
- }
- File file= locate(name);
- if (file == null)
- throw new ClassNotFoundException();
- byte data[]= loadClassData(file);
- c= defineClass(name, data, 0, data.length);
- if (resolve)
- resolveClass(c);
- return c;
- }
- private byte[] loadClassData(File f) throws ClassNotFoundException {
- FileInputStream stream = null;
- try {
- //System.out.println("loading: "+f.getPath());
- stream = new FileInputStream(f);
-
- try {
- byte[] b= new byte[stream.available()];
- stream.read(b);
- return b;
- }
- catch (IOException e) {
- throw new ClassNotFoundException();
- }
- }
- catch (FileNotFoundException e) {
- throw new ClassNotFoundException();
- } finally {
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- /* ignore */
- }
- }
- }
- }
- /**
- * Locate the given file.
- * @return Returns null if file couldn't be found.
- */
- private File locate(String fileName) {
- if (fileName != null) {
- fileName= fileName.replace('.', '/')+".class";
- File path= null;
- for (int i= 0; i < this.pathItems.length; i++) {
- path= new File(this.pathItems[i], fileName);
- if (path.exists())
- return path;
- }
- }
- return null;
+private static URL[] classPathToURLs(String[] classPath) throws MalformedURLException {
+ URL[] urls = new URL[classPath.length];
+ for (int i = 0; i < classPath.length; i++) {
+ urls[i] = new File(classPath[i]).toURI().toURL();
}
+ return urls;
}
-public void loadAndRun(String className) throws Throwable {
- //System.out.println("Loading " + className + "...");
- Class testClass = new VerifyClassLoader().loadClass(className);
- //System.out.println("Loaded " + className);
+public void loadAndRun(String className, String[] classPath) throws Throwable {
+ URLClassLoader urlClassLoader = new URLClassLoader(classPathToURLs(classPath));
try {
- Method main = testClass.getMethod("main", new Class[] {String[].class});
- //System.out.println("Running " + className);
- main.invoke(null, new Object[] {new String[] {}});
- //System.out.println("Finished running " + className);
- } catch (NoSuchMethodException e) {
- return;
- } catch (InvocationTargetException e) {
- throw e.getTargetException();
+ //System.out.println("Loading " + className + "...");
+ Class testClass = urlClassLoader.loadClass(className);
+ //System.out.println("Loaded " + className);
+ try {
+ Method main = testClass.getMethod("main", new Class[] {String[].class});
+ //System.out.println("Running " + className);
+ main.invoke(null, new Object[] {new String[] {}});
+ //System.out.println("Finished running " + className);
+ } catch (NoSuchMethodException e) {
+ return;
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ } finally {
+ urlClassLoader.close();
}
}
public static void main(String[] args) throws IOException {
@@ -211,11 +85,16 @@ public void run() throws IOException {
final DataOutputStream out = new DataOutputStream(this.socket.getOutputStream());
while (true) {
final String className = in.readUTF();
+ final int length = in.readInt();
+ final String[] classPath = new String[length];
+ for (int i = 0; i < length; i++) {
+ classPath[i] = in.readUTF();
+ }
Thread thread = new Thread() {
@Override
public void run() {
try {
- loadAndRun(className);
+ loadAndRun(className, classPath);
out.writeBoolean(true);
System.out.println(VerifyTests.class.getName());
System.err.println(VerifyTests.class.getName());

Back to the top