Skip to content

Commit

Permalink
Add checks to ycm_core library
Browse files Browse the repository at this point in the history
In addition to the outdated library check, add the following checks:
 - missing library;
 - compiled with Python 2 but imported with Python 3;
 - compiled with Python 3 but imported with Python 2.
Exit with a non-zero status code if one of these requirements is not
met.
Add corresponding tests.
Remove check_core_version script.
  • Loading branch information
micbou committed Apr 26, 2016
1 parent a3c41b4 commit 964d969
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 112 deletions.
44 changes: 0 additions & 44 deletions check_core_version.py

This file was deleted.

8 changes: 4 additions & 4 deletions ycmd/__main__.py
Expand Up @@ -25,7 +25,7 @@
import os

sys.path.insert( 0, os.path.dirname( os.path.abspath( __file__ ) ) )
from server_utils import SetUpPythonPath, CompatibleWithCurrentCoreVersion
from server_utils import SetUpPythonPath, CompatibleWithCurrentCore
SetUpPythonPath()

from future import standard_library
Expand Down Expand Up @@ -157,9 +157,9 @@ def Main():
YcmCoreSanityCheck()
extra_conf_store.CallGlobalExtraConfYcmCorePreloadIfExists()

if not CompatibleWithCurrentCoreVersion():
# ycm_core.[so|dll|dylib] is too old and needs to be recompiled.
sys.exit( 2 )
code = CompatibleWithCurrentCore()
if code:
sys.exit( code )

PossiblyDetachFromTerminal()

Expand Down
23 changes: 5 additions & 18 deletions ycmd/handlers.py
Expand Up @@ -23,30 +23,17 @@
standard_library.install_aliases()
from builtins import * # noqa

from os import path

try:
import ycm_core
except ImportError as e:
raise RuntimeError(
'Error importing ycm_core. Are you sure you have placed a '
'version 3.2+ libclang.[so|dll|dylib] in folder "{0}"? '
'See the Installation Guide in the docs. Full error: {1}'.format(
path.realpath( path.join( path.abspath( __file__ ), '..', '..' ) ),
str( e ) ) )

import atexit
import logging
import json
import bottle
import http.client
import json
import logging
import traceback
from bottle import request
from . import server_state
from ycmd import user_options_store

import ycm_core
from ycmd import extra_conf_store, hmac_plugin, server_state, user_options_store
from ycmd.responses import BuildExceptionResponse, BuildCompletionResponse
from ycmd import hmac_plugin
from ycmd import extra_conf_store
from ycmd.request_wrap import RequestWrap
from ycmd.bottle_utils import SetResponseHeader
from ycmd.completers.completer_utils import FilterAndSortCandidatesWrap
Expand Down
83 changes: 69 additions & 14 deletions ycmd/server_utils.py
Expand Up @@ -22,24 +22,34 @@
# No other imports from `future` because this module is loaded before we have
# put our submodules in sys.path

import sys
import os
import io
import logging
import os
import re
import sys

CORE_MISSING_ERROR_REGEX = re.compile( "No module named '?ycm_core'?" )
CORE_PYTHON2_ERROR_REGEX = re.compile(
'dynamic module does not define (?:init|module export) '
'function \(PyInit_ycm_core\)|'
'Module use of python2[0-9].dll conflicts with this version of Python\.$' )
CORE_PYTHON3_ERROR_REGEX = re.compile(
'dynamic module does not define init function \(initycm_core\)|'
'Module use of python3[0-9].dll conflicts with this version of Python\.$' )

CORE_MISSING_MESSAGE = 'ycm_core library not detected; you need to compile it.'
CORE_PYTHON2_MESSAGE = ( 'ycm_core library compiled with Python 2 '
'but loaded with Python 3.' )
CORE_PYTHON3_MESSAGE = ( 'ycm_core library compiled with Python 3 '
'but loaded with Python 2.' )
CORE_OUTDATED_MESSAGE = 'ycm_core library too old, PLEASE RECOMPILE.'

VERSION_FILENAME = 'CORE_VERSION'
CORE_NOT_COMPATIBLE_MESSAGE = (
'ycmd can\'t run: ycm_core lib too old, PLEASE RECOMPILE'
)

DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) )
DIR_PACKAGES_REGEX = re.compile( '(site|dist)-packages$' )


def SetUpPythonPath():
sys.path.insert( 0, os.path.join( DIR_OF_CURRENT_SCRIPT, '..' ) )

AddNearestThirdPartyFoldersToSysPath( __file__ )
_logger = logging.getLogger( __name__ )


def ExpectedCoreVersion():
Expand All @@ -48,13 +58,58 @@ def ExpectedCoreVersion():
return int( f.read() )


def CompatibleWithCurrentCoreVersion():
import ycm_core
def ImportCore():
"""Imports and returns the ycm_core module. This function exists for easily
mocking this import in tests."""
import ycm_core as ycm_core
return ycm_core


def CompatibleWithCurrentCore():
"""Checks if ycm_core library is compatible and returns with one of the
following status codes:
0: ycm_core is compatible;
1: unexpected error;
3: ycm_core is missing;
4: ycm_core is compiled with Python 2 but loaded with Python 3;
5: ycm_core is compiled with Python 3 but loaded with Python 2;
6: ycm_core version is outdated.
2 is not used as a status code because it has often a special meaning for Unix
programs. See https://docs.python.org/2/library/sys.html#sys.exit"""
try:
ycm_core = ImportCore()
except ImportError as error:
message = str( error )
if CORE_MISSING_ERROR_REGEX.match( message ):
_logger.exception( CORE_MISSING_MESSAGE )
return 3
if CORE_PYTHON2_ERROR_REGEX.match( message ):
_logger.exception( CORE_PYTHON2_MESSAGE )
return 4
if CORE_PYTHON3_ERROR_REGEX.match( message ):
_logger.exception( CORE_PYTHON3_MESSAGE )
return 5
_logger.exception( message )
return 1

try:
current_core_version = ycm_core.YcmCoreVersion()
except AttributeError:
return False
return ExpectedCoreVersion() == current_core_version
_logger.exception( CORE_OUTDATED_MESSAGE )
return 6

if ExpectedCoreVersion() != current_core_version:
_logger.error( CORE_OUTDATED_MESSAGE )
return 6

return 0


def SetUpPythonPath():
sys.path.insert( 0, os.path.join( DIR_OF_CURRENT_SCRIPT, '..' ) )

AddNearestThirdPartyFoldersToSysPath( __file__ )


def AncestorFolders( path ):
Expand Down
31 changes: 0 additions & 31 deletions ycmd/tests/check_core_version_test.py

This file was deleted.

106 changes: 105 additions & 1 deletion ycmd/tests/server_utils_test.py
Expand Up @@ -24,13 +24,14 @@
from builtins import * # noqa

from hamcrest import ( assert_that, calling, contains, contains_inanyorder,
raises )
equal_to, empty, raises )
from mock import patch
from nose.tools import ok_
import os.path
import sys

from ycmd.server_utils import ( AddNearestThirdPartyFoldersToSysPath,
CompatibleWithCurrentCore,
PathToNearestThirdPartyFolder )

DIR_OF_THIRD_PARTY = os.path.abspath(
Expand All @@ -50,6 +51,109 @@
)


@patch( 'ycmd.server_utils._logger' )
def RunCompatibleWithCurrentCore( test, logger ):
if 'import_error' in test:
with patch( 'ycmd.server_utils.ImportCore',
side_effect = ImportError( test[ 'import_error' ] ) ):
code = CompatibleWithCurrentCore()
else:
code = CompatibleWithCurrentCore()

assert_that( code, equal_to( test[ 'return_code' ] ) )

if 'message' in test:
assert_that( logger.method_calls[ 0 ][ 1 ][ 0 ],
equal_to( test[ 'message' ] ) )
else:
assert_that( logger.method_calls, empty() )


def CompatibleWithCurrentCore_Compatible_test():
RunCompatibleWithCurrentCore( {
'return_code': 0
} )


def CompatibleWithCurrentCore_Unexpected_test():
RunCompatibleWithCurrentCore( {
'import_error': 'unexpected import error',
'return_code': 1,
'message': 'unexpected import error'
} )


def CompatibleWithCurrentCore_Missing_test():
import_errors = [
# Raised by Python 2.
'No module named ycm_core',
# Raised by Python 3.
"No module named 'ycm_core'"
]

for error in import_errors:
yield RunCompatibleWithCurrentCore, {
'import_error': error,
'return_code': 3,
'message': 'ycm_core library not detected; you need to compile it.'
}


def CompatibleWithCurrentCore_Python2_test():
import_errors = [
# Raised on Linux and OS X with Python 3.3 and 3.4.
'dynamic module does not define init function (PyInit_ycm_core).',
# Raised on Linux and OS X with Python 3.5.
'dynamic module does not define module export function (PyInit_ycm_core).',
# Raised on Windows.
'Module use of python26.dll conflicts with this version of Python.',
'Module use of python27.dll conflicts with this version of Python.'
]

for error in import_errors:
yield RunCompatibleWithCurrentCore, {
'import_error': error,
'return_code': 4,
'message': 'ycm_core library compiled with Python 2 '
'but loaded with Python 3.'
}


def CompatibleWithCurrentCore_Python3_test():
import_errors = [
# Raised on Linux and OS X.
'dynamic module does not define init function (initycm_core).',
# Raised on Windows.
'Module use of python34.dll conflicts with this version of Python.',
'Module use of python35.dll conflicts with this version of Python.'
]

for error in import_errors:
yield RunCompatibleWithCurrentCore, {
'import_error': error,
'return_code': 5,
'message': 'ycm_core library compiled with Python 3 '
'but loaded with Python 2.'
}


@patch( 'ycm_core.YcmCoreVersion', return_value = AttributeError )
def CompatibleWithCurrentCore_Outdated_NoYcmCoreVersionMethod_test( *args ):
RunCompatibleWithCurrentCore( {
'return_code': 6,
'message': 'ycm_core library too old, PLEASE RECOMPILE.'
} )


@patch( 'ycm_core.YcmCoreVersion', return_value = 10 )
@patch( 'ycmd.server_utils.ExpectedCoreVersion', return_value = 11 )
def CompatibleWithCurrentCore_Outdated_NoVersionMatch_test( *args ):
RunCompatibleWithCurrentCore( {
'return_code': 6,
'message': 'ycm_core library too old, PLEASE RECOMPILE.'
} )


def PathToNearestThirdPartyFolder_Success_test():
ok_( PathToNearestThirdPartyFolder( os.path.abspath( __file__ ) ) )

Expand Down

0 comments on commit 964d969

Please sign in to comment.