/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.ruby.rubyproject;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.Utilities;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.netbeans.api.project.Project;
import org.netbeans.api.ruby.platform.RubyPlatform;
import org.netbeans.editor.BaseAction;
import org.netbeans.api.extexecution.ExecutionService;
import org.netbeans.api.extexecution.print.LineConvertor;
import org.netbeans.api.extexecution.print.LineConvertors.FileLocator;
import org.netbeans.modules.csl.spi.GsfUtilities;
import org.netbeans.modules.ruby.AstUtilities;
import org.netbeans.modules.ruby.platform.execution.RubyExecutionDescriptor;
import org.netbeans.modules.ruby.platform.execution.RubyProcessCreator;
import org.netbeans.modules.ruby.rubyproject.spi.TestRunner;
import org.netbeans.modules.ruby.spi.project.support.rake.PropertyEvaluator;
import org.netbeans.spi.project.ActionProvider;
import org.openide.LifecycleManager;
import org.openide.filesystems.FileUtil;
import org.openide.util.Lookup;
/**
* Run the current focused test or spec (test under caret)
*
* @author Tor Norbye
*/
public class RunFocusedTest extends BaseAction {
public RunFocusedTest() {
super("run-focused-spec", 0); // NOI18N
}
@Override
public void actionPerformed(ActionEvent evt, JTextComponent target) {
runTest(target, false);
}
@Override
public Class getShortDescriptionBundleClass() {
return RunFocusedTest.class;
}
@Override
public boolean isEnabled() {
return true;
}
static void runTest(JTextComponent target, boolean debug) {
if (target.getCaret() == null) {
return;
}
FileObject file = GsfUtilities.findFileObject(target);
if (file != null) {
Project project = FileOwnerQuery.getOwner(file);
if (project != null) {
int offset = target.getCaret().getDot();
BaseDocument doc = (BaseDocument)target.getDocument();
FileLocator locator = project.getLookup().lookup(FileLocator.class);
try {
RSpecSupport rspec = new RSpecSupport(project);
if (rspec.isRSpecInstalled() && RSpecSupport.isSpecFile(file)) {
int line = Utilities.getLineOffset(doc, offset);
if (line >= 0) {
// TODO - compute line number of surrounding "spec" ? Or can spec find it on its own?
// Save all files first - this spec file could be accessing other files being tested
LifecycleManager.getDefault().saveAll();
// Line+1: spec seems to be 1-based rather than 0-based (first line is 1)
TestRunner runner = getTestRunner(TestRunner.TestType.RSPEC);
if (runner != null) {
runner.runSingleTest(file, String.valueOf(line + 1), debug);
} else {
rspec.runRSpec(null, file, line + 1, file.getName(), locator, true, debug);
}
return;
}
} else {
// Regular Test::Unit? Find the test surrounding the caret.
// "ruby my_test.rb -n test_this"
String testName = AstUtilities.getTestName(file, offset);
if (testName != null) {
// No validation that the method is a test or the parentclass
// is Test::Unit -- make this work with possibly other useful
// single-method execution purposes as well. See
// http://www.nabble.com/Should-be-Ruby-from-bits.netbeans.org-preferred--tf4607093s27020.html#a13185222
// && testName.startsWith("test") { // NOI18N
// Save all files first - this spec file could be accessing other files being tested
LifecycleManager.getDefault().saveAll();
TestRunner runner = getTestRunner(TestRunner.TestType.TEST_UNIT);
if (runner != null) {
runner.runSingleTest(file, testName, debug);
} else {
runTest(project, null, file, testName, file.getName(), locator, true, debug);
}
return;
} else {
Toolkit.getDefaultToolkit().beep();
}
}
} catch (BadLocationException ble) {
// do nothing - see #154991
}
}
}
}
/**
* Run rspec on the given specfile.
* (If you pass null as the directory, the project directory will be used, and if not set,
* the directory containing the spec file.)
* @param lineNumber if not -1, run the spec at the given line
* @param warn If true, produce popups if Ruby or RSpec are not configured
* correctly.
*/
private static void runTest(Project project, File pwd, FileObject target, String testName, String displayName,
FileLocator fileLocator, boolean warn, boolean debug, String... parameters) {
FileObject projectDir = null;
if (project != null) {
projectDir = project.getProjectDirectory();
}
if (pwd == null) {
FileObject pfo = (projectDir != null) ? projectDir : target.getParent();
pwd = FileUtil.toFile(pfo);
}
RubyPlatform platform = RubyPlatform.platformFor(project);
if (!platform.isValid(warn)) {
return;
}
List<String> additionalArgs = new ArrayList<String>();
additionalArgs.add("-n"); // NOI18N
additionalArgs.add(testName);
if ((parameters != null) && (parameters.length > 0)) {
for (String parameter : parameters) {
additionalArgs.add(parameter);
}
}
String targetPath = FileUtil.toFile(target).getAbsolutePath();
RubyExecutionDescriptor desc = null;
String charsetName = null;
if (project != null) {
PropertyEvaluator evaluator = project.getLookup().lookup(PropertyEvaluator.class);
if (evaluator != null) {
charsetName = evaluator.getProperty(SharedRubyProjectProperties.SOURCE_ENCODING);
}
ActionProvider provider = project.getLookup().lookup(ActionProvider.class);
if (provider instanceof ScriptDescProvider) { // Lookup ScriptDescProvider directly?
ScriptDescProvider descProvider = (ScriptDescProvider)provider;
desc = descProvider.getScriptDescriptor(pwd, target, targetPath, displayName, project.getLookup(), debug, new TestNotifierLineConvertor(true, true));
// Override args
desc. additionalArgs(additionalArgs.toArray(
new String[additionalArgs.size()])); // NOI18N
}
} else {
desc = new RubyExecutionDescriptor(platform, displayName, pwd, targetPath);
desc.additionalArgs(additionalArgs.toArray(
new String[additionalArgs.size()])); // NOI18N
desc.debug(debug);
desc.allowInput();
desc.fileLocator(fileLocator);
desc.addStandardRecognizers();
LineConvertor testConvertor = new TestNotifierLineConvertor(true, true);
desc.addOutConvertor(testConvertor);
desc.addErrConvertor(testConvertor);
}
RubyProcessCreator rpc = new RubyProcessCreator(desc, charsetName);
ExecutionService.newService(rpc, desc.toExecutionDescriptor(), displayName).run();
}
private static TestRunner getTestRunner(TestRunner.TestType testType) {
Collection<? extends TestRunner> testRunners = Lookup.getDefault().lookupAll(TestRunner.class);
for (TestRunner each : testRunners) {
if (each.supports(testType)) {
return each;
}
}
return null;
}
}