Skip to content

Commit

Permalink
verify Python is runnable
Browse files Browse the repository at this point in the history
  • Loading branch information
mmarchetti committed Oct 16, 2023
1 parent 9cab7dd commit ae3d263
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 5 deletions.
23 changes: 21 additions & 2 deletions internal/environment/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ func NewPythonInspector(projectDir util.Path, pythonPath util.Path, log logging.
}
}

func (i *defaultPythonInspector) validatePythonExecutable(pythonExecutable string) error {
args := []string{"--version"}
_, err := i.executor.runPythonCommand(pythonExecutable, args)
return err
}

func (i *defaultPythonInspector) getPythonExecutable(exec util.PathLooker) (string, error) {
if i.pythonPath.Path() != "" {
// User-provided python executable
Expand All @@ -71,10 +77,23 @@ func (i *defaultPythonInspector) getPythonExecutable(exec util.PathLooker) (stri
} else {
// Use whatever is on PATH
path, err := exec.LookPath("python3")
if err == nil {
// Ensure the Python is actually runnable. This is especially
// needed on Windows, where `python3` is (by default)
// an app execution alias. Also, installing Python from
// python.org does not disable the built-in app execution aliases.
err = i.validatePythonExecutable(path)
}
if err != nil {
return exec.LookPath("python")
path, err = exec.LookPath("python")
if err == nil {
err = i.validatePythonExecutable(path)
}
}
if err != nil {
return "", err
}
return path, err
return path, nil
}
}

Expand Down
73 changes: 70 additions & 3 deletions internal/environment/python_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,83 @@ func (s *PythonSuite) TestGetPythonVersionFromRealDefaultPython() {
s.True(strings.HasPrefix(version, "3."))
}

type mockPythonExecutor struct {
mock.Mock
}

var _ pythonExecutor = &mockPythonExecutor{}

func (m *mockPythonExecutor) runPythonCommand(pythonExecutable string, args []string) ([]byte, error) {
mockArgs := m.Called(pythonExecutable, args)
out := mockArgs.Get(0)
if out != nil {
return out.([]byte), mockArgs.Error(1)
} else {
return nil, mockArgs.Error(1)
}
}

func (s *PythonSuite) TestGetPythonExecutableFallbackPython() {
// python3 does not exist
// python exists and is runnable
log := logging.New()
inspector := NewPythonInspector(util.Path{}, util.Path{}, log)
executor := &mockPythonExecutor{}
executor.On("runPythonCommand", "/some/python", mock.Anything).Return(nil, nil)
inspector := &defaultPythonInspector{
executor: executor,
log: log,
}

exec := util.NewMockPathLooker()
exec.On("LookPath", "python3").Return("", os.ErrNotExist)
exec.On("LookPath", "python").Return("/usr/bin/python", nil)
exec.On("LookPath", "python").Return("/some/python", nil)
executable, err := inspector.getPythonExecutable(exec)
s.NoError(err)
s.Equal("/some/python", executable)
}

func (s *PythonSuite) TestGetPythonExecutablePython3NotRunnable() {
// python3 exists but is not runnable
// python exists and is runnable
log := logging.New()
executor := &mockPythonExecutor{}
testError := errors.New("exit status 9009")
executor.On("runPythonCommand", "/some/python3", mock.Anything).Return(nil, testError)
executor.On("runPythonCommand", "/some/python", mock.Anything).Return(nil, nil)

inspector := &defaultPythonInspector{
executor: executor,
log: log,
}

exec := util.NewMockPathLooker()
exec.On("LookPath", "python3").Return("/some/python3", nil)
exec.On("LookPath", "python").Return("/some/python", nil)
executable, err := inspector.getPythonExecutable(exec)
s.NoError(err)
s.Equal("/usr/bin/python", executable)
s.Equal("/some/python", executable)
}

func (s *PythonSuite) TestGetPythonExecutableNoRunnablePython() {
// python3 exists but is not runnable
// python exists but is not runnable
log := logging.New()
executor := &mockPythonExecutor{}
testError := errors.New("exit status 9009")
executor.On("runPythonCommand", "/some/python3", mock.Anything).Return(nil, testError)
executor.On("runPythonCommand", "/some/python", mock.Anything).Return(nil, testError)

inspector := &defaultPythonInspector{
executor: executor,
log: log,
}

exec := util.NewMockPathLooker()
exec.On("LookPath", "python3").Return("/some/python3", nil)
exec.On("LookPath", "python").Return("/some/python", nil)
executable, err := inspector.getPythonExecutable(exec)
s.NotNil(err)
s.Equal("", executable)
}

func (s *PythonSuite) TestGetRequirementsFromFile() {
Expand Down

0 comments on commit ae3d263

Please sign in to comment.