package org.rubypeople.rdt.debug.core.tests; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import junit.framework.TestCase; import org.rubypeople.rdt.internal.debug.core.ExceptionSuspensionPoint; import org.rubypeople.rdt.internal.debug.core.StepSuspensionPoint; import org.rubypeople.rdt.internal.debug.core.SuspensionPoint; import org.rubypeople.rdt.internal.debug.core.commands.AbstractCommand; import org.rubypeople.rdt.internal.debug.core.commands.AbstractDebuggerConnection; import org.rubypeople.rdt.internal.debug.core.commands.BreakpointCommand; import org.rubypeople.rdt.internal.debug.core.commands.GenericCommand; import org.rubypeople.rdt.internal.debug.core.commands.StepCommand; import org.rubypeople.rdt.internal.debug.core.model.RubyProcessingException; import org.rubypeople.rdt.internal.debug.core.model.RubyStackFrame; import org.rubypeople.rdt.internal.debug.core.model.RubyThread; import org.rubypeople.rdt.internal.debug.core.model.RubyVariable; import org.rubypeople.rdt.internal.debug.core.model.ThreadInfo; import org.rubypeople.rdt.internal.debug.core.parsing.AbstractReadStrategy; import org.rubypeople.rdt.internal.debug.core.parsing.BreakpointModificationReader; import org.rubypeople.rdt.internal.debug.core.parsing.EvalReader; import org.rubypeople.rdt.internal.debug.core.parsing.FramesReader; import org.rubypeople.rdt.internal.debug.core.parsing.LoadResultReader; import org.rubypeople.rdt.internal.debug.core.parsing.SuspensionReader; import org.rubypeople.rdt.internal.debug.core.parsing.ThreadInfoReader; import org.rubypeople.rdt.internal.debug.core.parsing.VariableReader; public abstract class FTC_AbstractDebuggerCommunicationTest extends TestCase { private static final boolean VERBOSE = false; private static String tmpDir; protected static String getTmpDir() { if (tmpDir == null) { tmpDir = System.getProperty("java.io.tmpdir"); if (tmpDir.charAt(tmpDir.length() - 1) != File.separatorChar) { tmpDir = tmpDir + File.separator; } } return tmpDir; } public static String RUBY_INTERPRETER; static { RUBY_INTERPRETER = System.getProperty("rdt.rubyInterpreter"); if (RUBY_INTERPRETER == null) { RUBY_INTERPRETER = "ruby"; } } private static long TIMEOUT_MS = 30000; protected Process process; protected OutputRedirectorThread rubyStdoutRedirectorThread; protected OutputRedirectorThread rubyStderrRedirectorThread; private AbstractDebuggerConnection debuggerConnection; // for timeout handling private Thread mainThread; private Thread timeoutThread; private AbstractReadStrategy readStrategy; public FTC_AbstractDebuggerCommunicationTest(String arg0) { super(arg0); } public static void main(String[] args) { junit.textui.TestRunner.run(FTC_ClassicDebuggerCommunicationTest.class); } protected String getTestFilename() { return getTmpDir() + "test.rb"; } protected String getRubyTestFilename() { return getTestFilename().replace('\\', '/'); } protected SuspensionReader getSuspensionReader() throws Exception { return new SuspensionReader(readStrategy); } protected VariableReader getVariableReader() throws Exception { return new VariableReader(readStrategy); } protected EvalReader getEvalExceptionReader() throws Exception { return new EvalReader(readStrategy); } protected FramesReader getFramesReader() throws Exception { return new FramesReader(readStrategy); } protected ThreadInfoReader getThreadInfoReader() throws Exception { return new ThreadInfoReader(readStrategy); } protected LoadResultReader getLoadResultReader() throws Exception { return new LoadResultReader(readStrategy); } protected EvalReader getEvalReader() throws Exception { return new EvalReader(readStrategy); } protected BreakpointModificationReader getBreakpointAddedReader() throws Exception { return new BreakpointModificationReader(readStrategy); } protected String getOSIndependent(String path) { return path.replace('\\', '/'); } public void setUp() throws Exception { if (!new File(getTmpDir()).exists() || !new File(getTmpDir()).isDirectory()) { throw new RuntimeException("Temp directory does not exist: " + getTmpDir()); } // if a reader hangs, because the expected data from the ruby process // does not arrive, it gets interrupted from the timeout watchdog. mainThread = Thread.currentThread(); timeoutThread = new Thread() { public void run() { try { while (true) { log("Starting timeout watchdog."); Thread.sleep(TIMEOUT_MS); log("Timeout reached."); mainThread.interrupt(); } } catch (InterruptedException e) { log("Watchdog deactivated."); } } }; timeoutThread.start(); log("Setup finished."); } public void tearDown() { log("TearDown"); timeoutThread.interrupt(); if (process == null) { // here we go if there was an error in the creation of the process // (process == null) // or there was an error creating the socket, e.g. ruby process has // died early return; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } try { debuggerConnection.exit(); } catch (IOException e) { log("Exception while closing socket."); e.printStackTrace(); } try { if (process.exitValue() != 0) { log("Ruby finished with exit value: " + process.exitValue()); } } catch (IllegalThreadStateException ex) { process.destroy(); log("Ruby process had to be destroyed."); // wait so that the debugger port will be availabel for the next // test // There seems to be a delay after the destroying of a process and // freeing the server port try { Thread.sleep(5000); } catch (InterruptedException e) { log("Exception while sleeping after destroying the ruby process"); // TODO: find out what causes interruption! try { Thread.sleep(5000); } catch (InterruptedException e1) { log("Exception again while sleeping after destroying the ruby process"); } } } log("Waiting for stdout redirector thread.."); try { rubyStdoutRedirectorThread.join(); } catch (InterruptedException e) { log("Exception while waiting for stdout redirector"); e.printStackTrace(); } log("..done"); log("Waiting for stderr redirector thread.."); try { rubyStderrRedirectorThread.join(); } catch (InterruptedException e) { log("Exception while waiting for stderr redirector"); e.printStackTrace(); } log("..done"); } protected void writeFile(String name, String[] content) throws Exception { PrintWriter writer = new PrintWriter(new FileOutputStream(getTmpDir() + name)); for (int i = 0; i < content.length; i++) { writer.println(content[i]); } writer.close(); } protected abstract AbstractDebuggerConnection createDebuggerConnection(); protected abstract void startRubyProcess() throws Exception; protected void createSocket(String[] lines) throws Exception { writeFile("test.rb", lines); startRubyProcess(); Thread.sleep(1000); debuggerConnection = createDebuggerConnection(); debuggerConnection.connect(); // new AbstractDebuggerConnection // try { // socket = new Socket("localhost", 1098); // } catch (ConnectException cex) { // throw new RuntimeException( // "Ruby process finished prematurely. Last line in stderr: " // + rubyStderrRedirectorThread.getLastLine(), cex); // } // multiReaderStrategy = new MultiReaderStrategy(getXpp(socket)); // Runnable runnable = new Runnable() { // public void run() { // try { // while (true) { // new WasteReader(multiReaderStrategy).read(); // } // } catch (Exception e) { // e.printStackTrace(); // } // }; // }; // new Thread(runnable).start(); // Thread.sleep(500); // out = new PrintWriter(socket.getOutputStream(), true); // createControlSocket() ; } protected SuspensionReader startDebugger() throws Exception { return debuggerConnection.start(); } protected void sendCommand(AbstractCommand command) throws Exception { command.execute(debuggerConnection); } /* * @deprecated */ protected void sendRuby(String debuggerCommand) throws Exception { try { process.exitValue(); throw new RuntimeException("Ruby debugger has finished prematurely."); } catch (IllegalThreadStateException ex) { // not yet finished, normal behaviour // why does process does not have a function like isRunning() ? // TODO: remove generic command GenericCommand command = new GenericCommand(debuggerCommand, false /* iscontrol */); command.execute(debuggerConnection); readStrategy = command.getReadStrategy(); } } // public void testNameError() throws Exception { // createSocket(new String[] { "puts 'x'" }); // sendRuby("cont"); // SuspensionPoint hit = getSuspensionReader().readSuspension(); // // TODO: assertion is wrong, I want the debugger to suspend here // // but there must be several catchpoints available // assertNull(hit); // } protected void runToLine(int lineNumber) throws Exception { runTo("test.rb", lineNumber); } private void runTo(String filename, int lineNumber) throws Exception { setBreakpoint(filename, lineNumber) ; SuspensionReader reader; if (!debuggerConnection.isStarted()) { reader = debuggerConnection.start(); } else { StepCommand stepCommand = new StepCommand("cont"); stepCommand.execute(debuggerConnection); reader = stepCommand.getSuspensionReader(); } SuspensionPoint hit = reader.readSuspension(); assertNotNull(hit); assertTrue(hit.isBreakpoint()); assertEquals(lineNumber, hit.getLine()); } private void setBreakpoint(String filename, int line) throws Exception{ String command = "b " + filename + ":" + line; new BreakpointCommand(command).executeWithResult(debuggerConnection); } private void setBreakpoint(int line) throws Exception { setBreakpoint("test.rb", line) ; } public void testBreakpointOnFirstLine() throws Exception { createSocket(new String[] { "puts 'a'" }); runTo("test.rb", 1); sendRuby("exit") ; } public void testBreakpointAddAndRemove() throws Exception { createSocket(new String[] { "1.upto(3) {", "puts 'a'", "puts 'b'", "puts 'c'", "}" }); int breakpointId1 = new BreakpointCommand("b test.rb:2").executeWithResult(debuggerConnection); assertEquals(1, breakpointId1); int breakpointId2 = new BreakpointCommand("b test.rb:4").executeWithResult(debuggerConnection); assertEquals(2, breakpointId2); SuspensionPoint hit1 = startDebugger().readSuspension(); assertBreakpoint(hit1, "test.rb", 2); SuspensionPoint hit2 = new StepCommand("cont").readSuspension(debuggerConnection); assertBreakpoint(hit2, "test.rb", 4); SuspensionPoint hit3 = new StepCommand("cont").readSuspension(debuggerConnection); assertBreakpoint(hit3, "test.rb", 2); int idDeleted = new BreakpointCommand("delete 100").executeWithResult(debuggerConnection); assertEquals(-1, idDeleted); idDeleted = new BreakpointCommand("delete 2").executeWithResult(debuggerConnection); assertEquals(2, idDeleted); SuspensionPoint hit4 = new StepCommand("cont").readSuspension(debuggerConnection); assertBreakpoint(hit4, "test.rb", 2); SuspensionPoint hit5 = new StepCommand("cont").readSuspension(debuggerConnection); assertNull("null expected, but was: " + hit5, hit5); } public void testSimpleCycleSteppingWorks() throws Exception { createSocket(new String[] { "1.upto(2) {", "puts 'a'", "}", "puts 'b'" }); int breakpointId1 = new BreakpointCommand("b test.rb:2").executeWithResult(debuggerConnection); assertEquals(1, breakpointId1); int breakpointId2 = new BreakpointCommand("b test.rb:4").executeWithResult(debuggerConnection); assertEquals(2, breakpointId2); SuspensionReader reader = startDebugger(); assertBreakpoint(reader.readSuspension(), "test.rb", 2); sendRuby("cont"); // 2 -> 2 assertBreakpoint(getSuspensionReader().readSuspension(), "test.rb", 2); sendRuby("cont"); // 2 -> 2 assertBreakpoint(getSuspensionReader().readSuspension(), "test.rb", 4); sendRuby("cont"); // 4 -> finish SuspensionPoint hit = getSuspensionReader().readSuspension(); assertNull("null expected, but was: " + hit, hit); } public void testStoppingOnOneLineTwice() throws Exception { createSocket(new String[] { "1.upto(2) {", "if true", "puts 'a'", "end", "}", "puts 'b'" }); sendRuby("b test.rb:2"); assertEquals(1, getBreakpointAddedReader().readBreakpointNo()); sendRuby("b test.rb:6"); assertEquals(2, getBreakpointAddedReader().readBreakpointNo()); sendRuby("cont"); // -> 2 assertBreakpoint(getSuspensionReader().readSuspension(), "test.rb", 2); sendRuby("cont"); // 2 -> 2 assertBreakpoint(getSuspensionReader().readSuspension(), "test.rb", 2); sendRuby("cont"); // 2 -> 2 assertBreakpoint(getSuspensionReader().readSuspension(), "test.rb", 2); sendRuby("cont"); // 2 -> 2 assertBreakpoint(getSuspensionReader().readSuspension(), "test.rb", 2); sendRuby("cont"); // 2 -> 2 assertBreakpoint(getSuspensionReader().readSuspension(), "test.rb", 6); sendRuby("cont"); // 6 -> finish SuspensionPoint hit = getSuspensionReader().readSuspension(); assertNull("null expected, but was: " + hit, hit); } public void assertBreakpoint(SuspensionPoint hit, String file, int line) { assertNotNull(hit); assertTrue(hit.isBreakpoint()); assertEquals(line, hit.getLine()); assertEquals(file, hit.getFile()); } public void testException() throws Exception { // per default catch is set to StandardError, i.e. every raise of a // subclass of StandardError // will suspend createSocket(new String[] { "puts 'a'", "raise 'message \\dir\\file: <xml/>\n<8>'", "puts 'c'" }); GenericCommand catchCommand = new GenericCommand("catch StandardError", true /* iscontrol */); catchCommand.execute(debuggerConnection); SuspensionPoint hit = startDebugger().readSuspension(); assertNotNull(hit); assertEquals(3, hit.getLine()); assertEquals(getOSIndependent(getTmpDir() + "test.rb"), hit.getFile()); assertTrue(hit.isException()); assertEquals("message \\dir\\file: <xml/> <8>", ((ExceptionSuspensionPoint) hit).getExceptionMessage()); assertEquals("RuntimeError", ((ExceptionSuspensionPoint) hit).getExceptionType()); // TODO: test catch off with a second exception sendRuby("catch off"); sendRuby("cont"); } public void testExceptionsIgnoredByDefault() throws Exception { createSocket(new String[] { "puts 'a'", "raise 'dont stop'" }); SuspensionPoint hit = startDebugger().readSuspension(); assertNull(hit); } public void testExceptionHierarchy() throws Exception { createSocket(new String[] { "class MyError < StandardError", "end", "begin", "raise StandardError.new", "rescue", "end", "raise MyError.new" }); GenericCommand catchCommand = new GenericCommand("catch MyError", true /* iscontrol */); catchCommand.execute(debuggerConnection); SuspensionPoint hit = startDebugger().readSuspension(); assertNotNull(hit); assertEquals(7, hit.getLine()); assertEquals("MyError", ((ExceptionSuspensionPoint) hit).getExceptionType()); sendRuby("cont"); hit = getSuspensionReader().readSuspension(); assertNull(hit); } public void testBreakpointNeverReached() throws Exception { createSocket(new String[] { "puts 'a'", "puts 'b'", "puts 'c'" }); new BreakpointCommand("b test.rb:10").executeWithResult(debuggerConnection); log("Waiting for breakpoint.."); SuspensionPoint hit = startDebugger().readSuspension(); assertNull(hit); } private void log(String string) { if (VERBOSE) System.out.println(string); } public void testStepOver() throws Exception { createSocket(new String[] { "puts 'a'", "puts 'b'", "puts 'c'" }); BreakpointCommand breakpointCommand = new BreakpointCommand("b test.rb:2"); sendCommand(breakpointCommand); breakpointCommand.getBreakpointAddedReader().readBreakpointNo(); startDebugger().readSuspension(); SuspensionPoint info = new StepCommand("next").readSuspension(debuggerConnection); assertEquals(3, info.getLine()); assertEquals(getOSIndependent(getTmpDir() + "test.rb"), info.getFile()); assertTrue(info.isStep()); assertEquals(1, ((StepSuspensionPoint) info).getFramesNumber()); info = new StepCommand("next").readSuspension(debuggerConnection); assertNull(info); } public void testStepOverFrames() throws Exception { createSocket(new String[] { "require 'test2.rb'", "puts 'a'", "Test2.new.print()" }); writeFile("test2.rb", new String[] { "class Test2", "def print", "puts 'XX'", "puts 'XX'", "end", "end" }); runTo("test2.rb", 3); sendRuby("next"); SuspensionPoint info = getSuspensionReader().readSuspension(); assertEquals(4, info.getLine()); assertEquals(getOSIndependent(getTmpDir() + "test2.rb"), info.getFile()); assertTrue(info.isStep()); assertEquals(2, ((StepSuspensionPoint) info).getFramesNumber()); sendRuby("next"); info = getSuspensionReader().readSuspension(); assertNull(info); } public void testStepOverInDifferentFrame() throws Exception { createSocket(new String[] { "require 'test2.rb'", "Test2.new.print()", "puts 'a'" }); writeFile("test2.rb", new String[] { "class Test2", "def print", "puts 'XX'", "puts 'XX'", "end", "end" }); runTo("test2.rb", 4); sendRuby("next"); SuspensionPoint info = getSuspensionReader().readSuspension(); assertEquals(3, info.getLine()); assertEquals(getOSIndependent(getTmpDir() + "test.rb"), info.getFile()); assertTrue(info.isStep()); assertEquals(1, ((StepSuspensionPoint) info).getFramesNumber()); sendRuby("cont"); } public void testStepReturn() throws Exception { createSocket(new String[] { "require 'test2.rb'", "Test2.new.print()", "puts 'a'" }); writeFile("test2.rb", new String[] { "class Test2", "def print", "puts 'XX'", "puts 'XX'", "end", "end" }); runTo("test2.rb", 4); sendRuby("next"); SuspensionPoint info = getSuspensionReader().readSuspension(); assertEquals(3, info.getLine()); assertEquals(getOSIndependent(getTmpDir() + "test.rb"), info.getFile()); assertTrue(info.isStep()); assertEquals(1, ((StepSuspensionPoint) info).getFramesNumber()); sendRuby("cont"); } public void testHitBreakpointWhileSteppingOver() throws Exception { createSocket(new String[] { "require 'test2.rb'", "Test2.new.print()", "puts 'a'" }); writeFile("test2.rb", new String[] { "class Test2", "def print", "puts 'XX'", "puts 'XX'", "end", "end" }); new BreakpointCommand("b test2.rb:4").executeWithResult(debuggerConnection); runTo("test.rb", 2); sendRuby("next"); SuspensionPoint info = getSuspensionReader().readSuspension(); assertEquals(4, info.getLine()); assertEquals("test2.rb", info.getFile()); assertTrue(info.isBreakpoint()); sendRuby("cont"); } public void testStepInto() throws Exception { createSocket(new String[] { "require 'test2.rb'", "puts 'a'", "Test2.new.print()" }); writeFile("test2.rb", new String[] { "class Test2", "def print", "puts 'XX'", "puts 'XX'", "end", "end" }); runTo("test.rb", 3); sendRuby("step"); SuspensionPoint info = getSuspensionReader().readSuspension(); assertEquals(3, info.getLine()); assertEquals(getOSIndependent(getTmpDir() + "test2.rb"), info.getFile()); assertTrue(info.isStep()); assertEquals(2, ((StepSuspensionPoint) info).getFramesNumber()); sendRuby("cont"); } protected RubyStackFrame createStackFrame() throws Exception { RubyStackFrame stackFrame = new RubyStackFrame(null, "", 5, 1); // RubyThread // thread // = new // RubyThread(null) // ; // thread.addStackFrame(stackFrame) ; return stackFrame; } public void testCommandList() throws Exception { // test that commands separated by comma will be processed createSocket(new String[] { "a=5", "puts a" }); runToLine(2); sendRuby("v l ; v i a"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); variables = getVariableReader().readVariables(createStackFrame()); assertEquals(0, variables.length); // escaped semicolon sendRuby("v inspect a=1\\;a+1"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals("Handled escaded semicolon", 1, variables.length); assertEquals("2", variables[0].getValue().getValueString()); sendRuby("cont"); } public void testVariableNil() throws Exception { createSocket(new String[] { "puts 'a'", "puts 'b'", "stringA='XX'" }); runToLine(2); sendRuby("v l"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("stringA", variables[0].getName()); assertEquals("nil", variables[0].getValue().getValueString()); assertEquals(null, variables[0].getValue().getReferenceTypeName()); assertTrue(!variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testVariableWithXmlContent() throws Exception { createSocket(new String[] { "stringA='<start test=\"&\"/>'", "testHashValue=Hash[ '$&' => nil]", "puts 'b'" }); runToLine(3); sendRuby("v l"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(2, variables.length); assertEquals("stringA", variables[0].getName()); assertEquals("<start test=\"&\"/>", variables[0].getValue().getValueString()); assertTrue(variables[0].isLocal()); // the testHashValue contains an example, where the name consists of // special characters sendRuby("v i testHashValue"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("'$&'", variables[0].getName()); sendRuby("cont"); } public void testVariableInObject() throws Exception { createSocket(new String[] { "class Test", "def initialize", "@y=5", "puts @y", "end", "def to_s", "'test'", "end", "end", "Test.new()" }); runTo("test.rb", 4); // Read numerical variable sendRuby("v l"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("self", variables[0].getName()); assertEquals("test", variables[0].getValue().getValueString()); assertEquals("Test", variables[0].getValue().getReferenceTypeName()); assertTrue(variables[0].getValue().hasVariables()); sendRuby("v i self"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("@y", variables[0].getName()); assertEquals("5", variables[0].getValue().getValueString()); assertEquals("Fixnum", variables[0].getValue().getReferenceTypeName()); assertTrue(!variables[0].isStatic()); assertTrue(!variables[0].isLocal()); assertTrue(variables[0].isInstance()); assertTrue(!variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testStaticVariables() throws Exception { createSocket(new String[] { "class Test", "@@staticVar=55", "def method", "puts 'a'", "end", "end", "test=Test.new()", "test.method()" }); runTo("test.rb", 4); sendRuby("v l"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("self", variables[0].getName()); assertTrue(variables[0].getValue().hasVariables()); sendRuby("v i self"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("@@staticVar", variables[0].getName()); assertEquals("55", variables[0].getValue().getValueString()); assertEquals("Fixnum", variables[0].getValue().getReferenceTypeName()); assertTrue(variables[0].isStatic()); assertTrue(!variables[0].isLocal()); assertTrue(!variables[0].isInstance()); assertTrue(!variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testSingletonStaticVariables() throws Exception { createSocket(new String[] { "class Test", "def method", "puts 'a'", "end", "class << Test", "@@staticVar=55", "end", "end", "Test.new().method()" }); runTo("test.rb", 3); sendRuby("v i self"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("@@staticVar", variables[0].getName()); assertEquals("55", variables[0].getValue().getValueString()); assertEquals("Fixnum", variables[0].getValue().getReferenceTypeName()); assertTrue(variables[0].isStatic()); assertTrue(!variables[0].isLocal()); assertTrue(!variables[0].isInstance()); assertTrue(!variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testConstants() throws Exception { createSocket(new String[] { "class Test", "TestConstant=5", "end", "test=Test.new()", "puts 'a'" }); runTo("test.rb", 5); sendRuby("v i test"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("TestConstant", variables[0].getName()); assertEquals("5", variables[0].getValue().getValueString()); assertEquals("Fixnum", variables[0].getValue().getReferenceTypeName()); assertTrue(variables[0].isConstant()); assertTrue(!variables[0].isStatic()); assertTrue(!variables[0].isLocal()); assertTrue(!variables[0].isInstance()); assertTrue(!variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testConstantDefinedInBothClassAndSuperclass() throws Exception { createSocket(new String[] { "class A", "TestConstant=5", "TestConstant2=2", "end", "class B < A", "TestConstant=6", "end", "b=B.new()", "puts 'a'" }); runTo("test.rb", 9); sendRuby("v i b"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("TestConstant", variables[0].getName()); assertEquals("6", variables[0].getValue().getValueString()); assertEquals("Fixnum", variables[0].getValue().getReferenceTypeName()); assertTrue(variables[0].isConstant()); assertTrue(!variables[0].isStatic()); assertTrue(!variables[0].isLocal()); assertTrue(!variables[0].isInstance()); assertTrue(!variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testVariableString() throws Exception { createSocket(new String[] { "stringA='XX'", "puts stringA" }); runToLine(2); // Read numerical variable sendRuby("v l"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("stringA", variables[0].getName()); assertEquals("XX", variables[0].getValue().getValueString()); assertEquals("String", variables[0].getValue().getReferenceTypeName()); assertTrue(!variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testVariableLocal() throws Exception { createSocket(new String[] { "class User", "def initialize(id)", "@id=id", "end", "end", "class CallClass", "def method(user)", "puts user", "end", "end", "CallClass.new.method(User.new(22))" }); runTo("test.rb", 8); sendRuby("v local"); RubyVariable[] localVariables = getVariableReader().readVariables(createStackFrame()); assertEquals(2, localVariables.length); RubyVariable userVariable = localVariables[1]; // sendRuby("v i 1 " + userVariable.getObjectId()); sendRuby("v i " + userVariable.getObjectId()); RubyVariable[] userVariables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, userVariables.length); assertEquals("@id", userVariables[0].getName()); assertEquals("22", userVariables[0].getValue().getValueString()); assertEquals("Fixnum", userVariables[0].getValue().getReferenceTypeName()); assertTrue(!userVariables[0].getValue().hasVariables()); sendRuby("cont"); } public void testVariableInstance() throws Exception { createSocket(new String[] { "require 'test2.rb'", "customObject=Test2.new", "puts customObject" }); writeFile("test2.rb", new String[] { "class Test2", "def initialize", "@y=5", "end", "def to_s", "'test'", "end", "end" }); runTo("test2.rb", 6); sendRuby("frame 2 ; v i customObject"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("@y", variables[0].getName()); assertEquals("5", variables[0].getValue().getValueString()); assertEquals("Fixnum", variables[0].getValue().getReferenceTypeName()); assertTrue(!variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testVariableArray() throws Exception { createSocket(new String[] { "array = []", "array << 1", "array << 2", "puts 'a'" }); runTo("test.rb", 4); sendRuby("v local"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("array", variables[0].getName()); assertTrue("array has children", variables[0].getValue().hasVariables()); sendRuby("v i array"); RubyVariable[] elements = getVariableReader().readVariables(variables[0]); assertEquals(2, elements.length); assertEquals("[0]", elements[0].getName()); assertEquals("1", elements[0].getValue().getValueString()); assertEquals("Fixnum", elements[0].getValue().getReferenceTypeName()); assertEquals("array[0]", elements[0].getQualifiedName()); sendRuby("cont"); } public void testVariableHashWithStringKeys() throws Exception { createSocket(new String[] { "hash = Hash['a' => 'z', 'b' => 'y']", "puts 'a'" }); runTo("test.rb", 2); sendRuby("v local"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("hash", variables[0].getName()); assertTrue("hash has children", variables[0].getValue().hasVariables()); sendRuby("v i hash"); RubyVariable[] elements = getVariableReader().readVariables(variables[0]); assertEquals(2, elements.length); assertEquals("'a'", elements[0].getName()); assertEquals("z", elements[0].getValue().getValueString()); assertEquals("String", elements[0].getValue().getReferenceTypeName()); assertEquals("hash['a']", elements[0].getQualifiedName()); sendRuby("cont"); } public void testVariableHashWithObjectKeys() throws Exception { createSocket(new String[] { "class KeyAndValue", "def initialize(v)", "@a=v", "end", "def to_s", "return @a.to_s", "end", "end", "hash = Hash[KeyAndValue.new(55) => KeyAndValue.new(66)]", "puts 'a'" }); runTo("test.rb", 10); sendRuby("v local"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("hash", variables[0].getName()); assertTrue("hash has children", variables[0].getValue().hasVariables()); sendRuby("frame 1 ; v i " + variables[0].getObjectId()); RubyVariable[] elements = getVariableReader().readVariables(variables[0]); assertEquals(1, elements.length); assertEquals("55", elements[0].getName()); // assertEquals("z", elements[0].getValue().getValueString()); assertEquals("KeyAndValue", elements[0].getValue().getReferenceTypeName()); // get the value sendRuby("frame 1 ; v i " + elements[0].getObjectId()); RubyVariable[] values = getVariableReader().readVariables(variables[0]); assertEquals(1, values.length); assertEquals("@a", values[0].getName()); assertEquals("Fixnum", values[0].getValue().getReferenceTypeName()); assertEquals("66", values[0].getValue().getValueString()); sendRuby("cont"); } public void testVariableArrayEmpty() throws Exception { createSocket(new String[] { "emptyArray = []", "puts 'a'" }); runTo("test.rb", 2); sendRuby("v local"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("emptyArray", variables[0].getName()); assertTrue("array does not have children", !variables[0].getValue().hasVariables()); sendRuby("cont"); } public void testVariableInstanceNested() throws Exception { createSocket(new String[] { "class Test", "def initialize(test)", "@privateTest = test", "end", "end", "test2 = Test.new(Test.new(nil))", "puts test2" }); runToLine(7); sendRuby("v l"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); RubyVariable test2Variable = variables[0]; assertEquals("test2", test2Variable.getName()); assertEquals("test2", test2Variable.getQualifiedName()); sendRuby("v i " + test2Variable.getQualifiedName()); variables = getVariableReader().readVariables(test2Variable); assertEquals(1, variables.length); RubyVariable privateTestVariable = variables[0]; assertEquals("@privateTest", privateTestVariable.getName()); assertEquals("test2.@privateTest", privateTestVariable.getQualifiedName()); assertTrue(privateTestVariable.getValue().hasVariables()); sendRuby("v i " + privateTestVariable.getQualifiedName()); variables = getVariableReader().readVariables(privateTestVariable); assertEquals(1, variables.length); RubyVariable privateTestprivateTestVariable = variables[0]; assertEquals("@privateTest", privateTestprivateTestVariable.getName()); assertEquals("test2.@privateTest.@privateTest", privateTestprivateTestVariable.getQualifiedName()); assertEquals("nil", privateTestprivateTestVariable.getValue().getValueString()); assertTrue(!privateTestprivateTestVariable.getValue().hasVariables()); sendRuby("cont"); } public void testInspect() throws Exception { createSocket(new String[] { "class Test", "def calc(a)", "a = a*2", "return a", "end", "end", "test=Test.new()", "a=3", "test.calc(a)" }); runToLine(4); // test variable value in stack 1 (top stack frame) sendRuby("frame 1 ; v inspect a*2"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals("There is one variable returned.", 1, variables.length); assertEquals("Result in frame 1 is 12", "12", variables[0].getValue().getValueString()); // test variable value in stack 2 (caller stack) sendRuby("frame 2 ; v inspect a*2"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals("There is one variable returned.", 1, variables.length); assertEquals("Result in frame 2 is 6", "6", variables[0].getValue().getValueString()); // test more complex expression sendRuby("frame 1 ; v inspect Test.new().calc(5)"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals("There is one variable returned.", 1, variables.length); assertEquals("Result is 10", "10", variables[0].getValue().getValueString()); sendRuby("cont"); } public void testInspectTemporaryArray() throws Exception { createSocket(new String[] { "a=0", "puts a=2" }); runToLine(2); sendRuby("v inspect %w[a b c]"); // this inspection will create a new temporary arrray object RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals("There is one variable returned which contains the array.", 1, variables.length); // the following two commands starts the garbage collector. This test // is somehow implementation aware. It makes sure that the objects which // are // created as a result of an expression are referenced so that they will // not be // swept away from the GC // TODO: actually garbage_collect does nothing, also the GC can not be // enabled with GC.enable() sendRuby("v inspect ObjectSpace.garbage_collect\\;sleep(2)"); RubyVariable[] gcResult = getVariableReader().readVariables(createStackFrame()); assertEquals("There is one variable returned as result of running the GC", 1, gcResult.length); sendRuby("v i " + variables[0].getObjectId()); RubyVariable[] elements = getVariableReader().readVariables(variables[0]); assertEquals("The array contains 3 elements", 3, elements.length); sendRuby("cont"); } public void testInspectNil() throws Exception { createSocket(new String[] { "puts 'dummy'", "puts 'dummy'" }); runToLine(2); sendRuby("v inspect nil"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals("There is one variable returned which is nil.", 1, variables.length); assertEquals("nil", variables[0].getValue().getValueString()); sendRuby("cont"); } public void testSendCommandWithSpecialCharacters() throws Exception { // the inspect command can contain arbitrary characters // a %w for example can raise an error when it is directly given to // printf sendRuby("v inspect %w[a b]"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); sendRuby("cont"); // just do not fail } public void testInspectError() throws Exception { createSocket(new String[] { "puts 'test'", "puts 'test'" }); runToLine(2); sendRuby("v inspect a*2"); try { getVariableReader().readVariables(createStackFrame()); fail("RubyProcessingException not thrown."); } catch (RubyProcessingException e) { assertNotNull(e.getMessage()); assertFalse(e.getMessage().indexOf("Timeout") > -1) ; sendRuby("cont"); } } public void testInspectTimeout() throws Exception { createSocket(new String[] { "puts 'test'", "puts 'test'" }); runToLine(2); // timeout is 10 seconds sendRuby("v inspect sleep(15)"); try { getVariableReader().readVariables(createStackFrame()); fail("Timeout did not occur."); } catch (RubyProcessingException e) { assertTrue(e.getMessage().indexOf("Timeout") > -1) ; sendRuby("cont"); } } public void testEvalError() throws Exception { createSocket(new String[] { "puts 'test'", "puts 'test'" }); runToLine(2); sendRuby("eval unknown_"); try { getEvalReader().readEvalResult(); } catch (RubyProcessingException e) { assertNotNull(e.getMessage()); sendRuby("cont"); return; } fail("RubyProcessingException not thrown."); } public void testStaticVariableInstanceNested() throws Exception { createSocket(new String[] { "class TestStatic", "def initialize(no)", "@no = no", "end", "@@staticVar=TestStatic.new(2)", "end", "test = TestStatic.new(1)", "puts test" }); runToLine(8); sendRuby("v i test.@@staticVar"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); assertEquals(2, variables.length); assertEquals("@no", variables[0].getName()); assertEquals("2", variables[0].getValue().getValueString()); assertEquals("@@staticVar", variables[1].getName()); assertTrue("2", variables[1].getValue().hasVariables()); } public void testVariablesInFrames() throws Exception { createSocket(new String[] { "require 'test2.rb'", "y=5", "Test2.new().test()" }); writeFile("test2.rb", new String[] { "class Test2", "def test", "y=6", "puts y", "end", "end" }); runTo("test2.rb", 4); sendRuby("v l"); RubyVariable[] variables = getVariableReader().readVariables(createStackFrame()); // there are 2 variables self and y assertEquals(2, variables.length); // the variables are sorted: self = variables[0], y = variables[1] assertEquals("y", variables[1].getName()); assertEquals("6", variables[1].getValue().getValueString()); sendRuby("frame 1 ; v l"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals(2, variables.length); assertEquals("y", variables[1].getName()); assertEquals("6", variables[1].getValue().getValueString()); sendRuby("frame 2 ; v l"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("y", variables[0].getName()); assertEquals("5", variables[0].getValue().getValueString()); // 20 is out of range, then the top level (the one with the highest // number) // will be used, in this case 2 sendRuby("frame 20 ; v l"); variables = getVariableReader().readVariables(createStackFrame()); assertEquals(1, variables.length); assertEquals("y", variables[0].getName()); assertEquals("5", variables[0].getValue().getValueString()); } public void testFrames() throws Exception { createSocket(new String[] { "require 'test2.rb'", "test = Test2.new()", "test.print()", "test.print()" }); writeFile("test2.rb", new String[] { "class Test2", "def print", "puts 'Test2.print'", "end", "end" }); runTo("test2.rb", 3); sendRuby("b test.rb:4"); getBreakpointAddedReader().readBreakpointNo(); sendRuby("w"); RubyThread thread = new RubyThread(null, 0, "run"); getFramesReader().readFrames(thread); assertEquals(2, thread.getStackFrames().length); RubyStackFrame frame1 = (RubyStackFrame) thread.getStackFrames()[0]; assertEquals(getOSIndependent(getTmpDir() + "test2.rb"), frame1.getFileName()); assertEquals(1, frame1.getIndex()); assertEquals(3, frame1.getLineNumber()); RubyStackFrame frame2 = (RubyStackFrame) thread.getStackFrames()[1]; assertEquals(getOSIndependent(getTmpDir() + "test.rb"), frame2.getFileName()); assertEquals(2, frame2.getIndex()); assertEquals(3, frame2.getLineNumber()); sendRuby("cont"); getSuspensionReader().readSuspension(); sendRuby("w"); getFramesReader().readFrames(thread); assertEquals(1, thread.getStackFramesSize()); sendRuby("cont"); } public void testFramesWhenThreadSpawned() throws Exception { createSocket(new String[] { "def startThread", "Thread.new() { a = 5 }", "end", "def calc", "5 + 5", "end", "startThread()", "calc()" }); runTo("test.rb", 5); RubyThread thread = new RubyThread(null, 0, "run"); sendRuby("w"); getFramesReader().readFrames(thread); assertEquals(2, thread.getStackFramesSize()); } public void testThreadFramesAndVariables() throws Exception { createSocket(new String[] { "Thread.new {", "a=5", "x=6", "puts 'x'", "}", "b=10", "b=11" }); setBreakpoint(7) ; runToLine(3); sendRuby("th l"); ThreadInfo[] threads = getThreadInfoReader().readThreads(); sendRuby("th resume 1"); getSuspensionReader().readSuspension(); getSuspensionReader().readSuspension(); // the main thread and the "puts 'a'" - thread are active sendRuby("th l"); threads = getThreadInfoReader().readThreads(); assertEquals(2, threads.length); sendRuby("th " + threads[0].getId() + " ; w "); RubyStackFrame[] stackFrames = getFramesReader().readFrames(new RubyThread(null, 1, "run")); assertEquals(1, stackFrames.length); assertEquals(7, stackFrames[0].getLineNumber()); sendRuby("th " + threads[0].getId() + " ; v l"); RubyVariable[] variables = getVariableReader().readVariables(stackFrames[0]); assertEquals(1, variables.length); assertEquals("b", variables[0].getName()); sendRuby("th " + threads[1].getId() + " ; w"); stackFrames = getFramesReader().readFrames(new RubyThread(null, 1, "run")); assertEquals(1, stackFrames.length); assertEquals(3, stackFrames[0].getLineNumber()); sendRuby("th " + threads[1].getId() + " ; v l"); variables = getVariableReader().readVariables(stackFrames[0]); assertEquals("a", variables[0].getName()); assertEquals("b", variables[1].getName()); // there is a third variable 'x' for ruby 1.8.0 sendRuby("next"); getSuspensionReader().readSuspension(); sendRuby("th " + threads[1].getId() + " ; v l"); variables = getVariableReader().readVariables(stackFrames[0]); assertEquals(3, variables.length); assertEquals("a", variables[0].getName()); assertEquals("b", variables[1].getName()); assertEquals("x", variables[2].getName()); } }