package org.dresdenocl.debug.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.junit.Assert; import org.dresdenocl.debug.OclDebugger; import org.dresdenocl.facade.Ocl2ForEclipseFacade; import org.dresdenocl.model.IModel; import org.dresdenocl.model.ModelAccessException; import org.dresdenocl.modelbus.ModelBusPlugin; import org.dresdenocl.modelinstance.IModelInstance; import org.dresdenocl.modelinstancetype.types.IModelInstanceObject; import org.dresdenocl.parser.ParseException; import org.dresdenocl.pivotmodel.Constraint; import org.dresdenocl.pivotmodel.Type; import org.dresdenocl.testsuite._abstract.AbstractDresdenOclTest; import org.junit.After; import org.junit.Before; public abstract class AbstractDebuggerTest extends AbstractDresdenOclTest { /** Declares possible events to be happened during debugging. */ protected enum DebugEvent { CONSTRAINT_INTERPRETED, STARTED, SUSPENDED, RESUMED, TERMINATED } /** Declared possible debug steps to be executed by the debugger. */ protected enum DebugStep { RESUME, TERMINATE, STEP_INTO, STEP_OVER, STEP_RETURN } protected static final String MODEL_PATH = "target/classes/resource/package01/TestModel.class"; protected static final String MODEL_INSTANCE_PATH = "target/classes/resource/package01/TestModelInstance.class"; protected static final String RESOURCE01_PATH = "resources/resource01.ocl"; /** The last lines received as an event from the {@link OclDebugger}. */ protected static volatile List<String> lastReceivedLines = new LinkedList<String>(); protected static IModel modelUnderTest; protected static IModelInstance modelInstanceUnderTest; /** Socket the {@link OclDebugger} is sending events to. */ protected static Socket socket; /** * Thread used to listen for events on the {@link OclDebugger}'s * {@link Socket}. */ protected static SocketListener socketListener; protected static OclDebugger debugger; public static void setUp() throws Exception { AbstractDresdenOclTest.setUp(); } @Before public void before() { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // assertEquals(null, ModelBusPlugin.getModelRegistry().getActiveModel()); } @After public void after() { debugStepAndWaitFor(DebugStep.TERMINATE, DebugEvent.TERMINATED, debugger); try { debugger.shutdown(); } catch (Exception e) { // drop it } assertTrue(ModelBusPlugin.getModelInstanceRegistry().removeModelInstance( modelInstanceUnderTest)); assertTrue(ModelBusPlugin.getModelRegistry().removeModel(modelUnderTest)); modelUnderTest = null; modelInstanceUnderTest = null; socketListener.terminate = true; debugger = null; lastReceivedLines = new LinkedList<String>(); if (null != socket) { try { socket.close(); } catch (IOException e) { /* Not important. */ } } // no else. } protected static IModel getModel(String modelPath) throws ModelAccessException { IModel result; File modelFile; try { modelFile = AbstractDebuggerTest.getFile(modelPath, DebugTestPlugin.PLUGIN_ID); } catch (IOException e) { throw new ModelAccessException(e.getMessage(), e); } assertNotNull(modelFile); result = Ocl2ForEclipseFacade.getModel(modelFile, Ocl2ForEclipseFacade.JAVA_META_MODEL); return result; } protected static IModelInstance getModelInstance(String modelInstancePath) throws ModelAccessException { IModelInstance result; File modelInstanceFile; try { modelInstanceFile = AbstractDebuggerTest.getFile(modelInstancePath, DebugTestPlugin.PLUGIN_ID); } catch (IOException e) { throw new ModelAccessException(e.getMessage(), e); } assertNotNull(modelInstanceFile); result = Ocl2ForEclipseFacade.getModelInstance(modelInstanceFile, modelUnderTest, Ocl2ForEclipseFacade.JAVA_MODEL_INSTANCE_TYPE); return result; } protected static Set<IModelInstanceObject> getModelInstanceObjects( String modelInstancePath, String... modelObjects) throws ModelAccessException { modelInstanceUnderTest = getModelInstance(modelInstancePath); assertNotNull(modelInstanceUnderTest); ModelBusPlugin.getModelInstanceRegistry().addModelInstance( modelInstanceUnderTest); ModelBusPlugin.getModelInstanceRegistry().setActiveModelInstance( modelUnderTest, modelInstanceUnderTest); // check the parameter assertTrue(modelObjects != null && modelObjects.length >= 1); // get the types Type objectType = modelUnderTest.findType(Arrays.asList(modelObjects)); assertNotNull("cannot find type", objectType); // find the objects Set<IModelInstanceObject> result = modelInstanceUnderTest.getAllInstances(objectType); assertNotNull("could not get all instances", result); return result; } protected static List<Constraint> getConstraints(String modelPath, String resourcePath) throws ModelAccessException, ParseException { modelUnderTest = getModel(modelPath); assertNotNull("could not get model", modelUnderTest); ModelBusPlugin.getModelRegistry().addModel(modelUnderTest); ModelBusPlugin.getModelRegistry().setActiveModel(modelUnderTest); List<Constraint> result; File resourceFile; try { resourceFile = AbstractDebuggerTest.getFile(resourcePath, DebugTestPlugin.PLUGIN_ID); } catch (IOException e) { throw new ModelAccessException(e.getMessage(), e); } assertNotNull("resource file is null", resourceFile); assertTrue("Cannot read the resource file", resourceFile.canRead()); result = Ocl2ForEclipseFacade.parseConstraints(resourceFile, modelUnderTest, true); assertNotNull("parse result is null", result); assertTrue("parse result is empty", result.size() >= 1); return result; } protected static int findFreePort() { ServerSocket socket = null; try { socket = new ServerSocket(0); return socket.getLocalPort(); } catch (IOException e) { } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { } } } return -1; } /** * Helper method asserting that the {@link OclDebugger} is at the right line. * * @param currentLine * The line to be asserted. * @param debugger * The {@link OclDebugger}. */ protected void assertCurrentLine(int currentLine, OclDebugger debugger) { assertEquals("The OclDebugger should bet at line " + currentLine + ".", currentLine, debugger.getCurrentLine()); } /** * Helper method asserting that the last entry of the call stack has the right * name. * * @param name * The name to be asserted. * @param debugger * The {@link OclDebugger}. */ protected void assertStackName(String name, OclDebugger debugger) { assertNotNull("The call stack should not be empty.", debugger.getStack()); assertFalse("The call stack should not be empty.", debugger.getStack().length == 0); String[] debuggerStack = debugger.getStack(); String callStackName = debuggerStack[debuggerStack.length - 1]; assertEquals( "The name of the last entry on the call stack should start with '" + name + "'", name, callStackName.substring(0, callStackName.indexOf(","))); } /** * Helper method asserting the size of the call stack. * * @param stackSize * The size of the stack. * @param debugger * The {@link OclDebugger}. */ protected void assertStackSize(int stackSize, OclDebugger debugger) { assertEquals("The Stack should have the size " + stackSize + ".", stackSize, debugger.getStack().length); } /** * Helper method asserting that a variable with a given name is visible in the * current stack frame. * * @param name * The name of the variable expected to be visible. * @param debugger * The {@link OclDebugger} to assert. */ protected void assertVariableExist(String name, OclDebugger debugger) { assertTrue("The variable '" + name + "' should be visible for the current stack.", getVariablesFromStackFrame(debugger).keySet().contains(name)); } /** * Helper method asserting the count of variables visible in the current stack * frame. * * @param count * The expected count of variables. * @param debugger * The {@link OclDebugger} to assert. */ protected void assertVariableNumber(int count, OclDebugger debugger) { assertEquals( "The expected number of variables for the current stack frame was wrong.", count, getVariablesFromStackFrame(debugger).size()); } /** * Helper method executing a given {@link DebugStep} and waiting for a given * {@link DebugEvent} afterwards. Fails, if the {@link DebugEvent} will not * happen within 10 seconds. * * @param step * The {@link DebugStep} to be executed. * @param event * The {@link DebugEvent} to be wait for. * @param debugger * The {@link OclDebugger}. */ protected void debugStepAndWaitFor(DebugStep step, DebugEvent event, OclDebugger debugger) { debugStepAndWaitFor(step, event, debugger, 10000); } /** * Helper method executing a given {@link DebugStep} and waiting for a given * {@link DebugEvent} afterwards. Fails, if the {@link DebugEvent} will not * happen within the specified timeout. * * @param step * The {@link DebugStep} to be executed. * @param event * The {@link DebugEvent} to be wait for. * @param debugger * The {@link OclDebugger}. * @param timeout * The timeout. */ protected void debugStepAndWaitFor(DebugStep step, DebugEvent event, OclDebugger debugger, long timeout) { lastReceivedLines.clear(); switch (step) { case RESUME: debugger.resume(); break; case TERMINATE: debugger.terminate(); break; case STEP_INTO: debugger.stepInto(); break; case STEP_OVER: debugger.stepOver(); break; case STEP_RETURN: debugger.stepReturn(); break; default: Assert.fail("Unknown debugstep: " + step.name()); } /* * try { Thread.sleep(100); } catch (InterruptedException e) { // Not * important. } */ waitForEvent(event, timeout); } /** * Helper method creating an {@link OclDebugger} for a given OCL resource. * * Besides, a {@link SocketListener} will be created and started that puts the * last received event into the lastReceivedLine field. * * @param oclResource * Path leading to the OCL file used for this {@link OclDebugger} * relative to the root directory of this test plugin. * @return The created {@link OclDebugger} * @throws Exception */ protected OclDebugger generateDebugger(String oclResource) throws Exception { synchronized (System.out) { System.out.println("== generateDebugger =="); System.out.println(oclResource); } final String[] modelObjects = { "TestClass" }; final List<Constraint> constraints; final Set<IModelInstanceObject> imio; final int eventPort = findFreePort(); constraints = getConstraints(MODEL_PATH, oclResource); imio = getModelInstanceObjects(MODEL_INSTANCE_PATH, modelObjects); assertNotNull("There is no active model set.", ModelBusPlugin .getModelRegistry().getActiveModel()); assertNotNull( "There is no active model instance for the model under test.", ModelBusPlugin.getModelInstanceRegistry().getActiveModelInstance( modelUnderTest)); debugger = new OclDebugger(modelInstanceUnderTest); debugger.setDebugMode(true); new Thread(new Runnable() { @Override public void run() { debugger.setEventPort(eventPort); debugger.interpretConstraint(constraints.get(0), imio.iterator().next()); } }).start(); // Thread.sleep(1000); socket = new Socket("localhost", eventPort); socketListener = new SocketListener(); socketListener.start(); return debugger; } /** * Helper method returning all variables for the last stack frame as a Map. * * @param debugger * The {@link OclDebugger} for which the variables shall be returned. * @return The variables as a {@link Map} (keys are names as {@link String} * s). */ protected Map<String, Object> getVariablesFromStackFrame(OclDebugger debugger) { int frameId = debugger.getStack().length; if (frameId > 0) { frameId = frameId - 1; return debugger .getFrameVariables(debugger.getStack()[frameId].split(",")[1]); } else return Collections.emptyMap(); } /** * Helper method that waits till a given event will be sent by the * {@link OclDebugger} with a timeout of 10 seconds. * * @param event * The {@link DebugEvent} to be waited for. */ protected void waitForEvent(DebugEvent event) { waitForEvent(event, 10000); } /** * Helper method that waits till a given event will be sent by the * {@link OclDebugger}. * * @param event * The {@link DebugEvent} to be waited for. * @param timeout * The maximum amount of time to wait for the {@link DebugEvent} . * Fails otherwise. */ protected void waitForEvent(DebugEvent event, long timeout) { long currentMillis = System.currentTimeMillis(); while (!lastReceivedLines.contains(event.name() + ":")) { try { Thread.sleep(100); if (currentMillis + timeout < System.currentTimeMillis()) Assert.fail("Expected DebugEvent " + event.name() + " did not happen within specified timeout."); // no else. } catch (InterruptedException e) { /* Not that important. */ } } } /** * Helper {@link Thread} listening to the {@link Socket} of the * {@link OclDebugger} for events. * * @author Claas Wilke */ private class SocketListener extends Thread { /** * If this {@link SocketListener} shall terminate, set this to * <code>true</code>. */ public boolean terminate = false; /** * Creates a new {@link SocketListener}. */ public SocketListener() { lastReceivedLines.clear(); } /* * (non-Javadoc) * @see java.lang.Thread#run() */ @Override public void run() { BufferedReader in; try { in = new BufferedReader(new InputStreamReader(socket.getInputStream())); String inputLine; while (!terminate && (inputLine = in.readLine()) != null) { lastReceivedLines.add(inputLine); } } catch (IOException e) { if (!terminate) Assert.fail("Could not read from socket of debugger: " + e.getMessage()); // no else. } } } }