Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Validate that Main Interpreter is really a Python interpreter #3842

Merged
merged 11 commits into from
Jan 7, 2017
10 changes: 9 additions & 1 deletion spyder/plugins/maininterpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,15 @@ def python_executable_changed(self, pyexec):
return
if not is_text_string(pyexec):
pyexec = to_text_string(pyexec.toUtf8(), 'utf-8')
self.warn_python_compatibility(pyexec)
if programs.is_python_interpreter(pyexec):
self.warn_python_compatibility(pyexec)
else:
QMessageBox.warning(self, _('Warning'),
_("You selected an invalid Python interpreter for the "
"console so the previous interpreter will stay. Please "
"make sure to select a valid one."), QMessageBox.Ok)
self.pyexec_edit.setText(get_python_executable())
return

def python_executable_switched(self, custom):
"""Python executable default/custom radio button has been toggled"""
Expand Down
28 changes: 27 additions & 1 deletion spyder/utils/programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

# Local imports
from spyder.utils import encoding
from spyder.py3compat import PY2, is_text_string
from spyder.py3compat import PY2, is_text_string, to_text_string


class ProgramError(Exception):
Expand Down Expand Up @@ -462,6 +462,32 @@ def is_module_installed(module_name, version=None, installed_version=None,

return check_version(actver, version, symb)

def is_python_interpreter_valid_name(filename):
"""Check that the python interpreter file has a valid name."""
pattern = r'.*python(\d\.?\d*)?(w)?(.exe)?$'
if re.match(pattern, filename, flags=re.I) is None:
return False
else:
return True

def is_python_interpreter(filename):
"""Evaluate wether a file is a python interpreter or not."""
real_filename = os.path.realpath(filename) # To follow symlink if existent
if (not osp.isfile(real_filename) or encoding.is_text_file(real_filename)
or not is_python_interpreter_valid_name(filename)):
return False
try:
proc = run_program(filename, ["-h"])
output = to_text_string(proc.communicate()[0])
valid = ("Options and arguments (and corresponding environment "
"variables)")
if 'usage:' in output and valid in output:
return True
else:
return False
except:
return False


def test_programs():
assert find_program('git')
Expand Down
36 changes: 33 additions & 3 deletions spyder/utils/tests/test_programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
"""Tests for programs.py"""

import os

import pytest

from spyder.utils.programs import run_python_script_in_terminal
from spyder.utils.programs import (run_python_script_in_terminal,
is_python_interpreter,
is_python_interpreter_valid_name)


if os.name == 'nt':
python_dir = os.environ['PYTHON']
VALID_INTERPRETER = os.path.join(python_dir, 'python.exe')
INVALID_INTERPRETER = os.path.join(python_dir, 'Scripts', 'ipython.exe')
else:
home_dir = os.environ['HOME']
VALID_INTERPRETER = os.path.join(home_dir, 'miniconda', 'bin', 'python')
INVALID_INTERPRETER = os.path.join(home_dir, 'miniconda', 'bin', 'ipython')


@pytest.mark.skipif(os.name == 'nt', reason='gets stuck on Windows') # FIXME
def test_run_python_script_in_terminal(tmpdir, qtbot):
Expand All @@ -24,6 +36,7 @@ def test_run_python_script_in_terminal(tmpdir, qtbot):
res = outfilepath.read()
assert res == 'done'


@pytest.mark.skipif(os.name == 'nt', reason='gets stuck on Windows') # FIXME
def test_run_python_script_in_terminal_with_wdir_empty(tmpdir, qtbot):
scriptpath = tmpdir.join('write-done.py')
Expand All @@ -36,7 +49,24 @@ def test_run_python_script_in_terminal_with_wdir_empty(tmpdir, qtbot):
res = outfilepath.read()
assert res == 'done'



@pytest.mark.skipif(os.environ.get('CI', None) == True,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mariacamilaremolinagutierrez Is this the right condition? It appears that the test will be skipped if CI is set to True, but I think you want the test to run when CI is set to True.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is these tests are running locally (which is what I wanted to prevent)

@mariacamilaremolinagutierrez, please fix that.

reason='It only runs in CI services.')
def test_is_valid_interpreter():
assert is_python_interpreter(VALID_INTERPRETER)


@pytest.mark.skipif(os.environ.get('CI', None) == True,
reason='It only runs in CI services.')
def test_is_invalid_interpreter():
assert not is_python_interpreter(INVALID_INTERPRETER)


def test_is_valid_interpreter_name():
names = ['python', 'pythonw', 'python2.7', 'python3.5', 'python.exe', 'pythonw.exe']
assert all([is_python_interpreter_valid_name(n) for n in names])


if __name__ == '__main__':
pytest.main()