diff --git a/de.gebit.integrity.eclipse/icons/auto_pause_abort_disabled.gif b/de.gebit.integrity.eclipse/icons/auto_pause_abort_disabled.gif new file mode 100644 index 000000000..004f4acb6 Binary files /dev/null and b/de.gebit.integrity.eclipse/icons/auto_pause_abort_disabled.gif differ diff --git a/de.gebit.integrity.eclipse/icons/auto_pause_abort_on_abort.gif b/de.gebit.integrity.eclipse/icons/auto_pause_abort_on_abort.gif new file mode 100644 index 000000000..8159da870 Binary files /dev/null and b/de.gebit.integrity.eclipse/icons/auto_pause_abort_on_abort.gif differ diff --git a/de.gebit.integrity.eclipse/icons/auto_pause_abort_on_pause.gif b/de.gebit.integrity.eclipse/icons/auto_pause_abort_on_pause.gif new file mode 100644 index 000000000..66e8b6aba Binary files /dev/null and b/de.gebit.integrity.eclipse/icons/auto_pause_abort_on_pause.gif differ diff --git a/de.gebit.integrity.eclipse/src/de/gebit/integrity/eclipse/views/ConnectDialog.java b/de.gebit.integrity.eclipse/src/de/gebit/integrity/eclipse/views/ConnectDialog.java new file mode 100644 index 000000000..bf490c23c --- /dev/null +++ b/de.gebit.integrity.eclipse/src/de/gebit/integrity/eclipse/views/ConnectDialog.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package de.gebit.integrity.eclipse.views; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Shell; + +/** + * + * + * @author Rene Schneider - initial API and implementation + * + */ +public class ConnectDialog extends InputDialog { + + /** + * Whether auto-retry was chosen. + */ + private boolean autoRetryEnabled; + + /** + * @param parentShell + * @param aDialogTitle + * @param aDialogMessage + * @param anInitialValue + * @param aValidator + */ + public ConnectDialog(Shell aParentShell, String anInitialValue, IInputValidator aValidator) { + super(aParentShell, "Connect to test runner", "Please enter the hostname or IP address to connect to", + anInitialValue, null); + } + + @Override + protected void createButtonsForButtonBar(Composite aParent) { + super.createButtonsForButtonBar(aParent); + + createButton(aParent, IDialogConstants.RETRY_ID, "OK (retry)", false); + } + + @Override + protected void buttonPressed(int aButtonId) { + if (aButtonId == IDialogConstants.RETRY_ID) { + autoRetryEnabled = true; + super.buttonPressed(IDialogConstants.OK_ID); + } else { + super.buttonPressed(aButtonId); + } + } + + public boolean isAutoRetryEnabled() { + return autoRetryEnabled; + } + +} diff --git a/de.gebit.integrity.eclipse/src/de/gebit/integrity/eclipse/views/IntegrityTestRunnerView.java b/de.gebit.integrity.eclipse/src/de/gebit/integrity/eclipse/views/IntegrityTestRunnerView.java index df5c0b9f8..fa212237d 100644 --- a/de.gebit.integrity.eclipse/src/de/gebit/integrity/eclipse/views/IntegrityTestRunnerView.java +++ b/de.gebit.integrity.eclipse/src/de/gebit/integrity/eclipse/views/IntegrityTestRunnerView.java @@ -49,7 +49,6 @@ import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.ArrayContentProvider; @@ -559,6 +558,38 @@ public class IntegrityTestRunnerView extends ViewPart { */ private boolean scrollLockActive; + /** + * The action which toggles between normal mode, pause at first error or abort at first error. + */ + private Action pauseAbortAtFirstErrorAction; + + /** + * Whether the test run should be paused when the first error is encountered. + */ + private boolean pauseAtFirstErrorActive; + + /** + * Whether the test run should be aborted when the first error is encountered. + */ + private boolean abortAtFirstErrorActive; + + /** + * Will be set when the {@link #abortAtFirstErrorActive} flag triggers an abortion of test execution. + */ + private boolean lastRunWasAutoAborted; + + /** + * The threshold of accepted test failures before the {@link #pauseAtFirstErrorActive} or + * {@link #abortAtFirstErrorActive} triggers an action. + */ + private int pauseAbortAtFirstErrorFailureThreshold; + + /** + * If the last test run has reported its ending normally. This is used to distinguish between connection losses of + * unexpected nature and such losses that occur because testing has ended. + */ + private boolean lastRunEndStatusReceived; + /** * The last level of node expansion. */ @@ -589,6 +620,11 @@ public class IntegrityTestRunnerView extends ViewPart { */ private SetList setList; + /** + * Used to measure the connected time. + */ + private long connectionTimestamp; + /** * The set of breakpoints currently in use. */ @@ -604,6 +640,11 @@ public class IntegrityTestRunnerView extends ViewPart { */ private ILaunchConfiguration launchConfiguration; + /** + * The currently active autoconnect thread. + */ + private AutoConnectThread autoConnectThread; + /** * The constructor. */ @@ -1501,6 +1542,7 @@ private void fillLocalToolBar(IToolBarManager aManager) { aManager.add(stepIntoAction); aManager.add(stepOverAction); aManager.add(clearBreakpointsAction); + aManager.add(pauseAbortAtFirstErrorAction); aManager.add(new Separator()); aManager.add(connectToTestRunnerAction); } @@ -1512,8 +1554,7 @@ private void makeActions() { @Override public void run() { if (client == null || !client.isActive()) { - InputDialog tempDialog = new InputDialog(getSite().getShell(), "Connect to test runner", - "Please enter the hostname or IP address to connect to", lastHostname, null); + ConnectDialog tempDialog = new ConnectDialog(getSite().getShell(), lastHostname, null); if (tempDialog.open() == IStatus.OK && tempDialog.getValue() != null && tempDialog.getValue().length() > 0) { lastHostname = tempDialog.getValue(); @@ -1531,7 +1572,23 @@ public void run() { } tempHost = tempHost.substring(0, tempHost.indexOf(':')); } - connectToTestRunnerAsync(tempHost, tempPort); + + if (tempDialog.isAutoRetryEnabled()) { + if (autoConnectThread != null && autoConnectThread.isAlive()) { + autoConnectThread.kill(); + autoConnectThread = null; + } + if (autoConnectThread == null || !autoConnectThread.isAlive()) { + autoConnectThread = new AutoConnectThread(tempHost, tempPort); + autoConnectThread.start(); + } + } else { + connectToTestRunnerAsync(tempHost, tempPort); + } + } else { + if (autoConnectThread != null && autoConnectThread.isAlive()) { + autoConnectThread.kill(); + } } } else { disconnectFromTestRunner(); @@ -1671,7 +1728,11 @@ public void run() { executeTestAction.setEnabled(false); executeDebugTestAction.setEnabled(false); final ILaunch tempLaunch = launchConfiguration.launch(ILaunchManager.RUN_MODE, null); - new AutoConnectThread(tempLaunch).start(); + if (autoConnectThread != null && autoConnectThread.isAlive()) { + autoConnectThread.kill(); + } + autoConnectThread = new AutoConnectThread(tempLaunch); + autoConnectThread.start(); } catch (CoreException exc) { showException(exc); } @@ -1689,7 +1750,11 @@ public void run() { executeTestAction.setEnabled(false); executeDebugTestAction.setEnabled(false); final ILaunch tempLaunch = launchConfiguration.launch(ILaunchManager.DEBUG_MODE, null); - new AutoConnectThread(tempLaunch).start(); + if (autoConnectThread != null && autoConnectThread.isAlive()) { + autoConnectThread.kill(); + } + autoConnectThread = new AutoConnectThread(tempLaunch); + autoConnectThread.start(); } catch (CoreException exc) { showException(exc); } @@ -1758,6 +1823,40 @@ public void run() { scrollLockAction.setToolTipText("Toggles the scroll lock setting."); scrollLockAction.setImageDescriptor(Activator.getImageDescriptor("icons/scrolllock.gif")); + pauseAbortAtFirstErrorAction = new Action("Pause/abort at first test failure", IAction.AS_CHECK_BOX) { + @Override + public void run() { + if (!pauseAtFirstErrorActive && !abortAtFirstErrorActive) { + pauseAtFirstErrorActive = true; + setChecked(true); + setImageDescriptor(Activator.getImageDescriptor("icons/auto_pause_abort_on_pause.gif")); + setToolTipText("Will automatically pause test execution on first test failure."); + } else if (pauseAtFirstErrorActive) { + pauseAtFirstErrorActive = false; + abortAtFirstErrorActive = true; + setChecked(true); + setImageDescriptor(Activator.getImageDescriptor("icons/auto_pause_abort_on_abort.gif")); + setToolTipText("Will automatically abort test execution on first test failure."); + } else { + pauseAtFirstErrorActive = false; + abortAtFirstErrorActive = false; + setChecked(false); + setImageDescriptor(Activator.getImageDescriptor("icons/auto_pause_abort_disabled.gif")); + setToolTipText("Automatically pause or abort on first test failure is currently disabled."); + } + + if (setList != null) { + pauseAbortAtFirstErrorFailureThreshold = setList + .getNumberOfEntriesInResultState(SetListEntryResultStates.FAILED) + + setList.getNumberOfEntriesInResultState(SetListEntryResultStates.EXCEPTION); + } + }; + }; + pauseAbortAtFirstErrorAction + .setToolTipText("Automatically pause or abort on first test failure is currently disabled."); + pauseAbortAtFirstErrorAction + .setImageDescriptor(Activator.getImageDescriptor("icons/auto_pause_abort_disabled.gif")); + updateActionStatus(null); } @@ -1856,9 +1955,19 @@ public void run() { pauseAction.setEnabled(false); stepIntoAction.setEnabled(false); stepOverAction.setEnabled(false); - updateStatusRunnable( - determineIntermediateTestResultStatusString("Test execution finished (", ")")) - .run(); + if (lastRunWasAutoAborted) { + updateStatusRunnable( + determineIntermediateTestResultStatusString("Test was aborted on failure (", + ") after " + DateUtil.convertNanosecondTimespanToHumanReadableFormat( + System.nanoTime() - connectionTimestamp, true, false))).run(); + } else { + updateStatusRunnable( + determineIntermediateTestResultStatusString("Test execution finished (", + ") after " + DateUtil.convertNanosecondTimespanToHumanReadableFormat( + System.nanoTime() - connectionTimestamp, true, false))).run(); + } + lastRunWasAutoAborted = false; + lastRunEndStatusReceived = true; break; default: break; @@ -2442,6 +2551,8 @@ private void updateSetList(SetList aNewSetList) { setList = aNewSetList; breakpointSet.clear(); setListSearch = null; + pauseAbortAtFirstErrorFailureThreshold = 0; + lastRunWasAutoAborted = false; Display.getDefault().asyncExec(new Runnable() { @Override @@ -2492,11 +2603,6 @@ public boolean isSkippable() { private class RemotingListener implements IntegrityRemotingClientListener { - /** - * Used to measure the connected time. - */ - private long connectionTimestamp; - @Override public void onConnectionSuccessful(IntegrityRemotingVersionMessage aRemoteVersion, Endpoint anEndpoint) { // request set list baseline and execution state @@ -2578,9 +2684,20 @@ public void onConnectionLost(Endpoint anEndpoint) { public void run() { executionProgress.redraw(); updateActionStatusRunnable(null).run(); - updateStatusRunnable(determineIntermediateTestResultStatusString("Test Runner disconnected (", - ") after " + DateUtil.convertNanosecondTimespanToHumanReadableFormat( - System.nanoTime() - connectionTimestamp, true, false))).run(); + if (lastRunWasAutoAborted) { + updateStatusRunnable( + determineIntermediateTestResultStatusString("Test was aborted on failure (", + ") after " + DateUtil.convertNanosecondTimespanToHumanReadableFormat( + System.nanoTime() - connectionTimestamp, true, false))).run(); + } else { + if (!lastRunEndStatusReceived) { + updateStatusRunnable( + determineIntermediateTestResultStatusString("Test Runner disconnected (", + ") after " + DateUtil.convertNanosecondTimespanToHumanReadableFormat( + System.nanoTime() - connectionTimestamp, true, false))).run(); + } + } + lastRunEndStatusReceived = false; } }); @@ -2623,6 +2740,31 @@ public void onSetListUpdate(final SetListEntry[] someUpdatedEntries, final Integ if (anEntryInExecutionReference != null) { setList.setEntryInExecutionReference(anEntryInExecutionReference); } + + if (pauseAtFirstErrorActive || abortAtFirstErrorActive) { + int tempCurrentFailures = setList.getNumberOfEntriesInResultState(SetListEntryResultStates.FAILED) + + setList.getNumberOfEntriesInResultState(SetListEntryResultStates.EXCEPTION); + if (tempCurrentFailures > pauseAbortAtFirstErrorFailureThreshold) { + pauseAbortAtFirstErrorFailureThreshold = tempCurrentFailures; + if (pauseAtFirstErrorActive && isConnected()) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + pauseAction.run(); + } + }); + } else if (abortAtFirstErrorActive && isConnected()) { + lastRunWasAutoAborted = true; + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + shutdownAction.run(); + } + }); + } + } + } + Display.getDefault().asyncExec(new Runnable() { @Override public void run() { @@ -2745,6 +2887,21 @@ private class AutoConnectThread extends Thread { */ private ILaunch launch; + /** + * The host to connect to. + */ + private String host = "localhost"; + + /** + * The port to connect to. + */ + private int port = IntegrityRemotingConstants.DEFAULT_PORT; + + /** + * Used to kill this thread gracefully. + */ + private boolean killSwitch; + /** * Creates a new instance. */ @@ -2753,15 +2910,28 @@ private class AutoConnectThread extends Thread { launch = aLaunch; } + /** + * Creates a new instance. + */ + AutoConnectThread(String aHost, int aPort) { + super("Integrity Autoconnect Thread"); + host = aHost; + port = aPort; + } + + public void kill() { + killSwitch = true; + } + @Override public void run() { boolean tempSuccess = false; - while (!launch.isTerminated()) { + while (!killSwitch && (launch == null || !launch.isTerminated())) { try { if (!tempSuccess && !isConnected()) { // try to connect at least once - connectToTestRunner("localhost", IntegrityRemotingConstants.DEFAULT_PORT); + connectToTestRunner(host, IntegrityRemotingConstants.DEFAULT_PORT); tempSuccess = true; } else { // Now we'll wait until the launch has terminated @@ -2784,6 +2954,10 @@ public void run() { // don't care } } + + if (!tempSuccess) { + updateStatus("Aborted connection attempts"); + } executeTestAction.setEnabled(true); executeDebugTestAction.setEnabled(true); }; diff --git a/de.gebit.integrity.runner/src/de/gebit/integrity/runner/DefaultTestRunner.java b/de.gebit.integrity.runner/src/de/gebit/integrity/runner/DefaultTestRunner.java index cda7c58a1..2959b4779 100644 --- a/de.gebit.integrity.runner/src/de/gebit/integrity/runner/DefaultTestRunner.java +++ b/de.gebit.integrity.runner/src/de/gebit/integrity/runner/DefaultTestRunner.java @@ -772,6 +772,8 @@ public void run() { if (remotingServer != null && tempBlockForRemoting) { try { + System.out.println( + "WAITING FOR REMOTE CONTROLLER TO CONNECT ON PORT " + remotingServer.getPort() + "..."); performanceLogger.executeAndLog(TestRunnerPerformanceLogger.PERFORMANCE_LOG_CATEGORY_RUNNER, "Wait for Remoting", new TestRunnerPerformanceLogger.RunnableWithException() { @@ -864,8 +866,10 @@ protected void initializeParameterizedConstants() { String tempValue = (parameterizedConstantValues != null) ? parameterizedConstantValues.get(tempName) : null; try { - defineConstant(tempDefinition, tempValue, (tempDefinition.eContainer() instanceof SuiteDefinition) - ? ((SuiteDefinition) tempDefinition.eContainer()) : null); + defineConstant(tempDefinition, tempValue, + (tempDefinition.eContainer() instanceof SuiteDefinition) + ? ((SuiteDefinition) tempDefinition.eContainer()) + : null); } catch (ClassNotFoundException | InstantiationException | UnexecutableException exc) { // Cannot happen - parameterized constants aren't evaluated } @@ -1583,7 +1587,8 @@ protected void defineConstant(final ConstantDefinition aDefinition, Object aValu protected void defineVariable(final VariableOrConstantEntity anEntity, Object anInitialValue, final SuiteDefinition aSuite) { final Object tempInitialValue = (anInitialValue instanceof Variable) - ? variableManager.get(((Variable) anInitialValue).getName()) : anInitialValue; + ? variableManager.get(((Variable) anInitialValue).getName()) + : anInitialValue; // We need to send variable updates to forks in the main phase here. boolean tempSendToForks = (!isFork()) && shouldExecuteFixtures();