Skip to content

Commit

Permalink
Improve tests for FindExecutable
Browse files Browse the repository at this point in the history
  • Loading branch information
vheon committed Jun 6, 2016
1 parent 214dcdd commit 4268395
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 41 deletions.
1 change: 1 addition & 0 deletions ycmd/completers/typescript/typescript_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def __init__( self, user_options ):
if not binarypath:
_logger.error( BINARY_NOT_FOUND_MESSAGE )
raise RuntimeError( BINARY_NOT_FOUND_MESSAGE )
_logger.info( 'Found TSServer at {0}'.format( binarypath ) )

self._logfile = _LogFileName()
tsserver_log = '-file {path} -level {level}'.format( path = self._logfile,
Expand Down
13 changes: 12 additions & 1 deletion ycmd/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import nose
import functools
import os
import tempfile
import stat

from ycmd import handlers, user_options_store
from ycmd.completers.completer import Completer
Expand Down Expand Up @@ -160,13 +162,22 @@ def UserOption( key, value ):
@contextlib.contextmanager
def CurrentWorkingDirectory( path ):
old_cwd = os.getcwd()
os.chdir( path )
try:
os.chdir( path )
yield
finally:
os.chdir( old_cwd )


# The "exe" suffix is needed on Windows and not harmful on other platforms.
@contextlib.contextmanager
def TemporaryExecutable( extension = '.exe' ):
with tempfile.NamedTemporaryFile( prefix = 'Temp',
suffix = extension ) as executable:
os.chmod( executable.name, stat.S_IXUSR )
yield executable.name


def SetUpApp():
bottle.debug( True )
handlers.SetServerStateToDefaults()
Expand Down
50 changes: 38 additions & 12 deletions ycmd/tests/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,22 @@

import os
import subprocess
import tempfile
from shutil import rmtree
import ycm_core
from future.utils import native
from mock import patch, call
from nose.tools import eq_, ok_
from ycmd import utils
from ycmd.tests.test_utils import ( Py2Only, Py3Only, WindowsOnly,
CurrentWorkingDirectory )
CurrentWorkingDirectory,
TemporaryExecutable )
from ycmd.tests import PathToTestFile

# NOTE: isinstance() vs type() is carefully used in this test file. Before
# changing things here, read the comments in utils.ToBytes.


ROOT_PATH = os.path.join( os.path.dirname( os.path.abspath( __file__ ) ),
'..', '..' )


@Py2Only
def ToBytes_Py2Bytes_test():
value = utils.ToBytes( bytes( 'abc' ) )
Expand Down Expand Up @@ -470,13 +468,41 @@ def SplitLines_test():
yield lambda: eq_( utils.SplitLines( test[ 0 ] ), test[ 1 ] )


def FindExecutable_ReturnSameRelativePath_IfFileIsExecutable_test():
with CurrentWorkingDirectory( ROOT_PATH ):
executable = os.path.join( '.', 'build.py' )
def FindExecutable_AbsolutePath_test():
with TemporaryExecutable() as executable:
eq_( executable, utils.FindExecutable( executable ) )


def FindExecutable_ReturnNone_IfFileIsNotExecutable_test():
with CurrentWorkingDirectory( ROOT_PATH ):
executable = os.path.join( '.', 'README.md' )
eq_( None, utils.FindExecutable( executable ) )
def FindExecutable_RelativePath_test():
with TemporaryExecutable() as executable:
dirname, exename = os.path.split( executable )
relative_executable = os.path.join( '.', exename )
with CurrentWorkingDirectory( dirname ):
eq_( relative_executable, utils.FindExecutable( relative_executable ) )


@patch.dict( 'os.environ', { 'PATH': tempfile.gettempdir() } )
def FindExecutable_ExecutableNameInPath_test():
with TemporaryExecutable() as executable:
dirname, exename = os.path.split( executable )
eq_( executable, utils.FindExecutable( exename ) )


def FindExecutable_ReturnNoneIfFileIsNotExecutable_test():
with tempfile.NamedTemporaryFile() as non_executable:
eq_( None, utils.FindExecutable( non_executable.name ) )


@WindowsOnly
def FindExecutable_CurrentDirectory_test():
with TemporaryExecutable() as executable:
dirname, exename = os.path.split( executable )
with CurrentWorkingDirectory( dirname ):
eq_( executable, utils.FindExecutable( exename ) )


@WindowsOnly
@patch.dict( 'os.environ', { 'PATHEXT': '.xyz' } )
def FindExecutable_AdditionalPathExt_test():
with TemporaryExecutable( extension = '.xyz' ) as executable:
eq_( executable, utils.FindExecutable( executable ) )
62 changes: 34 additions & 28 deletions ycmd/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,33 @@ def PathToFirstExistingExecutable( executable_name_list ):
return None


# Check that a given file can be accessed as an executable file.
# Additionally check that `file` is not a directory, as on Windows
# directories pass the os.access check.
def IsExecutable( filename ):
return ( os.path.exists( filename )
and os.access( filename, EXECUTABLE_FILE_MASK )
and not os.path.isdir( filename ) )
def _GetWindowsExecutable( filename ):
def _GetPossibleWindowsExecutable( filename ):
pathext = [ ext.lower() for ext in
os.environ.get( 'PATHEXT', '' ).split( os.pathsep ) ]
base, extension = os.path.splitext( filename )
if extension.lower() in pathext:
return [ filename ]
else:
return [ base + ext for ext in pathext ]

for exe in _GetPossibleWindowsExecutable( filename ):
if os.path.isfile( exe ):
return exe
return None


# Check that a given file can be accessed as an executable file, so controlling
# the access mask on Unix and if has a valid extension on Windows. It returns
# the path to the executable or None if no executable was found.
def GetExecutable( filename ):
if OnWindows():
return _GetWindowsExecutable( filename )

if ( os.path.isfile( filename )
and os.access( filename, EXECUTABLE_FILE_MASK ) ):
return filename
return None


# Adapted from https://hg.python.org/cpython/file/3.5/Lib/shutil.py#l1081
Expand All @@ -224,34 +244,20 @@ def FindExecutable( executable ):
# than referring to PATH directories. This includes checking relative to the
# current directory, e.g. ./script
if os.path.dirname( executable ):
if IsExecutable( executable ):
return executable
return None
return GetExecutable( executable )

paths = os.environ[ 'PATH' ].split( os.pathsep )
base, extension = os.path.splitext( executable )

if OnWindows():
# The current directory takes precedence on Windows.
if os.curdir not in paths:
paths.insert( 0, os.curdir )

# See if the given file matches any of the expected path extensions. This
# will allow us to short circuit when given "python.exe". If it does
# match, only test that one, otherwise we have to try others.
pathext = os.environ.get( 'PATHEXT', '' ).split( os.pathsep )
if extension in pathext:
files = [ executable ]
else:
files = [ base + ext for ext in pathext ]
else:
files = [ executable ]
curdir = os.path.abspath( os.curdir )
if curdir not in paths:
paths.insert( 0, curdir )

for path in paths:
for file in files:
name = os.path.join( path, file )
if IsExecutable( name ):
return name
exe = GetExecutable( os.path.join( path, executable ) )
if exe:
return exe
return None


Expand Down

0 comments on commit 4268395

Please sign in to comment.