package com.redhat.ceylon.compiler.java.test.fordebug;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import org.junit.Assert;
import org.junit.BeforeClass;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.compiler.java.test.CompilerTests;
public class DebuggerTests extends CompilerTests {
protected String getClassPathAsPath(ModuleWithArtifact... modules) {
List<File> files = getClassPathAsFiles(modules);
files.add(new File(System.getProperty("user.home"), ".ceylon/repo/ceylon/language/" + Versions.CEYLON_VERSION_NUMBER + "/ceylon.language-" + Versions.CEYLON_VERSION_NUMBER + ".car"));
for(String path : CLASS_PATH){
files.add(new File(path));
}
return toPath(files);
}
private String toPath(List<File> files) {
StringBuilder sb = new StringBuilder();
for (File file : files) {
sb.append(file.getAbsolutePath()).append(File.pathSeparator);
}
return sb.toString();
}
public Tracer tracer(String mainClass) throws Exception {
return tracer(mainClass, getDestModuleWithArtifact(mainClass));
}
/**
* Reflectively instantiate the TracerImpl which was compiled
* by {@link #compileTracerImpl()}.
* @see #compileTracerImpl()
*/
public Tracer tracer(String mainClass, ModuleWithArtifact module) throws Exception {
Class<? extends Tracer> tracerClass = (Class)Class.forName("com.redhat.ceylon.compiler.java.test.trace.TracerImpl", false, tracerClassLoader);
Constructor<? extends Tracer> ctor = tracerClass.getConstructor(String.class, String.class);
return ctor.newInstance(mainClass, getClassPathAsPath(module));
}
protected void assertSameTrace(Tracer tracer, String traceFile) {
File expectedSrcFile = new File(getPackagePath(), traceFile);
String expectedTrace = normalizeLineEndings(readFile(expectedSrcFile)).trim();
String actualTrace = normalizeLineEndings(tracer.getTrace()).trim();
// THIS IS FOR INTERNAL USE ONLY!!!
// Can be used to do batch updating of known correct tests
// Uncomment only when you know what you're doing!
// if (expectedSrc != null && compiledSrc != null && !expectedSrc.equals(compiledSrc)) {
// writeFile(expectedSrcFile, compiledSrc);
// expectedSrc = compiledSrc;
// }
Assert.assertEquals("Traces differ", expectedTrace, actualTrace);
}
private static ClassLoader tracerClassLoader;
/**
* Tries to find {@code tools.jar}:
* <ol>
* <li>If it exists within the {@code lib} directory of parent of
* the java installation directory, as specified by the
* {@code java.home} system property.</li>
* <li>Look for {@code javac} in the {@code $PATH}. It if exists,
* canonicalise that path , and look in the lib directory of its
* grandparent directory.<li>
* <li>Give up, and return null</li>
* </ol>
* @return
*/
private static String findToolsJar() {
try {
String javaHome = System.getProperty("java.home");
if (javaHome != null) {
File toolsJar = new File(new File(javaHome).getCanonicalFile().getParentFile(), "lib/tools.jar");
if (toolsJar.exists()
&& toolsJar.canRead()) {
return toolsJar.getCanonicalPath();
}
}
String osPath = System.getenv("PATH");
if (osPath != null) {
String[] paths = osPath.split(java.util.regex.Pattern.quote(File.pathSeparator));
for (String path : paths) {
File file = new File(path, "javac");
if (file.exists() && file.canExecute()) {
File tools = new File(file.getCanonicalFile().getParentFile().getParentFile(), "lib/tools.jar");
if (tools.exists() && tools.canRead()) {
return tools.getCanonicalPath();
}
}
}
}
return null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* <p>Find the jar file containing the JDI implementation:</p>
* <ol>
* <li>If the system property {@code jdi.jar} is set use that
* <li>Try to find {@code tools.jar} using {@link #findToolsJar()}
* </ol>
*/
private static String findJdiJar() {
String jdiJar = System.getProperty("jdi.jar");
if (jdiJar == null) {
jdiJar = findToolsJar();
}
if (jdiJar == null) {
throw new RuntimeException("System property jdi.jar was not set, and could not find tools.jar");
}
File file = new File(jdiJar);
if (!file.exists()
|| !file.canRead()) {
throw new RuntimeException("Found jdi.jar " + file.getAbsolutePath() + " but it doesn't exist or cannot be read");
}
System.out.println("Found JDI in " + jdiJar);
return jdiJar;
}
/**
* <p>The tracer tests need to use JDI to execute a ceylon program under a
* debugger. The JDK ships with a JDI implementation, but it's in
* {@code tools.jar} (at least for OpenJDK). We can't just put
* {@code tools.jar} on the classpath because it also contains the classes
* for javac, but the ceylon compile contains versions of those classes
* too, so the compiler breaks.</p>
*
* <p>Therefore we:</p>
* <ol>
* <li>Split the {@link Tracer} into a JDI-independent set of interfaces and
* a separate implementation ({@code TracerImpl}).
* <li>Keep the {@code TraceImpl} source files out of an Eclipse
* source path, so Eclipse doesn't need tools.jar on the classpath
* <li>Compile the {@code TraceImpl} here and obtain an instance
* reflectively using a ClassLoader pointing at the class we compiled.
* </ol>
*/
@BeforeClass
public static void compileTracerImpl() throws Exception {
String srcDir = "test/trace";
String jdiJar = findJdiJar();
Javac javac = new Javac();
javac.appendSourcePath(srcDir);
javac.addSourceFiles(new FileCollector().addFiles(srcDir, FileCollector.JAVA_SOURCE_FILES));
javac.appendClassPath(new FileCollector().addFiles("lib", FileCollector.JAR_FILES));
javac.appendClassPath(jdiJar);
javac.appendClassPath("build/classes");
System.out.println("Compiling test classes: "+ javac);
int sc = javac.exec();
if (sc != 0) {
throw new RuntimeException("javac did not return normally");
}
tracerClassLoader = new URLClassLoader(
new URL[]{
new File(srcDir).toURI().toURL(),
new File(jdiJar).toURI().toURL()},
DebuggerTests.class.getClassLoader());
}
}