/*******************************************************************************
* Copyright (c) 2006 RadRails.org and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.radrails.rails.internal.ui.autotest;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
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.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
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.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.widgets.Display;
import org.radrails.rails.internal.core.RailsPlugin;
import org.radrails.rails.ui.RailsUILog;
import org.radrails.rails.ui.test.TestLauncher;
import org.rubypeople.rdt.internal.testunit.ui.TestUnitView;
import org.rubypeople.rdt.internal.testunit.ui.TestunitPlugin;
import org.rubypeople.rdt.testunit.ITestRunListener;
/**
* Listens for resource change events. When Ruby files are saved, launches
* associated Test::Unit suites based on preferences. For example, launches
* corresponding unit test for models, functional tests for controllers, and so
* forth. Exactly which tests are run for which classes is configurable via
* preferences.
*
* @author mkent
*
*/
public class AutotestManager implements IResourceChangeListener,
IPropertyChangeListener, ITestRunListener {
private Collection<IAutotestRunListener> fRunListeners;
private AutotestThread fAutotestThread;
private TestUnitView view;
private IResourceDeltaVisitor fDeltaVisitor = new IResourceDeltaVisitor() {
public boolean visit(IResourceDelta delta) throws CoreException {
switch (delta.getKind()) {
case IResourceDelta.ADDED:
// handle added resource
break;
case IResourceDelta.REMOVED:
// handle removed resource
break;
case IResourceDelta.CHANGED:
// only fire on content change events on the active editor file
if((delta.getFlags() & IResourceDelta.CONTENT) == IResourceDelta.CONTENT) {
if(delta.getResource() != null && delta.getResource() instanceof IFile) {
IFile changedFile = (IFile) delta.getResource();
IFile activeEditorFile = AutotestHelper.getActiveEditorFile();
if(changedFile.getLocation().equals(activeEditorFile.getLocation())
&& changedFile.getFileExtension().equals("rb")) {
attemptTestLaunch(delta.getResource());
}
}
}
break;
}
return true;
}
};
private boolean fTestsPass;
public AutotestManager() {
fRunListeners = new ArrayList<IAutotestRunListener>();
if(AutotestHelper.runAutotestOnInterval()) {
fAutotestThread = new AutotestThread();
fAutotestThread.start();
}
}
public void resourceChanged(IResourceChangeEvent event) {
if (event == null) return;
boolean runOnSave = AutotestHelper.runAutotestOnSave();
try {
if (runOnSave) {
IResourceDelta delta = event.getDelta();
if (delta != null) delta.accept(fDeltaVisitor);
}
} catch (CoreException e) {
RailsUILog.logError("Error launching tests", e);
}
}
public void attemptTestLaunch(final IResource file) {
Job j = new Job("Autotest suite") {
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask("Running autotest suite", 3);
monitor.worked(1);
final List<String> tests = AutotestHelper.getTests(file.getProject(),
file.getProjectRelativePath());
monitor.worked(1);
if (!tests.isEmpty()) {
launchTests(file.getProject(), tests);
}
monitor.worked(1);
monitor.done();
return Status.OK_STATUS;
}
};
j.schedule();
}
private void launchTests(IProject project, List<String> testFiles) {
try {
TestLauncher launcher = new TestLauncher();
IPath path = RailsPlugin.getInstance().getStateLocation();
path = path.append("run_auto2.rb");
File temp = path.toFile();
FileWriter writer = new FileWriter(temp);
writer.write("require 'test/unit'\n");
for (String string : testFiles) {
writer.write("require '" + string + "'\n");
}
writer.close();
Display.getDefault().syncExec(new Runnable() {
public void run() {
view = TestunitPlugin.getDefault().findTestUnitViewInActivePage();
}
});
boolean oldShowOnErrorOnly = false;
if (view != null) {
oldShowOnErrorOnly = view.getShowOnErrorOnly();
view.setShowOnErrorOnly(true);
}
TestunitPlugin.getDefault().addTestRunListener(this);
ILaunch launch = launcher.goLaunch(project, ILaunchManager.RUN_MODE, "run_auto2.rb");
if (launch == null)
return;
IProcess iproc = launch.getProcesses()[0];
while (!iproc.isTerminated()) {
Thread.yield();
// XXX add a timeout so we don't loop infinitely
}
if (view != null) {
view.setShowOnErrorOnly(oldShowOnErrorOnly);
}
TestunitPlugin.getDefault().removeTestRunListener(this);
} catch (IOException e) {
RailsUILog.log(e);
}
}
public void addRunListener(IAutotestRunListener listener) {
fRunListeners.add(listener);
}
public void removeRunListener(IAutotestRunListener listener) {
fRunListeners.remove(listener);
}
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty().equals(
IAutotestPreferenceConstants.RUN_ON_INTERVAL)) {
boolean enabled = ((Boolean) event.getNewValue()).booleanValue();
if (enabled) {
if(fAutotestThread != null) {
fAutotestThread.stopRunning();
}
fAutotestThread = new AutotestThread();
fAutotestThread.start();
}
else {
fAutotestThread.stopRunning();
fAutotestThread = null;
}
}
}
private class AutotestThread extends Thread {
private boolean keepRunning;
public AutotestThread() {
keepRunning = true;
}
public void run() {
while (keepRunning) {
int intervalMinutes = AutotestHelper
.getAutotestIntervalLength();
long intervalMs = intervalMinutes * 60 * 1000;
try {
sleep(intervalMs);
} catch (InterruptedException e) {
RailsUILog.logError(
"Error sleeping during autotest interval", e);
}
// Run autotest suite
IFile activeEditorFile = AutotestHelper.getActiveEditorFile();
if (activeEditorFile != null) {
attemptTestLaunch(activeEditorFile);
}
}
}
public void stopRunning() {
keepRunning = false;
}
}
public void testEnded(String testId, String testName) {}
public void testFailed(int status, String testId, String testName, String trace) {
for (IAutotestRunListener listener : fRunListeners) {
if (status == STATUS_ERROR) {
listener.suiteError();
} else {
listener.suiteFail();
}
}
fTestsPass = false;
}
public void testReran(String testId, String testClass, String testName, int status, String trace) {}
public void testRunEnded(long elapsedTime) {
if (!fTestsPass) return;
for (IAutotestRunListener listener : fRunListeners) {
listener.suitePass();
}
}
public void testRunStarted(int testCount) {
fTestsPass = true;
}
public void testRunStopped(long elapsedTime) {}
public void testRunTerminated() {}
public void testStarted(String testId, String testName) {}
}