/** * Copyright (c) 2005-2013 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.pyunit.preferences; import java.util.StringTokenizer; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jface.preference.BooleanFieldEditor; import org.eclipse.jface.preference.PreferenceDialog; import org.eclipse.jface.text.DefaultInformationControl.IInformationPresenter; import org.eclipse.jface.text.TextPresentation; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; import org.eclipse.ui.dialogs.PreferencesUtil; import org.python.pydev.core.log.Log; import org.python.pydev.editor.preferences.PyScopedPreferences; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.plugin.preferences.PydevPrefs; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_ui.field_editors.ComboFieldEditor; import org.python.pydev.shared_ui.field_editors.LabelFieldEditor; import org.python.pydev.shared_ui.field_editors.LinkFieldEditor; import org.python.pydev.shared_ui.field_editors.MultiStringFieldEditor; import org.python.pydev.shared_ui.field_editors.ScopedFieldEditorPreferencePage; import org.python.pydev.shared_ui.field_editors.ScopedPreferencesFieldEditor; import org.python.pydev.shared_ui.tooltips.presenter.AbstractTooltipInformationPresenter; import org.python.pydev.shared_ui.tooltips.presenter.ToolTipPresenterHandler; public class PyUnitPrefsPage2 extends ScopedFieldEditorPreferencePage implements IWorkbenchPreferencePage { private class PyUnitPageLinkListener implements SelectionListener { private String fTag; public PyUnitPageLinkListener(String tag) { this.fTag = tag; } @Override public void widgetSelected(SelectionEvent e) { String comboValue = comboField.getComboValue(); int val = 0; try { val = Integer.parseInt(comboValue); } catch (NumberFormatException e1) { Log.log(e1); } int testRunner = val; String tag; boolean addEquals = true; boolean addSpace = false; if (testRunner == TEST_RUNNER_PYDEV) { tag = "--" + fTag; } else if (testRunner == TEST_RUNNER_PY_TEST) { if ("n".equals(fTag)) { tag = "-" + fTag; addEquals = false; addSpace = true; } else if ("showlocals".equals(fTag) || "runxfail".equals(fTag)) { tag = "--" + fTag; addEquals = false; } else { //durations, maxfail, tb tag = "--" + fTag; } } else { tag = fTag; } Text textControl = parametersField.getTextControl(); String currentText = textControl.getText(); StringTokenizer stringTokenizer = new StringTokenizer(currentText); FastStringBuffer buf = new FastStringBuffer(currentText.length() * 2); boolean found = false; while (stringTokenizer.hasMoreTokens()) { String tok = stringTokenizer.nextToken(); if (tok.startsWith(tag)) { found = true; } buf.append(tok); buf.append('\n'); } if (!found) { buf.append(tag); if (addEquals) { buf.append('='); } if (addSpace) { buf.append(' '); } } else { buf.deleteLast(); //remove the last '\n' } textControl.setText(buf.toString()); textControl.setSelection(textControl.getSize()); textControl.setFocus(); } @Override public void widgetDefaultSelected(SelectionEvent e) { } } public static final int TEST_RUNNER_PYDEV = 0; public static final int TEST_RUNNER_NOSE = 1; public static final int TEST_RUNNER_PY_TEST = 2; public static final String[][] ENTRY_NAMES_AND_VALUES = new String[][] { { "PyDev test runner", Integer.toString(TEST_RUNNER_PYDEV) }, { "Nose test runner", Integer.toString(TEST_RUNNER_NOSE) }, { "Py.test runner", Integer.toString(TEST_RUNNER_PY_TEST) }, }; public static final String TEST_RUNNER = "PYDEV_TEST_RUNNER"; public static final int DEFAULT_TEST_RUNNER = TEST_RUNNER_PYDEV; public static final String TEST_RUNNER_DEFAULT_PARAMETERS = "PYDEV_TEST_RUNNER_DEFAULT_PARAMETERS"; public static final String DEFAULT_TEST_RUNNER_DEFAULT_PARAMETERS = "--verbosity 0"; public static final String USE_PYUNIT_VIEW = "PYDEV_USE_PYUNIT_VIEW"; public static final boolean DEFAULT_USE_PYUNIT_VIEW = true; public static final String LAUNCH_CONFIG_OVERRIDE_PYUNIT_RUN_PARAMS_CHOICE = "LAUNCH_CONFIG_OVERRIDE_PYUNIT_RUN_PARAMS_CHOICE"; public static final String LAUNCH_CONFIG_OVERRIDE_TEST_RUNNER = "LAUNCH_CONFIG_OVERRIDE_TEST_RUNNER"; public static final String LAUNCH_CONFIG_OVERRIDE_PYUNIT_RUN_PARAMS = "LAUNCH_CONFIG_OVERRIDE_PYUNIT_RUN_PARAMS"; private MultiStringFieldEditor parametersField; private ToolTipPresenterHandler tooltipPresenter; private Composite parentPyDev; private Composite parentNose; private Composite parentPyTest; private ComboFieldEditor comboField; /** * Create the preference page. */ public PyUnitPrefsPage2() { super(FLAT); setPreferenceStore(PydevPlugin.getDefault().getPreferenceStore()); } /** * Creates the editors */ @Override protected void createFieldEditors() { IInformationPresenter presenter = new AbstractTooltipInformationPresenter() { @Override protected void onUpdatePresentation(String hoverInfo, TextPresentation presentation) { } @Override protected void onHandleClick(Object data) { } }; Composite p = getFieldEditorParent(); tooltipPresenter = new ToolTipPresenterHandler(p.getShell(), presenter, "Tip: Click on the link to add it as a parameter to the test runner."); final Composite parentAll = p; final StackLayout stackLayout = new StackLayout(); comboField = createTestRunnerEditor(p); addField(comboField); Combo combo = comboField.getCombo(); parametersField = new MultiStringFieldEditor(TEST_RUNNER_DEFAULT_PARAMETERS, "Parameters for test runner (click links below to add flags)", p); addField(parametersField); addField(new BooleanFieldEditor(USE_PYUNIT_VIEW, "Show the results in the unittest results view?", p)); String s = "Note: if unchecked, no xml-rpc communication will be done when running tests\nand the output will only be shown in the console."; addField(new LabelFieldEditor("LabelFieldEditor", s, p)); String s2 = "Parameters for PyDev test runner (hover for description):" + ""; addField(new LabelFieldEditor("LabelFieldEditor2", s2, p)); final Composite contentPanel = new Composite(parentAll, SWT.None); contentPanel.setLayout(stackLayout); parentPyTest = p = new Composite(contentPanel, SWT.None); add("-<a>n</a> number of processes (requires xdist plugin)", "n", "Sets the number of processes to be used to run\n" + "tests (requires py.test xdist plugin)\n\n", p); add("--<a>maxfail</a>=number (max number of failures)", "maxfail", "When the maximum number of failures\n" + "is reached execution stops.\n\n", p); add("--<a>tb</a>=long | native | short | line", "tb", "Traceback style.\n" + "long = the default informative traceback formatting\n" + "native = the Python standard library formatting\n" + "short = a shorter traceback format\n" + "line = only one line per failure\n\n", p); add("--<a>capture</a>=no | sys | fd", "capture", "Capture stdout/stderr\nno = disable capture\n" + "sys = replace stdout/stderr with in-mem files\n" + "fd = also point filedescriptors 1 and 2 to temp file\n\n", p); add("--<a>showlocals</a>", "showlocals", "Show local variables in tracebacks.\n\n", p); add("--<a>runxfail</a>", "runxfail", "Run tests even if they are marked xfali.\n\n", p); add("--<a>assert</a>=plain | reinterp | rewrite", "assert", "Control assertion debugging tools. 'plain' performs no\n" + "assertion debugging. 'reinterp' reinterprets assert\n" + "statements after they failed to provide assertion\n" + "expression information. 'rewrite' (the default)\n" + "rewrites assert statements in test modules on import\n" + "to provide assert expression information..\n\n", p); add("--<a>durations</a>=number of slower tests to show", "durations", "Profiling test execution duration\n" + "(shows n slowest tests).\n\n", p); parentNose = p = new Composite(contentPanel, SWT.None); parentPyDev = p = new Composite(contentPanel, SWT.None); add("--<a>verbosity</a>=number", "verbosity", "Sets the verbosity level for the run (0-9)\n" + " 0: almost no output\n" + " 9: many details", p); add("--<a>jobs</a>=number", "jobs", "The number of processes to be used to run the tests.\n\n" + "The --split_jobs flag actually mandates how the tests will be scheduled (if jobs > 1).", p); add("--<a>split_jobs</a>=tests | module", "split_jobs", "tests: if 'tests' is passed (default), a process will randomly get a\n" + "\tnew test to be run after the current one finishes running.\n\n" + "module: if 'module' is passed, a given job will always run all the\n" + "\ttests from a module and then get a new module to run tests from.", p); add("--<a>include_files</a>=comma separated list of patterns to match files to include", "include_files", "Patters to match filenames to be included during test discovery.\n\n" + "Patters are fnmatch-style patterns (i.e.: test*, todo* and not regexps).\n\n" + "Note that *.py,*.pyw files are already pre-selected, so, patterns\n" + "will be matched against those pre-selected by default.", p); add("--<a>exclude_files</a>=comma separated list of patterns to match files to exclude", "exclude_files", "Patters to match filenames to be excluded during test discovery.\n\n" + "Patters are fnmatch-style patterns (i.e.: test*, todo* and not regexps).\n\n" + "Note that *.py,*.pyw files are already pre-selected, so, patterns\n" + "will be matched against those pre-selected by default.", p); add("--<a>include_tests</a>=comma separated list of patterns to match tests to include", "include_tests", "Patters to match tests (method names) to be included during test discovery.\n\n" + "Patters are fnmatch-style patterns (i.e.: *_todo, *_slow and not regexps).\n\n", p); add("--<a>exclude_tests</a>=comma separated list of patterns to match tests to exclude", "exclude_tests", "Patters to match tests (method names) to be excluded during test discovery.\n\n" + "Patters are fnmatch-style patterns (i.e.: *_todo, *_slow and not regexps).\n\n", p); add("--<a>django</a>=true | false (default is true on django projects and false otherwise)", "django", "Whether the django runner should be used for setup/teardown of the django test environment\n\n", p); combo.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { String comboValue = comboField.getComboValue(); int val = 0; try { val = Integer.parseInt(comboValue); } catch (NumberFormatException e1) { Log.log(e1); return; } layoutTestRunnerOptions(stackLayout, val, contentPanel); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); layoutTestRunnerOptions(stackLayout, getTestRunner(null), contentPanel); addField( new ScopedPreferencesFieldEditor(parentAll, PydevPlugin.DEFAULT_PYDEV_SCOPE, this)); } private void add(String linkText, String flag, String tooltip, Composite p) { LinkFieldEditor field = new LinkFieldEditor("link_" + flag, linkText, p, new PyUnitPrefsPage2.PyUnitPageLinkListener(flag), tooltip + "\n", tooltipPresenter); addField(field); } public static ComboFieldEditor createTestRunnerEditor(Composite p) { return new ComboFieldEditor(TEST_RUNNER, "Test Runner", ENTRY_NAMES_AND_VALUES, p); } /** * Initialize the preference page. */ @Override public void init(IWorkbench workbench) { // Initialize the preference page } private void layoutTestRunnerOptions(final StackLayout stackLayout, final int val, final Composite contentPanel) { switch (val) { case TEST_RUNNER_PYDEV: stackLayout.topControl = parentPyDev; break; case TEST_RUNNER_PY_TEST: stackLayout.topControl = parentPyTest; break; case TEST_RUNNER_NOSE: stackLayout.topControl = parentNose; break; } contentPanel.layout(); } public static String getTestRunnerParameters(ILaunchConfiguration config, IProject project) { int testRunner = getTestRunner(config, project); boolean override = false; try { override = config.getAttribute(LAUNCH_CONFIG_OVERRIDE_PYUNIT_RUN_PARAMS_CHOICE, false); } catch (CoreException e) { Log.log(e); } String ret = PyScopedPreferences.getString(TEST_RUNNER_DEFAULT_PARAMETERS, project); if (override) { try { ret = config.getAttribute(LAUNCH_CONFIG_OVERRIDE_PYUNIT_RUN_PARAMS, ret); } catch (CoreException e) { Log.log(e); } } switch (testRunner) { case TEST_RUNNER_NOSE: ret = "--nose-params " + ret; //From this point onwards, only nose parameters. break; case TEST_RUNNER_PY_TEST: ret = "--py-test-params " + ret; //From this point onwards, only py.test parameters. break; default: //Only add --django when we have a django nature in the default runner try { if (project.hasNature(PythonNature.DJANGO_NATURE_ID)) { if (!ret.contains("--django")) { ret += " --django=true"; } } } catch (CoreException e) { Log.log(e); //just ignore } break; } return ret; } public static int getTestRunner(ILaunchConfiguration config, IProject project) { boolean override = false; try { override = config.getAttribute(LAUNCH_CONFIG_OVERRIDE_PYUNIT_RUN_PARAMS_CHOICE, false); } catch (CoreException e) { Log.log(e); } int testRunner = getTestRunner(project); if (override) { try { testRunner = config.getAttribute(LAUNCH_CONFIG_OVERRIDE_TEST_RUNNER, testRunner); } catch (CoreException e) { Log.log(e); } } return testRunner; } /** * See: TEST_RUNNER_NOSE or TEST_RUNNER_PY_TEST (any other value indicates the default runner). */ public static int getTestRunner(IAdaptable projectAdaptable) { return PyScopedPreferences.getInt(TEST_RUNNER, projectAdaptable, 0); } public static boolean getUsePyUnitView(IAdaptable projectAdaptable) { return PydevPrefs.getPreferenceStore().getBoolean(USE_PYUNIT_VIEW); } public static void showPage() { String id = "org.python.pydev.prefs.pyunitPage"; PreferenceDialog prefDialog = PreferencesUtil.createPreferenceDialogOn(null, id, null, null); prefDialog.open(); } public static boolean isPyTestRun(IAdaptable projectAdaptable) { return getTestRunner(projectAdaptable) == TEST_RUNNER_PY_TEST; } }