Repository: spyder-ide/spyder

Issue:
Exception raised when running script in external terminal
## Description

If I try to run a script in an external terminal, an exception is raised.

**What steps will reproduce the problem?**
1. Write a Python script (contents do not matter)
2. Click "Configure" in the "Run" menu. 
3. In the window that appears, pick "Execute in external system terminal" and uncheck the other options.
4. Press the "Run" button.

**What is the expected output? What do you see instead?**

The script does not run. Instead, the traceback below is printed in the external console (I abbreviated the paths). The issue occurs in both 3.0.0b2 and the current git revision.

```
Traceback (most recent call last):
  File "(...)/site-packages/spyderlib/plugins/editor.py", line 2082, in edit_run_configurations
    self.run_file()
  File "(...)/site-packages/spyderlib/plugins/editor.py", line 2129, in run_file
    self.re_run_file()
  File "(...)/site-packages/spyderlib/plugins/editor.py", line 2172, in re_run_file
    systerm, post_mortem)
  File "(...)/site-packages/spyderlib/spyder.py", line 2483, in open_external_console
    interact, debug, python_args)
  File "(...)/site-packages/spyderlib/utils/programs.py", line 183, in run_python_script_in_terminal
    cwd=wdir)
  File "(...)/site-packages/spyderlib/utils/programs.py", line 64, in run_program
    subprocess.Popen([path]+args, cwd=cwd)
  File "(...)/subprocess.py", line 950, in __init__
    restore_signals, start_new_session)
  File "(...)/subprocess.py", line 1544, in _execute_child
    raise child_exception_type(errno_num, err_msg)
FileNotFoundError: [Errno 2] No such file or directory: ''
```
## Version and main components
- Spyder Version: 3.0.0b2 
- Python Version: 3.5.1
- Qt Versions: 4.8.7, PyQt4 (API v2) 4.11.4 on Linux
## Dependencies

```
pyflakes >=0.6.0   :  1.1.0 (OK)
pep8 >=0.6         :  1.7.0 (OK)
pygments >=1.6     :  2.1.1 (OK)
IPython >=3.0      :  4.1.2 (OK)
zmq >=13.0.0       :  15.2.0 (OK)
qtconsole >=4.0    :  4.1.1 (OK)
pandas >=0.13.1    :  0.17.1 (OK)
numpy >=1.7        :  1.10.4 (OK)
sphinx >=0.6.6     :  1.3.5 (OK)
psutil >=0.3       :  4.1.0 (OK)
rope >=0.9.4       :  0.9.4-1 (OK)
jedi >=0.8.1;<0.9.0:  0.9.0 (NOK)
matplotlib >=1.0   :  1.5.1 (OK)
sympy >=0.7.3      :  0.7.6.1 (OK)
pylint >=0.25      :  1.5.4 (OK)
```



Discussion:

Resolving patch:
diff --git a/spyderlib/utils/programs.py b/spyderlib/utils/programs.py
--- a/spyderlib/utils/programs.py
+++ b/spyderlib/utils/programs.py
@@ -252,7 +252,11 @@ def get_python_args(fname, python_args, interact, debug, end_args):
 
 def run_python_script_in_terminal(fname, wdir, args, interact,
                                   debug, python_args):
-    """Run Python script in an external system terminal"""
+    """
+    Run Python script in an external system terminal.
+
+    :str wdir: working directory, may be empty.
+    """
     
     # If fname has spaces on it it can't be ran on Windows, so we have to
     # enclose it in quotes. Also wdir can come with / as os.sep, so we
@@ -285,25 +289,30 @@ def run_python_script_in_terminal(fname, wdir, args, interact,
                                    "an external terminal"),
                                  QMessageBox.Ok)
     elif os.name == 'posix':
-        cmd = 'gnome-terminal'
-        if is_program_installed(cmd):
-            run_program(cmd, ['--working-directory', wdir, '-x'] + p_args,
-                        cwd=wdir)
-            return
-        cmd = 'konsole'
-        if is_program_installed(cmd):
-            run_program(cmd, ['--workdir', wdir, '-e'] + p_args,
-                        cwd=wdir)
-            return
-        cmd = 'xfce4-terminal'
-        if is_program_installed(cmd):
-            run_program(cmd, ['--working-directory', wdir, '-x'] + p_args,
-                        cwd=wdir)
-            return
-        cmd = 'xterm'
-        if is_program_installed(cmd):
-            run_program(cmd, ['-e'] + p_args + [wdir])
-            return		
+        programs = [{'cmd': 'gnome-terminal',
+                     'wdir-option': '--working-directory',
+                     'execute-option': '-x'},
+                    {'cmd': 'konsole',
+                     'wdir-option': '--workdir',
+                     'execute-option': '-e'},
+                    {'cmd': 'xfce4-terminal',
+                     'wdir-option': '--working-directory',
+                     'execute-option': '-x'},
+                    {'cmd': 'xterm',
+                     'wdir-option': None,
+                     'execute-option': '-e'},]
+        for program in programs:
+            if is_program_installed(program['cmd']):
+                arglist = []
+                if program['wdir-option'] and wdir:
+                    arglist += [program['wdir-option'], wdir]
+                arglist.append(program['execute-option'])
+                arglist += p_args
+                if wdir:
+                    run_program(program['cmd'], arglist, cwd=wdir)
+                else:
+                    run_program(program['cmd'], arglist)
+                return
         # TODO: Add a fallback to OSX
     else:
         raise NotImplementedError


Test patch:
diff --git a/spyderlib/utils/tests/test_programs.py b/spyderlib/utils/tests/test_programs.py
new file mode 100644
--- /dev/null
+++ b/spyderlib/utils/tests/test_programs.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2009- The Spyder Development Team
+# Licensed under the terms of the MIT License
+
+"""Tests for programs.py"""
+
+import time
+
+import pytest
+
+from spyderlib.utils.programs import run_python_script_in_terminal
+
+def test_run_python_script_in_terminal(tmpdir):
+    scriptpath = tmpdir.join('write-done.py')
+    outfilepath = tmpdir.join('out.txt')
+    script = ("with open('out.txt', 'w') as f:\n"
+              "    f.write('done')\n")
+    scriptpath.write(script)
+    run_python_script_in_terminal(scriptpath.strpath, tmpdir.strpath, '',
+                                  False, False, '')
+    time.sleep(1) # wait for script to finish
+    res = outfilepath.read()
+    assert res == 'done'
+
+def test_run_python_script_in_terminal_with_wdir_empty(tmpdir):
+    scriptpath = tmpdir.join('write-done.py')
+    outfilepath = tmpdir.join('out.txt')
+    script = ("with open('{}', 'w') as f:\n"
+              "    f.write('done')\n").format(outfilepath.strpath)
+    scriptpath.write(script)
+    run_python_script_in_terminal(scriptpath.strpath, '', '', False, False, '')
+    time.sleep(1) # wait for script to finish
+    res = outfilepath.read()
+    assert res == 'done'
+
+    
+if __name__ == '__main__':
+    pytest.main()
+