/** * Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.debug.pyunit; import java.lang.ref.WeakReference; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuCreator; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Menu; import org.python.pydev.core.log.Log; import org.python.pydev.debug.core.PydevDebugPlugin; import org.python.pydev.debug.pyunit.HistoryAction.IActionsMenu; import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper; import org.python.pydev.plugin.PydevPlugin; /** * This action will take care of relaunching the current test suite whenever any file changes. * * @author fabioz */ public class RelaunchInBackgroundAction extends Action implements IResourceChangeListener { /** * @author fabioz * */ private final class RelaunchJob extends Job { private PyUnitTestRun currentTestRun; private RelaunchJob() { super("Relaunch test suite"); this.setPriority(Job.SHORT); this.setSystem(true); } @Override protected IStatus run(IProgressMonitor monitor) { if (!currentTestRun.getFinished()) { //Wait for the current run to finish. this.schedule(300); return Status.OK_STATUS; } if (PydevDebugPlugin.getDefault().getPreferenceStore() .getBoolean(PyUnitView.PYUNIT_VIEW_BACKGROUND_RELAUNCH_SHOW_ONLY_ERRORS)) { currentTestRun.relaunchOnlyErrors(); } else { currentTestRun.relaunch(); } return Status.OK_STATUS; } public void setTestToRun(PyUnitTestRun currentTestRun) { this.currentTestRun = currentTestRun; } } public class RelaunchInBackgroundOptionsMenuCreator implements IMenuCreator { private Menu fMenu; public RelaunchInBackgroundOptionsMenuCreator() { } @Override public void dispose() { if (fMenu != null) { fMenu.dispose(); fMenu = null; } } @Override public Menu getMenu(Control parent) { if (fMenu != null) { fMenu.dispose(); } final MenuManager manager = new MenuManager(); manager.setRemoveAllWhenShown(true); manager.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(final IMenuManager manager2) { fillMenuManager(new IActionsMenu() { @Override public void add(IAction action) { manager2.add(action); } }); } }); fMenu = manager.createContextMenu(parent); return fMenu; } @Override public Menu getMenu(Menu parent) { return null; //yes, return null here (no sub children) } public void fillMenuManager(IActionsMenu actionsMenu) { actionsMenu.add(new RelaunchOnlyErrorsOnBackgroundRelaunch()); } } private class RelaunchOnlyErrorsOnBackgroundRelaunch extends Action { public RelaunchOnlyErrorsOnBackgroundRelaunch() { this.setText("Run only failed tests when relaunching due to file changes?"); this.setToolTipText("If checked, a relaunch will relaunch only the errors in the current test run.\n" + "\n" + "If no errors are found, the full test suite is run again."); this.setChecked(PydevDebugPlugin.getDefault().getPreferenceStore() .getBoolean(PyUnitView.PYUNIT_VIEW_BACKGROUND_RELAUNCH_SHOW_ONLY_ERRORS)); } /* (non-Javadoc) * @see org.eclipse.jface.action.Action#run() */ @Override public void run() { PydevDebugPlugin.getDefault().getPreferenceStore() .setValue(PyUnitView.PYUNIT_VIEW_BACKGROUND_RELAUNCH_SHOW_ONLY_ERRORS, this.isChecked()); } } private WeakReference<PyUnitView> view; RelaunchJob relaunchJob = new RelaunchJob(); private boolean listeningChanges; public RelaunchInBackgroundAction(PyUnitView pyUnitView) { this.view = new WeakReference<PyUnitView>(pyUnitView); setMenuCreator(new RelaunchInBackgroundOptionsMenuCreator()); //Options for user this.listeningChanges = false; this.setImageDescriptor(PydevPlugin.getImageCache().getDescriptor("icons/relaunch_background_disabled.png")); setInitialTooltipText(); } private void setInitialTooltipText() { this.setToolTipText("Click to rerun the current test suite whenever any Python file changes.\n" + "\nNote that a new run will only be done after the current test run finishes."); } private void stopListening() { ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); } private void startListening() { ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); } /* (non-Javadoc) * @see org.eclipse.jface.action.Action#run() */ @Override public void run() { this.listeningChanges = !this.listeningChanges; if (this.listeningChanges) { this.setImageDescriptor(PydevPlugin.getImageCache().getDescriptor("icons/relaunch_background_enabled.png")); startListening(); } else { this.setImageDescriptor(PydevPlugin.getImageCache().getDescriptor("icons/relaunch_background_disabled.png")); stopListening(); } } @Override public void resourceChanged(final IResourceChangeEvent event) { //Handle cases where the view was closed... if (view == null) { stopListening(); return; } PyUnitView pyUnitView = view.get(); if (pyUnitView == null) { stopListening(); return; } final boolean[] run = new boolean[] { false }; try { event.getDelta().accept(new IResourceDeltaVisitor() { @Override public boolean visit(IResourceDelta delta) { switch (delta.getKind()) { case IResourceDelta.CHANGED: IResource resource = delta.getResource(); if (resource instanceof IFile) { //Check if a source file was changed (i.e.: don't get .pyc, .class, etc). if (PythonPathHelper.isValidSourceFile((IFile) resource)) { int flags = delta.getFlags(); if ((flags & IResourceDelta.CONTENT) != 0) { //Uncomment to debug... //System.out.println("----------------------"); //System.out.println("----------------------"); //System.out.println("----------------------"); //System.out.println("----------------------"); //System.out.println(event.getResource()); //System.out.println(event.getBuildKind()); //System.out.println(event.getSource()); //System.out.println(resource+" "+resource.getModificationStamp()); run[0] = true; } } } break; } return true; } }); } catch (CoreException e) { Log.log(e); } if (run[0]) { //Ok, we've the view, let's relaunch the current launch (if any) PyUnitTestRun currentTestRun = pyUnitView.getCurrentTestRun(); if (currentTestRun != null) { relaunchJob.setTestToRun(currentTestRun); relaunchJob.schedule(200); //Give some time to handle more changes... } } } }