Jelajahi Sumber

Auto merge of #2016 - Valloric:py3, r=micbou

[READY] Making YCM run in Vim with only python3

Since https://github.com/Valloric/ycmd/pull/358, ycmd has supported Python 3. Now's the time to make the YCM client work in Python 3 too. As with the ycmd PR, **until we merge this, no other code-changing PR should be merged.** We need to get to a state where we have green Travis for Python 3 before any other PRs start landing.

This was substantially easier than porting ycmd, but it was still a massive pain. It's likely to fail in certain corner cases, so please give this a spin!

The best way to test it out is to build a Vim that has only Python 3 support. Our wiki page on [building Vim from source](https://github.com/Valloric/YouCompleteMe/wiki/Building-Vim-from-source) is a great start; you'll probably need to change `--enable-pythoninterp` to `--enable-python3interp` and similar for `--with-python-config-dir`. Or just get yourself a beta image of Ubuntu 16.04 LTS and run it in a VM (16.04 doesn't even ship Python 2 in the base image!). You can verify that you're running the right Vim by looking at the output of `vim --version`. It should have `-python` and `+python3`.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.svg" height="40" alt="Review on Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/2016)
<!-- Reviewable:end -->
Homu 9 tahun lalu
induk
melakukan
d4f8c1d404
41 mengubah file dengan 1023 tambahan dan 572 penghapusan
  1. 37 10
      .travis.yml
  2. 58 33
      README.md
  3. 8 6
      appveyor.yml
  4. 73 41
      autoload/youcompleteme.vim
  5. 29 0
      ci/appveyor/appveyor_install.bat
  6. 2 0
      ci/travis/travis_install.linux.sh
  7. 24 0
      ci/travis/travis_install.osx.sh
  8. 60 0
      ci/travis/travis_install.sh
  9. 0 5
      install.py
  10. 2 2
      plugin/youcompleteme.vim
  11. 13 4
      python/ycm/base.py
  12. 18 7
      python/ycm/client/base_request.py
  13. 14 3
      python/ycm/client/command_request.py
  14. 10 1
      python/ycm/client/completer_available_request.py
  15. 18 9
      python/ycm/client/completion_request.py
  16. 10 2
      python/ycm/client/event_notification.py
  17. 13 6
      python/ycm/client/omni_completion_request.py
  18. 10 2
      python/ycm/client/tests/command_request_test.py
  19. 11 3
      python/ycm/client/tests/completion_request_test.py
  20. 8 0
      python/ycm/client/ycmd_keepalive.py
  21. 16 7
      python/ycm/diagnostic_interface.py
  22. 12 3
      python/ycm/omni_completer.py
  23. 28 11
      python/ycm/paths.py
  24. 16 5
      python/ycm/setup.py
  25. 10 1
      python/ycm/syntax_parse.py
  26. 8 0
      python/ycm/test_utils.py
  27. 8 0
      python/ycm/tests/base_test.py
  28. 22 6
      python/ycm/tests/event_notification_test.py
  29. 7 0
      python/ycm/tests/omni_completion_request_tests.py
  30. 35 5
      python/ycm/tests/paths_test.py
  31. 282 271
      python/ycm/tests/postcomplete_tests.py
  32. 8 0
      python/ycm/tests/syntax_parse_test.py
  33. 92 24
      python/ycm/tests/vimsupport_test.py
  34. 10 22
      python/ycm/tests/youcompleteme_test.py
  35. 34 17
      python/ycm/vimsupport.py
  36. 15 5
      python/ycm/youcompleteme.py
  37. 1 2
      third_party/retries/retries.py
  38. 1 1
      third_party/ycmd
  39. 0 3
      travis/travis_install.linux.sh
  40. 0 19
      travis/travis_install.osx.sh
  41. 0 36
      travis/travis_install.sh

+ 37 - 10
.travis.yml

@@ -7,22 +7,49 @@ before_install:
   - git submodule update --init --recursive
 install:
   # source because it sets up env vars on some platforms
-  - source travis/travis_install.sh
+  - source ci/travis/travis_install.sh
 script: ./run_tests.py
 env:
   matrix:
-    - YCMD_PYTHON_VERSION=2.7
-    - YCMD_PYTHON_VERSION=2.6
+    - YCM_PYTHON_VERSION=2.7
+    - YCM_PYTHON_VERSION=2.6
+    - YCM_PYTHON_VERSION=3.3
+matrix:
+  exclude:
+    - os: osx
+      env: YCM_PYTHON_VERSION=2.6
 addons:
   apt:
     sources:
-     - deadsnakes
+     # The Travis apt source whitelist can be found here:
+     #   https://github.com/travis-ci/apt-source-whitelist/blob/master/ubuntu.json
+     - ubuntu-toolchain-r-test  # for new libstdc++
+     - deadsnakes  # for various versions of python
+     - kalakris-cmake # for a more recent version of cmake (needed for ninja-build)
     packages:
-     - python2.6
-     - python2.6-dev
-     - python2.7
-     - python2.7-dev
-     - python-virtualenv
+     - cmake
+     - ninja-build
+     # The confusing part is that on Travis Linux with YCMD_PYTHON_VERSION=3.3,
+     # we build the C++ parts against the below system python3.3, but run
+     # against the pyenv python3.3. This is because stupid cmake 2.8.11 has a
+     # bug preventing it from finding the pyenv pythons (ostensibly; I haven't
+     # checked, but online reports say the issue is gone with cmake 3.4).
+     # Everything still works though, it's just weird.
+     - python3.3
+     - python3.3-dev
+     # Everything below is a Python build dep (though it depends on Python
+     # version). We need them because pyenv builds Python.
+     - libssl-dev
+     - zlib1g-dev
+     - libbz2-dev
+     - libreadline-dev
+     - libsqlite3-dev
+     - wget
+     - curl
+     - llvm
+     - libncurses5-dev
+     - libncursesw5-dev
 cache:
   directories:
-    - $HOME/.cache/pip
+    - $HOME/.cache/pip  # Python packages from pip
+    - $HOME/.pyenv  # pyenv

+ 58 - 33
README.md

@@ -205,10 +205,10 @@ that are conservatively turned off by default that you may want to turn on.
 Please refer to the full Installation Guide below; the following commands are
 provided on a best-effort basis and may not work for you.
 
-Make sure you have Vim 7.3.598 with python2 support. Ubuntu 14.04 and later have
-a Vim that's recent enough. You can see the version of Vim installed by running
-`vim --version`. If the version is too old, you may need to [compile Vim
-from source][vim-build] (don't worry, it's easy).
+Make sure you have Vim 7.3.598 with python2 or python3 support. Ubuntu 14.04 and
+later have a Vim that's recent enough. You can see the version of Vim installed
+by running `vim --version`. If the version is too old, you may need to [compile
+Vim from source][vim-build] (don't worry, it's easy).
 
 Install YouCompleteMe with [Vundle][].
 
@@ -219,7 +219,8 @@ process.
 
 Install development tools and CMake: `sudo apt-get install build-essential cmake`
 
-Make sure you have Python headers installed: `sudo apt-get install python-dev`.
+Make sure you have Python headers installed: `sudo apt-get install python-dev
+python3-dev`.
 
 Compiling YCM **with** semantic support for C-family languages:
 
@@ -263,10 +264,10 @@ that are conservatively turned off by default that you may want to turn on.
 Please refer to the full Installation Guide below; the following commands are
 provided on a best-effort basis and may not work for you.
 
-Make sure you have Vim 7.3.598 with python2 support. Fedora 21 and later have
-a Vim that's recent enough. You can see the version of Vim installed by running
-`vim --version`. If the version is too old, you may need to [compile Vim
-from source][vim-build] (don't worry, it's easy).
+Make sure you have Vim 7.3.598 with python2 or python3 support. Fedora 21 and
+later have a Vim that's recent enough. You can see the version of Vim installed
+by running `vim --version`. If the version is too old, you may need to [compile
+Vim from source][vim-build] (don't worry, it's easy).
 
 Install YouCompleteMe with [Vundle][].
 
@@ -277,7 +278,8 @@ process.
 
 Install development tools and CMake: `sudo dnf install automake gcc gcc-c++ kernel-devel cmake`
 
-Make sure you have Python headers installed: `sudo dnf install python-devel`.
+Make sure you have Python headers installed: `sudo dnf install python-devel
+python3-devel`.
 
 Compiling YCM **with** semantic support for C-family languages:
 
@@ -324,11 +326,11 @@ provided on a best-effort basis and may not work for you.
 **Important:** we assume that you are using the `cmd.exe` command prompt and
 that you know how to add an executable to the PATH environment variable.
 
-Make sure you have at least Vim 7.3.598 with python2 support. You can check the
-version by typing `:version` inside Vim. Take note of the Vim architecture, i.e.
-32 or 64-bit. It will be important when choosing the python2 installer. We
-recommend using a 64-bit client. Don't worry, [a frequently updated copy of
-64-bit Vim][vim64-win-download] is available.
+Make sure you have at least Vim 7.3.598 with python2 or python3 support. You can
+check the version by typing `:version` inside Vim. Take note of the Vim
+architecture, i.e. 32 or 64-bit. It will be important when choosing the python2
+installer. We recommend using a 64-bit client. Don't worry, [a frequently
+updated copy of 64-bit Vim][vim64-win-download] is available.
 
 Install YouCompleteMe with [Vundle][].
 
@@ -339,7 +341,7 @@ process.
 
 Download and install the following software:
 
-- [python2][python-win-download]. Be sure to pick the version corresponding to
+- [python3][python-win-download]. Be sure to pick the version corresponding to
 your Vim architecture. It is _Windows x86-64 MSI installer_ if you are using the
 Vim previously linked.
 - [CMake][cmake-download]. Add CMake executable to the PATH environment
@@ -397,7 +399,7 @@ Please refer to the full Installation Guide below; the following commands are
 provided on a best-effort basis and may not work for you. OpenBSD / FreeBSD are
 not officially supported platforms by YCM.
 
-Make sure you have Vim 7.3.598 with python2 support.
+Make sure you have Vim 7.3.598 with python2 or python3 support.
 
 OpenBSD 5.5 and later have a Vim that's recent enough. You can see the version of
 Vim installed by running `vim --version`.
@@ -473,7 +475,7 @@ process.
 **Please follow the instructions carefully. Read EVERY WORD.**
 
 1.  **Ensure that your version of Vim is _at least_ 7.3.598 _and_ that it has
-    support for python2 scripting**.
+    support for python2 or python3 scripting**.
 
     Inside Vim, type `:version`. Look at the first two to three lines of output;
     it should say `Vi IMproved X.Y`, where X.Y is the major version of vim. If
@@ -485,11 +487,11 @@ process.
     from source][vim-build] (don't worry, it's easy).
 
     After you have made sure that you have Vim 7.3.598+, type the following in
-    Vim: `:echo has('python')`. The output should be 1. If it's 0, then get a
-    version of Vim with Python support.
+    Vim: `:echo has('python') || has('python3')`. The output should be 1. If
+    it's 0, then get a version of Vim with Python support.
 
     On Windows, check also if your Vim architecture is 32 or 64-bit. This is
-    critical because it must match the python2 and the YCM libraries
+    critical because it must match the python and the YCM libraries
     architectures. We recommend using a 64-bit Vim.
 
 2.  **Install YCM** with [Vundle][] (or [Pathogen][], but Vundle is a better
@@ -518,8 +520,8 @@ process.
     the upstream compiled binaries. Random things may break. Save yourself the
     hassle and use the upstream pre-built libclang.
 
-4.  **Compile the `ycm_core` libraries** that YCM needs. These libs
-    are the C++ engines that YCM uses to get fast completions.
+4.  **Compile the `ycm_core` library** that YCM needs. This library
+    is the C++ engine that YCM uses to get fast completions.
 
     You will need to have `cmake` installed in order to generate the required
     makefiles. Linux users can install cmake with their package manager (`sudo
@@ -528,10 +530,10 @@ process.
     it through [Homebrew][brew] with `brew install cmake`.
 
     On a Unix OS, you need to make sure you have Python headers installed. On a
-    Debian-like Linux distro, this would be `sudo apt-get install python-dev`.
-    On Mac they should already be present.
+    Debian-like Linux distro, this would be `sudo apt-get install python-dev
+    python3-dev`. On Mac they should already be present.
 
-    On Windows, you need to download and install [python2][python-win-download].
+    On Windows, you need to download and install [python3][python-win-download].
     Pick the version corresponding to your Vim architecture. You will also need
     Microsoft Visual C++ (MSVC) to build YCM. You can obtain it by installing
     [Visual Studio][visual-studio-download]. MSVC 11 (Visual Studio 2012), 12
@@ -931,7 +933,7 @@ let g:ycm_rust_src_path = '/usr/local/rust/rustc-1.5.0/src'
 
 Completion and GoTo commands work out of the box with no additional
 configuration. Those features are provided by the [jedi][] library which
-supports a variety of python versions (2.6, 2.7, 3.2, 3.3 or 3.4) as long as it
+supports a variety of python versions (2.6, 2.7, 3.2+) as long as it
 runs in the corresponding python interpreter. By default YCM runs [jedi][] with
 the same python interpreter used by the [ycmd server][ycmd], so if you would like to
 use a different interpreter, use the following option specifying the python
@@ -2300,8 +2302,8 @@ let g:ycm_disable_for_files_larger_than_kb = 1000
 
 This option specifies the Python interpreter to use to run the [jedi][]
 completion library.  Specify the python interpreter to use to get completions.
-By default the python under which [ycmd][] runs is used ([ycmd][] only runs
-under Python 2.6 or 2.7).
+By default the python under which [ycmd][] runs is used ([ycmd][] runs on
+Python 2.6, 2.7 or 3.3+).
 
 Default: `''`
 
@@ -2331,6 +2333,29 @@ option for details.
 That's a very rare Vim bug most users never encounter. It's fixed in Vim
 7.4.72. Update to that version (or above) to resolve the issue.
 
+### I get `ImportError` exceptions that mention `PyInit_ycm_core` or
+`initycm_core`
+
+These errors are caused by building the YCM native libraries for Python 2 and
+trying to load them into a Python 3 process (or the other way around).
+
+For instance, if building for Python 2 but loading in Python 3:
+
+```
+ImportError: dynamic module does not define init function (PyInit_ycm_core)
+```
+
+If building for Python 3 but loading in Python 2:
+
+```
+ImportError: dynamic module does not define init function (initycm_core)
+```
+
+Setting the `g:ycm_path_to_python_interpreter` option to force the use of a
+specific Python interpreter for `ycmd` is usually the easiest way to solve the
+problem. Common values for that option are `/usr/bin/python` and
+`/usr/bin/python3`.
+
 ### I get a linker warning regarding `libpython` on Mac when compiling YCM
 
 If the warning is `ld: warning: path '/usr/lib/libpython2.7.dylib' following -L
@@ -2721,10 +2746,10 @@ EOF
 
 ### I hear that YCM only supports Python 2, is that true?
 
-No. The Vim client and the [ycmd server][ycmd] only run under Python 2 but if
-you work on a Python 3 project then just set the `g:ycm_python_binary_path` to
-the Python interpreter you use for your project and you will get completions for
-that version of Python.
+**No.** Both the Vim client and the [ycmd server][ycmd] run on Python 2 or 3. If
+you work on a Python 3 project, you may need to set `g:ycm_python_binary_path`
+to the Python interpreter you use for your project to get completions for that
+version of Python.
 
 Contributor Code of Conduct
 ---------------------------

+ 8 - 6
appveyor.yml

@@ -2,15 +2,17 @@ version: '{build}'
 environment:
   matrix:
   - arch: 32
+    python: 35
+  # We only test Python 2.7 on 64 bits.
   - arch: 64
+    python: 27
+  - arch: 64
+    python: 35
 install:
-  - git submodule update --init --recursive
-  - ps: $env:python = if ($env:arch -eq 32) { 'C:\Python27' } else { 'C:\Python27-x64' }
-  - appveyor DownloadFile https://bootstrap.pypa.io/get-pip.py
-  - set PATH=%python%;%python%\Scripts;%PATH%
-  - python get-pip.py
-  - pip install -r python\test_requirements.txt
+  - ci\appveyor\appveyor_install.bat
 build_script:
   - python run_tests.py
 # Disable automatic tests
 test: off
+cache:
+  - '%LOCALAPPDATA%\pip\cache'  # Python packages from pip

+ 73 - 41
autoload/youcompleteme.vim

@@ -30,6 +30,29 @@ let s:moved_vertically_in_insert_mode = 0
 let s:previous_num_chars_on_current_line = strlen( getline('.') )
 
 
+function! s:UsingPython2()
+  " I'm willing to bet quite a bit that sooner or later, somebody will ask us to
+  " make it configurable which version of Python we use.
+  if has('python')
+    return 1
+  endif
+  return 0
+endfunction
+
+
+let s:using_python2 = s:UsingPython2()
+let s:python_until_eof = s:using_python2 ? "python << EOF" : "python3 << EOF"
+let s:python_command = s:using_python2 ? "py " : "py3 "
+
+
+function! s:Pyeval( eval_string )
+  if s:using_python2
+    return pyeval( a:eval_string )
+  endif
+  return py3eval( a:eval_string )
+endfunction
+
+
 function! youcompleteme#Enable()
   " When vim is in diff mode, don't run
   if &diff
@@ -120,17 +143,22 @@ endfunction
 
 
 function! youcompleteme#GetErrorCount()
-  return pyeval( 'ycm_state.GetErrorCount()' )
+  return s:Pyeval( 'ycm_state.GetErrorCount()' )
 endfunction
 
 
 function! youcompleteme#GetWarningCount()
-  return pyeval( 'ycm_state.GetWarningCount()' )
+  return s:Pyeval( 'ycm_state.GetWarningCount()' )
 endfunction
 
 
 function! s:SetUpPython() abort
-python << EOF
+  exec s:python_until_eof
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
 import os
 import sys
 import traceback
@@ -340,7 +368,7 @@ function! s:SetUpCpoptions()
 
   " This prevents the display of "Pattern not found" & similar messages during
   " completion. This is only available since Vim 7.4.314
-  if pyeval( 'vimsupport.VimVersionAtLeast("7.4.314")' )
+  if s:Pyeval( 'vimsupport.VimVersionAtLeast("7.4.314")' )
     set shortmess+=c
   endif
 endfunction
@@ -383,12 +411,12 @@ endfunction
 
 
 function! s:OnVimLeave()
-  py ycm_state.OnVimLeave()
+  exec s:python_command "ycm_state.OnVimLeave()"
 endfunction
 
 
 function! s:OnCompleteDone()
-  py ycm_state.OnCompleteDone()
+  exec s:python_command "ycm_state.OnCompleteDone()"
 endfunction
 
 
@@ -421,7 +449,7 @@ function! s:OnBufferVisit()
     call s:SetOmnicompleteFunc()
   endif
 
-  py ycm_state.OnBufferVisit()
+  exec s:python_command "ycm_state.OnBufferVisit()"
   call s:OnFileReadyToParse()
 endfunction
 
@@ -431,7 +459,8 @@ function! s:OnBufferUnload( deleted_buffer_file )
     return
   endif
 
-  py ycm_state.OnBufferUnload( vim.eval( 'a:deleted_buffer_file' ) )
+  exec s:python_command "ycm_state.OnBufferUnload("
+        \ "vim.eval( 'a:deleted_buffer_file' ) )"
 endfunction
 
 
@@ -454,11 +483,11 @@ function! s:OnFileReadyToParse()
   " reparsing the file again. If we sent the new parse request first, then
   " the response would always be pending when we called
   " HandleFileParseRequest.
-  py ycm_state.HandleFileParseRequest()
+  exec s:python_command "ycm_state.HandleFileParseRequest()"
 
   let buffer_changed = b:changedtick != b:ycm_changedtick.file_ready_to_parse
   if buffer_changed
-    py ycm_state.OnFileReadyToParse()
+    exec s:python_command "ycm_state.OnFileReadyToParse()"
   endif
   let b:ycm_changedtick.file_ready_to_parse = b:changedtick
 endfunction
@@ -471,7 +500,7 @@ endfunction
 
 
 function! s:SetOmnicompleteFunc()
-  if pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
+  if s:Pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
     let &omnifunc = 'youcompleteme#OmniComplete'
     let &l:omnifunc = 'youcompleteme#OmniComplete'
 
@@ -489,7 +518,7 @@ function! s:OnCursorMovedInsertMode()
     return
   endif
 
-  py ycm_state.OnCursorMoved()
+  exec s:python_command "ycm_state.OnCursorMoved()"
   call s:UpdateCursorMoved()
 
   " Basically, we need to only trigger the completion menu when the user has
@@ -515,7 +544,7 @@ function! s:OnCursorMovedInsertMode()
   " We have to make sure we correctly leave omnifunc mode even when the user
   " inserts something like a "operator[]" candidate string which fails
   " CurrentIdentifierFinished check.
-  if s:omnifunc_mode && !pyeval( 'base.LastEnteredCharIsIdentifierChar()')
+  if s:omnifunc_mode && !s:Pyeval( 'base.LastEnteredCharIsIdentifierChar()')
     let s:omnifunc_mode = 0
   endif
 endfunction
@@ -527,7 +556,7 @@ function! s:OnCursorMovedNormalMode()
   endif
 
   call s:OnFileReadyToParse()
-  py ycm_state.OnCursorMoved()
+  exec s:python_command "ycm_state.OnCursorMoved()"
 endfunction
 
 
@@ -538,7 +567,7 @@ function! s:OnInsertLeave()
 
   let s:omnifunc_mode = 0
   call s:OnFileReadyToParse()
-  py ycm_state.OnInsertLeave()
+  exec s:python_command "ycm_state.OnInsertLeave()"
   if g:ycm_autoclose_preview_window_after_completion ||
         \ g:ycm_autoclose_preview_window_after_insertion
     call s:ClosePreviewWindowIfNeeded()
@@ -601,10 +630,10 @@ endfunction
 
 
 function! s:IdentifierFinishedOperations()
-  if !pyeval( 'base.CurrentIdentifierFinished()' )
+  if !s:Pyeval( 'base.CurrentIdentifierFinished()' )
     return
   endif
-  py ycm_state.OnCurrentIdentifierFinished()
+  exec s:python_command "ycm_state.OnCurrentIdentifierFinished()"
   let s:omnifunc_mode = 0
 endfunction
 
@@ -613,7 +642,8 @@ endfunction
 function! s:InsideCommentOrString()
   " Has to be col('.') -1 because col('.') doesn't exist at this point. We are
   " in insert mode when this func is called.
-  let syntax_group = synIDattr( synIDtrans( synID( line( '.' ), col( '.' ) - 1, 1 ) ), 'name')
+  let syntax_group = synIDattr(
+        \ synIDtrans( synID( line( '.' ), col( '.' ) - 1, 1 ) ), 'name')
 
   if stridx(syntax_group, 'Comment') > -1
     return 1
@@ -642,7 +672,7 @@ endfunction
 
 
 function! s:OnBlankLine()
-  return pyeval( 'not vim.current.line or vim.current.line.isspace()' )
+  return s:Pyeval( 'not vim.current.line or vim.current.line.isspace()' )
 endfunction
 
 
@@ -678,7 +708,7 @@ function! s:InvokeCompletion()
 endfunction
 
 
-python << EOF
+exec s:python_until_eof
 def GetCompletionsInner():
   request = ycm_state.GetCurrentCompletionRequest()
   request.Start()
@@ -692,8 +722,8 @@ EOF
 
 
 function! s:GetCompletions()
-  py results = GetCompletionsInner()
-  let results = pyeval( 'results' )
+  exec s:python_command "results = GetCompletionsInner()"
+  let results = s:Pyeval( 'results' )
   return results
 endfunction
 
@@ -718,11 +748,11 @@ function! youcompleteme#Complete( findstart, base )
       return -2
     endif
 
-    if !pyeval( 'ycm_state.IsServerAlive()' )
+    if !s:Pyeval( 'ycm_state.IsServerAlive()' )
       return -2
     endif
-    py ycm_state.CreateCompletionRequest()
-    return pyeval( 'base.CompletionStartColumn()' )
+    exec s:python_command "ycm_state.CreateCompletionRequest()"
+    return s:Pyeval( 'base.CompletionStartColumn()' )
   else
     return s:GetCompletions()
   endif
@@ -731,12 +761,13 @@ endfunction
 
 function! youcompleteme#OmniComplete( findstart, base )
   if a:findstart
-    if !pyeval( 'ycm_state.IsServerAlive()' )
+    if !s:Pyeval( 'ycm_state.IsServerAlive()' )
       return -2
     endif
     let s:omnifunc_mode = 1
-    py ycm_state.CreateCompletionRequest( force_semantic = True )
-    return pyeval( 'base.CompletionStartColumn()' )
+    exec s:python_command "ycm_state.CreateCompletionRequest("
+          \ "force_semantic = True )"
+    return s:Pyeval( 'base.CompletionStartColumn()' )
   else
     return s:GetCompletions()
   endif
@@ -744,23 +775,23 @@ endfunction
 
 
 function! youcompleteme#ServerPid()
-  return pyeval( 'ycm_state.ServerPid()' )
+  return s:Pyeval( 'ycm_state.ServerPid()' )
 endfunction
 
 
 function! s:RestartServer()
-  py ycm_state.RestartServer()
+  exec s:python_command "ycm_state.RestartServer()"
 endfunction
 
 
 function! s:ShowDetailedDiagnostic()
-  py ycm_state.ShowDetailedDiagnostic()
+  exec s:python_command "ycm_state.ShowDetailedDiagnostic()"
 endfunction
 
 
 function! s:DebugInfo()
   echom "Printing YouCompleteMe debug information..."
-  let debug_info = pyeval( 'ycm_state.DebugInfo()' )
+  let debug_info = s:Pyeval( 'ycm_state.DebugInfo()' )
   for line in split( debug_info, "\n" )
     echom '-- ' . line
   endfor
@@ -770,8 +801,9 @@ endfunction
 function! s:ToggleLogs(...)
   let stderr = a:0 == 0 || a:1 !=? 'stdout'
   let stdout = a:0 == 0 || a:1 !=? 'stderr'
-  py ycm_state.ToggleLogs( stdout = vimsupport.GetBoolValue( 'l:stdout' ),
-                         \ stderr = vimsupport.GetBoolValue( 'l:stderr' ) )
+  exec s:python_command "ycm_state.ToggleLogs("
+        \ "stdout = vimsupport.GetBoolValue( 'l:stdout' ),"
+        \ "stderr = vimsupport.GetBoolValue( 'l:stderr' ) )"
 endfunction
 
 
@@ -793,8 +825,8 @@ function! s:CompleterCommand(...)
     let arguments = arguments[1:]
   endif
 
-  py ycm_state.SendCommandRequest( vim.eval( 'l:arguments' ),
-        \                          vim.eval( 'l:completer' ) )
+  exec s:python_command "ycm_state.SendCommandRequest("
+        \ "vim.eval( 'l:arguments' ), vim.eval( 'l:completer' ) ) "
 endfunction
 
 
@@ -814,21 +846,21 @@ endfunction
 
 
 function! youcompleteme#SubCommandsComplete( arglead, cmdline, cursorpos )
-  return join( pyeval( 'ycm_state.GetDefinedSubcommands()' ),
+  return join( s:Pyeval( 'ycm_state.GetDefinedSubcommands()' ),
     \ "\n")
 endfunction
 
 
 function! s:ForceCompile()
-  if !pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
+  if !s:Pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
     echom "Native filetype completion not supported for current file, "
           \ . "cannot force recompilation."
     return 0
   endif
 
   echom "Forcing compilation, this will block Vim until done."
-  py ycm_state.OnFileReadyToParse()
-  py ycm_state.HandleFileParseRequest( True )
+  exec s:python_command "ycm_state.OnFileReadyToParse()"
+  exec s:python_command "ycm_state.HandleFileParseRequest( True )"
 
   return 1
 endfunction
@@ -849,7 +881,7 @@ function! s:ShowDiagnostics()
     return
   endif
 
-  if pyeval( 'ycm_state.PopulateLocationListWithLatestDiagnostics()' )
+  if s:Pyeval( 'ycm_state.PopulateLocationListWithLatestDiagnostics()' )
     if g:ycm_open_loclist_on_ycm_diags
       lopen
     endif

+ 29 - 0
ci/appveyor/appveyor_install.bat

@@ -0,0 +1,29 @@
+git submodule update --init --recursive
+:: Batch script will not exit if a command returns an error, so we manually do
+:: it for commands that may fail.
+if %errorlevel% neq 0 exit /b %errorlevel%
+
+::
+:: Python configuration
+::
+
+if %arch% == 32 (
+  set python_path=C:\Python%python%
+) else (
+  set python_path=C:\Python%python%-x64
+)
+
+set PATH=%python_path%;%python_path%\Scripts;%PATH%
+python --version
+
+:: When using Python 3 on AppVeyor, CMake will always pick the 64 bit
+:: libraries. We specifically tell CMake the right path to the libraries
+:: according to the architecture.
+if %python% == 35 (
+  set EXTRA_CMAKE_ARGS="-DPYTHON_LIBRARY=%python_path%\libs\python%python%.lib"
+)
+
+appveyor DownloadFile https://bootstrap.pypa.io/get-pip.py
+python get-pip.py
+pip install -r python\test_requirements.txt
+if %errorlevel% neq 0 exit /b %errorlevel%

+ 2 - 0
ci/travis/travis_install.linux.sh

@@ -0,0 +1,2 @@
+# Linux installation
+

+ 24 - 0
ci/travis/travis_install.osx.sh

@@ -0,0 +1,24 @@
+# OS X-specific installation
+
+# There's a homebrew bug which causes brew update to fail the first time. Run
+# it twice to workaround. https://github.com/Homebrew/homebrew/issues/42553
+brew update || brew update
+
+# List of homebrew formulae to install in the order they appear.
+# These are dependencies of pyenv.
+REQUIREMENTS="ninja
+              readline
+              autoconf
+              pkg-config
+              openssl"
+
+# Install pyenv and dependencies
+for pkg in $REQUIREMENTS; do
+  # Install package, or upgrade it if it is already installed
+  brew install $pkg || brew outdated $pkg || brew upgrade $pkg
+done
+
+# In order to work with ycmd, python *must* be built as a shared library. The
+# most compatible way to do this on OS X is with --enable-framework. This is
+# set via the PYTHON_CONFIGURE_OPTS option
+export PYTHON_CONFIGURE_OPTS="--enable-framework"

+ 60 - 0
ci/travis/travis_install.sh

@@ -0,0 +1,60 @@
+#!/bin/bash
+
+set -ev
+
+####################
+# OS-specific setup
+####################
+
+# Requirements of OS-specific install:
+#  - install any software which is not installed by Travis configuration
+#  - set up everything necessary so that pyenv can build python
+source ci/travis/travis_install.${TRAVIS_OS_NAME}.sh
+
+#############
+# pyenv setup
+#############
+
+# DON'T exit if error
+set +e
+git clone https://github.com/yyuu/pyenv.git ~/.pyenv
+git fetch --tags
+git checkout v20160202
+# Exit if error
+set -e
+
+export PYENV_ROOT="$HOME/.pyenv"
+export PATH="$PYENV_ROOT/bin:$PATH"
+
+eval "$(pyenv init -)"
+
+if [ "${YCM_PYTHON_VERSION}" == "2.6" ]; then
+  PYENV_VERSION="2.6.6"
+elif [ "${YCM_PYTHON_VERSION}" == "2.7" ]; then
+  PYENV_VERSION="2.7.6"
+else
+  PYENV_VERSION="3.3.6"
+fi
+
+pyenv install --skip-existing ${PYENV_VERSION}
+pyenv rehash
+pyenv global ${PYENV_VERSION}
+
+# It is quite easy to get the above series of steps wrong. Verify that the
+# version of python actually in the path and used is the version that was
+# requested, and fail the build if we broke the travis setup
+python_version=$(python -c 'import sys; print( "{0}.{1}".format( sys.version_info[0], sys.version_info[1] ) )')
+echo "Checking python version (actual ${python_version} vs expected ${YCM_PYTHON_VERSION})"
+test ${python_version} == ${YCM_PYTHON_VERSION}
+
+############
+# pip setup
+############
+
+pip install -U pip wheel setuptools
+pip install -r python/test_requirements.txt
+
+# The build infrastructure prints a lot of spam after this script runs, so make
+# sure to disable printing, and failing on non-zero exit code after this script
+# finishes
+set +ev

+ 0 - 5
install.py

@@ -6,11 +6,6 @@ import sys
 import os.path as p
 import glob
 
-major, minor = sys.version_info[ 0 : 2 ]
-if major != 2 or minor < 6:
-  sys.exit( 'The build script requires Python version >= 2.6 and < 3.0; '
-            'your version of Python is ' + sys.version )
-
 DIR_OF_THIS_SCRIPT = p.dirname( p.abspath( __file__ ) )
 DIR_OF_OLD_LIBS = p.join( DIR_OF_THIS_SCRIPT, 'python' )
 

+ 2 - 2
plugin/youcompleteme.vim

@@ -33,10 +33,10 @@ elseif v:version < 703 || (v:version == 703 && !has('patch598'))
         \ echohl None
   call s:restore_cpo()
   finish
-elseif !has( 'python' )
+elseif !has( 'python' ) && !has( 'python3' )
   echohl WarningMsg |
         \ echomsg "YouCompleteMe unavailable: requires Vim compiled with " .
-        \ "Python 2.x support" |
+        \ "Python (2.6+ or 3.3+) support" |
         \ echohl None
   call s:restore_cpo()
   finish

+ 13 - 4
python/ycm/base.py

@@ -15,6 +15,15 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
+from future.utils import iteritems
 from ycm import vimsupport
 from ycmd import user_options_store
 from ycmd import request_wrap
@@ -29,7 +38,7 @@ def BuildServerConf():
 
   vim_globals = vimsupport.GetReadOnlyVimGlobals( force_python_objects = True )
   server_conf = {}
-  for key, value in vim_globals.items():
+  for key, value in iteritems( vim_globals ):
     if not key.startswith( YCM_VAR_PREFIX ):
       continue
     try:
@@ -45,7 +54,7 @@ def BuildServerConf():
 def LoadJsonDefaultsIntoVim():
   defaults = user_options_store.DefaultOptions()
   vim_defaults = {}
-  for key, value in defaults.iteritems():
+  for key, value in iteritems( defaults ):
     vim_defaults[ 'ycm_' + key ] = value
 
   vimsupport.LoadDictIntoVimGlobals( vim_defaults, overwrite = False )
@@ -115,7 +124,7 @@ def AdjustCandidateInsertionText( candidates ):
 
   new_candidates = []
   for candidate in candidates:
-    if type( candidate ) is dict:
+    if isinstance( candidate, dict ):
       new_candidate = candidate.copy()
 
       if not 'abbr' in new_candidate:
@@ -127,7 +136,7 @@ def AdjustCandidateInsertionText( candidates ):
 
       new_candidates.append( new_candidate )
 
-    elif type( candidate ) is str:
+    elif isinstance( candidate, str ) or isinstance( candidate, bytes ):
       new_candidates.append(
         { 'abbr': candidate,
           'word': NewCandidateInsertionText( candidate, text_after_cursor ) } )

+ 18 - 7
python/ycm/client/base_request.py

@@ -15,9 +15,18 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 import requests
-import urlparse
+import urllib.parse
 import json
+from future.utils import native
 from base64 import b64decode, b64encode
 from retries import retries
 from requests_futures.sessions import FuturesSession
@@ -33,6 +42,7 @@ _EXECUTOR = UnsafeThreadPoolExecutor( max_workers = 30 )
 _DEFAULT_TIMEOUT_SEC = 30
 _HMAC_HEADER = 'x-ycm-hmac'
 
+
 class BaseRequest( object ):
   def __init__( self ):
     pass
@@ -129,11 +139,11 @@ class BaseRequest( object ):
   @staticmethod
   def _ExtraHeaders( method, request_uri, request_body = None ):
     if not request_body:
-      request_body = ''
+      request_body = bytes( b'' )
     headers = dict( _HEADERS )
     headers[ _HMAC_HEADER ] = b64encode(
-        CreateRequestHmac( method,
-                           urlparse.urlparse( request_uri ).path,
+        CreateRequestHmac( ToBytes( method ),
+                           ToBytes( urllib.parse.urlparse( request_uri ).path ),
                            request_body,
                            BaseRequest.hmac_secret ) )
     return headers
@@ -180,7 +190,7 @@ def HandleServerException( exception ):
   # up often and isn't something that's actionable by the user.
   if 'already being parsed' in serialized_exception:
     return
-  vimsupport.PostVimMessage( serialized_exception )
+  vimsupport.PostMultiLineNotice( serialized_exception )
 
 
 def _ToUtf8Json( data ):
@@ -196,7 +206,8 @@ def _ValidateResponseObject( response ):
 
 
 def _BuildUri( handler ):
-  return urlparse.urljoin( BaseRequest.server_location, handler )
+  return native( ToBytes( urllib.parse.urljoin( BaseRequest.server_location,
+                                                handler ) ) )
 
 
 SERVER_HEALTHY = False
@@ -208,7 +219,7 @@ def _CheckServerIsHealthyWithCache():
     request_uri = _BuildUri( 'healthy' )
     response = requests.get( request_uri,
                              headers = BaseRequest._ExtraHeaders(
-                                 'GET', request_uri, '' ) )
+                                 'GET', request_uri, bytes( b'' ) ) )
     _ValidateResponseObject( response )
     response.raise_for_status()
     return response.json()

+ 14 - 3
python/ycm/client/command_request.py

@@ -15,8 +15,19 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 import vim
-from ycm.client.base_request import BaseRequest, BuildRequestData, ServerError
+from ycmd.responses import ServerError
+from ycm.client.base_request import ( BaseRequest, BuildRequestData,
+                                      HandleServerException )
+
 from ycm import vimsupport
 from ycmd.utils import ToUnicode
 
@@ -46,7 +57,7 @@ class CommandRequest( BaseRequest ):
       self._response = self.PostDataToHandler( request_data,
                                                'run_completer_command' )
     except ServerError as e:
-      vimsupport.PostMultiLineNotice( e )
+      HandleServerException( e )
 
 
   def Response( self ):
@@ -97,7 +108,7 @@ class CommandRequest( BaseRequest ):
       try:
         vimsupport.ReplaceChunks( chunks )
       except RuntimeError as e:
-        vimsupport.PostMultiLineNotice( e.message )
+        vimsupport.PostMultiLineNotice( str( e ) )
 
 
   def _HandleBasicResponse( self ):

+ 10 - 1
python/ycm/client/completer_available_request.py

@@ -15,8 +15,17 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.client.base_request import ( BaseRequest, BuildRequestData,
                                       HandleServerException )
+from ycmd.responses import ServerError
 
 class CompleterAvailableRequest( BaseRequest ):
   def __init__( self, filetypes ):
@@ -31,7 +40,7 @@ class CompleterAvailableRequest( BaseRequest ):
     try:
       self._response = self.PostDataToHandler( request_data,
                                                'semantic_completion_available' )
-    except Exception as e:
+    except ServerError as e:
       HandleServerException( e )
 
 

+ 18 - 9
python/ycm/client/completion_request.py

@@ -15,10 +15,19 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
-from ycmd.utils import ToBytes, ToUnicode
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
+from ycmd.utils import ToUnicode
 from ycm.client.base_request import ( BaseRequest, JsonFromFuture,
                                       HandleServerException,
                                       MakeServerException )
+from ycmd.responses import ServerError
 
 TIMEOUT_SECONDS = 0.5
 
@@ -45,12 +54,12 @@ class CompletionRequest( BaseRequest ):
     try:
       response = JsonFromFuture( self._response_future )
 
-      errors = response['errors'] if 'errors' in response else []
+      errors = response[ 'errors' ] if 'errors' in response else []
       for e in errors:
         HandleServerException( MakeServerException( e ) )
 
       return JsonFromFuture( self._response_future )[ 'completions' ]
-    except Exception as e:
+    except ServerError as e:
       HandleServerException( e )
     return []
 
@@ -69,22 +78,22 @@ def ConvertCompletionDataToVimData( completion_data ):
 
   if ( 'extra_data' in completion_data and
        'doc_string' in completion_data[ 'extra_data' ] ):
-    doc_string = ToBytes( completion_data[ 'extra_data' ][ 'doc_string' ] )
+    doc_string = completion_data[ 'extra_data' ][ 'doc_string' ]
   else:
     doc_string = ""
 
   if 'insertion_text' in completion_data:
-    vim_data[ 'word' ] = ToBytes( completion_data[ 'insertion_text' ] )
+    vim_data[ 'word' ] = completion_data[ 'insertion_text' ]
   if 'menu_text' in completion_data:
-    vim_data[ 'abbr' ] = ToBytes( completion_data[ 'menu_text' ] )
+    vim_data[ 'abbr' ] = completion_data[ 'menu_text' ]
   if 'extra_menu_info' in completion_data:
-    vim_data[ 'menu' ] = ToBytes( completion_data[ 'extra_menu_info' ] )
+    vim_data[ 'menu' ] = completion_data[ 'extra_menu_info' ]
   if 'kind' in completion_data:
     kind = ToUnicode( completion_data[ 'kind' ] )
     if kind:
-      vim_data[ 'kind' ] = ToBytes( kind[ 0 ].lower() )
+      vim_data[ 'kind' ] = kind[ 0 ].lower()
   if 'detailed_info' in completion_data:
-    vim_data[ 'info' ] = ToBytes( completion_data[ 'detailed_info' ] )
+    vim_data[ 'info' ] = completion_data[ 'detailed_info' ]
     if doc_string:
       vim_data[ 'info' ] += '\n' + doc_string
   elif doc_string:

+ 10 - 2
python/ycm/client/event_notification.py

@@ -15,8 +15,16 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm import vimsupport
-from ycmd.responses import UnknownExtraConf
+from ycmd.responses import UnknownExtraConf, ServerError
 from ycm.client.base_request import ( BaseRequest, BuildRequestData,
                                       JsonFromFuture, HandleServerException )
 
@@ -58,7 +66,7 @@ class EventNotification( BaseRequest ):
             _LoadExtraConfFile( e.extra_conf_file )
           else:
             _IgnoreExtraConfFile( e.extra_conf_file )
-    except Exception as e:
+    except ServerError as e:
       HandleServerException( e )
 
     return self._cached_response if self._cached_response else []

+ 13 - 6
python/ycm/client/omni_completion_request.py

@@ -15,7 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
-from ycmd.utils import ToBytes
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.client.completion_request import CompletionRequest
 
 
@@ -46,15 +53,15 @@ def ConvertVimDataToCompletionData( vim_data ):
   completion_data = {}
 
   if 'word' in vim_data:
-    completion_data[ 'insertion_text' ] = ToBytes( vim_data[ 'word' ] )
+    completion_data[ 'insertion_text' ] = vim_data[ 'word' ]
   if 'abbr' in vim_data:
-    completion_data[ 'menu_text' ] = ToBytes( vim_data[ 'abbr' ] )
+    completion_data[ 'menu_text' ] = vim_data[ 'abbr' ]
   if 'menu' in vim_data:
-    completion_data[ 'extra_menu_info' ] = ToBytes( vim_data[ 'menu' ] )
+    completion_data[ 'extra_menu_info' ] = vim_data[ 'menu' ]
   if 'kind' in vim_data:
-    completion_data[ 'kind' ] = [ ToBytes( vim_data[ 'kind' ] ) ]
+    completion_data[ 'kind' ] = [ vim_data[ 'kind' ] ]
   if 'info' in vim_data:
-    completion_data[ 'detailed_info' ] = ToBytes( vim_data[ 'info' ] )
+    completion_data[ 'detailed_info' ] = vim_data[ 'info' ]
 
   return completion_data
 

+ 10 - 2
python/ycm/client/tests/command_request_test.py

@@ -15,6 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.test_utils import MockVimModule
 MockVimModule()
 
@@ -24,7 +32,7 @@ from nose.tools import ok_
 from ycm.client.command_request import CommandRequest
 
 
-class GoToResponse_QuickFix_test:
+class GoToResponse_QuickFix_test( object ):
   """This class tests the generation of QuickFix lists for GoTo responses which
   return multiple locations, such as the Python completer and JavaScript
   completer. It mostly proves that we use 1-based indexing for the column
@@ -92,7 +100,7 @@ class GoToResponse_QuickFix_test:
     ] )
 
 
-class Response_Detection_test:
+class Response_Detection_test( object ):
 
   def BasicResponse_test( self ):
     def _BasicResponseTest( command, response ):

+ 11 - 3
python/ycm/client/tests/completion_request_test.py

@@ -15,13 +15,21 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from nose.tools import eq_
 from ycm.test_utils import MockVimModule
 vim_mock = MockVimModule()
 
 from .. import completion_request
 
-class ConvertCompletionResponseToVimDatas_test:
+class ConvertCompletionResponseToVimDatas_test( object ):
   """ This class tests the
       completion_request._ConvertCompletionResponseToVimDatas method """
 
@@ -32,10 +40,10 @@ class ConvertCompletionResponseToVimDatas_test:
     try:
       eq_( expected_vim_data, vim_data )
     except:
-      print "Expected:\n'{0}'\nwhen parsing:\n'{1}'\nBut found:\n'{2}'".format(
+      print( "Expected:\n'{0}'\nwhen parsing:\n'{1}'\nBut found:\n'{2}'".format(
           expected_vim_data,
           completion_data,
-          vim_data )
+          vim_data ) )
       raise
 
 

+ 8 - 0
python/ycm/client/ycmd_keepalive.py

@@ -15,6 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 import time
 from threading import Thread
 from ycm.client.base_request import BaseRequest

+ 16 - 7
python/ycm/diagnostic_interface.py

@@ -15,6 +15,15 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
+from future.utils import itervalues, iteritems
 from collections import defaultdict, namedtuple
 from ycm import vimsupport
 import vim
@@ -95,8 +104,8 @@ class DiagnosticInterface( object ):
     line_to_diags = self._buffer_number_to_line_to_diags[
       vim.current.buffer.number ]
 
-    for diags in line_to_diags.itervalues():
-      matched_diags.extend( filter( predicate, diags ) )
+    for diags in itervalues( line_to_diags ):
+      matched_diags.extend( list( filter( predicate, diags ) ) )
     return matched_diags
 
 
@@ -104,7 +113,7 @@ def _UpdateSquiggles( buffer_number_to_line_to_diags ):
   vimsupport.ClearYcmSyntaxMatches()
   line_to_diags = buffer_number_to_line_to_diags[ vim.current.buffer.number ]
 
-  for diags in line_to_diags.itervalues():
+  for diags in itervalues( line_to_diags ):
     for diag in diags:
       location_extent = diag[ 'location_extent' ]
       is_error = _DiagnosticIsError( diag )
@@ -168,11 +177,11 @@ def _GetKeptAndNewSigns( placed_signs, buffer_number_to_line_to_diags,
                          next_sign_id ):
   new_signs = []
   kept_signs = []
-  for buffer_number, line_to_diags in buffer_number_to_line_to_diags.iteritems():
+  for buffer_number, line_to_diags in iteritems( buffer_number_to_line_to_diags ):
     if not vimsupport.BufferIsVisible( buffer_number ):
       continue
 
-    for line, diags in line_to_diags.iteritems():
+    for line, diags in iteritems( line_to_diags ):
       for diag in diags:
         sign = _DiagSignPlacement( next_sign_id,
                                    line,
@@ -217,8 +226,8 @@ def _ConvertDiagListToDict( diag_list ):
     line_number = location[ 'line_num' ]
     buffer_to_line_to_diags[ buffer_number ][ line_number ].append( diag )
 
-  for line_to_diags in buffer_to_line_to_diags.itervalues():
-    for diags in line_to_diags.itervalues():
+  for line_to_diags in itervalues( buffer_to_line_to_diags ):
+    for diags in itervalues( line_to_diags ):
       # We also want errors to be listed before warnings so that errors aren't
       # hidden by the warnings; Vim won't place a sign oven an existing one.
       diags.sort( key = lambda diag: ( diag[ 'location' ][ 'column_num' ],

+ 12 - 3
python/ycm/omni_completer.py

@@ -15,10 +15,19 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 import vim
 from ycm import vimsupport
+from ycmd.responses import ServerError
 from ycmd.completers.completer import Completer
-from ycm.client.base_request import BaseRequest, ServerError
+from ycm.client.base_request import BaseRequest, HandleServerException
 
 OMNIFUNC_RETURNED_BAD_VALUE = 'Omnifunc returned bad value to YCM!'
 OMNIFUNC_NOT_LIST = ( 'Omnifunc did not return a list or a dict with a "words" '
@@ -84,7 +93,7 @@ class OmniCompleter( Completer ):
       if not hasattr( items, '__iter__' ):
         raise TypeError( OMNIFUNC_NOT_LIST )
 
-      return filter( bool, items )
+      return list( filter( bool, items ) )
     except ( TypeError, ValueError, vim.error ) as error:
       vimsupport.PostVimMessage(
         OMNIFUNC_RETURNED_BAD_VALUE + ' ' + str( error ) )
@@ -106,5 +115,5 @@ class OmniCompleter( Completer ):
       return BaseRequest.PostDataToHandler( request_data,
                                             'filter_and_sort_candidates' )
     except ServerError as e:
-      vimsupport.PostMultiLineNotice( e )
+      HandleServerException( e )
       return candidates

+ 28 - 11
python/ycm/paths.py

@@ -15,18 +15,27 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 import os
 import sys
 import vim
 import functools
 import re
 
+# Can't import these from setup.py because it makes nosetests go crazy.
 DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) )
 DIR_OF_YCMD = os.path.join( DIR_OF_CURRENT_SCRIPT, '..', '..', 'third_party',
                             'ycmd' )
-
 WIN_PYTHON_PATH = os.path.join( sys.exec_prefix, 'python.exe' )
-PYTHON_BINARY_REGEX = re.compile( r'python(2(\.[67])?)?(.exe)?$' )
+PYTHON_BINARY_REGEX = re.compile(
+  r'python((2(\.[67])?)|(3(\.[3-9])?))?(.exe)?$' )
 
 
 def Memoize( obj ):
@@ -52,7 +61,7 @@ def PathToPythonInterpreter():
       return python_interpreter
 
     raise RuntimeError( "Path in 'g:ycm_path_to_python_interpreter' option "
-                        "does not point to a valid Python 2.6 or 2.7." )
+                        "does not point to a valid Python 2.6+ or 3.3+." )
 
   # On UNIX platforms, we use sys.executable as the Python interpreter path.
   # We cannot use sys.executable on Windows because for unknown reasons, it
@@ -64,27 +73,30 @@ def PathToPythonInterpreter():
   if IsPythonVersionCorrect( python_interpreter ):
     return python_interpreter
 
-  # As a last resort, we search python in the PATH. We check 'python2' before
-  # 'python' because on some distributions (Arch Linux for example), python
-  # refers to python3.
+  # As a last resort, we search python in the PATH. We prefer Python 2 over 3
+  # for the sake of backwards compatibility with ycm_extra_conf.py files out
+  # there; few people wrote theirs to work on py3.
+  # So we check 'python2' before 'python' because on some distributions (Arch
+  # Linux for example), python refers to python3.
   python_interpreter = utils.PathToFirstExistingExecutable( [ 'python2',
-                                                              'python' ] )
+                                                              'python',
+                                                              'python3' ] )
 
   if IsPythonVersionCorrect( python_interpreter ):
     return python_interpreter
 
-  raise RuntimeError( "Cannot find Python 2.6 or 2.7. You can set its path "
+  raise RuntimeError( "Cannot find Python 2.6+ or 3.3+. You can set its path "
                       "using the 'g:ycm_path_to_python_interpreter' "
                       "option." )
 
 
 def EndsWithPython( path ):
-  """Check if given path ends with a python 2.6 or 2.7 name."""
+  """Check if given path ends with a python 2.6+ or 3.3+ name."""
   return PYTHON_BINARY_REGEX.search( path ) is not None
 
 
 def IsPythonVersionCorrect( path ):
-  """Check if given path is the Python interpreter version 2.6 or 2.7."""
+  """Check if given path is the Python interpreter version 2.6+ or 3.3+."""
   from ycmd import utils
 
   if not EndsWithPython( path ):
@@ -94,7 +106,12 @@ def IsPythonVersionCorrect( path ):
               '-c',
               "import sys;"
               "major, minor = sys.version_info[ :2 ];"
-              "sys.exit( major != 2 or minor < 6)" ]
+              "good_python = ( major == 2 and minor >= 6 ) "
+              "or ( major == 3 and minor >= 3 ) or major > 3;"
+              # If this looks weird, remember that:
+              #   int( True ) == 1
+              #   int( False ) == 0
+              "sys.exit( not good_python )" ]
 
   return utils.SafePopen( command ).wait() == 0
 

+ 16 - 5
python/ycm/setup.py

@@ -15,25 +15,36 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+# No imports from `future` because when this is loaded, sys.path hasn't been set
+# up yet!
+
 import sys
 import os
-import paths
+
+# Can't import these from paths.py because that uses `future` imports
+DIR_OF_CURRENT_SCRIPT = os.path.dirname( os.path.abspath( __file__ ) )
+DIR_OF_YCMD = os.path.join( DIR_OF_CURRENT_SCRIPT, '..', '..', 'third_party',
+                            'ycmd' )
 
 
 def SetUpSystemPaths():
-  sys.path.insert( 0, os.path.join( paths.DIR_OF_YCMD ) )
+  sys.path.insert( 0, os.path.join( DIR_OF_YCMD ) )
 
   from ycmd import server_utils as su
-  su.AddNearestThirdPartyFoldersToSysPath( paths.DIR_OF_CURRENT_SCRIPT )
+  su.AddNearestThirdPartyFoldersToSysPath( DIR_OF_CURRENT_SCRIPT )
   # We need to import ycmd's third_party folders as well since we import and
   # use ycmd code in the client.
   su.AddNearestThirdPartyFoldersToSysPath( su.__file__ )
 
 
 def SetUpYCM():
-  import base
+  from ycm import base, paths
   from ycmd import user_options_store, utils
-  from youcompleteme import YouCompleteMe
+  from ycm.youcompleteme import YouCompleteMe
 
   base.LoadJsonDefaultsIntoVim()
 

+ 10 - 1
python/ycm/syntax_parse.py

@@ -15,6 +15,15 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
+from future.utils import itervalues
 import re
 import vim
 from ycm import vimsupport
@@ -174,7 +183,7 @@ def _ConnectGroupChildren( group_name_to_group ):
         parent_names.append( line[ len( links_to ): ] )
     return parent_names
 
-  for group in group_name_to_group.itervalues():
+  for group in itervalues( group_name_to_group ):
     parent_names = GetParentNames( group )
 
     for parent_name in parent_names:

+ 8 - 0
python/ycm/test_utils.py

@@ -15,6 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from mock import MagicMock
 from hamcrest import assert_that, equal_to
 import re

+ 8 - 0
python/ycm/tests/base_test.py

@@ -17,6 +17,14 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 import contextlib
 from nose.tools import eq_, ok_
 from mock import patch

+ 22 - 6
python/ycm/tests/event_notification_test.py

@@ -15,6 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.test_utils import MockVimModule, ExtendedMock
 MockVimModule()
 
@@ -24,7 +32,7 @@ import os
 from ycm.youcompleteme import YouCompleteMe
 from ycmd import user_options_store
 from ycmd.responses import ( BuildDiagnosticData, Diagnostic, Location, Range,
-                             UnknownExtraConf )
+                             UnknownExtraConf, ServerError )
 
 from mock import call, MagicMock, patch
 from nose.tools import eq_, ok_
@@ -51,6 +59,14 @@ def PostVimMessage_Call( message ):
                '\' | echohl None' )
 
 
+def PostMultiLineNotice_Call( message ):
+  """Return a mock.call object for a call to vimsupport.PostMultiLineNotice with
+  the supplied message"""
+  return call( 'echohl WarningMsg | echo \''
+               + message +
+               '\' | echohl None' )
+
+
 def PresentDialog_Confirm_Call( message ):
   """Return a mock.call object for a call to vimsupport.PresentDialog, as called
   why vimsupport.Confirm with the supplied confirmation message"""
@@ -174,7 +190,7 @@ class EventNotification_test( object ):
     ERROR_TEXT = 'Some completer response text'
 
     def ErrorResponse( *args ):
-      raise RuntimeError( ERROR_TEXT )
+      raise ServerError( ERROR_TEXT )
 
     with MockArbitraryBuffer( 'javascript' ):
       with MockEventNotification( ErrorResponse ):
@@ -184,13 +200,13 @@ class EventNotification_test( object ):
 
         # The first call raises a warning
         vim_command.assert_has_exact_calls( [
-          PostVimMessage_Call( ERROR_TEXT ),
+          PostMultiLineNotice_Call( ERROR_TEXT ),
         ] )
 
         # Subsequent calls don't re-raise the warning
         self.server_state.HandleFileParseRequest()
         vim_command.assert_has_exact_calls( [
-          PostVimMessage_Call( ERROR_TEXT ),
+          PostMultiLineNotice_Call( ERROR_TEXT ),
         ] )
 
         # But it does if a subsequent event raises again
@@ -198,8 +214,8 @@ class EventNotification_test( object ):
         assert self.server_state.FileParseRequestReady()
         self.server_state.HandleFileParseRequest()
         vim_command.assert_has_exact_calls( [
-          PostVimMessage_Call( ERROR_TEXT ),
-          PostVimMessage_Call( ERROR_TEXT ),
+          PostMultiLineNotice_Call( ERROR_TEXT ),
+          PostMultiLineNotice_Call( ERROR_TEXT ),
         ] )
 
 

+ 7 - 0
python/ycm/tests/omni_completion_request_tests.py

@@ -15,6 +15,13 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
 
 from mock import MagicMock
 from nose.tools import eq_

+ 35 - 5
python/ycm/tests/paths_test.py

@@ -15,6 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.test_utils import MockVimModule
 MockVimModule()
 
@@ -22,25 +30,47 @@ from nose.tools import ok_
 from ycm.paths import EndsWithPython
 
 
+def EndsWithPython_Good( path ):
+  ok_( EndsWithPython( path ) )
+
+
+def EndsWithPython_Bad( path ):
+  ok_( not EndsWithPython( path ) )
+
+
 def EndsWithPython_Python2Paths_test():
   python_paths = [
     'python',
+    'python2',
     '/usr/bin/python2.6',
     '/home/user/.pyenv/shims/python2.7',
     r'C:\Python26\python.exe'
   ]
 
   for path in python_paths:
-    ok_( EndsWithPython( path ) )
+    yield EndsWithPython_Good, path
+
+
+
+def EndsWithPython_Python3Paths_test():
+  python_paths = [
+    'python3',
+    '/usr/bin/python3.3',
+    '/home/user/.pyenv/shims/python3.3',
+    r'C:\Python33\python.exe'
+  ]
+
+  for path in python_paths:
+    yield EndsWithPython_Good, path
 
 
-def EndsWithPython_NotPython2Paths_test():
+def EndsWithPython_BadPaths_test():
   not_python_paths = [
     '/opt/local/bin/vim',
     r'C:\Program Files\Vim\vim74\gvim.exe',
-    '/usr/bin/python3',
-    '/home/user/.pyenv/shims/python3',
+    '/usr/bin/python2.5',
+    '/home/user/.pyenv/shims/python3.2',
   ]
 
   for path in not_python_paths:
-    ok_( not EndsWithPython( path ) )
+    yield EndsWithPython_Bad, path

+ 282 - 271
python/ycm/tests/postcomplete_tests.py

@@ -15,16 +15,25 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.test_utils import MockVimModule
 MockVimModule()
 
-from mock import ( MagicMock, DEFAULT, patch )
-from nose.tools import eq_
+import contextlib
 from hamcrest import assert_that, empty
+from mock import MagicMock, DEFAULT, patch
+from nose.tools import eq_, ok_
+
 from ycm import vimsupport
 from ycm.youcompleteme import YouCompleteMe
 
-import contextlib
 
 def GetVariableValue_CompleteItemIs( word, abbr = None, menu = None,
                                      info = None, kind = None ):
@@ -37,28 +46,15 @@ def GetVariableValue_CompleteItemIs( word, abbr = None, menu = None,
         'info': info,
         'kind': kind,
       }
-    else:
-      return DEFAULT
+    return DEFAULT
   return MagicMock( side_effect = Result )
 
 
-@contextlib.contextmanager
-def _SetupForCsharpCompletionDone( completions ):
-  with patch( 'ycm.vimsupport.InsertNamespace' ):
-    with patch( 'ycm.vimsupport.TextBeforeCursor', return_value = '   Test' ):
-      ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-      request = MagicMock();
-      request.Done = MagicMock( return_value = True )
-      request.RawResponse = MagicMock( return_value = completions )
-      ycm_state._latest_completion_request = request
-      yield ycm_state
-
-
-def _BuildCompletion( namespace = None, insertion_text = 'Test',
-                      menu_text = None, extra_menu_info = None,
-                      detailed_info = None, kind = None ):
+def BuildCompletion( namespace = None, insertion_text = 'Test',
+                     menu_text = None, extra_menu_info = None,
+                     detailed_info = None, kind = None ):
   return {
-    'extra_data': { 'required_namespace_import' : namespace },
+    'extra_data': { 'required_namespace_import': namespace },
     'insertion_text': insertion_text,
     'menu_text': menu_text,
     'extra_menu_info': extra_menu_info,
@@ -67,348 +63,363 @@ def _BuildCompletion( namespace = None, insertion_text = 'Test',
   }
 
 
-@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "cs" ] )
-def GetCompleteDoneHooks_ResultOnCsharp_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  result = ycm_state.GetCompleteDoneHooks()
-  eq_( 1, len( list( result ) ) )
+class PostComplete_test():
+
+  def setUp( self ):
+    self.ycm = YouCompleteMe( MagicMock( spec_set = dict ) )
+
 
+  def tearDown( self ):
+    self.ycm.OnVimLeave()
 
-@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
-def GetCompleteDoneHooks_EmptyOnOtherFiletype_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  result = ycm_state.GetCompleteDoneHooks()
-  eq_( 0, len( list( result ) ) )
 
+  @contextlib.contextmanager
+  def _SetupForCsharpCompletionDone( self, completions ):
+    with patch( 'ycm.vimsupport.InsertNamespace' ):
+      with patch( 'ycm.vimsupport.TextBeforeCursor',
+                  return_value = '   Test' ):
+        request = MagicMock()
+        request.Done = MagicMock( return_value = True )
+        request.RawResponse = MagicMock( return_value = completions )
+        self.ycm._latest_completion_request = request
+        yield
 
-@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
-def OnCompleteDone_WithActionCallsIt_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  action = MagicMock()
-  ycm_state._complete_done_hooks[ "txt" ] = action
-  ycm_state.OnCompleteDone()
 
-  assert action.called
+  @patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "cs" ] )
+  def GetCompleteDoneHooks_ResultOnCsharp_test( self, *args ):
+    result = self.ycm.GetCompleteDoneHooks()
+    eq_( 1, len( list( result ) ) )
 
 
-@patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
-def OnCompleteDone_NoActionNoError_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
+  @patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
+  def GetCompleteDoneHooks_EmptyOnOtherFiletype_test( self, *args ):
+    result = self.ycm.GetCompleteDoneHooks()
+    eq_( 0, len( list( result ) ) )
 
-  ycm_state.OnCompleteDone()
 
+  @patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
+  def OnCompleteDone_WithActionCallsIt_test( self, *args ):
+    action = MagicMock()
+    self.ycm._complete_done_hooks[ "txt" ] = action
+    self.ycm.OnCompleteDone()
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
-@patch( 'ycm.vimsupport.GetVariableValue',
-        GetVariableValue_CompleteItemIs( 'Test' ) )
-def FilterToCompletedCompletions_NewVim_MatchIsReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "Test" ) ]
+    ok_( action.called )
 
-  result = ycm_state._FilterToMatchingCompletions( completions, False )
 
-  eq_( list( result ), completions )
+  @patch( 'ycm.vimsupport.CurrentFiletypes', return_value = [ "txt" ] )
+  def OnCompleteDone_NoActionNoError_test( self, *args ):
+    self.ycm.OnCompleteDone()
 
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
-@patch( 'ycm.vimsupport.GetVariableValue',
-        GetVariableValue_CompleteItemIs( 'A' ) )
-def FilterToCompletedCompletions_NewVim_ShortTextDoesntRaise_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "AAA" ) ]
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
+  @patch( 'ycm.vimsupport.GetVariableValue',
+          GetVariableValue_CompleteItemIs( 'Test' ) )
+  def FilterToCompletedCompletions_NewVim_MatchIsReturned_test( self, *args ):
+    completions = [ BuildCompletion( "Test" ) ]
 
-  ycm_state._FilterToMatchingCompletions( completions, False )
+    result = self.ycm._FilterToMatchingCompletions( completions, False )
 
+    eq_( list( result ), completions )
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
-@patch( 'ycm.vimsupport.GetVariableValue',
-        GetVariableValue_CompleteItemIs( 'Test' ) )
-def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "Test" ) ]
 
-  result = ycm_state._FilterToMatchingCompletions( completions, False )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
+  @patch( 'ycm.vimsupport.GetVariableValue',
+          GetVariableValue_CompleteItemIs( 'A' ) )
+  def FilterToCompletedCompletions_NewVim_ShortTextDoesntRaise_test( self,
+                                                                     *args ):
+    completions = [ BuildCompletion( "AAA" ) ]
 
-  eq_( list( result ), completions )
+    self.ycm._FilterToMatchingCompletions( completions, False )
 
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
-@patch( 'ycm.vimsupport.GetVariableValue',
-        GetVariableValue_CompleteItemIs( '   Quote' ) )
-def FilterToCompletedCompletions_NewVim_NonMatchIsntReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "A" ) ]
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
+  @patch( 'ycm.vimsupport.GetVariableValue',
+          GetVariableValue_CompleteItemIs( 'Test' ) )
+  def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test( self,
+                                                                     *args ):
+    completions = [ BuildCompletion( "Test" ) ]
 
-  result = ycm_state._FilterToMatchingCompletions( completions, False )
+    result = self.ycm._FilterToMatchingCompletions( completions, False )
 
-  assert_that( list( result ), empty() )
+    eq_( list( result ), completions )
 
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Test" )
-def FilterToCompletedCompletions_OldVim_MatchIsReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "Test" ) ]
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
+  @patch( 'ycm.vimsupport.GetVariableValue',
+          GetVariableValue_CompleteItemIs( '   Quote' ) )
+  def FilterToCompletedCompletions_NewVim_NonMatchIsntReturned_test( self,
+                                                                     *args ):
+    completions = [ BuildCompletion( "A" ) ]
 
-  result = ycm_state._FilterToMatchingCompletions( completions, False )
+    result = self.ycm._FilterToMatchingCompletions( completions, False )
 
-  eq_( list( result ), completions )
+    assert_that( list( result ), empty() )
 
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "X" )
-def FilterToCompletedCompletions_OldVim_ShortTextDoesntRaise_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "AAA" ) ]
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Test" )
+  def FilterToCompletedCompletions_OldVim_MatchIsReturned_test( self, *args ):
+    completions = [ BuildCompletion( "Test" ) ]
 
-  ycm_state._FilterToMatchingCompletions( completions, False )
+    result = self.ycm._FilterToMatchingCompletions( completions, False )
 
+    eq_( list( result ), completions )
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "Test" )
-def FilterToCompletedCompletions_OldVim_ExactMatchIsReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "Test" ) ]
 
-  result = ycm_state._FilterToMatchingCompletions( completions, False )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "X" )
+  def FilterToCompletedCompletions_OldVim_ShortTextDoesntRaise_test( self,
+                                                                     *args ):
+    completions = [ BuildCompletion( "AAA" ) ]
 
-  eq_( list( result ), completions )
+    self.ycm._FilterToMatchingCompletions( completions, False )
 
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Quote" )
-def FilterToCompletedCompletions_OldVim_NonMatchIsntReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "A" ) ]
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "Test" )
+  def FilterToCompletedCompletions_OldVim_ExactMatchIsReturned_test( self,
+                                                                     *args ):
+    completions = [ BuildCompletion( "Test" ) ]
 
-  result = ycm_state._FilterToMatchingCompletions( completions, False )
+    result = self.ycm._FilterToMatchingCompletions( completions, False )
 
-  assert_that( list( result ), empty() )
+    eq_( list( result ), completions )
 
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Te" )
-def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_MatchIsReturned_test(
-    *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "Test" ) ]
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Quote" )
+  def FilterToCompletedCompletions_OldVim_NonMatchIsntReturned_test( self,
+                                                                     *args ):
+    completions = [ BuildCompletion( "A" ) ]
 
-  result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
+    result = self.ycm._FilterToMatchingCompletions( completions, False )
 
-  eq_( result, True )
+    assert_that( list( result ), empty() )
 
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "X" )
-def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ShortTextDoesntRaise_test( *args ) :
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "AAA" ) ]
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Te" )
+  def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_MatchIsReturned_test(
+    self, *args ):
+    completions = [ BuildCompletion( "Test" ) ]
 
-  ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
+    result = self.ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "Test" )
-def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ExactMatchIsntReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "Test" ) ]
+    eq_( result, True )
 
-  result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
-  eq_( result, False )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "X" )
+  def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ShortTextDoesntRaise_test(
+    self, *args ):
+    completions = [ BuildCompletion( "AAA" ) ]
 
+    self.ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Quote" )
-def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_NonMatchIsntReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "A" ) ]
 
-  result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "Test" )
+  def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ExactMatchIsntReturned_test(
+    self, *args ):
+    completions = [ BuildCompletion( "Test" ) ]
 
-  eq_( result, False )
+    result = self.ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
+    eq_( result, False )
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
-@patch( 'ycm.vimsupport.GetVariableValue',
-        GetVariableValue_CompleteItemIs( "Te") )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Quote" )
-def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_MatchIsReturned_test(
-    *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "Test" ) ]
 
-  result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Quote" )
+  def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_NonMatchIsntReturned_test(
+    self, *args ):
+    completions = [ BuildCompletion( "A" ) ]
 
-  eq_( result, True )
+    result = self.ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
+    eq_( result, False )
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
-@patch( 'ycm.vimsupport.GetVariableValue',
-        GetVariableValue_CompleteItemIs( "X") )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Quote" )
-def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ShortTextDoesntRaise_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "AAA" ) ]
 
-  ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
+  @patch( 'ycm.vimsupport.GetVariableValue',
+          GetVariableValue_CompleteItemIs( "Te") )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Quote" )
+  def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_MatchIsReturned_test(
+    self, *args ):
+    completions = [ BuildCompletion( "Test" ) ]
 
+    result = self.ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
-@patch( 'ycm.vimsupport.GetVariableValue',
-        GetVariableValue_CompleteItemIs( "Test" ) )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = '   Quote' )
-def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ExactMatchIsntReturned_test(
-    *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "Test" ) ]
+    eq_( result, True )
 
-  result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
-  eq_( result, False )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
+  @patch( 'ycm.vimsupport.GetVariableValue',
+          GetVariableValue_CompleteItemIs( "X") )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = "   Quote" )
+  def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ShortTextDoesntRaise_test(
+    self, *args ):
+    completions = [ BuildCompletion( "AAA" ) ]
 
+    self.ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
-@patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
-@patch( 'ycm.vimsupport.GetVariableValue',
-        GetVariableValue_CompleteItemIs( "   Quote" ) )
-@patch( 'ycm.vimsupport.TextBeforeCursor', return_value = '   Quote' )
-def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_NonMatchIsntReturned_test( *args ):
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
-  completions = [ _BuildCompletion( "A" ) ]
 
-  result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
+  @patch( 'ycm.vimsupport.GetVariableValue',
+          GetVariableValue_CompleteItemIs( "Test" ) )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = '   Quote' )
+  def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ExactMatchIsntReturned_test(
+    self, *args ):
+    completions = [ BuildCompletion( "Test" ) ]
 
-  eq_( result, False )
+    result = self.ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
+    eq_( result, False )
 
-def GetRequiredNamespaceImport_ReturnNoneForNoExtraData_test():
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
 
-  eq_( None, ycm_state._GetRequiredNamespaceImport( {} ) )
+  @patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True )
+  @patch( 'ycm.vimsupport.GetVariableValue',
+          GetVariableValue_CompleteItemIs( "   Quote" ) )
+  @patch( 'ycm.vimsupport.TextBeforeCursor', return_value = '   Quote' )
+  def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_NonMatchIsntReturned_test(
+    self, *args ):
+    completions = [ BuildCompletion( "A" ) ]
 
+    result = self.ycm._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
 
-def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test():
-  namespace = "A_NAMESPACE"
-  ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
+    eq_( result, False )
 
-  eq_( namespace, ycm_state._GetRequiredNamespaceImport(
-    _BuildCompletion( namespace )
-  ))
 
+  def GetRequiredNamespaceImport_ReturnNoneForNoExtraData_test( self ):
+    eq_( None, self.ycm._GetRequiredNamespaceImport( {} ) )
 
-def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfNotDone_test():
-  with _SetupForCsharpCompletionDone( [] ) as ycm_state:
-    ycm_state._latest_completion_request.Done = MagicMock( return_value = False )
 
-    eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
+  def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test( self ):
+    namespace = "A_NAMESPACE"
 
+    eq_( namespace, self.ycm._GetRequiredNamespaceImport(
+      BuildCompletion( namespace )
+    ) )
 
-def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_NewVim_test():
-  completions = [ _BuildCompletion( None ) ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
-      with patch( 'ycm.vimsupport.GetVariableValue',
-                  GetVariableValue_CompleteItemIs( 'Te' ) ):
-        eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
 
+  def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfNotDone_test( self ):
+    with self._SetupForCsharpCompletionDone( [] ):
+      self.ycm._latest_completion_request.Done = MagicMock(
+        return_value = False )
 
-def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_OldVim_test(
-    *args ):
-  completions = [ _BuildCompletion( None ) ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
-      with patch( 'ycm.vimsupport.GetVariableValue',
-                  GetVariableValue_CompleteItemIs( 'Te' ) ):
-        eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
+      eq_( [], self.ycm.GetCompletionsUserMayHaveCompleted() )
 
 
-def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatches_NewVim_test(
-    *args ):
-  info = [ "NS","Test", "Abbr", "Menu", "Info", "Kind" ]
-  completions = [ _BuildCompletion( *info ) ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
-      with patch( 'ycm.vimsupport.GetVariableValue',
-                  GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
-        eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
+  def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_NewVim_test(
+    self ):
+    completions = [ BuildCompletion( None ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
+        with patch( 'ycm.vimsupport.GetVariableValue',
+                    GetVariableValue_CompleteItemIs( 'Te' ) ):
+          eq_( [], self.ycm.GetCompletionsUserMayHaveCompleted() )
 
 
-def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatchesEvenIfPartial_NewVim_test( *args ):
-  info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
-  completions = [ _BuildCompletion( *info ),
-                  _BuildCompletion( insertion_text = "TestTest" ) ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
-      with patch( 'ycm.vimsupport.GetVariableValue',
-                  GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
-        eq_( [ completions[ 0 ] ],
-             ycm_state.GetCompletionsUserMayHaveCompleted() )
+  def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_OldVim_test(
+    self, *args ):
+    completions = [ BuildCompletion( None ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
+        with patch( 'ycm.vimsupport.GetVariableValue',
+                    GetVariableValue_CompleteItemIs( 'Te' ) ):
+          eq_( [], self.ycm.GetCompletionsUserMayHaveCompleted() )
 
 
-def GetCompletionsUserMayHaveCompleted_DontReturnMatchIfNontExactMatchesAndPartial_NewVim_test():
-  info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
-  completions = [ _BuildCompletion( insertion_text = info[ 0 ] ),
-                  _BuildCompletion( insertion_text = "TestTest" ) ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
-      with patch( 'ycm.vimsupport.GetVariableValue',
-                  GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
-        eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
+  def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatches_NewVim_test(
+    self, *args ):
+    info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
+    completions = [ BuildCompletion( *info ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
+        with patch( 'ycm.vimsupport.GetVariableValue',
+                    GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
+          eq_( completions, self.ycm.GetCompletionsUserMayHaveCompleted() )
 
 
-def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_NewVim_test( *args ):
-  completions = [ _BuildCompletion( None ) ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
-      with patch( 'ycm.vimsupport.GetVariableValue',
-                  GetVariableValue_CompleteItemIs( "Test" ) ):
-        eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
+  def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatchesEvenIfPartial_NewVim_test(
+    self, *args ):
+    info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
+    completions = [ BuildCompletion( *info ),
+                    BuildCompletion( insertion_text = "TestTest" ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
+        with patch( 'ycm.vimsupport.GetVariableValue',
+                    GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
+          eq_( [ completions[ 0 ] ],
+               self.ycm.GetCompletionsUserMayHaveCompleted() )
 
 
-def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_OldVim_test( *args ):
-  completions = [ _BuildCompletion( None ) ]
-  ycm_state = _SetupForCsharpCompletionDone( completions )
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
-      eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
+  def GetCompletionsUserMayHaveCompleted_DontReturnMatchIfNontExactMatchesAndPartial_NewVim_test(
+    self ):
+    info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
+    completions = [ BuildCompletion( insertion_text = info[ 0 ] ),
+                    BuildCompletion( insertion_text = "TestTest" ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
+        with patch( 'ycm.vimsupport.GetVariableValue',
+                    GetVariableValue_CompleteItemIs( *info[ 1: ] ) ):
+          eq_( [], self.ycm.GetCompletionsUserMayHaveCompleted() )
+
 
+  def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_NewVim_test(
+    self, *args ):
+    completions = [ BuildCompletion( None ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = True ):
+        with patch( 'ycm.vimsupport.GetVariableValue',
+                    GetVariableValue_CompleteItemIs( "Test" ) ):
+          eq_( completions, self.ycm.GetCompletionsUserMayHaveCompleted() )
 
-def PostCompleteCsharp_EmptyDoesntInsertNamespace_test( *args ):
-  with _SetupForCsharpCompletionDone( [] ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
-      ycm_state._OnCompleteDone_Csharp()
 
-    assert not vimsupport.InsertNamespace.called
+  def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_OldVim_test(
+    self, *args ):
+    completions = [ BuildCompletion( None ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
+        eq_( completions, self.ycm.GetCompletionsUserMayHaveCompleted() )
 
 
-def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test( *args
-    ):
-  completions = [ _BuildCompletion( None ) ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
-      ycm_state._OnCompleteDone_Csharp()
-
-    assert not vimsupport.InsertNamespace.called
-
-
-def PostCompleteCsharp_ValueDoesInsertNamespace_test( *args ):
-  namespace = "A_NAMESPACE"
-  completions = [ _BuildCompletion( namespace ) ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
-      ycm_state._OnCompleteDone_Csharp()
-
-      vimsupport.InsertNamespace.assert_called_once_with( namespace )
-
-def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test( *args ):
-  namespace = "A_NAMESPACE"
-  namespace2 = "ANOTHER_NAMESPACE"
-  completions = [
-    _BuildCompletion( namespace ),
-    _BuildCompletion( namespace2 ),
-  ]
-  with _SetupForCsharpCompletionDone( completions ) as ycm_state:
-    with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
-      with patch( 'ycm.vimsupport.PresentDialog', return_value = 1 ):
-        ycm_state._OnCompleteDone_Csharp()
-
-        vimsupport.InsertNamespace.assert_called_once_with( namespace2 )
+  def PostCompleteCsharp_EmptyDoesntInsertNamespace_test( self, *args ):
+    with self._SetupForCsharpCompletionDone( [] ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
+        self.ycm._OnCompleteDone_Csharp()
+
+      ok_( not vimsupport.InsertNamespace.called )
+
+
+  def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test( 
+    self, *args ):
+    completions = [ BuildCompletion( None ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
+        self.ycm._OnCompleteDone_Csharp()
+
+      ok_( not vimsupport.InsertNamespace.called )
+
+
+  def PostCompleteCsharp_ValueDoesInsertNamespace_test( self, *args ):
+    namespace = "A_NAMESPACE"
+    completions = [ BuildCompletion( namespace ) ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
+        self.ycm._OnCompleteDone_Csharp()
+
+        vimsupport.InsertNamespace.assert_called_once_with( namespace )
+
+  def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test( self, *args ):
+    namespace = "A_NAMESPACE"
+    namespace2 = "ANOTHER_NAMESPACE"
+    completions = [
+      BuildCompletion( namespace ),
+      BuildCompletion( namespace2 ),
+    ]
+    with self._SetupForCsharpCompletionDone( completions ):
+      with patch( 'ycm.vimsupport.VimVersionAtLeast', return_value = False ):
+        with patch( 'ycm.vimsupport.PresentDialog', return_value = 1 ):
+          self.ycm._OnCompleteDone_Csharp()
+
+          vimsupport.InsertNamespace.assert_called_once_with( namespace2 )

+ 8 - 0
python/ycm/tests/syntax_parse_test.py

@@ -15,6 +15,14 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.test_utils import MockVimModule
 MockVimModule()
 

+ 92 - 24
python/ycm/tests/vimsupport_test.py

@@ -1,3 +1,5 @@
+# coding: utf-8
+#
 # Copyright (C) 2015 YouCompleteMe contributors
 #
 # This file is part of YouCompleteMe.
@@ -15,13 +17,22 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.test_utils import ExtendedMock, MockVimModule, MockVimCommand
 MockVimModule()
 
 from ycm import vimsupport
 from nose.tools import eq_
-from hamcrest import assert_that, calling, raises, none
+from hamcrest import assert_that, calling, raises, none, has_entry
 from mock import MagicMock, call, patch
+from ycmd.utils import ToBytes
 import os
 import json
 
@@ -565,7 +576,7 @@ def ReplaceChunksInBuffer_UnsortedChunks_test():
   eq_( expected_buffer, result_buffer )
 
 
-class MockBuffer( ):
+class MockBuffer( object ):
   """An object that looks like a vim.buffer object, enough for ReplaceChunk to
   generate a location list"""
 
@@ -1142,7 +1153,7 @@ def CheckFilename_test():
     calling( vimsupport.CheckFilename ).with_args( 'nonexistent_file' ),
     raises( RuntimeError,
             "filename 'nonexistent_file' cannot be opened. "
-            "\[Errno 2\] No such file or directory: 'nonexistent_file'" )
+            "No such file or directory." )
   )
 
   assert_that( vimsupport.CheckFilename( __file__ ), none() )
@@ -1167,27 +1178,13 @@ def BufferIsVisibleForFilename_test():
     eq_( vimsupport.BufferIsVisibleForFilename( 'another_filename' ), False )
 
 
+@patch( 'ycm.vimsupport.GetBufferNumberForFilename',
+        side_effect = [ 2, 5, -1 ] )
 @patch( 'vim.command',
         side_effect = MockVimCommand,
-        new_callable=ExtendedMock )
-def CloseBuffersForFilename_test( vim_command ):
-  buffers = [
-    {
-      'number': 2,
-      'filename': os.path.realpath( 'some_filename' ),
-    },
-    {
-      'number': 5,
-      'filename': os.path.realpath( 'some_filename' ),
-    },
-    {
-      'number': 1,
-      'filename': os.path.realpath( 'another_filename' )
-    }
-  ]
-
-  with patch( 'vim.buffers', buffers ):
-    vimsupport.CloseBuffersForFilename( 'some_filename' )
+        new_callable = ExtendedMock )
+def CloseBuffersForFilename_test( vim_command, *args ):
+  vimsupport.CloseBuffersForFilename( 'some_filename' )
 
   vim_command.assert_has_exact_calls( [
     call( 'silent! bwipeout! 2' ),
@@ -1195,8 +1192,8 @@ def CloseBuffersForFilename_test( vim_command ):
   ], any_order = True )
 
 
-@patch( 'vim.command', new_callable=ExtendedMock )
-@patch( 'vim.current', new_callable=ExtendedMock )
+@patch( 'vim.command', new_callable = ExtendedMock )
+@patch( 'vim.current', new_callable = ExtendedMock )
 def OpenFilename_test( vim_current, vim_command ):
   # Options used to open a logfile
   options = {
@@ -1223,3 +1220,74 @@ def OpenFilename_test( vim_current, vim_command ):
   vim_current.window.options.__setitem__.assert_has_exact_calls( [
     call( 'winfixheight', True )
   ] )
+
+
+@patch( 'ycm.vimsupport.BufferModified', side_effect = [ True ] )
+@patch( 'ycm.vimsupport.FiletypesForBuffer', side_effect = [ [ 'cpp' ] ] )
+def GetUnsavedAndCurrentBufferData_EncodedUnicodeCharsInBuffers_test( *args ):
+  mock_buffer = MagicMock()
+  mock_buffer.name = os.path.realpath( 'filename' )
+  mock_buffer.number = 1
+  mock_buffer.__iter__.return_value = [ u'abc', ToBytes( u'fДa' ) ]
+
+  with patch( 'vim.buffers', [ mock_buffer ] ):
+    assert_that( vimsupport.GetUnsavedAndCurrentBufferData(),
+                 has_entry( mock_buffer.name,
+                            has_entry( u'contents', u'abc\nfДa\n' ) ) )
+
+
+# NOTE: Vim returns byte offsets for columns, not actual character columns. This
+# makes 'ДД' have 4 columns: column 0, column 2 and column 4.
+@patch( 'vim.current.line', ToBytes( 'ДДaa' ) )
+@patch( 'ycm.vimsupport.CurrentColumn', side_effect = [ 4 ] )
+def TextBeforeCursor_EncodedUnicode_test( *args ):
+  eq_( vimsupport.TextBeforeCursor(), u'ДД' )
+
+
+# NOTE: Vim returns byte offsets for columns, not actual character columns. This
+# makes 'ДД' have 4 columns: column 0, column 2 and column 4.
+@patch( 'vim.current.line', ToBytes( 'aaДД' ) )
+@patch( 'ycm.vimsupport.CurrentColumn', side_effect = [ 2 ] )
+def TextAfterCursor_EncodedUnicode_test( *args ):
+  eq_( vimsupport.TextAfterCursor(), u'ДД' )
+
+
+@patch( 'vim.current.line', ToBytes( 'fДa' ) )
+def CurrentLineContents_EncodedUnicode_test( *args ):
+  eq_( vimsupport.CurrentLineContents(), u'fДa' )
+
+
+@patch( 'vim.eval', side_effect = lambda x: x )
+def VimExpressionToPythonType_IntAsUnicode_test( *args ):
+  eq_( vimsupport.VimExpressionToPythonType( '123' ), 123 )
+
+
+@patch( 'vim.eval', side_effect = lambda x: x )
+def VimExpressionToPythonType_IntAsBytes_test( *args ):
+  eq_( vimsupport.VimExpressionToPythonType( ToBytes( '123' ) ), 123 )
+
+
+@patch( 'vim.eval', side_effect = lambda x: x )
+def VimExpressionToPythonType_StringAsUnicode_test( *args ):
+  eq_( vimsupport.VimExpressionToPythonType( 'foo' ), 'foo' )
+
+
+@patch( 'vim.eval', side_effect = lambda x: x )
+def VimExpressionToPythonType_StringAsBytes_test( *args ):
+  eq_( vimsupport.VimExpressionToPythonType( ToBytes( 'foo' ) ), 'foo' )
+
+
+@patch( 'vim.eval', side_effect = lambda x: x )
+def VimExpressionToPythonType_ListPassthrough_test( *args ):
+  eq_( vimsupport.VimExpressionToPythonType( [ 1, 2 ] ), [ 1, 2 ] )
+
+
+@patch( 'vim.eval', side_effect = lambda x: x )
+def VimExpressionToPythonType_ObjectPassthrough_test( *args ):
+  eq_( vimsupport.VimExpressionToPythonType( { 1: 2 } ), { 1: 2 } )
+
+
+@patch( 'vim.eval', side_effect = lambda x: x )
+def VimExpressionToPythonType_GeneratorPassthrough_test( *args ):
+  gen = ( x**2 for x in [ 1, 2, 3 ] )
+  eq_( vimsupport.VimExpressionToPythonType( gen ), gen )

+ 10 - 22
python/ycm/tests/youcompleteme_test.py

@@ -15,45 +15,33 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
 from ycm.test_utils import MockVimModule
 MockVimModule()
 
 import sys
-
-from mock import patch
+from mock import MagicMock
 from hamcrest import assert_that, is_in, is_not
 
-from ycm import base
 from ycm.youcompleteme import YouCompleteMe
-from ycmd import user_options_store
 
 
 class YouCompleteMe_test():
 
-  # Minimal options to create the YouCompleteMe object.
-  DEFAULT_OPTIONS = {
-    'ycm_server_log_level': 'info',
-    'ycm_server_keep_logfiles': 0,
-    'ycm_min_num_of_chars_for_completion': 2,
-    'ycm_auto_trigger': 1,
-    'ycm_semantic_triggers': {}
-  }
-
-
   def setUp( self ):
-    with patch( 'vim.eval', side_effect = self.VimEval ):
-      user_options_store.SetAll( base.BuildServerConf() )
-      self.ycm = YouCompleteMe( user_options_store.GetAll() )
+    self.ycm = YouCompleteMe( MagicMock( spec_set = dict ) )
 
 
   def tearDown( self ):
     self.ycm.OnVimLeave()
 
 
-  def VimEval( self, value ):
-    if value == 'g:':
-      return self.DEFAULT_OPTIONS
-
-
   def YcmCoreNotImported_test( self ):
     assert_that( 'ycm_core', is_not( is_in( sys.modules ) ) )

+ 34 - 17
python/ycm/vimsupport.py

@@ -15,13 +15,22 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
+from future.utils import iterkeys
 import vim
 import os
 import tempfile
 import json
 import re
 from collections import defaultdict
-from ycmd.utils import ToBytes, ToUnicode
+from ycmd.utils import ToUnicode
 from ycmd import user_options_store
 
 BUFFER_COMMAND_MAP = { 'same-buffer'      : 'edit',
@@ -59,17 +68,17 @@ def CurrentColumn():
 
 
 def CurrentLineContents():
-  return vim.current.line
+  return ToUnicode( vim.current.line )
 
 
 def TextAfterCursor():
   """Returns the text after CurrentColumn."""
-  return vim.current.line[ CurrentColumn(): ]
+  return ToUnicode( vim.current.line[ CurrentColumn(): ] )
 
 
 def TextBeforeCursor():
   """Returns the text before CurrentColumn."""
-  return vim.current.line[ :CurrentColumn() ]
+  return ToUnicode( vim.current.line[ :CurrentColumn() ] )
 
 
 # Expects version_string in 'MAJOR.MINOR.PATCH' format, e.g. '7.4.301'
@@ -114,7 +123,7 @@ def GetUnsavedAndCurrentBufferData():
 
     buffers_data[ GetBufferFilepath( buffer_object ) ] = {
       # Add a newline to match what gets saved to disk. See #1455 for details.
-      'contents': '\n'.join( buffer_object ) + '\n',
+      'contents': '\n'.join( ToUnicode( x ) for x in buffer_object ) + '\n',
       'filetypes': FiletypesForBuffer( buffer_object )
     }
 
@@ -277,7 +286,7 @@ def ConvertDiagnosticsToQfList( diagnostics ):
       'bufnr' : GetBufferNumberForFilename( location[ 'filepath' ] ),
       'lnum'  : line_num,
       'col'   : location[ 'column_num' ],
-      'text'  : ToBytes( text ),
+      'text'  : text,
       'type'  : diagnostic[ 'kind' ][ 0 ],
       'valid' : 1
     }
@@ -309,13 +318,20 @@ def GetReadOnlyVimGlobals( force_python_objects = False ):
 
 
 def VimExpressionToPythonType( vim_expression ):
+  """Returns a Python type from the return value of the supplied Vim expression.
+  If the expression returns a list, dict or other non-string type, then it is
+  returned unmodified. If the string return can be converted to an
+  integer, returns an integer, otherwise returns the result converted to a
+  Unicode string."""
+
   result = vim.eval( vim_expression )
-  if not isinstance( result, basestring ):
+  if not ( isinstance( result, str ) or isinstance( result, bytes ) ):
     return result
+
   try:
     return int( result )
   except ValueError:
-    return result
+    return ToUnicode( result )
 
 
 def HiddenEnabled( buffer_object ):
@@ -480,7 +496,7 @@ def EchoTextVimWidth( text ):
 
 
 def EscapeForVim( text ):
-  return text.replace( "'", "''" )
+  return ToUnicode( text.replace( "'", "''" ) )
 
 
 def CurrentFiletypes():
@@ -607,7 +623,7 @@ def ReplaceChunks( chunks ):
   chunks_by_file = _SortChunksByFile( chunks )
 
   # We sort the file list simply to enable repeatable testing
-  sorted_file_list = sorted( chunks_by_file.iterkeys() )
+  sorted_file_list = sorted( iterkeys( chunks_by_file ) )
 
   # Make sure the user is prepared to have her screen mutilated by the new
   # buffers
@@ -840,7 +856,8 @@ def CheckFilename( filename ):
     raise RuntimeError( "'{0}' is not a valid filename".format( filename ) )
   except IOError as error:
     raise RuntimeError(
-      "filename '{0}' cannot be opened. {1}".format( filename, error ) )
+      "filename '{0}' cannot be opened. {1}.".format( filename,
+                                                      error.strerror ) )
 
 
 def BufferIsVisibleForFilename( filename ):
@@ -852,7 +869,7 @@ def BufferIsVisibleForFilename( filename ):
 def CloseBuffersForFilename( filename ):
   """Close all buffers for a specific file."""
   buffer_number = GetBufferNumberForFilename( filename, False )
-  while buffer_number is not -1:
+  while buffer_number != -1:
     vim.command( 'silent! bwipeout! {0}'.format( buffer_number ) )
     new_buffer_number = GetBufferNumberForFilename( filename, False )
     if buffer_number == new_buffer_number:
@@ -885,7 +902,7 @@ def OpenFilename( filename, options = {} ):
 
   # There is no command in Vim to return to the previous tab so we need to
   # remember the current tab if needed.
-  if not focus and command is 'tabedit':
+  if not focus and command == 'tabedit':
     previous_tab = GetIntValue( 'tabpagenr()' )
   else:
     previous_tab = None
@@ -920,7 +937,7 @@ def OpenFilename( filename, options = {} ):
   # focus back (if the focus option is disabled) when opening a new tab or
   # window.
   if not focus:
-    if command is 'tabedit':
+    if command == 'tabedit':
       JumpToTab( previous_tab )
     if command in [ 'split', 'vsplit' ]:
       JumpToPreviousWindow()
@@ -930,9 +947,9 @@ def _SetUpLoadedBuffer( command, filename, fix, position, watch ):
   """After opening a buffer, configure it according to the supplied options,
   which are as defined by the OpenFilename method."""
 
-  if command is 'split':
+  if command == 'split':
     vim.current.window.options[ 'winfixheight' ] = fix
-  if command is 'vsplit':
+  if command == 'vsplit':
     vim.current.window.options[ 'winfixwidth' ] = fix
 
   if watch:
@@ -940,6 +957,6 @@ def _SetUpLoadedBuffer( command, filename, fix, position, watch ):
     vim.command( "exec 'au BufEnter <buffer> :silent! checktime {0}'"
                  .format( filename ) )
 
-  if position is 'end':
+  if position == 'end':
     vim.command( 'silent! normal G zz' )
 

+ 15 - 5
python/ycm/youcompleteme.py

@@ -15,14 +15,23 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import *  # noqa
+
+from future.utils import iteritems
 import os
 import vim
-import tempfile
 import json
 import re
 import signal
 import base64
 from subprocess import PIPE
+from tempfile import NamedTemporaryFile
 from ycm import paths, vimsupport
 from ycmd import utils
 from ycmd.request_wrap import RequestWrap
@@ -95,17 +104,18 @@ class YouCompleteMe( object ):
     self._SetupServer()
     self._ycmd_keepalive.Start()
     self._complete_done_hooks = {
-      'cs': lambda( self ): self._OnCompleteDone_Csharp()
+      'cs': lambda self: self._OnCompleteDone_Csharp()
     }
 
   def _SetupServer( self ):
     self._available_completers = {}
     server_port = utils.GetUnusedLocalhostPort()
     # The temp options file is deleted by ycmd during startup
-    with tempfile.NamedTemporaryFile( delete = False ) as options_file:
+    with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file:
       hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
       options_dict = dict( self._user_options )
-      options_dict[ 'hmac_secret' ] = base64.b64encode( hmac_secret )
+      options_dict[ 'hmac_secret' ] = utils.ToUnicode(
+        base64.b64encode( hmac_secret ) )
       json.dump( options_dict, options_file )
       options_file.flush()
 
@@ -303,7 +313,7 @@ class YouCompleteMe( object ):
 
   def GetCompleteDoneHooks( self ):
     filetypes = vimsupport.CurrentFiletypes()
-    for key, value in self._complete_done_hooks.iteritems():
+    for key, value in iteritems( self._complete_done_hooks ):
       if key in filetypes:
         yield value
 

+ 1 - 2
third_party/retries/retries.py

@@ -61,8 +61,7 @@ def retries(max_tries, delay=1, backoff=2, exceptions=(Exception,), hook=None):
     def dec(func):
         def f2(*args, **kwargs):
             mydelay = delay
-            tries = range(max_tries)
-            tries.reverse()
+            tries = reversed(range(max_tries))
             for tries_remaining in tries:
                 try:
                    return func(*args, **kwargs)

+ 1 - 1
third_party/ycmd

@@ -1 +1 @@
-Subproject commit 7ea44df61e524338db66521fd2d5a917a7c472fb
+Subproject commit 206efaf2f517133af1bd6603c9501d6aa08711ea

+ 0 - 3
travis/travis_install.linux.sh

@@ -1,3 +0,0 @@
-# Linux installation
-
-virtualenv -p python${YCMD_PYTHON_VERSION} ${YCMD_VENV_DIR}

+ 0 - 19
travis/travis_install.osx.sh

@@ -1,19 +0,0 @@
-# OS X installation
-
-# OS X comes with 2 versions of python by default, and a neat system
-# (versioner) to switch between them:
-#   /usr/bin/python2.7 - python 2.7
-#   /usr/bin/python2.6 - python 2.6
-#
-# We just set the system default to match it
-# http://stackoverflow.com/q/6998545
-defaults write com.apple.versioner.python Version ${YCMD_PYTHON_VERSION}
-
-# virtualenv is not installed by default on OS X under python2.6, and we don't
-# have sudo, so we install it manually. There is no "latest" link, so we have
-# to install a specific version.
-VENV_VERSION=13.1.2
-
-curl -O https://pypi.python.org/packages/source/v/virtualenv/virtualenv-${VENV_VERSION}.tar.gz
-tar xvfz virtualenv-${VENV_VERSION}.tar.gz
-python virtualenv-${VENV_VERSION}/virtualenv.py -p python${YCMD_PYTHON_VERSION} ${YCMD_VENV_DIR}

+ 0 - 36
travis/travis_install.sh

@@ -1,36 +0,0 @@
-#!/bin/bash
-
-set -ev
-
-YCMD_VENV_DIR=${HOME}/venvs/ycmd_test
-
-# Requirements of OS-specific install:
-#  - install any software which is not installed by Travis configuration
-#  - create (but don't activate) a virtualenv for the python version
-#    ${YCMD_PYTHON_VERSION} in the directory ${YCMD_VENV_DIR}, e.g.
-#    virtualenv -p python${YCMD_PYTHON_VERSION} ${YCMD_VENV_DIR}
-source travis/travis_install.${TRAVIS_OS_NAME}.sh
-
-# virtualenv doesn't copy python-config https://github.com/pypa/virtualenv/issues/169
-# but our build system uses it
-cp /usr/bin/python${YCMD_PYTHON_VERSION}-config ${YCMD_VENV_DIR}/bin/python-config
-
-# virtualenv script is noisy, so don't print every command
-set +v
-source ${YCMD_VENV_DIR}/bin/activate
-set -v
-
-# It is quite easy to get the above series of steps wrong. Verify that the
-# version of python actually in the path and used is the version that was
-# requested, and fail the build if we broke the travis setup
-python_version=$(python -c 'import sys; print "{0}.{1}".format( sys.version_info[0], sys.version_info[1] )')
-echo "Checking python version (actual ${python_version} vs expected ${YCMD_PYTHON_VERSION})"
-test ${python_version} == ${YCMD_PYTHON_VERSION}
-
-pip install -U pip wheel setuptools
-pip install -r python/test_requirements.txt
-
-# The build infrastructure prints a lot of spam after this script runs, so make
-# sure to disable printing, and failing on non-zero exit code after this script
-# finishes
-set +ev