Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSravan Kumar Lakkimsetti2021-05-17 04:52:50 +0000
committerManoj Palat2021-10-18 11:30:34 +0000
commit89168b7454f75fd318f7f21d5c38c7352453b5f8 (patch)
treeb947cf21c2418c528b4e9a1f0f4c72e3dc7b0c53
parent0171fc1ad417c06219cf50676a497f9d6bf85749 (diff)
downloadeclipse.jdt.core-89168b7454f75fd318f7f21d5c38c7352453b5f8.tar.gz
eclipse.jdt.core-89168b7454f75fd318f7f21d5c38c7352453b5f8.tar.xz
eclipse.jdt.core-89168b7454f75fd318f7f21d5c38c7352453b5f8.zip
Bug 573363: Fix classloader isolation for VerifyTestsI20211019-1800I20211018-1800
Before this fix, VerifyTests used a dedicated classloader subclass VerifyTests.VerifyClassLoader in order to try and achieve class loader isolation for separate tests running in the same cached JVM instance. However, the isolation only worked for file system folders, not for JAR files as e.g. used in DeprecatedTest.test008a. Because the JVM normally does not close JAR files, replacing such a file would have no effect on the loaded classes on UNIX-oid platforms and the additional bad effect of locked file errors when trying to delete such JARs on Windows. The solution is, - to use a URLClassLoader, because it can be closed when no longer used and then releases its resources, - to make sure the classpath for test-specific directories and JARs is not set as a classpath parameter for the test JVM, but communicated per test via the existing socket connection between main and forked JVM. In order to achieve this, some changes had to be done on both sides, in classes TestVerifier (main JVM) and VerifyTests (forked test JVM). Because it was inconvenient to test the interplay of both classes due to the fact that not the original VerifyTests class was copied into the forked JVM, but a hard-coded copy stored as a string inside TestVerifier, I also added an option to switch into a mode actually copying the original file. This mode could be the future default (it falls back to the hard-coded value if the file cannot be read anyway), but for backward compatibility the flag READ_VERIFY_TEST_FROM_FILE is false by default. Another goodie I added is a primitive filter for the source code, capable of stripping out some annotations and 'assert', which are incompatible with Java 1.5. So they do not need to be stripped out of the VerifyTests source anymore, only the rest of the class needs to be compatible with 1.5, i.e. you should avoid things like diamonds, multi-catch, catch-with-resources and other more recent Java features. Signed-off-by: Alexander Kriegisch <Alexander@Kriegisch.name> Bug 573363: TestVerifier.launchAndRun does not need minimal classpath I guess I replaced one call too many and reverted from launcher.setClassPath(getMinimalClassPath(classpaths)); back to launcher.setClassPath(classpaths); It actually only makes sense to minimise the classpath, if later the missing paths can be communicated to the test VM via socket communication (DataOutputStream). But that is only the case when TestVerifier.loadAndRun is called, which again only happens in the case that a JVM is reused. If in the future we detect cases in which class loader isolation is needed for TestVerifier.launchAndRun too, because e.g. later the same JVM is to be re-used with the same class directory or JAR file names, we need to invent a new way of communicating the additional paths (e.g. via a temporary file) or always open a dedicated communication socket to the target VM. Signed-off-by: Alexander Kriegisch <Alexander@Kriegisch.name> Bug 573363: Fix 2 tests checking cast error messages 1) MethodVerifyTest.testBug536978_comment5 2) LambdaExpressionsTest.test039 Make assertions more robust by producing predictable output: - Omit stack trace for test robustness. - Cut off class loader name (e.g. 'java.net.URLClassLoader @f3f9f4b') for easier matching. Change-Id: Ia9afc08fd8df71ebdaac45124fbf5013b8cc61d5 Signed-off-by: Alexander Kriegisch <Alexander@Kriegisch.name> Signed-off-by: Sravan Kumar Lakkimsetti <sravankumarl@in.ibm.com> Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.core/+/182689 Reviewed-by: Manoj Palat <manpalat@in.ibm.com> Tested-by: JDT Bot <jdt-bot@eclipse.org>
-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