From 7602601e61d3e9dcb23fe405475653b878cc9318 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 7 Sep 2021 07:53:06 +0200 Subject: [PATCH] Port to unittest Things wrong with pytest: 1. It's trying to do too much and ends up getting in the way. 1.1. Most notably, rerunning tests is pretty broken - see pytest-dev/rerun-failures#51 2. Everything needs to be a fixture, instead of being compatible with argument-altering decorators. 3. Yet, parametrizing fixtures is an annoying chore. 4. Weird interaction with @patch and its arguments - they are in reversed order, which messes up quite a few things. As a result, we're moving to the standard unittest, which looks similar to nose. Here are the relevant differences: - Writing tests: - Each test needs to be a member function, named `test_whatever()`. - Each test class needs to - Inherit from `unittest.TestCase` - Be called `ThingTest` - Instead of `yield`ing generated tests a `self.subTest()` is called in a loop: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests - Running tests: - There are far fewer command line options and the biggest problem was `--ignore`. - We have used `--ignore` to omit libclang tests from that one CI run. Now we add a `load_tests()` to `ycmd/tests/clang/__init__.py`. Reference https://docs.python.org/3/library/unittest.html#load-tests-protocol --- .github/workflows/ci.yml | 6 +- .gitignore | 2 +- TESTS.md | 14 +- pytest.ini | 9 - run_tests.py | 90 +- test_requirements.txt | 4 - valgrind.suppressions | 16 + ycmd/tests/__init__.py | 32 +- .../bindings/cpp_bindings_general_test.py | 893 ++-- .../cpp_bindings_raises_exception_test.py | 205 +- .../bindings/cpp_bindings_vectors_test.py | 627 ++- ycmd/tests/clang/__init__.py | 49 +- ycmd/tests/clang/comment_strip_test.py | 132 +- ycmd/tests/clang/conftest.py | 86 - ycmd/tests/clang/debug_info_test.py | 494 +- ycmd/tests/clang/diagnostics_test.py | 712 ++- ycmd/tests/clang/flags_test.py | 3407 +++++++------- ycmd/tests/clang/get_completions_test.py | 2706 +++++------ ycmd/tests/clang/include_cache_test.py | 223 +- ycmd/tests/clang/signature_help_test.py | 83 +- ycmd/tests/clang/subcommands_test.py | 1993 ++++---- ycmd/tests/clangd/__init__.py | 52 +- ycmd/tests/clangd/conftest.py | 97 - ycmd/tests/clangd/debug_info_test.py | 767 ++- ycmd/tests/clangd/diagnostics_test.py | 964 ++-- ycmd/tests/clangd/get_completions_test.py | 1572 +++---- ycmd/tests/clangd/server_management_test.py | 204 +- ycmd/tests/clangd/signature_help_test.py | 1039 ++-- ycmd/tests/clangd/subcommands_test.py | 1393 +++--- ycmd/tests/clangd/utilities_test.py | 366 +- ycmd/tests/client_test.py | 14 +- ycmd/tests/completer_test.py | 79 +- ycmd/tests/completer_utils_test.py | 339 +- ycmd/tests/conftest.py | 84 - ycmd/tests/cs/__init__.py | 130 +- ycmd/tests/cs/conftest.py | 181 - ycmd/tests/cs/debug_info_test.py | 320 +- ycmd/tests/cs/diagnostics_test.py | 243 +- ycmd/tests/cs/get_completions_test.py | 270 +- ycmd/tests/cs/signature_help_test.py | 375 +- ycmd/tests/cs/subcommands_test.py | 1500 +++--- ycmd/tests/diagnostics_test.py | 51 +- ycmd/tests/extra_conf_store_test.py | 393 +- ycmd/tests/filename_completer_test.py | 587 +-- ycmd/tests/get_completions_test.py | 1338 +++--- ycmd/tests/go/__init__.py | 65 +- ycmd/tests/go/conftest.py | 108 - ycmd/tests/go/debug_info_test.py | 157 +- ycmd/tests/go/diagnostics_test.py | 141 +- ycmd/tests/go/get_completions_test.py | 95 +- ycmd/tests/go/go_completer_test.py | 33 +- ycmd/tests/go/server_management_test.py | 161 +- ycmd/tests/go/signature_help_test.py | 195 +- ycmd/tests/go/subcommands_test.py | 660 +-- ycmd/tests/hmac_utils_test.py | 83 +- ycmd/tests/identifier_completer_test.py | 504 +- ycmd/tests/identifier_utils_test.py | 813 ++-- ycmd/tests/java/__init__.py | 94 +- ycmd/tests/java/conftest.py | 146 - ycmd/tests/java/debug_info_test.py | 398 +- ycmd/tests/java/diagnostics_test.py | 889 ++-- ycmd/tests/java/get_completions_test.py | 1324 +++--- ycmd/tests/java/java_completer_test.py | 410 +- ycmd/tests/java/server_management_test.py | 902 ++-- ycmd/tests/java/signature_help_test.py | 215 +- ycmd/tests/java/subcommands_test.py | 4165 +++++++++-------- ycmd/tests/javascript/__init__.py | 57 +- ycmd/tests/javascript/conftest.py | 98 - ycmd/tests/javascript/debug_info_test.py | 73 +- ycmd/tests/javascript/diagnostics_test.py | 143 +- ycmd/tests/javascript/get_completions_test.py | 270 +- ycmd/tests/javascript/subcommands_test.py | 1337 +++--- ycmd/tests/javascriptreact/__init__.py | 61 +- ycmd/tests/javascriptreact/conftest.py | 92 - .../javascriptreact/get_completions_test.py | 60 +- .../tests/javascriptreact/subcommands_test.py | 62 +- ycmd/tests/language_server/__init__.py | 16 +- ycmd/tests/language_server/conftest.py | 56 - .../language_server/generic_completer_test.py | 1053 ++--- .../language_server_completer_test.py | 2456 +++++----- .../language_server_connection_test.py | 229 +- .../language_server_protocol_test.py | 372 +- ycmd/tests/misc_handlers_test.py | 442 +- ycmd/tests/python/__init__.py | 35 +- ycmd/tests/python/conftest.py | 88 - ycmd/tests/python/debug_info_test.py | 81 +- ycmd/tests/python/get_completions_test.py | 638 +-- ycmd/tests/python/signature_help_test.py | 431 +- ycmd/tests/python/subcommands_test.py | 1118 ++--- ycmd/tests/python_support_test.py | 85 +- ycmd/tests/request_validation_test.py | 93 +- ycmd/tests/request_wrap_test.py | 617 +-- ycmd/tests/rust/__init__.py | 66 +- ycmd/tests/rust/conftest.py | 108 - ycmd/tests/rust/debug_info_test.py | 195 +- ycmd/tests/rust/diagnostics_test.py | 195 +- .../rust/get_completions_proc_macro_test.py | 89 +- ycmd/tests/rust/get_completions_test.py | 101 +- ycmd/tests/rust/rust_completer_test.py | 64 +- ycmd/tests/rust/server_management_test.py | 163 +- ycmd/tests/rust/signature_help_test.py | 151 +- ycmd/tests/rust/subcommands_test.py | 776 +-- ycmd/tests/rust/testdata/common/Cargo.lock | 7 + ycmd/tests/shutdown_test.py | 43 +- ycmd/tests/signature_help_test.py | 61 +- ycmd/tests/subcommands_test.py | 56 +- ycmd/tests/tern/__init__.py | 63 +- ycmd/tests/tern/conftest.py | 105 - ycmd/tests/tern/debug_info_test.py | 71 +- ycmd/tests/tern/event_notification_test.py | 462 +- ycmd/tests/tern/get_completions_test.py | 822 ++-- ycmd/tests/tern/subcommands_test.py | 886 ++-- ycmd/tests/test_utils.py | 53 +- ycmd/tests/typescript/__init__.py | 48 +- ycmd/tests/typescript/conftest.py | 92 - ycmd/tests/typescript/debug_info_test.py | 51 +- ycmd/tests/typescript/diagnostics_test.py | 255 +- .../typescript/event_notification_test.py | 149 +- ycmd/tests/typescript/get_completions_test.py | 518 +- ycmd/tests/typescript/signature_help_test.py | 431 +- ycmd/tests/typescript/subcommands_test.py | 1863 ++++---- .../typescript/typescript_completer_test.py | 34 +- ycmd/tests/utils_test.py | 899 ++-- 123 files changed, 27006 insertions(+), 27633 deletions(-) delete mode 100644 pytest.ini delete mode 100644 ycmd/tests/clang/conftest.py delete mode 100644 ycmd/tests/clangd/conftest.py delete mode 100644 ycmd/tests/conftest.py delete mode 100644 ycmd/tests/cs/conftest.py delete mode 100644 ycmd/tests/go/conftest.py delete mode 100644 ycmd/tests/java/conftest.py delete mode 100644 ycmd/tests/javascript/conftest.py delete mode 100644 ycmd/tests/javascriptreact/conftest.py delete mode 100644 ycmd/tests/language_server/conftest.py delete mode 100644 ycmd/tests/python/conftest.py delete mode 100644 ycmd/tests/rust/conftest.py create mode 100644 ycmd/tests/rust/testdata/common/Cargo.lock delete mode 100644 ycmd/tests/tern/conftest.py delete mode 100644 ycmd/tests/typescript/conftest.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ba5ee9949..61c9cd2e1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: echo -e "import coverage\ncoverage.process_startup()" > $(python -c "print(__import__('sysconfig').get_path('purelib'))")/sitecustomize.py - name: Run tests if: matrix.benchmark == false - run: python3 run_tests.py --no-parallel --quiet + run: python3 run_tests.py --quiet - name: Run benchmarks if: matrix.benchmark == true run: python3 benchmark.py --quiet @@ -118,7 +118,7 @@ jobs: - name: Lint run: | YCM_TESTRUN=1 python3 build.py --clang-completer --clang-tidy --valgrind - python3 run_tests.py --valgrind --skip-build --no-flake8 --no-parallel --quiet + python3 run_tests.py --valgrind --skip-build --no-flake8 --quiet windows: strategy: @@ -174,7 +174,7 @@ jobs: run: python3 benchmark.py --msvc ${{ matrix.msvc }} --quiet - name: Run tests if: matrix.benchmark == false - run: python3 run_tests.py --msvc ${{ matrix.msvc }} --no-parallel --quiet + run: python3 run_tests.py --msvc ${{ matrix.msvc }} --quiet - name: Upload coverage data if: matrix.benchmark == false run: codecov --name ${{ matrix.runs-on }}-${{ matrix.python-arch }} >null diff --git a/.gitignore b/.gitignore index 0342b3b8c7..0570a4f8bf 100644 --- a/.gitignore +++ b/.gitignore @@ -89,7 +89,7 @@ third_party/clangd # Rust third_party/rust-analyzer ycmd/tests/rust/testdata/common/target -ycmd/tests/rust/testdata/formatting/target +ycmd/tests/rust/testdata/macro/target # generic LSP third_party/generic_server/node_modules diff --git a/TESTS.md b/TESTS.md index a4217894c4..116c918412 100644 --- a/TESTS.md +++ b/TESTS.md @@ -49,16 +49,16 @@ To run the full suite, just run `run_tests.py`. Options are: Windows only; * `--coverage`: generate code coverage data. -Remaining arguments are passed to "pytest" directly. This means that you +Remaining arguments are passed to "unittest" directly. This means that you can run a specific script or a specific test as follows: * Specific script: `./run_tests.py ycmd/tests/.py` -* Specific test: `./run_tests.py ycmd/tests/.py:` +* Specific test: `./run_tests.py -k ycmd/tests/.py` For example: * `./run_tests.py ycmd/tests/subcommands_test.py` -* `./run_tests.py ycmd/tests/subcommands_test.py:Subcommands_Basic_test` +* `./run_tests.py -k Subcommands_Basic_test ycmd/tests/subcommands_test.py` NOTE: you must have UTF-8 support in your terminal when you do this, e.g.: @@ -76,9 +76,8 @@ C++ coverage testing is available only on Linux/Mac and uses gcov. Stricly speaking, we use the `-coverage` option to your compiler, which in the case of GNU and LLVM compilers, generate gcov-compatible data. -For Python, there's a coverage module which works nicely with `pytest`. This -is very useful for highlighting areas of your code which are not covered by the -automated integration tests. +For Python, there's a coverage module. This is very useful for highlighting +areas of your code which are not covered by the automated integration tests. Run it like this: @@ -88,8 +87,7 @@ Run it like this: This will print a summary and generate HTML output in `./cover`. -More information: https://coverage.readthedocs.org and -https://pytest-cov.readthedocs.io/en/stable/ +More information: https://coverage.readthedocs.org ## Troubleshooting diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 9718108fd3..0000000000 --- a/pytest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[pytest] -python_functions = *_test -python_classes = *_test -xfail_strict = true -log_level = debug -log_date_format = %Y-%m-%d %H:%M:%S -log_format = %(asctime)s - %(levelname)s - %(message)s -log_cli_level = debug -markers = valgrind_skip diff --git a/run_tests.py b/run_tests.py index 9dd847f238..b7979e18fa 100755 --- a/run_tests.py +++ b/run_tests.py @@ -9,8 +9,7 @@ import sys import urllib.request -BASE_PYTEST_ARGS = [ '-v', '--color=yes' ] - +BASE_UNITTEST_ARGS = [ '-b' ] DIR_OF_THIS_SCRIPT = p.dirname( p.abspath( __file__ ) ) DIR_OF_THIRD_PARTY = p.join( DIR_OF_THIS_SCRIPT, 'third_party' ) DIR_OF_WATCHDOG_DEPS = p.join( DIR_OF_THIRD_PARTY, 'watchdog_deps' ) @@ -59,7 +58,6 @@ def RunFlake8(): # Newer completers follow a standard convention of: # - build: ---completer -# - test directory: ycmd/tests/ # - no aliases. SIMPLE_COMPLETERS = [ 'clangd', @@ -69,38 +67,30 @@ def RunFlake8(): # More complex or legacy cases can specify all of: # - build: flags to add to build.py to include this completer -# - test: flags to add to run_tests.py when _not_ testing this completer # - aliases?: list of completer aliases for the --completers option COMPLETERS = { 'cfamily': { 'build': [ '--clang-completer' ], - 'test': [ '--ignore=ycmd/tests/clang' ], 'aliases': [ 'c', 'cpp', 'c++', 'objc', 'clang', ] }, 'cs': { 'build': [ '--cs-completer' ], - 'test': [ '--ignore=ycmd/tests/cs' ], 'aliases': [ 'omnisharp', 'csharp', 'c#' ] }, 'javascript': { 'build': [ '--js-completer' ], - 'test': [ '--ignore=ycmd/tests/tern' ], 'aliases': [ 'js', 'tern' ] }, 'typescript': { 'build': [ '--ts-completer' ], - 'test': [ '--ignore=ycmd/tests/javascript', - '--ignore=ycmd/tests/typescript' ], 'aliases': [ 'ts' ] }, 'python': { 'build': [], - 'test': [ '--ignore=ycmd/tests/python' ], 'aliases': [ 'jedi', 'jedihttp', ] }, 'java': { 'build': [ '--java-completer' ], - 'test': [ '--ignore=ycmd/tests/java' ], 'aliases': [ 'jdt' ], }, } @@ -109,7 +99,6 @@ def RunFlake8(): for completer in SIMPLE_COMPLETERS: COMPLETERS[ completer ] = { 'build': [ '--{}-completer'.format( completer ) ], - 'test': [ '--ignore=ycmd/tests/{}'.format( completer ) ], } @@ -161,18 +150,15 @@ def ParseArguments(): parser.add_argument( '--valgrind', action = 'store_true', help = 'Run tests inside valgrind.' ) - parser.add_argument( '--no-parallel', action='store_true', - help='Run tests in serial, default is to parallelize ' - 'tests execution' ) - parsed_args, pytests_args = parser.parse_known_args() + parsed_args, unittest_args = parser.parse_known_args() parsed_args.completers = FixupCompleters( parsed_args ) if 'COVERAGE' in os.environ: parsed_args.coverage = ( os.environ[ 'COVERAGE' ] == 'true' ) - return parsed_args, pytests_args + return parsed_args, unittest_args def FixupCompleters( parsed_args ): @@ -228,26 +214,27 @@ def BuildYcmdLibs( args ): subprocess.check_call( build_cmd ) -def PytestValgrind( parsed_args, extra_pytests_args ): - pytests_args = BASE_PYTEST_ARGS +def UnittestValgrind( parsed_args, extra_unittest_args ): + unittest_args = BASE_UNITTEST_ARGS if parsed_args.quiet: - pytests_args[ 0 ] = '-q' + unittest_args.append( '-q' ) - if extra_pytests_args: - pytests_args.extend( extra_pytests_args ) + if extra_unittest_args: + unittest_args.extend( extra_unittest_args ) else: - pytests_args += glob.glob( + unittest_args += glob.glob( p.join( DIR_OF_THIS_SCRIPT, 'ycmd', 'tests', 'bindings', '*_test.py' ) ) - pytests_args += glob.glob( + unittest_args += glob.glob( p.join( DIR_OF_THIS_SCRIPT, 'ycmd', 'tests', 'clang', '*_test.py' ) ) - pytests_args += glob.glob( + unittest_args += glob.glob( p.join( DIR_OF_THIS_SCRIPT, 'ycmd', 'tests', '*_test.py' ) ) - # Avoids needing all completers for a valgrind run - pytests_args += [ '-m', 'not valgrind_skip' ] + # # Avoids needing all completers for a valgrind run + # unittest_args += [ '-m', 'not valgrind_skip' ] new_env = os.environ.copy() new_env[ 'PYTHONMALLOC' ] = 'malloc' new_env[ 'LD_LIBRARY_PATH' ] = LIBCLANG_DIR + new_env[ 'YCM_VALGRIND_RUN' ] = '1' cmd = [ 'valgrind', '--gen-suppressions=all', '--error-exitcode=1', @@ -257,36 +244,21 @@ def PytestValgrind( parsed_args, extra_pytests_args ): '--suppressions=' + p.join( DIR_OF_THIS_SCRIPT, 'valgrind.suppressions' ) ] subprocess.check_call( cmd + - [ sys.executable, '-m', 'pytest' ] + - pytests_args, + [ sys.executable, '-m', 'unittest' ] + + unittest_args, env = new_env ) -def PytestTests( parsed_args, extra_pytests_args ): - pytests_args = BASE_PYTEST_ARGS +def UnittestTests( parsed_args, extra_unittest_args ): + unittest_args = BASE_UNITTEST_ARGS + [ '-p', '*_test.py' ] if parsed_args.quiet: - pytests_args[ 0 ] = '-q' - - for key in COMPLETERS: - if key not in parsed_args.completers: - pytests_args.extend( COMPLETERS[ key ][ 'test' ] ) + unittest_args.append( '-q' ) - if parsed_args.coverage: - # We need to exclude the ycmd/tests/python/testdata directory since it - # contains Python files and its base name starts with "test". - pytests_args += [ '--ignore=ycmd/tests/python/testdata', '--cov=ycmd' ] - - if not parsed_args.no_parallel: - # Execute tests in parallel with n workers where n = NUMCPUS. Tests are - # grouped by module for test functions and by class for test methods.Groups - # are distributed to available workers as whole units. This guarantees that - # all tests in a group run in the same process. - pytests_args += [ '-n', 'auto', '--dist', 'loadscope' ] - - if extra_pytests_args: - pytests_args.extend( extra_pytests_args ) + if extra_unittest_args: + unittest_args.extend( extra_unittest_args ) else: - pytests_args.append( p.join( DIR_OF_THIS_SCRIPT, 'ycmd' ) ) + unittest_args.append( '-s' ) + unittest_args.append( 'ycmd.tests' ) env = os.environ.copy() @@ -301,7 +273,15 @@ def PytestTests( parsed_args, extra_pytests_args ): else: env[ 'LD_LIBRARY_PATH' ] = LIBCLANG_DIR - subprocess.check_call( [ sys.executable, '-m', 'pytest' ] + pytests_args, + if parsed_args.coverage: + executable = [ sys.executable, '-m', 'coverage', 'run' ] + else: + executable = [ sys.executable ] + + subprocess.check_call( executable + [ + '-m', + 'unittest', + 'discover' ] + unittest_args, env=env ) @@ -371,7 +351,7 @@ def SetUpJavaCompleter(): def Main(): - parsed_args, pytests_args = ParseArguments() + parsed_args, unittest_args = ParseArguments() if parsed_args.dump_path: print( os.environ[ 'PYTHONPATH' ] ) sys.exit() @@ -384,9 +364,9 @@ def Main(): RunFlake8() BuildYcmdLibs( parsed_args ) if parsed_args.valgrind: - PytestValgrind( parsed_args, pytests_args ) + UnittestValgrind( parsed_args, unittest_args ) else: - PytestTests( parsed_args, pytests_args ) + UnittestTests( parsed_args, unittest_args ) if __name__ == "__main__": diff --git a/test_requirements.txt b/test_requirements.txt index 2cbc73b85f..e1efc18571 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -6,8 +6,4 @@ WebTest >= 2.0.20 psutil >= 5.6.6 coverage >= 4.2 codecov >= 2.0.5 -pytest -pytest-cov -pytest-rerunfailures requests -pytest-xdist diff --git a/valgrind.suppressions b/valgrind.suppressions index e69de29bb2..e5c11b966f 100644 --- a/valgrind.suppressions +++ b/valgrind.suppressions @@ -0,0 +1,16 @@ +{ + Valgrind bug (https://stackoverflow.com/questions/1542457/memory-leak-reported-by-valgrind-in-dlopen) + Memcheck:Leak + fun:malloc + ... + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run + fun:dlopen@@GLIBC_2.2.5 + fun:_PyImport_FindSharedFuncptr + fun:_PyImport_LoadDynamicModuleWithSpec +} diff --git a/ycmd/tests/__init__.py b/ycmd/tests/__init__.py index 8a3f97f44e..f306e93f7d 100644 --- a/ycmd/tests/__init__.py +++ b/ycmd/tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -15,10 +15,38 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . +import functools import os -from ycmd.tests.conftest import * # noqa +from ycmd.tests.test_utils import ClearCompletionsCache, IsolatedApp, SetUpApp + +shared_app = None def PathToTestFile( *args ): dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) return os.path.join( dir_of_current_script, 'testdata', *args ) + + +def SharedYcmd( test ): + """Defines a decorator to be attached to tests of this package. This decorator + passes the shared ycmd application as a parameter. + Do NOT attach it to test generators but directly to the yielded tests.""" + global shared_app + if shared_app is None: + shared_app = SetUpApp() + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + return Wrapper + return Decorator diff --git a/ycmd/tests/bindings/cpp_bindings_general_test.py b/ycmd/tests/bindings/cpp_bindings_general_test.py index 06cbfbe456..af9432e495 100644 --- a/ycmd/tests/bindings/cpp_bindings_general_test.py +++ b/ycmd/tests/bindings/cpp_bindings_general_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -29,467 +29,464 @@ equal_to, has_entries, has_properties ) +from unittest import TestCase ycm_core = ImportCore() import os -def CppBindings_FilterAndSortCandidates_test(): - candidates = [ 'foo1', 'foo2', 'foo3' ] - query = 'oo' - candidate_property = '' - - result_full = ycm_core.FilterAndSortCandidates( candidates, - candidate_property, - query ) - result_2 = ycm_core.FilterAndSortCandidates( candidates, - candidate_property, - query, - 2 ) - - del candidates - del query - del candidate_property - - assert_that( result_full, contains_exactly( 'foo1', 'foo2', 'foo3' ) ) - assert_that( result_2, contains_exactly( 'foo1', 'foo2' ) ) - - -def CppBindings_IdentifierCompleter_test(): - identifier_completer = ycm_core.IdentifierCompleter() - identifiers = ycm_core.StringVector() - identifiers.append( 'foo' ) - identifiers.append( 'bar' ) - identifiers.append( 'baz' ) - identifier_completer.ClearForFileAndAddIdentifiersToDatabase( identifiers, - 'foo', - 'file' ) - del identifiers - query_fo_10 = identifier_completer.CandidatesForQueryAndType( - 'fo', 'foo', 10 ) - query_fo = identifier_completer.CandidatesForQueryAndType( 'fo', 'foo' ) - query_a = identifier_completer.CandidatesForQueryAndType( 'a', 'foo' ) - assert_that( query_fo_10, contains_exactly( 'foo' ) ) - assert_that( query_fo, contains_exactly( 'foo' ) ) - assert_that( query_a, contains_exactly( 'bar', 'baz' ) ) - identifiers = ycm_core.StringVector() - identifiers.append( 'oof' ) - identifiers.append( 'rab' ) - identifiers.append( 'zab' ) - identifier_completer.ClearForFileAndAddIdentifiersToDatabase( - identifiers, 'foo', 'file' ) - query_a_10 = identifier_completer.CandidatesForQueryAndType( 'a', 'foo' ) - assert_that( query_a_10, contains_exactly( 'rab', 'zab' ) ) - - -@ClangOnly -def CppBindings_UnsavedFile_test(): - unsaved_file = ycm_core.UnsavedFile() - filename = 'foo' - contents = 'bar\\n' - length = len( contents ) - unsaved_file.filename_ = filename - unsaved_file.contents_ = contents - unsaved_file.length_ = length - del filename - del contents - del length - assert_that( unsaved_file, has_properties( { - 'filename_': 'foo', - 'contents_': 'bar\\n', - 'length_': len( 'bar\\n' ) - } ) ) - - -@ClangOnly -def CppBindings_DeclarationLocation_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 9 - column = 17 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - location = clang_completer.GetDeclarationLocation( translation_unit, - filename, - line, - column, - unsaved_file_vector, - flags, - reparse ) +class CppBindingsGeneralTest( TestCase ): + def test_CppBindings_FilterAndSortCandidates( self ): + candidates = [ 'foo1', 'foo2', 'foo3' ] + query = 'oo' + candidate_property = '' + + result_full = ycm_core.FilterAndSortCandidates( candidates, + candidate_property, + query ) + result_2 = ycm_core.FilterAndSortCandidates( candidates, + candidate_property, + query, + 2 ) + + del candidates + del query + del candidate_property + + assert_that( result_full, contains_exactly( 'foo1', 'foo2', 'foo3' ) ) + assert_that( result_2, contains_exactly( 'foo1', 'foo2' ) ) + + + def test_CppBindings_IdentifierCompleter( self ): + identifier_completer = ycm_core.IdentifierCompleter() + identifiers = ycm_core.StringVector() + identifiers.append( 'foo' ) + identifiers.append( 'bar' ) + identifiers.append( 'baz' ) + identifier_completer.ClearForFileAndAddIdentifiersToDatabase( identifiers, + 'foo', + 'file' ) + del identifiers + query_fo_10 = identifier_completer.CandidatesForQueryAndType( + 'fo', 'foo', 10 ) + query_fo = identifier_completer.CandidatesForQueryAndType( 'fo', 'foo' ) + query_a = identifier_completer.CandidatesForQueryAndType( 'a', 'foo' ) + assert_that( query_fo_10, contains_exactly( 'foo' ) ) + assert_that( query_fo, contains_exactly( 'foo' ) ) + assert_that( query_a, contains_exactly( 'bar', 'baz' ) ) + identifiers = ycm_core.StringVector() + identifiers.append( 'oof' ) + identifiers.append( 'rab' ) + identifiers.append( 'zab' ) + identifier_completer.ClearForFileAndAddIdentifiersToDatabase( + identifiers, 'foo', 'file' ) + query_a_10 = identifier_completer.CandidatesForQueryAndType( 'a', 'foo' ) + assert_that( query_a_10, contains_exactly( 'rab', 'zab' ) ) + + + @ClangOnly + def test_CppBindings_UnsavedFile( self ): + unsaved_file = ycm_core.UnsavedFile() + filename = 'foo' + contents = 'bar\\n' + length = len( contents ) + unsaved_file.filename_ = filename + unsaved_file.contents_ = contents + unsaved_file.length_ = length + del filename + del contents + del length + assert_that( unsaved_file, has_properties( { + 'filename_': 'foo', + 'contents_': 'bar\\n', + 'length_': len( 'bar\\n' ) + } ) ) + + + @ClangOnly + def test_CppBindings_DeclarationLocation( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 9 + column = 17 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + location = clang_completer.GetDeclarationLocation( translation_unit, + filename, + line, + column, + unsaved_file_vector, + flags, + reparse ) - del translation_unit - del filename - del line - del column - del unsaved_file_vector - del flags - del clang_completer - del reparse - assert_that( location, - has_properties( { 'line_number_': 2, - 'column_number_': 5, - 'filename_': PathToTestFile( 'foo.c' ) } ) ) - - -@ClangOnly -def CppBindings_DefinitionOrDeclarationLocation_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 9 - column = 17 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - location = ( clang_completer. - GetDefinitionOrDeclarationLocation( translation_unit, - filename, - line, - column, - unsaved_file_vector, - flags, - reparse ) ) - - del translation_unit - del filename - del line - del column - del unsaved_file_vector - del flags - del clang_completer - del reparse - assert_that( location, - has_properties( { 'line_number_': 2, - 'column_number_': 5, - 'filename_': PathToTestFile( 'foo.c' ) } ) ) - - -@ClangOnly -def CppBindings_DefinitionLocation_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 9 - column = 17 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - location = clang_completer.GetDefinitionLocation( translation_unit, - filename, - line, - column, - unsaved_file_vector, - flags, - reparse ) - - del translation_unit - del filename - del line - del column - del unsaved_file_vector - del flags - del clang_completer - del reparse - assert_that( location, - has_properties( { 'line_number_': 2, - 'column_number_': 5, - 'filename_': PathToTestFile( 'foo.c' ) } ) ) - - -@ClangOnly -def CppBindings_Candidates_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 11 - column = 6 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - candidates = ( clang_completer - .CandidatesForLocationInFile( translation_unit, - filename, - line, - column, - unsaved_file_vector, - flags ) ) - - del translation_unit - del filename - del line - del column - del unsaved_file_vector - del flags - del clang_completer - del reparse - candidates = [ ConvertCompletionData( x ) for x in candidates ] - assert_that( candidates, contains_inanyorder( - has_entries( { - 'detailed_info': 'float b\n', - 'extra_menu_info': 'float', - 'insertion_text': 'b', - 'kind': 'MEMBER', - 'menu_text': 'b' - } ), - has_entries( { - 'detailed_info': 'int a\n', - 'extra_menu_info': 'int', - 'insertion_text': 'a', - 'kind': 'MEMBER', - 'menu_text': 'a' - } ) - ) ) - - -@ClangOnly -def CppBindings_GetType_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 9 - column = 17 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - type_at_cursor = clang_completer.GetTypeAtLocation( translation_unit, + del translation_unit + del filename + del line + del column + del unsaved_file_vector + del flags + del clang_completer + del reparse + assert_that( location, + has_properties( { 'line_number_': 2, + 'column_number_': 5, + 'filename_': PathToTestFile( 'foo.c' ) } ) ) + + + @ClangOnly + def test_CppBindings_DefinitionOrDeclarationLocation( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 9 + column = 17 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + location = ( clang_completer. + GetDefinitionOrDeclarationLocation( translation_unit, + filename, + line, + column, + unsaved_file_vector, + flags, + reparse ) ) + + del translation_unit + del filename + del line + del column + del unsaved_file_vector + del flags + del clang_completer + del reparse + assert_that( location, + has_properties( { 'line_number_': 2, + 'column_number_': 5, + 'filename_': PathToTestFile( 'foo.c' ) } ) ) + + + @ClangOnly + def test_CppBindings_DefinitionLocation( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 9 + column = 17 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + location = clang_completer.GetDefinitionLocation( translation_unit, filename, line, column, unsaved_file_vector, flags, reparse ) - del translation_unit - del filename - del line - del column - del unsaved_file_vector - del flags - del clang_completer - del reparse - - assert_that( 'int ()', equal_to( type_at_cursor ) ) - - -@ClangOnly -def CppBindings_GetParent_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 9 - column = 17 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - enclosing_function = ( clang_completer - .GetEnclosingFunctionAtLocation( translation_unit, - filename, - line, - column, - unsaved_file_vector, - flags, - reparse ) ) - - del translation_unit - del filename - del line - del column - del unsaved_file_vector - del flags - del clang_completer - del reparse - - assert_that( 'bar', equal_to( enclosing_function ) ) - - -@ClangOnly -def CppBindings_FixIt_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 3 - column = 5 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - fixits = clang_completer.GetFixItsForLocationInFile( translation_unit, - filename, - line, - column, - unsaved_file_vector, - flags, - reparse ) - del translation_unit - del filename - del line - del column - del unsaved_file_vector - del flags - del clang_completer - del reparse - - assert_that( - fixits, - contains_exactly( has_properties( { - 'text': ( PathToTestFile( 'foo.c' ) + - ':3:16: error: expected \';\' at end of declaration' ), - 'location': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - 'filename_': PathToTestFile( 'foo.c' ) - } ), - 'chunks': contains_exactly( has_properties( { - 'replacement_text': ';', - 'range': has_properties( { - 'start_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - 'end_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - } ) - } ) ), - 'kind': None, - } ) ) ) - - -@ClangOnly -def CppBindings_Docs_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 9 - column = 16 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - docs = clang_completer.GetDocsForLocationInFile( translation_unit, + + del translation_unit + del filename + del line + del column + del unsaved_file_vector + del flags + del clang_completer + del reparse + assert_that( location, + has_properties( { 'line_number_': 2, + 'column_number_': 5, + 'filename_': PathToTestFile( 'foo.c' ) } ) ) + + + @ClangOnly + def test_CppBindings_Candidates( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 11 + column = 6 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + candidates = ( clang_completer + .CandidatesForLocationInFile( translation_unit, filename, line, column, unsaved_file_vector, - flags, - reparse ) - del translation_unit - del filename - del line - del column - del unsaved_file_vector - del flags - del clang_completer - del reparse - assert_that( - docs, - has_properties( { - 'comment_xml': 'fooooc:@F@foooo#' - 'int foooo()' - ' Foo', - 'brief_comment': 'Foo', - 'raw_comment': '/// Foo', - 'canonical_type': 'int ()', - 'display_name': 'foooo' } ) ) - - -@ClangOnly -def CppBindings_Diags_test(): - filename = PathToTestFile( 'foo.c' ) - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - reparse = True - clang_completer = ycm_core.ClangCompleter() - - diag_vector = clang_completer.UpdateTranslationUnit( filename, - unsaved_file_vector, - flags ) - - diags = [ BuildDiagnosticData( x ) for x in diag_vector ] - - del diag_vector - del filename - del unsaved_file_vector - del flags - del clang_completer - del reparse - - assert_that( - diags, - contains_exactly( - has_entries( { - 'kind': 'ERROR', - 'text': contains_string( 'expected \';\' at end of declaration' ), - 'ranges': contains_exactly(), - 'location': has_entries( { - 'line_num': 3, - 'column_num': 16, + flags ) ) + + del translation_unit + del filename + del line + del column + del unsaved_file_vector + del flags + del clang_completer + del reparse + candidates = [ ConvertCompletionData( x ) for x in candidates ] + assert_that( candidates, contains_inanyorder( + has_entries( { + 'detailed_info': 'float b\n', + 'extra_menu_info': 'float', + 'insertion_text': 'b', + 'kind': 'MEMBER', + 'menu_text': 'b' + } ), + has_entries( { + 'detailed_info': 'int a\n', + 'extra_menu_info': 'int', + 'insertion_text': 'a', + 'kind': 'MEMBER', + 'menu_text': 'a' + } ) + ) ) + + + @ClangOnly + def test_CppBindings_GetType( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 9 + column = 17 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + type_at_cursor = clang_completer.GetTypeAtLocation( translation_unit, + filename, + line, + column, + unsaved_file_vector, + flags, + reparse ) + del translation_unit + del filename + del line + del column + del unsaved_file_vector + del flags + del clang_completer + del reparse + + assert_that( 'int ()', equal_to( type_at_cursor ) ) + + + @ClangOnly + def test_CppBindings_GetParent( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 9 + column = 17 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + enclosing_function = clang_completer.GetEnclosingFunctionAtLocation( + translation_unit, + filename, + line, + column, + unsaved_file_vector, + flags, + reparse ) + + del translation_unit + del filename + del line + del column + del unsaved_file_vector + del flags + del clang_completer + del reparse + + assert_that( 'bar', equal_to( enclosing_function ) ) + + + @ClangOnly + def test_CppBindings_FixIt( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 3 + column = 5 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + fixits = clang_completer.GetFixItsForLocationInFile( translation_unit, + filename, + line, + column, + unsaved_file_vector, + flags, + reparse ) + del translation_unit + del filename + del line + del column + del unsaved_file_vector + del flags + del clang_completer + del reparse + + assert_that( + fixits, + contains_exactly( has_properties( { + 'text': ( PathToTestFile( 'foo.c' ) + + ':3:16: error: expected \';\' at end of declaration' ), + 'location': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + 'filename_': PathToTestFile( 'foo.c' ) } ), - 'location_extent': has_entries( { - 'start': has_entries( { - 'line_num': 3, - 'column_num': 16, - } ), - 'end': has_entries( { - 'line_num': 3, - 'column_num': 16, + 'chunks': contains_exactly( has_properties( { + 'replacement_text': ';', + 'range': has_properties( { + 'start_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + 'end_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + } ) + } ) ), + 'kind': None, + } ) ) ) + + + @ClangOnly + def test_CppBindings_Docs( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 9 + column = 16 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + docs = clang_completer.GetDocsForLocationInFile( translation_unit, + filename, + line, + column, + unsaved_file_vector, + flags, + reparse ) + del translation_unit + del filename + del line + del column + del unsaved_file_vector + del flags + del clang_completer + del reparse + assert_that( + docs, + has_properties( { + 'comment_xml': 'fooooc:@F@foooo#' + 'int foooo()' + ' Foo', + 'brief_comment': 'Foo', + 'raw_comment': '/// Foo', + 'canonical_type': 'int ()', + 'display_name': 'foooo' } ) ) + + + @ClangOnly + def test_CppBindings_Diags( self ): + filename = PathToTestFile( 'foo.c' ) + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + reparse = True + clang_completer = ycm_core.ClangCompleter() + + diag_vector = clang_completer.UpdateTranslationUnit( filename, + unsaved_file_vector, + flags ) + + diags = [ BuildDiagnosticData( x ) for x in diag_vector ] + + del diag_vector + del filename + del unsaved_file_vector + del flags + del clang_completer + del reparse + + assert_that( + diags, + contains_exactly( + has_entries( { + 'kind': 'ERROR', + 'text': contains_string( 'expected \';\' at end of declaration' ), + 'ranges': contains_exactly(), + 'location': has_entries( { + 'line_num': 3, + 'column_num': 16, + } ), + 'location_extent': has_entries( { + 'start': has_entries( { + 'line_num': 3, + 'column_num': 16, + } ), + 'end': has_entries( { + 'line_num': 3, + 'column_num': 16, + } ), } ), - } ), - } ) ) ) - - -@ClangOnly -def CppBindings_CompilationDatabase_test(): - with TemporaryTestDir() as tmp_dir: - compile_commands = [ - { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -I. -I/absolute/path -Wall', - 'file': os.path.join( tmp_dir, 'test.cc' ), - }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - db = ycm_core.CompilationDatabase( tmp_dir ) - db_successful = db.DatabaseSuccessfullyLoaded() - db_busy = db.AlreadyGettingFlags() - db_dir = db.database_directory - compilation_info = db.GetCompilationInfoForFile( - compile_commands[ 0 ][ 'file' ] ) - del db - del compile_commands - assert_that( db_successful, equal_to( True ) ) - assert_that( db_busy, equal_to( False ) ) - assert_that( db_dir, equal_to( tmp_dir ) ) - assert_that( compilation_info, - has_properties( { - 'compiler_working_dir_': tmp_dir, - 'compiler_flags_': contains_exactly( 'clang++', - '--driver-mode=g++', - '-x', - 'c++', - '-I.', - '-I/absolute/path', - '-Wall' ) - } ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + } ) ) ) + + + @ClangOnly + def test_CppBindings_CompilationDatabase( self ): + with TemporaryTestDir() as tmp_dir: + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -I. -I/absolute/path -Wall', + 'file': os.path.join( tmp_dir, 'test.cc' ), + }, + ] + with TemporaryClangProject( tmp_dir, compile_commands ): + db = ycm_core.CompilationDatabase( tmp_dir ) + db_successful = db.DatabaseSuccessfullyLoaded() + db_busy = db.AlreadyGettingFlags() + db_dir = db.database_directory + compilation_info = db.GetCompilationInfoForFile( + compile_commands[ 0 ][ 'file' ] ) + del db + del compile_commands + assert_that( db_successful, equal_to( True ) ) + assert_that( db_busy, equal_to( False ) ) + assert_that( db_dir, equal_to( tmp_dir ) ) + assert_that( compilation_info, + has_properties( { + 'compiler_working_dir_': tmp_dir, + 'compiler_flags_': contains_exactly( 'clang++', + '--driver-mode=g++', + '-x', + 'c++', + '-I.', + '-I/absolute/path', + '-Wall' ) + } ) ) diff --git a/ycmd/tests/bindings/cpp_bindings_raises_exception_test.py b/ycmd/tests/bindings/cpp_bindings_raises_exception_test.py index 5b8febcf66..b18e23f64b 100644 --- a/ycmd/tests/bindings/cpp_bindings_raises_exception_test.py +++ b/ycmd/tests/bindings/cpp_bindings_raises_exception_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -18,111 +18,108 @@ from ycmd.utils import ImportCore from ycmd.tests.test_utils import ClangOnly from hamcrest import assert_that, calling, raises +from unittest import TestCase ycm_core = ImportCore() READONLY_MESSAGE = 'can\'t set attribute' -@ClangOnly -def CppBindings_ReadOnly_test(): - assert_that( calling( ycm_core.CompletionData().__setattr__ ) - .with_args( 'kind_', ycm_core.CompletionData().kind_ ), - raises( AttributeError, READONLY_MESSAGE ) ) - - assert_that( calling( ycm_core.Location().__setattr__ ) - .with_args( 'line_number_', 1 ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Location().__setattr__ ) - .with_args( 'column_number_', 1 ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Location().__setattr__ ) - .with_args( 'filename_', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - - assert_that( calling( ycm_core.Range().__setattr__ ) - .with_args( 'end_', ycm_core.Range().end_ ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Range().__setattr__ ) - .with_args( 'start_', ycm_core.Range().start_ ), - raises( AttributeError, READONLY_MESSAGE ) ) - - assert_that( calling( ycm_core.FixItChunk().__setattr__ ) - .with_args( 'range', ycm_core.FixItChunk().range ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.FixItChunk().__setattr__ ) - .with_args( 'replacement_text', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - - assert_that( calling( ycm_core.FixIt().__setattr__ ) - .with_args( 'chunks', ycm_core.FixIt().chunks ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.FixIt().__setattr__ ) - .with_args( 'location', ycm_core.FixIt().location ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.FixIt().__setattr__ ) - .with_args( 'text', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - - assert_that( calling( ycm_core.Diagnostic().__setattr__ ) - .with_args( 'ranges_', ycm_core.Diagnostic().ranges_ ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Diagnostic().__setattr__ ) - .with_args( 'location_', ycm_core.Diagnostic().location_ ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Diagnostic().__setattr__ ) - .with_args( 'location_extent_', - ycm_core.Diagnostic().location_extent_ ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Diagnostic().__setattr__ ) - .with_args( 'fixits_', ycm_core.Diagnostic().fixits_ ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Diagnostic().__setattr__ ) - .with_args( 'text_', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Diagnostic().__setattr__ ) - .with_args( 'long_formatted_text_', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.Diagnostic().__setattr__ ) - .with_args( 'kind_', ycm_core.Diagnostic().kind_.WARNING ), - raises( AttributeError, READONLY_MESSAGE ) ) - - assert_that( calling( ycm_core.DocumentationData().__setattr__ ) - .with_args( 'raw_comment', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.DocumentationData().__setattr__ ) - .with_args( 'brief_comment', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.DocumentationData().__setattr__ ) - .with_args( 'canonical_type', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.DocumentationData().__setattr__ ) - .with_args( 'display_name', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( ycm_core.DocumentationData().__setattr__ ) - .with_args( 'comment_xml', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - - db = ycm_core.CompilationDatabase( 'foo' ) - assert_that( calling( db.__setattr__ ) - .with_args( 'database_directory', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - - compilation_info = db.GetCompilationInfoForFile( 'foo.c' ) - assert_that( calling( compilation_info.__setattr__ ) - .with_args( 'compiler_working_dir_', 'foo' ), - raises( AttributeError, READONLY_MESSAGE ) ) - assert_that( calling( compilation_info.__setattr__ ) - .with_args( 'compiler_flags_', ycm_core.StringVector() ), - raises( AttributeError, READONLY_MESSAGE ) ) - - -@ClangOnly -def CppBindings_CompilationInfo_NoInit_test(): - assert_that( calling( ycm_core.CompilationInfoForFile ), - raises( TypeError, 'ycm_core.CompilationInfoForFile:' - ' No constructor defined!' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class CppBindingsExceptionTest( TestCase ): + @ClangOnly + def test_CppBindings_ReadOnly( self ): + assert_that( calling( ycm_core.CompletionData().__setattr__ ) + .with_args( 'kind_', ycm_core.CompletionData().kind_ ), + raises( AttributeError, READONLY_MESSAGE ) ) + + assert_that( calling( ycm_core.Location().__setattr__ ) + .with_args( 'line_number_', 1 ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Location().__setattr__ ) + .with_args( 'column_number_', 1 ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Location().__setattr__ ) + .with_args( 'filename_', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + + assert_that( calling( ycm_core.Range().__setattr__ ) + .with_args( 'end_', ycm_core.Range().end_ ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Range().__setattr__ ) + .with_args( 'start_', ycm_core.Range().start_ ), + raises( AttributeError, READONLY_MESSAGE ) ) + + assert_that( calling( ycm_core.FixItChunk().__setattr__ ) + .with_args( 'range', ycm_core.FixItChunk().range ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.FixItChunk().__setattr__ ) + .with_args( 'replacement_text', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + + assert_that( calling( ycm_core.FixIt().__setattr__ ) + .with_args( 'chunks', ycm_core.FixIt().chunks ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.FixIt().__setattr__ ) + .with_args( 'location', ycm_core.FixIt().location ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.FixIt().__setattr__ ) + .with_args( 'text', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + + assert_that( calling( ycm_core.Diagnostic().__setattr__ ) + .with_args( 'ranges_', ycm_core.Diagnostic().ranges_ ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Diagnostic().__setattr__ ) + .with_args( 'location_', ycm_core.Diagnostic().location_ ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Diagnostic().__setattr__ ) + .with_args( 'location_extent_', + ycm_core.Diagnostic().location_extent_ ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Diagnostic().__setattr__ ) + .with_args( 'fixits_', ycm_core.Diagnostic().fixits_ ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Diagnostic().__setattr__ ) + .with_args( 'text_', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Diagnostic().__setattr__ ) + .with_args( 'long_formatted_text_', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.Diagnostic().__setattr__ ) + .with_args( 'kind_', ycm_core.Diagnostic().kind_.WARNING ), + raises( AttributeError, READONLY_MESSAGE ) ) + + assert_that( calling( ycm_core.DocumentationData().__setattr__ ) + .with_args( 'raw_comment', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.DocumentationData().__setattr__ ) + .with_args( 'brief_comment', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.DocumentationData().__setattr__ ) + .with_args( 'canonical_type', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.DocumentationData().__setattr__ ) + .with_args( 'display_name', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( ycm_core.DocumentationData().__setattr__ ) + .with_args( 'comment_xml', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + + db = ycm_core.CompilationDatabase( 'foo' ) + assert_that( calling( db.__setattr__ ) + .with_args( 'database_directory', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + + compilation_info = db.GetCompilationInfoForFile( 'foo.c' ) + assert_that( calling( compilation_info.__setattr__ ) + .with_args( 'compiler_working_dir_', 'foo' ), + raises( AttributeError, READONLY_MESSAGE ) ) + assert_that( calling( compilation_info.__setattr__ ) + .with_args( 'compiler_flags_', ycm_core.StringVector() ), + raises( AttributeError, READONLY_MESSAGE ) ) + + + @ClangOnly + def test_CppBindings_CompilationInfo_NoInit( self ): + assert_that( calling( ycm_core.CompilationInfoForFile ), + raises( TypeError, 'ycm_core.CompilationInfoForFile:' + ' No constructor defined!' ) ) diff --git a/ycmd/tests/bindings/cpp_bindings_vectors_test.py b/ycmd/tests/bindings/cpp_bindings_vectors_test.py index 05850d3b7b..2875d73b6e 100644 --- a/ycmd/tests/bindings/cpp_bindings_vectors_test.py +++ b/ycmd/tests/bindings/cpp_bindings_vectors_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -27,6 +27,7 @@ contains_string, has_entries, has_properties ) +from unittest import TestCase ycm_core = ImportCore() @@ -34,341 +35,337 @@ def EmplaceBack( vector, element ): vector.append( element ) -def CppBindings_StringVector_test(): - str1 = 'foo' - str2 = 'bar' - str3 = 'baz' - string_vector = ycm_core.StringVector() - string_vector.append( str1 ) - EmplaceBack( string_vector, str2 ) - string_vector.append( str3 ) - del str1 - del str2 - del str3 - assert_that( string_vector, contains_exactly( 'foo', 'bar', 'baz' ) ) - - -@ClangOnly -def CppBindings_UnsavedFileVector_test(): - unsaved_file_vector = ycm_core.UnsavedFileVector() - unsaved_file = ycm_core.UnsavedFile() - unsaved_file.filename_ = 'foo' - unsaved_file.contents_ = 'bar' - unsaved_file.length_ = 3 - unsaved_file_vector.append( unsaved_file ) - EmplaceBack( unsaved_file_vector, unsaved_file ) - del unsaved_file - assert_that( unsaved_file_vector, - contains_exactly( - has_properties( { - 'filename_': 'foo', - 'contents_': 'bar', - 'length_': len( 'bar' ) - } ), - has_properties( { - 'filename_': 'foo', - 'contents_': 'bar', - 'length_': len( 'bar' ) - } ) - ) ) - - -@ClangOnly -def CppBindings_FixItVector_test(): - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - clang_completer = ycm_core.ClangCompleter() - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - fixits = ( clang_completer - .GetFixItsForLocationInFile( translation_unit, - filename, - 3, - 5, - ycm_core.UnsavedFileVector(), - flags, - True ) ) - - fixits = fixits[ 0:1 ] - EmplaceBack( fixits, fixits[ 0 ] ) - del translation_unit - del flags - del filename - del clang_completer - assert_that( - fixits, - contains_exactly( - has_properties( { - 'text': ( PathToTestFile( 'foo.c' ) + - ':3:16: error: expected \';\' at end of declaration' ), - 'location': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - 'filename_': PathToTestFile( 'foo.c' ) - } ), - 'chunks': contains_exactly( has_properties( { - 'replacement_text': ';', - 'range': has_properties( { - 'start_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - 'end_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - } ) - } ) ), - } ), - has_properties( { - 'text': ( PathToTestFile( 'foo.c' ) + - ':3:16: error: expected \';\' at end of declaration' ), - 'location': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - 'filename_': PathToTestFile( 'foo.c' ) +class CppBindingsVectorTest( TestCase ): + def test_CppBindings_StringVector( self ): + str1 = 'foo' + str2 = 'bar' + str3 = 'baz' + string_vector = ycm_core.StringVector() + string_vector.append( str1 ) + EmplaceBack( string_vector, str2 ) + string_vector.append( str3 ) + del str1 + del str2 + del str3 + assert_that( string_vector, contains_exactly( 'foo', 'bar', 'baz' ) ) + + + @ClangOnly + def test_CppBindings_UnsavedFileVector( self ): + unsaved_file_vector = ycm_core.UnsavedFileVector() + unsaved_file = ycm_core.UnsavedFile() + unsaved_file.filename_ = 'foo' + unsaved_file.contents_ = 'bar' + unsaved_file.length_ = 3 + unsaved_file_vector.append( unsaved_file ) + EmplaceBack( unsaved_file_vector, unsaved_file ) + del unsaved_file + assert_that( unsaved_file_vector, + contains_exactly( + has_properties( { + 'filename_': 'foo', + 'contents_': 'bar', + 'length_': len( 'bar' ) + } ), + has_properties( { + 'filename_': 'foo', + 'contents_': 'bar', + 'length_': len( 'bar' ) + } ) + ) ) + + + @ClangOnly + def test_CppBindings_FixItVector( self ): + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + clang_completer = ycm_core.ClangCompleter() + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + fixits = ( clang_completer + .GetFixItsForLocationInFile( translation_unit, + filename, + 3, + 5, + ycm_core.UnsavedFileVector(), + flags, + True ) ) + + fixits = fixits[ 0:1 ] + EmplaceBack( fixits, fixits[ 0 ] ) + del translation_unit + del flags + del filename + del clang_completer + assert_that( + fixits, + contains_exactly( + has_properties( { + 'text': ( PathToTestFile( 'foo.c' ) + + ':3:16: error: expected \';\' at end of declaration' ), + 'location': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + 'filename_': PathToTestFile( 'foo.c' ) + } ), + 'chunks': contains_exactly( has_properties( { + 'replacement_text': ';', + 'range': has_properties( { + 'start_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + 'end_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + } ) + } ) ), } ), - 'chunks': contains_exactly( has_properties( { - 'replacement_text': ';', - 'range': has_properties( { - 'start_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - 'end_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - } ) - } ) ), - } ) ) ) - - -@ClangOnly -def CppBindings_FixItChunkVector_test(): - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - clang_completer = ycm_core.ClangCompleter() - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - fixits = ( clang_completer - .GetFixItsForLocationInFile( translation_unit, - filename, - 3, - 5, - ycm_core.UnsavedFileVector(), - flags, - True ) ) - - fixit_chunks = fixits[ 0 ].chunks[ 0:1 ] - EmplaceBack( fixit_chunks, fixit_chunks[ 0 ] ) - del translation_unit - del flags - del filename - del clang_completer - del fixits - assert_that( fixit_chunks, contains_exactly( - has_properties( { - 'replacement_text': ';', - 'range': has_properties( { - 'start_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - 'end_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, + has_properties( { + 'text': ( PathToTestFile( 'foo.c' ) + + ':3:16: error: expected \';\' at end of declaration' ), + 'location': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + 'filename_': PathToTestFile( 'foo.c' ) + } ), + 'chunks': contains_exactly( has_properties( { + 'replacement_text': ';', + 'range': has_properties( { + 'start_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + 'end_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + } ) + } ) ), + } ) ) ) + + + @ClangOnly + def test_CppBindings_FixItChunkVector( self ): + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + clang_completer = ycm_core.ClangCompleter() + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + fixits = ( clang_completer + .GetFixItsForLocationInFile( translation_unit, + filename, + 3, + 5, + ycm_core.UnsavedFileVector(), + flags, + True ) ) + + fixit_chunks = fixits[ 0 ].chunks[ 0:1 ] + EmplaceBack( fixit_chunks, fixit_chunks[ 0 ] ) + del translation_unit + del flags + del filename + del clang_completer + del fixits + assert_that( fixit_chunks, contains_exactly( + has_properties( { + 'replacement_text': ';', + 'range': has_properties( { + 'start_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + 'end_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), } ), } ), - } ), - has_properties( { - 'replacement_text': ';', - 'range': has_properties( { - 'start_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - 'end_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, + has_properties( { + 'replacement_text': ';', + 'range': has_properties( { + 'start_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + 'end_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), } ), - } ), - } ) ) ) - - -@ClangOnly -def CppBindings_RangeVector_test(): - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - clang_completer = ycm_core.ClangCompleter() - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - - fixits = ( clang_completer - .GetFixItsForLocationInFile( translation_unit, - filename, - 3, - 5, - ycm_core.UnsavedFileVector(), - flags, - True ) ) - fixit_range = fixits[ 0 ].chunks[ 0 ].range - ranges = ycm_core.RangeVector() - ranges.append( fixit_range ) - EmplaceBack( ranges, fixit_range ) - del flags - del translation_unit - del filename - del clang_completer - del fixits - del fixit_range - assert_that( ranges, contains_exactly( - has_properties( { - 'start_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - 'end_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, - } ), - } ), - has_properties( { - 'start_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, + } ) ) ) + + + @ClangOnly + def test_CppBindings_RangeVector( self ): + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + clang_completer = ycm_core.ClangCompleter() + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + + fixits = ( clang_completer + .GetFixItsForLocationInFile( translation_unit, + filename, + 3, + 5, + ycm_core.UnsavedFileVector(), + flags, + True ) ) + fixit_range = fixits[ 0 ].chunks[ 0 ].range + ranges = ycm_core.RangeVector() + ranges.append( fixit_range ) + EmplaceBack( ranges, fixit_range ) + del flags + del translation_unit + del filename + del clang_completer + del fixits + del fixit_range + assert_that( ranges, contains_exactly( + has_properties( { + 'start_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + 'end_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), } ), - 'end_': has_properties( { - 'line_number_': 3, - 'column_number_': 16, + has_properties( { + 'start_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), + 'end_': has_properties( { + 'line_number_': 3, + 'column_number_': 16, + } ), } ), - } ), - ) ) + ) ) -@ClangOnly -def CppBindings_DiagnosticVector_test(): - filename = PathToTestFile( 'foo.c' ) - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc++' ) - clang_completer = ycm_core.ClangCompleter() + @ClangOnly + def test_CppBindings_DiagnosticVector( self ): + filename = PathToTestFile( 'foo.c' ) + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc++' ) + clang_completer = ycm_core.ClangCompleter() - diag_vector = clang_completer.UpdateTranslationUnit( filename, - unsaved_file_vector, - flags ) + diag_vector = clang_completer.UpdateTranslationUnit( filename, + unsaved_file_vector, + flags ) - del filename - del unsaved_file_vector - del flags - del clang_completer + del filename + del unsaved_file_vector + del flags + del clang_completer - diag_vector = diag_vector[ 0:1 ] - EmplaceBack( diag_vector, diag_vector[ 0 ] ) + diag_vector = diag_vector[ 0:1 ] + EmplaceBack( diag_vector, diag_vector[ 0 ] ) - diags = [ BuildDiagnosticData( x ) for x in diag_vector ] + diags = [ BuildDiagnosticData( x ) for x in diag_vector ] - del diag_vector + del diag_vector - assert_that( - diags, - contains_exactly( - has_entries( { - 'kind': 'ERROR', - 'text': contains_string( 'expected \';\' at end of declaration' ), - 'ranges': contains_exactly(), - 'location': has_entries( { - 'line_num': 3, - 'column_num': 16, - } ), - 'location_extent': has_entries( { - 'start': has_entries( { + assert_that( + diags, + contains_exactly( + has_entries( { + 'kind': 'ERROR', + 'text': contains_string( 'expected \';\' at end of declaration' ), + 'ranges': contains_exactly(), + 'location': has_entries( { 'line_num': 3, 'column_num': 16, } ), - 'end': has_entries( { - 'line_num': 3, - 'column_num': 16, + 'location_extent': has_entries( { + 'start': has_entries( { + 'line_num': 3, + 'column_num': 16, + } ), + 'end': has_entries( { + 'line_num': 3, + 'column_num': 16, + } ), } ), } ), - } ), - has_entries( { - 'kind': 'ERROR', - 'text': contains_string( 'expected \';\' at end of declaration' ), - 'ranges': contains_exactly(), - 'location': has_entries( { - 'line_num': 3, - 'column_num': 16, - } ), - 'location_extent': has_entries( { - 'start': has_entries( { + has_entries( { + 'kind': 'ERROR', + 'text': contains_string( 'expected \';\' at end of declaration' ), + 'ranges': contains_exactly(), + 'location': has_entries( { 'line_num': 3, 'column_num': 16, } ), - 'end': has_entries( { - 'line_num': 3, - 'column_num': 16, + 'location_extent': has_entries( { + 'start': has_entries( { + 'line_num': 3, + 'column_num': 16, + } ), + 'end': has_entries( { + 'line_num': 3, + 'column_num': 16, + } ), } ), } ), - } ), - ) ) - - -@ClangOnly -def CppBindings_CompletionDataVector_test(): - translation_unit = PathToTestFile( 'foo.c' ) - filename = PathToTestFile( 'foo.c' ) - line = 11 - column = 6 - unsaved_file_vector = ycm_core.UnsavedFileVector() - flags = ycm_core.StringVector() - flags.append( '-xc' ) - clang_completer = ycm_core.ClangCompleter() - - candidates = ( clang_completer - .CandidatesForLocationInFile( translation_unit, - filename, - line, - column, - unsaved_file_vector, - flags ) ) - - - if candidates[ 0 ].TextToInsertInBuffer() == 'a': - candidate = candidates[ 0 ] - else: - candidate = candidates[ 1 ] - candidates = ycm_core.CompletionVector() - candidates.append( candidate ) - EmplaceBack( candidates, candidate ) - - del translation_unit - del filename - del candidate - del clang_completer - del line - del column - del flags - del unsaved_file_vector - candidates = [ ConvertCompletionData( x ) for x in candidates ] - assert_that( candidates, contains_inanyorder( - has_entries( { - 'detailed_info': 'int a\n', - 'extra_menu_info': 'int', - 'insertion_text': 'a', - 'kind': 'MEMBER', - 'menu_text': 'a' - } ), - has_entries( { - 'detailed_info': 'int a\n', - 'extra_menu_info': 'int', - 'insertion_text': 'a', - 'kind': 'MEMBER', - 'menu_text': 'a' - } ) - ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + ) ) + + + @ClangOnly + def test_CppBindings_CompletionDataVector( self ): + translation_unit = PathToTestFile( 'foo.c' ) + filename = PathToTestFile( 'foo.c' ) + line = 11 + column = 6 + unsaved_file_vector = ycm_core.UnsavedFileVector() + flags = ycm_core.StringVector() + flags.append( '-xc' ) + clang_completer = ycm_core.ClangCompleter() + + candidates = ( clang_completer + .CandidatesForLocationInFile( translation_unit, + filename, + line, + column, + unsaved_file_vector, + flags ) ) + + + if candidates[ 0 ].TextToInsertInBuffer() == 'a': + candidate = candidates[ 0 ] + else: + candidate = candidates[ 1 ] + candidates = ycm_core.CompletionVector() + candidates.append( candidate ) + EmplaceBack( candidates, candidate ) + + del translation_unit + del filename + del candidate + del clang_completer + del line + del column + del flags + del unsaved_file_vector + candidates = [ ConvertCompletionData( x ) for x in candidates ] + assert_that( candidates, contains_inanyorder( + has_entries( { + 'detailed_info': 'int a\n', + 'extra_menu_info': 'int', + 'insertion_text': 'a', + 'kind': 'MEMBER', + 'menu_text': 'a' + } ), + has_entries( { + 'detailed_info': 'int a\n', + 'extra_menu_info': 'int', + 'insertion_text': 'a', + 'kind': 'MEMBER', + 'menu_text': 'a' + } ) + ) ) diff --git a/ycmd/tests/clang/__init__.py b/ycmd/tests/clang/__init__.py index 2d36fdae0e..969a386403 100644 --- a/ycmd/tests/clang/__init__.py +++ b/ycmd/tests/clang/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,8 +15,14 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . +import functools import os -from ycmd.tests.clang.conftest import * # noqa +import unittest +from ycmd.tests.test_utils import ( ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp ) +from ycmd.utils import ImportCore shared_app = None @@ -26,6 +32,36 @@ def PathToTestFile( *args ): return os.path.join( dir_of_current_script, 'testdata', *args ) +def setUpModule(): + global shared_app + shared_app = SetUpApp( { 'use_clangd': 0 } ) + + +def SharedYcmd( test ): + """Defines a decorator to be attached to tests of this package. This decorator + passes the shared ycmd application as a parameter. + Do NOT attach it to test generators but directly to the yielded tests.""" + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + custom_options.update( { 'use_clangd': 0 } ) + with IsolatedApp( custom_options ) as app: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + return Wrapper + return Decorator + + # A mock of ycm_core.ClangCompleter with translation units still being parsed. class MockCoreClangCompleter: @@ -52,3 +88,12 @@ def GetFixItsForLocationInFile( self, *args ): def UpdatingTranslationUnit( self, filename ): return True + + +def load_tests( loader: unittest.TestLoader, standard_tests, pattern ): + if not ImportCore().HasClangSupport(): + return unittest.TestSuite() + this_dir = os.path.dirname( __file__ ) + package_tests = loader.discover( this_dir, pattern ) + standard_tests.addTests( package_tests ) + return standard_tests diff --git a/ycmd/tests/clang/comment_strip_test.py b/ycmd/tests/clang/comment_strip_test.py index 0900d5d3a5..3f4c18b03c 100644 --- a/ycmd/tests/clang/comment_strip_test.py +++ b/ycmd/tests/clang/comment_strip_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -22,6 +22,7 @@ # flake8: noqa from hamcrest import assert_that, equal_to +from unittest import TestCase from ycmd.completers.cpp import clang_completer @@ -39,71 +40,72 @@ def _Check_FormatRawComment( comment, expected ): raise -def ClangCompleter_FormatRawComment_SingleLine_Doxygen_test(): - # - /// +class CommentStripTest( TestCase ): + def test_ClangCompleter_FormatRawComment_SingleLine_Doxygen( self ): + # - /// - # Indent + /// + - _Check_FormatRawComment( ' /// Single line comment', - 'Single line comment' ) + # Indent + /// + + _Check_FormatRawComment( ' /// Single line comment', + 'Single line comment' ) - # No indent, Internal indent removed, trailing whitespace removed - _Check_FormatRawComment( '/// Single line comment ', - 'Single line comment' ) + # No indent, Internal indent removed, trailing whitespace removed + _Check_FormatRawComment( '/// Single line comment ', + 'Single line comment' ) - # Extra / prevents initial indent being removed - _Check_FormatRawComment( '//// Single line comment ', - '/ Single line comment' ) - _Check_FormatRawComment( '////* Test ', '/* Test' ) + # Extra / prevents initial indent being removed + _Check_FormatRawComment( '//// Single line comment ', + '/ Single line comment' ) + _Check_FormatRawComment( '////* Test ', '/* Test' ) -def ClangCompleter_FormatRawComment_SingleLine_InlineDoxygen_test(): - # Inline-style comments with and without leading/trailing tokens - # - ///< - _Check_FormatRawComment( '//////< + _Check_FormatRawComment( '/////< - _Check_FormatRawComment( '////< + _Check_FormatRawComment( '////! - _Check_FormatRawComment( '//!Test', 'Test' ) - _Check_FormatRawComment( ' ////! + _Check_FormatRawComment( '//!Test', 'Test' ) + _Check_FormatRawComment( ' ///* - # - /** - # - */ - _Check_FormatRawComment( '/*Test', 'Test' ) - _Check_FormatRawComment( ' /** Test */ ', 'Test' ) - _Check_FormatRawComment( '/*** Test', '* Test' ) + def test_ClangCompleter_FormatRawComment_SingleLine_JavaDoc( self ): + # - /* + # - /** + # - */ + _Check_FormatRawComment( '/*Test', 'Test' ) + _Check_FormatRawComment( ' /** Test */ ', 'Test' ) + _Check_FormatRawComment( '/*** Test', '* Test' ) -def ClangCompleter_FormatRawComment_MultiOneLine_JavaDoc_test(): - # sic: This one isn't ideal, but it is (probably) uncommon - _Check_FormatRawComment( '/** Test */ /** Test2 */', - 'Test */ /** Test2' ) + def test_ClangCompleter_FormatRawComment_MultiOneLine_JavaDoc( self ): + # sic: This one isn't ideal, but it is (probably) uncommon + _Check_FormatRawComment( '/** Test */ /** Test2 */', + 'Test */ /** Test2' ) -def ClangCompleter_FormatRawComment_MultiLine_Doxygen_Inbalance_test(): - # The dedenting only applies to consistent indent - # Note trailing whitespace is intentional - _Check_FormatRawComment( + def test_ClangCompleter_FormatRawComment_MultiLine_Doxygen_Inbalance( self ): + # The dedenting only applies to consistent indent + # Note trailing whitespace is intentional + _Check_FormatRawComment( """ /// This is /// a @@ -125,11 +127,11 @@ def ClangCompleter_FormatRawComment_MultiLine_Doxygen_Inbalance_test(): """ ) # noqa -def ClangCompleter_FormatRawComment_MultiLine_JavaDoc_Inconsistent_test(): - # The dedenting only applies to consistent indent, and leaves any subsequent - # indent intact - # Note trailing whitespace is intentional - _Check_FormatRawComment( + def test_ClangCompleter_FormatRawComment_MultiLine_JavaDoc_Inconsistent( self ): + # The dedenting only applies to consistent indent, and leaves any subsequent + # indent intact + # Note trailing whitespace is intentional + _Check_FormatRawComment( """ /** All of the * Lines in this @@ -143,16 +145,15 @@ def ClangCompleter_FormatRawComment_MultiLine_JavaDoc_Inconsistent_test(): * Have a 2-space indent """ ) # noqa + def test_ClangCompleter_FormatRawComment_ZeroLine( self ): + _Check_FormatRawComment( '', '' ) -def ClangCompleter_FormatRawComment_ZeroLine_test(): - _Check_FormatRawComment( '', '' ) - -def ClangCompleter_FormatRawComment_MultiLine_empty_test(): - # Note trailing whitespace is intentional - _Check_FormatRawComment( + def test_ClangCompleter_FormatRawComment_MultiLine_empty( self ): + # Note trailing whitespace is intentional + _Check_FormatRawComment( """ - + * /// @@ -165,8 +166,3 @@ def ClangCompleter_FormatRawComment_MultiLine_empty_test(): """ ) # noqa - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True diff --git a/ycmd/tests/clang/conftest.py b/ycmd/tests/clang/conftest.py deleted file mode 100644 index 32447c98d7..0000000000 --- a/ycmd/tests/clang/conftest.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import pytest -from ycmd.tests.test_utils import ( ClearCompletionsCache, - IsolatedApp, - SetUpApp ) - -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - shared_app = SetUpApp( { 'use_clangd': 0 } ) - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - custom_options = request.param[ 1 ] - custom_options.update( { 'use_clangd': 0 } ) - with IsolatedApp( custom_options ) as app: - yield app - else: - global shared_app - ClearCompletionsCache() - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) diff --git a/ycmd/tests/clang/debug_info_test.py b/ycmd/tests/clang/debug_info_test.py index 336870b9c4..c0823b55ef 100644 --- a/ycmd/tests/clang/debug_info_test.py +++ b/ycmd/tests/clang/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -19,267 +19,242 @@ from hamcrest import ( assert_that, contains_exactly, empty, has_entries, has_entry, instance_of, matches_regexp ) -from ycmd.tests.clang import IsolatedYcmd, PathToTestFile, SharedYcmd +from unittest import TestCase +from ycmd.tests.clang import IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule # noqa from ycmd.tests.test_utils import ( BuildRequest, TemporaryTestDir, TemporaryClangProject ) -@SharedYcmd -def DebugInfo_FlagsWhenExtraConfLoadedAndNoCompilationDatabase_test( app ): - app.post_json( '/load_extra_conf_file', - { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) - request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), - filetype = 'cpp' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': empty(), - 'items': contains_exactly( - has_entries( { - 'key': 'compilation database path', - 'value': 'None' - } ), - has_entries( { - 'key': 'flags', - 'value': matches_regexp( "\\['-x', 'c\\+\\+', .*\\]" ) - } ), - has_entries( { - 'key': 'translation unit', - 'value': PathToTestFile( 'basic.cpp' ) - } ) - ) - } ) ) - ) - - -@SharedYcmd -def DebugInfo_FlagsWhenNoExtraConfAndNoCompilationDatabase_test( app ): - request_data = BuildRequest( filetype = 'cpp' ) - # First request, FlagsForFile raises a NoExtraConfDetected exception. - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': empty(), - 'items': contains_exactly( - has_entries( { - 'key': 'compilation database path', - 'value': 'None' - } ), - has_entries( { - 'key': 'flags', - 'value': '[]' - } ), - has_entries( { - 'key': 'translation unit', - 'value': instance_of( str ) - } ) - ) - } ) ) - ) - # Second request, FlagsForFile returns None. - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': empty(), - 'items': contains_exactly( - has_entries( { - 'key': 'compilation database path', - 'value': 'None' - } ), - has_entries( { - 'key': 'flags', - 'value': '[]' - } ), - has_entries( { - 'key': 'translation unit', - 'value': instance_of( str ) - } ) - ) - } ) ) - ) - +class DebugInfoTest( TestCase ): + @SharedYcmd + def test_DebugInfo_FlagsWhenExtraConfLoadedAndNoCompilationDatabase( + self, app ): + app.post_json( '/load_extra_conf_file', + { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) + request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), + filetype = 'cpp' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': empty(), + 'items': contains_exactly( + has_entries( { + 'key': 'compilation database path', + 'value': 'None' + } ), + has_entries( { + 'key': 'flags', + 'value': matches_regexp( "\\['-x', 'c\\+\\+', .*\\]" ) + } ), + has_entries( { + 'key': 'translation unit', + 'value': PathToTestFile( 'basic.cpp' ) + } ) + ) + } ) ) + ) -@IsolatedYcmd() -def DebugInfo_FlagsWhenExtraConfNotLoadedAndNoCompilationDatabase_test( - app ): - request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), - filetype = 'cpp' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': empty(), - 'items': contains_exactly( - has_entries( { - 'key': 'compilation database path', - 'value': 'None' - } ), - has_entries( { - 'key': 'flags', - 'value': '[]' - } ), - has_entries( { - 'key': 'translation unit', - 'value': PathToTestFile( 'basic.cpp' ) - } ) - ) - } ) ) - ) + @SharedYcmd + def test_DebugInfo_FlagsWhenNoExtraConfAndNoCompilationDatabase( self, app ): + request_data = BuildRequest( filetype = 'cpp' ) + # First request, FlagsForFile raises a NoExtraConfDetected exception. + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': empty(), + 'items': contains_exactly( + has_entries( { + 'key': 'compilation database path', + 'value': 'None' + } ), + has_entries( { + 'key': 'flags', + 'value': '[]' + } ), + has_entries( { + 'key': 'translation unit', + 'value': instance_of( str ) + } ) + ) + } ) ) + ) + # Second request, FlagsForFile returns None. + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': empty(), + 'items': contains_exactly( + has_entries( { + 'key': 'compilation database path', + 'value': 'None' + } ), + has_entries( { + 'key': 'flags', + 'value': '[]' + } ), + has_entries( { + 'key': 'translation unit', + 'value': instance_of( str ) + } ) + ) + } ) ) + ) -@IsolatedYcmd() -def DebugInfo_FlagsWhenNoExtraConfAndCompilationDatabaseLoaded_test( app ): - with TemporaryTestDir() as tmp_dir: - compile_commands = [ - { - 'directory': tmp_dir, - 'command': 'clang++ -I. -I/absolute/path -Wall', - 'file': os.path.join( tmp_dir, 'test.cc' ), - }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - request_data = BuildRequest( - filepath = os.path.join( tmp_dir, 'test.cc' ), - filetype = 'cpp' ) + @IsolatedYcmd() + def test_DebugInfo_FlagsWhenExtraConfNotLoadedAndNoCompilationDatabase( + self, app ): - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': empty(), - 'items': contains_exactly( - has_entries( { - 'key': 'compilation database path', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'flags', - 'value': matches_regexp( - "\\['clang\\+\\+', '-x', 'c\\+\\+', .*, '-Wall', .*\\]" - ) - } ), - has_entries( { - 'key': 'translation unit', - 'value': os.path.join( tmp_dir, 'test.cc' ), - } ) - ) - } ) ) - ) + request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), + filetype = 'cpp' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': empty(), + 'items': contains_exactly( + has_entries( { + 'key': 'compilation database path', + 'value': 'None' + } ), + has_entries( { + 'key': 'flags', + 'value': '[]' + } ), + has_entries( { + 'key': 'translation unit', + 'value': PathToTestFile( 'basic.cpp' ) + } ) + ) + } ) ) + ) -@IsolatedYcmd() -def DebugInfo_FlagsWhenNoExtraConfAndInvalidCompilationDatabase_test( app ): - with TemporaryTestDir() as tmp_dir: - compile_commands = 'garbage' - with TemporaryClangProject( tmp_dir, compile_commands ): - request_data = BuildRequest( - filepath = os.path.join( tmp_dir, 'test.cc' ), - filetype = 'cpp' ) + @IsolatedYcmd() + def test_DebugInfo_FlagsWhenNoExtraConfAndCompilationDatabaseLoaded( + self, app ): + with TemporaryTestDir() as tmp_dir: + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -I. -I/absolute/path -Wall', + 'file': os.path.join( tmp_dir, 'test.cc' ), + }, + ] + with TemporaryClangProject( tmp_dir, compile_commands ): + request_data = BuildRequest( + filepath = os.path.join( tmp_dir, 'test.cc' ), + filetype = 'cpp' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': empty(), - 'items': contains_exactly( - has_entries( { - 'key': 'compilation database path', - 'value': 'None' - } ), - has_entries( { - 'key': 'flags', - 'value': '[]' - } ), - has_entries( { - 'key': 'translation unit', - 'value': os.path.join( tmp_dir, 'test.cc' ) - } ) - ) - } ) ) - ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': empty(), + 'items': contains_exactly( + has_entries( { + 'key': 'compilation database path', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'flags', + 'value': matches_regexp( + "\\['clang\\+\\+', '-x', 'c\\+\\+', .*, '-Wall', .*\\]" + ) + } ), + has_entries( { + 'key': 'translation unit', + 'value': os.path.join( tmp_dir, 'test.cc' ), + } ) + ) + } ) ) + ) -@IsolatedYcmd( - { 'global_ycm_extra_conf': PathToTestFile( '.ycm_extra_conf.py' ) } ) -def DebugInfo_FlagsWhenGlobalExtraConfAndCompilationDatabaseLoaded_test( app ): - with TemporaryTestDir() as tmp_dir: - compile_commands = [ - { - 'directory': tmp_dir, - 'command': 'clang++ -I. -I/absolute/path -Wall', - 'file': os.path.join( tmp_dir, 'test.cc' ), - }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - request_data = BuildRequest( - filepath = os.path.join( tmp_dir, 'test.cc' ), - filetype = 'cpp' ) + @IsolatedYcmd() + def test_DebugInfo_FlagsWhenNoExtraConfAndInvalidCompilationDatabase( + self, app ): + with TemporaryTestDir() as tmp_dir: + compile_commands = 'garbage' + with TemporaryClangProject( tmp_dir, compile_commands ): + request_data = BuildRequest( + filepath = os.path.join( tmp_dir, 'test.cc' ), + filetype = 'cpp' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': empty(), - 'items': contains_exactly( - has_entries( { - 'key': 'compilation database path', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'flags', - 'value': matches_regexp( - "\\['clang\\+\\+', '-x', 'c\\+\\+', .*, '-Wall', .*\\]" - ) - } ), - has_entries( { - 'key': 'translation unit', - 'value': os.path.join( tmp_dir, 'test.cc' ), - } ) - ) - } ) ) - ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': empty(), + 'items': contains_exactly( + has_entries( { + 'key': 'compilation database path', + 'value': 'None' + } ), + has_entries( { + 'key': 'flags', + 'value': '[]' + } ), + has_entries( { + 'key': 'translation unit', + 'value': os.path.join( tmp_dir, 'test.cc' ) + } ) + ) + } ) ) + ) -@IsolatedYcmd( - { 'global_ycm_extra_conf': PathToTestFile( '.ycm_extra_conf.py' ) } ) -def DebugInfo_FlagsWhenGlobalExtraConfAndNoCompilationDatabase_test( app ): - request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), - filetype = 'cpp' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': empty(), - 'items': contains_exactly( - has_entries( { - 'key': 'compilation database path', - 'value': 'None' - } ), - has_entries( { - 'key': 'flags', - 'value': matches_regexp( "\\['-x', 'c\\+\\+', .*\\]" ) - } ), - has_entries( { - 'key': 'translation unit', - 'value': PathToTestFile( 'basic.cpp' ) - } ) - ) - } ) ) - ) + @IsolatedYcmd( + { 'global_ycm_extra_conf': PathToTestFile( '.ycm_extra_conf.py' ) } ) + def test_DebugInfo_FlagsWhenGlobalExtraConfAndCompilationDatabaseLoaded( + self, app ): + with TemporaryTestDir() as tmp_dir: + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -I. -I/absolute/path -Wall', + 'file': os.path.join( tmp_dir, 'test.cc' ), + }, + ] + with TemporaryClangProject( tmp_dir, compile_commands ): + request_data = BuildRequest( + filepath = os.path.join( tmp_dir, 'test.cc' ), + filetype = 'cpp' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': empty(), + 'items': contains_exactly( + has_entries( { + 'key': 'compilation database path', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'flags', + 'value': matches_regexp( + "\\['clang\\+\\+', '-x', 'c\\+\\+', .*, '-Wall', .*\\]" + ) + } ), + has_entries( { + 'key': 'translation unit', + 'value': os.path.join( tmp_dir, 'test.cc' ), + } ) + ) + } ) ) + ) -@SharedYcmd -def DebugInfo_Unity_test( app ): - # Main TU - app.post_json( '/load_extra_conf_file', - { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) - for filename in [ 'unity.cc', 'unity.h', 'unitya.cc' ]: - request_data = BuildRequest( filepath = PathToTestFile( filename ), + @IsolatedYcmd( + { 'global_ycm_extra_conf': PathToTestFile( '.ycm_extra_conf.py' ) } ) + def test_DebugInfo_FlagsWhenGlobalExtraConfAndNoCompilationDatabase( + self, app ): + request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), filetype = 'cpp' ) assert_that( app.post_json( '/debug_info', request_data ).json, @@ -297,13 +272,40 @@ def DebugInfo_Unity_test( app ): } ), has_entries( { 'key': 'translation unit', - 'value': PathToTestFile( 'unity.cc' ) + 'value': PathToTestFile( 'basic.cpp' ) } ) ) } ) ) ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_DebugInfo_Unity( self, app ): + # Main TU + app.post_json( '/load_extra_conf_file', + { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) + + for filename in [ 'unity.cc', 'unity.h', 'unitya.cc' ]: + request_data = BuildRequest( filepath = PathToTestFile( filename ), + filetype = 'cpp' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': empty(), + 'items': contains_exactly( + has_entries( { + 'key': 'compilation database path', + 'value': 'None' + } ), + has_entries( { + 'key': 'flags', + 'value': matches_regexp( "\\['-x', 'c\\+\\+', .*\\]" ) + } ), + has_entries( { + 'key': 'translation unit', + 'value': PathToTestFile( 'unity.cc' ) + } ) + ) + } ) ) + ) diff --git a/ycmd/tests/clang/diagnostics_test.py b/ycmd/tests/clang/diagnostics_test.py index 2de97569fa..d51fef862c 100644 --- a/ycmd/tests/clang/diagnostics_test.py +++ b/ycmd/tests/clang/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -26,14 +26,16 @@ equal_to ) from pprint import pprint -from ycmd.tests.clang import SharedYcmd, IsolatedYcmd, PathToTestFile +from unittest import TestCase +from ycmd.tests.clang import SharedYcmd, IsolatedYcmd, PathToTestFile, setUpModule # noqa from ycmd.tests.test_utils import BuildRequest, LocationMatcher, RangeMatcher from ycmd.utils import ReadFile -@IsolatedYcmd() -def Diagnostics_ZeroBasedLineAndColumn_test( app ): - contents = """ +class DiagnosticsTest( TestCase ): + @IsolatedYcmd() + def test_Diagnostics_ZeroBasedLineAndColumn( self, app ): + contents = """ void foo() { double baz = "foo"; } @@ -41,27 +43,28 @@ def Diagnostics_ZeroBasedLineAndColumn_test( app ): // Padding to 5 lines """ - event_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], - event_name = 'FileReadyToParse', - contents = contents, - filepath = 'foo', - filetype = 'cpp' ) - - results = app.post_json( '/event_notification', event_data ).json - assert_that( results, contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'text': contains_string( 'cannot initialize' ), - 'ranges': contains_exactly( RangeMatcher( 'foo', ( 3, 16 ), ( 3, 21 ) ) ), - 'location': LocationMatcher( 'foo', 3, 10 ), - 'location_extent': RangeMatcher( 'foo', ( 3, 10 ), ( 3, 13 ) ) - } ) - ) ) + event_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], + event_name = 'FileReadyToParse', + contents = contents, + filepath = 'foo', + filetype = 'cpp' ) + results = app.post_json( '/event_notification', event_data ).json + assert_that( results, contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'text': contains_string( 'cannot initialize' ), + 'ranges': contains_exactly( + RangeMatcher( 'foo', ( 3, 16 ), ( 3, 21 ) ) ), + 'location': LocationMatcher( 'foo', 3, 10 ), + 'location_extent': RangeMatcher( 'foo', ( 3, 10 ), ( 3, 13 ) ) + } ) + ) ) -@IsolatedYcmd() -def Diagnostics_SimpleLocationExtent_test( app ): - contents = """ + + @IsolatedYcmd() + def test_Diagnostics_SimpleLocationExtent( self, app ): + contents = """ void foo() { baz = 5; } @@ -69,23 +72,23 @@ def Diagnostics_SimpleLocationExtent_test( app ): // Padding to 5 lines """ - event_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], - event_name = 'FileReadyToParse', - contents = contents, - filepath = 'foo', - filetype = 'cpp' ) + event_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], + event_name = 'FileReadyToParse', + contents = contents, + filepath = 'foo', + filetype = 'cpp' ) - results = app.post_json( '/event_notification', event_data ).json - assert_that( results, contains_exactly( - has_entries( { - 'location_extent': RangeMatcher( 'foo', ( 3, 3 ), ( 3, 6 ) ) - } ) - ) ) + results = app.post_json( '/event_notification', event_data ).json + assert_that( results, contains_exactly( + has_entries( { + 'location_extent': RangeMatcher( 'foo', ( 3, 3 ), ( 3, 6 ) ) + } ) + ) ) -@IsolatedYcmd() -def Diagnostics_PragmaOnceWarningIgnored_test( app ): - contents = """ + @IsolatedYcmd() + def test_Diagnostics_PragmaOnceWarningIgnored( self, app ): + contents = """ #pragma once struct Foo { @@ -96,19 +99,19 @@ def Diagnostics_PragmaOnceWarningIgnored_test( app ): }; """ - event_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], - event_name = 'FileReadyToParse', - contents = contents, - filepath = '/foo.h', - filetype = 'cpp' ) + event_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], + event_name = 'FileReadyToParse', + contents = contents, + filepath = '/foo.h', + filetype = 'cpp' ) - response = app.post_json( '/event_notification', event_data ).json - assert_that( response, empty() ) + response = app.post_json( '/event_notification', event_data ).json + assert_that( response, empty() ) -@IsolatedYcmd() -def Diagnostics_Works_test( app ): - contents = """ + @IsolatedYcmd() + def test_Diagnostics_Works( self, app ): + contents = """ struct Foo { int x // semicolon missing here! int y; @@ -117,25 +120,25 @@ def Diagnostics_Works_test( app ): }; """ - diag_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], - line_num = 3, - contents = contents, - filetype = 'cpp' ) + diag_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], + line_num = 3, + contents = contents, + filetype = 'cpp' ) - event_data = diag_data.copy() - event_data.update( { - 'event_name': 'FileReadyToParse', - } ) + event_data = diag_data.copy() + event_data.update( { + 'event_name': 'FileReadyToParse', + } ) - app.post_json( '/event_notification', event_data ) - results = app.post_json( '/detailed_diagnostic', diag_data ).json - assert_that( results, - has_entry( 'message', contains_string( "expected ';'" ) ) ) + app.post_json( '/event_notification', event_data ) + results = app.post_json( '/detailed_diagnostic', diag_data ).json + assert_that( results, + has_entry( 'message', contains_string( "expected ';'" ) ) ) -@IsolatedYcmd() -def Diagnostics_Multiline_test( app ): - contents = """ + @IsolatedYcmd() + def test_Diagnostics_Multiline( self, app ): + contents = """ struct Foo { Foo(int z) {} }; @@ -145,144 +148,183 @@ def Diagnostics_Multiline_test( app ): } """ - diag_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], - line_num = 7, - contents = contents, - filetype = 'cpp' ) - - event_data = diag_data.copy() - event_data.update( { - 'event_name': 'FileReadyToParse', - } ) - - app.post_json( '/event_notification', event_data ) - results = app.post_json( '/detailed_diagnostic', diag_data ).json - assert_that( results, - has_entry( 'message', contains_string( "\n" ) ) ) - - -@IsolatedYcmd() -def Diagnostics_FixIt_Available_test( app ): - filepath = PathToTestFile( 'FixIt_Clang_cpp11.cpp' ) - - event_data = BuildRequest( contents = ReadFile( filepath ), - event_name = 'FileReadyToParse', - filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++', - '-std=c++03', - '-Wall', - '-Wextra', - '-pedantic' ] ) - - response = app.post_json( '/event_notification', event_data ).json - - pprint( response ) - - assert_that( response, has_items( - has_entries( { - 'location': LocationMatcher( filepath, 16, 3 ), - 'text': equal_to( 'switch condition type \'A\' ' - 'requires explicit conversion to \'int\'' ), - 'fixit_available': True - } ), - has_entries( { - 'location': LocationMatcher( filepath, 11, 3 ), - 'text': equal_to( - 'explicit conversion functions are a C++11 extension' ), - 'fixit_available': False - } ), - ) ) - - -@IsolatedYcmd() -def Diagnostics_MultipleMissingIncludes_test( app ): - filepath = PathToTestFile( 'multiple_missing_includes.cc' ) - - event_data = BuildRequest( contents = ReadFile( filepath ), - event_name = 'FileReadyToParse', - filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ] ) - - response = app.post_json( '/event_notification', event_data ).json - - pprint( response ) - - assert_that( response, has_items( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 1, 10 ), - 'text': equal_to( "'first_missing_include' file not found" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 2, 10 ), - 'text': equal_to( "'second_missing_include' file not found" ), - 'fixit_available': False - } ), - ) ) - - -@IsolatedYcmd() -def Diagnostics_LocationExtent_MissingSemicolon_test( app ): - filepath = PathToTestFile( 'location_extent.cc' ) - - event_data = BuildRequest( contents = ReadFile( filepath ), - event_name = 'FileReadyToParse', - filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ] ) - - response = app.post_json( '/event_notification', event_data ).json - - pprint( response ) - - assert_that( response, contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 2, 9 ), - 'location_extent': RangeMatcher( filepath, ( 2, 9 ), ( 2, 9 ) ), - 'ranges': empty(), - 'text': equal_to( "expected ';' at end of declaration list" ), - 'fixit_available': True - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 5, 1 ), - 'location_extent': RangeMatcher( filepath, ( 5, 1 ), ( 6, 11 ) ), - 'ranges': empty(), - 'text': equal_to( "unknown type name 'multiline_identifier'" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 8, 7 ), - 'location_extent': RangeMatcher( filepath, ( 8, 7 ), ( 8, 11 ) ), - 'ranges': contains_exactly( - # FIXME: empty ranges from libclang should be ignored. - RangeMatcher( '', ( 0, 0 ), ( 0, 0 ) ), - RangeMatcher( filepath, ( 8, 7 ), ( 8, 11 ) ) - ), - 'text': equal_to( 'constructor cannot have a return type' ), - 'fixit_available': False + diag_data = BuildRequest( compilation_flags = [ '-x', 'c++' ], + line_num = 7, + contents = contents, + filetype = 'cpp' ) + + event_data = diag_data.copy() + event_data.update( { + 'event_name': 'FileReadyToParse', } ) - ) ) + app.post_json( '/event_notification', event_data ) + results = app.post_json( '/detailed_diagnostic', diag_data ).json + assert_that( results, + has_entry( 'message', contains_string( "\n" ) ) ) -@SharedYcmd -def Diagnostics_Unity_test( app ): - app.post_json( '/load_extra_conf_file', - { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) - for filename in [ 'unity.cc', 'unity.h', 'unitya.cc' ]: - contents = ReadFile( PathToTestFile( filename ) ) + @IsolatedYcmd() + def test_Diagnostics_FixIt_Available( self, app ): + filepath = PathToTestFile( 'FixIt_Clang_cpp11.cpp' ) - event_data = BuildRequest( filepath = PathToTestFile( filename ), - contents = contents, + event_data = BuildRequest( contents = ReadFile( filepath ), event_name = 'FileReadyToParse', - filetype = 'cpp' ) + filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++', + '-std=c++03', + '-Wall', + '-Wextra', + '-pedantic' ] ) + + response = app.post_json( '/event_notification', event_data ).json + + pprint( response ) + + assert_that( response, has_items( + has_entries( { + 'location': LocationMatcher( filepath, 16, 3 ), + 'text': equal_to( 'switch condition type \'A\' ' + 'requires explicit conversion to \'int\'' ), + 'fixit_available': True + } ), + has_entries( { + 'location': LocationMatcher( filepath, 11, 3 ), + 'text': equal_to( + 'explicit conversion functions are a C++11 extension' ), + 'fixit_available': False + } ), + ) ) + + + @IsolatedYcmd() + def test_Diagnostics_MultipleMissingIncludes( self, app ): + filepath = PathToTestFile( 'multiple_missing_includes.cc' ) + + event_data = BuildRequest( contents = ReadFile( filepath ), + event_name = 'FileReadyToParse', + filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ] ) + + response = app.post_json( '/event_notification', event_data ).json + + pprint( response ) + + assert_that( response, has_items( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 1, 10 ), + 'text': equal_to( "'first_missing_include' file not found" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 2, 10 ), + 'text': equal_to( "'second_missing_include' file not found" ), + 'fixit_available': False + } ), + ) ) + + + @IsolatedYcmd() + def test_Diagnostics_LocationExtent_MissingSemicolon( self, app ): + filepath = PathToTestFile( 'location_extent.cc' ) + + event_data = BuildRequest( contents = ReadFile( filepath ), + event_name = 'FileReadyToParse', + filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ] ) + + response = app.post_json( '/event_notification', event_data ).json + + pprint( response ) + + assert_that( response, contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 2, 9 ), + 'location_extent': RangeMatcher( filepath, ( 2, 9 ), ( 2, 9 ) ), + 'ranges': empty(), + 'text': equal_to( "expected ';' at end of declaration list" ), + 'fixit_available': True + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 5, 1 ), + 'location_extent': RangeMatcher( filepath, ( 5, 1 ), ( 6, 11 ) ), + 'ranges': empty(), + 'text': equal_to( "unknown type name 'multiline_identifier'" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 8, 7 ), + 'location_extent': RangeMatcher( filepath, ( 8, 7 ), ( 8, 11 ) ), + 'ranges': contains_exactly( + # FIXME: empty ranges from libclang should be ignored. + RangeMatcher( '', ( 0, 0 ), ( 0, 0 ) ), + RangeMatcher( filepath, ( 8, 7 ), ( 8, 11 ) ) + ), + 'text': equal_to( 'constructor cannot have a return type' ), + 'fixit_available': False + } ) + ) ) + + + @SharedYcmd + def test_Diagnostics_Unity( self, app ): + app.post_json( '/load_extra_conf_file', + { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) + + for filename in [ 'unity.cc', 'unity.h', 'unitya.cc' ]: + contents = ReadFile( PathToTestFile( filename ) ) + + event_data = BuildRequest( filepath = PathToTestFile( filename ), + contents = contents, + event_name = 'FileReadyToParse', + filetype = 'cpp' ) + + response = app.post_json( '/event_notification', event_data ).json + + pprint( response ) + + assert_that( response, contains_inanyorder( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( PathToTestFile( 'unity.h' ), 4, 3 ), + 'location_extent': RangeMatcher( PathToTestFile( 'unity.h' ), + ( 4, 3 ), + ( 4, 14 ) ), + 'ranges': empty(), + 'text': equal_to( "use of undeclared identifier 'fake_method'" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( PathToTestFile( 'unitya.cc' ), 11, 18 ), + 'location_extent': RangeMatcher( PathToTestFile( 'unitya.cc' ), + ( 11, 18 ), + ( 11, 18 ) ), + 'ranges': empty(), + 'text': equal_to( "expected ';' after expression" ), + 'fixit_available': True + } ), + ) ) + + + @SharedYcmd + def test_Diagnostics_CUDA_Kernel( self, app ): + filepath = PathToTestFile( 'cuda', 'kernel_call.cu' ) + + event_data = BuildRequest( filepath = filepath, + contents = ReadFile( filepath ), + event_name = 'FileReadyToParse', + filetype = 'cuda', + compilation_flags = [ '-x', 'cuda', '-nocudainc', + '-nocudalib' ] ) response = app.post_json( '/event_notification', event_data ).json @@ -291,177 +333,133 @@ def Diagnostics_Unity_test( app ): assert_that( response, contains_inanyorder( has_entries( { 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( PathToTestFile( 'unity.h' ), 4, 3 ), - 'location_extent': RangeMatcher( PathToTestFile( 'unity.h' ), - ( 4, 3 ), - ( 4, 14 ) ), + 'location': LocationMatcher( filepath, 59, 5 ), + 'location_extent': RangeMatcher( filepath, ( 59, 5 ), ( 59, 6 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 59, 3 ), ( 59, 5 ) ) ), + 'text': equal_to( "call to global function 'g1' not configured" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 60, 9 ), + 'location_extent': RangeMatcher( filepath, ( 60, 9 ), ( 60, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 60, 5 ), ( 60, 8 ) ) ), + 'text': equal_to( + 'too few execution configuration arguments to kernel function call, ' + 'expected at least 2, have 1' + ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 61, 20 ), + 'location_extent': RangeMatcher( filepath, ( 61, 20 ), ( 61, 21 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 61, 5 ), ( 61, 8 ) ), + RangeMatcher( filepath, ( 61, 20 ), ( 61, 21 ) ) + ), + 'text': equal_to( 'too many execution configuration arguments to ' + 'kernel function call, expected at most 4, have 5' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 65, 15 ), + 'location_extent': RangeMatcher( filepath, ( 65, 15 ), ( 65, 16 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 65, 3 ), ( 65, 5 ) ) ), + 'text': equal_to( "kernel call to non-global function 'h1'" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 68, 15 ), + 'location_extent': RangeMatcher( filepath, ( 68, 15 ), ( 68, 16 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 68, 3 ), ( 68, 5 ) ) ), + 'text': equal_to( "kernel function type 'int (*)(int)' must have " + "void return type" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 70, 8 ), + 'location_extent': RangeMatcher( filepath, ( 70, 8 ), ( 70, 18 ) ), 'ranges': empty(), - 'text': equal_to( "use of undeclared identifier 'fake_method'" ), + 'text': equal_to( "use of undeclared identifier 'undeclared'" ), 'fixit_available': False } ), + ) ) + + + @IsolatedYcmd( { 'max_diagnostics_to_display': 1 } ) + def test_Diagnostics_MaximumDiagnosticsNumberExceeded( self, app ): + filepath = PathToTestFile( 'max_diagnostics.cc' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( contents = contents, + event_name = 'FileReadyToParse', + filetype = 'cpp', + filepath = filepath, + compilation_flags = [ '-x', 'c++' ] ) + + response = app.post_json( '/event_notification', event_data ).json + + pprint( response ) + + assert_that( response, contains_exactly( has_entries( { 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( PathToTestFile( 'unitya.cc' ), 11, 18 ), - 'location_extent': RangeMatcher( PathToTestFile( 'unitya.cc' ), - ( 11, 18 ), - ( 11, 18 ) ), + 'location': LocationMatcher( filepath, 3, 9 ), + 'location_extent': RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ), 'ranges': empty(), - 'text': equal_to( "expected ';' after expression" ), - 'fixit_available': True + 'text': equal_to( "redefinition of 'test'" ), + 'fixit_available': False } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 1, 1 ), + 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) ), + 'text': equal_to( 'Maximum number of diagnostics exceeded.' ), + 'fixit_available': False + } ) ) ) -@SharedYcmd -def Diagnostics_CUDA_Kernel_test( app ): - filepath = PathToTestFile( 'cuda', 'kernel_call.cu' ) - - event_data = BuildRequest( filepath = filepath, - contents = ReadFile( filepath ), - event_name = 'FileReadyToParse', - filetype = 'cuda', - compilation_flags = [ '-x', 'cuda', '-nocudainc', - '-nocudalib' ] ) - - response = app.post_json( '/event_notification', event_data ).json - - pprint( response ) - - assert_that( response, contains_inanyorder( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 59, 5 ), - 'location_extent': RangeMatcher( filepath, ( 59, 5 ), ( 59, 6 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 59, 3 ), ( 59, 5 ) ) ), - 'text': equal_to( "call to global function 'g1' not configured" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 60, 9 ), - 'location_extent': RangeMatcher( filepath, ( 60, 9 ), ( 60, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 60, 5 ), ( 60, 8 ) ) ), - 'text': equal_to( - 'too few execution configuration arguments to kernel function call, ' - 'expected at least 2, have 1' - ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 61, 20 ), - 'location_extent': RangeMatcher( filepath, ( 61, 20 ), ( 61, 21 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 61, 5 ), ( 61, 8 ) ), - RangeMatcher( filepath, ( 61, 20 ), ( 61, 21 ) ) - ), - 'text': equal_to( 'too many execution configuration arguments to kernel ' - 'function call, expected at most 4, have 5' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 65, 15 ), - 'location_extent': RangeMatcher( filepath, ( 65, 15 ), ( 65, 16 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 65, 3 ), ( 65, 5 ) ) ), - 'text': equal_to( "kernel call to non-global function 'h1'" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 68, 15 ), - 'location_extent': RangeMatcher( filepath, ( 68, 15 ), ( 68, 16 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 68, 3 ), ( 68, 5 ) ) ), - 'text': equal_to( "kernel function type 'int (*)(int)' must have " - "void return type" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 70, 8 ), - 'location_extent': RangeMatcher( filepath, ( 70, 8 ), ( 70, 18 ) ), - 'ranges': empty(), - 'text': equal_to( "use of undeclared identifier 'undeclared'" ), - 'fixit_available': False - } ), - ) ) - - -@IsolatedYcmd( { 'max_diagnostics_to_display': 1 } ) -def Diagnostics_MaximumDiagnosticsNumberExceeded_test( app ): - filepath = PathToTestFile( 'max_diagnostics.cc' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( contents = contents, - event_name = 'FileReadyToParse', - filetype = 'cpp', - filepath = filepath, - compilation_flags = [ '-x', 'c++' ] ) - - response = app.post_json( '/event_notification', event_data ).json - - pprint( response ) - - assert_that( response, contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 3, 9 ), - 'location_extent': RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ), - 'ranges': empty(), - 'text': equal_to( "redefinition of 'test'" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 1, 1 ), - 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) ), - 'text': equal_to( 'Maximum number of diagnostics exceeded.' ), - 'fixit_available': False - } ) - ) ) - - -@IsolatedYcmd( { 'max_diagnostics_to_display': 0 } ) -def Diagnostics_NoLimitToNumberOfDiagnostics_test( app ): - filepath = PathToTestFile( 'max_diagnostics.cc' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( contents = contents, - event_name = 'FileReadyToParse', - filetype = 'cpp', - filepath = filepath, - compilation_flags = [ '-x', 'c++' ] ) - - response = app.post_json( '/event_notification', event_data ).json - - pprint( response ) - - assert_that( response, contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 3, 9 ), - 'location_extent': RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ), - 'ranges': empty(), - 'text': equal_to( "redefinition of 'test'" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 4, 9 ), - 'location_extent': RangeMatcher( filepath, ( 4, 9 ), ( 4, 13 ) ), - 'ranges': empty(), - 'text': equal_to( "redefinition of 'test'" ), - 'fixit_available': False - } ) - ) ) + @IsolatedYcmd( { 'max_diagnostics_to_display': 0 } ) + def test_Diagnostics_NoLimitToNumberOfDiagnostics( self, app ): + filepath = PathToTestFile( 'max_diagnostics.cc' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( contents = contents, + event_name = 'FileReadyToParse', + filetype = 'cpp', + filepath = filepath, + compilation_flags = [ '-x', 'c++' ] ) + + response = app.post_json( '/event_notification', event_data ).json + pprint( response ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + assert_that( response, contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 3, 9 ), + 'location_extent': RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ), + 'ranges': empty(), + 'text': equal_to( "redefinition of 'test'" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 4, 9 ), + 'location_extent': RangeMatcher( filepath, ( 4, 9 ), ( 4, 13 ) ), + 'ranges': empty(), + 'text': equal_to( "redefinition of 'test'" ), + 'fixit_available': False + } ) + ) ) diff --git a/ycmd/tests/clang/flags_test.py b/ycmd/tests/clang/flags_test.py index 8449b792f9..71e92d7c31 100644 --- a/ycmd/tests/clang/flags_test.py +++ b/ycmd/tests/clang/flags_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2020 ycmd contributors +# Copyright (C) 2011-2021 ycmd contributors # # This file is part of ycmd. # @@ -17,7 +17,6 @@ import contextlib import os -import pytest from hamcrest import ( assert_that, calling, contains_exactly, @@ -25,12 +24,14 @@ equal_to, raises ) from unittest.mock import patch, MagicMock +from unittest import TestCase from types import ModuleType from ycmd.completers.cpp import flags from ycmd.completers.cpp.flags import ShouldAllowWinStyleFlags, INCLUDE_FLAGS from ycmd.tests.test_utils import ( MacOnly, TemporaryTestDir, WindowsOnly, TemporaryClangProject ) +from ycmd.tests.clang import setUpModule # noqa from ycmd.utils import CLANG_RESOURCE_DIR from ycmd.responses import NoExtraConfDetected @@ -45,1796 +46,1806 @@ def MockExtraConfModule( settings_function ): yield -def FlagsForFile_NothingReturned_test(): - flags_object = flags.Flags() +def _MakeRelativePathsInFlagsAbsoluteTest( test ): + wd = test[ 'wd' ] if 'wd' in test else '/test_not' + assert_that( + flags._MakeRelativePathsInFlagsAbsolute( test[ 'flags' ], wd ), + contains_exactly( *test[ 'expect' ] ) ) - def Settings( **kwargs ): - pass - with MockExtraConfModule( Settings ): - flags_list, filename = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, empty() ) - assert_that( filename, equal_to( '/foo' ) ) +def _AddLanguageFlagWhenAppropriateTester( compiler, language_flag = [] ): + to_removes = [ + [], + [ '/usr/bin/ccache' ], + [ 'some_command', 'another_command' ] + ] + expected = [ '-foo', '-bar' ] + for to_remove in to_removes: + assert_that( [ compiler ] + language_flag + expected, + equal_to( flags._AddLanguageFlagWhenAppropriate( + to_remove + [ compiler ] + expected, + ShouldAllowWinStyleFlags( to_remove + + [ compiler ] + + expected ) ) ) ) -def FlagsForFile_FlagsNotReady_test(): - flags_object = flags.Flags() - def Settings( **kwargs ): - return { - 'flags': [], - 'flags_ready': False - } +class FlagsTest( TestCase ): + def test_FlagsForFile_NothingReturned( self ): + flags_object = flags.Flags() - with MockExtraConfModule( Settings ): - flags_list, filename = flags_object.FlagsForFile( '/foo', False ) - assert_that( list( flags_list ), equal_to( [] ) ) - assert_that( filename, equal_to( '/foo' ) ) + def Settings( **kwargs ): + pass + with MockExtraConfModule( Settings ): + flags_list, filename = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, empty() ) + assert_that( filename, equal_to( '/foo' ) ) -def FlagsForFile_BadNonUnicodeFlagsAreAlsoRemoved_test( *args ): - flags_object = flags.Flags() - def Settings( **kwargs ): - return { - 'flags': [ bytes( b'-c' ), '-c', bytes( b'-foo' ), '-bar' ] - } + def test_FlagsForFile_FlagsNotReady( self ): + flags_object = flags.Flags() - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( list( flags_list ), equal_to( [ '-foo', '-bar' ] ) ) + def Settings( **kwargs ): + return { + 'flags': [], + 'flags_ready': False + } + with MockExtraConfModule( Settings ): + flags_list, filename = flags_object.FlagsForFile( '/foo', False ) + assert_that( list( flags_list ), equal_to( [] ) ) + assert_that( filename, equal_to( '/foo' ) ) -def FlagsForFile_FlagsCachedByDefault_test(): - flags_object = flags.Flags() - def Settings( **kwargs ): - return { - 'flags': [ '-x', 'c' ] - } + def test_FlagsForFile_BadNonUnicodeFlagsAreAlsoRemoved( *args ): + flags_object = flags.Flags() - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, contains_exactly( '-x', 'c' ) ) + def Settings( **kwargs ): + return { + 'flags': [ bytes( b'-c' ), '-c', bytes( b'-foo' ), '-bar' ] + } - def Settings( **kwargs ): - return { - 'flags': [ '-x', 'c++' ] - } + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( list( flags_list ), equal_to( [ '-foo', '-bar' ] ) ) - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, contains_exactly( '-x', 'c' ) ) + def test_FlagsForFile_FlagsCachedByDefault( self ): + flags_object = flags.Flags() -def FlagsForFile_FlagsNotCachedWhenDoCacheIsFalse_test(): - flags_object = flags.Flags() + def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c' ] + } - def Settings( **kwargs ): - return { - 'flags': [ '-x', 'c' ], - 'do_cache': False - } + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, contains_exactly( '-x', 'c' ) ) - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, contains_exactly( '-x', 'c' ) ) + def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c++' ] + } - def Settings( **kwargs ): - return { - 'flags': [ '-x', 'c++' ] - } + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, contains_exactly( '-x', 'c' ) ) - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, contains_exactly( '-x', 'c++' ) ) + def test_FlagsForFile_FlagsNotCachedWhenDoCacheIsFalse( self ): + flags_object = flags.Flags() -def FlagsForFile_FlagsCachedWhenDoCacheIsTrue_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-x', 'c' ], - 'do_cache': True - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, contains_exactly( '-x', 'c' ) ) - - def Settings( **kwargs ): - return { - 'flags': [ '-x', 'c++' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, contains_exactly( '-x', 'c' ) ) - - -def FlagsForFile_DoNotMakeRelativePathsAbsoluteByDefault_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-x', 'c', '-I', 'header' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, - contains_exactly( '-x', 'c', - '-I', 'header' ) ) - - -def FlagsForFile_MakeRelativePathsAbsoluteIfOptionSpecified_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-x', 'c', '-I', 'header' ], - 'include_paths_relative_to_dir': '/working_dir/' - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, - contains_exactly( '-x', 'c', - '-I', os.path.normpath( '/working_dir/header' ) ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path in [ - '/usr/include/c++/v1', - '/System/Library/Frameworks/Foundation.framework/Headers' - ] ) -def FlagsForFile_AddMacIncludePaths_SysRoot_Default_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/include/c++/v1', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path in [ - '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform' - '/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks' - '/Foundation.framework/Headers' - ] ) -def FlagsForFile_AddMacIncludePaths_SysRoot_Xcode_NoStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' - '/usr/local/include', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include', - '-iframework', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' - '/System/Library/Frameworks', - '-iframework', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' - '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path in [ - '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform' - '/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1', - '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform' - '/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks' - '/Foundation.framework/Headers' - ] ) -def FlagsForFile_AddMacIncludePaths_SysRoot_Xcode_WithStdlin_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' - '/usr/include/c++/v1', - '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' - '/usr/local/include', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include', - '-iframework', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' - '/System/Library/Frameworks', - '-iframework', '/Applications/Xcode.app/Contents/Developer/Platforms' - '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' - '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path in [ - '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/usr/include/c++/v1', - '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/System/Library/Frameworks/Foundation.framework/Headers' - ] ) -def FlagsForFile_AddMacIncludePaths_SysRoot_CommandLine_WithStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/usr/include/c++/v1', - '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/usr/local/include', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/usr/include', - '-iframework', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/System/Library/Frameworks', - '-iframework', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path in [ - '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/System/Library/Frameworks/Foundation.framework/Headers' - ] ) -def FlagsForFile_AddMacIncludePaths_SysRoot_CommandLine_NoStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/usr/local/include', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/usr/include', - '-iframework', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/System/Library/Frameworks', - '-iframework', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path in [ - '/path/to/second/sys/root/usr/include/c++/v1' - ] ) -def FlagsForFile_AddMacIncludePaths_Sysroot_Custom_WithStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', - '-isysroot/path/to/first/sys/root', - '-isysroot', '/path/to/second/sys/root/', - '--sysroot=/path/to/third/sys/root', - '--sysroot', '/path/to/fourth/sys/root' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-isysroot/path/to/first/sys/root', - '-isysroot', '/path/to/second/sys/root/', - '--sysroot=/path/to/third/sys/root', - '--sysroot', '/path/to/fourth/sys/root', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/path/to/second/sys/root/usr/include/c++/v1', - '-isystem', '/path/to/second/sys/root/usr/local/include', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/path/to/second/sys/root/usr/include', - '-iframework', '/path/to/second/sys/root/System/Library/Frameworks', - '-iframework', '/path/to/second/sys/root/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: False ) -def FlagsForFile_AddMacIncludePaths_Sysroot_Custom_NoStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', - '-isysroot/path/to/first/sys/root', - '-isysroot', '/path/to/second/sys/root/', - '--sysroot=/path/to/third/sys/root', - '--sysroot', '/path/to/fourth/sys/root' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-isysroot/path/to/first/sys/root', - '-isysroot', '/path/to/second/sys/root/', - '--sysroot=/path/to/third/sys/root', - '--sysroot', '/path/to/fourth/sys/root', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/path/to/second/sys/root/usr/local/include', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/path/to/second/sys/root/usr/include', - '-iframework', '/path/to/second/sys/root/System/Library/Frameworks', - '-iframework', '/path/to/second/sys/root/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path == '/Applications/Xcode.app/Contents/Developer/Toolchains/' - 'XcodeDefault.xctoolchain' ) -def FlagsForFile_AddMacIncludePaths_Toolchain_Xcode_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/Applications/Xcode.app/Contents/Developer/Toolchains' - '/XcodeDefault.xctoolchain/usr/include/c++/v1', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/Applications/Xcode.app/Contents/Developer/Toolchains' - '/XcodeDefault.xctoolchain/usr/include', - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path in [ - '/usr/include/c++/v1', - '/Applications/Xcode.app/Contents/Developer/Toolchains/' - 'XcodeDefault.xctoolchain' - ] ) -def FlagsForFile_AddMacIncludePaths_Toolchain_Xcode_WithSysrootStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/include/c++/v1', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/Applications/Xcode.app/Contents/Developer/Toolchains' - '/XcodeDefault.xctoolchain/usr/include', - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path == '/Library/Developer/CommandLineTools' ) -def FlagsForFile_AddMacIncludePaths_Toolchain_CommandLine_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/Library/Developer/CommandLineTools/usr/include/c++/v1', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/Library/Developer/CommandLineTools/usr/include', - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: - path in [ '/usr/include/c++/v1', '/Library/Developer/CommandLineTools' ] ) -def FlagsForFile_AddMacIncludePaths_Toolchain_CommandLine_SysrootStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/include/c++/v1', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/Library/Developer/CommandLineTools/usr/include', - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: path == '/usr/include/c++/v1' ) -def FlagsForFile_AddMacIncludePaths_ObjCppLanguage_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-x', 'c', '-xobjective-c++' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-x', 'c', - '-xobjective-c++', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/include/c++/v1', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: False ) -def FlagsForFile_AddMacIncludePaths_ObjCppLanguage_NoSysrootStdbib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-x', 'c', '-xobjective-c++' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-x', 'c', - '-xobjective-c++', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: path == '/usr/include/c++/v1' ) -def FlagsForFile_AddMacIncludePaths_CppLanguage_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-x', 'c', '-xc++' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-x', 'c', - '-xc++', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/include/c++/v1', - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: False ) -def FlagsForFile_AddMacIncludePaths_CppLanguage_NoStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-x', 'c', '-xc++' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-x', 'c', - '-xc++', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: False ) -def FlagsForFile_AddMacIncludePaths_CLanguage_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-xc++', '-xc' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-xc++', - '-xc', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: False ) -def FlagsForFile_AddMacIncludePaths_NoLibCpp_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-stdlib=libc++', '-stdlib=libstdc++' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-stdlib=libc++', - '-stdlib=libstdc++', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: False ) -def FlagsForFile_AddMacIncludePaths_NoStandardCppIncludes_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-nostdinc++' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-nostdinc++', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/local/include', - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: False ) -def FlagsForFile_AddMacIncludePaths_NoStandardSystemIncludes_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-nostdinc' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-nostdinc', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: False ) -def FlagsForFile_AddMacIncludePaths_NoBuiltinIncludes_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-nobuiltininc' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-nobuiltininc', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/local/include', - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -@MacOnly -@patch( 'os.path.exists', lambda path: path == '/usr/include/c++/v1' ) -def FlagsForFile_AddMacIncludePaths_NoBuiltinIncludes_SysrootStdlib_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [ '-Wall', '-nobuiltininc' ] - } - - with MockExtraConfModule( Settings ): - flags_list, _ = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly( - '-Wall', - '-nobuiltininc', - '-resource-dir=' + CLANG_RESOURCE_DIR, - '-isystem', '/usr/include/c++/v1', - '-isystem', '/usr/local/include', - '-isystem', '/usr/include', - '-iframework', '/System/Library/Frameworks', - '-iframework', '/Library/Frameworks', - '-fspell-checking' ) ) - - -def FlagsForFile_OverrideTranslationUnit_test(): - flags_object = flags.Flags() - - def Settings( **kwargs ): - return { - 'flags': [], - 'override_filename': 'changed:' + kwargs[ 'filename' ] - } - - with MockExtraConfModule( Settings ): - flags_list, filename = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly() ) - assert_that( filename, equal_to( 'changed:/foo' ) ) - - - def Settings( **kwargs ): - return { - 'flags': [], - 'override_filename': kwargs[ 'filename' ] - } - - with MockExtraConfModule( Settings ): - flags_list, filename = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly() ) - assert_that( filename, equal_to( '/foo' ) ) - - - def Settings( **kwargs ): - return { - 'flags': [], - 'override_filename': None - } - - with MockExtraConfModule( Settings ): - flags_list, filename = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly() ) - assert_that( filename, equal_to( '/foo' ) ) - - - def Settings( **kwargs ): - return { - 'flags': [], - } - - with MockExtraConfModule( Settings ): - flags_list, filename = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly() ) - assert_that( filename, equal_to( '/foo' ) ) - - - def Settings( **kwargs ): - return { - 'flags': [], - 'override_filename': '' - } - - with MockExtraConfModule( Settings ): - flags_list, filename = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly() ) - assert_that( filename, equal_to( '/foo' ) ) - - - def Settings( **kwargs ): - return { - 'flags': [], - 'override_filename': '0' - } + def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c' ], + 'do_cache': False + } - with MockExtraConfModule( Settings ): - flags_list, filename = flags_object.FlagsForFile( '/foo' ) - assert_that( flags_list, contains_exactly() ) - assert_that( filename, equal_to( '0' ) ) + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, contains_exactly( '-x', 'c' ) ) + def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c++' ] + } -def FlagsForFile_Compatibility_KeywordArguments_test(): - flags_object = flags.Flags() + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, contains_exactly( '-x', 'c++' ) ) - def FlagsForFile( filename, **kwargs ): - return { - 'flags': [ '-x', 'c' ] - } - with MockExtraConfModule( FlagsForFile ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, contains_exactly( '-x', 'c' ) ) + def test_FlagsForFile_FlagsCachedWhenDoCacheIsTrue( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c' ], + 'do_cache': True + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, contains_exactly( '-x', 'c' ) ) + + def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c++' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, contains_exactly( '-x', 'c' ) ) + + + def test_FlagsForFile_DoNotMakeRelativePathsAbsoluteByDefault( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c', '-I', 'header' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, + contains_exactly( '-x', 'c', + '-I', 'header' ) ) + + + def test_FlagsForFile_MakeRelativePathsAbsoluteIfOptionSpecified( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-x', 'c', '-I', 'header' ], + 'include_paths_relative_to_dir': '/working_dir/' + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, + contains_exactly( '-x', 'c', + '-I', os.path.normpath( '/working_dir/header' ) ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path in [ + '/usr/include/c++/v1', + '/System/Library/Frameworks/Foundation.framework/Headers' + ] ) + def test_FlagsForFile_AddMacIncludePaths_SysRoot_Default( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/include/c++/v1', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path in [ + '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform' + '/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks' + '/Foundation.framework/Headers' + ] ) + def test_FlagsForFile_AddMacIncludePaths_SysRoot_Xcode_NoStdlib( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' + '/usr/local/include', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include', + '-iframework', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' + '/System/Library/Frameworks', + '-iframework', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' + '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path in [ + '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform' + '/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1', + '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform' + '/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks' + '/Foundation.framework/Headers' + ] ) + def test_FlagsForFile_AddMacIncludePaths_SysRoot_Xcode_WithStdlin( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' + '/usr/include/c++/v1', + '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' + '/usr/local/include', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include', + '-iframework', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' + '/System/Library/Frameworks', + '-iframework', '/Applications/Xcode.app/Contents/Developer/Platforms' + '/MacOSX.platform/Developer/SDKs/MacOSX.sdk' + '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path in [ + '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/usr/include/c++/v1', + '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/System/Library/Frameworks/Foundation.framework/Headers' + ] ) + def test_FlagsForFile_AddMacIncludePaths_SysRoot_CommandLine_WithStdlib( + self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/usr/include/c++/v1', + '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/usr/local/include', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/usr/include', + '-iframework', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/System/Library/Frameworks', + '-iframework', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path in [ + '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/System/Library/Frameworks/Foundation.framework/Headers' + ] ) + def test_FlagsForFile_AddMacIncludePaths_SysRoot_CommandLine_NoStdlib( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/usr/local/include', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/usr/include', + '-iframework', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/System/Library/Frameworks', + '-iframework', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' + '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path in [ + '/path/to/second/sys/root/usr/include/c++/v1' + ] ) + def test_FlagsForFile_AddMacIncludePaths_Sysroot_Custom_WithStdlib( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', + '-isysroot/path/to/first/sys/root', + '-isysroot', '/path/to/second/sys/root/', + '--sysroot=/path/to/third/sys/root', + '--sysroot', '/path/to/fourth/sys/root' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-isysroot/path/to/first/sys/root', + '-isysroot', '/path/to/second/sys/root/', + '--sysroot=/path/to/third/sys/root', + '--sysroot', '/path/to/fourth/sys/root', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/path/to/second/sys/root/usr/include/c++/v1', + '-isystem', '/path/to/second/sys/root/usr/local/include', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/path/to/second/sys/root/usr/include', + '-iframework', '/path/to/second/sys/root/System/Library/Frameworks', + '-iframework', '/path/to/second/sys/root/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: False ) + def test_FlagsForFile_AddMacIncludePaths_Sysroot_Custom_NoStdlib( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', + '-isysroot/path/to/first/sys/root', + '-isysroot', '/path/to/second/sys/root/', + '--sysroot=/path/to/third/sys/root', + '--sysroot', '/path/to/fourth/sys/root' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-isysroot/path/to/first/sys/root', + '-isysroot', '/path/to/second/sys/root/', + '--sysroot=/path/to/third/sys/root', + '--sysroot', '/path/to/fourth/sys/root', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/path/to/second/sys/root/usr/local/include', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/path/to/second/sys/root/usr/include', + '-iframework', '/path/to/second/sys/root/System/Library/Frameworks', + '-iframework', '/path/to/second/sys/root/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path == '/Applications/Xcode.app/Contents/Developer/Toolchains/' + 'XcodeDefault.xctoolchain' ) + def test_FlagsForFile_AddMacIncludePaths_Toolchain_Xcode( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/Applications/Xcode.app/Contents/Developer/Toolchains' + '/XcodeDefault.xctoolchain/usr/include/c++/v1', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/Applications/Xcode.app/Contents/Developer/Toolchains' + '/XcodeDefault.xctoolchain/usr/include', + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path in [ + '/usr/include/c++/v1', + '/Applications/Xcode.app/Contents/Developer/Toolchains/' + 'XcodeDefault.xctoolchain' + ] ) + def test_FlagsForFile_AddMacIncludePaths_Toolchain_Xcode_WithSysrootStdlib( + self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/include/c++/v1', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/Applications/Xcode.app/Contents/Developer/Toolchains' + '/XcodeDefault.xctoolchain/usr/include', + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path == '/Library/Developer/CommandLineTools' ) + def test_FlagsForFile_AddMacIncludePaths_Toolchain_CommandLine( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/Library/Developer/CommandLineTools/usr/include/c++/v1', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/Library/Developer/CommandLineTools/usr/include', + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: + path in [ '/usr/include/c++/v1', '/Library/Developer/CommandLineTools' ] ) + def test_FlagsForFile_AddMacIncludePaths_Toolchain_CommandLine_SysrootStdlib( + self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/include/c++/v1', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/Library/Developer/CommandLineTools/usr/include', + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: path == '/usr/include/c++/v1' ) + def test_FlagsForFile_AddMacIncludePaths_ObjCppLanguage( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-x', 'c', '-xobjective-c++' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-x', 'c', + '-xobjective-c++', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/include/c++/v1', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: False ) + def test_FlagsForFile_AddMacIncludePaths_ObjCppLanguage_NoSysrootStdbib( + self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-x', 'c', '-xobjective-c++' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-x', 'c', + '-xobjective-c++', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: path == '/usr/include/c++/v1' ) + def test_FlagsForFile_AddMacIncludePaths_CppLanguage( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-x', 'c', '-xc++' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-x', 'c', + '-xc++', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/include/c++/v1', + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: False ) + def test_FlagsForFile_AddMacIncludePaths_CppLanguage_NoStdlib( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-x', 'c', '-xc++' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-x', 'c', + '-xc++', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: False ) + def test_FlagsForFile_AddMacIncludePaths_CLanguage( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-xc++', '-xc' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-xc++', + '-xc', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: False ) + def test_FlagsForFile_AddMacIncludePaths_NoLibCpp( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-stdlib=libc++', '-stdlib=libstdc++' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-stdlib=libc++', + '-stdlib=libstdc++', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: False ) + def test_FlagsForFile_AddMacIncludePaths_NoStandardCppIncludes( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-nostdinc++' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-nostdinc++', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/local/include', + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: False ) + def test_FlagsForFile_AddMacIncludePaths_NoStandardSystemIncludes( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-nostdinc' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-nostdinc', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', os.path.join( CLANG_RESOURCE_DIR, 'include' ), + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: False ) + def test_FlagsForFile_AddMacIncludePaths_NoBuiltinIncludes( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-nobuiltininc' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-nobuiltininc', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/local/include', + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + @MacOnly + @patch( 'os.path.exists', lambda path: path == '/usr/include/c++/v1' ) + def test_FlagsForFile_AddMacIncludePaths_NoBuiltinIncludes_SysrootStdlib( + self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [ '-Wall', '-nobuiltininc' ] + } + + with MockExtraConfModule( Settings ): + flags_list, _ = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly( + '-Wall', + '-nobuiltininc', + '-resource-dir=' + CLANG_RESOURCE_DIR, + '-isystem', '/usr/include/c++/v1', + '-isystem', '/usr/local/include', + '-isystem', '/usr/include', + '-iframework', '/System/Library/Frameworks', + '-iframework', '/Library/Frameworks', + '-fspell-checking' ) ) + + + def test_FlagsForFile_OverrideTranslationUnit( self ): + flags_object = flags.Flags() + + def Settings( **kwargs ): + return { + 'flags': [], + 'override_filename': 'changed:' + kwargs[ 'filename' ] + } + + with MockExtraConfModule( Settings ): + flags_list, filename = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly() ) + assert_that( filename, equal_to( 'changed:/foo' ) ) + + + def Settings( **kwargs ): + return { + 'flags': [], + 'override_filename': kwargs[ 'filename' ] + } + + with MockExtraConfModule( Settings ): + flags_list, filename = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly() ) + assert_that( filename, equal_to( '/foo' ) ) + + + def Settings( **kwargs ): + return { + 'flags': [], + 'override_filename': None + } + + with MockExtraConfModule( Settings ): + flags_list, filename = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly() ) + assert_that( filename, equal_to( '/foo' ) ) + + + def Settings( **kwargs ): + return { + 'flags': [], + } + + with MockExtraConfModule( Settings ): + flags_list, filename = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly() ) + assert_that( filename, equal_to( '/foo' ) ) + + + def Settings( **kwargs ): + return { + 'flags': [], + 'override_filename': '' + } + + with MockExtraConfModule( Settings ): + flags_list, filename = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly() ) + assert_that( filename, equal_to( '/foo' ) ) + + + def Settings( **kwargs ): + return { + 'flags': [], + 'override_filename': '0' + } + + with MockExtraConfModule( Settings ): + flags_list, filename = flags_object.FlagsForFile( '/foo' ) + assert_that( flags_list, contains_exactly() ) + assert_that( filename, equal_to( '0' ) ) + + def test_FlagsForFile_Compatibility_KeywordArguments( self ): + flags_object = flags.Flags() + + def FlagsForFile( filename, **kwargs ): + return { + 'flags': [ '-x', 'c' ] + } + + with MockExtraConfModule( FlagsForFile ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, contains_exactly( '-x', 'c' ) ) -def FlagsForFile_Compatibility_NoKeywordArguments_test(): - flags_object = flags.Flags() - def FlagsForFile( filename ): - return { - 'flags': [ '-x', 'c' ] - } + def test_FlagsForFile_Compatibility_NoKeywordArguments( self ): + flags_object = flags.Flags() - with MockExtraConfModule( FlagsForFile ): - flags_list, _ = flags_object.FlagsForFile( '/foo', False ) - assert_that( flags_list, contains_exactly( '-x', 'c' ) ) + def FlagsForFile( filename ): + return { + 'flags': [ '-x', 'c' ] + } + with MockExtraConfModule( FlagsForFile ): + flags_list, _ = flags_object.FlagsForFile( '/foo', False ) + assert_that( flags_list, contains_exactly( '-x', 'c' ) ) -def RemoveUnusedFlags_Passthrough_test(): - compiler_flags = [ '-foo', '-bar' ] - assert_that( flags.RemoveUnusedFlags( - compiler_flags, - 'file', - ShouldAllowWinStyleFlags( compiler_flags ) ), - contains_exactly( '-foo', '-bar' ) ) + def test_RemoveUnusedFlags_Passthrough( self ): + compiler_flags = [ '-foo', '-bar' ] + assert_that( flags.RemoveUnusedFlags( + compiler_flags, + 'file', + ShouldAllowWinStyleFlags( compiler_flags ) ), + contains_exactly( '-foo', '-bar' ) ) -def RemoveUnusedFlags_RemoveDashC_test(): - expected = [ '-foo', '-bar' ] - to_remove = [ '-c' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - to_remove + expected, - filename, - ShouldAllowWinStyleFlags( to_remove + expected ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ -1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ -1: ] ) ) ) ) - - -def RemoveUnusedFlags_RemoveColor_test(): - expected = [ '-foo', '-bar' ] - to_remove = [ '--fcolor-diagnostics' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - to_remove + expected, - filename, - ShouldAllowWinStyleFlags( to_remove + expected ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ -1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ -1: ] ) ) ) ) - - -def RemoveUnusedFlags_RemoveDashO_test(): - expected = [ '-foo', '-bar' ] - to_remove = [ '-o', 'output_name' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - to_remove + expected, - filename, - ShouldAllowWinStyleFlags( to_remove + expected ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ -1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ -1: ] ) ) ) ) - - -def RemoveUnusedFlags_RemoveMP_test(): - expected = [ '-foo', '-bar' ] - to_remove = [ '-MP' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - to_remove + expected, - filename, - ShouldAllowWinStyleFlags( to_remove + expected ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ -1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ -1: ] ) ) ) ) - - -def RemoveUnusedFlags_RemoveFilename_test(): - expected = [ 'foo', '-bar' ] - to_remove = [ 'file' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ -1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ -1: ] ) ) ) ) - - -def RemoveUnusedFlags_RemoveFlagWithoutPrecedingDashFlag_test(): - expected = [ 'g++', '-foo', '-x', 'c++', '-bar', 'include_dir' ] - to_remove = [ 'unrelated_file' ] - filename = 'file' - assert_that( - expected, equal_to( - flags.RemoveUnusedFlags( expected + to_remove, + def test_RemoveUnusedFlags_RemoveDashC( self ): + expected = [ '-foo', '-bar' ] + to_remove = [ '-c' ] + filename = 'file' + + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, filename, ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - - -@WindowsOnly -def RemoveUnusedFlags_RemoveStrayFilenames_CLDriver_test(): - # Only --driver-mode=cl specified. - expected = [ 'g++', '-foo', '--driver-mode=cl', '-xc++', '-bar', - 'include_dir', '/I', 'include_dir_other' ] - to_remove = [ '..' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - - # clang-cl and --driver-mode=cl - expected = [ 'clang-cl.exe', '-foo', '--driver-mode=cl', '-xc++', '-bar', - 'include_dir', '/I', 'include_dir_other' ] - to_remove = [ 'unrelated_file' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - - # clang-cl only - expected = [ 'clang-cl.exe', '-foo', '-xc++', '-bar', - 'include_dir', '/I', 'include_dir_other' ] - to_remove = [ 'unrelated_file' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - - # clang-cl and --driver-mode=gcc - expected = [ 'clang-cl', '-foo', '-xc++', '--driver-mode=gcc', - '-bar', 'include_dir' ] - to_remove = [ 'unrelated_file', '/I', 'include_dir_other' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - - - # cl only with extension - expected = [ 'cl.EXE', '-foo', '-xc++', '-bar', 'include_dir' ] - to_remove = [ '-c', 'path\\to\\unrelated_file' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - - # cl path with Windows separators - expected = [ 'path\\to\\cl', '-foo', '-xc++', '/I', 'path\\to\\include\\dir' ] - to_remove = [ '-c', 'path\\to\\unrelated_file' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - - - -@WindowsOnly -def RemoveUnusedFlags_MultipleDriverModeFlagsWindows_test(): - expected = [ 'g++', - '--driver-mode=cl', - '/Zi', - '-foo', - '--driver-mode=gcc', - '--driver-mode=cl', - 'include_dir' ] - to_remove = [ 'unrelated_file', '/c' ] - filename = 'file' - - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - - flags_expected = [ '/usr/bin/g++', '--driver-mode=cl', '--driver-mode=gcc' ] - flags_all = [ '/usr/bin/g++', - '/Zi', - '--driver-mode=cl', - '/foo', - '--driver-mode=gcc' ] - filename = 'file' - - assert_that( flags_expected, - equal_to( flags.RemoveUnusedFlags( - flags_all, - filename, - ShouldAllowWinStyleFlags( flags_all ) ) ) ) - - -def RemoveUnusedFlags_Depfiles_test(): - full_flags = [ - '/bin/clang', - '-x', 'objective-c', - '-arch', 'armv7', - '-MMD', - '-MT', 'dependencies', - '-MF', 'file', - '--serialize-diagnostics', 'diagnostics' - ] - - expected = [ - '/bin/clang', - '-x', 'objective-c', - '-arch', 'armv7', - ] - - assert_that( flags.RemoveUnusedFlags( full_flags, - 'test.m', - ShouldAllowWinStyleFlags( - full_flags ) ), - contains_exactly( *expected ) ) - - -def EnableTypoCorrection_Empty_test(): - assert_that( flags._EnableTypoCorrection( [] ), - equal_to( [ '-fspell-checking' ] ) ) - - -def EnableTypoCorrection_Trivial_test(): - assert_that( flags._EnableTypoCorrection( [ '-x', 'c++' ] ), - equal_to( [ '-x', 'c++', '-fspell-checking' ] ) ) - - -def EnableTypoCorrection_Reciprocal_test(): - assert_that( flags._EnableTypoCorrection( [ '-fno-spell-checking' ] ), - equal_to( [ '-fno-spell-checking' ] ) ) - + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + to_remove + expected, + filename, + ShouldAllowWinStyleFlags( to_remove + expected ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ -1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ -1: ] ) ) ) ) -def EnableTypoCorrection_ReciprocalOthers_test(): - compile_flags = [ '-x', 'c++', '-fno-spell-checking' ] - assert_that( flags._EnableTypoCorrection( compile_flags ), - equal_to( compile_flags ) ) + def test_RemoveUnusedFlags_RemoveColor( self ): + expected = [ '-foo', '-bar' ] + to_remove = [ '--fcolor-diagnostics' ] + filename = 'file' -@pytest.mark.parametrize( 'flag', INCLUDE_FLAGS ) -def RemoveUnusedFlags_RemoveFilenameWithoutPrecedingInclude_test( flag ): - to_remove = [ '/moo/boo' ] - filename = 'file' - expected = [ 'clang', flag, '/foo/bar', '-isystem/zoo/goo' ] + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + to_remove + expected, + filename, + ShouldAllowWinStyleFlags( to_remove + expected ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ -1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ -1: ] ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected + to_remove, - filename, - ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) - assert_that( expected, - equal_to( flags.RemoveUnusedFlags( - expected[ :1 ] + to_remove + expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected[ :1 ] + - to_remove + - expected[ 1: ] ) ) ) ) - assert_that( expected + expected[ 1: ], - equal_to( - flags.RemoveUnusedFlags( expected + - to_remove + - expected[ 1: ], - filename, - ShouldAllowWinStyleFlags( expected + - to_remove + - expected[ 1: ] ) ) ) ) + def test_RemoveUnusedFlags_RemoveDashO( self ): + expected = [ '-foo', '-bar' ] + to_remove = [ '-o', 'output_name' ] + filename = 'file' -def RemoveXclangFlags_test(): - expected = [ '-I', '/foo/bar', '-DMACRO=Value' ] - to_remove = [ '-Xclang', 'load', '-Xclang', 'libplugin.so', - '-Xclang', '-add-plugin', '-Xclang', 'plugin-name' ] + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + to_remove + expected, + filename, + ShouldAllowWinStyleFlags( to_remove + expected ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ -1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ -1: ] ) ) ) ) - assert_that( expected, - equal_to( flags._RemoveXclangFlags( expected + to_remove ) ) ) - assert_that( expected, - equal_to( flags._RemoveXclangFlags( to_remove + expected ) ) ) + def test_RemoveUnusedFlags_RemoveMP( self ): + expected = [ '-foo', '-bar' ] + to_remove = [ '-MP' ] + filename = 'file' - assert_that( expected + expected, - equal_to( flags._RemoveXclangFlags( expected + - to_remove + - expected ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + to_remove + expected, + filename, + ShouldAllowWinStyleFlags( to_remove + expected ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ -1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ -1: ] ) ) ) ) -def AddLanguageFlagWhenAppropriate_Passthrough_test(): - compiler_flags = [ '-foo', '-bar' ] - assert_that( flags._AddLanguageFlagWhenAppropriate( - compiler_flags, - ShouldAllowWinStyleFlags( compiler_flags ) ), - contains_exactly( '-foo', '-bar' ) ) + def test_RemoveUnusedFlags_RemoveFilename( self ): + expected = [ 'foo', '-bar' ] + to_remove = [ 'file' ] + filename = 'file' + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ -1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ -1: ] ) ) ) ) -@WindowsOnly -def AddLanguageFlagWhenAppropriate_CLDriver_Passthrough_test(): - compiler_flags = [ '-foo', '-bar', '--driver-mode=cl' ] - assert_that( flags._AddLanguageFlagWhenAppropriate( - compiler_flags, - ShouldAllowWinStyleFlags( compiler_flags ) ), - contains_exactly( '-foo', '-bar', '--driver-mode=cl' ) ) + def test_RemoveUnusedFlags_RemoveFlagWithoutPrecedingDashFlag( self ): + expected = [ 'g++', '-foo', '-x', 'c++', '-bar', 'include_dir' ] + to_remove = [ 'unrelated_file' ] + filename = 'file' -def _AddLanguageFlagWhenAppropriateTester( compiler, language_flag = [] ): - to_removes = [ - [], - [ '/usr/bin/ccache' ], - [ 'some_command', 'another_command' ] - ] - expected = [ '-foo', '-bar' ] + assert_that( + expected, equal_to( + flags.RemoveUnusedFlags( expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + + + @WindowsOnly + def test_RemoveUnusedFlags_RemoveStrayFilenames_CLDriver( self ): + # Only --driver-mode=cl specified. + expected = [ 'g++', '-foo', '--driver-mode=cl', '-xc++', '-bar', + 'include_dir', '/I', 'include_dir_other' ] + to_remove = [ '..' ] + filename = 'file' + + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + + # clang-cl and --driver-mode=cl + expected = [ 'clang-cl.exe', '-foo', '--driver-mode=cl', '-xc++', '-bar', + 'include_dir', '/I', 'include_dir_other' ] + to_remove = [ 'unrelated_file' ] + filename = 'file' + + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + + # clang-cl only + expected = [ 'clang-cl.exe', '-foo', '-xc++', '-bar', + 'include_dir', '/I', 'include_dir_other' ] + to_remove = [ 'unrelated_file' ] + filename = 'file' + + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + + # clang-cl and --driver-mode=gcc + expected = [ 'clang-cl', '-foo', '-xc++', '--driver-mode=gcc', + '-bar', 'include_dir' ] + to_remove = [ 'unrelated_file', '/I', 'include_dir_other' ] + filename = 'file' + + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) - for to_remove in to_removes: - assert_that( [ compiler ] + language_flag + expected, - equal_to( flags._AddLanguageFlagWhenAppropriate( - to_remove + [ compiler ] + expected, - ShouldAllowWinStyleFlags( to_remove + - [ compiler ] + - expected ) ) ) ) + # cl only with extension + expected = [ 'cl.EXE', '-foo', '-xc++', '-bar', 'include_dir' ] + to_remove = [ '-c', 'path\\to\\unrelated_file' ] + filename = 'file' -@pytest.mark.parametrize( 'compiler', [ 'cc', 'gcc', 'clang', '/usr/bin/cc', - '/some/other/path', 'some_command' ] ) -def AddLanguageFlagWhenAppropriate_CCompiler_test( compiler ): - _AddLanguageFlagWhenAppropriateTester( compiler ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + + # cl path with Windows separators + expected = [ 'path\\to\\cl', + '-foo', + '-xc++', + '/I', + 'path\\to\\include\\dir' ] + to_remove = [ '-c', 'path\\to\\unrelated_file' ] + filename = 'file' + + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + + + + @WindowsOnly + def test_RemoveUnusedFlags_MultipleDriverModeFlagsWindows( self ): + expected = [ 'g++', + '--driver-mode=cl', + '/Zi', + '-foo', + '--driver-mode=gcc', + '--driver-mode=cl', + 'include_dir' ] + to_remove = [ 'unrelated_file', '/c' ] + filename = 'file' + + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + + flags_expected = [ '/usr/bin/g++', '--driver-mode=cl', '--driver-mode=gcc' ] + flags_all = [ '/usr/bin/g++', + '/Zi', + '--driver-mode=cl', + '/foo', + '--driver-mode=gcc' ] + filename = 'file' + + assert_that( flags_expected, + equal_to( flags.RemoveUnusedFlags( + flags_all, + filename, + ShouldAllowWinStyleFlags( flags_all ) ) ) ) + + + def test_RemoveUnusedFlags_Depfiles( self ): + full_flags = [ + '/bin/clang', + '-x', 'objective-c', + '-arch', 'armv7', + '-MMD', + '-MT', 'dependencies', + '-MF', 'file', + '--serialize-diagnostics', 'diagnostics' + ] + expected = [ + '/bin/clang', + '-x', 'objective-c', + '-arch', 'armv7', + ] -@pytest.mark.parametrize( 'compiler', [ 'c++', 'g++', 'clang++', '/usr/bin/c++', - '/some/other/path++', 'some_command++', - 'c++-5', 'g++-5.1', 'clang++-3.7.3', '/usr/bin/c++-5', - 'c++-5.11', 'g++-50.1.49', 'clang++-3.12.3', '/usr/bin/c++-10', - '/some/other/path++-4.9.3', 'some_command++-5.1', - '/some/other/path++-4.9.31', 'some_command++-5.10' ] ) -def AddLanguageFlagWhenAppropriate_CppCompiler_test( compiler ): - _AddLanguageFlagWhenAppropriateTester( compiler, [ '-x', 'c++' ] ) + assert_that( flags.RemoveUnusedFlags( full_flags, + 'test.m', + ShouldAllowWinStyleFlags( + full_flags ) ), + contains_exactly( *expected ) ) + + + def test_EnableTypoCorrection_Empty( self ): + assert_that( flags._EnableTypoCorrection( [] ), + equal_to( [ '-fspell-checking' ] ) ) + + + def test_EnableTypoCorrection_Trivial( self ): + assert_that( flags._EnableTypoCorrection( [ '-x', 'c++' ] ), + equal_to( [ '-x', 'c++', '-fspell-checking' ] ) ) + + + def test_EnableTypoCorrection_Reciprocal( self ): + assert_that( flags._EnableTypoCorrection( [ '-fno-spell-checking' ] ), + equal_to( [ '-fno-spell-checking' ] ) ) + + + def test_EnableTypoCorrection_ReciprocalOthers( self ): + compile_flags = [ '-x', 'c++', '-fno-spell-checking' ] + assert_that( flags._EnableTypoCorrection( compile_flags ), + equal_to( compile_flags ) ) + + + def test_RemoveUnusedFlags_RemoveFilenameWithoutPrecedingInclude( self ): + for flag in INCLUDE_FLAGS: + with self.subTest( flag = flag ): + to_remove = [ '/moo/boo' ] + filename = 'file' + expected = [ 'clang', flag, '/foo/bar', '-isystem/zoo/goo' ] + + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected + to_remove, + filename, + ShouldAllowWinStyleFlags( expected + to_remove ) ) ) ) + assert_that( expected, + equal_to( flags.RemoveUnusedFlags( + expected[ :1 ] + to_remove + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected[ :1 ] + + to_remove + + expected[ 1: ] ) ) ) ) + assert_that( expected + expected[ 1: ], + equal_to( + flags.RemoveUnusedFlags( expected + + to_remove + + expected[ 1: ], + filename, + ShouldAllowWinStyleFlags( expected + + to_remove + + expected[ 1: ] ) ) ) ) + + + def test_RemoveXclangFlags( self ): + expected = [ '-I', '/foo/bar', '-DMACRO=Value' ] + to_remove = [ '-Xclang', 'load', '-Xclang', 'libplugin.so', + '-Xclang', '-add-plugin', '-Xclang', 'plugin-name' ] + + assert_that( expected, + equal_to( flags._RemoveXclangFlags( expected + to_remove ) ) ) + + assert_that( expected, + equal_to( flags._RemoveXclangFlags( to_remove + expected ) ) ) + + assert_that( expected + expected, + equal_to( flags._RemoveXclangFlags( expected + + to_remove + + expected ) ) ) + + + def test_AddLanguageFlagWhenAppropriate_Passthrough( self ): + compiler_flags = [ '-foo', '-bar' ] + assert_that( flags._AddLanguageFlagWhenAppropriate( + compiler_flags, + ShouldAllowWinStyleFlags( compiler_flags ) ), + contains_exactly( '-foo', '-bar' ) ) + + + @WindowsOnly + def test_AddLanguageFlagWhenAppropriate_CLDriver_Passthrough( self ): + compiler_flags = [ '-foo', '-bar', '--driver-mode=cl' ] + assert_that( flags._AddLanguageFlagWhenAppropriate( + compiler_flags, + ShouldAllowWinStyleFlags( compiler_flags ) ), + contains_exactly( '-foo', '-bar', '--driver-mode=cl' ) ) + + + def test_AddLanguageFlagWhenAppropriate_CCompiler( self ): + for compiler in [ 'cc', 'gcc', 'clang', + '/usr/bin/cc', '/some/other/path', 'some_command' ]: + with self.subTest( compiler = compiler ): + _AddLanguageFlagWhenAppropriateTester( compiler ) + + + def test_AddLanguageFlagWhenAppropriate_CppCompiler( self ): + for compiler in [ 'c++', 'g++', 'clang++', '/usr/bin/c++', + '/some/other/path++', 'some_command++', + 'c++-5', 'g++-5.1', 'clang++-3.7.3', '/usr/bin/c++-5', + 'c++-5.11', 'g++-50.1.49', 'clang++-3.12.3', '/usr/bin/c++-10', + '/some/other/path++-4.9.3', 'some_command++-5.1', + '/some/other/path++-4.9.31', 'some_command++-5.10' ]: + with self.subTest( compiler = compiler ): + _AddLanguageFlagWhenAppropriateTester( compiler, [ '-x', 'c++' ] ) + + + def test_CompilationDatabase_NoDatabase( self ): + with TemporaryTestDir() as tmp_dir: + assert_that( + calling( flags.Flags().FlagsForFile ).with_args( + os.path.join( tmp_dir, 'test.cc' ) ), + raises( NoExtraConfDetected ) ) -def CompilationDatabase_NoDatabase_test(): - with TemporaryTestDir() as tmp_dir: - assert_that( - calling( flags.Flags().FlagsForFile ).with_args( - os.path.join( tmp_dir, 'test.cc' ) ), - raises( NoExtraConfDetected ) ) + def test_CompilationDatabase_FileNotInDatabase( self ): + compile_commands = [] + with TemporaryTestDir() as tmp_dir: + with TemporaryClangProject( tmp_dir, compile_commands ): + assert_that( flags.Flags().FlagsForFile( + os.path.join( tmp_dir, 'test.cc' ) ), + equal_to( ( [], os.path.join( tmp_dir, 'test.cc' ) ) ) ) + + + def test_CompilationDatabase_InvalidDatabase( self ): + with TemporaryTestDir() as tmp_dir: + with TemporaryClangProject( tmp_dir, 'this is junk' ): + assert_that( + calling( flags.Flags().FlagsForFile ).with_args( + os.path.join( tmp_dir, 'test.cc' ) ), + raises( NoExtraConfDetected ) ) + + + def test_CompilationDatabase_UseFlagsFromDatabase( self ): + with TemporaryTestDir() as tmp_dir: + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -I. -I/absolute/path -Wall', + 'file': os.path.join( tmp_dir, 'test.cc' ), + }, + ] + with TemporaryClangProject( tmp_dir, compile_commands ): + assert_that( + flags.Flags().FlagsForFile( + os.path.join( tmp_dir, 'test.cc' ), + add_extra_clang_flags = False )[ 0 ], + contains_exactly( 'clang++', + '-x', + 'c++', + '--driver-mode=g++', + '-x', + 'c++', + '-I' + os.path.normpath( tmp_dir ), + '-I' + os.path.normpath( '/absolute/path' ), + '-Wall' ) ) + + + def test_CompilationDatabase_UseFlagsFromSameDir( self ): + with TemporaryTestDir() as tmp_dir: + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -Wall', + 'file': os.path.join( tmp_dir, 'test.cc' ), + }, + ] + with TemporaryClangProject( tmp_dir, compile_commands ): + f = flags.Flags() + + # If we ask for a file that is not in the DB but is in the same + # directory of another file present in the DB, we get its flags. + assert_that( + f.FlagsForFile( + os.path.join( tmp_dir, 'test1.cc' ), + add_extra_clang_flags = False ), + contains_exactly( + contains_exactly( 'clang++', + '-x', + 'c++', + '--driver-mode=g++', + '-Wall' ), + os.path.join( tmp_dir, 'test1.cc' ) + ) + ) -def CompilationDatabase_FileNotInDatabase_test(): - compile_commands = [] - with TemporaryTestDir() as tmp_dir: - with TemporaryClangProject( tmp_dir, compile_commands ): - assert_that( flags.Flags().FlagsForFile( - os.path.join( tmp_dir, 'test.cc' ) ), - equal_to( ( [], os.path.join( tmp_dir, 'test.cc' ) ) ) ) + # If we ask for a file that is not in the DB but in a subdirectory + # of another file present in the DB, we get its flags. + assert_that( + f.FlagsForFile( + os.path.join( tmp_dir, 'some_dir', 'test1.cc' ), + add_extra_clang_flags = False ), + contains_exactly( + contains_exactly( 'clang++', + '-x', + 'c++', + '--driver-mode=g++', + '-Wall' ), + os.path.join( tmp_dir, 'some_dir', 'test1.cc' ) + ) + ) -def CompilationDatabase_InvalidDatabase_test(): - with TemporaryTestDir() as tmp_dir: - with TemporaryClangProject( tmp_dir, 'this is junk' ): - assert_that( - calling( flags.Flags().FlagsForFile ).with_args( - os.path.join( tmp_dir, 'test.cc' ) ), - raises( NoExtraConfDetected ) ) + def test_CompilationDatabase_HeaderFile_SameNameAsSourceFile( self ): + with TemporaryTestDir() as tmp_dir: + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -Wall', + 'file': os.path.join( tmp_dir, 'test.cc' ), + }, + ] + with TemporaryClangProject( tmp_dir, compile_commands ): + # If we ask for a header file with the same name as a source file, it + # returns the flags of that cc file (and a special language flag for C++ + # headers). + assert_that( + flags.Flags().FlagsForFile( + os.path.join( tmp_dir, 'test.h' ), + add_extra_clang_flags = False )[ 0 ], + contains_exactly( 'clang++', + '-x', + 'c++', + '--driver-mode=g++', + '-Wall', + '-x', + 'c++-header' ) ) -def CompilationDatabase_UseFlagsFromDatabase_test(): - with TemporaryTestDir() as tmp_dir: - compile_commands = [ - { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -I. -I/absolute/path -Wall', - 'file': os.path.join( tmp_dir, 'test.cc' ), - }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - assert_that( - flags.Flags().FlagsForFile( - os.path.join( tmp_dir, 'test.cc' ), - add_extra_clang_flags = False )[ 0 ], - contains_exactly( 'clang++', - '-x', - 'c++', - '--driver-mode=g++', - '-x', - 'c++', - '-I' + os.path.normpath( tmp_dir ), - '-I' + os.path.normpath( '/absolute/path' ), - '-Wall' ) ) - - -def CompilationDatabase_UseFlagsFromSameDir_test(): - with TemporaryTestDir() as tmp_dir: - compile_commands = [ - { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -Wall', - 'file': os.path.join( tmp_dir, 'test.cc' ), - }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - f = flags.Flags() + def test_CompilationDatabase_HeaderFile_DifferentNameFromSourceFile( self ): + with TemporaryTestDir() as tmp_dir: + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -Wall', + 'file': os.path.join( tmp_dir, 'test.cc' ), + }, + ] - # If we ask for a file that is not in the DB but is in the same directory - # of another file present in the DB, we get its flags. - assert_that( - f.FlagsForFile( - os.path.join( tmp_dir, 'test1.cc' ), - add_extra_clang_flags = False ), - contains_exactly( + with TemporaryClangProject( tmp_dir, compile_commands ): + # Even if we ask for a header file with a different name than the source + # file, it still returns the flags from the cc file (and a special + # language flag for C++ headers). + assert_that( + flags.Flags().FlagsForFile( + os.path.join( tmp_dir, 'not_in_the_db.h' ), + add_extra_clang_flags = False )[ 0 ], contains_exactly( 'clang++', '-x', 'c++', '--driver-mode=g++', - '-Wall' ), - os.path.join( tmp_dir, 'test1.cc' ) - ) - ) - - # If we ask for a file that is not in the DB but in a subdirectory - # of another file present in the DB, we get its flags. - assert_that( - f.FlagsForFile( - os.path.join( tmp_dir, 'some_dir', 'test1.cc' ), - add_extra_clang_flags = False ), - contains_exactly( + '-Wall', + '-x', + 'c++-header' ) ) + + + def test_CompilationDatabase_ExplicitHeaderFileEntry( self ): + with TemporaryTestDir() as tmp_dir: + # Have an explicit header file entry which should take priority over the + # corresponding source file + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -I. -I/absolute/path -Wall', + 'file': os.path.join( tmp_dir, 'test.cc' ), + }, + { + 'directory': tmp_dir, + 'command': 'clang++ -I/absolute/path -Wall', + 'file': os.path.join( tmp_dir, 'test.h' ), + }, + ] + with TemporaryClangProject( tmp_dir, compile_commands ): + assert_that( + flags.Flags().FlagsForFile( + os.path.join( tmp_dir, 'test.h' ), + add_extra_clang_flags = False )[ 0 ], contains_exactly( 'clang++', '-x', 'c++', '--driver-mode=g++', - '-Wall' ), - os.path.join( tmp_dir, 'some_dir', 'test1.cc' ) - ) - ) + '-I' + os.path.normpath( '/absolute/path' ), + '-Wall' ) ) + + + def test_CompilationDatabase_CUDALanguageFlags( self ): + with TemporaryTestDir() as tmp_dir: + compile_commands = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -Wall ./test.cu', + 'file': os.path.join( tmp_dir, 'test.cu' ), + }, + ] + + with TemporaryClangProject( tmp_dir, compile_commands ): + # If we ask for a header file, it returns the equivalent cu file + assert_that( + flags.Flags().FlagsForFile( + os.path.join( tmp_dir, 'test.cuh' ), + add_extra_clang_flags = False )[ 0 ], + contains_exactly( 'clang++', + '--driver-mode=g++', + '-Wall', + '-x', + 'cuda' ) ) -def CompilationDatabase_HeaderFile_SameNameAsSourceFile_test(): - with TemporaryTestDir() as tmp_dir: - compile_commands = [ + def test_MakeRelativePathsInFlagsAbsolute( self ): + for test in [ + # Already absolute, positional arguments { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -Wall', - 'file': os.path.join( tmp_dir, 'test.cc' ), + 'flags': [ '-isystem', '/test' ], + 'expect': [ '-isystem', os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-I', '/test' ], + 'expect': [ '-I', os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-iquote', '/test' ], + 'expect': [ '-iquote', os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-isysroot', '/test' ], + 'expect': [ '-isysroot', os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-include-pch', '/test' ], + 'expect': [ '-include-pch', os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-idirafter', '/test' ], + 'expect': [ '-idirafter', os.path.normpath( '/test' ) ], }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - # If we ask for a header file with the same name as a source file, it - # returns the flags of that cc file (and a special language flag for C++ - # headers). - assert_that( - flags.Flags().FlagsForFile( - os.path.join( tmp_dir, 'test.h' ), - add_extra_clang_flags = False )[ 0 ], - contains_exactly( 'clang++', - '-x', - 'c++', - '--driver-mode=g++', - '-Wall', - '-x', - 'c++-header' ) ) - - -def CompilationDatabase_HeaderFile_DifferentNameFromSourceFile_test(): - with TemporaryTestDir() as tmp_dir: - compile_commands = [ + # Already absolute, single arguments { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -Wall', - 'file': os.path.join( tmp_dir, 'test.cc' ), + 'flags': [ '-isystem/test' ], + 'expect': [ '-isystem' + os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-I/test' ], + 'expect': [ '-I' + os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-iquote/test' ], + 'expect': [ '-iquote' + os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-isysroot/test' ], + 'expect': [ '-isysroot' + os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-include-pch/test' ], + 'expect': [ '-include-pch' + os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '-idirafter/test' ], + 'expect': [ '-idirafter' + os.path.normpath( '/test' ) ], }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - # Even if we ask for a header file with a different name than the source - # file, it still returns the flags from the cc file (and a special - # language flag for C++ headers). - assert_that( - flags.Flags().FlagsForFile( - os.path.join( tmp_dir, 'not_in_the_db.h' ), - add_extra_clang_flags = False )[ 0 ], - contains_exactly( 'clang++', - '-x', - 'c++', - '--driver-mode=g++', - '-Wall', - '-x', - 'c++-header' ) ) - - -def CompilationDatabase_ExplicitHeaderFileEntry_test(): - with TemporaryTestDir() as tmp_dir: - # Have an explicit header file entry which should take priority over the - # corresponding source file - compile_commands = [ + + # Already absolute, double-dash arguments { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -I. -I/absolute/path -Wall', - 'file': os.path.join( tmp_dir, 'test.cc' ), + 'flags': [ '--isystem=/test' ], + 'expect': [ '--isystem=/test' ], }, { - 'directory': tmp_dir, - 'command': 'clang++ -I/absolute/path -Wall', - 'file': os.path.join( tmp_dir, 'test.h' ), + 'flags': [ '--I=/test' ], + 'expect': [ '--I=/test' ], }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - assert_that( - flags.Flags().FlagsForFile( - os.path.join( tmp_dir, 'test.h' ), - add_extra_clang_flags = False )[ 0 ], - contains_exactly( 'clang++', - '-x', - 'c++', - '--driver-mode=g++', - '-I' + os.path.normpath( '/absolute/path' ), - '-Wall' ) ) - - -def CompilationDatabase_CUDALanguageFlags_test(): - with TemporaryTestDir() as tmp_dir: - compile_commands = [ { - 'directory': tmp_dir, - 'command': 'clang++ -Wall ./test.cu', - 'file': os.path.join( tmp_dir, 'test.cu' ), + 'flags': [ '--iquote=/test' ], + 'expect': [ '--iquote=/test' ], + }, + { + 'flags': [ '--sysroot=/test' ], + 'expect': [ '--sysroot=' + os.path.normpath( '/test' ) ], + }, + { + 'flags': [ '--include-pch=/test' ], + 'expect': [ '--include-pch=/test' ], + }, + { + 'flags': [ '--idirafter=/test' ], + 'expect': [ '--idirafter=/test' ], }, - ] - with TemporaryClangProject( tmp_dir, compile_commands ): - # If we ask for a header file, it returns the equivalent cu file - assert_that( - flags.Flags().FlagsForFile( - os.path.join( tmp_dir, 'test.cuh' ), - add_extra_clang_flags = False )[ 0 ], - contains_exactly( 'clang++', - '--driver-mode=g++', - '-Wall', - '-x', - 'cuda' ) ) + # Relative, positional arguments + { + 'flags': [ '-isystem', 'test' ], + 'expect': [ '-isystem', os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-I', 'test' ], + 'expect': [ '-I', os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-iquote', 'test' ], + 'expect': [ '-iquote', os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-isysroot', 'test' ], + 'expect': [ '-isysroot', os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-include-pch', 'test' ], + 'expect': [ '-include-pch', os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-idirafter', 'test' ], + 'expect': [ '-idirafter', os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + # Relative, single arguments + { + 'flags': [ '-isystemtest' ], + 'expect': [ '-isystem' + os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-Itest' ], + 'expect': [ '-I' + os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-iquotetest' ], + 'expect': [ '-iquote' + os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-isysroottest' ], + 'expect': [ '-isysroot' + os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-include-pchtest' ], + 'expect': [ '-include-pch' + os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '-idiraftertest' ], + 'expect': [ '-idirafter' + os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, -def _MakeRelativePathsInFlagsAbsoluteTest( test ): - wd = test[ 'wd' ] if 'wd' in test else '/not_test' - assert_that( - flags._MakeRelativePathsInFlagsAbsolute( test[ 'flags' ], wd ), - contains_exactly( *test[ 'expect' ] ) ) + # Already absolute, double-dash arguments + { + 'flags': [ '--isystem=test' ], + 'expect': [ '--isystem=test' ], + 'wd': '/test', + }, + { + 'flags': [ '--I=test' ], + 'expect': [ '--I=test' ], + 'wd': '/test', + }, + { + 'flags': [ '--iquote=test' ], + 'expect': [ '--iquote=test' ], + 'wd': '/test', + }, + { + 'flags': [ '--sysroot=test' ], + 'expect': [ '--sysroot=' + os.path.normpath( '/test/test' ) ], + 'wd': '/test', + }, + { + 'flags': [ '--include-pch=test' ], + 'expect': [ '--include-pch=test' ], + 'wd': '/test', + }, + { + 'flags': [ '--idirafter=test' ], + 'expect': [ '--idirafter=test' ], + 'wd': '/test', + }, + ]: + with self.subTest( test = test ): + _MakeRelativePathsInFlagsAbsoluteTest( test ) -@pytest.mark.parametrize( 'test', [ - # Already absolute, positional arguments - { - 'flags': [ '-isystem', '/test' ], - 'expect': [ '-isystem', os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-I', '/test' ], - 'expect': [ '-I', os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-iquote', '/test' ], - 'expect': [ '-iquote', os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-isysroot', '/test' ], - 'expect': [ '-isysroot', os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-include-pch', '/test' ], - 'expect': [ '-include-pch', os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-idirafter', '/test' ], - 'expect': [ '-idirafter', os.path.normpath( '/test' ) ], - }, - - # Already absolute, single arguments - { - 'flags': [ '-isystem/test' ], - 'expect': [ '-isystem' + os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-I/test' ], - 'expect': [ '-I' + os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-iquote/test' ], - 'expect': [ '-iquote' + os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-isysroot/test' ], - 'expect': [ '-isysroot' + os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-include-pch/test' ], - 'expect': [ '-include-pch' + os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '-idirafter/test' ], - 'expect': [ '-idirafter' + os.path.normpath( '/test' ) ], - }, - - - # Already absolute, double-dash arguments - { - 'flags': [ '--isystem=/test' ], - 'expect': [ '--isystem=/test' ], - }, - { - 'flags': [ '--I=/test' ], - 'expect': [ '--I=/test' ], - }, - { - 'flags': [ '--iquote=/test' ], - 'expect': [ '--iquote=/test' ], - }, - { - 'flags': [ '--sysroot=/test' ], - 'expect': [ '--sysroot=' + os.path.normpath( '/test' ) ], - }, - { - 'flags': [ '--include-pch=/test' ], - 'expect': [ '--include-pch=/test' ], - }, - { - 'flags': [ '--idirafter=/test' ], - 'expect': [ '--idirafter=/test' ], - }, - - # Relative, positional arguments - { - 'flags': [ '-isystem', 'test' ], - 'expect': [ '-isystem', os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-I', 'test' ], - 'expect': [ '-I', os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-iquote', 'test' ], - 'expect': [ '-iquote', os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-isysroot', 'test' ], - 'expect': [ '-isysroot', os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-include-pch', 'test' ], - 'expect': [ '-include-pch', os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-idirafter', 'test' ], - 'expect': [ '-idirafter', os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - - # Relative, single arguments - { - 'flags': [ '-isystemtest' ], - 'expect': [ '-isystem' + os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-Itest' ], - 'expect': [ '-I' + os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-iquotetest' ], - 'expect': [ '-iquote' + os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-isysroottest' ], - 'expect': [ '-isysroot' + os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-include-pchtest' ], - 'expect': [ '-include-pch' + os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '-idiraftertest' ], - 'expect': [ '-idirafter' + os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - - # Already absolute, double-dash arguments - { - 'flags': [ '--isystem=test' ], - 'expect': [ '--isystem=test' ], - 'wd': '/test', - }, - { - 'flags': [ '--I=test' ], - 'expect': [ '--I=test' ], - 'wd': '/test', - }, - { - 'flags': [ '--iquote=test' ], - 'expect': [ '--iquote=test' ], - 'wd': '/test', - }, - { - 'flags': [ '--sysroot=test' ], - 'expect': [ '--sysroot=' + os.path.normpath( '/test/test' ) ], - 'wd': '/test', - }, - { - 'flags': [ '--include-pch=test' ], - 'expect': [ '--include-pch=test' ], - 'wd': '/test', - }, - { - 'flags': [ '--idirafter=test' ], - 'expect': [ '--idirafter=test' ], - 'wd': '/test', - }, - ] ) -def MakeRelativePathsInFlagsAbsolute_test( test ): - _MakeRelativePathsInFlagsAbsoluteTest( test ) - - -@pytest.mark.parametrize( 'test', [ - { - 'flags': [ - 'ignored', - '-isystem', - '/test', - '-ignored', - '-I', - '/test', - '--ignored=ignored' - ], - 'expect': [ - 'ignored', - '-isystem', os.path.normpath( '/test' ), - '-ignored', - '-I', os.path.normpath( '/test' ), - '--ignored=ignored' - ] - }, - { - 'flags': [ - 'ignored', - '-isystem/test', - '-ignored', - '-I/test', - '--ignored=ignored' - ], - 'expect': [ - 'ignored', - '-isystem' + os.path.normpath( '/test' ), - '-ignored', - '-I' + os.path.normpath( '/test/' ), - '--ignored=ignored' - ] - }, - { - 'flags': [ - 'ignored', - '--isystem=/test', - '-ignored', - '--I=/test', - '--ignored=ignored' - ], - 'expect': [ - 'ignored', - '--isystem=/test', - '-ignored', - '--I=/test', - '--ignored=ignored' - ] - }, - { - 'flags': [ - 'ignored', - '-isystem', 'test', - '-ignored', - '-I', 'test', - '--ignored=ignored' - ], - 'expect': [ - 'ignored', - '-isystem', os.path.normpath( '/test/test' ), - '-ignored', - '-I', os.path.normpath( '/test/test' ), - '--ignored=ignored' - ], - 'wd': '/test', - }, - { - 'flags': [ - 'ignored', - '-isystemtest', - '-ignored', - '-Itest', - '--ignored=ignored' - ], - 'expect': [ - 'ignored', - '-isystem' + os.path.normpath( '/test/test' ), - '-ignored', - '-I' + os.path.normpath( '/test/test' ), - '--ignored=ignored' - ], - 'wd': '/test', - }, - { - 'flags': [ - 'ignored', - '--isystem=test', - '-ignored', - '--I=test', - '--ignored=ignored', - '--sysroot=test' - ], - 'expect': [ - 'ignored', - '--isystem=test', - '-ignored', - '--I=test', - '--ignored=ignored', - '--sysroot=' + os.path.normpath( '/test/test' ), - ], - 'wd': '/test', - }, - ] ) -def MakeRelativePathsInFlagsAbsolute_IgnoreUnknown_test( test ): - _MakeRelativePathsInFlagsAbsoluteTest( test ) - - -def MakeRelativePathsInFlagsAbsolute_NoWorkingDir_test(): - _MakeRelativePathsInFlagsAbsoluteTest( { - 'flags': [ 'list', 'of', 'flags', 'not', 'changed', '-Itest' ], - 'expect': [ 'list', 'of', 'flags', 'not', 'changed', '-Itest' ], - 'wd': '' - } ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + def test_MakeRelativePathsInFlagsAbsolute_IgnoreUnknown( self ): + for test in [ + { + 'flags': [ + 'ignored', + '-isystem', + '/test', + '-ignored', + '-I', + '/test', + '--ignored=ignored' + ], + 'expect': [ + 'ignored', + '-isystem', os.path.normpath( '/test' ), + '-ignored', + '-I', os.path.normpath( '/test' ), + '--ignored=ignored' + ] + }, + { + 'flags': [ + 'ignored', + '-isystem/test', + '-ignored', + '-I/test', + '--ignored=ignored' + ], + 'expect': [ + 'ignored', + '-isystem' + os.path.normpath( '/test' ), + '-ignored', + '-I' + os.path.normpath( '/test/' ), + '--ignored=ignored' + ] + }, + { + 'flags': [ + 'ignored', + '--isystem=/test', + '-ignored', + '--I=/test', + '--ignored=ignored' + ], + 'expect': [ + 'ignored', + '--isystem=/test', + '-ignored', + '--I=/test', + '--ignored=ignored' + ] + }, + { + 'flags': [ + 'ignored', + '-isystem', 'test', + '-ignored', + '-I', 'test', + '--ignored=ignored' + ], + 'expect': [ + 'ignored', + '-isystem', os.path.normpath( '/test/test' ), + '-ignored', + '-I', os.path.normpath( '/test/test' ), + '--ignored=ignored' + ], + 'wd': '/test', + }, + { + 'flags': [ + 'ignored', + '-isystemtest', + '-ignored', + '-Itest', + '--ignored=ignored' + ], + 'expect': [ + 'ignored', + '-isystem' + os.path.normpath( '/test/test' ), + '-ignored', + '-I' + os.path.normpath( '/test/test' ), + '--ignored=ignored' + ], + 'wd': '/test', + }, + { + 'flags': [ + 'ignored', + '--isystem=test', + '-ignored', + '--I=test', + '--ignored=ignored', + '--sysroot=test' + ], + 'expect': [ + 'ignored', + '--isystem=test', + '-ignored', + '--I=test', + '--ignored=ignored', + '--sysroot=' + os.path.normpath( '/test/test' ), + ], + 'wd': '/test', + }, + ]: + with self.subTest( test = test ): + _MakeRelativePathsInFlagsAbsoluteTest( test ) + + + def test_MakeRelativePathsInFlagsAbsolute_NoWorkingDir( self ): + _MakeRelativePathsInFlagsAbsoluteTest( { + 'flags': [ 'list', 'of', 'flags', 'not', 'changed', '-Itest' ], + 'expect': [ 'list', 'of', 'flags', 'not', 'changed', '-Itest' ], + 'wd': '' + } ) diff --git a/ycmd/tests/clang/get_completions_test.py b/ycmd/tests/clang/get_completions_test.py index 2612d380dc..60b64accd2 100644 --- a/ycmd/tests/clang/get_completions_test.py +++ b/ycmd/tests/clang/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -18,6 +18,7 @@ import json import requests from unittest.mock import patch +from unittest import TestCase from hamcrest import ( all_of, assert_that, contains_exactly, @@ -35,10 +36,11 @@ NO_COMPILE_FLAGS_MESSAGE, PARSING_FILE_MESSAGE ) from ycmd.responses import UnknownExtraConf, NoExtraConfDetected -from ycmd.tests.clang import ( IsolatedYcmd, +from ycmd.tests.clang import ( IsolatedYcmd, # noqa MockCoreClangCompleter, PathToTestFile, - SharedYcmd ) + SharedYcmd, + setUpModule ) from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, CombineRequest, @@ -111,221 +113,187 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def GetCompletions_ForcedWithNoTrigger_test( app ): - RunTest( app, { - 'description': 'semantic completion with force query=DO_SO', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_cpp.cc' ), - 'line_num' : 54, - 'column_num': 8, - 'extra_conf_data': { '&filetype': 'cpp' }, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'DO_SOMETHING_TO', 'void' ), - CompletionEntryMatcher( 'DO_SOMETHING_WITH', 'void' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -# This test is isolated to make sure we trigger c hook for clangd, instead of -# fetching completer from cache. -@IsolatedYcmd() -def GetCompletions_Fallback_NoSuggestions_test( app ): - RunTest( app, { - 'description': 'Triggered, fallback but no query so no completions', - 'request': { - 'filetype' : 'c', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 29, - 'column_num': 21, - 'extra_conf_data': { '&filetype': 'c' }, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': has_item( NO_COMPLETIONS_ERROR ), - } ) - }, - } ) +class GetCompletionsTest( TestCase ): + @SharedYcmd + def test_GetCompletions_ForcedWithNoTrigger( self, app ): + RunTest( app, { + 'description': 'semantic completion with force query=DO_SO', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_cpp.cc' ), + 'line_num' : 54, + 'column_num': 8, + 'extra_conf_data': { '&filetype': 'cpp' }, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'DO_SOMETHING_TO', 'void' ), + CompletionEntryMatcher( 'DO_SOMETHING_WITH', 'void' ), + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_Fallback_NoSuggestions_MinimumCharacters_test( app ): - RunTest( app, { - 'description': 'fallback general completion obeys min chars setting ' - ' (query="a")', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 29, - 'column_num': 22, - 'extra_conf_data': { '&filetype': 'c' }, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': has_item( NO_COMPLETIONS_ERROR ), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Fallback_Suggestions_test( app ): - RunTest( app, { - 'description': '. after macro with some query text (.a_)', - 'request': { - 'filetype' : 'c', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 29, - 'column_num': 23, - 'extra_conf_data': { '&filetype': 'c' }, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( CompletionEntryMatcher( 'a_parameter', - '[ID]' ) ), - 'errors': has_item( NO_COMPLETIONS_ERROR ), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Fallback_Exception_test( app ): - # extra conf throws exception - RunTest( app, { - 'description': '. on struct returns identifier because of error', - 'request': { - 'filetype' : 'c', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 62, - 'column_num': 20, - 'extra_conf_data': { '&filetype': 'c', 'throw': 'testy' }, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'a_parameter', '[ID]' ), - CompletionEntryMatcher( 'another_parameter', '[ID]' ), - ), - 'errors': has_item( ErrorMatcher( ValueError, 'testy' ) ) - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Forced_NoFallback_test( app ): - RunTest( app, { - 'description': '-> after macro with forced semantic', - 'request': { - 'filetype' : 'c', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 41, - 'column_num': 30, - 'extra_conf_data': { '&filetype': 'c' }, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': NO_COMPLETIONS_ERROR, - }, - } ) - - -@SharedYcmd -def GetCompletions_FilteredNoResults_Fallback_test( app ): - # no errors because the semantic completer returned results, but they - # were filtered out by the query, so this is considered working OK - # (whereas no completions from the semantic engine is considered an - # error) - RunTest( app, { - 'description': '. on struct returns IDs after query=do_', - 'request': { - 'filetype': 'c', - 'filepath': PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num': 71, - 'column_num': 18, - 'extra_conf_data': { '&filetype': 'c' }, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - # do_ is an identifier because it is already in the file when we - # load it - CompletionEntryMatcher( 'do_', '[ID]' ), - CompletionEntryMatcher( 'do_something', '[ID]' ), - CompletionEntryMatcher( 'do_another_thing', '[ID]' ), - CompletionEntryMatcher( 'DO_SOMETHING_TO', '[ID]' ), - CompletionEntryMatcher( 'DO_SOMETHING_VIA', '[ID]' ) - ), - 'errors': empty() - } ) - }, - } ) + # This test is isolated to make sure we trigger c hook for clangd, instead of + # fetching completer from cache. + @IsolatedYcmd() + def test_GetCompletions_Fallback_NoSuggestions( self, app ): + RunTest( app, { + 'description': 'Triggered, fallback but no query so no completions', + 'request': { + 'filetype' : 'c', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 29, + 'column_num': 21, + 'extra_conf_data': { '&filetype': 'c' }, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': has_item( NO_COMPLETIONS_ERROR ), + } ) + }, + } ) -@IsolatedYcmd() -def GetCompletions_WorksWithExplicitFlags_test( app ): - app.post_json( - '/ignore_extra_conf_file', - { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) - contents = """ -struct Foo { - int x; - int y; - char c; -}; + @SharedYcmd + def test_GetCompletions_Fallback_NoSuggestions_MinimumCharacters( self, app ): + RunTest( app, { + 'description': 'fallback general completion obeys min chars setting ' + ' (query="a")', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 29, + 'column_num': 22, + 'extra_conf_data': { '&filetype': 'c' }, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': has_item( NO_COMPLETIONS_ERROR ), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_Fallback_Suggestions( self, app ): + RunTest( app, { + 'description': '. after macro with some query text (.a_)', + 'request': { + 'filetype' : 'c', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 29, + 'column_num': 23, + 'extra_conf_data': { '&filetype': 'c' }, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( CompletionEntryMatcher( 'a_parameter', + '[ID]' ) ), + 'errors': has_item( NO_COMPLETIONS_ERROR ), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_Fallback_Exception( self, app ): + # extra conf throws exception + RunTest( app, { + 'description': '. on struct returns identifier because of error', + 'request': { + 'filetype' : 'c', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 62, + 'column_num': 20, + 'extra_conf_data': { '&filetype': 'c', 'throw': 'testy' }, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'a_parameter', '[ID]' ), + CompletionEntryMatcher( 'another_parameter', '[ID]' ), + ), + 'errors': has_item( ErrorMatcher( ValueError, 'testy' ) ) + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_Forced_NoFallback( self, app ): + RunTest( app, { + 'description': '-> after macro with forced semantic', + 'request': { + 'filetype' : 'c', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 41, + 'column_num': 30, + 'extra_conf_data': { '&filetype': 'c' }, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': NO_COMPLETIONS_ERROR, + }, + } ) + + + @SharedYcmd + def test_GetCompletions_FilteredNoResults_Fallback( self, app ): + # no errors because the semantic completer returned results, but they + # were filtered out by the query, so this is considered working OK + # (whereas no completions from the semantic engine is considered an + # error) + RunTest( app, { + 'description': '. on struct returns IDs after query=do_', + 'request': { + 'filetype': 'c', + 'filepath': PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num': 71, + 'column_num': 18, + 'extra_conf_data': { '&filetype': 'c' }, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + # do_ is an identifier because it is already in the file when we + # load it + CompletionEntryMatcher( 'do_', '[ID]' ), + CompletionEntryMatcher( 'do_something', '[ID]' ), + CompletionEntryMatcher( 'do_another_thing', '[ID]' ), + CompletionEntryMatcher( 'DO_SOMETHING_TO', '[ID]' ), + CompletionEntryMatcher( 'DO_SOMETHING_VIA', '[ID]' ) + ), + 'errors': empty() + } ) + }, + } ) -int main() -{ - Foo foo; - foo. -} -""" - completion_data = BuildRequest( filepath = '/foo.cpp', - filetype = 'cpp', - contents = contents, - line_num = 11, - column_num = 7, - compilation_flags = [ '-x', 'c++' ] ) - - response_data = app.post_json( '/completions', completion_data ).json - assert_that( response_data[ 'completions' ], - has_items( CompletionEntryMatcher( 'c' ), - CompletionEntryMatcher( 'x' ), - CompletionEntryMatcher( 'y' ) ) ) - assert_that( 7, - equal_to( response_data[ 'completion_start_column' ] ) ) - - -@IsolatedYcmd( { 'auto_trigger': 0 } ) -def GetCompletions_NoCompletionsWhenAutoTriggerOff_test( app ): - app.post_json( - '/ignore_extra_conf_file', - { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) - contents = """ + @IsolatedYcmd() + def test_GetCompletions_WorksWithExplicitFlags( self, app ): + app.post_json( + '/ignore_extra_conf_file', + { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) + contents = """ struct Foo { int x; int y; @@ -339,104 +307,139 @@ def GetCompletions_NoCompletionsWhenAutoTriggerOff_test( app ): } """ - completion_data = BuildRequest( filepath = '/foo.cpp', - filetype = 'cpp', - contents = contents, - line_num = 11, - column_num = 7, - compilation_flags = [ '-x', 'c++' ] ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, empty() ) - - -@IsolatedYcmd() -def GetCompletions_UnknownExtraConfException_test( app ): - filepath = PathToTestFile( 'basic.cpp' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - contents = ReadFile( filepath ), - line_num = 11, - column_num = 7, - force_semantic = True ) - - response = app.post_json( '/completions', - completion_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, - has_entry( 'exception', - has_entry( 'TYPE', UnknownExtraConf.__name__ ) ) ) - - app.post_json( - '/ignore_extra_conf_file', - { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) - - response = app.post_json( '/completions', - completion_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, - has_entry( 'exception', - has_entry( 'TYPE', - NoExtraConfDetected.__name__ ) ) ) - - -@IsolatedYcmd() -def GetCompletions_WorksWhenExtraConfExplicitlyAllowed_test( app ): - app.post_json( - '/load_extra_conf_file', - { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) - - filepath = PathToTestFile( 'basic.cpp' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - contents = ReadFile( filepath ), - line_num = 11, - column_num = 7 ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, has_items( CompletionEntryMatcher( 'c' ), - CompletionEntryMatcher( 'x' ), - CompletionEntryMatcher( 'y' ) ) ) - - -@SharedYcmd -def GetCompletions_ExceptionWhenNoFlagsFromExtraConf_test( app ): - app.post_json( - '/load_extra_conf_file', - { 'filepath': PathToTestFile( 'noflags', - '.ycm_extra_conf.py' ) } ) - - filepath = PathToTestFile( 'noflags', 'basic.cpp' ) - - completion_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - contents = ReadFile( filepath ), - line_num = 11, - column_num = 7, - force_semantic = True ) - - response = app.post_json( '/completions', - completion_data, - expect_errors = True ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( response.json, - has_entry( 'exception', - has_entry( 'TYPE', RuntimeError.__name__ ) ) ) - + completion_data = BuildRequest( filepath = '/foo.cpp', + filetype = 'cpp', + contents = contents, + line_num = 11, + column_num = 7, + compilation_flags = [ '-x', 'c++' ] ) + + response_data = app.post_json( '/completions', completion_data ).json + assert_that( response_data[ 'completions' ], + has_items( CompletionEntryMatcher( 'c' ), + CompletionEntryMatcher( 'x' ), + CompletionEntryMatcher( 'y' ) ) ) + assert_that( 7, + equal_to( response_data[ 'completion_start_column' ] ) ) + + + @IsolatedYcmd( { 'auto_trigger': 0 } ) + def test_GetCompletions_NoCompletionsWhenAutoTriggerOff( self, app ): + app.post_json( + '/ignore_extra_conf_file', + { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) + contents = """ + struct Foo { + int x; + int y; + char c; + }; + + int main() + { + Foo foo; + foo. + } + """ -@SharedYcmd -def GetCompletions_ForceSemantic_OnlyFilteredCompletions_test( app ): - contents = """ + completion_data = BuildRequest( filepath = '/foo.cpp', + filetype = 'cpp', + contents = contents, + line_num = 11, + column_num = 7, + compilation_flags = [ '-x', 'c++' ] ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, empty() ) + + + @IsolatedYcmd() + def test_GetCompletions_UnknownExtraConfException( self, app ): + filepath = PathToTestFile( 'basic.cpp' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + contents = ReadFile( filepath ), + line_num = 11, + column_num = 7, + force_semantic = True ) + + response = app.post_json( '/completions', + completion_data, + expect_errors = True ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, + has_entry( 'exception', + has_entry( 'TYPE', UnknownExtraConf.__name__ ) ) ) + + app.post_json( + '/ignore_extra_conf_file', + { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) + + response = app.post_json( '/completions', + completion_data, + expect_errors = True ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, + has_entry( 'exception', + has_entry( 'TYPE', + NoExtraConfDetected.__name__ ) ) ) + + + @IsolatedYcmd() + def test_GetCompletions_WorksWhenExtraConfExplicitlyAllowed( self, app ): + app.post_json( + '/load_extra_conf_file', + { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) + + filepath = PathToTestFile( 'basic.cpp' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + contents = ReadFile( filepath ), + line_num = 11, + column_num = 7 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, has_items( CompletionEntryMatcher( 'c' ), + CompletionEntryMatcher( 'x' ), + CompletionEntryMatcher( 'y' ) ) ) + + + @SharedYcmd + def test_GetCompletions_ExceptionWhenNoFlagsFromExtraConf( self, app ): + app.post_json( + '/load_extra_conf_file', + { 'filepath': PathToTestFile( 'noflags', + '.ycm_extra_conf.py' ) } ) + + filepath = PathToTestFile( 'noflags', 'basic.cpp' ) + + completion_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + contents = ReadFile( filepath ), + line_num = 11, + column_num = 7, + force_semantic = True ) + + response = app.post_json( '/completions', + completion_data, + expect_errors = True ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( response.json, + has_entry( 'exception', + has_entry( 'TYPE', RuntimeError.__name__ ) ) ) + + + @SharedYcmd + def test_GetCompletions_ForceSemantic_OnlyFilteredCompletions( self, app ): + contents = """ int main() { int foobar; @@ -448,1121 +451,1120 @@ def GetCompletions_ForceSemantic_OnlyFilteredCompletions_test( app ): } """ - completion_data = BuildRequest( filepath = '/foo.cpp', - filetype = 'cpp', - force_semantic = True, - contents = contents, - line_num = 9, - column_num = 8, - compilation_flags = [ '-x', 'c++' ] ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - contains_inanyorder( CompletionEntryMatcher( 'foobar' ), - CompletionEntryMatcher( 'floozar' ) ) - ) - - -@SharedYcmd -def GetCompletions_DocStringsAreIncluded_test( app ): - filepath = PathToTestFile( 'completion_docstring.cc' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - contents = ReadFile( filepath ), - line_num = 5, - column_num = 7, - compilation_flags = [ '-x', 'c++' ], - force_semantic = True ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, has_item( - has_entries( { - 'insertion_text': 'func', - 'extra_data': has_entry( 'doc_string', 'This is a docstring.' ) - } ) - ) ) - - -@SharedYcmd -def GetCompletions_PublicAndProtectedMembersAvailableInDerivedClass_test( app ): - filepath = PathToTestFile( 'completion_availability.cc' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - contents = ReadFile( filepath ), - line_num = 14, - column_num = 5, - compilation_flags = [ '-x', 'c++' ], - force_semantic = True ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - all_of( - has_items( CompletionEntryMatcher( 'public_member' ), - CompletionEntryMatcher( 'protected_member' ) ), - is_not( has_item( CompletionEntryMatcher( 'private_member' ) ) ) + completion_data = BuildRequest( filepath = '/foo.cpp', + filetype = 'cpp', + force_semantic = True, + contents = contents, + line_num = 9, + column_num = 8, + compilation_flags = [ '-x', 'c++' ] ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + contains_inanyorder( CompletionEntryMatcher( 'foobar' ), + CompletionEntryMatcher( 'floozar' ) ) + ) + + + @SharedYcmd + def test_GetCompletions_DocStringsAreIncluded( self, app ): + filepath = PathToTestFile( 'completion_docstring.cc' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + contents = ReadFile( filepath ), + line_num = 5, + column_num = 7, + compilation_flags = [ '-x', 'c++' ], + force_semantic = True ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, has_item( + has_entries( { + 'insertion_text': 'func', + 'extra_data': has_entry( 'doc_string', 'This is a docstring.' ) + } ) ) ) -@SharedYcmd -def GetCompletions_ClientDataGivenToExtraConf_test( app ): - app.post_json( - '/load_extra_conf_file', - { 'filepath': PathToTestFile( 'client_data', - '.ycm_extra_conf.py' ) } ) - - filepath = PathToTestFile( 'client_data', 'main.cpp' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - contents = ReadFile( filepath ), - line_num = 9, - column_num = 7, - extra_conf_data = { - 'flags': [ '-x', 'c++' ] - } ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, has_item( CompletionEntryMatcher( 'x' ) ) ) - - -@IsolatedYcmd( { 'max_num_candidates': 0 } ) -def GetCompletions_ClientDataGivenToExtraConf_Include_test( app ): - app.post_json( - '/load_extra_conf_file', - { 'filepath': PathToTestFile( 'client_data', - '.ycm_extra_conf.py' ) } ) - - filepath = PathToTestFile( 'client_data', 'include.cpp' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - contents = ReadFile( filepath ), - line_num = 1, - column_num = 11, - extra_conf_data = { - 'flags': [ '-x', 'c++' ] - } ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - has_item( CompletionEntryMatcher( 'include.hpp', - extra_menu_info = '[File]' ) ) - ) - - -@IsolatedYcmd() -def GetCompletions_ClientDataGivenToExtraConf_Cache_test( app ): - app.post_json( - '/load_extra_conf_file', - { 'filepath': PathToTestFile( 'client_data', '.ycm_extra_conf.py' ) } ) - - filepath = PathToTestFile( 'client_data', 'macro.cpp' ) - contents = ReadFile( filepath ) - request = { - 'filetype' : 'cpp', - 'filepath' : filepath, - 'contents' : contents, - 'line_num' : 11, - 'column_num': 8 - } - - # Complete with flags from the client. - completion_request = CombineRequest( request, { - 'extra_conf_data': { - 'flags': [ '-DSOME_MACRO' ] + @SharedYcmd + def test_GetCompletions_PublicAndProtectedMembersAvailableInDerivedClass( + self, app ): + filepath = PathToTestFile( 'completion_availability.cc' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + contents = ReadFile( filepath ), + line_num = 14, + column_num = 5, + compilation_flags = [ '-x', 'c++' ], + force_semantic = True ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + all_of( + has_items( CompletionEntryMatcher( 'public_member' ), + CompletionEntryMatcher( 'protected_member' ) ), + is_not( has_item( CompletionEntryMatcher( 'private_member' ) ) ) + ) ) + + + @SharedYcmd + def test_GetCompletions_ClientDataGivenToExtraConf( self, app ): + app.post_json( + '/load_extra_conf_file', + { 'filepath': PathToTestFile( 'client_data', + '.ycm_extra_conf.py' ) } ) + + filepath = PathToTestFile( 'client_data', 'main.cpp' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + contents = ReadFile( filepath ), + line_num = 9, + column_num = 7, + extra_conf_data = { + 'flags': [ '-x', 'c++' ] + } ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, has_item( CompletionEntryMatcher( 'x' ) ) ) + + + @IsolatedYcmd( { 'max_num_candidates': 0 } ) + def test_GetCompletions_ClientDataGivenToExtraConf_Include( self, app ): + app.post_json( + '/load_extra_conf_file', + { 'filepath': PathToTestFile( 'client_data', + '.ycm_extra_conf.py' ) } ) + + filepath = PathToTestFile( 'client_data', 'include.cpp' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + contents = ReadFile( filepath ), + line_num = 1, + column_num = 11, + extra_conf_data = { + 'flags': [ '-x', 'c++' ] + } ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_item( CompletionEntryMatcher( 'include.hpp', + extra_menu_info = '[File]' ) ) + ) + + + @IsolatedYcmd() + def test_GetCompletions_ClientDataGivenToExtraConf_Cache( self, app ): + app.post_json( + '/load_extra_conf_file', + { 'filepath': PathToTestFile( 'client_data', '.ycm_extra_conf.py' ) } ) + + filepath = PathToTestFile( 'client_data', 'macro.cpp' ) + contents = ReadFile( filepath ) + request = { + 'filetype' : 'cpp', + 'filepath' : filepath, + 'contents' : contents, + 'line_num' : 11, + 'column_num': 8 } - } ) - - assert_that( - app.post_json( '/completions', completion_request ).json, - has_entries( { - 'completions': has_item( - CompletionEntryMatcher( 'macro_defined' ) - ), - 'errors': empty() - } ) - ) - # Complete at the same position but for a different set of flags from the - # client. - completion_request = CombineRequest( request, { - 'extra_conf_data': { - 'flags': [ '-Wall' ] - } - } ) - - assert_that( - app.post_json( '/completions', completion_request ).json, - has_entries( { - 'completions': has_item( - CompletionEntryMatcher( 'macro_not_defined' ) - ), - 'errors': empty() - } ) - ) - - # Finally, complete once again at the same position but no flags are given by - # the client. An empty list of flags is returned by the extra conf file in - # that case. - completion_request = CombineRequest( request, {} ) - - assert_that( - app.post_json( '/completions', completion_request ).json, - has_entries( { - 'completions': empty(), - 'errors': contains_exactly( - ErrorMatcher( RuntimeError, NO_COMPILE_FLAGS_MESSAGE ) - ) + # Complete with flags from the client. + completion_request = CombineRequest( request, { + 'extra_conf_data': { + 'flags': [ '-DSOME_MACRO' ] + } } ) - ) - - -@SharedYcmd -@WindowsOnly -def GetCompletions_ClangCLDriverFlag_SimpleCompletion_test( app ): - RunTest( app, { - 'description': 'basic completion with --driver-mode=cl', - 'extra_conf': [ 'driver_mode_cl', 'flag', '.ycm_extra_conf.py' ], - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'driver_mode_cl', - 'flag', - 'driver_mode_cl.cpp' ), - 'line_num': 8, - 'column_num': 18, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 3, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'driver_mode_cl_include_func', 'void' ), - CompletionEntryMatcher( 'driver_mode_cl_include_int', 'int' ), - ), - 'errors': empty(), - } ) - } - } ) - - -@SharedYcmd -@WindowsOnly -def GetCompletions_ClangCLDriverExec_SimpleCompletion_test( app ): - RunTest( app, { - 'description': 'basic completion with --driver-mode=cl', - 'extra_conf': [ 'driver_mode_cl', 'executable', '.ycm_extra_conf.py' ], - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'driver_mode_cl', - 'executable', - 'driver_mode_cl.cpp' ), - 'line_num': 8, - 'column_num': 18, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 3, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'driver_mode_cl_include_func', 'void' ), - CompletionEntryMatcher( 'driver_mode_cl_include_int', 'int' ), - ), - 'errors': empty(), - } ) - } - } ) - - -@SharedYcmd -@WindowsOnly -def GetCompletions_ClangCLDriverFlag_IncludeStatementCandidate_test( app ): - RunTest( app, { - 'description': 'Completion inside include statement with CL driver', - 'extra_conf': [ 'driver_mode_cl', 'flag', '.ycm_extra_conf.py' ], - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'driver_mode_cl', - 'flag', - 'driver_mode_cl.cpp' ), - 'line_num': 1, - 'column_num': 34, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'driver_mode_cl_include.h', '[File]' ), + + assert_that( + app.post_json( '/completions', completion_request ).json, + has_entries( { + 'completions': has_item( + CompletionEntryMatcher( 'macro_defined' ) ), - 'errors': empty(), + 'errors': empty() } ) - } - } ) - - -@SharedYcmd -@WindowsOnly -def GetCompletions_ClangCLDriverExec_IncludeStatementCandidate_test( app ): - RunTest( app, { - 'description': 'Completion inside include statement with CL driver', - 'extra_conf': [ 'driver_mode_cl', 'executable', '.ycm_extra_conf.py' ], - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'driver_mode_cl', - 'executable', - 'driver_mode_cl.cpp' ), - 'line_num': 1, - 'column_num': 34, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'driver_mode_cl_include.h', '[File]' ), + ) + + # Complete at the same position but for a different set of flags from the + # client. + completion_request = CombineRequest( request, { + 'extra_conf_data': { + 'flags': [ '-Wall' ] + } + } ) + + assert_that( + app.post_json( '/completions', completion_request ).json, + has_entries( { + 'completions': has_item( + CompletionEntryMatcher( 'macro_not_defined' ) ), - 'errors': empty(), + 'errors': empty() } ) - } - } ) + ) + # Finally, complete once again at the same position but no flags are given + # by the client. An empty list of flags is returned by the extra conf file + # in that case. + completion_request = CombineRequest( request, {} ) -@SharedYcmd -def GetCompletions_UnicodeInLine_test( app ): - RunTest( app, { - 'description': 'member completion with a unicode identifier', - 'extra_conf': [ '.ycm_extra_conf.py' ], - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'unicode.cc' ), - 'line_num' : 9, - 'column_num': 8, - 'extra_conf_data': { '&filetype': 'cpp' }, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 8, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'member_with_å_unicøde', 'int' ), - CompletionEntryMatcher( '~MyStruct', 'void' ), - CompletionEntryMatcher( 'operator=', 'MyStruct &' ), - CompletionEntryMatcher( 'MyStruct::', '' ), - ), - 'errors': empty(), + assert_that( + app.post_json( '/completions', completion_request ).json, + has_entries( { + 'completions': empty(), + 'errors': contains_exactly( + ErrorMatcher( RuntimeError, NO_COMPILE_FLAGS_MESSAGE ) + ) } ) - }, - } ) + ) -@SharedYcmd -def GetCompletions_UnicodeInLineFilter_test( app ): - RunTest( app, { - 'description': 'member completion with a unicode identifier', - 'extra_conf': [ '.ycm_extra_conf.py' ], - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'unicode.cc' ), - 'line_num' : 9, - 'column_num': 10, - 'extra_conf_data': { '&filetype': 'cpp' }, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 8, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'member_with_å_unicøde', 'int' ), - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + @WindowsOnly + def test_GetCompletions_ClangCLDriverFlag_SimpleCompletion( self, app ): + RunTest( app, { + 'description': 'basic completion with --driver-mode=cl', + 'extra_conf': [ 'driver_mode_cl', 'flag', '.ycm_extra_conf.py' ], + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'driver_mode_cl', + 'flag', + 'driver_mode_cl.cpp' ), + 'line_num': 8, + 'column_num': 18, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 3, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'driver_mode_cl_include_func', 'void' ), + CompletionEntryMatcher( 'driver_mode_cl_include_int', 'int' ), + ), + 'errors': empty(), + } ) + } + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_AtStart_test( app ): - RunTest( app, { - 'description': 'completion of #include "', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 11, - 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), - CompletionEntryMatcher( 'a.hpp', '[File]' ), - CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), - CompletionEntryMatcher( 'main.cpp', '[File]' ), - CompletionEntryMatcher( 'quote', '[Dir]' ), - CompletionEntryMatcher( 'system', '[Dir]' ), - CompletionEntryMatcher( 'Frameworks', '[Dir]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + @WindowsOnly + def test_GetCompletions_ClangCLDriverExec_SimpleCompletion( self, app ): + RunTest( app, { + 'description': 'basic completion with --driver-mode=cl', + 'extra_conf': [ 'driver_mode_cl', 'executable', '.ycm_extra_conf.py' ], + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'driver_mode_cl', + 'executable', + 'driver_mode_cl.cpp' ), + 'line_num': 8, + 'column_num': 18, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 3, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'driver_mode_cl_include_func', 'void' ), + CompletionEntryMatcher( 'driver_mode_cl_include_int', 'int' ), + ), + 'errors': empty(), + } ) + } + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_UserIncludeFlag_test( app ): - RunTest( app, { - 'description': 'completion of #include " with a -I flag', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 11, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-I', PathToTestFile( 'test-include', 'system' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), - CompletionEntryMatcher( 'a.hpp', '[File]' ), - CompletionEntryMatcher( 'c.hpp', '[File]' ), - CompletionEntryMatcher( 'common', '[Dir]' ), - CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), - CompletionEntryMatcher( 'main.cpp', '[File]' ), - CompletionEntryMatcher( 'quote', '[Dir]' ), - CompletionEntryMatcher( 'system', '[Dir]' ), - CompletionEntryMatcher( 'Frameworks', '[Dir]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + @WindowsOnly + def test_GetCompletions_ClangCLDriverFlag_IncludeStatementCandidate( + self, app ): + RunTest( app, { + 'description': 'Completion inside include statement with CL driver', + 'extra_conf': [ 'driver_mode_cl', 'flag', '.ycm_extra_conf.py' ], + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'driver_mode_cl', + 'flag', + 'driver_mode_cl.cpp' ), + 'line_num': 1, + 'column_num': 34, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'driver_mode_cl_include.h', '[File]' ), + ), + 'errors': empty(), + } ) + } + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_SystemIncludeFlag_test( app ): - RunTest( app, { - 'description': 'completion of #include " with a -isystem flag', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 11, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-isystem', PathToTestFile( 'test-include', 'system' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), - CompletionEntryMatcher( 'a.hpp', '[File]' ), - CompletionEntryMatcher( 'c.hpp', '[File]' ), - CompletionEntryMatcher( 'common', '[Dir]' ), - CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), - CompletionEntryMatcher( 'main.cpp', '[File]' ), - CompletionEntryMatcher( 'quote', '[Dir]' ), - CompletionEntryMatcher( 'system', '[Dir]' ), - CompletionEntryMatcher( 'Frameworks', '[Dir]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + @WindowsOnly + def test_GetCompletions_ClangCLDriverExec_IncludeStatementCandidate( + self, app ): + RunTest( app, { + 'description': 'Completion inside include statement with CL driver', + 'extra_conf': [ 'driver_mode_cl', 'executable', '.ycm_extra_conf.py' ], + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'driver_mode_cl', + 'executable', + 'driver_mode_cl.cpp' ), + 'line_num': 1, + 'column_num': 34, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'driver_mode_cl_include.h', '[File]' ), + ), + 'errors': empty(), + } ) + } + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_QuoteIncludeFlag_test( app ): - RunTest( app, { - 'description': 'completion of #include " with a -iquote flag', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 11, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-iquote', PathToTestFile( 'test-include', 'quote' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), - CompletionEntryMatcher( 'a.hpp', '[File]' ), - CompletionEntryMatcher( 'b.hpp', '[File]' ), - CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), - CompletionEntryMatcher( 'main.cpp', '[File]' ), - CompletionEntryMatcher( 'quote', '[Dir]' ), - CompletionEntryMatcher( 'system', '[Dir]' ), - CompletionEntryMatcher( 'Frameworks', '[Dir]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_UnicodeInLine( self, app ): + RunTest( app, { + 'description': 'member completion with a unicode identifier', + 'extra_conf': [ '.ycm_extra_conf.py' ], + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'unicode.cc' ), + 'line_num' : 9, + 'column_num': 8, + 'extra_conf_data': { '&filetype': 'cpp' }, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 8, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'member_with_å_unicøde', 'int' ), + CompletionEntryMatcher( '~MyStruct', 'void' ), + CompletionEntryMatcher( 'operator=', 'MyStruct &' ), + CompletionEntryMatcher( 'MyStruct::', '' ), + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_MultipleIncludeFlags_test( app ): - RunTest( app, { - 'description': 'completion of #include " with multiple -I flags', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 11, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-I', PathToTestFile( 'test-include', 'dir with spaces' ), - '-I', PathToTestFile( 'test-include', 'quote' ), - '-I', PathToTestFile( 'test-include', 'system' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), - CompletionEntryMatcher( 'a.hpp', '[File]' ), - CompletionEntryMatcher( 'b.hpp', '[File]' ), - CompletionEntryMatcher( 'c.hpp', '[File]' ), - CompletionEntryMatcher( 'common', '[Dir]' ), - CompletionEntryMatcher( 'd.hpp', '[File]' ), - CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), - CompletionEntryMatcher( 'main.cpp', '[File]' ), - CompletionEntryMatcher( 'quote', '[Dir]' ), - CompletionEntryMatcher( 'system', '[Dir]' ), - CompletionEntryMatcher( 'Frameworks', '[Dir]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_UnicodeInLineFilter( self, app ): + RunTest( app, { + 'description': 'member completion with a unicode identifier', + 'extra_conf': [ '.ycm_extra_conf.py' ], + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'unicode.cc' ), + 'line_num' : 9, + 'column_num': 10, + 'extra_conf_data': { '&filetype': 'cpp' }, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 8, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'member_with_å_unicøde', 'int' ), + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_AfterDirectorySeparator_test( app ): - RunTest( app, { - 'description': 'completion of #include "quote/', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 27, - 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 27, - 'completions': contains_exactly( - CompletionEntryMatcher( 'd.hpp', '[File]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_AtStart( self, app ): + RunTest( app, { + 'description': 'completion of #include "', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 11, + 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_exactly( + CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), + CompletionEntryMatcher( 'a.hpp', '[File]' ), + CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), + CompletionEntryMatcher( 'main.cpp', '[File]' ), + CompletionEntryMatcher( 'quote', '[Dir]' ), + CompletionEntryMatcher( 'system', '[Dir]' ), + CompletionEntryMatcher( 'Frameworks', '[Dir]' ) + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_AfterDot_test( app ): - RunTest( app, { - 'description': 'completion of #include "quote/b.', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 28, - 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 27, - 'completions': contains_exactly( - CompletionEntryMatcher( 'd.hpp', '[File]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_UserIncludeFlag( self, app ): + RunTest( app, { + 'description': 'completion of #include " with a -I flag', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 11, + 'compilation_flags': [ + '-x', 'c++', '-nostdinc', '-nobuiltininc', + '-I', PathToTestFile( 'test-include', 'system' ) + ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_exactly( + CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), + CompletionEntryMatcher( 'a.hpp', '[File]' ), + CompletionEntryMatcher( 'c.hpp', '[File]' ), + CompletionEntryMatcher( 'common', '[Dir]' ), + CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), + CompletionEntryMatcher( 'main.cpp', '[File]' ), + CompletionEntryMatcher( 'quote', '[Dir]' ), + CompletionEntryMatcher( 'system', '[Dir]' ), + CompletionEntryMatcher( 'Frameworks', '[Dir]' ) + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_AfterSpace_test( app ): - RunTest( app, { - 'description': 'completion of #include "dir with ', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 20, - 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( 'dir with spaces', '[Dir]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_SystemIncludeFlag( self, app ): + RunTest( app, { + 'description': 'completion of #include " with a -isystem flag', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 11, + 'compilation_flags': [ + '-x', 'c++', '-nostdinc', '-nobuiltininc', + '-isystem', PathToTestFile( 'test-include', 'system' ) + ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_exactly( + CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), + CompletionEntryMatcher( 'a.hpp', '[File]' ), + CompletionEntryMatcher( 'c.hpp', '[File]' ), + CompletionEntryMatcher( 'common', '[Dir]' ), + CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), + CompletionEntryMatcher( 'main.cpp', '[File]' ), + CompletionEntryMatcher( 'quote', '[Dir]' ), + CompletionEntryMatcher( 'system', '[Dir]' ), + CompletionEntryMatcher( 'Frameworks', '[Dir]' ) + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_Invalid_test( app ): - RunTest( app, { - 'description': 'completion of an invalid include statement', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 13, - 'column_num': 12, - 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 12, - 'completions': empty(), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_QuoteIncludeFlag( self, app ): + RunTest( app, { + 'description': 'completion of #include " with a -iquote flag', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 11, + 'compilation_flags': [ + '-x', 'c++', '-nostdinc', '-nobuiltininc', + '-iquote', PathToTestFile( 'test-include', 'quote' ) + ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_exactly( + CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), + CompletionEntryMatcher( 'a.hpp', '[File]' ), + CompletionEntryMatcher( 'b.hpp', '[File]' ), + CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), + CompletionEntryMatcher( 'main.cpp', '[File]' ), + CompletionEntryMatcher( 'quote', '[Dir]' ), + CompletionEntryMatcher( 'system', '[Dir]' ), + CompletionEntryMatcher( 'Frameworks', '[Dir]' ) + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_QuotedInclude_FrameworkHeader_test( app ): - RunTest( app, { - 'description': 'completion of #include "OpenGL/', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 14, - 'column_num': 18, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-iframework', PathToTestFile( 'test-include', 'Frameworks' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 18, - 'completions': contains_exactly( - CompletionEntryMatcher( 'gl.h', '[File]' ) - ), - 'errors': empty() - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_MultipleIncludeFlags( self, app ): + RunTest( app, { + 'description': 'completion of #include " with multiple -I flags', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 11, + 'compilation_flags': [ + '-x', 'c++', '-nostdinc', '-nobuiltininc', + '-I', PathToTestFile( 'test-include', 'dir with spaces' ), + '-I', PathToTestFile( 'test-include', 'quote' ), + '-I', PathToTestFile( 'test-include', 'system' ) + ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_exactly( + CompletionEntryMatcher( '.ycm_extra_conf.py', '[File]' ), + CompletionEntryMatcher( 'a.hpp', '[File]' ), + CompletionEntryMatcher( 'b.hpp', '[File]' ), + CompletionEntryMatcher( 'c.hpp', '[File]' ), + CompletionEntryMatcher( 'common', '[Dir]' ), + CompletionEntryMatcher( 'd.hpp', '[File]' ), + CompletionEntryMatcher( 'dir with spaces', '[Dir]' ), + CompletionEntryMatcher( 'main.cpp', '[File]' ), + CompletionEntryMatcher( 'quote', '[Dir]' ), + CompletionEntryMatcher( 'system', '[Dir]' ), + CompletionEntryMatcher( 'Frameworks', '[Dir]' ) + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_BracketInclude_AtStart_test( app ): - RunTest( app, { - 'description': 'completion of #include <', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 12, - 'column_num': 11, - 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': empty(), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_AfterDirectorySeparator( self, app ): + RunTest( app, { + 'description': 'completion of #include "quote/', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 27, + 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 27, + 'completions': contains_exactly( + CompletionEntryMatcher( 'd.hpp', '[File]' ) + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_BracketInclude_UserIncludeFlag_test( app ): - RunTest( app, { - 'description': 'completion of #include < with a -I flag', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 12, - 'column_num': 11, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-I', PathToTestFile( 'test-include', 'system' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( 'a.hpp', '[File]' ), - CompletionEntryMatcher( 'c.hpp', '[File]' ), - CompletionEntryMatcher( 'common', '[Dir]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_AfterDot( self, app ): + RunTest( app, { + 'description': 'completion of #include "quote/b.', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 28, + 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 27, + 'completions': contains_exactly( + CompletionEntryMatcher( 'd.hpp', '[File]' ) + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_BracketInclude_SystemIncludeFlag_test( app ): - RunTest( app, { - 'description': 'completion of #include < with a -isystem flag', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 12, - 'column_num': 11, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-isystem', PathToTestFile( 'test-include', 'system' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( 'a.hpp', '[File]' ), - CompletionEntryMatcher( 'c.hpp', '[File]' ), - CompletionEntryMatcher( 'common', '[Dir]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_AfterSpace( self, app ): + RunTest( app, { + 'description': 'completion of #include "dir with ', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 20, + 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_exactly( + CompletionEntryMatcher( 'dir with spaces', '[Dir]' ) + ), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_BracketInclude_QuoteIncludeFlag_test( app ): - RunTest( app, { - 'description': 'completion of #include < with a -iquote flag', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 12, - 'column_num': 11, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-iquote', PathToTestFile( 'test-include', 'quote' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': empty(), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_Invalid( self, app ): + RunTest( app, { + 'description': 'completion of an invalid include statement', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 13, + 'column_num': 12, + 'compilation_flags': [ '-x', 'c++', '-nostdinc', '-nobuiltininc' ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 12, + 'completions': empty(), + 'errors': empty(), + } ) + }, + } ) -@SharedYcmd -def GetCompletions_BracketInclude_MultipleIncludeFlags_test( app ): - RunTest( app, { - 'description': 'completion of #include < with multiple -I flags', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 12, - 'column_num': 11, - 'compilation_flags': [ - '-x', 'c++', '-nostdinc', '-nobuiltininc', - '-I', PathToTestFile( 'test-include', 'dir with spaces' ), - '-I', PathToTestFile( 'test-include', 'quote' ), - '-I', PathToTestFile( 'test-include', 'system' ) - ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( 'a.hpp', '[File]' ), - CompletionEntryMatcher( 'b.hpp', '[File]' ), - CompletionEntryMatcher( 'c.hpp', '[File]' ), - CompletionEntryMatcher( 'common', '[Dir]' ), - CompletionEntryMatcher( 'd.hpp', '[File]' ) - ), - 'errors': empty(), - } ) - }, - } ) + @SharedYcmd + def test_GetCompletions_QuotedInclude_FrameworkHeader( self, app ): + RunTest( app, { + 'description': 'completion of #include "OpenGL/', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 14, + 'column_num': 18, + 'compilation_flags': [ + '-x', 'c++', '-nostdinc', '-nobuiltininc', + '-iframework', PathToTestFile( 'test-include', 'Frameworks' ) + ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 18, + 'completions': contains_exactly( + CompletionEntryMatcher( 'gl.h', '[File]' ) + ), + 'errors': empty() + } ) + }, + } ) -@SharedYcmd -def GetCompletions_BracketInclude_AtDirectorySeparator_test( app ): - RunTest( app, { - 'description': 'completion of #include "', - 'extra_conf': [ '.ycm_extra_conf.py' ], - 'request': { - 'filetype': 'cpp', - 'filepath': filepath, - 'line_num': 7, - 'column_num': 8, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( has_entries( { - 'insertion_text': 'bar', - 'extra_menu_info': 'int', - 'menu_text': 'bar', - 'detailed_info': 'int bar\n', - 'kind': 'MEMBER', - 'extra_data': has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( - '->', - LocationMatcher( filepath, 7, 6 ), - LocationMatcher( filepath, 7, 7 ) - ) - ), - 'location': LocationMatcher( '', 0, 0 ) - } ) - ) - } ) - } ) ) + @SharedYcmd + def test_GetCompletions_Unity( self, app ): + RunTest( app, { + 'description': 'Completion returns from file included in TU, but not in ' + 'opened file', + 'extra_conf': [ '.ycm_extra_conf.py' ], + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'unitya.cc' ), + 'line_num' : 10, + 'column_num': 24, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 20, + 'completions': contains_exactly( + CompletionEntryMatcher( 'this_is_an_it', 'int' ), + ), + 'errors': empty(), + } ) + } + } ) + + + @SharedYcmd + def test_GetCompletions_UnityInclude( self, app ): + RunTest( app, { + 'description': 'Completion returns for includes in unity setup', + 'extra_conf': [ '.ycm_extra_conf.py' ], + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'unitya.cc' ), + 'line_num' : 1, + 'column_num': 17, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': has_items( + CompletionEntryMatcher( 'unity.h', '[File]' ), + CompletionEntryMatcher( 'unity.cc', '[File]' ), + CompletionEntryMatcher( 'unitya.cc', '[File]' ), + ), + 'errors': empty(), + } ) + } + } ) + + + # This test is isolated to make sure we trigger c hook for clangd, instead of + # fetching completer from cache. + @IsolatedYcmd() + def test_GetCompletions_cuda( self, app ): + RunTest( app, { + 'description': 'Completion of CUDA files', + 'extra_conf': [ '.ycm_extra_conf.py' ], + 'request': { + 'filetype' : 'cuda', + 'filepath' : PathToTestFile( 'cuda', 'completion_test.cu' ), + 'line_num' : 16, + 'column_num': 29, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 29, + # NOTE: libclang also returns strange completions like 'cudaStream::', + # 'dim3::', 'and Kernels::'. + 'completions': has_item( + CompletionEntryMatcher( 'do_something', 'void' ), + ), + 'errors': empty(), + } ) + } + } ) + + + @SharedYcmd + def test_GetCompletions_StillParsingError( self, app ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) + with patch.object( completer, '_completer', MockCoreClangCompleter() ): + RunTest( app, { + 'description': 'raise an appropriate error if translation unit is ' + 'still being parsed.', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test.cpp' ), + 'contents' : '', + 'compilation_flags': [ '-x', 'c++' ], + 'force_semantic' : True + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, PARSING_FILE_MESSAGE ) + }, } ) - } - } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_GetCompletions_FixIt( self, app ): + filepath = PathToTestFile( 'completion_fixit.cc' ) + RunTest( app, { + 'description': 'member completion has a fixit that change "." into "->"', + 'extra_conf': [ '.ycm_extra_conf.py' ], + 'request': { + 'filetype': 'cpp', + 'filepath': filepath, + 'line_num': 7, + 'column_num': 8, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( has_entries( { + 'insertion_text': 'bar', + 'extra_menu_info': 'int', + 'menu_text': 'bar', + 'detailed_info': 'int bar\n', + 'kind': 'MEMBER', + 'extra_data': has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( + '->', + LocationMatcher( filepath, 7, 6 ), + LocationMatcher( filepath, 7, 7 ) + ) + ), + 'location': LocationMatcher( '', 0, 0 ) + } ) + ) + } ) + } ) ) + } ) + } + } ) diff --git a/ycmd/tests/clang/include_cache_test.py b/ycmd/tests/clang/include_cache_test.py index fd3e394c32..7fe1762c6c 100644 --- a/ycmd/tests/clang/include_cache_test.py +++ b/ycmd/tests/clang/include_cache_test.py @@ -1,6 +1,6 @@ # Copyright (C) 2017 Davit Samvelyan davitsamvelyan@gmail.com # Synopsys. -# 2020 ycmd contributors +# 2021 ycmd contributors # # This file is part of ycmd. # @@ -28,131 +28,128 @@ has_entry, has_properties, not_ ) +from unittest import TestCase from ycmd.completers.cpp.include_cache import IncludeCache from ycmd.tests.clang import PathToTestFile from ycmd.tests.test_utils import TemporaryTestDir -def IncludeCache_NotCached_DirInaccessible_test(): - include_cache = IncludeCache() - assert_that( include_cache._cache, equal_to( {} ) ) - includes = include_cache.GetIncludes( PathToTestFile( 'unknown_dir' ) ) - assert_that( includes, equal_to( [] ) ) - assert_that( include_cache._cache, equal_to( {} ) ) +class IncludeCacheTest( TestCase ): + def test_IncludeCache_NotCached_DirInaccessible( self ): + include_cache = IncludeCache() + assert_that( include_cache._cache, equal_to( {} ) ) + includes = include_cache.GetIncludes( PathToTestFile( 'unknown_dir' ) ) + assert_that( includes, equal_to( [] ) ) + assert_that( include_cache._cache, equal_to( {} ) ) -def IncludeCache_NotCached_DirAccessible_test(): - include_cache = IncludeCache() - assert_that( include_cache._cache, equal_to( {} ) ) - includes = include_cache.GetIncludes( PathToTestFile( 'cache_test' ) ) - mtime = os.path.getmtime( PathToTestFile( 'cache_test' ) ) - assert_that( includes, contains_exactly( has_properties( { - 'name': 'foo.h', - 'entry_type': 1 - } ) ) ) - assert_that( include_cache._cache, - has_entry( PathToTestFile( 'cache_test' ), - has_entries( { 'mtime': mtime, - 'includes': contains_exactly( has_properties( { - 'name': 'foo.h', - 'entry_type': 1 - } ) ) } ) ) ) - - -def IncludeCache_Cached_NoNewMtime_test(): - include_cache = IncludeCache() - assert_that( include_cache._cache, equal_to( {} ) ) - old_includes = include_cache.GetIncludes( PathToTestFile( 'cache_test' ) ) - old_mtime = os.path.getmtime( PathToTestFile( 'cache_test' ) ) - - assert_that( old_includes, contains_exactly( has_properties( { - 'name': 'foo.h', - 'entry_type': 1 - } ) ) ) - assert_that( include_cache._cache, - has_entry( PathToTestFile( 'cache_test' ), - has_entries( { 'mtime': old_mtime, - 'includes': contains_exactly( has_properties( { - 'name': 'foo.h', - 'entry_type': 1 - } ) ) } ) ) ) - - new_includes = include_cache.GetIncludes( PathToTestFile( 'cache_test' ) ) - new_mtime = os.path.getmtime( PathToTestFile( 'cache_test' ) ) - - assert_that( new_mtime, equal_to( old_mtime ) ) - assert_that( new_includes, contains_exactly( has_properties( { - 'name': 'foo.h', - 'entry_type': 1 - } ) ) ) - assert_that( include_cache._cache, - has_entry( PathToTestFile( 'cache_test' ), - has_entries( { 'mtime': new_mtime, - 'includes': contains_exactly( has_properties( { - 'name': 'foo.h', - 'entry_type': 1 - } ) ) } ) ) ) - - -def IncludeCache_Cached_NewMtime_test(): - with TemporaryTestDir() as tmp_dir: + def test_IncludeCache_NotCached_DirAccessible( self ): include_cache = IncludeCache() assert_that( include_cache._cache, equal_to( {} ) ) - foo_path = os.path.join( tmp_dir, 'foo' ) - with open( foo_path, 'w' ) as foo_file: - foo_file.write( 'foo' ) + includes = include_cache.GetIncludes( PathToTestFile( 'cache_test' ) ) + mtime = os.path.getmtime( PathToTestFile( 'cache_test' ) ) + assert_that( includes, contains_exactly( has_properties( { + 'name': 'foo.h', + 'entry_type': 1 + } ) ) ) + assert_that( include_cache._cache, + has_entry( PathToTestFile( 'cache_test' ), + has_entries( { 'mtime': mtime, + 'includes': contains_exactly( has_properties( { + 'name': 'foo.h', + 'entry_type': 1 + } ) ) } ) ) ) + + + def test_IncludeCache_Cached_NoNewMtime( self ): + include_cache = IncludeCache() + assert_that( include_cache._cache, equal_to( {} ) ) + old_includes = include_cache.GetIncludes( PathToTestFile( 'cache_test' ) ) + old_mtime = os.path.getmtime( PathToTestFile( 'cache_test' ) ) - old_includes = include_cache.GetIncludes( tmp_dir ) - old_mtime = os.path.getmtime( tmp_dir ) assert_that( old_includes, contains_exactly( has_properties( { - 'name': 'foo', + 'name': 'foo.h', 'entry_type': 1 } ) ) ) assert_that( include_cache._cache, - has_entry( tmp_dir, - has_entries( { - 'mtime': old_mtime, - 'includes': contains_exactly( has_properties( { - 'name': 'foo', - 'entry_type': 1 - } ) ) - } ) ) ) - - sleep( 2 ) - - bar_path = os.path.join( tmp_dir, 'bar' ) - with open( bar_path, 'w' ) as bar_file: - bar_file.write( 'bar' ) - - new_includes = include_cache.GetIncludes( tmp_dir ) - new_mtime = os.path.getmtime( tmp_dir ) - assert_that( old_mtime, not_( equal_to( new_mtime ) ) ) - assert_that( new_includes, contains_inanyorder( - has_properties( { - 'name': 'foo', - 'entry_type': 1 - } ), - has_properties( { - 'name': 'bar', - 'entry_type': 1 - } ) - ) ) + has_entry( PathToTestFile( 'cache_test' ), + has_entries( { 'mtime': old_mtime, + 'includes': contains_exactly( has_properties( { + 'name': 'foo.h', + 'entry_type': 1 + } ) ) } ) ) ) + + new_includes = include_cache.GetIncludes( PathToTestFile( 'cache_test' ) ) + new_mtime = os.path.getmtime( PathToTestFile( 'cache_test' ) ) + + assert_that( new_mtime, equal_to( old_mtime ) ) + assert_that( new_includes, contains_exactly( has_properties( { + 'name': 'foo.h', + 'entry_type': 1 + } ) ) ) assert_that( include_cache._cache, - has_entry( tmp_dir, has_entries( { - 'mtime': new_mtime, - 'includes': contains_inanyorder( - has_properties( { - 'name': 'foo', - 'entry_type': 1 - } ), - has_properties( { - 'name': 'bar', - 'entry_type': 1 - } ) ) - } ) ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + has_entry( PathToTestFile( 'cache_test' ), + has_entries( { 'mtime': new_mtime, + 'includes': contains_exactly( has_properties( { + 'name': 'foo.h', + 'entry_type': 1 + } ) ) } ) ) ) + + + def test_IncludeCache_Cached_NewMtime( self ): + with TemporaryTestDir() as tmp_dir: + include_cache = IncludeCache() + assert_that( include_cache._cache, equal_to( {} ) ) + foo_path = os.path.join( tmp_dir, 'foo' ) + with open( foo_path, 'w' ) as foo_file: + foo_file.write( 'foo' ) + + old_includes = include_cache.GetIncludes( tmp_dir ) + old_mtime = os.path.getmtime( tmp_dir ) + assert_that( old_includes, contains_exactly( has_properties( { + 'name': 'foo', + 'entry_type': 1 + } ) ) ) + assert_that( include_cache._cache, + has_entry( tmp_dir, + has_entries( { + 'mtime': old_mtime, + 'includes': contains_exactly( has_properties( { + 'name': 'foo', + 'entry_type': 1 + } ) ) + } ) ) ) + + sleep( 2 ) + + bar_path = os.path.join( tmp_dir, 'bar' ) + with open( bar_path, 'w' ) as bar_file: + bar_file.write( 'bar' ) + + new_includes = include_cache.GetIncludes( tmp_dir ) + new_mtime = os.path.getmtime( tmp_dir ) + assert_that( old_mtime, not_( equal_to( new_mtime ) ) ) + assert_that( new_includes, contains_inanyorder( + has_properties( { + 'name': 'foo', + 'entry_type': 1 + } ), + has_properties( { + 'name': 'bar', + 'entry_type': 1 + } ) + ) ) + assert_that( include_cache._cache, + has_entry( tmp_dir, has_entries( { + 'mtime': new_mtime, + 'includes': contains_inanyorder( + has_properties( { + 'name': 'foo', + 'entry_type': 1 + } ), + has_properties( { + 'name': 'bar', + 'entry_type': 1 + } ) ) + } ) ) ) diff --git a/ycmd/tests/clang/signature_help_test.py b/ycmd/tests/clang/signature_help_test.py index 03d0793b3c..39b7dfe672 100644 --- a/ycmd/tests/clang/signature_help_test.py +++ b/ycmd/tests/clang/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -15,58 +15,55 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -from hamcrest import ( assert_that, empty, has_entries, has_items ) +from hamcrest import assert_that, empty, has_entries, has_items +from unittest import TestCase from ycmd.utils import ReadFile -from ycmd.tests.clang import PathToTestFile, SharedYcmd +from ycmd.tests.clang import PathToTestFile, SharedYcmd, setUpModule # noqa from ycmd.tests.test_utils import ( EMPTY_SIGNATURE_HELP, BuildRequest, CompletionEntryMatcher ) -@SharedYcmd -def SignatureHelp_NotImplemented_test( app ): - app.post_json( - '/load_extra_conf_file', - { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) +class SignatureHelpTest( TestCase ): + @SharedYcmd + def test_SignatureHelp_NotImplemented( self, app ): + app.post_json( + '/load_extra_conf_file', + { 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } ) - filepath = PathToTestFile( 'unity.cc' ) - contents = ReadFile( filepath ) + filepath = PathToTestFile( 'unity.cc' ) + contents = ReadFile( filepath ) - app.post_json( '/event_notification', - BuildRequest( filepath = filepath, - contents = contents, - filetype = 'cpp', - event_name = 'FileReadyToParse' ) ) + app.post_json( '/event_notification', + BuildRequest( filepath = filepath, + contents = contents, + filetype = 'cpp', + event_name = 'FileReadyToParse' ) ) - # Doing a completion proves that we have semantic parsing working - response_data = app.post_json( '/completions', - BuildRequest( filepath = filepath, - contents = contents, - filetype = 'cpp', - line_num = 27, - column_num = 11, - force_semantic = True ) ).json + # Doing a completion proves that we have semantic parsing working + response_data = app.post_json( '/completions', + BuildRequest( filepath = filepath, + contents = contents, + filetype = 'cpp', + line_num = 27, + column_num = 11, + force_semantic = True ) ).json - assert_that( response_data[ 'completions' ], - has_items( CompletionEntryMatcher( 'an_int' ), - CompletionEntryMatcher( 'a_char' ) ) ) + assert_that( response_data[ 'completions' ], + has_items( CompletionEntryMatcher( 'an_int' ), + CompletionEntryMatcher( 'a_char' ) ) ) - # Signature help request always returns nothing - # FIXME: A method to say "don't bother sending more signature help request" - response_data = app.post_json( '/signature_help', - BuildRequest( filepath = filepath, - contents = contents, - filetype = 'cpp', - line_num = 24, - column_num = 19 ) ).json + # Signature help request always returns nothing + # FIXME: A method to say "don't bother sending more signature help request" + response_data = app.post_json( '/signature_help', + BuildRequest( filepath = filepath, + contents = contents, + filetype = 'cpp', + line_num = 24, + column_num = 19 ) ).json - assert_that( response_data, has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP - } ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + assert_that( response_data, has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP + } ) ) diff --git a/ycmd/tests/clang/subcommands_test.py b/ycmd/tests/clang/subcommands_test.py index 09884c3206..5c304e9702 100644 --- a/ycmd/tests/clang/subcommands_test.py +++ b/ycmd/tests/clang/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -19,18 +19,20 @@ contains_inanyorder, contains_string, empty, equal_to, has_entry, has_entries, raises, matches_regexp ) from unittest.mock import patch +from unittest import TestCase from pprint import pprint from webtest import AppError -import pytest +import itertools import requests import os.path from ycmd import handlers from ycmd.completers.cpp.clang_completer import ( NO_DOCUMENTATION_MESSAGE, PARSING_FILE_MESSAGE ) -from ycmd.tests.clang import ( MockCoreClangCompleter, +from ycmd.tests.clang import ( MockCoreClangCompleter, # noqa PathToTestFile, - SharedYcmd ) + SharedYcmd, + setUpModule ) from ycmd.tests.test_utils import ( BuildRequest, ErrorMatcher, ChunkMatcher, @@ -39,60 +41,6 @@ from ycmd.utils import ReadFile -@SharedYcmd -def Subcommands_DefinedSubcommands_test( app ): - subcommands_data = BuildRequest( completer_target = 'cpp' ) - assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_inanyorder( - 'ClearCompilationFlagCache', - 'FixIt', - 'GetDoc', - 'GetDocImprecise', - 'GetParent', - 'GetType', - 'GetTypeImprecise', - 'GoTo', - 'GoToDeclaration', - 'GoToDefinition', - 'GoToImprecise', - 'GoToInclude' ) ) - - -@SharedYcmd -def Subcommands_GoTo_ZeroBasedLineAndColumn_test( app ): - contents = ReadFile( PathToTestFile( - 'GoTo_Clang_ZeroBasedLineAndColumn_test.cc' ) ) - - goto_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GoToDefinition' ], - compilation_flags = [ '-x', 'c++' ], - line_num = 10, - column_num = 3, - contents = contents, - filetype = 'cpp' ) - - assert_that( app.post_json( '/run_completer_command', goto_data ).json, - LocationMatcher( os.path.abspath( '/foo' ), 2, 8 ) ) - - -@SharedYcmd -def Subcommands_GoTo_CUDA_test( app ): - filepath = PathToTestFile( 'cuda', 'basic.cu' ) - contents = ReadFile( filepath ) - - goto_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GoToDefinition' ], - compilation_flags = [ '-x', 'cuda' ], - line_num = 8, - column_num = 3, - filepath = filepath, - contents = contents, - filetype = 'cuda' ) - - assert_that( app.post_json( '/run_completer_command', goto_data ).json, - LocationMatcher( filepath, 4, 17 ) ) - - def RunGoToTest_all( app, filename, command, test ): contents = ReadFile( PathToTestFile( filename ) ) common_request = { @@ -137,181 +85,6 @@ def RunGoToTest_all( app, filename, command, test ): assert_that( response, equal_to( actual_response ) ) -@SharedYcmd -def Subcommands_GoTo_all_test( app ): - # GoToDeclaration - tests = [ - # Local::x -> definition/declaration of x - { 'request': [ 23, 21 ], 'response': [ 4, 9 ] }, - # Local::in_line -> definition/declaration of Local::in_line - { 'request': [ 24, 26 ], 'response': [ 6, 10 ] }, - # Local -> definition/declaration of Local - { 'request': [ 24, 16 ], 'response': [ 2, 11 ] }, - # Local::out_of_line -> declaration of Local::out_of_line - { 'request': [ 25, 27 ], 'response': [ 11, 10 ] }, - # GoToDeclaration on definition of out_of_line moves to declaration - { 'request': [ 14, 13 ], 'response': [ 11, 10 ] }, - # main -> declaration of main - { 'request': [ 21, 7 ], 'response': [ 19, 5 ] }, - # Unicøde - { 'request': [ 34, 8 ], 'response': [ 32, 26 ] }, - # Another_Unicøde - { 'request': [ 36, 25 ], 'response': [ 32, 54 ] }, - { 'request': [ 38, 3 ], 'response': [ 36, 28 ] }, - ] - - for test in tests: - RunGoToTest_all( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToDeclaration' ], - test ) - - # GoToDefinition - tests = [ - # Local::x -> declaration/definition of x - { 'request': [ 23, 21 ], 'response': [ 4, 9 ] }, - # Local::in_line -> declaration/definition of Local::in_line - { 'request': [ 24, 26 ], 'response': [ 6, 10 ] }, - # Local -> declaration/definition of Local - { 'request': [ 24, 16 ], 'response': [ 2, 11 ] }, - # Local::out_of_line -> definition of Local::out_of_line - { 'request': [ 25, 27 ], 'response': [ 14, 13 ] }, - # GoToDefinition on definition of out_of_line moves to itself - { 'request': [ 14, 13 ], 'response': [ 14, 13 ] }, - # main -> definition of main (not declaration) - { 'request': [ 21, 7 ], 'response': [ 21, 5 ] }, - # Unicøde - { 'request': [ 34, 8 ], 'response': [ 32, 26 ] }, - ] - - for test in tests: - RunGoToTest_all( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToDefinition' ], - test ) - - # GoTo - tests = [ - # Local::x -> declaration/definition of x - { 'request': [ 23, 21 ], 'response': [ 4, 9 ] }, - # Local::in_line -> declaration/definition of Local::in_line - { 'request': [ 24, 26 ], 'response': [ 6, 10 ] }, - # Local -> declaration/definition of Local - { 'request': [ 24, 16 ], 'response': [ 2, 11 ] }, - # Local::out_of_line -> definition of Local::out_of_line - { 'request': [ 25, 27 ], 'response': [ 14, 13 ] }, - # GoTo on definition of out_of_line moves to declaration - { 'request': [ 14, 13 ], 'response': [ 11, 10 ] }, - # GoTo on declaration of out_of_line moves to definition - { 'request': [ 11, 17 ], 'response': [ 14, 13 ] }, - # main -> definition of main - { 'request': [ 21, 7 ], 'response': [ 19, 5 ] }, - # Unicøde - { 'request': [ 34, 8 ], 'response': [ 32, 26 ] }, - # Another_Unicøde - { 'request': [ 36, 25 ], 'response': [ 32, 54 ] }, - { 'request': [ 38, 3 ], 'response': [ 36, 28 ] }, - ] - - for test in tests: - RunGoToTest_all( app, - 'GoTo_all_Clang_test.cc', - [ 'GoTo' ], - test ) - - # GoToImprecise - identical to GoTo - tests = [ - # Local::x -> declaration/definition of x - { 'request': [ 23, 21 ], 'response': [ 4, 9 ] }, - # Local::in_line -> declaration/definition of Local::in_line - { 'request': [ 24, 26 ], 'response': [ 6, 10 ] }, - # Local -> declaration/definition of Local - { 'request': [ 24, 16 ], 'response': [ 2, 11 ] }, - # Local::out_of_line -> definition of Local::out_of_line - { 'request': [ 25, 27 ], 'response': [ 14, 13 ] }, - # GoToImprecise on definition of out_of_line moves to declaration - { 'request': [ 14, 13 ], 'response': [ 11, 10 ] }, - # GoToImprecise on declaration of out_of_line moves to definition - { 'request': [ 11, 17 ], 'response': [ 14, 13 ] }, - # main -> definition of main - { 'request': [ 21, 7 ], 'response': [ 19, 5 ] }, - # Unicøde - { 'request': [ 34, 8 ], 'response': [ 32, 26 ] }, - # Another_Unicøde - { 'request': [ 36, 25 ], 'response': [ 32, 54 ] }, - { 'request': [ 38, 3 ], 'response': [ 36, 28 ] }, - ] - - for test in tests: - RunGoToTest_all( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToImprecise' ], - test ) - - -@SharedYcmd -def Subcommands_GoTo_all_Fail_test( app ): - cursor_on_nothing = { 'request': [ 13, 1 ], 'response': [ 1, 1 ] } - cursor_on_another_unicode = { 'request': [ 36, 17 ], 'response': [ 1, 1 ] } - cursor_on_keyword = { 'request': [ 16, 6 ], 'response': [ 1, 1 ] } - - # GoToDeclaration - assert_that( - calling( RunGoToTest_all ).with_args( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToDeclaration' ], - cursor_on_nothing ), - raises( AppError, r'Can\\\'t jump to declaration.' ) ) - assert_that( - calling( RunGoToTest_all ).with_args( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToDeclaration' ], - cursor_on_keyword ), - raises( AppError, r'Can\\\'t jump to declaration.' ) ) - - # GoToDefinition - assert_that( - calling( RunGoToTest_all ).with_args( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToDefinition' ], - cursor_on_nothing ), - raises( AppError, r'Can\\\'t jump to definition.' ) ) - assert_that( - calling( RunGoToTest_all ).with_args( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToDefinition' ], - cursor_on_another_unicode ), - raises( AppError, r'Can\\\'t jump to definition.' ) ) - - # GoTo - assert_that( - calling( RunGoToTest_all ).with_args( app, - 'GoTo_all_Clang_test.cc', - [ 'GoTo' ], - cursor_on_nothing ), - raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) - assert_that( - calling( RunGoToTest_all ).with_args( app, - 'GoTo_all_Clang_test.cc', - [ 'GoTo' ], - cursor_on_keyword ), - raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) - - # GoToImprecise - assert_that( - calling( RunGoToTest_all ).with_args( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToImprecise' ], - cursor_on_nothing ), - raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) - assert_that( - calling( RunGoToTest_all ).with_args( app, - 'GoTo_all_Clang_test.cc', - [ 'GoToImprecise' ], - cursor_on_keyword ), - raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) - - def RunGoToIncludeTest( app, command, test ): app.post_json( '/load_extra_conf_file', @@ -337,87 +110,6 @@ def RunGoToIncludeTest( app, command, test ): assert_that( response, equal_to( actual_response ) ) -@pytest.mark.parametrize( 'cmd', [ 'GoToInclude', 'GoTo', 'GoToImprecise' ] ) -@pytest.mark.parametrize( 'test', [ - { 'request': [ 1, 1 ], 'response': 'a.hpp' }, - { 'request': [ 2, 1 ], 'response': os.path.join( 'system', 'a.hpp' ) }, - { 'request': [ 3, 1 ], 'response': os.path.join( 'quote', 'b.hpp' ) }, - { 'request': [ 5, 1 ], 'response': os.path.join( 'system', 'c.hpp' ) }, - { 'request': [ 6, 1 ], 'response': os.path.join( 'system', 'c.hpp' ) }, - { 'request': [ 7, 1 ], 'response': os.path.join( 'Frameworks', - 'OpenGL.framework', - 'Headers', - 'gl.h' ) }, - { 'request': [ 8, 1 ], 'response': os.path.join( 'Frameworks', - 'OpenGL.framework', - 'Headers', - 'gl.h' ) }, - ] ) -@SharedYcmd -def Subcommands_GoToInclude_test( app, cmd, test ): - RunGoToIncludeTest( app, cmd, test ) - - -@SharedYcmd -def Subcommands_GoToInclude_Fail_test( app ): - test = { 'request': [ 4, 1 ], 'response': '' } - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoToInclude', test ), - raises( AppError, 'Include file not found.' ) ) - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoTo', test ), - raises( AppError, 'Include file not found.' ) ) - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoToImprecise', test ), - raises( AppError, 'Include file not found.' ) ) - - test = { 'request': [ 9, 1 ], 'response': '' } - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoToInclude', test ), - raises( AppError, 'Not an include/import line.' ) ) - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoTo', test ), - raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoToImprecise', test ), - raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) - - # Unclosed #include statement. - test = { 'request': [ 12, 13 ], 'response': '' } - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoToInclude', test ), - raises( AppError, 'Not an include/import line.' ) ) - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoTo', test ), - raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) - assert_that( - calling( RunGoToIncludeTest ).with_args( app, - 'GoToImprecise', test ), - raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) - - -@SharedYcmd -def Subcommands_GoTo_Unity_test( app ): - RunGoToTest_all( app, 'unitya.cc', [ 'GoToDeclaration' ], { - 'request': [ 8, 21 ], - 'response': [ 1, 8, 'unity.cc' ], - 'extra_conf': [ '.ycm_extra_conf.py' ], - } ) - RunGoToTest_all( app, 'unitya.cc', [ 'GoToInclude' ], { - 'request': [ 1, 14 ], - 'response': [ 1, 1, 'unity.h' ], - 'extra_conf': [ '.ycm_extra_conf.py' ], - } ) - - def RunGetSemanticTest( app, filepath, filetype, test, command ): contents = ReadFile( filepath ) language = { 'cpp': 'c++', 'cuda': 'cuda' } @@ -460,215 +152,6 @@ def RunGetSemanticTest( app, filepath, filetype, test, command ): assert_that( response, has_entry( 'message', expected ) ) -@pytest.mark.parametrize( 'test', [ - # Basic pod types - [ { 'line_num': 24, 'column_num': 3 }, 'Foo' ], - [ { 'line_num': 1, 'column_num': 1 }, 'Internal error: ' - 'cursor not valid' ], - [ { 'line_num': 12, 'column_num': 2 }, 'Foo' ], - [ { 'line_num': 12, 'column_num': 8 }, 'Foo' ], - [ { 'line_num': 12, 'column_num': 9 }, 'Foo' ], - [ { 'line_num': 12, 'column_num': 10 }, 'Foo' ], - [ { 'line_num': 13, 'column_num': 3 }, 'int' ], - [ { 'line_num': 13, 'column_num': 7 }, 'int' ], - [ { 'line_num': 15, 'column_num': 7 }, 'char' ], - - # Function - [ { 'line_num': 22, 'column_num': 2 }, 'int ()' ], - [ { 'line_num': 22, 'column_num': 6 }, 'int ()' ], - - # Declared and canonical type - # On Ns:: (Unknown) - [ { 'line_num': 25, 'column_num': 3 }, 'Unknown type' ], # sic - # On Type (Type) - [ { 'line_num': 25, 'column_num': 8 }, 'Ns::Type => Ns::BasicType' ], - # On "a" (Ns::Type) - [ { 'line_num': 25, 'column_num': 15 }, 'Ns::Type => Ns::BasicType' ], - [ { 'line_num': 26, 'column_num': 13 }, 'Ns::Type => Ns::BasicType' ], - - # Cursor on decl for refs & pointers - [ { 'line_num': 39, 'column_num': 3 }, 'Foo' ], - [ { 'line_num': 39, 'column_num': 11 }, 'Foo &' ], - [ { 'line_num': 39, 'column_num': 15 }, 'Foo' ], - [ { 'line_num': 40, 'column_num': 3 }, 'Foo' ], - [ { 'line_num': 40, 'column_num': 11 }, 'Foo *' ], - [ { 'line_num': 40, 'column_num': 18 }, 'Foo' ], - [ { 'line_num': 42, 'column_num': 3 }, 'const Foo &' ], - [ { 'line_num': 42, 'column_num': 16 }, 'const Foo &' ], - [ { 'line_num': 43, 'column_num': 3 }, 'const Foo *' ], - [ { 'line_num': 43, 'column_num': 16 }, 'const Foo *' ], - - # Cursor on usage - [ { 'line_num': 45, 'column_num': 13 }, 'const Foo' ], - [ { 'line_num': 45, 'column_num': 19 }, 'const int' ], - [ { 'line_num': 46, 'column_num': 13 }, 'const Foo *' ], - [ { 'line_num': 46, 'column_num': 20 }, 'const int' ], - [ { 'line_num': 47, 'column_num': 12 }, 'Foo' ], - [ { 'line_num': 47, 'column_num': 17 }, 'int' ], - [ { 'line_num': 48, 'column_num': 12 }, 'Foo *' ], - [ { 'line_num': 48, 'column_num': 18 }, 'int' ], - - # Auto in declaration - [ { 'line_num': 28, 'column_num': 3 }, 'Foo &' ], - [ { 'line_num': 28, 'column_num': 11 }, 'Foo &' ], - [ { 'line_num': 28, 'column_num': 18 }, 'Foo' ], - [ { 'line_num': 29, 'column_num': 3 }, 'Foo *' ], - [ { 'line_num': 29, 'column_num': 11 }, 'Foo *' ], - [ { 'line_num': 29, 'column_num': 18 }, 'Foo' ], - [ { 'line_num': 31, 'column_num': 3 }, 'const Foo &' ], - [ { 'line_num': 31, 'column_num': 16 }, 'const Foo &' ], - [ { 'line_num': 32, 'column_num': 3 }, 'const Foo *' ], - [ { 'line_num': 32, 'column_num': 16 }, 'const Foo *' ], - - # Auto in usage - [ { 'line_num': 34, 'column_num': 14 }, 'const Foo' ], - [ { 'line_num': 34, 'column_num': 21 }, 'const int' ], - [ { 'line_num': 35, 'column_num': 14 }, 'const Foo *' ], - [ { 'line_num': 35, 'column_num': 22 }, 'const int' ], - [ { 'line_num': 36, 'column_num': 13 }, 'Foo' ], - [ { 'line_num': 36, 'column_num': 19 }, 'int' ], - [ { 'line_num': 37, 'column_num': 13 }, 'Foo *' ], - [ { 'line_num': 37, 'column_num': 20 }, 'int' ], - - # Unicode - [ { 'line_num': 51, 'column_num': 13 }, 'Unicøde *' ], - - # Bound methods - # On Win32, methods pick up an __attribute__((thiscall)) to annotate their - # calling convention. This shows up in the type, which isn't ideal, but - # also prohibitively complex to try and strip out. - [ { 'line_num': 53, 'column_num': 15 }, - matches_regexp( r'int \(int\)(?: __attribute__\(\(thiscall\)\))?' ) ], - [ { 'line_num': 54, 'column_num': 18 }, - matches_regexp( r'int \(int\)(?: __attribute__\(\(thiscall\)\))?' ) ], - ] ) -@pytest.mark.parametrize( 'cmd', [ 'GetType', 'GoToImprecise' ] ) -@SharedYcmd -def Subcommands_GetType_test( app, cmd, test ): - RunGetSemanticTest( app, - PathToTestFile( 'GetType_Clang_test.cc' ), - 'cpp', - test, - [ 'GetType' ] ) - - -@SharedYcmd -def SubCommands_GetType_CUDA_test( app ): - test = [ { 'line_num': 8, 'column_num': 3, }, 'void ()' ] - RunGetSemanticTest( app, - PathToTestFile( 'cuda', 'basic.cu' ), - 'cuda', - test, - [ 'GetType' ] ) - - -@SharedYcmd -def SubCommands_GetType_Unity_test( app ): - test = [ - { - 'line_num': 10, - 'column_num': 25, - 'extra_conf': [ '.ycm_extra_conf.py' ] - }, - 'int' - ] - RunGetSemanticTest( app, - PathToTestFile( 'unitya.cc' ), - 'cpp', - test, - [ 'GetType' ] ) - - -@pytest.mark.parametrize( 'test', [ - [ { 'line_num': 1, 'column_num': 1 }, 'Internal error: ' - 'cursor not valid' ], - [ { 'line_num': 2, 'column_num': 8 }, - PathToTestFile( 'GetParent_Clang_test.cc' ) ], - - # The reported scope does not include parents - [ { 'line_num': 3, 'column_num': 11 }, 'A' ], - [ { 'line_num': 4, 'column_num': 13 }, 'B' ], - [ { 'line_num': 5, 'column_num': 13 }, 'B' ], - [ { 'line_num': 9, 'column_num': 17 }, 'do_z_inline()' ], - [ { 'line_num': 15, 'column_num': 22 }, 'do_anything(T &)' ], - [ { 'line_num': 19, 'column_num': 9 }, 'A' ], - [ { 'line_num': 20, 'column_num': 9 }, 'A' ], - [ { 'line_num': 22, 'column_num': 12 }, 'A' ], - [ { 'line_num': 23, 'column_num': 5 }, 'do_Z_inline()' ], - [ { 'line_num': 24, 'column_num': 12 }, 'do_Z_inline()' ], - [ { 'line_num': 28, 'column_num': 14 }, 'A' ], - - [ { 'line_num': 34, 'column_num': 1 }, 'do_anything(T &)' ], - [ { 'line_num': 39, 'column_num': 1 }, 'do_x()' ], - [ { 'line_num': 44, 'column_num': 1 }, 'do_y()' ], - [ { 'line_num': 49, 'column_num': 1 }, 'main()' ], - - # Lambdas report the name of the variable - [ { 'line_num': 49, 'column_num': 14 }, 'l' ], - [ { 'line_num': 50, 'column_num': 19 }, 'l' ], - [ { 'line_num': 51, 'column_num': 16 }, 'main()' ], - ] ) -@SharedYcmd -def Subcommands_GetParent_test( app, test ): - RunGetSemanticTest( app, - PathToTestFile( 'GetParent_Clang_test.cc' ), - 'cpp', - test, - [ 'GetParent' ] ) - - -def RunFixItTest( app, line, column, lang, file_path, check ): - contents = ReadFile( file_path ) - - language_options = { - 'cpp11': { - 'compilation_flags': [ '-x', - 'c++', - '-std=c++11', - '-Wall', - '-Wextra', - '-pedantic' ], - 'filetype' : 'cpp', - }, - 'cuda': { - 'compilation_flags': [ '-x', - 'cuda', - '-std=c++11', - '-Wall', - '-Wextra', - '-pedantic' ], - 'filetype' : 'cuda', - }, - 'objective-c': { - 'compilation_flags': [ '-x', - 'objective-c', - '-Wall', - '-Wextra' ], - 'filetype' : 'objc', - }, - } - - # Build the command arguments from the standard ones and the language-specific - # arguments. - args = { - 'completer_target' : 'filetype_default', - 'contents' : contents, - 'filepath' : file_path, - 'command_arguments': [ 'FixIt' ], - 'line_num' : line, - 'column_num' : column, - } - args.update( language_options[ lang ] ) - - # Get the fixes for the file. - event_data = BuildRequest( **args ) - - results = app.post_json( '/run_completer_command', event_data ).json - - pprint( results ) - check( results ) - - def FixIt_Check_cpp11_Ins( results ): # First fixit # switch(A()) { // expected-error{{explicit conversion to}} @@ -962,88 +445,48 @@ def FixIt_Check_cuda( results ): } ) ) -@pytest.mark.parametrize( 'test', [ - # L - # i C - # n o - # e l Lang File, Checker - [ 16, 0, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Ins ], - [ 16, 1, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Ins ], - [ 16, 10, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Ins ], - [ 25, 14, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_InsMultiLine ], - [ 25, 0, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_InsMultiLine ], - [ 35, 7, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Del ], - [ 40, 6, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Repl ], - [ 48, 3, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_DelAdd ], - - [ 5, 3, 'objective-c', PathToTestFile( 'FixIt_Clang_objc.m' ), - FixIt_Check_objc ], - [ 7, 1, 'objective-c', PathToTestFile( 'FixIt_Clang_objc.m' ), - FixIt_Check_objc_NoFixIt ], - - [ 3, 12, 'cuda', PathToTestFile( 'cuda', 'fixit_test.cu' ), - FixIt_Check_cuda ], - - # multiple errors on a single line; both with fixits - [ 54, 15, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiFirst ], - [ 54, 16, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiFirst ], - [ 54, 16, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiFirst ], - [ 54, 17, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiFirst ], - [ 54, 18, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiFirst ], - - # should put closest fix-it first? - [ 54, 51, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiSecond ], - [ 54, 52, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiSecond ], - [ 54, 53, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiSecond ], - - # unicode in line for fixit - [ 21, 16, 'cpp11', PathToTestFile( 'unicode.cc' ), - FixIt_Check_unicode_Ins ], - - # FixIt attached to a "child" diagnostic (i.e. a Note) - [ 60, 1, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Note ], - - # FixIt due to forced spell checking - [ 72, 9, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_SpellCheck ], - ] ) -@SharedYcmd -def Subcommands_FixIt_all_test( app, test ): - RunFixItTest( app, test[ 0 ], test[ 1 ], test[ 2 ], test[ 3 ], test[ 4 ] ) - - -@SharedYcmd -def Subcommands_FixIt_Unity_test( app ): - file_path = PathToTestFile( 'unitya.cc' ) +def RunFixItTest( app, line, column, lang, file_path, check ): + contents = ReadFile( file_path ) + + language_options = { + 'cpp11': { + 'compilation_flags': [ '-x', + 'c++', + '-std=c++11', + '-Wall', + '-Wextra', + '-pedantic' ], + 'filetype' : 'cpp', + }, + 'cuda': { + 'compilation_flags': [ '-x', + 'cuda', + '-std=c++11', + '-Wall', + '-Wextra', + '-pedantic' ], + 'filetype' : 'cuda', + }, + 'objective-c': { + 'compilation_flags': [ '-x', + 'objective-c', + '-Wall', + '-Wextra' ], + 'filetype' : 'objc', + }, + } + + # Build the command arguments from the standard ones and the language-specific + # arguments. args = { - 'filetype' : 'cpp', 'completer_target' : 'filetype_default', - 'contents' : ReadFile( file_path ), + 'contents' : contents, 'filepath' : file_path, 'command_arguments': [ 'FixIt' ], - 'line_num' : 11, - 'column_num' : 17, + 'line_num' : line, + 'column_num' : column, } - app.post_json( '/load_extra_conf_file', { - 'filepath': PathToTestFile( '.ycm_extra_conf.py' ), - } ) + args.update( language_options[ lang ] ) # Get the fixes for the file. event_data = BuildRequest( **args ) @@ -1051,105 +494,708 @@ def Subcommands_FixIt_Unity_test( app ): results = app.post_json( '/run_completer_command', event_data ).json pprint( results ) - assert_that( results, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'text': contains_string( "expected ';' after expression" ), - 'chunks': contains_exactly( - ChunkMatcher( ';', - LocationMatcher( file_path, 11, 18 ), - LocationMatcher( file_path, 11, 18 ) ), - ), - 'location': LocationMatcher( file_path, 11, 18 ), + check( results ) + + +def Subcommands_StillParsingError( app, command ): + filepath = PathToTestFile( 'test.cpp' ) + + data = BuildRequest( command_arguments = [ command ], + compilation_flags = [ '-x', 'c++' ], + line_num = 1, + column_num = 1, + filepath = filepath, + contents = '', + filetype = 'cpp' ) + + response = app.post_json( '/run_completer_command', + data, + expect_errors = True ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + pprint( response.json ) + + assert_that( response.json, ErrorMatcher( RuntimeError, + PARSING_FILE_MESSAGE ) ) + + +class SubcommandsTest( TestCase ): + @SharedYcmd + def test_Subcommands_DefinedSubcommands( self, app ): + subcommands_data = BuildRequest( completer_target = 'cpp' ) + assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, + contains_inanyorder( + 'ClearCompilationFlagCache', + 'FixIt', + 'GetDoc', + 'GetDocImprecise', + 'GetParent', + 'GetType', + 'GetTypeImprecise', + 'GoTo', + 'GoToDeclaration', + 'GoToDefinition', + 'GoToImprecise', + 'GoToInclude' ) ) + + + @SharedYcmd + def test_Subcommands_GoTo_ZeroBasedLineAndColumn( self, app ): + contents = ReadFile( PathToTestFile( + 'GoTo_Clang_ZeroBasedLineAndColumn_test.cc' ) ) + + goto_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GoToDefinition' ], + compilation_flags = [ '-x', 'c++' ], + line_num = 10, + column_num = 3, + contents = contents, + filetype = 'cpp' ) + + assert_that( app.post_json( '/run_completer_command', goto_data ).json, + LocationMatcher( os.path.abspath( '/foo' ), 2, 8 ) ) + + + @SharedYcmd + def test_Subcommands_GoTo_CUDA( self, app ): + filepath = PathToTestFile( 'cuda', 'basic.cu' ) + contents = ReadFile( filepath ) + + goto_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GoToDefinition' ], + compilation_flags = [ '-x', 'cuda' ], + line_num = 8, + column_num = 3, + filepath = filepath, + contents = contents, + filetype = 'cuda' ) + + assert_that( app.post_json( '/run_completer_command', goto_data ).json, + LocationMatcher( filepath, 4, 17 ) ) + + + @SharedYcmd + def test_Subcommands_GoTo_all( self, app ): + # GoToDeclaration + tests = [ + # Local::x -> definition/declaration of x + { 'request': [ 23, 21 ], 'response': [ 4, 9 ] }, + # Local::in_line -> definition/declaration of Local::in_line + { 'request': [ 24, 26 ], 'response': [ 6, 10 ] }, + # Local -> definition/declaration of Local + { 'request': [ 24, 16 ], 'response': [ 2, 11 ] }, + # Local::out_of_line -> declaration of Local::out_of_line + { 'request': [ 25, 27 ], 'response': [ 11, 10 ] }, + # GoToDeclaration on definition of out_of_line moves to declaration + { 'request': [ 14, 13 ], 'response': [ 11, 10 ] }, + # main -> declaration of main + { 'request': [ 21, 7 ], 'response': [ 19, 5 ] }, + # Unicøde + { 'request': [ 34, 8 ], 'response': [ 32, 26 ] }, + # Another_Unicøde + { 'request': [ 36, 25 ], 'response': [ 32, 54 ] }, + { 'request': [ 38, 3 ], 'response': [ 36, 28 ] }, + ] + + for test in tests: + RunGoToTest_all( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToDeclaration' ], + test ) + + # GoToDefinition + tests = [ + # Local::x -> declaration/definition of x + { 'request': [ 23, 21 ], 'response': [ 4, 9 ] }, + # Local::in_line -> declaration/definition of Local::in_line + { 'request': [ 24, 26 ], 'response': [ 6, 10 ] }, + # Local -> declaration/definition of Local + { 'request': [ 24, 16 ], 'response': [ 2, 11 ] }, + # Local::out_of_line -> definition of Local::out_of_line + { 'request': [ 25, 27 ], 'response': [ 14, 13 ] }, + # GoToDefinition on definition of out_of_line moves to itself + { 'request': [ 14, 13 ], 'response': [ 14, 13 ] }, + # main -> definition of main (not declaration) + { 'request': [ 21, 7 ], 'response': [ 21, 5 ] }, + # Unicøde + { 'request': [ 34, 8 ], 'response': [ 32, 26 ] }, + ] + + for test in tests: + RunGoToTest_all( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToDefinition' ], + test ) + + # GoTo + tests = [ + # Local::x -> declaration/definition of x + { 'request': [ 23, 21 ], 'response': [ 4, 9 ] }, + # Local::in_line -> declaration/definition of Local::in_line + { 'request': [ 24, 26 ], 'response': [ 6, 10 ] }, + # Local -> declaration/definition of Local + { 'request': [ 24, 16 ], 'response': [ 2, 11 ] }, + # Local::out_of_line -> definition of Local::out_of_line + { 'request': [ 25, 27 ], 'response': [ 14, 13 ] }, + # GoTo on definition of out_of_line moves to declaration + { 'request': [ 14, 13 ], 'response': [ 11, 10 ] }, + # GoTo on declaration of out_of_line moves to definition + { 'request': [ 11, 17 ], 'response': [ 14, 13 ] }, + # main -> definition of main + { 'request': [ 21, 7 ], 'response': [ 19, 5 ] }, + # Unicøde + { 'request': [ 34, 8 ], 'response': [ 32, 26 ] }, + # Another_Unicøde + { 'request': [ 36, 25 ], 'response': [ 32, 54 ] }, + { 'request': [ 38, 3 ], 'response': [ 36, 28 ] }, + ] + + for test in tests: + RunGoToTest_all( app, + 'GoTo_all_Clang_test.cc', + [ 'GoTo' ], + test ) + + # GoToImprecise - identical to GoTo + tests = [ + # Local::x -> declaration/definition of x + { 'request': [ 23, 21 ], 'response': [ 4, 9 ] }, + # Local::in_line -> declaration/definition of Local::in_line + { 'request': [ 24, 26 ], 'response': [ 6, 10 ] }, + # Local -> declaration/definition of Local + { 'request': [ 24, 16 ], 'response': [ 2, 11 ] }, + # Local::out_of_line -> definition of Local::out_of_line + { 'request': [ 25, 27 ], 'response': [ 14, 13 ] }, + # GoToImprecise on definition of out_of_line moves to declaration + { 'request': [ 14, 13 ], 'response': [ 11, 10 ] }, + # GoToImprecise on declaration of out_of_line moves to definition + { 'request': [ 11, 17 ], 'response': [ 14, 13 ] }, + # main -> definition of main + { 'request': [ 21, 7 ], 'response': [ 19, 5 ] }, + # Unicøde + { 'request': [ 34, 8 ], 'response': [ 32, 26 ] }, + # Another_Unicøde + { 'request': [ 36, 25 ], 'response': [ 32, 54 ] }, + { 'request': [ 38, 3 ], 'response': [ 36, 28 ] }, + ] + + for test in tests: + RunGoToTest_all( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToImprecise' ], + test ) + + + @SharedYcmd + def test_Subcommands_GoTo_all_Fail( self, app ): + cursor_on_nothing = { 'request': [ 13, 1 ], 'response': [ 1, 1 ] } + cursor_on_another_unicode = { 'request': [ 36, 17 ], 'response': [ 1, 1 ] } + cursor_on_keyword = { 'request': [ 16, 6 ], 'response': [ 1, 1 ] } + + # GoToDeclaration + assert_that( + calling( RunGoToTest_all ).with_args( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToDeclaration' ], + cursor_on_nothing ), + raises( AppError, r'Can\\\'t jump to declaration.' ) ) + assert_that( + calling( RunGoToTest_all ).with_args( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToDeclaration' ], + cursor_on_keyword ), + raises( AppError, r'Can\\\'t jump to declaration.' ) ) + + # GoToDefinition + assert_that( + calling( RunGoToTest_all ).with_args( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToDefinition' ], + cursor_on_nothing ), + raises( AppError, r'Can\\\'t jump to definition.' ) ) + assert_that( + calling( RunGoToTest_all ).with_args( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToDefinition' ], + cursor_on_another_unicode ), + raises( AppError, r'Can\\\'t jump to definition.' ) ) + + # GoTo + assert_that( + calling( RunGoToTest_all ).with_args( app, + 'GoTo_all_Clang_test.cc', + [ 'GoTo' ], + cursor_on_nothing ), + raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) + assert_that( + calling( RunGoToTest_all ).with_args( app, + 'GoTo_all_Clang_test.cc', + [ 'GoTo' ], + cursor_on_keyword ), + raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) + + # GoToImprecise + assert_that( + calling( RunGoToTest_all ).with_args( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToImprecise' ], + cursor_on_nothing ), + raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) + assert_that( + calling( RunGoToTest_all ).with_args( app, + 'GoTo_all_Clang_test.cc', + [ 'GoToImprecise' ], + cursor_on_keyword ), + raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) + + + @SharedYcmd + def test_Subcommands_GoToInclude( self, app ): + for cmd, test in itertools.product( + [ 'GoToInclude', 'GoTo', 'GoToImprecise' ], + [ + { 'request': [ 1, 1 ], 'response': 'a.hpp' }, + { 'request': [ 2, 1 ], 'response': os.path.join( 'system', 'a.hpp' ) }, + { 'request': [ 3, 1 ], 'response': os.path.join( 'quote', 'b.hpp' ) }, + { 'request': [ 5, 1 ], 'response': os.path.join( 'system', 'c.hpp' ) }, + { 'request': [ 6, 1 ], 'response': os.path.join( 'system', 'c.hpp' ) }, + { 'request': [ 7, 1 ], 'response': os.path.join( 'Frameworks', + 'OpenGL.framework', + 'Headers', + 'gl.h' ) }, + { 'request': [ 8, 1 ], 'response': os.path.join( 'Frameworks', + 'OpenGL.framework', + 'Headers', + 'gl.h' ) }, + ] ): + with self.subTest( cmd = cmd, test = test ): + RunGoToIncludeTest( app, cmd, test ) + + + @SharedYcmd + def test_Subcommands_GoToInclude_Fail( self, app ): + test = { 'request': [ 4, 1 ], 'response': '' } + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoToInclude', test ), + raises( AppError, 'Include file not found.' ) ) + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoTo', test ), + raises( AppError, 'Include file not found.' ) ) + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoToImprecise', test ), + raises( AppError, 'Include file not found.' ) ) + + test = { 'request': [ 9, 1 ], 'response': '' } + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoToInclude', test ), + raises( AppError, 'Not an include/import line.' ) ) + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoTo', test ), + raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoToImprecise', test ), + raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) + + # Unclosed #include statement. + test = { 'request': [ 12, 13 ], 'response': '' } + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoToInclude', test ), + raises( AppError, 'Not an include/import line.' ) ) + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoTo', test ), + raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) + assert_that( + calling( RunGoToIncludeTest ).with_args( app, + 'GoToImprecise', test ), + raises( AppError, r'Can\\\'t jump to definition or declaration.' ) ) + + + @SharedYcmd + def test_Subcommands_GoTo_Unity( self, app ): + RunGoToTest_all( app, 'unitya.cc', [ 'GoToDeclaration' ], { + 'request': [ 8, 21 ], + 'response': [ 1, 8, 'unity.cc' ], + 'extra_conf': [ '.ycm_extra_conf.py' ], + } ) + RunGoToTest_all( app, 'unitya.cc', [ 'GoToInclude' ], { + 'request': [ 1, 14 ], + 'response': [ 1, 1, 'unity.h' ], + 'extra_conf': [ '.ycm_extra_conf.py' ], + } ) + + + @SharedYcmd + def test_Subcommands_GetType( self, app ): + for cmd, test in itertools.product( + [ 'GetType', 'GetTypeImprecise' ], + [ + # Basic pod types + [ { 'line_num': 24, 'column_num': 3 }, 'Foo' ], + [ { 'line_num': 1, 'column_num': 1 }, 'Internal error: ' + 'cursor not valid' ], + [ { 'line_num': 12, 'column_num': 2 }, 'Foo' ], + [ { 'line_num': 12, 'column_num': 8 }, 'Foo' ], + [ { 'line_num': 12, 'column_num': 9 }, 'Foo' ], + [ { 'line_num': 12, 'column_num': 10 }, 'Foo' ], + [ { 'line_num': 13, 'column_num': 3 }, 'int' ], + [ { 'line_num': 13, 'column_num': 7 }, 'int' ], + [ { 'line_num': 15, 'column_num': 7 }, 'char' ], + + # Function + [ { 'line_num': 22, 'column_num': 2 }, 'int ()' ], + [ { 'line_num': 22, 'column_num': 6 }, 'int ()' ], + + # Declared and canonical type + # On Ns:: (Unknown) + [ { 'line_num': 25, 'column_num': 3 }, 'Unknown type' ], # sic + # On Type (Type) + [ { 'line_num': 25, 'column_num': 8 }, + 'Ns::Type => Ns::BasicType' ], + # On "a" (Ns::Type) + [ { 'line_num': 25, 'column_num': 15 }, + 'Ns::Type => Ns::BasicType' ], + [ { 'line_num': 26, 'column_num': 13 }, + 'Ns::Type => Ns::BasicType' ], + + # Cursor on decl for refs & pointers + [ { 'line_num': 39, 'column_num': 3 }, 'Foo' ], + [ { 'line_num': 39, 'column_num': 11 }, 'Foo &' ], + [ { 'line_num': 39, 'column_num': 15 }, 'Foo' ], + [ { 'line_num': 40, 'column_num': 3 }, 'Foo' ], + [ { 'line_num': 40, 'column_num': 11 }, 'Foo *' ], + [ { 'line_num': 40, 'column_num': 18 }, 'Foo' ], + [ { 'line_num': 42, 'column_num': 3 }, 'const Foo &' ], + [ { 'line_num': 42, 'column_num': 16 }, 'const Foo &' ], + [ { 'line_num': 43, 'column_num': 3 }, 'const Foo *' ], + [ { 'line_num': 43, 'column_num': 16 }, 'const Foo *' ], + + # Cursor on usage + [ { 'line_num': 45, 'column_num': 13 }, 'const Foo' ], + [ { 'line_num': 45, 'column_num': 19 }, 'const int' ], + [ { 'line_num': 46, 'column_num': 13 }, 'const Foo *' ], + [ { 'line_num': 46, 'column_num': 20 }, 'const int' ], + [ { 'line_num': 47, 'column_num': 12 }, 'Foo' ], + [ { 'line_num': 47, 'column_num': 17 }, 'int' ], + [ { 'line_num': 48, 'column_num': 12 }, 'Foo *' ], + [ { 'line_num': 48, 'column_num': 18 }, 'int' ], + + # Auto in declaration + [ { 'line_num': 28, 'column_num': 3 }, 'Foo &' ], + [ { 'line_num': 28, 'column_num': 11 }, 'Foo &' ], + [ { 'line_num': 28, 'column_num': 18 }, 'Foo' ], + [ { 'line_num': 29, 'column_num': 3 }, 'Foo *' ], + [ { 'line_num': 29, 'column_num': 11 }, 'Foo *' ], + [ { 'line_num': 29, 'column_num': 18 }, 'Foo' ], + [ { 'line_num': 31, 'column_num': 3 }, 'const Foo &' ], + [ { 'line_num': 31, 'column_num': 16 }, 'const Foo &' ], + [ { 'line_num': 32, 'column_num': 3 }, 'const Foo *' ], + [ { 'line_num': 32, 'column_num': 16 }, 'const Foo *' ], + + # Auto in usage + [ { 'line_num': 34, 'column_num': 14 }, 'const Foo' ], + [ { 'line_num': 34, 'column_num': 21 }, 'const int' ], + [ { 'line_num': 35, 'column_num': 14 }, 'const Foo *' ], + [ { 'line_num': 35, 'column_num': 22 }, 'const int' ], + [ { 'line_num': 36, 'column_num': 13 }, 'Foo' ], + [ { 'line_num': 36, 'column_num': 19 }, 'int' ], + [ { 'line_num': 37, 'column_num': 13 }, 'Foo *' ], + [ { 'line_num': 37, 'column_num': 20 }, 'int' ], + + # Unicode + [ { 'line_num': 51, 'column_num': 13 }, 'Unicøde *' ], + + # Bound methods On Win32, methods pick up an __attribute__((thiscall)) + # to annotate their calling convention. This shows up in the type, + # which isn't ideal, but also prohibitively complex to try and strip + # out. + [ { 'line_num': 53, 'column_num': 15 }, + matches_regexp( r'int \(int\)(?: ' + r'__attribute__\(\(thiscall\)\))?' ) ], + [ { 'line_num': 54, 'column_num': 18 }, + matches_regexp( r'int \(int\)(?: ' + r'__attribute__\(\(thiscall\)\))?' ) ], + ] ): + with self.subTest( cmd = cmd, test = test ): + RunGetSemanticTest( app, + PathToTestFile( 'GetType_Clang_test.cc' ), + 'cpp', + test, + [ cmd ] ) + + + @SharedYcmd + def test_SubCommands_GetType_CUDA( self, app ): + test = [ { 'line_num': 8, 'column_num': 3, }, 'void ()' ] + RunGetSemanticTest( app, + PathToTestFile( 'cuda', 'basic.cu' ), + 'cuda', + test, + [ 'GetType' ] ) + + + @SharedYcmd + def test_SubCommands_GetType_Unity( self, app ): + test = [ + { + 'line_num': 10, + 'column_num': 25, + 'extra_conf': [ '.ycm_extra_conf.py' ] + }, + 'int' + ] + RunGetSemanticTest( app, + PathToTestFile( 'unitya.cc' ), + 'cpp', + test, + [ 'GetType' ] ) + + + @SharedYcmd + def test_Subcommands_GetParent( self, app ): + for test in [ + [ { 'line_num': 1, 'column_num': 1 }, 'Internal error: ' + 'cursor not valid' ], + [ { 'line_num': 2, 'column_num': 8 }, + PathToTestFile( 'GetParent_Clang_test.cc' ) ], + + # The reported scope does not include parents + [ { 'line_num': 3, 'column_num': 11 }, 'A' ], + [ { 'line_num': 4, 'column_num': 13 }, 'B' ], + [ { 'line_num': 5, 'column_num': 13 }, 'B' ], + [ { 'line_num': 9, 'column_num': 17 }, 'do_z_inline()' ], + [ { 'line_num': 15, 'column_num': 22 }, 'do_anything(T &)' ], + [ { 'line_num': 19, 'column_num': 9 }, 'A' ], + [ { 'line_num': 20, 'column_num': 9 }, 'A' ], + [ { 'line_num': 22, 'column_num': 12 }, 'A' ], + [ { 'line_num': 23, 'column_num': 5 }, 'do_Z_inline()' ], + [ { 'line_num': 24, 'column_num': 12 }, 'do_Z_inline()' ], + [ { 'line_num': 28, 'column_num': 14 }, 'A' ], + + [ { 'line_num': 34, 'column_num': 1 }, 'do_anything(T &)' ], + [ { 'line_num': 39, 'column_num': 1 }, 'do_x()' ], + [ { 'line_num': 44, 'column_num': 1 }, 'do_y()' ], + [ { 'line_num': 49, 'column_num': 1 }, 'main()' ], + + # Lambdas report the name of the variable + [ { 'line_num': 49, 'column_num': 14 }, 'l' ], + [ { 'line_num': 50, 'column_num': 19 }, 'l' ], + [ { 'line_num': 51, 'column_num': 16 }, 'main()' ], + ]: + with self.subTest( test = test ): + RunGetSemanticTest( app, + PathToTestFile( 'GetParent_Clang_test.cc' ), + 'cpp', + test, + [ 'GetParent' ] ) + + + @SharedYcmd + def test_Subcommands_FixIt_all( self, app ): + for test in [ + # L + # i C + # n o + # e l Lang File, Checker + [ 16, 0, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Ins ], + [ 16, 1, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Ins ], + [ 16, 10, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Ins ], + [ 25, 14, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_InsMultiLine ], + [ 25, 0, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_InsMultiLine ], + [ 35, 7, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Del ], + [ 40, 6, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Repl ], + [ 48, 3, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_DelAdd ], + + [ 5, 3, 'objective-c', PathToTestFile( 'FixIt_Clang_objc.m' ), + FixIt_Check_objc ], + [ 7, 1, 'objective-c', PathToTestFile( 'FixIt_Clang_objc.m' ), + FixIt_Check_objc_NoFixIt ], + + [ 3, 12, 'cuda', PathToTestFile( 'cuda', 'fixit_test.cu' ), + FixIt_Check_cuda ], + + # multiple errors on a single line; both with fixits + [ 54, 15, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiFirst ], + [ 54, 16, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiFirst ], + [ 54, 16, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiFirst ], + [ 54, 17, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiFirst ], + [ 54, 18, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiFirst ], + + # should put closest fix-it first? + [ 54, 51, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiSecond ], + [ 54, 52, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiSecond ], + [ 54, 53, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiSecond ], + + # unicode in line for fixit + [ 21, 16, 'cpp11', PathToTestFile( 'unicode.cc' ), + FixIt_Check_unicode_Ins ], + + # FixIt attached to a "child" diagnostic (i.e. a Note) + [ 60, 1, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Note ], + + # FixIt due to forced spell checking + [ 72, 9, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_SpellCheck ], + ]: + with self.subTest( test = test ): + RunFixItTest( app, + test[ 0 ], + test[ 1 ], + test[ 2 ], + test[ 3 ], + test[ 4 ] ) + + + @SharedYcmd + def test_Subcommands_FixIt_Unity( self, app ): + file_path = PathToTestFile( 'unitya.cc' ) + args = { + 'filetype' : 'cpp', + 'completer_target' : 'filetype_default', + 'contents' : ReadFile( file_path ), + 'filepath' : file_path, + 'command_arguments': [ 'FixIt' ], + 'line_num' : 11, + 'column_num' : 17, + } + app.post_json( '/load_extra_conf_file', { + 'filepath': PathToTestFile( '.ycm_extra_conf.py' ), + } ) + + # Get the fixes for the file. + event_data = BuildRequest( **args ) + + results = app.post_json( '/run_completer_command', event_data ).json + + pprint( results ) + assert_that( results, has_entries( { + 'fixits': contains_exactly( has_entries( { + 'text': contains_string( "expected ';' after expression" ), + 'chunks': contains_exactly( + ChunkMatcher( ';', + LocationMatcher( file_path, 11, 18 ), + LocationMatcher( file_path, 11, 18 ) ), + ), + 'location': LocationMatcher( file_path, 11, 18 ), + } ) ) } ) ) - } ) ) -@SharedYcmd -def Subcommands_FixIt_UnityDifferentFile_test( app ): - # This checks that we only return FixIt for the requested file, not a fixit on - # the same line in a different file - file_path = PathToTestFile( 'unity.cc' ) - args = { - 'filetype' : 'cpp', - 'completer_target' : 'filetype_default', - 'contents' : ReadFile( file_path ), - 'filepath' : file_path, - 'command_arguments': [ 'FixIt' ], - 'line_num' : 11, - 'column_num' : 17, - } - app.post_json( '/load_extra_conf_file', { - 'filepath': PathToTestFile( '.ycm_extra_conf.py' ), - } ) + @SharedYcmd + def test_Subcommands_FixIt_UnityDifferentFile( self, app ): + # This checks that we only return FixIt for the requested file, not a fixit + # on the same line in a different file + file_path = PathToTestFile( 'unity.cc' ) + args = { + 'filetype' : 'cpp', + 'completer_target' : 'filetype_default', + 'contents' : ReadFile( file_path ), + 'filepath' : file_path, + 'command_arguments': [ 'FixIt' ], + 'line_num' : 11, + 'column_num' : 17, + } + app.post_json( '/load_extra_conf_file', { + 'filepath': PathToTestFile( '.ycm_extra_conf.py' ), + } ) - # Get the fixes for the file. - event_data = BuildRequest( **args ) + # Get the fixes for the file. + event_data = BuildRequest( **args ) - results = app.post_json( '/run_completer_command', event_data ).json + results = app.post_json( '/run_completer_command', event_data ).json - pprint( results ) - assert_that( results, has_entries( { - 'fixits': empty() - } ) ) + pprint( results ) + assert_that( results, has_entries( { + 'fixits': empty() + } ) ) -@SharedYcmd -def Subcommands_FixIt_NonExistingFile_test( app ): - # This checks that FixIt is working for a non-existing file and that the path - # is properly normalized ('.' and '..' are removed from the path). - file_path = PathToTestFile( 'non_existing_dir', '..', '.', 'non_existing.cc' ) - normal_file_path = PathToTestFile( 'non_existing.cc' ) - args = { - 'filetype' : 'cpp', - 'completer_target' : 'filetype_default', - 'contents' : 'int test', - 'filepath' : file_path, - 'command_arguments': [ 'FixIt' ], - 'line_num' : 1, - 'column_num' : 1, - } - app.post_json( '/load_extra_conf_file', { - 'filepath': PathToTestFile( '.ycm_extra_conf.py' ), - } ) + @SharedYcmd + def test_Subcommands_FixIt_NonExistingFile( self, app ): + # This checks that FixIt is working for a non-existing file and that the + # path is properly normalized ('.' and '..' are removed from the path). + file_path = PathToTestFile( 'non_existing_dir', + '..', + '.', + 'non_existing.cc' ) + normal_file_path = PathToTestFile( 'non_existing.cc' ) + args = { + 'filetype' : 'cpp', + 'completer_target' : 'filetype_default', + 'contents' : 'int test', + 'filepath' : file_path, + 'command_arguments': [ 'FixIt' ], + 'line_num' : 1, + 'column_num' : 1, + } + app.post_json( '/load_extra_conf_file', { + 'filepath': PathToTestFile( '.ycm_extra_conf.py' ), + } ) - # Get the fixes for the file. - event_data = BuildRequest( **args ) + # Get the fixes for the file. + event_data = BuildRequest( **args ) - results = app.post_json( '/run_completer_command', event_data ).json + results = app.post_json( '/run_completer_command', event_data ).json - pprint( results ) - assert_that( results, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'text': contains_string( "expected ';' after top level declarator" ), - 'chunks': contains_exactly( - ChunkMatcher( ';', - LocationMatcher( normal_file_path, 1, 9 ), - LocationMatcher( normal_file_path, 1, 9 ) ), - ), - 'location': LocationMatcher( normal_file_path, 1, 9 ), + pprint( results ) + assert_that( results, has_entries( { + 'fixits': contains_exactly( has_entries( { + 'text': contains_string( "expected ';' after top level declarator" ), + 'chunks': contains_exactly( + ChunkMatcher( ';', + LocationMatcher( normal_file_path, 1, 9 ), + LocationMatcher( normal_file_path, 1, 9 ) ), + ), + 'location': LocationMatcher( normal_file_path, 1, 9 ), + } ) ) } ) ) - } ) ) -@SharedYcmd -def Subcommands_GetDoc_Variable_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_Variable( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 70, - column_num = 24, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 70, + column_num = 24, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', event_data ).json + response = app.post_json( '/run_completer_command', event_data ).json - pprint( response ) + pprint( response ) - assert_that( response, has_entry( - 'detailed_info', """\ + assert_that( response, has_entry( + 'detailed_info', """\ char a_global_variable This really is a global variable. Type: char @@ -1160,25 +1206,25 @@ def Subcommands_GetDoc_Variable_test( app ): The first line of comment is the brief.""" ) ) -@SharedYcmd -def Subcommands_GetDoc_Method_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_Method( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 22, - column_num = 13, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 22, + column_num = 13, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', event_data ).json + response = app.post_json( '/run_completer_command', event_data ).json - pprint( response ) + pprint( response ) - assert_that( response, has_entry( - 'detailed_info', """\ + assert_that( response, has_entry( + 'detailed_info', """\ char with_brief() brevity is for suckers Type: char () @@ -1193,24 +1239,24 @@ def Subcommands_GetDoc_Method_test( app ): """ ) ) -@SharedYcmd -def Subcommands_GetDoc_Namespace_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_Namespace( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 65, - column_num = 14, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 65, + column_num = 14, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', event_data ).json + response = app.post_json( '/run_completer_command', event_data ).json - pprint( response ) + pprint( response ) - assert_that( response, has_entry( + assert_that( response, has_entry( 'detailed_info', """\ namespace Test {} This is a test namespace @@ -1220,73 +1266,73 @@ def Subcommands_GetDoc_Namespace_test( app ): This is a test namespace""" ) ) # noqa -@SharedYcmd -def Subcommands_GetDoc_Undocumented_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_Undocumented( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 81, - column_num = 17, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 81, + column_num = 17, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, - ErrorMatcher( ValueError, NO_DOCUMENTATION_MESSAGE ) ) + assert_that( response.json, + ErrorMatcher( ValueError, NO_DOCUMENTATION_MESSAGE ) ) -@SharedYcmd -def Subcommands_GetDoc_NoCursor_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_NoCursor( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 1, - column_num = 1, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 1, + column_num = 1, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, - ErrorMatcher( ValueError, NO_DOCUMENTATION_MESSAGE ) ) + assert_that( response.json, + ErrorMatcher( ValueError, NO_DOCUMENTATION_MESSAGE ) ) -@SharedYcmd -def Subcommands_GetDoc_SystemHeaders_test( app ): - app.post_json( '/load_extra_conf_file', { - 'filepath': PathToTestFile( 'get_doc', '.ycm_extra_conf.py' ) } ) + @SharedYcmd + def test_Subcommands_GetDoc_SystemHeaders( self, app ): + app.post_json( '/load_extra_conf_file', { + 'filepath': PathToTestFile( 'get_doc', '.ycm_extra_conf.py' ) } ) - filepath = PathToTestFile( 'get_doc', 'test.cpp' ) - contents = ReadFile( filepath ) + filepath = PathToTestFile( 'get_doc', 'test.cpp' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - line_num = 4, - column_num = 7, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + line_num = 4, + column_num = 7, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', event_data ).json + response = app.post_json( '/run_completer_command', event_data ).json - assert_that( response, - has_entry( 'detailed_info', """\ + assert_that( response, + has_entry( 'detailed_info', """\ int test() This is a function. Type: int () @@ -1299,33 +1345,33 @@ def Subcommands_GetDoc_SystemHeaders_test( app ): """ ) ) -# Following tests repeat the tests above, but without re-parsing the file -@SharedYcmd -def Subcommands_GetDocImprecise_Variable_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + # Following tests repeat the tests above, but without re-parsing the file + @SharedYcmd + def test_Subcommands_GetDocImprecise_Variable( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) + + app.post_json( '/event_notification', + BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + contents = contents, + event_name = 'FileReadyToParse' ) ) - app.post_json( '/event_notification', - BuildRequest( filepath = filepath, + event_data = BuildRequest( filepath = filepath, filetype = 'cpp', compilation_flags = [ '-x', 'c++' ], + line_num = 70, + column_num = 24, contents = contents, - event_name = 'FileReadyToParse' ) ) + command_arguments = [ 'GetDocImprecise' ] ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 70, - column_num = 24, - contents = contents, - command_arguments = [ 'GetDocImprecise' ] ) + response = app.post_json( '/run_completer_command', event_data ).json - response = app.post_json( '/run_completer_command', event_data ).json + pprint( response ) - pprint( response ) - - assert_that( response, has_entry( - 'detailed_info', """\ + assert_that( response, has_entry( + 'detailed_info', """\ char a_global_variable This really is a global variable. Type: char @@ -1336,34 +1382,34 @@ def Subcommands_GetDocImprecise_Variable_test( app ): The first line of comment is the brief.""" ) ) -@SharedYcmd -def Subcommands_GetDocImprecise_Method_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDocImprecise_Method( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - app.post_json( - '/event_notification', - BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - contents = contents, - event_name = 'FileReadyToParse' ) - ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 22, - column_num = 13, - contents = contents, - command_arguments = [ 'GetDocImprecise' ] ) - - response = app.post_json( '/run_completer_command', event_data ).json + app.post_json( + '/event_notification', + BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + contents = contents, + event_name = 'FileReadyToParse' ) + ) - pprint( response ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 22, + column_num = 13, + contents = contents, + command_arguments = [ 'GetDocImprecise' ] ) - assert_that( response, has_entry( - 'detailed_info', """\ + response = app.post_json( '/run_completer_command', event_data ).json + + pprint( response ) + + assert_that( response, has_entry( + 'detailed_info', """\ char with_brief() brevity is for suckers Type: char () @@ -1378,33 +1424,33 @@ def Subcommands_GetDocImprecise_Method_test( app ): """ ) ) -@SharedYcmd -def Subcommands_GetDocImprecise_Namespace_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDocImprecise_Namespace( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - app.post_json( - '/event_notification', - BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - contents = contents, - event_name = 'FileReadyToParse' ) - ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 65, - column_num = 14, - contents = contents, - command_arguments = [ 'GetDocImprecise' ] ) - - response = app.post_json( '/run_completer_command', event_data ).json + app.post_json( + '/event_notification', + BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + contents = contents, + event_name = 'FileReadyToParse' ) + ) - pprint( response ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 65, + column_num = 14, + contents = contents, + command_arguments = [ 'GetDocImprecise' ] ) + + response = app.post_json( '/run_completer_command', event_data ).json + + pprint( response ) - assert_that( response, has_entry( + assert_that( response, has_entry( 'detailed_info', """\ namespace Test {} This is a test namespace @@ -1414,89 +1460,89 @@ def Subcommands_GetDocImprecise_Namespace_test( app ): This is a test namespace""" ) ) # noqa -@SharedYcmd -def Subcommands_GetDocImprecise_Undocumented_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDocImprecise_Undocumented( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - app.post_json( - '/event_notification', - BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - contents = contents, - event_name = 'FileReadyToParse' ) - ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 81, - column_num = 17, - contents = contents, - command_arguments = [ 'GetDocImprecise' ] ) + app.post_json( + '/event_notification', + BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + contents = contents, + event_name = 'FileReadyToParse' ) + ) - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 81, + column_num = 17, + contents = contents, + command_arguments = [ 'GetDocImprecise' ] ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) - assert_that( response.json, - ErrorMatcher( ValueError, NO_DOCUMENTATION_MESSAGE ) ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, + ErrorMatcher( ValueError, NO_DOCUMENTATION_MESSAGE ) ) -@SharedYcmd -def Subcommands_GetDocImprecise_NoCursor_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) - app.post_json( - '/event_notification', - BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - contents = contents, - event_name = 'FileReadyToParse' ) - ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 1, - column_num = 1, - contents = contents, - command_arguments = [ 'GetDocImprecise' ] ) + @SharedYcmd + def test_Subcommands_GetDocImprecise_NoCursor( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) + app.post_json( + '/event_notification', + BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + contents = contents, + event_name = 'FileReadyToParse' ) + ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 1, + column_num = 1, + contents = contents, + command_arguments = [ 'GetDocImprecise' ] ) - assert_that( response.json, - ErrorMatcher( ValueError, NO_DOCUMENTATION_MESSAGE ) ) + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) -@SharedYcmd -def Subcommands_GetDocImprecise_NoReadyToParse_test( app ): - filepath = PathToTestFile( 'GetDoc_Clang.cc' ) - contents = ReadFile( filepath ) + assert_that( response.json, + ErrorMatcher( ValueError, NO_DOCUMENTATION_MESSAGE ) ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 11, - column_num = 18, - contents = contents, - command_arguments = [ 'GetDocImprecise' ] ) - response = app.post_json( '/run_completer_command', event_data ).json + @SharedYcmd + def test_Subcommands_GetDocImprecise_NoReadyToParse( self, app ): + filepath = PathToTestFile( 'GetDoc_Clang.cc' ) + contents = ReadFile( filepath ) - assert_that( response, has_entry( - 'detailed_info', """\ + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 11, + column_num = 18, + contents = contents, + command_arguments = [ 'GetDocImprecise' ] ) + + response = app.post_json( '/run_completer_command', event_data ).json + + assert_that( response, has_entry( + 'detailed_info', """\ int get_a_global_variable(bool test) This is a method which is only pretend global Type: int (bool) @@ -1506,25 +1552,25 @@ def Subcommands_GetDocImprecise_NoReadyToParse_test( app ): @param test Set this to true. Do it.""" ) ) -@SharedYcmd -def Subcommands_GetDocImprecise_SystemHeaders_test( app ): - app.post_json( '/load_extra_conf_file', { - 'filepath': PathToTestFile( 'get_doc', '.ycm_extra_conf.py' ) } ) + @SharedYcmd + def test_Subcommands_GetDocImprecise_SystemHeaders( self, app ): + app.post_json( '/load_extra_conf_file', { + 'filepath': PathToTestFile( 'get_doc', '.ycm_extra_conf.py' ) } ) - filepath = PathToTestFile( 'get_doc', 'test.cpp' ) - contents = ReadFile( filepath ) + filepath = PathToTestFile( 'get_doc', 'test.cpp' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - line_num = 4, - column_num = 7, - contents = contents, - command_arguments = [ 'GetDocImprecise' ] ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + line_num = 4, + column_num = 7, + contents = contents, + command_arguments = [ 'GetDocImprecise' ] ) - response = app.post_json( '/run_completer_command', event_data ).json + response = app.post_json( '/run_completer_command', event_data ).json - assert_that( response, - has_entry( 'detailed_info', """\ + assert_that( response, + has_entry( 'detailed_info', """\ int test() This is a function. Type: int () @@ -1537,26 +1583,26 @@ def Subcommands_GetDocImprecise_SystemHeaders_test( app ): """ ) ) -@SharedYcmd -def Subcommands_GetDoc_Unicode_test( app ): - filepath = PathToTestFile( 'unicode.cc' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_Unicode( self, app ): + filepath = PathToTestFile( 'unicode.cc' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cpp', - compilation_flags = [ '-x', 'c++' ], - line_num = 21, - column_num = 16, - contents = contents, - command_arguments = [ 'GetDoc' ], - completer_target = 'filetype_default' ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cpp', + compilation_flags = [ '-x', 'c++' ], + line_num = 21, + column_num = 16, + contents = contents, + command_arguments = [ 'GetDoc' ], + completer_target = 'filetype_default' ) - response = app.post_json( '/run_completer_command', event_data ).json + response = app.post_json( '/run_completer_command', event_data ).json - pprint( response ) + pprint( response ) - assert_that( response, has_entry( - 'detailed_info', """\ + assert_that( response, has_entry( + 'detailed_info', """\ int member_with_å_unicøde This method has unicøde in it Type: int @@ -1567,26 +1613,26 @@ def Subcommands_GetDoc_Unicode_test( app ): """ ) ) -@SharedYcmd -def Subcommands_GetDoc_CUDA_test( app ): - filepath = PathToTestFile( 'cuda', 'basic.cu' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_CUDA( self, app ): + filepath = PathToTestFile( 'cuda', 'basic.cu' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cuda', - compilation_flags = [ '-x', 'cuda' ], - line_num = 8, - column_num = 3, - contents = contents, - command_arguments = [ 'GetDoc' ], - completer_target = 'filetype_default' ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cuda', + compilation_flags = [ '-x', 'cuda' ], + line_num = 8, + column_num = 3, + contents = contents, + command_arguments = [ 'GetDoc' ], + completer_target = 'filetype_default' ) - response = app.post_json( '/run_completer_command', event_data ).json + response = app.post_json( '/run_completer_command', event_data ).json - pprint( response ) + pprint( response ) - assert_that( response, has_entry( - 'detailed_info', """\ + assert_that( response, has_entry( + 'detailed_info', """\ void kernel() This is a test kernel Type: void () @@ -1595,46 +1641,17 @@ def Subcommands_GetDoc_CUDA_test( app ): This is a test kernel""" ) ) -def Subcommands_StillParsingError( app, command ): - filepath = PathToTestFile( 'test.cpp' ) - - data = BuildRequest( command_arguments = [ command ], - compilation_flags = [ '-x', 'c++' ], - line_num = 1, - column_num = 1, - filepath = filepath, - contents = '', - filetype = 'cpp' ) - - response = app.post_json( '/run_completer_command', - data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - pprint( response.json ) - - assert_that( response.json, ErrorMatcher( RuntimeError, - PARSING_FILE_MESSAGE ) ) - - -@SharedYcmd -def Subcommands_StillParsingError_test( app ): - completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) - with patch.object( completer, '_completer', MockCoreClangCompleter() ): - Subcommands_StillParsingError( app, 'FixIt' ) - Subcommands_StillParsingError( app, 'GetDoc' ) - Subcommands_StillParsingError( app, 'GetDocImprecise' ) - Subcommands_StillParsingError( app, 'GetParent' ) - Subcommands_StillParsingError( app, 'GetType' ) - Subcommands_StillParsingError( app, 'GetTypeImprecise' ) - Subcommands_StillParsingError( app, 'GoTo' ) - Subcommands_StillParsingError( app, 'GoToDeclaration' ) - Subcommands_StillParsingError( app, 'GoToDefinition' ) - Subcommands_StillParsingError( app, 'GoToImprecise' ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_Subcommands_StillParsingError( self, app ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) + with patch.object( completer, '_completer', MockCoreClangCompleter() ): + Subcommands_StillParsingError( app, 'FixIt' ) + Subcommands_StillParsingError( app, 'GetDoc' ) + Subcommands_StillParsingError( app, 'GetDocImprecise' ) + Subcommands_StillParsingError( app, 'GetParent' ) + Subcommands_StillParsingError( app, 'GetType' ) + Subcommands_StillParsingError( app, 'GetTypeImprecise' ) + Subcommands_StillParsingError( app, 'GoTo' ) + Subcommands_StillParsingError( app, 'GoToDeclaration' ) + Subcommands_StillParsingError( app, 'GoToDefinition' ) + Subcommands_StillParsingError( app, 'GoToImprecise' ) diff --git a/ycmd/tests/clangd/__init__.py b/ycmd/tests/clangd/__init__.py index 13ea6e2637..e86e29c3f8 100644 --- a/ycmd/tests/clangd/__init__.py +++ b/ycmd/tests/clangd/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2020 ycmd contributors +# Copyright (C) 2018-2021 ycmd contributors # # This file is part of ycmd. # @@ -16,18 +16,59 @@ # along with ycmd. If not, see . import json +import os +import functools from hamcrest import assert_that, equal_to -from ycmd.tests.clangd.conftest import * # noqa +from ycmd.completers.cpp import clangd_completer from ycmd.tests.test_utils import ( CombineRequest, - WaitUntilCompleterServerReady ) + WaitUntilCompleterServerReady, + ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp, + StopCompleterServer ) from ycmd.utils import ReadFile shared_app = None +def setUpModule(): + global shared_app + shared_app = SetUpApp() + + +def tearDownModule(): + StopCompleterServer( shared_app, 'cpp' ) + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'cpp' ) + return Wrapper + return Decorator + + def RunAfterInitialized( app, test ): """Performs initialization of clangd server for the file contents specified in the |test| and optionally can run a test and check for its response. @@ -79,3 +120,8 @@ def RunAfterInitialized( app, test ): equal_to( test[ 'expect' ][ 'response' ] ) ) assert_that( response.json, test[ 'expect' ][ 'data' ] ) return response.json + + +def PathToTestFile( *args ): + dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) + return os.path.join( dir_of_current_script, 'testdata', *args ) diff --git a/ycmd/tests/clangd/conftest.py b/ycmd/tests/clangd/conftest.py deleted file mode 100644 index 8bf2468905..0000000000 --- a/ycmd/tests/clangd/conftest.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import os -import pytest -from ycmd.tests.test_utils import ( ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - SetUpApp, - StopCompleterServer ) -from ycmd.completers.cpp import clangd_completer -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - shared_app = SetUpApp() - yield - StopCompleterServer( shared_app, 'cpp' ) - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with IsolatedApp( request.param[ 1 ] ) as app: - clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - yield app - StopCompleterServer( app, 'cpp' ) - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def PathToTestFile( *args ): - dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) - return os.path.join( dir_of_current_script, 'testdata', *args ) diff --git a/ycmd/tests/clangd/debug_info_test.py b/ycmd/tests/clangd/debug_info_test.py index 0189d1958a..b5e4fcb435 100644 --- a/ycmd/tests/clangd/debug_info_test.py +++ b/ycmd/tests/clangd/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2020 ycmd contributors +# Copyright (C) 2011-2021 ycmd contributors # # This file is part of ycmd. # @@ -21,8 +21,9 @@ has_entries, has_entry, has_items ) +from unittest import TestCase -from ycmd.tests.clangd import ( IsolatedYcmd, PathToTestFile, SharedYcmd, +from ycmd.tests.clangd import ( IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule, tearDownModule, # noqa RunAfterInitialized ) from ycmd.tests.test_utils import ( BuildRequest, TemporaryClangProject, @@ -32,312 +33,367 @@ import os -@IsolatedYcmd() -def DebugInfo_NotInitialized_test( app ): - request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), - filetype = 'cpp' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'pid': None, - 'is_running': False, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Dead', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': None, - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': False, - } ), - ), - } ) ), - 'items': empty(), - } ) ) - ) +class DebugInfoTest( TestCase ): + @IsolatedYcmd() + def test_DebugInfo_NotInitialized( self, app ): + request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), + filetype = 'cpp' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'pid': None, + 'is_running': False, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Dead', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': None, + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': False, + } ), + ), + } ) ), + 'items': empty(), + } ) ) + ) -@SharedYcmd -def DebugInfo_Initialized_test( app ): - request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), - filetype = 'cpp' ) - test = { 'request': request_data } - RunAfterInitialized( app, test ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Initialized', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile(), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': False, - } ), - ), - } ) ), - 'items': empty() - } ) ) - ) + @SharedYcmd + def test_DebugInfo_Initialized( self, app ): + request_data = BuildRequest( filepath = PathToTestFile( 'basic.cpp' ), + filetype = 'cpp' ) + test = { 'request': request_data } + RunAfterInitialized( app, test ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Initialized', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile(), + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': False, + } ), + ), + } ) ), + 'items': empty() + } ) ) + ) -@IsolatedYcmd( { 'extra_conf_globlist': [ - PathToTestFile( 'extra_conf', '.ycm_extra_conf.py' ) ] } ) -def DebugInfo_ExtraConf_ReturningFlags_test( app ): - request_data = BuildRequest( filepath = PathToTestFile( 'extra_conf', - 'foo.cpp' ), - filetype = 'cpp' ) - test = { 'request': request_data } - RunAfterInitialized( app, test ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Initialized', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'extra_conf' ), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': has_items( '-I', 'include', '-DFOO' ), - } ), - ), - } ) ), - 'items': empty() - } ) ) - ) + @IsolatedYcmd( { 'extra_conf_globlist': [ + PathToTestFile( 'extra_conf', '.ycm_extra_conf.py' ) ] } ) + def test_DebugInfo_ExtraConf_ReturningFlags( self, app ): + request_data = BuildRequest( filepath = PathToTestFile( 'extra_conf', + 'foo.cpp' ), + filetype = 'cpp' ) + test = { 'request': request_data } + RunAfterInitialized( app, test ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Initialized', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'extra_conf' ), + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': has_items( '-I', 'include', '-DFOO' ), + } ), + ), + } ) ), + 'items': empty() + } ) ) + ) -@IsolatedYcmd( { 'extra_conf_globlist': [ - PathToTestFile( 'extra_conf', '.ycm_extra_conf.py' ) ] } ) -def DebugInfo_ExtraConf_NotReturningFlags_test( app ): - request_data = BuildRequest( filepath = PathToTestFile( 'extra_conf', - 'xyz.cpp' ), - filetype = 'cpp' ) - request_data[ 'contents' ] = '' - test = { 'request': request_data } - RunAfterInitialized( app, test ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Initialized', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'extra_conf' ), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': False - } ), - ), - } ) ), - 'items': empty() - } ) ) - ) + @IsolatedYcmd( { 'extra_conf_globlist': [ + PathToTestFile( 'extra_conf', '.ycm_extra_conf.py' ) ] } ) + def test_DebugInfo_ExtraConf_NotReturningFlags( self, app ): + request_data = BuildRequest( filepath = PathToTestFile( 'extra_conf', + 'xyz.cpp' ), + filetype = 'cpp' ) + request_data[ 'contents' ] = '' + test = { 'request': request_data } + RunAfterInitialized( app, test ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Initialized', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'extra_conf' ), + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': False + } ), + ), + } ) ), + 'items': empty() + } ) ) + ) -@IsolatedYcmd( { - 'global_ycm_extra_conf': PathToTestFile( 'extra_conf', - 'global_extra_conf.py' ), -} ) -def DebugInfo_ExtraConf_Global_test( app ): - request_data = BuildRequest( filepath = PathToTestFile( 'foo.cpp' ), - contents = '', - filetype = 'cpp' ) - test = { 'request': request_data } - request_data[ 'contents' ] = '' - RunAfterInitialized( app, test ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Initialized', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile(), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': has_items( '-I', 'test' ), - } ), - ), - } ) ), - 'items': empty() - } ) ) - ) + @IsolatedYcmd( { + 'global_ycm_extra_conf': PathToTestFile( 'extra_conf', + 'global_extra_conf.py' ), + } ) + def test_DebugInfo_ExtraConf_Global( self, app ): + request_data = BuildRequest( filepath = PathToTestFile( 'foo.cpp' ), + contents = '', + filetype = 'cpp' ) + test = { 'request': request_data } + request_data[ 'contents' ] = '' + RunAfterInitialized( app, test ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Initialized', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile(), + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': has_items( '-I', 'test' ), + } ), + ), + } ) ), + 'items': empty() + } ) ) + ) -@IsolatedYcmd( { - 'global_ycm_extra_conf': PathToTestFile( 'extra_conf', - 'global_extra_conf.py' ), - 'extra_conf_globlist': [ PathToTestFile( 'extra_conf', - '.ycm_extra_conf.py' ) ] -} ) -def DebugInfo_ExtraConf_LocalOverGlobal_test( app ): - request_data = BuildRequest( filepath = PathToTestFile( 'extra_conf', - 'foo.cpp' ), - filetype = 'cpp' ) - test = { 'request': request_data } - RunAfterInitialized( app, test ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Initialized', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'extra_conf' ), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': has_items( '-I', 'include', '-DFOO' ), - } ), - ), - } ) ), - 'items': empty() - } ) ) - ) + @IsolatedYcmd( { + 'global_ycm_extra_conf': PathToTestFile( 'extra_conf', + 'global_extra_conf.py' ), + 'extra_conf_globlist': [ PathToTestFile( 'extra_conf', + '.ycm_extra_conf.py' ) ] + } ) + def test_DebugInfo_ExtraConf_LocalOverGlobal( self, app ): + request_data = BuildRequest( filepath = PathToTestFile( 'extra_conf', + 'foo.cpp' ), + filetype = 'cpp' ) + test = { 'request': request_data } + RunAfterInitialized( app, test ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Initialized', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'extra_conf' ), + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': has_items( '-I', 'include', '-DFOO' ), + } ), + ), + } ) ), + 'items': empty() + } ) ) + ) -@IsolatedYcmd() -def DebugInfo_ExtraConf_Database_test( app ): - with TemporaryTestDir() as tmp_dir: - database = [ - { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -I test foo.cpp' , - 'file': os.path.join( tmp_dir, 'foo.cpp' ), - } - ] + @IsolatedYcmd() + def test_DebugInfo_ExtraConf_Database( self, app ): + with TemporaryTestDir() as tmp_dir: + database = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -I test foo.cpp' , + 'file': os.path.join( tmp_dir, 'foo.cpp' ), + } + ] - with TemporaryClangProject( tmp_dir, database ): - request_data = BuildRequest( filepath = os.path.join( tmp_dir, - 'foo.cpp' ), - filetype = 'cpp' ) - request_data[ 'contents' ] = '' - test = { 'request': request_data } - RunAfterInitialized( app, test ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Initialized', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': tmp_dir, - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': False - } ), - ), - } ) ), - 'items': empty() - } ) ) - ) + with TemporaryClangProject( tmp_dir, database ): + request_data = BuildRequest( filepath = os.path.join( tmp_dir, + 'foo.cpp' ), + filetype = 'cpp' ) + request_data[ 'contents' ] = '' + test = { 'request': request_data } + RunAfterInitialized( app, test ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Initialized', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': tmp_dir, + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': False + } ), + ), + } ) ), + 'items': empty() + } ) ) + ) -@IsolatedYcmd( { 'confirm_extra_conf': 0 } ) -def DebugInfo_ExtraConf_UseLocalOverDatabase_test( app ): - with TemporaryTestDir() as tmp_dir: - database = [ - { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -I test foo.cpp' , - 'file': os.path.join( tmp_dir, 'foo.cpp' ), - } - ] + @IsolatedYcmd( { 'confirm_extra_conf': 0 } ) + def test_DebugInfo_ExtraConf_UseLocalOverDatabase( self, app ): + with TemporaryTestDir() as tmp_dir: + database = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -I test foo.cpp' , + 'file': os.path.join( tmp_dir, 'foo.cpp' ), + } + ] - with TemporaryClangProject( tmp_dir, database ): - extra_conf = os.path.join( tmp_dir, '.ycm_extra_conf.py' ) - with open( extra_conf, 'w' ) as f: - f.write( ''' + with TemporaryClangProject( tmp_dir, database ): + extra_conf = os.path.join( tmp_dir, '.ycm_extra_conf.py' ) + with open( extra_conf, 'w' ) as f: + f.write( ''' def Settings( **kwargs ): return { 'flags': [ '-x', 'c++', '-I', 'ycm' ] } ''' ) - try: + try: + request_data = BuildRequest( filepath = os.path.join( tmp_dir, + 'foo.cpp' ), + filetype = 'cpp' ) + request_data[ 'contents' ] = '' + test = { 'request': request_data } + RunAfterInitialized( app, test ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Initialized', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': tmp_dir, + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': has_items( '-x', 'c++', '-I', 'ycm' ) + } ), + ), + } ) ), + 'items': empty() + } ) ) + ) + finally: + os.remove( extra_conf ) + + + @IsolatedYcmd( { + 'global_ycm_extra_conf': PathToTestFile( 'extra_conf', + 'global_extra_conf.py' ), + } ) + def test_DebugInfo_ExtraConf_UseDatabaseOverGlobal( self, app ): + with TemporaryTestDir() as tmp_dir: + database = [ + { + 'directory': tmp_dir, + 'command': 'clang++ -x c++ -I test foo.cpp' , + 'file': os.path.join( tmp_dir, 'foo.cpp' ), + } + ] + + with TemporaryClangProject( tmp_dir, database ): request_data = BuildRequest( filepath = os.path.join( tmp_dir, 'foo.cpp' ), filetype = 'cpp' ) @@ -366,109 +422,50 @@ def Settings( **kwargs ): } ), has_entries( { 'key': 'Compilation Command', - 'value': has_items( '-x', 'c++', '-I', 'ycm' ) + 'value': False } ), ), } ) ), 'items': empty() } ) ) ) - finally: - os.remove( extra_conf ) -@IsolatedYcmd( { - 'global_ycm_extra_conf': PathToTestFile( 'extra_conf', - 'global_extra_conf.py' ), -} ) -def DebugInfo_ExtraConf_UseDatabaseOverGlobal_test( app ): - with TemporaryTestDir() as tmp_dir: - database = [ - { - 'directory': tmp_dir, - 'command': 'clang++ -x c++ -I test foo.cpp' , - 'file': os.path.join( tmp_dir, 'foo.cpp' ), - } - ] - - with TemporaryClangProject( tmp_dir, database ): - request_data = BuildRequest( filepath = os.path.join( tmp_dir, + @MacOnly + @IsolatedYcmd( { 'extra_conf_globlist': [ + PathToTestFile( 'extra_conf', '.ycm_extra_conf.py' ) ] } ) + def test_DebugInfo_ExtraConf_MacIncludeFlags( self, app ): + request_data = BuildRequest( filepath = PathToTestFile( 'extra_conf', 'foo.cpp' ), - filetype = 'cpp' ) - request_data[ 'contents' ] = '' - test = { 'request': request_data } - RunAfterInitialized( app, test ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Initialized', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': tmp_dir, - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': False - } ), - ), - } ) ), - 'items': empty() - } ) ) - ) - - -@MacOnly -@IsolatedYcmd( { 'extra_conf_globlist': [ - PathToTestFile( 'extra_conf', '.ycm_extra_conf.py' ) ] } ) -def DebugInfo_ExtraConf_MacIncludeFlags_test( app ): - request_data = BuildRequest( filepath = PathToTestFile( 'extra_conf', - 'foo.cpp' ), - filetype = 'cpp' ) - test = { 'request': request_data } - RunAfterInitialized( app, test ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': 'Initialized', - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'extra_conf' ), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}', - } ), - has_entries( { - 'key': 'Compilation Command', - 'value': has_items( '-isystem', '-iframework' ) - } ), - ), - } ) ), - 'items': empty() - } ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + filetype = 'cpp' ) + test = { 'request': request_data } + RunAfterInitialized( app, test ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Initialized', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'extra_conf' ), + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}', + } ), + has_entries( { + 'key': 'Compilation Command', + 'value': has_items( '-isystem', '-iframework' ) + } ), + ), + } ) ), + 'items': empty() + } ) ) + ) diff --git a/ycmd/tests/clangd/diagnostics_test.py b/ycmd/tests/clangd/diagnostics_test.py index 01c6cc0212..8673ddc472 100644 --- a/ycmd/tests/clangd/diagnostics_test.py +++ b/ycmd/tests/clangd/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -26,6 +26,7 @@ has_entry, has_items ) from unittest.mock import patch +from unittest import TestCase from pprint import pprint from ycmd.tests.clangd import ( IsolatedYcmd, @@ -41,9 +42,10 @@ from ycmd import handlers -@IsolatedYcmd() -def Diagnostics_ZeroBasedLineAndColumn_test( app ): - contents = """ +class DiagnosticsTest( TestCase ): + @IsolatedYcmd() + def test_Diagnostics_ZeroBasedLineAndColumn( self, app ): + contents = """ void foo() { double baz = "foo"; } @@ -51,30 +53,30 @@ def Diagnostics_ZeroBasedLineAndColumn_test( app ): // Padding to 5 lines """ - filepath = PathToTestFile( 'foo.cc' ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } - - test = { 'request': request, 'route': '/receive_messages' } - results = RunAfterInitialized( app, test ) - assert_that( results, contains_exactly( - has_entries( { 'diagnostics': contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'text': contains_string( 'Cannot initialize' ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 3, 10 ), ( 3, 13 ) ) ), - 'location': LocationMatcher( filepath, 3, 10 ), - 'location_extent': RangeMatcher( filepath, ( 3, 10 ), ( 3, 13 ) ) - } ) - ) } ) - ) ) - - -@IsolatedYcmd() -def Diagnostics_SimpleLocationExtent_test( app ): - contents = """ + filepath = PathToTestFile( 'foo.cc' ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } + + test = { 'request': request, 'route': '/receive_messages' } + results = RunAfterInitialized( app, test ) + assert_that( results, contains_exactly( + has_entries( { 'diagnostics': contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'text': contains_string( 'Cannot initialize' ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 3, 10 ), ( 3, 13 ) ) ), + 'location': LocationMatcher( filepath, 3, 10 ), + 'location_extent': RangeMatcher( filepath, ( 3, 10 ), ( 3, 13 ) ) + } ) + ) } ) + ) ) + + + @IsolatedYcmd() + def test_Diagnostics_SimpleLocationExtent( self, app ): + contents = """ void foo() { baz = 5; } @@ -82,483 +84,479 @@ def Diagnostics_SimpleLocationExtent_test( app ): // Padding to 5 lines """ - filepath = PathToTestFile( 'foo.cc' ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } - test = { 'request': request, 'route': '/receive_messages' } - results = RunAfterInitialized( app, test ) - assert_that( results, contains_exactly( - has_entries( { 'diagnostics': contains_exactly( - has_entries( { - 'location_extent': RangeMatcher( filepath, ( 3, 3 ), ( 3, 6 ) ) - } ) - ) } ) ) ) - - -@IsolatedYcmd() -def Diagnostics_PragmaOnceWarningIgnored_test( app ): - contents = """ -#pragma once - -struct Foo { - int x; - int y; - int c; - int d; -}; -""" + filepath = PathToTestFile( 'foo.cc' ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } + test = { 'request': request, 'route': '/receive_messages' } + results = RunAfterInitialized( app, test ) + assert_that( results, contains_exactly( + has_entries( { 'diagnostics': contains_exactly( + has_entries( { + 'location_extent': RangeMatcher( filepath, ( 3, 3 ), ( 3, 6 ) ) + } ) + ) } ) ) ) + + + @IsolatedYcmd() + def test_Diagnostics_PragmaOnceWarningIgnored( self, app ): + contents = """ + #pragma once + + struct Foo { + int x; + int y; + int c; + int d; + }; + """ - request = { 'contents': contents, - 'filepath': PathToTestFile( 'foo.h' ), - 'filetype': 'cpp' } - test = { 'request': request, 'route': '/receive_messages' } - response = RunAfterInitialized( app, test ) - assert_that( response, contains_exactly( - has_entries( { 'diagnostics': empty() } ) ) ) - - -@IsolatedYcmd() -def Diagnostics_Works_test( app ): - contents = """ -struct Foo { - int x // semicolon missing here! - int y; - int c; - int d; -}; -""" + request = { 'contents': contents, + 'filepath': PathToTestFile( 'foo.h' ), + 'filetype': 'cpp' } + test = { 'request': request, 'route': '/receive_messages' } + response = RunAfterInitialized( app, test ) + assert_that( response, contains_exactly( + has_entries( { 'diagnostics': empty() } ) ) ) + + + @IsolatedYcmd() + def test_Diagnostics_Works( self, app ): + contents = """ + struct Foo { + int x // semicolon missing here! + int y; + int c; + int d; + }; + """ - filepath = PathToTestFile( 'foo.cc' ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } + filepath = PathToTestFile( 'foo.cc' ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } - test = { 'request': request, 'route': '/receive_messages' } - RunAfterInitialized( app, test ) - diag_data = BuildRequest( line_num = 3, - contents = contents, - filepath = filepath, - filetype = 'cpp' ) + test = { 'request': request, 'route': '/receive_messages' } + RunAfterInitialized( app, test ) + diag_data = BuildRequest( line_num = 3, + contents = contents, + filepath = filepath, + filetype = 'cpp' ) - results = app.post_json( '/detailed_diagnostic', diag_data ).json - assert_that( results, - has_entry( 'message', contains_string( "Expected ';'" ) ) ) + results = app.post_json( '/detailed_diagnostic', diag_data ).json + assert_that( results, + has_entry( 'message', contains_string( "Expected ';'" ) ) ) -@IsolatedYcmd() -def Diagnostics_Multiline_test( app ): - contents = """ -struct Foo { - Foo(int z) {} -}; + @IsolatedYcmd() + def test_Diagnostics_Multiline( self, app ): + contents = """ + struct Foo { + Foo(int z) {} + }; -int main() { - Foo foo("goo"); -} -""" - - filepath = PathToTestFile( 'foo.cc' ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } - - test = { 'request': request, 'route': '/receive_messages' } - RunAfterInitialized( app, test ) - diag_data = BuildRequest( line_num = 7, - contents = contents, - filepath = filepath, - filetype = 'cpp' ) - - results = app.post_json( '/detailed_diagnostic', diag_data ).json - assert_that( results, - has_entry( 'message', contains_string( "\n" ) ) ) - - -@IsolatedYcmd() -def Diagnostics_FixIt_Available_test( app ): - filepath = PathToTestFile( 'FixIt_Clang_cpp11.cpp' ) - contents = ReadFile( filepath ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } - - test = { 'request': request, 'route': '/receive_messages' } - response = RunAfterInitialized( app, test ) - - pprint( response ) - - assert_that( response, has_items( - has_entries( { 'diagnostics': has_items( - has_entries( { - 'location': LocationMatcher( filepath, 16, 3 ), - 'text': contains_string( 'Switch condition type \'A\' ' - 'requires explicit conversion to \'int\'' ), - 'fixit_available': False - } ) - ) } ) - ) ) - - -@IsolatedYcmd() -def Diagnostics_MultipleMissingIncludes_test( app ): - filepath = PathToTestFile( 'multiple_missing_includes.cc' ) - contents = ReadFile( filepath ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } - - test = { 'request': request, 'route': '/receive_messages' } - response = RunAfterInitialized( app, test ) - - pprint( response ) - - assert_that( response, has_items( - has_entries( { 'diagnostics': has_items( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 1, 10 ), - 'text': equal_to( "'first_missing_include' file not found" - " [pp_file_not_found]" ), - 'fixit_available': False - } ) - ) } ) - ) ) - - -@IsolatedYcmd() -def Diagnostics_LocationExtent_MissingSemicolon_test( app ): - filepath = PathToTestFile( 'location_extent.cc' ) - contents = ReadFile( filepath ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } - - test = { 'request': request, 'route': '/receive_messages' } - response = RunAfterInitialized( app, test ) - - pprint( response ) - - assert_that( response, contains_exactly( - has_entries( { 'diagnostics': has_items( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 2, 9 ), - 'location_extent': RangeMatcher( filepath, ( 2, 9 ), ( 2, 9 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 2, 9 ), ( 2, 9 ) ) ), - 'text': equal_to( "Expected ';' at end of declaration list (fix " - "available) [expected_semi_decl_list]" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 5, 1 ), - 'location_extent': RangeMatcher( filepath, ( 5, 1 ), ( 6, 11 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 5, 1 ), ( 6, 11 ) ) ), - 'text': equal_to( "Unknown type name 'multiline_identifier'" - " [unknown_typename]" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 8, 7 ), - 'location_extent': RangeMatcher( filepath, ( 8, 7 ), ( 8, 11 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 8, 7 ), ( 8, 11 ) ) ), - 'text': equal_to( 'Constructor cannot have a return type' - ' [constructor_return_type]' ), - 'fixit_available': False - } ) - ) } ) - ) ) - - -@IsolatedYcmd() -def Diagnostics_CUDA_Kernel_test( app ): - filepath = PathToTestFile( 'cuda', 'kernel_call.cu' ) - contents = ReadFile( filepath ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cuda' } - - test = { 'request': request, 'route': '/receive_messages' } - response = RunAfterInitialized( app, test ) - - pprint( response ) - - assert_that( response, contains_exactly( - has_entries( { 'diagnostics': has_items( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 59, 5 ), - 'location_extent': RangeMatcher( filepath, ( 59, 5 ), ( 59, 6 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 59, 5 ), ( 59, 6 ) ) ), - 'text': equal_to( 'Call to global function \'g1\' not configured' - ' [global_call_not_config]' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 60, 9 ), - 'location_extent': RangeMatcher( filepath, ( 60, 9 ), ( 60, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 60, 9 ), ( 60, 12 ) ) ), - 'text': equal_to( 'Too few execution configuration arguments to kernel ' - 'function call, expected at least 2, have 1' - ' [typecheck_call_too_few_args_at_least]' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 61, 20 ), - 'location_extent': RangeMatcher( filepath, ( 61, 20 ), ( 61, 21 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 61, 20 ), ( 61, 21 ) ) - ), - 'text': equal_to( 'Too many execution configuration arguments to ' - 'kernel function call, expected at most 4, have 5' - ' [typecheck_call_too_many_args_at_most]' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 65, 15 ), - 'location_extent': RangeMatcher( filepath, ( 65, 15 ), ( 65, 16 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 65, 15 ), ( 65, 16 ) ) ), - 'text': equal_to( 'Kernel call to non-global function \'h1\'' - ' [kern_call_not_global_function]' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 68, 15 ), - 'location_extent': RangeMatcher( filepath, ( 68, 15 ), ( 68, 16 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 68, 15 ), ( 68, 16 ) ) ), - 'text': equal_to( "Kernel function type 'int (*)(int)' must have " - "void return type [kern_type_not_void_return]" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 70, 8 ), - 'location_extent': RangeMatcher( filepath, ( 70, 8 ), ( 70, 18 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 70, 8 ), ( 70, 18 ) ) ), - 'text': equal_to( "Use of undeclared identifier 'undeclared'" - ' [undeclared_var_use]' ), - 'fixit_available': False - } ), - ) } ) - ) ) - - -@IsolatedYcmd( { 'max_diagnostics_to_display': 1 } ) -def Diagnostics_MaximumDiagnosticsNumberExceeded_test( app ): - filepath = PathToTestFile( 'max_diagnostics.cc' ) - contents = ReadFile( filepath ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } - - test = { 'request': request, 'route': '/receive_messages' } - response = RunAfterInitialized( app, test ) - - pprint( response ) - - assert_that( response, contains_exactly( - has_entries( { 'diagnostics': has_items( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 3, 9 ), - 'location_extent': RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ) ), - 'text': contains_string( "Redefinition of 'test'" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 1, 1 ), - 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) ), - 'text': equal_to( 'Maximum number of diagnostics exceeded.' ), - 'fixit_available': False - } ) - ) } ) - ) ) - - -@IsolatedYcmd( { 'max_diagnostics_to_display': 0 } ) -def Diagnostics_NoLimitToNumberOfDiagnostics_test( app ): - filepath = PathToTestFile( 'max_diagnostics.cc' ) - contents = ReadFile( filepath ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } - - test = { 'request': request, 'route': '/receive_messages' } - response = RunAfterInitialized( app, test ) - - pprint( response ) - - assert_that( response, contains_exactly( - has_entries( { 'diagnostics': has_items( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 3, 9 ), - 'location_extent': RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ) ), - 'text': contains_string( "Redefinition of 'test'" ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 4, 9 ), - 'location_extent': RangeMatcher( filepath, ( 4, 9 ), ( 4, 13 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 4, 9 ), ( 4, 13 ) ) ), - 'text': contains_string( "Redefinition of 'test'" ), - 'fixit_available': False - } ) - ) } ) - ) ) - - -@IsolatedYcmd() -def Diagnostics_DiagsNotReady_test( app ): - completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) - contents = """ -struct Foo { - int x // semicolon missing here! - int y; - int c; - int d; -}; -""" + int main() { + Foo foo("goo"); + } + """ - filepath = PathToTestFile( 'foo.cc' ) - request = { 'contents': contents, - 'filepath': filepath, - 'filetype': 'cpp' } + filepath = PathToTestFile( 'foo.cc' ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } - test = { 'request': request, 'route': '/receive_messages' } - RunAfterInitialized( app, test ) - diag_data = BuildRequest( line_num = 3, - contents = contents, - filepath = filepath, - filetype = 'cpp' ) + test = { 'request': request, 'route': '/receive_messages' } + RunAfterInitialized( app, test ) + diag_data = BuildRequest( line_num = 7, + contents = contents, + filepath = filepath, + filetype = 'cpp' ) - with patch.object( completer, '_latest_diagnostics', None ): results = app.post_json( '/detailed_diagnostic', diag_data ).json assert_that( results, - has_entry( 'message', contains_string( "are not ready yet" ) ) ) + has_entry( 'message', contains_string( "\n" ) ) ) + + + @IsolatedYcmd() + def test_Diagnostics_FixIt_Available( self, app ): + filepath = PathToTestFile( 'FixIt_Clang_cpp11.cpp' ) + contents = ReadFile( filepath ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } + + test = { 'request': request, 'route': '/receive_messages' } + response = RunAfterInitialized( app, test ) + + pprint( response ) + + assert_that( response, has_items( + has_entries( { 'diagnostics': has_items( + has_entries( { + 'location': LocationMatcher( filepath, 16, 3 ), + 'text': contains_string( 'Switch condition type \'A\' ' + 'requires explicit conversion to \'int\'' ), + 'fixit_available': False + } ) + ) } ) + ) ) + + + @IsolatedYcmd() + def test_Diagnostics_MultipleMissingIncludes( self, app ): + filepath = PathToTestFile( 'multiple_missing_includes.cc' ) + contents = ReadFile( filepath ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } + + test = { 'request': request, 'route': '/receive_messages' } + response = RunAfterInitialized( app, test ) + pprint( response ) + + assert_that( response, has_items( + has_entries( { 'diagnostics': has_items( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 1, 10 ), + 'text': equal_to( "'first_missing_include' file not found" + " [pp_file_not_found]" ), + 'fixit_available': False + } ) + ) } ) + ) ) + + + @IsolatedYcmd() + def test_Diagnostics_LocationExtent_MissingSemicolon( self, app ): + filepath = PathToTestFile( 'location_extent.cc' ) + contents = ReadFile( filepath ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } + + test = { 'request': request, 'route': '/receive_messages' } + response = RunAfterInitialized( app, test ) + + pprint( response ) + + assert_that( response, contains_exactly( + has_entries( { 'diagnostics': has_items( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 2, 9 ), + 'location_extent': RangeMatcher( filepath, ( 2, 9 ), ( 2, 9 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 2, 9 ), ( 2, 9 ) ) ), + 'text': equal_to( "Expected ';' at end of declaration list (fix " + "available) [expected_semi_decl_list]" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 5, 1 ), + 'location_extent': RangeMatcher( filepath, ( 5, 1 ), ( 6, 11 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 5, 1 ), ( 6, 11 ) ) ), + 'text': equal_to( "Unknown type name 'multiline_identifier'" + " [unknown_typename]" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 8, 7 ), + 'location_extent': RangeMatcher( filepath, ( 8, 7 ), ( 8, 11 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 8, 7 ), ( 8, 11 ) ) ), + 'text': equal_to( 'Constructor cannot have a return type' + ' [constructor_return_type]' ), + 'fixit_available': False + } ) + ) } ) + ) ) + + + @IsolatedYcmd() + def test_Diagnostics_CUDA_Kernel( self, app ): + filepath = PathToTestFile( 'cuda', 'kernel_call.cu' ) + contents = ReadFile( filepath ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cuda' } + + test = { 'request': request, 'route': '/receive_messages' } + response = RunAfterInitialized( app, test ) + + pprint( response ) + + assert_that( response, contains_exactly( + has_entries( { 'diagnostics': has_items( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 59, 5 ), + 'location_extent': RangeMatcher( filepath, ( 59, 5 ), ( 59, 6 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 59, 5 ), ( 59, 6 ) ) ), + 'text': equal_to( 'Call to global function \'g1\' not configured' + ' [global_call_not_config]' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 60, 9 ), + 'location_extent': RangeMatcher( filepath, ( 60, 9 ), ( 60, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 60, 9 ), ( 60, 12 ) ) ), + 'text': equal_to( 'Too few execution configuration arguments to ' + 'kernel function call, expected at least 2, have 1' + ' [typecheck_call_too_few_args_at_least]' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 61, 20 ), + 'location_extent': RangeMatcher( filepath, ( 61, 20 ), ( 61, 21 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 61, 20 ), ( 61, 21 ) ) + ), + 'text': equal_to( 'Too many execution configuration arguments to ' + 'kernel function call, expected at most 4, have 5' + ' [typecheck_call_too_many_args_at_most]' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 65, 15 ), + 'location_extent': RangeMatcher( filepath, ( 65, 15 ), ( 65, 16 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 65, 15 ), ( 65, 16 ) ) ), + 'text': equal_to( 'Kernel call to non-global function \'h1\'' + ' [kern_call_not_global_function]' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 68, 15 ), + 'location_extent': RangeMatcher( filepath, ( 68, 15 ), ( 68, 16 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 68, 15 ), ( 68, 16 ) ) ), + 'text': equal_to( "Kernel function type 'int (*)(int)' must have " + "void return type [kern_type_not_void_return]" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 70, 8 ), + 'location_extent': RangeMatcher( filepath, ( 70, 8 ), ( 70, 18 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 70, 8 ), ( 70, 18 ) ) ), + 'text': equal_to( "Use of undeclared identifier 'undeclared'" + ' [undeclared_var_use]' ), + 'fixit_available': False + } ), + ) } ) + ) ) + + + @IsolatedYcmd( { 'max_diagnostics_to_display': 1 } ) + def test_Diagnostics_MaximumDiagnosticsNumberExceeded( self, app ): + filepath = PathToTestFile( 'max_diagnostics.cc' ) + contents = ReadFile( filepath ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } + + test = { 'request': request, 'route': '/receive_messages' } + response = RunAfterInitialized( app, test ) + + pprint( response ) -@UnixOnly -@IsolatedYcmd() -def Diagnostics_UpdatedOnBufferVisit_test( app ): - with TemporaryTestDir() as tmp_dir: - source_file = os.path.join( tmp_dir, 'source.cpp' ) - source_contents = """#include "header.h" + assert_that( response, contains_exactly( + has_entries( { 'diagnostics': has_items( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 3, 9 ), + 'location_extent': RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ) ), + 'text': contains_string( "Redefinition of 'test'" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 1, 1 ), + 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) ), + 'text': equal_to( 'Maximum number of diagnostics exceeded.' ), + 'fixit_available': False + } ) + ) } ) + ) ) + + + @IsolatedYcmd( { 'max_diagnostics_to_display': 0 } ) + def test_Diagnostics_NoLimitToNumberOfDiagnostics( self, app ): + filepath = PathToTestFile( 'max_diagnostics.cc' ) + contents = ReadFile( filepath ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } + + test = { 'request': request, 'route': '/receive_messages' } + response = RunAfterInitialized( app, test ) + + pprint( response ) + + assert_that( response, contains_exactly( + has_entries( { 'diagnostics': has_items( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 3, 9 ), + 'location_extent': RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 3, 9 ), ( 3, 13 ) ) ), + 'text': contains_string( "Redefinition of 'test'" ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 4, 9 ), + 'location_extent': RangeMatcher( filepath, ( 4, 9 ), ( 4, 13 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 4, 9 ), ( 4, 13 ) ) ), + 'text': contains_string( "Redefinition of 'test'" ), + 'fixit_available': False + } ) + ) } ) + ) ) + + + @IsolatedYcmd() + def test_Diagnostics_DiagsNotReady( self, app ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) + contents = """ + struct Foo { + int x // semicolon missing here! + int y; + int c; + int d; + }; + """ + + filepath = PathToTestFile( 'foo.cc' ) + request = { 'contents': contents, + 'filepath': filepath, + 'filetype': 'cpp' } + + test = { 'request': request, 'route': '/receive_messages' } + RunAfterInitialized( app, test ) + diag_data = BuildRequest( line_num = 3, + contents = contents, + filepath = filepath, + filetype = 'cpp' ) + + with patch.object( completer, '_latest_diagnostics', None ): + results = app.post_json( '/detailed_diagnostic', diag_data ).json + assert_that( results, + has_entry( 'message', + contains_string( "are not ready yet" ) ) ) + + + @UnixOnly + @IsolatedYcmd() + def test_Diagnostics_UpdatedOnBufferVisit( self, app ): + with TemporaryTestDir() as tmp_dir: + source_file = os.path.join( tmp_dir, 'source.cpp' ) + source_contents = """#include "header.h" int main() {return S::h();} """ - with open( source_file, 'w' ) as sf: - sf.write( source_contents ) + with open( source_file, 'w' ) as sf: + sf.write( source_contents ) - header_file = os.path.join( tmp_dir, 'header.h' ) - old_header_content = """#pragma once + header_file = os.path.join( tmp_dir, 'header.h' ) + old_header_content = """#pragma once struct S{static int h();}; """ - with open( header_file, 'w' ) as hf: - hf.write( old_header_content ) + with open( header_file, 'w' ) as hf: + hf.write( old_header_content ) - flags_file = os.path.join( tmp_dir, 'compile_flags.txt' ) - flags_content = """-xc++""" - with open( flags_file, 'w' ) as ff: - ff.write( flags_content ) + flags_file = os.path.join( tmp_dir, 'compile_flags.txt' ) + flags_content = """-xc++""" + with open( flags_file, 'w' ) as ff: + ff.write( flags_content ) - messages_request = { 'contents': source_contents, - 'filepath': source_file, - 'filetype': 'cpp' } + messages_request = { 'contents': source_contents, + 'filepath': source_file, + 'filetype': 'cpp' } - test = { 'request': messages_request, 'route': '/receive_messages' } - response = RunAfterInitialized( app, test ) - assert_that( response, contains_exactly( - has_entries( { 'diagnostics': empty() } ) ) ) + test = { 'request': messages_request, 'route': '/receive_messages' } + response = RunAfterInitialized( app, test ) + assert_that( response, contains_exactly( + has_entries( { 'diagnostics': empty() } ) ) ) - # Overwrite header.cpp - new_header_content = """#pragma once + # Overwrite header.cpp + new_header_content = """#pragma once static int h(); """ - with open( header_file, 'w' ) as f: - f.write( new_header_content ) - - # Send BufferSaved notification for the header - file_save_request = { "event_name": "FileSave", - "filepath": header_file, - "filetype": 'cpp' } - app.post_json( '/event_notification', - BuildRequest( **file_save_request ) ) - - # Send BufferVisit notification - buffer_visit_request = { "event_name": "BufferVisit", - "filepath": source_file, - "filetype": 'cpp' } - app.post_json( '/event_notification', - BuildRequest( **buffer_visit_request ) ) - # Assert diagnostics - for message in PollForMessages( app, messages_request ): - if 'diagnostics' in message: - assert_that( message, - has_entries( { 'diagnostics': contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'text': "Use of undeclared identifier 'S' [undeclared_var_use]", - 'ranges': contains_exactly( RangeMatcher( - contains_string( source_file ), ( 2, 20 ), ( 2, 21 ) ) ), - 'location': LocationMatcher( - contains_string( source_file ), 2, 20 ), - 'location_extent': RangeMatcher( - contains_string( source_file ), ( 2, 20 ), ( 2, 21 ) ) - } ) - ) } ) ) - break - - # Restore original content - with open( header_file, 'w' ) as f: - f.write( old_header_content ) - - # Send BufferSaved notification for the header - file_save_request = { "event_name": "FileSave", - "filepath": header_file, - "filetype": 'cpp' } - app.post_json( '/event_notification', - BuildRequest( **file_save_request ) ) - - # Send BufferVisit notification - app.post_json( '/event_notification', - BuildRequest( **buffer_visit_request ) ) - - # Assert no diagnostics - for message in PollForMessages( app, messages_request ): - print( f'Message { pformat( message ) }' ) - if 'diagnostics' in message: - assert_that( message, - has_entries( { 'diagnostics': empty() } ) ) - break - - # Assert no dirty files - with open( header_file, 'r' ) as f: - assert_that( f.read(), equal_to( old_header_content ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + with open( header_file, 'w' ) as f: + f.write( new_header_content ) + + # Send BufferSaved notification for the header + file_save_request = { "event_name": "FileSave", + "filepath": header_file, + "filetype": 'cpp' } + app.post_json( '/event_notification', + BuildRequest( **file_save_request ) ) + + # Send BufferVisit notification + buffer_visit_request = { "event_name": "BufferVisit", + "filepath": source_file, + "filetype": 'cpp' } + app.post_json( '/event_notification', + BuildRequest( **buffer_visit_request ) ) + # Assert diagnostics + for message in PollForMessages( app, messages_request ): + if 'diagnostics' in message: + assert_that( message, + has_entries( { 'diagnostics': contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'text': "Use of undeclared identifier 'S' [undeclared_var_use]", + 'ranges': contains_exactly( RangeMatcher( + contains_string( source_file ), ( 2, 20 ), ( 2, 21 ) ) ), + 'location': LocationMatcher( + contains_string( source_file ), 2, 20 ), + 'location_extent': RangeMatcher( + contains_string( source_file ), ( 2, 20 ), ( 2, 21 ) ) + } ) + ) } ) ) + break + + # Restore original content + with open( header_file, 'w' ) as f: + f.write( old_header_content ) + + # Send BufferSaved notification for the header + file_save_request = { "event_name": "FileSave", + "filepath": header_file, + "filetype": 'cpp' } + app.post_json( '/event_notification', + BuildRequest( **file_save_request ) ) + + # Send BufferVisit notification + app.post_json( '/event_notification', + BuildRequest( **buffer_visit_request ) ) + + # Assert no diagnostics + for message in PollForMessages( app, messages_request ): + print( f'Message { pformat( message ) }' ) + if 'diagnostics' in message: + assert_that( message, + has_entries( { 'diagnostics': empty() } ) ) + break + + # Assert no dirty files + with open( header_file, 'r' ) as f: + assert_that( f.read(), equal_to( old_header_content ) ) diff --git a/ycmd/tests/clangd/get_completions_test.py b/ycmd/tests/clangd/get_completions_test.py index f2e9c92bbf..b402807c6c 100644 --- a/ycmd/tests/clangd/get_completions_test.py +++ b/ycmd/tests/clangd/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -26,9 +26,10 @@ has_item, has_items, has_entries ) +from unittest import TestCase from ycmd import handlers -from ycmd.tests.clangd import IsolatedYcmd, PathToTestFile, SharedYcmd +from ycmd.tests.clangd import IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, CombineRequest, CompletionEntryMatcher, @@ -98,249 +99,251 @@ def RunTest( app, test ): pass -@IsolatedYcmd( { 'clangd_uses_ycmd_caching': 0 } ) -def GetCompletions_ForcedWithNoTrigger_NoYcmdCaching_test( app ): - RunTest( app, { - 'description': 'semantic completion with force query=DO_SO', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'lang_cpp.cc' ), - 'line_num' : 54, - 'column_num': 8, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'do_something', 'void' ), - CompletionEntryMatcher( 'DO_SOMETHING_TO', 'void' ), - CompletionEntryMatcher( 'DO_SOMETHING_WITH', 'void' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@IsolatedYcmd( { 'clangd_uses_ycmd_caching': 0 } ) -def GetCompletions_NotForced_NoYcmdCaching_test( app ): - RunTest( app, { - 'description': 'semantic completion with force query=DO_SO', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'lang_cpp.cc' ), - 'line_num' : 54, - 'column_num': 8, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'do_something', 'void' ), - CompletionEntryMatcher( 'DO_SOMETHING_TO', 'void' ), - CompletionEntryMatcher( 'DO_SOMETHING_WITH', 'void' ), - ), - 'errors': empty(), - } ) - }, - } ) - - - -@WithRetry -@SharedYcmd -def GetCompletions_ForcedWithNoTrigger_test( app ): - RunTest( app, { - 'description': 'semantic completion with force query=DO_SO', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'lang_cpp.cc' ), - 'line_num' : 54, - 'column_num': 8, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'DO_SOMETHING_TO', 'void' ), - CompletionEntryMatcher( 'DO_SOMETHING_WITH', 'void' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -# This test is isolated to make sure we trigger c hook for clangd, instead of -# fetching completer from cache. -@IsolatedYcmd() -def GetCompletions_Fallback_NoSuggestions_test( app ): - # TESTCASE1 (general_fallback/lang_c.c) - RunTest( app, { - 'description': 'Triggered, fallback but no query so no completions', - 'request': { - 'filetype' : 'c', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 29, - 'column_num': 21, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_Fallback_NoSuggestions_MinimumCharaceters_test( app ): - # TESTCASE1 (general_fallback/lang_cpp.cc) - RunTest( app, { - 'description': 'fallback general completion obeys min chars setting ' - ' (query="a")', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'lang_cpp.cc' ), - 'line_num' : 21, - 'column_num': 22, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_Fallback_Suggestions_test( app ): - # TESTCASE1 (general_fallback/lang_c.c) - RunTest( app, { - 'description': '. after macro with some query text (.a_)', - 'request': { - 'filetype' : 'c', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 29, - 'column_num': 23, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( CompletionEntryMatcher( 'a_parameter', - '[ID]' ) ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_Fallback_Exception_test( app ): - # TESTCASE4 (general_fallback/lang_c.c) - # extra conf throws exception - RunTest( app, { - 'description': '. on struct returns identifier because of error', - 'request': { - 'filetype' : 'c', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 62, - 'column_num': 20, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'a_parameter', 'int' ), - CompletionEntryMatcher( 'another_parameter', 'int' ), - ), - 'errors': empty() - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_Forced_NoFallback_test( app ): - # TESTCASE2 (general_fallback/lang_c.c) - RunTest( app, { - 'description': '-> after macro with forced semantic', - 'request': { - 'filetype' : 'c', - 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num' : 41, - 'column_num': 30, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { 'completions': empty() } ), - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_FilteredNoResults_Fallback_test( app ): - # no errors because the semantic completer returned results, but they - # were filtered out by the query, so this is considered working OK - # (whereas no completions from the semantic engine is considered an - # error) - - # TESTCASE5 (general_fallback/lang_cpp.cc) - RunTest( app, { - 'description': '. on struct returns IDs after query=do_', - 'request': { - 'filetype': 'c', - 'filepath': PathToTestFile( 'general_fallback', 'lang_c.c' ), - 'line_num': 71, - 'column_num': 18, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - # do_ is an identifier because it is already in the file when we - # load it - CompletionEntryMatcher( 'do_', '[ID]' ), - CompletionEntryMatcher( 'do_something', '[ID]' ), - CompletionEntryMatcher( 'do_another_thing', '[ID]' ), - CompletionEntryMatcher( 'DO_SOMETHING_TO', '[ID]' ), - CompletionEntryMatcher( 'DO_SOMETHING_VIA', '[ID]' ) - ), - 'errors': empty() - } ) - }, - } ) - - -@IsolatedYcmd( { 'auto_trigger': 0 } ) -def GetCompletions_NoCompletionsWhenAutoTriggerOff_test( app ): - RunTest( app, { - 'description': 'no completions on . when auto trigger is off', - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'foo.cc' ), - 'contents': """ +class GetCompletionsTest( TestCase ): + @IsolatedYcmd( { 'clangd_uses_ycmd_caching': 0 } ) + def test_GetCompletions_ForcedWithNoTrigger_NoYcmdCaching( self, app ): + RunTest( app, { + 'description': 'semantic completion with force query=DO_SO', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'lang_cpp.cc' ), + 'line_num' : 54, + 'column_num': 8, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'do_something', 'void' ), + CompletionEntryMatcher( 'DO_SOMETHING_TO', 'void' ), + CompletionEntryMatcher( 'DO_SOMETHING_WITH', 'void' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @IsolatedYcmd( { 'clangd_uses_ycmd_caching': 0 } ) + def test_GetCompletions_NotForced_NoYcmdCaching( self, app ): + RunTest( app, { + 'description': 'semantic completion with force query=DO_SO', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'lang_cpp.cc' ), + 'line_num' : 54, + 'column_num': 8, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'do_something', 'void' ), + CompletionEntryMatcher( 'DO_SOMETHING_TO', 'void' ), + CompletionEntryMatcher( 'DO_SOMETHING_WITH', 'void' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_ForcedWithNoTrigger( self, app ): + RunTest( app, { + 'description': 'semantic completion with force query=DO_SO', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'lang_cpp.cc' ), + 'line_num' : 54, + 'column_num': 8, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'DO_SOMETHING_TO', 'void' ), + CompletionEntryMatcher( 'DO_SOMETHING_WITH', 'void' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + # This test is isolated to make sure we trigger c hook for clangd, instead of + # fetching completer from cache. + @IsolatedYcmd() + def test_GetCompletions_Fallback_NoSuggestions( self, app ): + # TESTCASE1 (general_fallback/lang_c.c) + RunTest( app, { + 'description': 'Triggered, fallback but no query so no completions', + 'request': { + 'filetype' : 'c', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 29, + 'column_num': 21, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_Fallback_NoSuggestions_MinimumCharaceters( + self, app ): + # TESTCASE1 (general_fallback/lang_cpp.cc) + RunTest( app, { + 'description': 'fallback general completion obeys min chars setting ' + ' (query="a")', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'lang_cpp.cc' ), + 'line_num' : 21, + 'column_num': 22, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_Fallback_Suggestions( self, app ): + # TESTCASE1 (general_fallback/lang_c.c) + RunTest( app, { + 'description': '. after macro with some query text (.a_)', + 'request': { + 'filetype' : 'c', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 29, + 'column_num': 23, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( CompletionEntryMatcher( 'a_parameter', + '[ID]' ) ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_Fallback_Exception( self, app ): + # TESTCASE4 (general_fallback/lang_c.c) + # extra conf throws exception + RunTest( app, { + 'description': '. on struct returns identifier because of error', + 'request': { + 'filetype' : 'c', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 62, + 'column_num': 20, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'a_parameter', 'int' ), + CompletionEntryMatcher( 'another_parameter', 'int' ), + ), + 'errors': empty() + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_Forced_NoFallback( self, app ): + # TESTCASE2 (general_fallback/lang_c.c) + RunTest( app, { + 'description': '-> after macro with forced semantic', + 'request': { + 'filetype' : 'c', + 'filepath' : PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num' : 41, + 'column_num': 30, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { 'completions': empty() } ), + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_FilteredNoResults_Fallback( self, app ): + # no errors because the semantic completer returned results, but they + # were filtered out by the query, so this is considered working OK + # (whereas no completions from the semantic engine is considered an + # error) + + # TESTCASE5 (general_fallback/lang_cpp.cc) + RunTest( app, { + 'description': '. on struct returns IDs after query=do_', + 'request': { + 'filetype': 'c', + 'filepath': PathToTestFile( 'general_fallback', 'lang_c.c' ), + 'line_num': 71, + 'column_num': 18, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + # do_ is an identifier because it is already in the file when we + # load it + CompletionEntryMatcher( 'do_', '[ID]' ), + CompletionEntryMatcher( 'do_something', '[ID]' ), + CompletionEntryMatcher( 'do_another_thing', '[ID]' ), + CompletionEntryMatcher( 'DO_SOMETHING_TO', '[ID]' ), + CompletionEntryMatcher( 'DO_SOMETHING_VIA', '[ID]' ) + ), + 'errors': empty() + } ) + }, + } ) + + + @IsolatedYcmd( { 'auto_trigger': 0 } ) + def test_GetCompletions_NoCompletionsWhenAutoTriggerOff( self, app ): + RunTest( app, { + 'description': 'no completions on . when auto trigger is off', + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'foo.cc' ), + 'contents': """ struct Foo { int x; int y; @@ -353,28 +356,61 @@ def GetCompletions_NoCompletionsWhenAutoTriggerOff_test( app ): foo. } """, - 'line_num': 11, - 'column_num': 7 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': empty() - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_ForceSemantic_YcmdCache_test( app ): - RunTest( app, { - 'description': 'completions are returned when using ycmd filtering', - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'foo.cc' ), - 'contents': """ + 'line_num': 11, + 'column_num': 7 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': empty() + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_ForceSemantic_YcmdCache( self, app ): + RunTest( app, { + 'description': 'completions are returned when using ycmd filtering', + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'foo.cc' ), + 'contents': """ + int main() + { + int foobar; + int floozar; + int gooboo; + int bleble; + + fooar + } + """, + 'line_num': 9, + 'column_num': 8, + 'force_semantic': True + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( CompletionEntryMatcher( 'foobar' ), + CompletionEntryMatcher( 'floozar' ) ), + 'errors': empty() + } ) + }, + } ) + + + @IsolatedYcmd( { 'clangd_uses_ycmd_caching': 0 } ) + def test_GetCompletions_ForceSemantic_NoYcmdCache( self, app ): + RunTest( app, { + 'description': 'no completions are returned when using Clangd filtering', + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'foo.cc' ), + 'contents': """ int main() { int foobar; @@ -385,522 +421,486 @@ def GetCompletions_ForceSemantic_YcmdCache_test( app ): fooar } """, - 'line_num': 9, - 'column_num': 8, - 'force_semantic': True - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( CompletionEntryMatcher( 'foobar' ), - CompletionEntryMatcher( 'floozar' ) ), - 'errors': empty() - } ) - }, - } ) - - -@IsolatedYcmd( { 'clangd_uses_ycmd_caching': 0 } ) -def GetCompletions_ForceSemantic_NoYcmdCache_test( app ): - RunTest( app, { - 'description': 'no completions are returned when using Clangd filtering', - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'foo.cc' ), - 'contents': """ -int main() -{ - int foobar; - int floozar; - int gooboo; - int bleble; - - fooar -} -""", - 'line_num': 9, - 'column_num': 8, - 'force_semantic': True - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': empty() - } ) - }, - } ) - - -@WindowsOnly -@WithRetry -@SharedYcmd -def GetCompletions_ClangCLDriverFlag_SimpleCompletion_test( app ): - RunTest( app, { - 'description': 'basic completion with --driver-mode=cl', - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'driver_mode_cl', - 'flag', - 'driver_mode_cl.cpp' ), - 'line_num': 8, - 'column_num': 18, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 3, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'driver_mode_cl_include_func', 'void' ), - CompletionEntryMatcher( 'driver_mode_cl_include_int', 'int' ), - ), - 'errors': empty(), - } ) - } - } ) - - -@WindowsOnly -@WithRetry -@IsolatedYcmd() -def GetCompletions_ClangCLDriverExec_SimpleCompletion_test( app ): - RunTest( app, { - 'description': 'basic completion with --driver-mode=cl', - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'driver_mode_cl', - 'executable', - 'driver_mode_cl.cpp' ), - 'line_num': 8, - 'column_num': 18, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 3, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'driver_mode_cl_include_func', 'void' ), - CompletionEntryMatcher( 'driver_mode_cl_include_int', 'int' ), - ), - 'errors': empty(), - } ) - } - } ) - - -@WindowsOnly -@WithRetry -@SharedYcmd -def GetCompletions_ClangCLDriverFlag_IncludeStatementCandidate_test( app ): - RunTest( app, { - 'description': 'Completion inside include statement with CL driver', - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'driver_mode_cl', - 'flag', - 'driver_mode_cl.cpp' ), - 'line_num': 1, - 'column_num': 34, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'driver_mode_cl_include.h\"' ), - ), - 'errors': empty(), - } ) - } - } ) - - -@WindowsOnly -@WithRetry -@SharedYcmd -def GetCompletions_ClangCLDriverExec_IncludeStatementCandidate_test( app ): - RunTest( app, { - 'description': 'Completion inside include statement with CL driver', - 'request': { - 'filetype': 'cpp', - 'filepath': PathToTestFile( 'driver_mode_cl', - 'executable', - 'driver_mode_cl.cpp' ), - 'line_num': 1, - 'column_num': 34, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'driver_mode_cl_include.h\"' ), - ), - 'errors': empty(), - } ) - } - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_UnicodeInLine_test( app ): - RunTest( app, { - 'description': 'member completion with a unicode identifier', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'unicode.cc' ), - 'line_num' : 9, - 'column_num': 8, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 8, - 'completions': contains_exactly( - CompletionEntryMatcher( 'member_with_å_unicøde', 'int' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_UnicodeInLineFilter_test( app ): - RunTest( app, { - 'description': 'member completion with a unicode identifier', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'unicode.cc' ), - 'line_num' : 9, - 'column_num': 10, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 8, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'member_with_å_unicøde', 'int' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_QuotedInclude_test( app ): - RunTest( app, { - 'description': 'completion of #include "', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 9, - 'column_num': 11, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( 'a.hpp"' ), - CompletionEntryMatcher( 'b.hpp"' ), - CompletionEntryMatcher( 'c.hpp"' ), - CompletionEntryMatcher( 'dir with spaces/' ), - CompletionEntryMatcher( 'quote/' ), - CompletionEntryMatcher( 'system/' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_QuotedInclude_AfterDirectorySeparator_test( app ): - RunTest( app, { - 'description': 'completion of #include "quote/', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 9, - 'column_num': 27, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 27, - 'completions': contains_exactly( - CompletionEntryMatcher( 'd.hpp"' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_QuotedInclude_AfterDot_test( app ): - RunTest( app, { - 'description': 'completion of #include "quote/b.', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 9, - 'column_num': 28, - 'compilation_flags': [ '-x', 'c++' ] - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 27, - 'completions': contains_exactly( - CompletionEntryMatcher( 'd.hpp"' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_QuotedInclude_AfterSpace_test( app ): - RunTest( app, { - 'description': 'completion of #include "dir with ', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 9, - 'column_num': 20, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': contains_exactly( - CompletionEntryMatcher( 'dir with spaces/' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_QuotedInclude_Invalid_test( app ): - RunTest( app, { - 'description': 'completion of an invalid include statement', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 11, - 'column_num': 12, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 12, - 'completions': empty(), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_BracketInclude_test( app ): - RunTest( app, { - 'description': 'completion of #include <', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), - 'line_num' : 10, - 'column_num': 11, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 11, - 'completions': has_items( - CompletionEntryMatcher( 'a.hpp>' ), - CompletionEntryMatcher( 'c.hpp>' ) - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_BracketInclude_AtDirectorySeparator_test( app ): - RunTest( app, { - 'description': 'completion of #include |", - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'template.cc' ), - 'line_num' : 1, - 'column_num': 25 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 25, - 'completions': empty(), - 'errors': empty(), - } ) - } - } ) - - -@IsolatedYcmd( { 'extra_conf_globlist': [ - PathToTestFile( 'extra_conf', '.ycm_extra_conf.py' ) ] } ) -def GetCompletions_SupportExtraConf_test( app ): - RunTest( app, { - 'description': 'Flags for foo.cpp from extra conf file are used', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'extra_conf', 'foo.cpp' ), - 'line_num' : 5, - 'column_num': 15 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 15, - 'completions': contains_exactly( - CompletionEntryMatcher( 'member_foo' ) ), - 'errors': empty(), - } ) - } - } ) - - RunTest( app, { - 'description': 'Same flags are used again for foo.cpp', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'extra_conf', 'foo.cpp' ), - 'line_num' : 5, - 'column_num': 15 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 15, - 'completions': contains_exactly( - CompletionEntryMatcher( 'member_foo' ) ), - 'errors': empty(), - } ) - } - } ) - - RunTest( app, { - 'description': 'Flags for bar.cpp from extra conf file are used', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'extra_conf', 'bar.cpp' ), - 'line_num' : 5, - 'column_num': 15 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 15, - 'completions': contains_exactly( - CompletionEntryMatcher( 'member_bar' ) ), - 'errors': empty(), - } ) - } - } ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + 'line_num': 9, + 'column_num': 8, + 'force_semantic': True + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': empty() + } ) + }, + } ) + + + @WindowsOnly + @WithRetry() + @SharedYcmd + def test_GetCompletions_ClangCLDriverFlag_SimpleCompletion( self, app ): + RunTest( app, { + 'description': 'basic completion with --driver-mode=cl', + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'driver_mode_cl', + 'flag', + 'driver_mode_cl.cpp' ), + 'line_num': 8, + 'column_num': 18, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 3, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'driver_mode_cl_include_func', 'void' ), + CompletionEntryMatcher( 'driver_mode_cl_include_int', 'int' ), + ), + 'errors': empty(), + } ) + } + } ) + + + @WindowsOnly + @WithRetry() + @IsolatedYcmd() + def test_GetCompletions_ClangCLDriverExec_SimpleCompletion( self, app ): + RunTest( app, { + 'description': 'basic completion with --driver-mode=cl', + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'driver_mode_cl', + 'executable', + 'driver_mode_cl.cpp' ), + 'line_num': 8, + 'column_num': 18, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 3, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'driver_mode_cl_include_func', 'void' ), + CompletionEntryMatcher( 'driver_mode_cl_include_int', 'int' ), + ), + 'errors': empty(), + } ) + } + } ) + + + @WindowsOnly + @WithRetry() + @SharedYcmd + def test_GetCompletions_ClangCLDriverFlag_IncludeStatementCandidate( + self, app ): + RunTest( app, { + 'description': 'Completion inside include statement with CL driver', + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'driver_mode_cl', + 'flag', + 'driver_mode_cl.cpp' ), + 'line_num': 1, + 'column_num': 34, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'driver_mode_cl_include.h\"' ), + ), + 'errors': empty(), + } ) + } + } ) + + + @WindowsOnly + @WithRetry() + @SharedYcmd + def test_GetCompletions_ClangCLDriverExec_IncludeStatementCandidate( + self, app ): + RunTest( app, { + 'description': 'Completion inside include statement with CL driver', + 'request': { + 'filetype': 'cpp', + 'filepath': PathToTestFile( 'driver_mode_cl', + 'executable', + 'driver_mode_cl.cpp' ), + 'line_num': 1, + 'column_num': 34, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'driver_mode_cl_include.h\"' ), + ), + 'errors': empty(), + } ) + } + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_UnicodeInLine( self, app ): + RunTest( app, { + 'description': 'member completion with a unicode identifier', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'unicode.cc' ), + 'line_num' : 9, + 'column_num': 8, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 8, + 'completions': contains_exactly( + CompletionEntryMatcher( 'member_with_å_unicøde', 'int' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_UnicodeInLineFilter( self, app ): + RunTest( app, { + 'description': 'member completion with a unicode identifier', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'unicode.cc' ), + 'line_num' : 9, + 'column_num': 10, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 8, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'member_with_å_unicøde', 'int' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_QuotedInclude( self, app ): + RunTest( app, { + 'description': 'completion of #include "', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 9, + 'column_num': 11, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_exactly( + CompletionEntryMatcher( 'a.hpp"' ), + CompletionEntryMatcher( 'b.hpp"' ), + CompletionEntryMatcher( 'c.hpp"' ), + CompletionEntryMatcher( 'dir with spaces/' ), + CompletionEntryMatcher( 'quote/' ), + CompletionEntryMatcher( 'system/' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_QuotedInclude_AfterDirectorySeparator( self, app ): + RunTest( app, { + 'description': 'completion of #include "quote/', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 9, + 'column_num': 27, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 27, + 'completions': contains_exactly( + CompletionEntryMatcher( 'd.hpp"' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_QuotedInclude_AfterDot( self, app ): + RunTest( app, { + 'description': 'completion of #include "quote/b.', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 9, + 'column_num': 28, + 'compilation_flags': [ '-x', 'c++' ] + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 27, + 'completions': contains_exactly( + CompletionEntryMatcher( 'd.hpp"' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_QuotedInclude_AfterSpace( self, app ): + RunTest( app, { + 'description': 'completion of #include "dir with ', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 9, + 'column_num': 20, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': contains_exactly( + CompletionEntryMatcher( 'dir with spaces/' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_QuotedInclude_Invalid( self, app ): + RunTest( app, { + 'description': 'completion of an invalid include statement', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 11, + 'column_num': 12, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 12, + 'completions': empty(), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_BracketInclude( self, app ): + RunTest( app, { + 'description': 'completion of #include <', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'test-include', 'main.cpp' ), + 'line_num' : 10, + 'column_num': 11, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': has_items( + CompletionEntryMatcher( 'a.hpp>' ), + CompletionEntryMatcher( 'c.hpp>' ) + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_BracketInclude_AtDirectorySeparator( self, app ): + RunTest( app, { + 'description': 'completion of #include |", + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'template.cc' ), + 'line_num' : 1, + 'column_num': 25 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 25, + 'completions': empty(), + 'errors': empty(), + } ) + } + } ) + + + @IsolatedYcmd( { 'extra_conf_globlist': [ + PathToTestFile( 'extra_conf', '.ycm_extra_conf.py' ) ] } ) + def test_GetCompletions_SupportExtraConf( self, app ): + RunTest( app, { + 'description': 'Flags for foo.cpp from extra conf file are used', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'extra_conf', 'foo.cpp' ), + 'line_num' : 5, + 'column_num': 15 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 15, + 'completions': contains_exactly( + CompletionEntryMatcher( 'member_foo' ) ), + 'errors': empty(), + } ) + } + } ) + + RunTest( app, { + 'description': 'Same flags are used again for foo.cpp', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'extra_conf', 'foo.cpp' ), + 'line_num' : 5, + 'column_num': 15 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 15, + 'completions': contains_exactly( + CompletionEntryMatcher( 'member_foo' ) ), + 'errors': empty(), + } ) + } + } ) + + RunTest( app, { + 'description': 'Flags for bar.cpp from extra conf file are used', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'extra_conf', 'bar.cpp' ), + 'line_num' : 5, + 'column_num': 15 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 15, + 'completions': contains_exactly( + CompletionEntryMatcher( 'member_bar' ) ), + 'errors': empty(), + } ) + } + } ) diff --git a/ycmd/tests/clangd/server_management_test.py b/ycmd/tests/clangd/server_management_test.py index c4ac57ba0b..ac32f23ecc 100644 --- a/ycmd/tests/clangd/server_management_test.py +++ b/ycmd/tests/clangd/server_management_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2020 ycmd contributors +# Copyright (C) 2011-2021 ycmd contributors # # This file is part of ycmd. # @@ -24,6 +24,7 @@ has_entries, has_entry, has_item ) +from unittest import TestCase from ycmd import handlers, utils from ycmd.tests.clangd import ( IsolatedYcmd, PathToTestFile, @@ -66,113 +67,112 @@ def CheckStopped( app ): ) -@IsolatedYcmd() -def ServerManagement_StopServer_Clean_test( app ): - StartClangd( app ) - StopCompleterServer( app, 'cpp', '' ) - CheckStopped( app ) - - -@IsolatedYcmd() -@patch( 'os.remove', side_effect = OSError ) -@patch( 'ycmd.utils.WaitUntilProcessIsTerminated', - MockProcessTerminationTimingOut ) -def ServerManagement_StopServer_Unclean_test( rm, app ): - StartClangd( app ) - StopCompleterServer( app, 'cpp', '' ) - CheckStopped( app ) - - -@IsolatedYcmd() -def ServerManagement_StopServer_Twice_test( app ): - StartClangd( app ) - StopCompleterServer( app, 'cpp', '' ) - CheckStopped( app ) - StopCompleterServer( app, 'cpp', '' ) - CheckStopped( app ) - - -@IsolatedYcmd() -def ServerManagement_StopServer_Killed_test( app ): - StartClangd( app ) - process = psutil.Process( GetPid( app ) ) - process.terminate() - process.wait( timeout = 5 ) - StopCompleterServer( app, 'cpp', '' ) - CheckStopped( app ) - - -@IsolatedYcmd() -def ServerManagement_ServerDiesWhileShuttingDown_test( app ): - StartClangd( app ) - process = psutil.Process( GetPid( app ) ) - completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) - - # We issue a shutdown but make sure it never reaches server by mocking - # WriteData in Connection. Then we kill the server and check shutdown still - # succeeds. - with patch.object( completer.GetConnection(), 'WriteData' ): - stop_server_task = utils.StartThread( StopCompleterServer, app, 'cpp', '' ) - process.terminate() - stop_server_task.join() - - CheckStopped( app ) - - -@IsolatedYcmd() -def ServerManagement_ConnectionRaisesWhileShuttingDown_test( app ): - StartClangd( app ) - process = psutil.Process( GetPid( app ) ) - completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) - - # We issue a shutdown but make sure it never reaches server by mocking - # WriteData in Connection. Then we kill the server and check shutdown still - # succeeds. - with patch.object( completer.GetConnection(), 'GetResponse', - side_effect = RuntimeError ): +class ServerManagementTest( TestCase ): + @IsolatedYcmd() + def test_ServerManagement_StopServer_Clean( self, app ): + StartClangd( app ) StopCompleterServer( app, 'cpp', '' ) + CheckStopped( app ) - CheckStopped( app ) - if process.is_running(): - process.terminate() - raise AssertionError( 'Termination failed' ) + @IsolatedYcmd() + @patch( 'os.remove', side_effect = OSError ) + @patch( 'ycmd.utils.WaitUntilProcessIsTerminated', + MockProcessTerminationTimingOut ) + def test_ServerManagement_StopServer_Unclean( self, app, *args ): + StartClangd( app ) + StopCompleterServer( app, 'cpp', '' ) + CheckStopped( app ) -@IsolatedYcmd() -def ServerManagement_RestartServer_test( app ): - StartClangd( app, PathToTestFile( 'basic.cpp' ) ) - assert_that( - GetDebugInfo( app ), - CompleterProjectDirectoryMatcher( PathToTestFile() ) ) - - app.post_json( - '/run_completer_command', - BuildRequest( - filepath = PathToTestFile( 'test-include', 'main.cpp' ), - filetype = 'cpp', - command_arguments = [ 'RestartServer' ], - ), - ) + @IsolatedYcmd() + def test_ServerManagement_StopServer_Twice( self, app ): + StartClangd( app ) + StopCompleterServer( app, 'cpp', '' ) + CheckStopped( app ) + StopCompleterServer( app, 'cpp', '' ) + CheckStopped( app ) - WaitUntilCompleterServerReady( app, 'cpp' ) - assert_that( - GetDebugInfo( app ), - has_entry( 'completer', has_entries( { - 'name': 'C-family', - 'servers': contains_exactly( has_entries( { - 'name': 'Clangd', - 'is_running': True, - 'extras': has_item( has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'test-include' ), + @IsolatedYcmd() + def test_ServerManagement_StopServer_Killed( self, app ): + StartClangd( app ) + process = psutil.Process( GetPid( app ) ) + process.terminate() + process.wait( timeout = 5 ) + StopCompleterServer( app, 'cpp', '' ) + CheckStopped( app ) + + + @IsolatedYcmd() + def test_ServerManagement_ServerDiesWhileShuttingDown( self, app ): + StartClangd( app ) + process = psutil.Process( GetPid( app ) ) + completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) + + # We issue a shutdown but make sure it never reaches server by mocking + # WriteData in Connection. Then we kill the server and check shutdown still + # succeeds. + with patch.object( completer.GetConnection(), 'WriteData' ): + stop_server_task = utils.StartThread( StopCompleterServer, + app, + 'cpp', + '' ) + process.terminate() + stop_server_task.join() + + CheckStopped( app ) + + + @IsolatedYcmd() + def test_ServerManagement_ConnectionRaisesWhileShuttingDown( self, app ): + StartClangd( app ) + process = psutil.Process( GetPid( app ) ) + completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) + + # We issue a shutdown but make sure it never reaches server by mocking + # WriteData in Connection. Then we kill the server and check shutdown still + # succeeds. + with patch.object( completer.GetConnection(), 'GetResponse', + side_effect = RuntimeError ): + StopCompleterServer( app, 'cpp', '' ) + + CheckStopped( app ) + if process.is_running(): + process.terminate() + raise AssertionError( 'Termination failed' ) + + + @IsolatedYcmd() + def test_ServerManagement_RestartServer( self, app ): + StartClangd( app, PathToTestFile( 'basic.cpp' ) ) + + assert_that( + GetDebugInfo( app ), + CompleterProjectDirectoryMatcher( PathToTestFile() ) ) + + app.post_json( + '/run_completer_command', + BuildRequest( + filepath = PathToTestFile( 'test-include', 'main.cpp' ), + filetype = 'cpp', + command_arguments = [ 'RestartServer' ], + ), + ) + + WaitUntilCompleterServerReady( app, 'cpp' ) + + assert_that( + GetDebugInfo( app ), + has_entry( 'completer', has_entries( { + 'name': 'C-family', + 'servers': contains_exactly( has_entries( { + 'name': 'Clangd', + 'is_running': True, + 'extras': has_item( has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'test-include' ), + } ) ) } ) ) } ) ) - } ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + ) diff --git a/ycmd/tests/clangd/signature_help_test.py b/ycmd/tests/clangd/signature_help_test.py index de718d0ed3..a4d1d335bd 100644 --- a/ycmd/tests/clangd/signature_help_test.py +++ b/ycmd/tests/clangd/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -18,10 +18,11 @@ import json import requests from unittest.mock import patch +from unittest import TestCase from hamcrest import assert_that, contains_exactly, empty, equal_to, has_entries from ycmd import handlers -from ycmd.tests.clangd import PathToTestFile, SharedYcmd, IsolatedYcmd +from ycmd.tests.clangd import PathToTestFile, SharedYcmd, setUpModule, tearDownModule, IsolatedYcmd # noqa from ycmd.tests.test_utils import ( EMPTY_SIGNATURE_HELP, BuildRequest, CombineRequest, @@ -81,562 +82,556 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def Signature_Help_Trigger_test( app ): - RunTest( app, { - 'description': 'trigger after (', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 14, - 'signature_help_state': 'INACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'Temperature temp, ' - 'int sugargs) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 58 ), - ] ), - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'double fizziness, ' - 'Flavour Flavour) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 62 ), - ] ), - ) +class SignatureHelpTest( TestCase ): + @SharedYcmd + def test_Signature_Help_Trigger( self, app ): + RunTest( app, { + 'description': 'trigger after (', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 14, + 'signature_help_state': 'INACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'Temperature temp, ' + 'int sugargs) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 58 ), + ] ), + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'double fizziness, ' + 'Flavour Flavour) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 62 ), + ] ), + ) + } ), + } ) + }, + } ) + + + @IsolatedYcmd( { 'disable_signature_help': 1 } ) + def test_Signature_Help_Disabled( self, app ): + RunTest( app, { + 'description': 'trigger after (', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 14, + 'signature_help_state': 'INACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP, + } ) + }, + } ) + + + @SharedYcmd + def test_Signature_Help_NoTrigger( self, app ): + RunTest( app, { + 'description': 'do not trigger before (', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 13, + 'signature_help_state': 'INACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP, } ), - } ) - }, - } ) + }, + } ) -@IsolatedYcmd( { 'disable_signature_help': 1 } ) -def Signature_Help_Disabled_test( app ): - RunTest( app, { - 'description': 'trigger after (', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 14, - 'signature_help_state': 'INACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP, - } ) - }, - } ) - - -@SharedYcmd -def Signature_Help_NoTrigger_test( app ): - RunTest( app, { - 'description': 'do not trigger before (', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 13, - 'signature_help_state': 'INACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP, - } ), - }, - } ) - - -@SharedYcmd -def Signature_Help_NoTrigger_After_Trigger_test( app ): - RunTest( app, { - 'description': 'do not trigger too far after (', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 15, - 'signature_help_state': 'INACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP, - } ), - }, - } ) - - -@SharedYcmd -def Signature_Help_Trigger_After_Trigger_test( app ): - RunTest( app, { - 'description': 'Auto trigger due to state of existing request', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 15, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'Temperature temp, ' - 'int sugargs) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 58 ), - ] ), - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'double fizziness, ' - 'Flavour Flavour) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 62 ), - ] ), - ) + @SharedYcmd + def test_Signature_Help_NoTrigger_After_Trigger( self, app ): + RunTest( app, { + 'description': 'do not trigger too far after (', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 15, + 'signature_help_state': 'INACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP, } ), - } ), - }, - } ) + }, + } ) -@IsolatedYcmd( { 'disable_signature_help': 1 } ) -def Signature_Help_Trigger_After_Trigger_Disabled_test( app ): - RunTest( app, { - 'description': 'Auto trigger due to state of existing request', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 15, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP, - } ), - }, - } ) - - -@SharedYcmd -def Signature_Help_Trigger_After_Trigger_PlusText_test( app ): - RunTest( app, { - 'description': 'Triggering after additional text beyond (', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 17, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'Temperature temp, ' - 'int sugargs) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 58 ), - ] ), - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'double fizziness, ' - 'Flavour Flavour) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 62 ), - ] ), - ) + @SharedYcmd + def test_Signature_Help_Trigger_After_Trigger( self, app ): + RunTest( app, { + 'description': 'Auto trigger due to state of existing request', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 15, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'Temperature temp, ' + 'int sugargs) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 58 ), + ] ), + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'double fizziness, ' + 'Flavour Flavour) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 62 ), + ] ), + ) + } ), + } ), + }, + } ) + + + @IsolatedYcmd( { 'disable_signature_help': 1 } ) + def test_Signature_Help_Trigger_After_Trigger_Disabled( self, app ): + RunTest( app, { + 'description': 'Auto trigger due to state of existing request', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 15, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP, } ), - } ), - }, - } ) + }, + } ) -@SharedYcmd -def Signature_Help_Trigger_After_Trigger_PlusCompletion_test( app ): - RunTest( app, { - 'description': 'Triggering after semantic trigger after (', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 28, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'Temperature temp, ' - 'int sugargs) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 58 ), - ] ), - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'double fizziness, ' - 'Flavour Flavour) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 62 ), - ] ), - ) + @SharedYcmd + def test_Signature_Help_Trigger_After_Trigger_PlusText( self, app ): + RunTest( app, { + 'description': 'Triggering after additional text beyond (', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 17, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'Temperature temp, ' + 'int sugargs) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 58 ), + ] ), + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'double fizziness, ' + 'Flavour Flavour) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 62 ), + ] ), + ) + } ), } ), - } ), - }, - } ) + }, + } ) -@SharedYcmd -def Signature_Help_Trigger_After_OtherTrigger_test( app ): - RunTest( app, { - 'description': 'Triggering after ,', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 35, - 'signature_help_state': 'INACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'Temperature temp, ' - 'int sugargs) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 58 ), - ] ), - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'double fizziness, ' - 'Flavour Flavour) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 62 ), - ] ), - ) + @SharedYcmd + def test_Signature_Help_Trigger_After_Trigger_PlusCompletion( self, app ): + RunTest( app, { + 'description': 'Triggering after semantic trigger after (', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 28, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'Temperature temp, ' + 'int sugargs) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 58 ), + ] ), + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'double fizziness, ' + 'Flavour Flavour) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 62 ), + ] ), + ) + } ), } ), - } ), - }, - } ) + }, + } ) -@SharedYcmd -def Signature_Help_Trigger_After_Arguments_Narrow_test( app ): - RunTest( app, { - 'description': 'After resolution of overload', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 41, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 2, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'double fizziness, ' - 'Flavour Flavour) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 62 ), - ] ) - ) + @SharedYcmd + def test_Signature_Help_Trigger_After_OtherTrigger( self, app ): + RunTest( app, { + 'description': 'Triggering after ,', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 35, + 'signature_help_state': 'INACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'Temperature temp, ' + 'int sugargs) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 58 ), + ] ), + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'double fizziness, ' + 'Flavour Flavour) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 62 ), + ] ), + ) + } ), } ), - } ), - }, - } ) + }, + } ) -@SharedYcmd -def Signature_Help_Trigger_After_Arguments_Narrow2_test( app ): - RunTest( app, { - 'description': 'After resolution of overload not the first one', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 8, - 'column_num': 53, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 2, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'Temperature temp, ' - 'int sugargs) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 58 ), - ] ) - ) + @SharedYcmd + def test_Signature_Help_Trigger_After_Arguments_Narrow( self, app ): + RunTest( app, { + 'description': 'After resolution of overload', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 41, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 2, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'double fizziness, ' + 'Flavour Flavour) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 62 ), + ] ) + ) + } ), } ), - } ), - }, - } ) + }, + } ) -@SharedYcmd -def Signature_Help_Trigger_After_OtherTrigger_ReTrigger_test( app ): - RunTest( app, { - 'description': 'Triggering after , but already ACTIVE', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 35, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'Temperature temp, ' - 'int sugargs) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 58 ), - ] ), - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'double fizziness, ' - 'Flavour Flavour) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 62 ), - ] ), - ) + @SharedYcmd + def test_Signature_Help_Trigger_After_Arguments_Narrow2( self, app ): + RunTest( app, { + 'description': 'After resolution of overload not the first one', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 8, + 'column_num': 53, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 2, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'Temperature temp, ' + 'int sugargs) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 58 ), + ] ) + ) + } ), } ), - } ), - }, - } ) + }, + } ) -@SharedYcmd -def Signature_Help_Trigger_JustBeforeClose_test( app ): - RunTest( app, { - 'description': 'Last argument, before )', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 8, - 'column_num': 33, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'Temperature temp, ' - 'int sugargs) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 58 ), - ] ), - SignatureMatcher( 'make_drink(TypeOfDrink type, ' - 'double fizziness, ' - 'Flavour Flavour) -> Drink &', [ - ParameterMatcher( 11, 27 ), - ParameterMatcher( 29, 45 ), - ParameterMatcher( 47, 62 ), - ] ), - ) + @SharedYcmd + def test_Signature_Help_Trigger_After_OtherTrigger_ReTrigger( self, app ): + RunTest( app, { + 'description': 'Triggering after , but already ACTIVE', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 35, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'Temperature temp, ' + 'int sugargs) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 58 ), + ] ), + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'double fizziness, ' + 'Flavour Flavour) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 62 ), + ] ), + ) + } ), } ), - } ), - }, - } ) + }, + } ) -@SharedYcmd -def Signature_Help_Clears_After_EndFunction_test( app ): - RunTest( app, { - 'description': 'Empty response on )', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 70, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP, - } ), - }, - } ) - - -@SharedYcmd -def Signature_Help_Clears_After_Function_Call_test( app ): - RunTest( app, { - 'description': 'Empty response after )', - 'request': { - 'filetype' : 'cpp', - 'filepath' : PathToTestFile( 'general_fallback', - 'make_drink.cc' ), - 'line_num' : 7, - 'column_num': 71, - 'signature_help_state': 'ACTIVE', - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP, - } ), - }, - } ) - - -@patch( 'ycmd.completers.completer.Completer.ShouldUseSignatureHelpNow', - return_value = True ) -@patch( 'ycmd.completers.language_server.language_server_completer.' - 'LanguageServerCompleter._ServerIsInitialized', return_value = False ) -@IsolatedYcmd() -def Signature_Help_Server_Not_Initialized_test( should_use_sig, - server_init, - app ): - filepath = PathToTestFile( 'general_fallback', 'make_drink.cc' ) - request = { - 'filetype' : 'cpp', - 'filepath' : filepath, - 'line_num' : 7, - 'column_num': 71, - 'signature_help_state': 'INACTIVE', - 'contents': ReadFile( filepath ) - } - response = app.post_json( '/signature_help', - BuildRequest( **request ), - expect_errors = True ) - assert_that( response.json, has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP, - } ) ) + @SharedYcmd + def test_Signature_Help_Trigger_JustBeforeClose( self, app ): + RunTest( app, { + 'description': 'Last argument, before )', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 8, + 'column_num': 33, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'Temperature temp, ' + 'int sugargs) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 58 ), + ] ), + SignatureMatcher( 'make_drink(TypeOfDrink type, ' + 'double fizziness, ' + 'Flavour Flavour) -> Drink &', [ + ParameterMatcher( 11, 27 ), + ParameterMatcher( 29, 45 ), + ParameterMatcher( 47, 62 ), + ] ), + ) + } ), + } ), + }, + } ) + + @SharedYcmd + def test_Signature_Help_Clears_After_EndFunction( self, app ): + RunTest( app, { + 'description': 'Empty response on )', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 70, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP, + } ), + }, + } ) -def Signature_Help_Available_Server_Not_Initialized_test(): - completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) @SharedYcmd - @patch.object( completer, '_ServerIsInitialized', return_value = False ) - def Test( app ): - response = app.get( '/signature_help_available', - { 'subserver': 'cpp' } ).json - assert_that( response, SignatureAvailableMatcher( 'PENDING' ) ) + def test_Signature_Help_Clears_After_Function_Call( self, app ): + RunTest( app, { + 'description': 'Empty response after )', + 'request': { + 'filetype' : 'cpp', + 'filepath' : PathToTestFile( 'general_fallback', + 'make_drink.cc' ), + 'line_num' : 7, + 'column_num': 71, + 'signature_help_state': 'ACTIVE', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP, + } ), + }, + } ) + + + @patch( 'ycmd.completers.completer.Completer.ShouldUseSignatureHelpNow', + return_value = True ) + @patch( 'ycmd.completers.language_server.language_server_completer.' + 'LanguageServerCompleter._ServerIsInitialized', return_value = False ) + @IsolatedYcmd() + def test_Signature_Help_Server_Not_Initialized( self, app, *args ): + filepath = PathToTestFile( 'general_fallback', 'make_drink.cc' ) + request = { + 'filetype' : 'cpp', + 'filepath' : filepath, + 'line_num' : 7, + 'column_num': 71, + 'signature_help_state': 'INACTIVE', + 'contents': ReadFile( filepath ) + } + response = app.post_json( '/signature_help', + BuildRequest( **request ), + expect_errors = True ) + assert_that( response.json, has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP, + } ) ) -@SharedYcmd -def Signature_Help_Supported_test( app ): - request = { 'filepath' : PathToTestFile( 'goto.cc' ) } - app.post_json( '/event_notification', - CombineRequest( request, { - 'event_name': 'FileReadyToParse', - 'filetype': 'cpp' - } ), - expect_errors = True ) - WaitUntilCompleterServerReady( app, 'cpp' ) + def test_Signature_Help_Available_Server_Not_Initialized( self ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) - response = app.get( '/signature_help_available', - { 'subserver': 'cpp' } ).json - assert_that( response, SignatureAvailableMatcher( 'YES' ) ) + @SharedYcmd + @patch.object( completer, '_ServerIsInitialized', return_value = False ) + def Test( self, app ): + response = app.get( '/signature_help_available', + { 'subserver': 'cpp' } ).json + assert_that( response, SignatureAvailableMatcher( 'PENDING' ) ) -@IsolatedYcmd( { 'disable_signature_help': 1 } ) -def Signature_Help_Available_Disabled_By_User_test( app, *args ): - request = { 'filepath' : PathToTestFile( 'goto.cc' ) } - app.post_json( '/event_notification', - CombineRequest( request, { - 'event_name': 'FileReadyToParse', - 'filetype': 'cpp' - } ), - expect_errors = True ) - WaitUntilCompleterServerReady( app, 'cpp' ) + @SharedYcmd + def test_Signature_Help_Supported( self, app ): + request = { 'filepath' : PathToTestFile( 'goto.cc' ) } + app.post_json( '/event_notification', + CombineRequest( request, { + 'event_name': 'FileReadyToParse', + 'filetype': 'cpp' + } ), + expect_errors = True ) + WaitUntilCompleterServerReady( app, 'cpp' ) + + response = app.get( '/signature_help_available', + { 'subserver': 'cpp' } ).json + assert_that( response, SignatureAvailableMatcher( 'YES' ) ) - response = app.get( '/signature_help_available', - { 'subserver': 'cpp' } ).json - assert_that( response, SignatureAvailableMatcher( 'NO' ) ) + @IsolatedYcmd( { 'disable_signature_help': 1 } ) + def test_Signature_Help_Available_Disabled_By_User( self, app, *args ): + request = { 'filepath' : PathToTestFile( 'goto.cc' ) } + app.post_json( '/event_notification', + CombineRequest( request, { + 'event_name': 'FileReadyToParse', + 'filetype': 'cpp' + } ), + expect_errors = True ) + WaitUntilCompleterServerReady( app, 'cpp' ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + response = app.get( '/signature_help_available', + { 'subserver': 'cpp' } ).json + assert_that( response, SignatureAvailableMatcher( 'NO' ) ) diff --git a/ycmd/tests/clangd/subcommands_test.py b/ycmd/tests/clangd/subcommands_test.py index de5e36fb7a..3bdbe8507d 100644 --- a/ycmd/tests/clangd/subcommands_test.py +++ b/ycmd/tests/clangd/subcommands_test.py @@ -1,6 +1,4 @@ -# encoding: utf-8 -# -# Copyright (C) 2018 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -17,11 +15,6 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -from __future__ import absolute_import -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division - from hamcrest import ( assert_that, contains_exactly, contains_string, @@ -30,16 +23,19 @@ has_entry, matches_regexp ) from unittest.mock import patch +from unittest import TestCase from pprint import pprint +import itertools import requests -import pytest import os.path from ycmd import handlers -from ycmd.tests.clangd import ( IsolatedYcmd, +from ycmd.tests.clangd import ( IsolatedYcmd, # noqa SharedYcmd, PathToTestFile, - RunAfterInitialized ) + RunAfterInitialized, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, CombineRequest, @@ -51,115 +47,6 @@ from ycmd.utils import ReadFile -# This test is isolated to trigger objcpp hooks, rather than fetching completer -# from cache. -@IsolatedYcmd() -def Subcommands_DefinedSubcommands_test( app ): - file_path = PathToTestFile( 'GoTo_Clang_ZeroBasedLineAndColumn_test.cc' ) - RunAfterInitialized( app, { - 'request': { - 'completer_target': 'filetype_default', - 'line_num': 10, - 'column_num': 3, - 'filetype': 'objcpp', - 'filepath': file_path - }, - 'expect': { - 'response': requests.codes.ok, - 'data': contains_exactly( *sorted( [ 'ExecuteCommand', - 'FixIt', - 'Format', - 'GetDoc', - 'GetDocImprecise', - 'GetType', - 'GetTypeImprecise', - 'GoTo', - 'GoToDeclaration', - 'GoToDefinition', - 'GoToDocumentOutline', - 'GoToImprecise', - 'GoToImplementation', - 'GoToInclude', - 'GoToReferences', - 'GoToSymbol', - 'RefactorRename', - 'RestartServer' ] ) ) - }, - 'route': '/defined_subcommands', - } ) - - -@WithRetry -@SharedYcmd -@pytest.mark.parametrize( 'cmd', [ - 'FixIt', - 'Format', - 'GetDoc', - 'GetDocImprecise', - 'GetType', - 'GetTypeImprecise', - 'GoTo', - 'GoToDeclaration', - 'GoToDefinition', - 'GoToInclude', - 'GoToImplementation', - 'GoToReferences', - 'RefactorRename', -] ) -def Subcommands_ServerNotInitialized_test( app, cmd ): - - completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) - - @patch.object( completer, '_ServerIsInitialized', return_value = False ) - def Test( app, cmd, *args ): - request = { - 'line_num': 1, - 'column_num': 1, - 'event_name': 'FileReadyToParse', - 'filetype': 'cpp', - 'command_arguments': [ cmd ] - } - app.post_json( '/event_notification', - BuildRequest( **request ), - expect_errors = True ) - response = app.post_json( - '/run_completer_command', - BuildRequest( **request ), - expect_errors = True - ) - assert_that( response.status_code, equal_to( requests.codes.server_error ) ) - assert_that( response.json, - ErrorMatcher( RuntimeError, - 'Server is initializing. Please wait.' ) ) - - Test( app, cmd ) - - -@SharedYcmd -def Subcommands_GoTo_ZeroBasedLineAndColumn_test( app ): - file_path = PathToTestFile( 'GoTo_Clang_ZeroBasedLineAndColumn_test.cc' ) - RunAfterInitialized( app, { - 'request': { - 'contents': ReadFile( file_path ), - 'completer_target': 'filetype_default', - 'command_arguments': [ 'GoToDefinition' ], - 'line_num': 10, - 'column_num': 3, - 'filetype': 'cpp', - 'filepath': file_path - }, - 'expect': { - 'response': requests.codes.ok, - 'data': { - 'filepath': os.path.abspath( file_path ), - 'line_num': 2, - 'column_num': 8 - } - }, - 'route': '/run_completer_command', - } ) - - def RunGoToTest_all( app, folder, command, test ): req = test[ 'req' ] filepath = PathToTestFile( folder, req[ 0 ] ) @@ -210,199 +97,6 @@ def RunGoToTest_all( app, folder, command, test ): } ) -@pytest.mark.parametrize( 'test', [ - # Local::x -> definition/declaration of x - { 'req': ( 'goto.cc', 23, 21 ), 'res': ( 'goto.cc', 4, 9 ) }, - # Local::in_line -> definition/declaration of Local::in_line - { 'req': ( 'goto.cc', 24, 26 ), 'res': ( 'goto.cc', 6, 10 ) }, - # Local -> definition/declaration of Local - { 'req': ( 'goto.cc', 24, 16 ), 'res': ( 'goto.cc', 2, 11 ) }, - # Local::out_of_line -> definition of Local::out_of_line - { 'req': ( 'goto.cc', 25, 27 ), 'res': ( 'goto.cc', 14, 13 ) }, - # GoToDeclaration alternates between definition and declaration - { 'req': ( 'goto.cc', 14, 13 ), 'res': ( 'goto.cc', 11, 10 ) }, - { 'req': ( 'goto.cc', 11, 10 ), 'res': ( 'goto.cc', 14, 13 ) }, - # test -> definition and declaration of test - { 'req': ( 'goto.cc', 21, 5 ), 'res': ( 'goto.cc', 19, 5 ) }, - { 'req': ( 'goto.cc', 19, 5 ), 'res': ( 'goto.cc', 21, 5 ) }, - # Unicøde - { 'req': ( 'goto.cc', 34, 9 ), 'res': ( 'goto.cc', 32, 26 ) }, - # Another_Unicøde - { 'req': ( 'goto.cc', 36, 17 ), 'res': ( 'goto.cc', 32, 54 ) }, - { 'req': ( 'goto.cc', 36, 25 ), 'res': ( 'goto.cc', 32, 54 ) }, - { 'req': ( 'goto.cc', 38, 3 ), 'res': ( 'goto.cc', 36, 28 ) }, - # Expected failures - { 'req': ( 'goto.cc', 13, 1 ), 'res': 'Cannot jump to location' }, - { 'req': ( 'goto.cc', 16, 6 ), 'res': 'Cannot jump to location' }, - ] ) -@pytest.mark.parametrize( 'cmd', [ 'GoToImprecise', 'GoToDefinition', 'GoTo' ] ) -@SharedYcmd -def Subcommands_GoTo_all_test( app, cmd, test ): - RunGoToTest_all( app, '', cmd, test ) - - -@pytest.mark.parametrize( 'test', [ - # Local::x -> definition/declaration of x - { 'req': ( 'goto.cc', 23, 21 ), 'res': ( 'goto.cc', 4, 9 ) }, - # Local::in_line -> definition/declaration of Local::in_line - { 'req': ( 'goto.cc', 24, 26 ), 'res': ( 'goto.cc', 6, 10 ) }, - # Local -> definition/declaration of Local - { 'req': ( 'goto.cc', 24, 16 ), 'res': ( 'goto.cc', 2, 11 ) }, - # Local::out_of_line -> declaration of Local::out_of_line - { 'req': ( 'goto.cc', 25, 27 ), 'res': ( 'goto.cc', 11, 10 ) }, - # GoToDeclaration alternates between definition and declaration - { 'req': ( 'goto.cc', 14, 13 ), 'res': ( 'goto.cc', 11, 10 ) }, - { 'req': ( 'goto.cc', 11, 10 ), 'res': ( 'goto.cc', 14, 13 ) }, - # test -> definition and declaration of test - { 'req': ( 'goto.cc', 21, 5 ), 'res': ( 'goto.cc', 19, 5 ) }, - { 'req': ( 'goto.cc', 19, 5 ), 'res': ( 'goto.cc', 21, 5 ) }, - # Unicøde - { 'req': ( 'goto.cc', 34, 9 ), 'res': ( 'goto.cc', 32, 26 ) }, - # Another_Unicøde - { 'req': ( 'goto.cc', 36, 17 ), 'res': ( 'goto.cc', 32, 54 ) }, - { 'req': ( 'goto.cc', 36, 25 ), 'res': ( 'goto.cc', 32, 54 ) }, - { 'req': ( 'goto.cc', 38, 3 ), 'res': ( 'goto.cc', 36, 28 ) }, - # Expected failures - { 'req': ( 'goto.cc', 13, 1 ), 'res': 'Cannot jump to location' }, - { 'req': ( 'goto.cc', 16, 6 ), 'res': 'Cannot jump to location' }, - ] ) -@SharedYcmd -def Subcommands_GoToDeclaration_all_test( app, test ): - RunGoToTest_all( app, '', 'GoToDeclaration', test ) - - -@pytest.mark.parametrize( 'test', [ - { 'req': ( 'main.cpp', 1, 6 ), 'res': ( 'a.hpp', 1, 1 ) }, - { 'req': ( 'main.cpp', 2, 14 ), 'res': ( 'system/a.hpp', 1, 1 ) }, - { 'req': ( 'main.cpp', 3, 1 ), 'res': ( 'quote/b.hpp', 1, 1 ) }, - # FIXME: should fail since b.hpp is included with angled brackets but its - # folder is added with -iquote. - { 'req': ( 'main.cpp', 4, 10 ), 'res': ( 'quote/b.hpp', 1, 1 ) }, - { 'req': ( 'main.cpp', 5, 11 ), 'res': ( 'system/c.hpp', 1, 1 ) }, - { 'req': ( 'main.cpp', 6, 11 ), 'res': ( 'system/c.hpp', 1, 1 ) }, - # Expected failures - { 'req': ( 'main.cpp', 7, 1 ), 'res': 'Cannot jump to location' }, - { 'req': ( 'main.cpp', 10, 13 ), 'res': 'Cannot jump to location' }, - ] ) -@pytest.mark.parametrize( 'cmd', [ 'GoToImprecise', 'GoToInclude', 'GoTo' ] ) -@SharedYcmd -def Subcommands_GoToInclude_test( app, cmd, test ): - RunGoToTest_all( app, 'test-include', cmd, test ) - - -@pytest.mark.parametrize( 'test', [ - # Function - { 'req': ( 'goto.cc', 14, 21 ), 'res': [ ( 'goto.cc', 11, 10 ), - ( 'goto.cc', 14, 13 ), - ( 'goto.cc', 25, 22 ) ] }, - # Namespace - { 'req': ( 'goto.cc', 24, 17 ), 'res': [ ( 'goto.cc', 2, 11 ), - ( 'goto.cc', 14, 6 ), - ( 'goto.cc', 23, 14 ), - ( 'goto.cc', 24, 15 ), - ( 'goto.cc', 25, 15 ) ] }, - # Expected failure - { 'req': ( 'goto.cc', 27, 8 ), 'res': 'Cannot jump to location' }, - ] ) -@SharedYcmd -def Subcommands_GoToReferences_test( app, test ): - RunGoToTest_all( app, '', 'GoToReferences', test ) - - -@pytest.mark.parametrize( 'test', [ - # In same file - 1 result - { 'req': ( 'goto.cc', 1, 1, [ 'out_of_line' ] ), - 'res': ( 'goto.cc', 14, 13, { - 'description': 'Function: out_of_line', - 'extra_data': { 'kind': 'Function', 'name': 'out_of_line', } } - ) }, - # In same file - multiple results - { 'req': ( 'goto.cc', 1, 1, [ 'line' ] ), - 'res': [ - ( 'goto.cc', 6, 10, { - 'description': 'Function: in_line', - 'extra_data': { 'kind': 'Function', 'name': 'in_line' }, - } ), - ( 'goto.cc', 14, 13, { - 'description': 'Function: out_of_line', - 'extra_data': { 'kind': 'Function', 'name': 'out_of_line' }, - } ), - ] }, - # None - { 'req': ( 'goto.cc', 1, 1, [ '' ] ), 'res': 'Symbol not found' }, - - # Note we don't actually have any testdata that has a full index, so we can't - # test multiple files easily, but that's really a clangd thing, not a ycmd - # thing. -] ) -@SharedYcmd -def Subcommands_GoToSymbol_test( app, test ): - RunGoToTest_all( app, '', 'GoToSymbol', test ) - - -@pytest.mark.parametrize( 'test', [ - # In same file - multiple results - { 'req': ( 'goto.cc', 1, 1 ), - 'res': [ - ( 'goto.cc', 2, 1, { - 'description': "Namespace: Local", - 'extra_data': { 'kind': 'Namespace', 'name': 'Local' } } ), - ( 'goto.cc', 14, 1, { - 'description': "Function: Local::out_of_line", - 'extra_data': { 'kind': 'Function', 'name': 'Local::out_of_line' } } ), - ( 'goto.cc', 6, 5, { - 'description': "Function: in_line", - 'extra_data': { 'kind': 'Function', 'name': 'in_line' } } ), - ( 'goto.cc', 11, 5, { - 'description': 'Function: out_of_line', - 'extra_data': { 'kind': 'Function', 'name': 'out_of_line' } } ), - ( 'goto.cc', 19, 1, { - 'description': "Function: test", - 'extra_data': { 'kind': 'Function', 'name': 'test' } } ), - ( 'goto.cc', 21, 1, { - 'description': "Function: test", - 'extra_data': { 'kind': 'Function', 'name': 'test' } } ), - ( 'goto.cc', 30, 1, { - 'description': "Function: unicode", - 'extra_data': { 'kind': 'Function', 'name': 'unicode' } } ), - ( 'goto.cc', 4, 5, { - 'description': "Variable: x", - 'extra_data': { 'kind': 'Variable', 'name': 'x' } } ), - ] }, -] ) -@SharedYcmd -def Subcommands_GoToDocumentOutline_test( app, test ): - RunGoToTest_all( app, '', 'GoToDocumentOutline', test ) - - -@pytest.mark.parametrize( 'test', [ - { 'req': ( 'virtual.cpp', 1, 9 ), - 'res': [ - ( 'virtual.cpp', 5, 8 ), - ( 'virtual.cpp', 13, 8, ), - ( 'virtual.cpp', 9, 8 ), - ] }, - { 'req': ( 'virtual.cpp', 2, 15 ), - 'res': [ - ( 'virtual.cpp', 10, 15 ), - ( 'virtual.cpp', 6, 7 ), - ] }, - { 'req': ( 'virtual.cpp', 9, 8 ), - 'res': ( 'virtual.cpp', 14, 8 ) }, - { 'req': ( 'virtual.cpp', 13, 15 ), - 'res': [ - ( 'virtual.cpp', 5, 8 ), - ( 'virtual.cpp', 13, 8, ), - ( 'virtual.cpp', 9, 8 ), - ] }, - { 'req': ( 'virtual.cpp', 10, 15 ), - 'res': 'Cannot jump to location' }, -] ) -@SharedYcmd -def Subcommands_GoToImplementation_test( app, test ): - RunGoToTest_all( app, '', 'GoToImplementation', test ) - - def RunGetSemanticTest( app, filepath, filetype, @@ -429,200 +123,6 @@ def RunGetSemanticTest( app, RunAfterInitialized( app, test ) -@pytest.mark.parametrize( 'test', [ - # Basic pod types - [ { 'line_num': 24, 'column_num': 3 }, - has_entry( 'message', equal_to( 'struct Foo {}' ) ), - requests.codes.ok ], - # [ { 'line_num': 12, 'column_num': 2 }, 'Foo', - [ { 'line_num': 12, 'column_num': 8 }, - has_entry( 'message', equal_to( 'struct Foo {}' ) ), - requests.codes.ok ], - [ { 'line_num': 12, 'column_num': 9 }, - has_entry( 'message', equal_to( 'struct Foo {}' ) ), - requests.codes.ok ], - [ { 'line_num': 12, 'column_num': 10 }, - has_entry( 'message', equal_to( 'struct Foo {}' ) ), - requests.codes.ok ], - # [ { 'line_num': 13, 'column_num': 3 }, 'int', - [ { 'line_num': 13, 'column_num': 7 }, - has_entry( 'message', equal_to( 'public: int x; // In Foo' ) ), - requests.codes.ok ], - # [ { 'line_num': 15, 'column_num': 7 }, 'char' ], - - # Function - # [ { 'line_num': 22, 'column_num': 2 }, 'int main()' ], - [ { 'line_num': 22, 'column_num': 6 }, 'int main()', requests.codes.ok ], - - # Declared and canonical type - # On Ns:: - [ { 'line_num': 25, 'column_num': 3 }, 'namespace Ns', requests.codes.ok ], - # On Type (Type) - # [ { 'line_num': 25, 'column_num': 8 }, - # 'Ns::Type => Ns::BasicType' ], - # On "a" (Ns::Type) - # [ { 'line_num': 25, 'column_num': 15 }, - # 'Ns::Type => Ns::BasicType' ], - # [ { 'line_num': 26, 'column_num': 13 }, - # 'Ns::Type => Ns::BasicType' ], - - # Cursor on decl for refs & pointers - [ { 'line_num': 39, 'column_num': 3 }, - has_entry( 'message', equal_to( 'struct Foo {}' ) ), - requests.codes.ok ], - [ { 'line_num': 39, 'column_num': 11 }, - has_entry( 'message', equal_to( 'Foo &rFoo = foo; // In main' ) ), - requests.codes.ok ], - [ { 'line_num': 39, 'column_num': 15 }, - has_entry( 'message', equal_to( 'Foo foo; // In main' ) ), - requests.codes.ok ], - [ { 'line_num': 40, 'column_num': 3 }, - has_entry( 'message', equal_to( 'struct Foo {}' ) ), - requests.codes.ok ], - [ { 'line_num': 40, 'column_num': 11 }, - has_entry( 'message', equal_to( 'Foo *pFoo = &foo; // In main' ) ), - requests.codes.ok ], - [ { 'line_num': 40, 'column_num': 18 }, - has_entry( 'message', equal_to( 'Foo foo; // In main' ) ), - requests.codes.ok ], - # [ { 'line_num': 42, 'column_num': 3 }, 'const Foo &' ], - [ { 'line_num': 42, 'column_num': 16 }, - has_entry( 'message', equal_to( 'const Foo &crFoo = foo; // In main' ) ), - requests.codes.ok ], - # [ { 'line_num': 43, 'column_num': 3 }, 'const Foo *' ], - [ { 'line_num': 43, 'column_num': 16 }, - has_entry( 'message', equal_to( 'const Foo *cpFoo = &foo; // In main' ) ), - requests.codes.ok ], - - # Cursor on usage - [ { 'line_num': 45, 'column_num': 13 }, - has_entry( 'message', equal_to( 'const Foo &crFoo = foo; // In main' ) ), - requests.codes.ok ], - # [ { 'line_num': 45, 'column_num': 19 }, 'const int' ], - [ { 'line_num': 46, 'column_num': 13 }, - has_entry( 'message', equal_to( 'const Foo *cpFoo = &foo; // In main' ) ), - requests.codes.ok ], - # [ { 'line_num': 46, 'column_num': 20 }, 'const int' ], - [ { 'line_num': 47, 'column_num': 12 }, - has_entry( 'message', equal_to( 'Foo &rFoo = foo; // In main' ) ), - requests.codes.ok ], - [ { 'line_num': 47, 'column_num': 17 }, - has_entry( 'message', equal_to( 'public: int y; // In Foo' ) ), - requests.codes.ok ], - [ { 'line_num': 48, 'column_num': 12 }, - has_entry( 'message', equal_to( 'Foo *pFoo = &foo; // In main' ) ), - requests.codes.ok ], - [ { 'line_num': 48, 'column_num': 18 }, - has_entry( 'message', equal_to( 'public: int x; // In Foo' ) ), - requests.codes.ok ], - - # Auto in declaration - # [ { 'line_num': 28, 'column_num': 3 }, 'struct Foo &' ], - # [ { 'line_num': 28, 'column_num': 11 }, 'struct Foo &' ], - [ { 'line_num': 28, 'column_num': 18 }, - has_entry( 'message', equal_to( 'Foo foo; // In main' ) ), - requests.codes.ok ], - # [ { 'line_num': 29, 'column_num': 3 }, 'Foo *' ], - # [ { 'line_num': 29, 'column_num': 11 }, 'Foo *' ], - [ { 'line_num': 29, 'column_num': 18 }, - has_entry( 'message', equal_to( 'Foo foo; // In main' ) ), - requests.codes.ok ], - # [ { 'line_num': 31, 'column_num': 3 }, 'const Foo &' ], - # [ { 'line_num': 31, 'column_num': 16 }, 'const Foo &' ], - # [ { 'line_num': 32, 'column_num': 3 }, 'const Foo *' ], - # [ { 'line_num': 32, 'column_num': 16 }, 'const Foo *' ], - - # Auto in usage - # [ { 'line_num': 34, 'column_num': 14 }, 'const Foo' ], - # [ { 'line_num': 34, 'column_num': 21 }, 'const int' ], - # [ { 'line_num': 35, 'column_num': 14 }, 'const Foo *' ], - # [ { 'line_num': 35, 'column_num': 22 }, 'const int' ], - [ { 'line_num': 36, 'column_num': 13 }, - has_entry( 'message', equal_to( 'auto &arFoo = foo; // In main' ) ), - requests.codes.ok ], - [ { 'line_num': 36, 'column_num': 19 }, - has_entry( 'message', equal_to( 'public: int y; // In Foo' ) ), - requests.codes.ok ], - # [ { 'line_num': 37, 'column_num': 13 }, 'Foo *' ], - [ { 'line_num': 37, 'column_num': 20 }, - has_entry( 'message', equal_to( 'public: int x; // In Foo' ) ), - requests.codes.ok ], - - # Unicode - [ { 'line_num': 51, 'column_num': 13 }, - has_entry( 'message', equal_to( 'Unicøde *ø; // In main' ) ), - requests.codes.ok ], - - # Bound methods - # On Win32, methods pick up an __attribute__((thiscall)) to annotate their - # calling convention. This shows up in the type, which isn't ideal, but - # also prohibitively complex to try and strip out. - [ { 'line_num': 53, 'column_num': 15 }, - has_entry( 'message', matches_regexp( - r'int bar\(int i\)(?: __attribute__\(\(thiscall\)\))?; // In Foo' ) ), - requests.codes.ok ], - [ { 'line_num': 54, 'column_num': 18 }, - has_entry( 'message', matches_regexp( - r'int bar\(int i\)(?: __attribute__\(\(thiscall\)\))?; // In Foo' ) ), - requests.codes.ok ], - # Multi-line function declaration - [ { 'line_num': 58, 'column_num': 20 }, - has_entry( 'message', equal_to( - 'unsigned long long long_function_name(unsigned long long first, ' - 'unsigned long long second)' ) ), - requests.codes.ok ], - [ { 'line_num': 61, 'column_num': 20 }, - has_entry( 'message', equal_to( - 'unsigned long long long_function_name(unsigned long long first, ' - 'unsigned long long second); // In namespace ns' ) ), - requests.codes.ok ], - ] ) -@pytest.mark.parametrize( 'subcommand', [ 'GetType', 'GetTypeImprecise' ] ) -@SharedYcmd -def Subcommands_GetType_test( app, subcommand, test ): - RunGetSemanticTest( app, - PathToTestFile( 'GetType_Clang_test.cc' ), - 'cpp', - test, - [ subcommand ], - test[ 2 ] ) - - -@pytest.mark.parametrize( 'test', [ - # from local file - [ { 'line_num': 5, 'column_num': 10 }, - has_entry( 'detailed_info', equal_to( - 'function docstring_int_main_TU_file\n\n→ void\ndocstring\n\n' - 'void docstring_int_main_TU_file()' ) ), - requests.codes.ok ], - # from header - [ { 'line_num': 6, 'column_num': 10 }, - has_entry( 'detailed_info', equal_to( - 'function docstring_from_header_file\n\n→ void\ndocstring\n\n' - 'void docstring_from_header_file()' ) ), - requests.codes.ok ], - # no docstring - [ { 'line_num': 7, 'column_num': 7 }, - has_entry( 'detailed_info', equal_to( - 'variable x\n\nType: int\nValue = 3\n\n' - '// In docstring_int_main_TU_file\nint x = 3' ) ), - requests.codes.ok ], - # no hover - [ { 'line_num': 8, 'column_num': 1 }, - ErrorMatcher( RuntimeError, 'No documentation available.' ), - requests.codes.server_error ] - ] ) -@pytest.mark.parametrize( 'subcommand', [ 'GetDoc', 'GetDocImprecise' ] ) -@SharedYcmd -def Subcommands_GetDoc_test( app, subcommand, test ): - RunGetSemanticTest( app, - PathToTestFile( 'GetDoc_Clang_test.cc' ), - 'cpp', - test, - [ subcommand ], - test[ 2 ] ) - - def RunFixItTest( app, line, column, lang, file_path, check ): contents = ReadFile( file_path ) @@ -1031,45 +531,6 @@ def FixIt_Check_AutoExpand_Resolved( results ): } ) ) -@pytest.mark.parametrize( 'line,column,language,filepath,check', [ - [ 16, 1, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Ins ], - [ 25, 14, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_InsMultiLine ], - [ 35, 7, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Del ], - [ 40, 6, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Repl ], - [ 48, 3, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_DelAdd ], - [ 5, 3, 'objective-c', PathToTestFile( 'objc', 'FixIt_Clang_objc.m' ), - FixIt_Check_objc ], - [ 7, 1, 'objective-c', PathToTestFile( 'objc', 'FixIt_Clang_objc.m' ), - FixIt_Check_objc_NoFixIt ], - [ 3, 12, 'cuda', PathToTestFile( 'cuda', 'fixit_test.cu' ), - FixIt_Check_cuda ], - # multiple errors on a single line; both with fixits. The cursor is on the - # first one (so just that one is fixed) - [ 54, 15, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiFirst ], - # should put closest fix-it first? - [ 54, 51, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_MultiSecond ], - # unicode in line for fixit - [ 21, 16, 'cpp11', PathToTestFile( 'unicode.cc' ), - FixIt_Check_unicode_Ins ], - # FixIt attached to a "child" diagnostic (i.e. a Note) - [ 60, 1, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_Note ], - # FixIt due to forced spell checking - [ 72, 9, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), - FixIt_Check_cpp11_SpellCheck ], - ] ) -@SharedYcmd -def Subcommands_FixIt_all_test( app, line, column, language, filepath, check ): - RunFixItTest( app, line, column, language, filepath, check ) - - def RunRangedFixItTest( app, rng, expected, chosen_fixit = 0 ): contents = ReadFile( PathToTestFile( 'FixIt_Clang_cpp11.cpp' ) ) args = { @@ -1096,150 +557,710 @@ def RunRangedFixItTest( app, rng, expected, chosen_fixit = 0 ): expected( response ) -@WithRetry -@pytest.mark.parametrize( 'test', [ - [ { - 'start': { 'line_num': 80, 'column_num': 1 }, - 'end': { 'line_num': 80, 'column_num': 4 }, - }, - FixIt_Check_AutoExpand_Resolved ], - [ { - 'start': { 'line_num': 83, 'column_num': 3 }, - 'end': { 'line_num': 83, 'column_num': 13 }, - }, - FixIt_Check_MacroExpand_Resolved ], - [ { - 'start': { 'line_num': 84, 'column_num': 14 }, - 'end': { 'line_num': 84, 'column_num': 20 }, - }, - FixIt_Check_SubexprExtract_Resolved ], - [ { - 'start': { 'line_num': 80, 'column_num': 19 }, - 'end': { 'line_num': 80, 'column_num': 35 }, - }, - FixIt_Check_RawStringReplace_Resolved ], - ] ) -@SharedYcmd -def Subcommands_FixIt_Ranged_test( app, test ): - RunRangedFixItTest( app, test[ 0 ], test[ 1 ] ) +class SubcommandsTest( TestCase ): + # This test is isolated to trigger objcpp hooks, rather than fetching + # completer from cache. + @IsolatedYcmd() + def test_Subcommands_DefinedSubcommands( self, app ): + file_path = PathToTestFile( 'GoTo_Clang_ZeroBasedLineAndColumn_test.cc' ) + RunAfterInitialized( app, { + 'request': { + 'completer_target': 'filetype_default', + 'line_num': 10, + 'column_num': 3, + 'filetype': 'objcpp', + 'filepath': file_path + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_exactly( *sorted( [ 'ExecuteCommand', + 'FixIt', + 'Format', + 'GetDoc', + 'GetDocImprecise', + 'GetType', + 'GetTypeImprecise', + 'GoTo', + 'GoToDeclaration', + 'GoToDefinition', + 'GoToDocumentOutline', + 'GoToImprecise', + 'GoToImplementation', + 'GoToInclude', + 'GoToReferences', + 'GoToSymbol', + 'RefactorRename', + 'RestartServer' ] ) ) + }, + 'route': '/defined_subcommands', + } ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_ServerNotInitialized( self, app ): + for cmd in [ + 'FixIt', + 'Format', + 'GetDoc', + 'GetDocImprecise', + 'GetType', + 'GetTypeImprecise', + 'GoTo', + 'GoToDeclaration', + 'GoToDefinition', + 'GoToInclude', + 'GoToImplementation', + 'GoToReferences', + 'RefactorRename', + ]: + with self.subTest( cmd = cmd ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) + + @patch.object( completer, '_ServerIsInitialized', return_value = False ) + def Test( app, cmd, *args ): + request = { + 'line_num': 1, + 'column_num': 1, + 'event_name': 'FileReadyToParse', + 'filetype': 'cpp', + 'command_arguments': [ cmd ] + } + app.post_json( '/event_notification', + BuildRequest( **request ), + expect_errors = True ) + response = app.post_json( + '/run_completer_command', + BuildRequest( **request ), + expect_errors = True + ) + assert_that( response.status_code, + equal_to( requests.codes.server_error ) ) + assert_that( response.json, + ErrorMatcher( RuntimeError, + 'Server is initializing. Please wait.' ) ) + + Test( app, cmd ) + + + @SharedYcmd + def test_Subcommands_GoTo_ZeroBasedLineAndColumn( self, app ): + file_path = PathToTestFile( 'GoTo_Clang_ZeroBasedLineAndColumn_test.cc' ) + RunAfterInitialized( app, { + 'request': { + 'contents': ReadFile( file_path ), + 'completer_target': 'filetype_default', + 'command_arguments': [ 'GoToDefinition' ], + 'line_num': 10, + 'column_num': 3, + 'filetype': 'cpp', + 'filepath': file_path + }, + 'expect': { + 'response': requests.codes.ok, + 'data': { + 'filepath': os.path.abspath( file_path ), + 'line_num': 2, + 'column_num': 8 + } + }, + 'route': '/run_completer_command', + } ) + + + @SharedYcmd + def test_Subcommands_GoTo_all( self, app ): + for test, cmd in itertools.product( + [ + # Local::x -> definition/declaration of x + { 'req': ( 'goto.cc', 23, 21 ), 'res': ( 'goto.cc', 4, 9 ) }, + # Local::in_line -> definition/declaration of Local::in_line + { 'req': ( 'goto.cc', 24, 26 ), 'res': ( 'goto.cc', 6, 10 ) }, + # Local -> definition/declaration of Local + { 'req': ( 'goto.cc', 24, 16 ), 'res': ( 'goto.cc', 2, 11 ) }, + # Local::out_of_line -> definition of Local::out_of_line + { 'req': ( 'goto.cc', 25, 27 ), 'res': ( 'goto.cc', 14, 13 ) }, + # GoToDeclaration alternates between definition and declaration + { 'req': ( 'goto.cc', 14, 13 ), 'res': ( 'goto.cc', 11, 10 ) }, + { 'req': ( 'goto.cc', 11, 10 ), 'res': ( 'goto.cc', 14, 13 ) }, + # test -> definition and declaration of test + { 'req': ( 'goto.cc', 21, 5 ), 'res': ( 'goto.cc', 19, 5 ) }, + { 'req': ( 'goto.cc', 19, 5 ), 'res': ( 'goto.cc', 21, 5 ) }, + # Unicøde + { 'req': ( 'goto.cc', 34, 9 ), 'res': ( 'goto.cc', 32, 26 ) }, + # Another_Unicøde + { 'req': ( 'goto.cc', 36, 17 ), 'res': ( 'goto.cc', 32, 54 ) }, + { 'req': ( 'goto.cc', 36, 25 ), 'res': ( 'goto.cc', 32, 54 ) }, + { 'req': ( 'goto.cc', 38, 3 ), 'res': ( 'goto.cc', 36, 28 ) }, + # Expected failures + { 'req': ( 'goto.cc', 13, 1 ), 'res': 'Cannot jump to location' }, + { 'req': ( 'goto.cc', 16, 6 ), 'res': 'Cannot jump to location' }, + ], + [ 'GoToImprecise', 'GoToDefinition', 'GoTo' ] ): + with self.subTest( test = test, cmd = cmd ): + RunGoToTest_all( app, '', cmd, test ) + + + @SharedYcmd + def test_Subcommands_GoToDeclaration_all( self, app ): + for test in [ + # Local::x -> definition/declaration of x + { 'req': ( 'goto.cc', 23, 21 ), 'res': ( 'goto.cc', 4, 9 ) }, + # Local::in_line -> definition/declaration of Local::in_line + { 'req': ( 'goto.cc', 24, 26 ), 'res': ( 'goto.cc', 6, 10 ) }, + # Local -> definition/declaration of Local + { 'req': ( 'goto.cc', 24, 16 ), 'res': ( 'goto.cc', 2, 11 ) }, + # Local::out_of_line -> declaration of Local::out_of_line + { 'req': ( 'goto.cc', 25, 27 ), 'res': ( 'goto.cc', 11, 10 ) }, + # GoToDeclaration alternates between definition and declaration + { 'req': ( 'goto.cc', 14, 13 ), 'res': ( 'goto.cc', 11, 10 ) }, + { 'req': ( 'goto.cc', 11, 10 ), 'res': ( 'goto.cc', 14, 13 ) }, + # test -> definition and declaration of test + { 'req': ( 'goto.cc', 21, 5 ), 'res': ( 'goto.cc', 19, 5 ) }, + { 'req': ( 'goto.cc', 19, 5 ), 'res': ( 'goto.cc', 21, 5 ) }, + # Unicøde + { 'req': ( 'goto.cc', 34, 9 ), 'res': ( 'goto.cc', 32, 26 ) }, + # Another_Unicøde + { 'req': ( 'goto.cc', 36, 17 ), 'res': ( 'goto.cc', 32, 54 ) }, + { 'req': ( 'goto.cc', 36, 25 ), 'res': ( 'goto.cc', 32, 54 ) }, + { 'req': ( 'goto.cc', 38, 3 ), 'res': ( 'goto.cc', 36, 28 ) }, + # Expected failures + { 'req': ( 'goto.cc', 13, 1 ), 'res': 'Cannot jump to location' }, + { 'req': ( 'goto.cc', 16, 6 ), 'res': 'Cannot jump to location' }, + ]: + with self.subTest( test = test ): + RunGoToTest_all( app, '', 'GoToDeclaration', test ) + + + @SharedYcmd + def test_Subcommands_GoToInclude( self, app ): + for test, cmd in itertools.product( [ + { 'req': ( 'main.cpp', 1, 6 ), 'res': ( 'a.hpp', 1, 1 ) }, + { 'req': ( 'main.cpp', 2, 14 ), 'res': ( 'system/a.hpp', 1, 1 ) }, + { 'req': ( 'main.cpp', 3, 1 ), 'res': ( 'quote/b.hpp', 1, 1 ) }, + # FIXME: should fail since b.hpp is included with angled brackets but + # its folder is added with -iquote. + { 'req': ( 'main.cpp', 4, 10 ), 'res': ( 'quote/b.hpp', 1, 1 ) }, + { 'req': ( 'main.cpp', 5, 11 ), 'res': ( 'system/c.hpp', 1, 1 ) }, + { 'req': ( 'main.cpp', 6, 11 ), 'res': ( 'system/c.hpp', 1, 1 ) }, + # Expected failures + { 'req': ( 'main.cpp', 7, 1 ), 'res': 'Cannot jump to location' }, + { 'req': ( 'main.cpp', 10, 13 ), 'res': 'Cannot jump to location' }, + ], + [ 'GoToImprecise', 'GoToInclude', 'GoTo' ] ): + with self.subTest( test = test, cmd = cmd ): + RunGoToTest_all( app, 'test-include', cmd, test ) + + + @SharedYcmd + def test_Subcommands_GoToReferences( self, app ): + for test in [ + # Function + { 'req': ( 'goto.cc', 14, 21 ), 'res': [ ( 'goto.cc', 11, 10 ), + ( 'goto.cc', 14, 13 ), + ( 'goto.cc', 25, 22 ) ] }, + # Namespace + { 'req': ( 'goto.cc', 24, 17 ), 'res': [ ( 'goto.cc', 2, 11 ), + ( 'goto.cc', 14, 6 ), + ( 'goto.cc', 23, 14 ), + ( 'goto.cc', 24, 15 ), + ( 'goto.cc', 25, 15 ) ] }, + # Expected failure + { 'req': ( 'goto.cc', 27, 8 ), 'res': 'Cannot jump to location' }, + ]: + with self.subTest( test = test ): + RunGoToTest_all( app, '', 'GoToReferences', test ) + + + @SharedYcmd + def test_Subcommands_GoToSymbol( self, app ): + for test in [ + # In same file - 1 result + { 'req': ( 'goto.cc', 1, 1, [ 'out_of_line' ] ), + 'res': ( 'goto.cc', 14, 13, { + 'description': 'Function: out_of_line', + 'extra_data': { 'kind': 'Function', 'name': 'out_of_line', } } + ) }, + # In same file - multiple results + { 'req': ( 'goto.cc', 1, 1, [ 'line' ] ), + 'res': [ + ( 'goto.cc', 6, 10, { + 'description': 'Function: in_line', + 'extra_data': { 'kind': 'Function', 'name': 'in_line' }, + } ), + ( 'goto.cc', 14, 13, { + 'description': 'Function: out_of_line', + 'extra_data': { 'kind': 'Function', 'name': 'out_of_line' }, + } ), + ] }, + # None + { 'req': ( 'goto.cc', 1, 1, [ '' ] ), 'res': 'Symbol not found' }, + + # Note we don't actually have any testdata that has a full index, so we + # can't test multiple files easily, but that's really a clangd thing, not + # a ycmd thing. + ]: + with self.subTest( test = test ): + RunGoToTest_all( app, '', 'GoToSymbol', test ) + + + @SharedYcmd + def test_Subcommands_GoToDocumentOutline( self, app ): + for test in [ + # In same file - multiple results + { 'req': ( 'goto.cc', 1, 1 ), + 'res': [ + ( 'goto.cc', 2, 1, { + 'description': "Namespace: Local", + 'extra_data': { 'kind': 'Namespace', 'name': 'Local' } } ), + ( 'goto.cc', 14, 1, { + 'description': "Function: Local::out_of_line", + 'extra_data': { 'kind': 'Function', + 'name': 'Local::out_of_line' } } ), + ( 'goto.cc', 6, 5, { + 'description': "Function: in_line", + 'extra_data': { 'kind': 'Function', 'name': 'in_line' } } ), + ( 'goto.cc', 11, 5, { + 'description': 'Function: out_of_line', + 'extra_data': { 'kind': 'Function', 'name': 'out_of_line' } } ), + ( 'goto.cc', 19, 1, { + 'description': "Function: test", + 'extra_data': { 'kind': 'Function', 'name': 'test' } } ), + ( 'goto.cc', 21, 1, { + 'description': "Function: test", + 'extra_data': { 'kind': 'Function', 'name': 'test' } } ), + ( 'goto.cc', 30, 1, { + 'description': "Function: unicode", + 'extra_data': { 'kind': 'Function', 'name': 'unicode' } } ), + ( 'goto.cc', 4, 5, { + 'description': "Variable: x", + 'extra_data': { 'kind': 'Variable', 'name': 'x' } } ), + ] } ]: + with self.subTest( test = test ): + RunGoToTest_all( app, '', 'GoToDocumentOutline', test ) + + + @SharedYcmd + def test_Subcommands_GoToImplementation( self, app ): + for test in [ + { 'req': ( 'virtual.cpp', 1, 9 ), + 'res': [ + ( 'virtual.cpp', 5, 8 ), + ( 'virtual.cpp', 13, 8, ), + ( 'virtual.cpp', 9, 8 ), + ] }, + { 'req': ( 'virtual.cpp', 2, 15 ), + 'res': [ + ( 'virtual.cpp', 10, 15 ), + ( 'virtual.cpp', 6, 7 ), + ] }, + { 'req': ( 'virtual.cpp', 9, 8 ), + 'res': ( 'virtual.cpp', 14, 8 ) }, + { 'req': ( 'virtual.cpp', 13, 15 ), + 'res': [ + ( 'virtual.cpp', 5, 8 ), + ( 'virtual.cpp', 13, 8, ), + ( 'virtual.cpp', 9, 8 ), + ] }, + { 'req': ( 'virtual.cpp', 10, 15 ), + 'res': 'Cannot jump to location' }, + ]: + with self.subTest( test = test ): + RunGoToTest_all( app, '', 'GoToImplementation', test ) + + + @SharedYcmd + def test_Subcommands_GetType( self, app ): + for test, cmd in itertools.product( [ + # Basic pod types + [ { 'line_num': 24, 'column_num': 3 }, + has_entry( 'message', equal_to( 'struct Foo {}' ) ), + requests.codes.ok ], + # [ { 'line_num': 12, 'column_num': 2 }, 'Foo', + [ { 'line_num': 12, 'column_num': 8 }, + has_entry( 'message', equal_to( 'struct Foo {}' ) ), + requests.codes.ok ], + [ { 'line_num': 12, 'column_num': 9 }, + has_entry( 'message', equal_to( 'struct Foo {}' ) ), + requests.codes.ok ], + [ { 'line_num': 12, 'column_num': 10 }, + has_entry( 'message', equal_to( 'struct Foo {}' ) ), + requests.codes.ok ], + # [ { 'line_num': 13, 'column_num': 3 }, 'int', + [ { 'line_num': 13, 'column_num': 7 }, + has_entry( 'message', equal_to( 'public: int x; // In Foo' ) ), + requests.codes.ok ], + # [ { 'line_num': 15, 'column_num': 7 }, 'char' ], + + # Function + # [ { 'line_num': 22, 'column_num': 2 }, 'int main()' ], + [ { 'line_num': 22, 'column_num': 6 }, + 'int main()', + requests.codes.ok ], + + # Declared and canonical type + # On Ns:: + [ { 'line_num': 25, 'column_num': 3 }, + 'namespace Ns', + requests.codes.ok ], + # On Type (Type) + # [ { 'line_num': 25, 'column_num': 8 }, + # 'Ns::Type => Ns::BasicType' ], + # On "a" (Ns::Type) + # [ { 'line_num': 25, 'column_num': 15 }, + # 'Ns::Type => Ns::BasicType' ], + # [ { 'line_num': 26, 'column_num': 13 }, + # 'Ns::Type => Ns::BasicType' ], + + # Cursor on decl for refs & pointers + [ { 'line_num': 39, 'column_num': 3 }, + has_entry( 'message', equal_to( 'struct Foo {}' ) ), + requests.codes.ok ], + [ { 'line_num': 39, 'column_num': 11 }, + has_entry( 'message', equal_to( 'Foo &rFoo = foo; // In main' ) ), + requests.codes.ok ], + [ { 'line_num': 39, 'column_num': 15 }, + has_entry( 'message', equal_to( 'Foo foo; // In main' ) ), + requests.codes.ok ], + [ { 'line_num': 40, 'column_num': 3 }, + has_entry( 'message', equal_to( 'struct Foo {}' ) ), + requests.codes.ok ], + [ { 'line_num': 40, 'column_num': 11 }, + has_entry( 'message', equal_to( 'Foo *pFoo = &foo; // In main' ) ), + requests.codes.ok ], + [ { 'line_num': 40, 'column_num': 18 }, + has_entry( 'message', equal_to( 'Foo foo; // In main' ) ), + requests.codes.ok ], + # [ { 'line_num': 42, 'column_num': 3 }, 'const Foo &' ], + [ { 'line_num': 42, 'column_num': 16 }, + has_entry( 'message', + equal_to( 'const Foo &crFoo = foo; // In main' ) ), + requests.codes.ok ], + # [ { 'line_num': 43, 'column_num': 3 }, 'const Foo *' ], + [ { 'line_num': 43, 'column_num': 16 }, + has_entry( 'message', + equal_to( 'const Foo *cpFoo = &foo; // In main' ) ), + requests.codes.ok ], + + # Cursor on usage + [ { 'line_num': 45, 'column_num': 13 }, + has_entry( 'message', + equal_to( 'const Foo &crFoo = foo; // In main' ) ), + requests.codes.ok ], + # [ { 'line_num': 45, 'column_num': 19 }, 'const int' ], + [ { 'line_num': 46, 'column_num': 13 }, + has_entry( 'message', + equal_to( 'const Foo *cpFoo = &foo; // In main' ) ), + requests.codes.ok ], + # [ { 'line_num': 46, 'column_num': 20 }, 'const int' ], + [ { 'line_num': 47, 'column_num': 12 }, + has_entry( 'message', equal_to( 'Foo &rFoo = foo; // In main' ) ), + requests.codes.ok ], + [ { 'line_num': 47, 'column_num': 17 }, + has_entry( 'message', equal_to( 'public: int y; // In Foo' ) ), + requests.codes.ok ], + [ { 'line_num': 48, 'column_num': 12 }, + has_entry( 'message', equal_to( 'Foo *pFoo = &foo; // In main' ) ), + requests.codes.ok ], + [ { 'line_num': 48, 'column_num': 18 }, + has_entry( 'message', equal_to( 'public: int x; // In Foo' ) ), + requests.codes.ok ], + + # Auto in declaration + # [ { 'line_num': 28, 'column_num': 3 }, 'struct Foo &' ], + # [ { 'line_num': 28, 'column_num': 11 }, 'struct Foo &' ], + [ { 'line_num': 28, 'column_num': 18 }, + has_entry( 'message', equal_to( 'Foo foo; // In main' ) ), + requests.codes.ok ], + # [ { 'line_num': 29, 'column_num': 3 }, 'Foo *' ], + # [ { 'line_num': 29, 'column_num': 11 }, 'Foo *' ], + [ { 'line_num': 29, 'column_num': 18 }, + has_entry( 'message', equal_to( 'Foo foo; // In main' ) ), + requests.codes.ok ], + # [ { 'line_num': 31, 'column_num': 3 }, 'const Foo &' ], + # [ { 'line_num': 31, 'column_num': 16 }, 'const Foo &' ], + # [ { 'line_num': 32, 'column_num': 3 }, 'const Foo *' ], + # [ { 'line_num': 32, 'column_num': 16 }, 'const Foo *' ], + + # Auto in usage + # [ { 'line_num': 34, 'column_num': 14 }, 'const Foo' ], + # [ { 'line_num': 34, 'column_num': 21 }, 'const int' ], + # [ { 'line_num': 35, 'column_num': 14 }, 'const Foo *' ], + # [ { 'line_num': 35, 'column_num': 22 }, 'const int' ], + [ { 'line_num': 36, 'column_num': 13 }, + has_entry( 'message', equal_to( 'auto &arFoo = foo; // In main' ) ), + requests.codes.ok ], + [ { 'line_num': 36, 'column_num': 19 }, + has_entry( 'message', equal_to( 'public: int y; // In Foo' ) ), + requests.codes.ok ], + # [ { 'line_num': 37, 'column_num': 13 }, 'Foo *' ], + [ { 'line_num': 37, 'column_num': 20 }, + has_entry( 'message', equal_to( 'public: int x; // In Foo' ) ), + requests.codes.ok ], + + # Unicode + [ { 'line_num': 51, 'column_num': 13 }, + has_entry( 'message', equal_to( 'Unicøde *ø; // In main' ) ), + requests.codes.ok ], + + # Bound methods + # On Win32, methods pick up an __attribute__((thiscall)) to annotate + # their calling convention. This shows up in the type, which isn't + # ideal, but also prohibitively complex to try and strip out. + [ { 'line_num': 53, 'column_num': 15 }, + has_entry( 'message', matches_regexp( + r'int bar\(int i\)(?: ' + r'__attribute__\(\(thiscall\)\))?; // In Foo' ) ), + requests.codes.ok ], + [ { 'line_num': 54, 'column_num': 18 }, + has_entry( 'message', matches_regexp( + r'int bar\(int i\)(?: _' + r'_attribute__\(\(thiscall\)\))?; // In Foo' ) ), + requests.codes.ok ], + # Multi-line function declaration + [ { 'line_num': 58, 'column_num': 20 }, + has_entry( 'message', equal_to( + 'unsigned long long long_function_name(unsigned long long first, ' + 'unsigned long long second)' ) ), + requests.codes.ok ], + [ { 'line_num': 61, 'column_num': 20 }, + has_entry( 'message', equal_to( + 'unsigned long long long_function_name(unsigned long long first, ' + 'unsigned long long second); // In namespace ns' ) ), + requests.codes.ok ], + ], + [ 'GetType', 'GetTypeImprecise' ] ): + with self.subTest( test = test, cmd = cmd ): + RunGetSemanticTest( app, + PathToTestFile( 'GetType_Clang_test.cc' ), + 'cpp', + test, + [ cmd ], + test[ 2 ] ) + + + @SharedYcmd + def test_Subcommands_GetDoc( self, app ): + for test, cmd in itertools.product( [ + # from local file + [ { 'line_num': 5, 'column_num': 10 }, + has_entry( 'detailed_info', equal_to( + 'function docstring_int_main_TU_file\n\n→ void\ndocstring\n\n' + 'void docstring_int_main_TU_file()' ) ), + requests.codes.ok ], + # from header + [ { 'line_num': 6, 'column_num': 10 }, + has_entry( 'detailed_info', equal_to( + 'function docstring_from_header_file\n\n→ void\ndocstring\n\n' + 'void docstring_from_header_file()' ) ), + requests.codes.ok ], + # no docstring + [ { 'line_num': 7, 'column_num': 7 }, + has_entry( 'detailed_info', equal_to( + 'variable x\n\nType: int\nValue = 3\n\n' + '// In docstring_int_main_TU_file\nint x = 3' ) ), + requests.codes.ok ], + # no hover + [ { 'line_num': 8, 'column_num': 1 }, + ErrorMatcher( RuntimeError, 'No documentation available.' ), + requests.codes.server_error ] + ], + [ 'GetDoc', 'GetDocImprecise' ] ): + with self.subTest( test = test, cmd = cmd ): + RunGetSemanticTest( app, + PathToTestFile( 'GetDoc_Clang_test.cc' ), + 'cpp', + test, + [ cmd ], + test[ 2 ] ) + + + @SharedYcmd + def test_Subcommands_FixIt_all( self, app ): + for line, column, language, filepath, check in [ + [ 16, 1, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Ins ], + [ 25, 14, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_InsMultiLine ], + [ 35, 7, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Del ], + [ 40, 6, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Repl ], + [ 48, 3, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_DelAdd ], + [ 5, 3, 'objective-c', PathToTestFile( 'objc', 'FixIt_Clang_objc.m' ), + FixIt_Check_objc ], + [ 7, 1, 'objective-c', PathToTestFile( 'objc', 'FixIt_Clang_objc.m' ), + FixIt_Check_objc_NoFixIt ], + [ 3, 12, 'cuda', PathToTestFile( 'cuda', 'fixit_test.cu' ), + FixIt_Check_cuda ], + # multiple errors on a single line; both with fixits. The cursor is on the + # first one (so just that one is fixed) + [ 54, 15, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiFirst ], + # should put closest fix-it first? + [ 54, 51, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_MultiSecond ], + # unicode in line for fixit + [ 21, 16, 'cpp11', PathToTestFile( 'unicode.cc' ), + FixIt_Check_unicode_Ins ], + # FixIt attached to a "child" diagnostic (i.e. a Note) + [ 60, 1, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_Note ], + # FixIt due to forced spell checking + [ 72, 9, 'cpp11', PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), + FixIt_Check_cpp11_SpellCheck ], + ]: + with self.subTest( line = line, + column = column, + language = language, + filepath = filepath, + check = check ): + RunFixItTest( app, line, column, language, filepath, check ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_FixIt_Ranged( self, app ): + for test in [ + [ { + 'start': { 'line_num': 80, 'column_num': 1 }, + 'end': { 'line_num': 80, 'column_num': 4 }, + }, + FixIt_Check_AutoExpand_Resolved ], + [ { + 'start': { 'line_num': 83, 'column_num': 3 }, + 'end': { 'line_num': 83, 'column_num': 13 }, + }, + FixIt_Check_MacroExpand_Resolved ], + [ { + 'start': { 'line_num': 84, 'column_num': 14 }, + 'end': { 'line_num': 84, 'column_num': 20 }, + }, + FixIt_Check_SubexprExtract_Resolved ], + [ { + 'start': { 'line_num': 80, 'column_num': 19 }, + 'end': { 'line_num': 80, 'column_num': 35 }, + }, + FixIt_Check_RawStringReplace_Resolved ], + ]: + with self.subTest( test = test ): + RunRangedFixItTest( app, test[ 0 ], test[ 1 ] ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_FixIt_AlreadyResolved( self, app ): + filename = PathToTestFile( 'FixIt_Clang_cpp11.cpp' ) + request = { + 'completer_target' : 'filetype_default', + 'contents' : ReadFile( filename ), + 'filepath' : filename, + 'command_arguments': [ 'FixIt' ], + 'line_num' : 16, + 'column_num' : 1, + 'filetype' : 'cpp' + } + app.post_json( '/event_notification', + CombineRequest( request, { + 'event_name': 'FileReadyToParse', + } ), + expect_errors = True ) + WaitUntilCompleterServerReady( app, 'cpp' ) + expected = app.post_json( '/run_completer_command', + BuildRequest( **request ) ).json + print( 'expected = ' ) + print( expected ) + request[ 'fixit' ] = expected[ 'fixits' ][ 0 ] + actual = app.post_json( '/resolve_fixit', + BuildRequest( **request ) ).json + print( 'actual = ' ) + print( actual ) + assert_that( actual, equal_to( expected ) ) -@WithRetry -@SharedYcmd -def Subcommands_FixIt_AlreadyResolved_test( app ): - filename = PathToTestFile( 'FixIt_Clang_cpp11.cpp' ) - request = { - 'completer_target' : 'filetype_default', - 'contents' : ReadFile( filename ), - 'filepath' : filename, - 'command_arguments': [ 'FixIt' ], - 'line_num' : 16, - 'column_num' : 1, - 'filetype' : 'cpp' - } - app.post_json( '/event_notification', - CombineRequest( request, { - 'event_name': 'FileReadyToParse', - } ), - expect_errors = True ) - WaitUntilCompleterServerReady( app, 'cpp' ) - expected = app.post_json( '/run_completer_command', - BuildRequest( **request ) ).json - print( 'expected = ' ) - print( expected ) - request[ 'fixit' ] = expected[ 'fixits' ][ 0 ] - actual = app.post_json( '/resolve_fixit', - BuildRequest( **request ) ).json - print( 'actual = ' ) - print( actual ) - assert_that( actual, equal_to( expected ) ) - - -@WithRetry -@IsolatedYcmd( { 'clangd_args': [ '-hidden-features' ] } ) -def Subcommands_FixIt_ClangdTweaks_test( app ): - selection = { - 'start': { 'line_num': 80, 'column_num': 19 }, - 'end': { 'line_num': 80, 'column_num': 4 } - } + @WithRetry() + @IsolatedYcmd( { 'clangd_args': [ '-hidden-features' ] } ) + def test_Subcommands_FixIt_ClangdTweaks( self, app ): + selection = { + 'start': { 'line_num': 80, 'column_num': 19 }, + 'end': { 'line_num': 80, 'column_num': 4 } + } - def NoFixitsProduced( results ): - assert_that( results, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': [], - 'location': LocationMatcher( - PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), 1, 1 ) - } ) ) - } ) ) - RunRangedFixItTest( app, selection, NoFixitsProduced, 2 ) - - -@SharedYcmd -def Subcommands_RefactorRename_test( app ): - basic_cpp = PathToTestFile( 'basic.cpp' ) - gettype_clang_text_cc = PathToTestFile( 'GetType_Clang_test.cc' ) - goto_clang_zerobasedline = PathToTestFile( - 'GoTo_Clang_ZeroBasedLineAndColumn_test.cc' ) - test = { - 'request': { - 'filetype': 'cpp', - 'completer_target': 'filetype_default', - 'contents': ReadFile( basic_cpp ), - 'filepath': basic_cpp, - 'command_arguments': [ 'RefactorRename', 'Bar' ], - 'line_num': 17, - 'column_num': 4, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { + def NoFixitsProduced( results ): + assert_that( results, has_entries( { 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( 'Bar', - LocationMatcher( gettype_clang_text_cc, 12, 8 ), - LocationMatcher( gettype_clang_text_cc, 12, 11 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( gettype_clang_text_cc, 24, 3 ), - LocationMatcher( gettype_clang_text_cc, 24, 6 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( gettype_clang_text_cc, 39, 3 ), - LocationMatcher( gettype_clang_text_cc, 39, 6 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( gettype_clang_text_cc, 40, 3 ), - LocationMatcher( gettype_clang_text_cc, 40, 6 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( gettype_clang_text_cc, 42, 9 ), - LocationMatcher( gettype_clang_text_cc, 42, 12 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( gettype_clang_text_cc, 43, 9 ), - LocationMatcher( gettype_clang_text_cc, 43, 12 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( goto_clang_zerobasedline, 2, 8 ), - LocationMatcher( goto_clang_zerobasedline, 2, 11 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( goto_clang_zerobasedline, 10, 3 ), - LocationMatcher( goto_clang_zerobasedline, 10, 6 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( basic_cpp, 1, 8 ), - LocationMatcher( basic_cpp, 1, 11 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( basic_cpp, 9, 3 ), - LocationMatcher( basic_cpp, 9, 6 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( basic_cpp, 15, 8 ), - LocationMatcher( basic_cpp, 15, 11 ) ), - ChunkMatcher( 'Bar', - LocationMatcher( basic_cpp, 17, 3 ), - LocationMatcher( basic_cpp, 17, 6 ) ), - ) + 'chunks': [], + 'location': LocationMatcher( + PathToTestFile( 'FixIt_Clang_cpp11.cpp' ), 1, 1 ) } ) ) - } ) - }, - 'route': '/run_completer_command' - } - RunAfterInitialized( app, test ) + } ) ) + RunRangedFixItTest( app, selection, NoFixitsProduced, 2 ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_Subcommands_RefactorRename( self, app ): + basic_cpp = PathToTestFile( 'basic.cpp' ) + gettype_clang_text_cc = PathToTestFile( 'GetType_Clang_test.cc' ) + goto_clang_zerobasedline = PathToTestFile( + 'GoTo_Clang_ZeroBasedLineAndColumn_test.cc' ) + test = { + 'request': { + 'filetype': 'cpp', + 'completer_target': 'filetype_default', + 'contents': ReadFile( basic_cpp ), + 'filepath': basic_cpp, + 'command_arguments': [ 'RefactorRename', 'Bar' ], + 'line_num': 17, + 'column_num': 4, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( 'Bar', + LocationMatcher( gettype_clang_text_cc, 12, 8 ), + LocationMatcher( gettype_clang_text_cc, 12, 11 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( gettype_clang_text_cc, 24, 3 ), + LocationMatcher( gettype_clang_text_cc, 24, 6 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( gettype_clang_text_cc, 39, 3 ), + LocationMatcher( gettype_clang_text_cc, 39, 6 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( gettype_clang_text_cc, 40, 3 ), + LocationMatcher( gettype_clang_text_cc, 40, 6 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( gettype_clang_text_cc, 42, 9 ), + LocationMatcher( gettype_clang_text_cc, 42, 12 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( gettype_clang_text_cc, 43, 9 ), + LocationMatcher( gettype_clang_text_cc, 43, 12 ) ), + ChunkMatcher( + 'Bar', + LocationMatcher( goto_clang_zerobasedline, 2, 8 ), + LocationMatcher( goto_clang_zerobasedline, 2, 11 ) ), + ChunkMatcher( + 'Bar', + LocationMatcher( goto_clang_zerobasedline, 10, 3 ), + LocationMatcher( goto_clang_zerobasedline, 10, 6 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( basic_cpp, 1, 8 ), + LocationMatcher( basic_cpp, 1, 11 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( basic_cpp, 9, 3 ), + LocationMatcher( basic_cpp, 9, 6 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( basic_cpp, 15, 8 ), + LocationMatcher( basic_cpp, 15, 11 ) ), + ChunkMatcher( 'Bar', + LocationMatcher( basic_cpp, 17, 3 ), + LocationMatcher( basic_cpp, 17, 6 ) ), + ) + } ) ) + } ) + }, + 'route': '/run_completer_command' + } + RunAfterInitialized( app, test ) diff --git a/ycmd/tests/clangd/utilities_test.py b/ycmd/tests/clangd/utilities_test.py index 2be8824d36..6ad3297d75 100644 --- a/ycmd/tests/clangd/utilities_test.py +++ b/ycmd/tests/clangd/utilities_test.py @@ -1,5 +1,4 @@ -# Copyright (C) 2011-2012 Google Inc. -# 2018 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -19,6 +18,7 @@ """This test is for utilities used in clangd.""" from unittest.mock import patch +from unittest import TestCase from hamcrest import assert_that, equal_to from ycmd import handlers from ycmd.completers.cpp import clangd_completer @@ -29,220 +29,222 @@ from ycmd.user_options_store import DefaultOptions -def ClangdCompleter_GetClangdCommand_NoCustomBinary_test(): - user_options = DefaultOptions() +class MockPopen: + stdin = None + stdout = None + pid = 0 - # Supported binary in third_party. - THIRD_PARTY = '/third_party/clangd' - clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - with patch( 'ycmd.completers.cpp.clangd_completer.GetThirdPartyClangd', - return_value = THIRD_PARTY ): - assert_that( clangd_completer.GetClangdCommand( user_options )[ 0 ], - equal_to( THIRD_PARTY ) ) - # With args - clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - CLANGD_ARGS = [ "1", "2", "3" ] - user_options[ 'clangd_args' ] = CLANGD_ARGS - assert_that( clangd_completer.GetClangdCommand( user_options )[ 1:4 ], - equal_to( CLANGD_ARGS ) ) - - # No supported binary in third_party. - clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - with patch( 'ycmd.completers.cpp.clangd_completer.GetThirdPartyClangd', - return_value = None ): - assert_that( clangd_completer.GetClangdCommand( user_options ), - equal_to( None ) ) + def communicate( self ): + return ( bytes(), None ) - clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED +class UtilitiesTest( TestCase ): + def test_ClangdCompleter_GetClangdCommand_NoCustomBinary( self ): + user_options = DefaultOptions() -@patch( 'ycmd.completers.cpp.clangd_completer.FindExecutable', lambda exe: exe ) -def ClangdCompleter_GetClangdCommand_CustomBinary_test(): - CLANGD_PATH = '/test/clangd' - user_options = DefaultOptions() - user_options[ 'clangd_binary_path' ] = CLANGD_PATH - # Supported version. - with patch( 'ycmd.completers.cpp.clangd_completer.CheckClangdVersion', - return_value = True ): + # Supported binary in third_party. + THIRD_PARTY = '/third_party/clangd' clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - assert_that( clangd_completer.GetClangdCommand( user_options )[ 0 ], - equal_to( CLANGD_PATH ) ) + with patch( 'ycmd.completers.cpp.clangd_completer.GetThirdPartyClangd', + return_value = THIRD_PARTY ): + assert_that( clangd_completer.GetClangdCommand( user_options )[ 0 ], + equal_to( THIRD_PARTY ) ) + # With args + clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED + CLANGD_ARGS = [ "1", "2", "3" ] + user_options[ 'clangd_args' ] = CLANGD_ARGS + assert_that( clangd_completer.GetClangdCommand( user_options )[ 1:4 ], + equal_to( CLANGD_ARGS ) ) - # No Clangd binary in the given path. - with patch( 'ycmd.completers.cpp.clangd_completer.FindExecutable', + # No supported binary in third_party. + clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED + with patch( 'ycmd.completers.cpp.clangd_completer.GetThirdPartyClangd', return_value = None ): - clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED assert_that( clangd_completer.GetClangdCommand( user_options ), equal_to( None ) ) - # Unsupported version. - with patch( 'ycmd.completers.cpp.clangd_completer.CheckClangdVersion', - return_value = False ): - # Never fall back to the third-party Clangd. clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - assert_that( clangd_completer.GetClangdCommand( user_options ), - equal_to( None ) ) - clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - - -@patch( 'ycmd.completers.cpp.clangd_completer.GetVersion', - side_effect = [ None, - ( 5, 0, 0 ), - clangd_completer.MIN_SUPPORTED_VERSION, - ( 12, 0, 0 ), - ( 12, 10, 10 ), - ( 100, 100, 100 ) ] ) -def ClangdCompleter_CheckClangdVersion_test( *args ): - assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), - equal_to( True ) ) - assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), - equal_to( False ) ) - assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), - equal_to( True ) ) - assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), - equal_to( True ) ) - assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), - equal_to( True ) ) - assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), - equal_to( True ) ) - - -def ClangdCompleter_ShouldEnableClangdCompleter_test(): - user_options = DefaultOptions() - - # Clangd not in third_party (or an old version). - with patch( 'ycmd.completers.cpp.clangd_completer.GetThirdPartyClangd', - return_value = None ): - # Default. - clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), - equal_to( False ) ) - # Found supported binary. - with patch( 'ycmd.completers.cpp.clangd_completer.GetClangdCommand', - return_value = [ 'clangd' ] ): - assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), - equal_to( True ) ) - # No supported binary found. - with patch( 'ycmd.completers.cpp.clangd_completer.GetClangdCommand', - return_value = None ): - assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), - equal_to( False ) ) + @patch( 'ycmd.completers.cpp.clangd_completer.FindExecutable', + lambda exe: exe ) + def test_ClangdCompleter_GetClangdCommand_CustomBinary( self ): + CLANGD_PATH = '/test/clangd' + user_options = DefaultOptions() + user_options[ 'clangd_binary_path' ] = CLANGD_PATH + # Supported version. + with patch( 'ycmd.completers.cpp.clangd_completer.CheckClangdVersion', + return_value = True ): + clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED + assert_that( clangd_completer.GetClangdCommand( user_options )[ 0 ], + equal_to( CLANGD_PATH ) ) - # Clangd is disabled. - user_options[ 'use_clangd' ] = 0 - assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), - equal_to( False ) ) + # No Clangd binary in the given path. + with patch( 'ycmd.completers.cpp.clangd_completer.FindExecutable', + return_value = None ): + clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED + assert_that( clangd_completer.GetClangdCommand( user_options ), + equal_to( None ) ) - user_options = DefaultOptions() + # Unsupported version. + with patch( 'ycmd.completers.cpp.clangd_completer.CheckClangdVersion', + return_value = False ): + # Never fall back to the third-party Clangd. + clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED + assert_that( clangd_completer.GetClangdCommand( user_options ), + equal_to( None ) ) - # Clangd in third_party with a supported version. - with patch( 'ycmd.completers.cpp.clangd_completer.GetThirdPartyClangd', - return_value = 'third_party_clangd' ): - # Default. clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED - assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), - equal_to( True ) ) - # Enabled. - user_options[ 'use_clangd' ] = 1 - # Found supported binary. - with patch( 'ycmd.completers.cpp.clangd_completer.GetClangdCommand', - return_value = [ 'clangd' ] ): - assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), - equal_to( True ) ) - # No supported binary found. - with patch( 'ycmd.completers.cpp.clangd_completer.GetClangdCommand', - return_value = None ): - assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), - equal_to( False ) ) - # Disabled. - user_options[ 'use_clangd' ] = 0 - assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), + @patch( 'ycmd.completers.cpp.clangd_completer.GetVersion', + side_effect = [ None, + ( 5, 0, 0 ), + clangd_completer.MIN_SUPPORTED_VERSION, + ( 12, 0, 0 ), + ( 12, 10, 10 ), + ( 100, 100, 100 ) ] ) + def test_ClangdCompleter_CheckClangdVersion( *args ): + assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), + equal_to( True ) ) + assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), equal_to( False ) ) + assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), + equal_to( True ) ) + assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), + equal_to( True ) ) + assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), + equal_to( True ) ) + assert_that( clangd_completer.CheckClangdVersion( 'clangd' ), + equal_to( True ) ) -class MockPopen: - stdin = None - stdout = None - pid = 0 + def test_ClangdCompleter_ShouldEnableClangdCompleter( self ): + user_options = DefaultOptions() - def communicate( self ): - return ( bytes(), None ) + # Clangd not in third_party (or an old version). + with patch( 'ycmd.completers.cpp.clangd_completer.GetThirdPartyClangd', + return_value = None ): + # Default. + clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED + assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), + equal_to( False ) ) + # Found supported binary. + with patch( 'ycmd.completers.cpp.clangd_completer.GetClangdCommand', + return_value = [ 'clangd' ] ): + assert_that( + clangd_completer.ShouldEnableClangdCompleter( user_options ), + equal_to( True ) ) + # No supported binary found. + with patch( 'ycmd.completers.cpp.clangd_completer.GetClangdCommand', + return_value = None ): + assert_that( + clangd_completer.ShouldEnableClangdCompleter( user_options ), + equal_to( False ) ) + + # Clangd is disabled. + user_options[ 'use_clangd' ] = 0 + assert_that( + clangd_completer.ShouldEnableClangdCompleter( user_options ), + equal_to( False ) ) + + user_options = DefaultOptions() + + # Clangd in third_party with a supported version. + with patch( 'ycmd.completers.cpp.clangd_completer.GetThirdPartyClangd', + return_value = 'third_party_clangd' ): + # Default. + clangd_completer.CLANGD_COMMAND = clangd_completer.NOT_CACHED + assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), + equal_to( True ) ) -@patch( 'subprocess.Popen', return_value = MockPopen() ) -def ClangdCompleter_GetVersion_test( mock_popen ): - assert_that( clangd_completer.GetVersion( '' ), - equal_to( None ) ) - mock_popen.assert_called() + # Enabled. + user_options[ 'use_clangd' ] = 1 + # Found supported binary. + with patch( 'ycmd.completers.cpp.clangd_completer.GetClangdCommand', + return_value = [ 'clangd' ] ): + assert_that( + clangd_completer.ShouldEnableClangdCompleter( user_options ), + equal_to( True ) ) + # No supported binary found. + with patch( 'ycmd.completers.cpp.clangd_completer.GetClangdCommand', + return_value = None ): + assert_that( + clangd_completer.ShouldEnableClangdCompleter( user_options ), + equal_to( False ) ) + + # Disabled. + user_options[ 'use_clangd' ] = 0 + assert_that( clangd_completer.ShouldEnableClangdCompleter( user_options ), + equal_to( False ) ) -def ClangdCompleter_ParseClangdVersion_test(): - cases = [ - ( 'clangd version 10.0.0 (https://github.com/llvm/llvm-project.git ' - '45be5e477e9216363191a8ac9123bea4585cf14f)', ( 10, 0, 0 ) ), - ( 'clangd version 8.0.0-3 (tags/RELEASE_800/final)', ( 8, 0, 0 ) ), - ( 'LLVM (http://llvm.org/):\nLLVM version 6.0.0\n' - 'Optimized build.\nDefault target: x86_64-unknown-linux-gnu\n' - 'Host CPU: haswell', ( 6, 0, 0 ) ), - ] + @patch( 'subprocess.Popen', return_value = MockPopen() ) + def test_ClangdCompleter_GetVersion( self, mock_popen ): + assert_that( clangd_completer.GetVersion( '' ), + equal_to( None ) ) + mock_popen.assert_called() - for version_str, expected in cases: - assert_that( clangd_completer.ParseClangdVersion( version_str ), - equal_to( expected ) ) + def test_ClangdCompleter_ParseClangdVersion( self ): + cases = [ + ( 'clangd version 10.0.0 (https://github.com/llvm/llvm-project.git ' + '45be5e477e9216363191a8ac9123bea4585cf14f)', ( 10, 0, 0 ) ), + ( 'clangd version 8.0.0-3 (tags/RELEASE_800/final)', ( 8, 0, 0 ) ), + ( 'LLVM (http://llvm.org/):\nLLVM version 6.0.0\n' + 'Optimized build.\nDefault target: x86_64-unknown-linux-gnu\n' + 'Host CPU: haswell', ( 6, 0, 0 ) ), + ] -@IsolatedYcmd() -def ClangdCompleter_ShutdownFail_test( app ): - completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) - with patch.object( completer, 'ShutdownServer', - side_effect = Exception ) as shutdown_server: - completer._server_handle = MockPopen() - with patch.object( completer, 'ServerIsHealthy', return_value = True ): - completer.Shutdown() - shutdown_server.assert_called() + for version_str, expected in cases: + assert_that( clangd_completer.ParseClangdVersion( version_str ), + equal_to( expected ) ) -def ClangdCompleter_GetThirdParty_test(): - with patch( 'ycmd.completers.cpp.clangd_completer.GetExecutable', - return_value = None ): - assert_that( clangd_completer.GetThirdPartyClangd(), - equal_to( None ) ) + @IsolatedYcmd() + def test_ClangdCompleter_ShutdownFail( self, app ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'cpp' ] ) + with patch.object( completer, 'ShutdownServer', + side_effect = Exception ) as shutdown_server: + completer._server_handle = MockPopen() + with patch.object( completer, 'ServerIsHealthy', return_value = True ): + completer.Shutdown() + shutdown_server.assert_called() - with patch( 'ycmd.completers.cpp.clangd_completer.GetExecutable', - return_value = '/third_party/clangd' ): - with patch( 'ycmd.completers.cpp.clangd_completer.CheckClangdVersion', - return_value = True ): - assert_that( clangd_completer.GetThirdPartyClangd(), - equal_to( '/third_party/clangd' ) ) - with patch( 'ycmd.completers.cpp.clangd_completer.CheckClangdVersion', - return_value = False ): + def test_ClangdCompleter_GetThirdParty( self ): + with patch( 'ycmd.completers.cpp.clangd_completer.GetExecutable', + return_value = None ): assert_that( clangd_completer.GetThirdPartyClangd(), equal_to( None ) ) - -@IsolatedYcmd() -def ClangdCompleter_StartServer_Fails_test( app ): - with patch( 'ycmd.completers.language_server.language_server_completer.' - 'LanguageServerConnection.AwaitServerConnection', - side_effect = LanguageServerConnectionTimeout ): - with patch( 'ycmd.completers.cpp.clangd_completer.ClangdCompleter.' - 'ShutdownServer' ) as shutdown: - resp = app.post_json( '/event_notification', - BuildRequest( - event_name = 'FileReadyToParse', - filetype = 'cpp', - filepath = PathToTestFile( 'foo.cc' ), - contents = "" - ) ) - assert_that( resp.status_code, equal_to( 200 ) ) - shutdown.assert_called() - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + with patch( 'ycmd.completers.cpp.clangd_completer.GetExecutable', + return_value = '/third_party/clangd' ): + with patch( 'ycmd.completers.cpp.clangd_completer.CheckClangdVersion', + return_value = True ): + assert_that( clangd_completer.GetThirdPartyClangd(), + equal_to( '/third_party/clangd' ) ) + + with patch( 'ycmd.completers.cpp.clangd_completer.CheckClangdVersion', + return_value = False ): + assert_that( clangd_completer.GetThirdPartyClangd(), + equal_to( None ) ) + + + @IsolatedYcmd() + def test_ClangdCompleter_StartServer_Fails( self, app ): + with patch( 'ycmd.completers.language_server.language_server_completer.' + 'LanguageServerConnection.AwaitServerConnection', + side_effect = LanguageServerConnectionTimeout ): + with patch( 'ycmd.completers.cpp.clangd_completer.ClangdCompleter.' + 'ShutdownServer' ) as shutdown: + resp = app.post_json( '/event_notification', + BuildRequest( + event_name = 'FileReadyToParse', + filetype = 'cpp', + filepath = PathToTestFile( 'foo.cc' ), + contents = "" + ) ) + assert_that( resp.status_code, equal_to( 200 ) ) + shutdown.assert_called() diff --git a/ycmd/tests/client_test.py b/ycmd/tests/client_test.py index 7e44920d48..567ec884ad 100644 --- a/ycmd/tests/client_test.py +++ b/ycmd/tests/client_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -19,6 +19,7 @@ from hamcrest import assert_that, empty, equal_to, is_in from hmac import compare_digest from tempfile import NamedTemporaryFile +from unittest import TestCase import functools import json import os @@ -45,8 +46,8 @@ LOGFILE_FORMAT = 'server_{port}_{std}_' -class Client_test: - def setup_method( self ): +class ClientTest( TestCase ): + def setUp( self ): self._location = None self._port = None self._servers = [] @@ -58,7 +59,7 @@ def setup_method( self ): b64encode( self._hmac_secret ) ) - def teardown_method( self ): + def tearDown( self ): for server in self._servers: if server.is_running(): server.terminate() @@ -253,8 +254,3 @@ def Wrapper( self, *args ): sys.stdout.write( '\n' ) return Wrapper - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True diff --git a/ycmd/tests/completer_test.py b/ycmd/tests/completer_test.py index add45506e9..c258a9bb6a 100644 --- a/ycmd/tests/completer_test.py +++ b/ycmd/tests/completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors. +# Copyright (C) 2016-2021 ycmd contributors. # # This file is part of ycmd. # @@ -17,6 +17,7 @@ from ycmd.tests.test_utils import DummyCompleter from ycmd.user_options_store import DefaultOptions +from unittest import TestCase from unittest.mock import patch from hamcrest import assert_that, contains_exactly, equal_to @@ -27,55 +28,51 @@ def _FilterAndSortCandidates_Match( candidates, query, expected_matches ): assert_that( expected_matches, equal_to( matches ) ) -def FilterAndSortCandidates_OmniCompleter_List_test(): - _FilterAndSortCandidates_Match( [ 'password' ], - 'p', - [ 'password' ] ) - _FilterAndSortCandidates_Match( [ 'words' ], - 'w', - [ 'words' ] ) +class CompleterTest( TestCase ): + def test_FilterAndSortCandidates_OmniCompleter_List( self ): + _FilterAndSortCandidates_Match( [ 'password' ], + 'p', + [ 'password' ] ) + _FilterAndSortCandidates_Match( [ 'words' ], + 'w', + [ 'words' ] ) -def FilterAndSortCandidates_OmniCompleter_Dictionary_test(): - _FilterAndSortCandidates_Match( { 'words': [ 'password' ] }, - 'p', - [ 'password' ] ) - _FilterAndSortCandidates_Match( { 'words': [ { 'word': 'password' } ] }, - 'p', - [ { 'word': 'password' } ] ) + def test_FilterAndSortCandidates_OmniCompleter_Dictionary( self ): + _FilterAndSortCandidates_Match( { 'words': [ 'password' ] }, + 'p', + [ 'password' ] ) + _FilterAndSortCandidates_Match( { 'words': [ { 'word': 'password' } ] }, + 'p', + [ { 'word': 'password' } ] ) -def FilterAndSortCandidates_ServerCompleter_test(): - _FilterAndSortCandidates_Match( [ { 'insertion_text': 'password' } ], - 'p', - [ { 'insertion_text': 'password' } ] ) + def test_FilterAndSortCandidates_ServerCompleter( self ): + _FilterAndSortCandidates_Match( [ { 'insertion_text': 'password' } ], + 'p', + [ { 'insertion_text': 'password' } ] ) -def FilterAndSortCandidates_SortOnEmptyQuery_test(): - _FilterAndSortCandidates_Match( [ 'foo', 'bar' ], - '', - [ 'bar', 'foo' ] ) + def test_FilterAndSortCandidates_SortOnEmptyQuery( self ): + _FilterAndSortCandidates_Match( [ 'foo', 'bar' ], + '', + [ 'bar', 'foo' ] ) -def FilterAndSortCandidates_IgnoreEmptyCandidate_test(): - _FilterAndSortCandidates_Match( [ '' ], - '', - [] ) + def test_FilterAndSortCandidates_IgnoreEmptyCandidate( self ): + _FilterAndSortCandidates_Match( [ '' ], + '', + [] ) -def FilterAndSortCandidates_Unicode_test(): - _FilterAndSortCandidates_Match( [ { 'insertion_text': 'ø' } ], - 'ø', - [ { 'insertion_text': 'ø' } ] ) + def test_FilterAndSortCandidates_Unicode( self ): + _FilterAndSortCandidates_Match( [ { 'insertion_text': 'ø' } ], + 'ø', + [ { 'insertion_text': 'ø' } ] ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.GetSubcommandsMap', - return_value = { 'Foo': '', 'StopServer': '' } ) -def DefinedSubcommands_RemoveStopServerSubcommand_test( subcommands_map ): - completer = DummyCompleter( DefaultOptions() ) - assert_that( completer.DefinedSubcommands(), contains_exactly( 'Foo' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @patch( 'ycmd.tests.test_utils.DummyCompleter.GetSubcommandsMap', + return_value = { 'Foo': '', 'StopServer': '' } ) + def test_DefinedSubcommands_RemoveStopServerSubcommand( self, *args ): + completer = DummyCompleter( DefaultOptions() ) + assert_that( completer.DefinedSubcommands(), contains_exactly( 'Foo' ) ) diff --git a/ycmd/tests/completer_utils_test.py b/ycmd/tests/completer_utils_test.py index 99079bbe27..0aea08d4ae 100644 --- a/ycmd/tests/completer_utils_test.py +++ b/ycmd/tests/completer_utils_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -17,6 +17,7 @@ from collections import defaultdict from hamcrest import assert_that, equal_to, none +from unittest import TestCase from ycmd.completers import completer_utils as cu from ycmd.utils import re @@ -32,176 +33,172 @@ def _ExtractPatternsFromFiletypeTriggerDict( triggerDict ): return copy -def FiletypeTriggerDictFromSpec_Works_test(): - assert_that( defaultdict( set, { - 'foo': { cu._PrepareTrigger( 'zoo' ).pattern, - cu._PrepareTrigger( 'bar' ).pattern }, - 'goo': { cu._PrepareTrigger( 'moo' ).pattern }, - 'moo': { cu._PrepareTrigger( 'moo' ).pattern }, - 'qux': { cu._PrepareTrigger( 'q' ).pattern } - } ), - equal_to( _ExtractPatternsFromFiletypeTriggerDict( - cu._FiletypeTriggerDictFromSpec( { - 'foo': [ 'zoo', 'bar' ], - 'goo,moo': [ 'moo' ], - 'qux': [ 'q' ] +class CompleterUtilsTest( TestCase ): + def test_FiletypeTriggerDictFromSpec_Works( self ): + assert_that( defaultdict( set, { + 'foo': { cu._PrepareTrigger( 'zoo' ).pattern, + cu._PrepareTrigger( 'bar' ).pattern }, + 'goo': { cu._PrepareTrigger( 'moo' ).pattern }, + 'moo': { cu._PrepareTrigger( 'moo' ).pattern }, + 'qux': { cu._PrepareTrigger( 'q' ).pattern } + } ), + equal_to( _ExtractPatternsFromFiletypeTriggerDict( + cu._FiletypeTriggerDictFromSpec( { + 'foo': [ 'zoo', 'bar' ], + 'goo,moo': [ 'moo' ], + 'qux': [ 'q' ] + } ) ) ) ) + + + def test_FiletypeDictUnion_Works( self ): + assert_that( defaultdict( set, { + 'foo': { 'zoo', 'bar', 'maa' }, + 'goo': { 'moo' }, + 'bla': { 'boo' }, + 'qux': { 'q' } + } ), + equal_to( cu._FiletypeDictUnion( defaultdict( set, { + 'foo': { 'zoo', 'bar' }, + 'goo': { 'moo' }, + 'qux': { 'q' } + } ), defaultdict( set, { + 'foo': { 'maa' }, + 'bla': { 'boo' }, + 'qux': { 'q' } } ) ) ) ) -def FiletypeDictUnion_Works_test(): - assert_that( defaultdict( set, { - 'foo': { 'zoo', 'bar', 'maa' }, - 'goo': { 'moo' }, - 'bla': { 'boo' }, - 'qux': { 'q' } - } ), - equal_to( cu._FiletypeDictUnion( defaultdict( set, { - 'foo': { 'zoo', 'bar' }, - 'goo': { 'moo' }, - 'qux': { 'q' } - } ), defaultdict( set, { - 'foo': { 'maa' }, - 'bla': { 'boo' }, - 'qux': { 'q' } - } ) ) ) ) - - -def PrepareTrigger_UnicodeTrigger_Test(): - regex = cu._PrepareTrigger( 'æ' ) - assert_that( regex.pattern, equal_to( re.escape( 'æ' ) ) ) - - -def MatchingSemanticTrigger_Basic_test(): - triggers = [ cu._PrepareTrigger( '.' ), cu._PrepareTrigger( ';' ), - cu._PrepareTrigger( '::' ) ] - - assert_that( cu._MatchingSemanticTrigger( 'foo->bar', 5, 9, triggers ), - none() ) - assert_that( cu._MatchingSemanticTrigger( 'foo::bar', - 5, - 9, - triggers ).pattern, - equal_to( re.escape( '::' ) ) ) - - -def MatchingSemanticTrigger_JustTrigger_test(): - triggers = [ cu._PrepareTrigger( '.' ) ] - - assert_that( cu._MatchingSemanticTrigger( '.', 2, 2, triggers ), none() ) - assert_that( cu._MatchingSemanticTrigger( '.', 1, 1, triggers ), - re.escape( '.' ) ) - assert_that( cu._MatchingSemanticTrigger( '.', 0, 0, triggers ), none() ) - - -def MatchingSemanticTrigger_TriggerBetweenWords_test(): - triggers = [ cu._PrepareTrigger( '.' ) ] - - assert_that( cu._MatchingSemanticTrigger( 'foo . bar', 6, 9, triggers ), - none() ) - assert_that( cu._MatchingSemanticTrigger( 'foo . bar', 5, 9, triggers ), - re.escape( '.' ) ) - assert_that( cu._MatchingSemanticTrigger( 'foo . bar', 4, 9, triggers ), - re.escape( '.' ) ) - - -def MatchingSemanticTrigger_BadInput_test(): - triggers = [ cu._PrepareTrigger( '.' ) ] - - assert_that( cu._MatchingSemanticTrigger( 'foo.bar', 10, 7, triggers ), - none() ) - assert_that( cu._MatchingSemanticTrigger( 'foo.bar', -1, 7, triggers ), - none() ) - assert_that( cu._MatchingSemanticTrigger( 'foo.bar', 4, -1, triggers ), - none() ) - assert_that( cu._MatchingSemanticTrigger( '', -1, 0, triggers ), none() ) - assert_that( cu._MatchingSemanticTrigger( '', 0, 0, triggers ), none() ) - assert_that( cu._MatchingSemanticTrigger( '', 1, 0, triggers ), none() ) - assert_that( cu._MatchingSemanticTrigger( 'foo.bar', 4, 7, [] ), none() ) - - -def MatchingSemanticTrigger_RegexTrigger_test(): - triggers = [ cu._PrepareTrigger( r're!\w+\.' ) ] - - assert_that( cu._MatchingSemanticTrigger( 'foo.bar', 4, 8, triggers ), - re.escape( r'\w+\.' ) ) - assert_that( cu._MatchingSemanticTrigger( 'foo . bar', 5, 8, triggers ), - none() ) - - -def PreparedTriggers_Basic_test(): - triggers = cu.PreparedTriggers() - - assert_that( triggers.MatchesForFiletype( 'foo.bar', 4, 8, 'c' ) ) - assert_that( triggers.MatchingTriggerForFiletype( 'foo.bar', - 4, - 8, - 'c' ).pattern, - equal_to( re.escape( '.' ) ) ) - assert_that( triggers.MatchesForFiletype( 'foo->bar', 5, 9, 'cpp' ) ) - assert_that( triggers.MatchingTriggerForFiletype( 'foo->bar', - 5, - 9, - 'cpp' ).pattern, - equal_to( re.escape( '->' ) ) ) - - -def PreparedTriggers_OnlySomeFiletypesSelected_test(): - triggers = cu.PreparedTriggers( filetype_set = set( 'c' ) ) - - assert_that( triggers.MatchesForFiletype( 'foo.bar', 4, 7, 'c' ) ) - assert_that( triggers.MatchingTriggerForFiletype( 'foo.bar', - 4, - 7, - 'c' ).pattern, - equal_to( re.escape( '.' ) ) ) - assert_that( not triggers.MatchesForFiletype( 'foo->bar', 5, 8, 'cpp' ) ) - assert_that( triggers.MatchingTriggerForFiletype( 'foo->bar', - 5, - 8, - 'cpp' ), - none() ) - - -def PreparedTriggers_UserTriggers_test(): - triggers = cu.PreparedTriggers( user_trigger_map = { 'c': [ '->' ] } ) - - assert_that( triggers.MatchesForFiletype( 'foo->bar', 5, 8, 'c' ) ) - assert_that( triggers.MatchingTriggerForFiletype( 'foo->bar', - 5, - 8, - 'c' ).pattern, - equal_to( re.escape( '->' ) ) ) - - -def PreparedTriggers_ObjectiveC_test(): - triggers = cu.PreparedTriggers() - - # Bracketed calls - assert_that( triggers.MatchesForFiletype( '[foo ', 5, 6, 'objc' ) ) - assert_that( not triggers.MatchesForFiletype( '[foo', 4, 5, 'objc' ) ) - assert_that( not triggers.MatchesForFiletype( '[3foo ', 6, 6, 'objc' ) ) - assert_that( triggers.MatchesForFiletype( '[f3oo ', 6, 6, 'objc' ) ) - assert_that( triggers.MatchesForFiletype( '[[foo ', 6, 6, 'objc' ) ) - - # Bracketless calls - assert_that( not triggers.MatchesForFiletype( '3foo ', 5, 5, 'objc' ) ) - assert_that( triggers.MatchesForFiletype( 'foo3 ', 5, 5, 'objc' ) ) - assert_that( triggers.MatchesForFiletype( 'foo ', 4, 4, 'objc' ) ) - - # Method composition - assert_that( triggers.MatchesForFiletype( - '[NSString stringWithFormat:@"Test %@", stuff] ', 46, 46, 'objc' ) ) - assert_that( triggers.MatchesForFiletype( - ' [NSString stringWithFormat:@"Test"] ', 39, 39, 'objc' ) ) - assert_that( triggers.MatchesForFiletype( - ' [[NSString stringWithFormat:@"Test"] stringByAppendingString:%@] ', - 68, - 68, - 'objc' ) ) - - assert_that( not triggers.MatchesForFiletype( '// foo ', 8, 8, 'objc' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + def test_PrepareTrigger_UnicodeTrigger( self ): + regex = cu._PrepareTrigger( 'æ' ) + assert_that( regex.pattern, equal_to( re.escape( 'æ' ) ) ) + + + def test_MatchingSemanticTrigger_Basic( self ): + triggers = [ cu._PrepareTrigger( '.' ), cu._PrepareTrigger( ';' ), + cu._PrepareTrigger( '::' ) ] + + assert_that( cu._MatchingSemanticTrigger( 'foo->bar', 5, 9, triggers ), + none() ) + assert_that( cu._MatchingSemanticTrigger( 'foo::bar', + 5, + 9, + triggers ).pattern, + equal_to( re.escape( '::' ) ) ) + + + def test_MatchingSemanticTrigger_JustTrigger( self ): + triggers = [ cu._PrepareTrigger( '.' ) ] + + assert_that( cu._MatchingSemanticTrigger( '.', 2, 2, triggers ), none() ) + assert_that( cu._MatchingSemanticTrigger( '.', 1, 1, triggers ), + re.escape( '.' ) ) + assert_that( cu._MatchingSemanticTrigger( '.', 0, 0, triggers ), none() ) + + + def test_MatchingSemanticTrigger_TriggerBetweenWords( self ): + triggers = [ cu._PrepareTrigger( '.' ) ] + + assert_that( cu._MatchingSemanticTrigger( 'foo . bar', 6, 9, triggers ), + none() ) + assert_that( cu._MatchingSemanticTrigger( 'foo . bar', 5, 9, triggers ), + re.escape( '.' ) ) + assert_that( cu._MatchingSemanticTrigger( 'foo . bar', 4, 9, triggers ), + re.escape( '.' ) ) + + + def test_MatchingSemanticTrigger_BadInput( self ): + triggers = [ cu._PrepareTrigger( '.' ) ] + + assert_that( cu._MatchingSemanticTrigger( 'foo.bar', 10, 7, triggers ), + none() ) + assert_that( cu._MatchingSemanticTrigger( 'foo.bar', -1, 7, triggers ), + none() ) + assert_that( cu._MatchingSemanticTrigger( 'foo.bar', 4, -1, triggers ), + none() ) + assert_that( cu._MatchingSemanticTrigger( '', -1, 0, triggers ), none() ) + assert_that( cu._MatchingSemanticTrigger( '', 0, 0, triggers ), none() ) + assert_that( cu._MatchingSemanticTrigger( '', 1, 0, triggers ), none() ) + assert_that( cu._MatchingSemanticTrigger( 'foo.bar', 4, 7, [] ), none() ) + + + def test_MatchingSemanticTrigger_RegexTrigger( self ): + triggers = [ cu._PrepareTrigger( r're!\w+\.' ) ] + + assert_that( cu._MatchingSemanticTrigger( 'foo.bar', 4, 8, triggers ), + re.escape( r'\w+\.' ) ) + assert_that( cu._MatchingSemanticTrigger( 'foo . bar', 5, 8, triggers ), + none() ) + + + def test_PreparedTriggers_Basic( self ): + triggers = cu.PreparedTriggers() + + assert_that( triggers.MatchesForFiletype( 'foo.bar', 4, 8, 'c' ) ) + assert_that( triggers.MatchingTriggerForFiletype( 'foo.bar', + 4, + 8, + 'c' ).pattern, + equal_to( re.escape( '.' ) ) ) + assert_that( triggers.MatchesForFiletype( 'foo->bar', 5, 9, 'cpp' ) ) + assert_that( triggers.MatchingTriggerForFiletype( 'foo->bar', + 5, + 9, + 'cpp' ).pattern, + equal_to( re.escape( '->' ) ) ) + + + def test_PreparedTriggers_OnlySomeFiletypesSelected( self ): + triggers = cu.PreparedTriggers( filetype_set = set( 'c' ) ) + + assert_that( triggers.MatchesForFiletype( 'foo.bar', 4, 7, 'c' ) ) + assert_that( triggers.MatchingTriggerForFiletype( 'foo.bar', + 4, + 7, + 'c' ).pattern, + equal_to( re.escape( '.' ) ) ) + assert_that( not triggers.MatchesForFiletype( 'foo->bar', 5, 8, 'cpp' ) ) + assert_that( triggers.MatchingTriggerForFiletype( 'foo->bar', + 5, + 8, + 'cpp' ), + none() ) + + + def test_PreparedTriggers_UserTriggers( self ): + triggers = cu.PreparedTriggers( user_trigger_map = { 'c': [ '->' ] } ) + + assert_that( triggers.MatchesForFiletype( 'foo->bar', 5, 8, 'c' ) ) + assert_that( triggers.MatchingTriggerForFiletype( 'foo->bar', + 5, + 8, + 'c' ).pattern, + equal_to( re.escape( '->' ) ) ) + + + def test_PreparedTriggers_ObjectiveC( self ): + triggers = cu.PreparedTriggers() + + # Bracketed calls + assert_that( triggers.MatchesForFiletype( '[foo ', 5, 6, 'objc' ) ) + assert_that( not triggers.MatchesForFiletype( '[foo', 4, 5, 'objc' ) ) + assert_that( not triggers.MatchesForFiletype( '[3foo ', 6, 6, 'objc' ) ) + assert_that( triggers.MatchesForFiletype( '[f3oo ', 6, 6, 'objc' ) ) + assert_that( triggers.MatchesForFiletype( '[[foo ', 6, 6, 'objc' ) ) + + # Bracketless calls + assert_that( not triggers.MatchesForFiletype( '3foo ', 5, 5, 'objc' ) ) + assert_that( triggers.MatchesForFiletype( 'foo3 ', 5, 5, 'objc' ) ) + assert_that( triggers.MatchesForFiletype( 'foo ', 4, 4, 'objc' ) ) + + # Method composition + assert_that( triggers.MatchesForFiletype( + '[NSString stringWithFormat:@"Test %@", stuff] ', 46, 46, 'objc' ) ) + assert_that( triggers.MatchesForFiletype( + ' [NSString stringWithFormat:@"Test"] ', 39, 39, 'objc' ) ) + assert_that( triggers.MatchesForFiletype( + ' [[NSString stringWithFormat:@"Test"] stringByAppendingString:%@] ', + 68, + 68, + 'objc' ) ) + + assert_that( not triggers.MatchesForFiletype( '// foo ', 8, 8, 'objc' ) ) diff --git a/ycmd/tests/conftest.py b/ycmd/tests/conftest.py deleted file mode 100644 index e96529bec0..0000000000 --- a/ycmd/tests/conftest.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (C) 2016-2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import pytest -from ycmd.tests.test_utils import ( ClearCompletionsCache, - IsolatedApp, - SetUpApp ) - -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - shared_app = SetUpApp() - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with IsolatedApp( request.param[ 1 ] ) as app: - yield app - else: - global shared_app - ClearCompletionsCache() - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) diff --git a/ycmd/tests/cs/__init__.py b/ycmd/tests/cs/__init__.py index 84a22ff198..b03f5d0c17 100644 --- a/ycmd/tests/cs/__init__.py +++ b/ycmd/tests/cs/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,10 +15,136 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . +import functools import os -from ycmd.tests.cs.conftest import * # noqa +import sys +import time +from contextlib import contextmanager +from ycmd.tests.test_utils import ( BuildRequest, + ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + WaitUntilCompleterServerReady, + StopCompleterServer, + SetUpApp ) + +shared_app = None +# map of 'app' to filepaths +shared_filepaths = {} +shared_log_indexes = {} def PathToTestFile( *args ): dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) return os.path.join( dir_of_current_script, 'testdata', *args ) + + +def setUpModule(): + global shared_app + shared_app = SetUpApp() + + +def tearDownModule(): + global shared_app, shared_filepaths + for filepath in shared_filepaths.get( shared_app, [] ): + StopCompleterServer( shared_app, 'cs', filepath ) + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + global shared_filepaths + for filepath in shared_filepaths.get( app, [] ): + StopCompleterServer( app, 'cs', filepath ) + return Wrapper + return Decorator + + +def GetDebugInfo( app, filepath ): + request_data = BuildRequest( filetype = 'cs', filepath = filepath ) + return app.post_json( '/debug_info', request_data ).json + + +def ReadFile( filepath, fileposition ): + with open( filepath, encoding = 'utf8' ) as f: + if fileposition: + f.seek( fileposition ) + return f.read(), f.tell() + + +def GetDiagnostics( app, filepath ): + contents, _ = ReadFile( filepath, 0 ) + + event_data = BuildRequest( filepath = filepath, + event_name = 'FileReadyToParse', + filetype = 'cs', + contents = contents ) + + return app.post_json( '/event_notification', event_data ).json + + +@contextmanager +def WrapOmniSharpServer( app, filepath ): + global shared_filepaths + global shared_log_indexes + + if filepath not in shared_filepaths.setdefault( app, [] ): + # StartCompleterServer( app, 'cs', filepath ) + GetDiagnostics( app, filepath ) + shared_filepaths[ app ].append( filepath ) + WaitUntilCsCompleterIsReady( app, filepath ) + + logfiles = [] + response = GetDebugInfo( app, filepath ) + for server in response[ 'completer' ][ 'servers' ]: + logfiles.extend( server[ 'logfiles' ] ) + + try: + yield + finally: + for logfile in logfiles: + if os.path.isfile( logfile ): + log_content, log_end_position = ReadFile( + logfile, shared_log_indexes.get( logfile, 0 ) ) + shared_log_indexes[ logfile ] = log_end_position + sys.stdout.write( f'Logfile { logfile }:\n\n' ) + sys.stdout.write( log_content ) + sys.stdout.write( '\n' ) + + +def WaitUntilCsCompleterIsReady( app, filepath ): + WaitUntilCompleterServerReady( app, 'cs' ) + # Omnisharp isn't ready when it says it is, so wait until Omnisharp returns + # at least one diagnostic multiple times. + success_count = 0 + for reraise_error in [ False ] * 39 + [ True ]: + try: + if len( GetDiagnostics( app, filepath ) ) == 0: + raise RuntimeError( "No diagnostic" ) + success_count += 1 + if success_count > 2: + break + except Exception: + success_count = 0 + if reraise_error: + raise + + time.sleep( .5 ) + else: + raise RuntimeError( "Never was ready" ) diff --git a/ycmd/tests/cs/conftest.py b/ycmd/tests/cs/conftest.py deleted file mode 100644 index dcd5f4b9ab..0000000000 --- a/ycmd/tests/cs/conftest.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import os -import pytest -import sys -import time - -from contextlib import contextmanager -from ycmd.tests.test_utils import ( BuildRequest, - ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - WaitUntilCompleterServerReady, - StopCompleterServer, - SetUpApp ) - -shared_app = None -# map of 'app' to filepaths -shared_filepaths = {} -shared_log_indexes = {} - - -@pytest.fixture( scope='package', autouse=True ) -def set_up_shared_app(): - global shared_app, shared_filepaths - shared_app = SetUpApp() - yield - for filepath in shared_filepaths.get( shared_app, [] ): - StopCompleterServer( shared_app, 'cs', filepath ) - - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - custom_options = request.param[ 1 ] - with IsolatedApp( custom_options ) as app: - yield app - # Shutdown the isolated app - for filepath in shared_filepaths.get( app, [] ): - StopCompleterServer( app, 'cs', filepath ) - - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def GetDebugInfo( app, filepath ): - """ TODO: refactor here and in clangd test to common util """ - request_data = BuildRequest( filetype = 'cs', filepath = filepath ) - return app.post_json( '/debug_info', request_data ).json - - -def ReadFile( filepath, fileposition ): - with open( filepath, encoding = 'utf8' ) as f: - if fileposition: - f.seek( fileposition ) - return f.read(), f.tell() - - -def GetDiagnostics( app, filepath ): - contents, _ = ReadFile( filepath, 0 ) - - event_data = BuildRequest( filepath = filepath, - event_name = 'FileReadyToParse', - filetype = 'cs', - contents = contents ) - - return app.post_json( '/event_notification', event_data ).json - - -@contextmanager -def WrapOmniSharpServer( app, filepath ): - global shared_filepaths - global shared_log_indexes - - if filepath not in shared_filepaths.setdefault( app, [] ): - # StartCompleterServer( app, 'cs', filepath ) - GetDiagnostics( app, filepath ) - shared_filepaths[ app ].append( filepath ) - WaitUntilCsCompleterIsReady( app, filepath ) - - logfiles = [] - response = GetDebugInfo( app, filepath ) - for server in response[ 'completer' ][ 'servers' ]: - logfiles.extend( server[ 'logfiles' ] ) - - try: - yield - finally: - for logfile in logfiles: - if os.path.isfile( logfile ): - log_content, log_end_position = ReadFile( - logfile, shared_log_indexes.get( logfile, 0 ) ) - shared_log_indexes[ logfile ] = log_end_position - sys.stdout.write( f'Logfile { logfile }:\n\n' ) - sys.stdout.write( log_content ) - sys.stdout.write( '\n' ) - - -def WaitUntilCsCompleterIsReady( app, filepath ): - WaitUntilCompleterServerReady( app, 'cs' ) - # Omnisharp isn't ready when it says it is, so wait until Omnisharp returns - # at least one diagnostic multiple times. - success_count = 0 - for reraise_error in [ False ] * 39 + [ True ]: - try: - if len( GetDiagnostics( app, filepath ) ) == 0: - raise RuntimeError( "No diagnostic" ) - success_count += 1 - if success_count > 2: - break - except Exception: - success_count = 0 - if reraise_error: - raise - - time.sleep( .5 ) - else: - raise RuntimeError( "Never was ready" ) diff --git a/ycmd/tests/cs/debug_info_test.py b/ycmd/tests/cs/debug_info_test.py index 14f9137666..e84973bff4 100644 --- a/ycmd/tests/cs/debug_info_test.py +++ b/ycmd/tests/cs/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -18,73 +18,16 @@ from hamcrest import ( assert_that, contains_exactly, empty, equal_to, has_entries, has_entry, instance_of ) from unittest.mock import patch +from unittest import TestCase from ycmd.completers.cs.hook import GetCompleter -from ycmd.tests.cs import PathToTestFile, SharedYcmd +from ycmd.tests.cs import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, WaitUntilCompleterServerReady ) from ycmd import user_options_store from ycmd.utils import ReadFile -@SharedYcmd -def DebugInfo_ServerIsRunning_test( app ): - filepath = PathToTestFile( 'testy', 'Program.cs' ) - contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - WaitUntilCompleterServerReady( app, 'cs' ) - - request_data = BuildRequest( filepath = filepath, - filetype = 'cs' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C#', - 'servers': contains_exactly( has_entries( { - 'name': 'OmniSharp', - 'is_running': True, - 'executable': instance_of( str ), - 'pid': instance_of( int ), - 'address': instance_of( str ), - 'port': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ), - instance_of( str ) ), - 'extras': contains_exactly( has_entries( { - 'key': 'solution', - 'value': instance_of( str ) - } ) ) - } ) ), - 'items': empty() - } ) ) - ) - - -@SharedYcmd -def DebugInfo_ServerIsNotRunning_NoSolution_test( app ): - request_data = BuildRequest( filetype = 'cs' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'C#', - 'servers': contains_exactly( has_entries( { - 'name': 'OmniSharp', - 'is_running': False, - 'executable': instance_of( str ), - 'pid': None, - 'address': None, - 'port': None, - 'logfiles': empty() - } ) ), - 'items': empty() - } ) ) - ) - - def SolutionSelectCheck( app, sourcefile, reference_solution, extra_conf_store = None ): # reusable test: verify that the correct solution (reference_solution) is @@ -112,104 +55,159 @@ def SolutionSelectCheck( app, sourcefile, reference_solution, ) -@SharedYcmd -def DebugInfo_UsesSubfolderHint_test( app ): - SolutionSelectCheck( app, - PathToTestFile( 'testy-multiple-solutions', - 'solution-named-like-folder', - 'testy', 'Program.cs' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-named-like-folder', - 'testy.sln' ) ) - - -@SharedYcmd -def DebugInfo_UsesSuperfolderHint_test( app ): - SolutionSelectCheck( app, - PathToTestFile( 'testy-multiple-solutions', - 'solution-named-like-folder', - 'not-testy', 'Program.cs' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-named-like-folder', - 'solution-named-like-folder.sln' ) ) - - -@SharedYcmd -def DebugInfo_ExtraConfStoreAbsolute_test( app ): - SolutionSelectCheck( app, - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'extra-conf-abs', - 'testy', 'Program.cs' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'testy2.sln' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'extra-conf-abs', - '.ycm_extra_conf.py' ) ) - - -@SharedYcmd -def DebugInfo_ExtraConfStoreRelative_test( app ): - SolutionSelectCheck( app, - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'extra-conf-rel', - 'testy', 'Program.cs' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'extra-conf-rel', - 'testy2.sln' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'extra-conf-rel', - '.ycm_extra_conf.py' ) ) - - -@SharedYcmd -def DebugInfo_ExtraConfStoreNonexisting_test( app ): - SolutionSelectCheck( app, - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'extra-conf-bad', - 'testy', 'Program.cs' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'extra-conf-bad', - 'testy2.sln' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'extra-conf-bad', - 'testy', '.ycm_extra_conf.py' ) ) - - -def GetCompleter_RoslynFound_test(): - assert_that( GetCompleter( user_options_store.GetAll() ) ) - - -@patch( 'ycmd.completers.cs.cs_completer.PATH_TO_OMNISHARP_ROSLYN_BINARY', - None ) -def GetCompleter_RoslynNotFound_test( *args ): - assert_that( not GetCompleter( user_options_store.GetAll() ) ) - - -@patch( 'ycmd.completers.cs.cs_completer.FindExecutableWithFallback', - wraps = lambda x, fb: x if x == 'roslyn' else fb ) -@patch( 'os.path.isfile', return_value = True ) -def GetCompleter_RoslynFromUserOption_test( *args ): - user_options = user_options_store.GetAll().copy( - roslyn_binary_path = 'roslyn' ) - assert_that( GetCompleter( user_options )._roslyn_path, equal_to( 'roslyn' ) ) - - -@patch( 'os.path.isfile', return_value = False ) -def GetCompleter_CustomPathToServer_NotAFile_test( *args ): - user_options = user_options_store.GetAll().copy( - roslyn_binary_path = 'does-not-exist' ) - assert_that( not GetCompleter( user_options ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class DebugInfoTest( TestCase ): + @SharedYcmd + def test_DebugInfo_ServerIsRunning( self, app ): + filepath = PathToTestFile( 'testy', 'Program.cs' ) + contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', event_data ) + WaitUntilCompleterServerReady( app, 'cs' ) + + request_data = BuildRequest( filepath = filepath, + filetype = 'cs' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C#', + 'servers': contains_exactly( has_entries( { + 'name': 'OmniSharp', + 'is_running': True, + 'executable': instance_of( str ), + 'pid': instance_of( int ), + 'address': instance_of( str ), + 'port': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ), + instance_of( str ) ), + 'extras': contains_exactly( has_entries( { + 'key': 'solution', + 'value': instance_of( str ) + } ) ) + } ) ), + 'items': empty() + } ) ) + ) + + + @SharedYcmd + def test_DebugInfo_ServerIsNotRunning_NoSolution( self, app ): + request_data = BuildRequest( filetype = 'cs' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'C#', + 'servers': contains_exactly( has_entries( { + 'name': 'OmniSharp', + 'is_running': False, + 'executable': instance_of( str ), + 'pid': None, + 'address': None, + 'port': None, + 'logfiles': empty() + } ) ), + 'items': empty() + } ) ) + ) + + + @SharedYcmd + def test_DebugInfo_UsesSubfolderHint( self, app ): + SolutionSelectCheck( app, + PathToTestFile( 'testy-multiple-solutions', + 'solution-named-like-folder', + 'testy', 'Program.cs' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-named-like-folder', + 'testy.sln' ) ) + + + @SharedYcmd + def test_DebugInfo_UsesSuperfolderHint( self, app ): + SolutionSelectCheck( app, + PathToTestFile( 'testy-multiple-solutions', + 'solution-named-like-folder', + 'not-testy', 'Program.cs' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-named-like-folder', + 'solution-named-like-folder.sln' ) ) + + + @SharedYcmd + def test_DebugInfo_ExtraConfStoreAbsolute( self, app ): + SolutionSelectCheck( app, + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'extra-conf-abs', + 'testy', 'Program.cs' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'testy2.sln' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'extra-conf-abs', + '.ycm_extra_conf.py' ) ) + + + @SharedYcmd + def test_DebugInfo_ExtraConfStoreRelative( self, app ): + SolutionSelectCheck( app, + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'extra-conf-rel', + 'testy', 'Program.cs' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'extra-conf-rel', + 'testy2.sln' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'extra-conf-rel', + '.ycm_extra_conf.py' ) ) + + + @SharedYcmd + def test_DebugInfo_ExtraConfStoreNonexisting( self, app ): + SolutionSelectCheck( app, + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'extra-conf-bad', + 'testy', 'Program.cs' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'extra-conf-bad', + 'testy2.sln' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'extra-conf-bad', + 'testy', '.ycm_extra_conf.py' ) ) + + + def test_GetCompleter_RoslynFound( self ): + assert_that( GetCompleter( user_options_store.GetAll() ) ) + + + @patch( 'ycmd.completers.cs.cs_completer.PATH_TO_OMNISHARP_ROSLYN_BINARY', + None ) + def test_GetCompleter_RoslynNotFound( *args ): + assert_that( not GetCompleter( user_options_store.GetAll() ) ) + + + @patch( 'ycmd.completers.cs.cs_completer.FindExecutableWithFallback', + wraps = lambda x, fb: x if x == 'roslyn' else fb ) + @patch( 'os.path.isfile', return_value = True ) + def test_GetCompleter_RoslynFromUserOption( *args ): + user_options = user_options_store.GetAll().copy( + roslyn_binary_path = 'roslyn' ) + assert_that( GetCompleter( user_options )._roslyn_path, + equal_to( 'roslyn' ) ) + + + @patch( 'os.path.isfile', return_value = False ) + def test_GetCompleter_CustomPathToServer_NotAFile( self, *args ): + user_options = user_options_store.GetAll().copy( + roslyn_binary_path = 'does-not-exist' ) + assert_that( not GetCompleter( user_options ) ) diff --git a/ycmd/tests/cs/diagnostics_test.py b/ycmd/tests/cs/diagnostics_test.py index f8f4857224..6a0e334c49 100644 --- a/ycmd/tests/cs/diagnostics_test.py +++ b/ycmd/tests/cs/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -17,9 +17,10 @@ from hamcrest import ( assert_that, contains_exactly, contains_string, equal_to, has_entries, has_entry, has_items ) +from unittest import TestCase -from ycmd.tests.cs import ( IsolatedYcmd, PathToTestFile, SharedYcmd, - WrapOmniSharpServer ) +from ycmd.tests.cs import ( IsolatedYcmd, PathToTestFile, SharedYcmd, # noqa + WrapOmniSharpServer, setUpModule, tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, LocationMatcher, RangeMatcher, @@ -27,140 +28,136 @@ from ycmd.utils import ReadFile -@WithRetry -@SharedYcmd -def Diagnostics_Basic_test( app ): - filepath = PathToTestFile( 'testy', 'Program.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - event_name = 'FileReadyToParse', - filetype = 'cs', - contents = contents ) - app.post_json( '/event_notification', event_data ) - - diag_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - line_num = 10, - column_num = 2 ) - - results = app.post_json( '/detailed_diagnostic', diag_data ).json - assert_that( results, - has_entry( - 'message', - contains_string( - "Identifier expected" ) ) ) - - -@SharedYcmd -def Diagnostics_ZeroBasedLineAndColumn_test( app ): - filepath = PathToTestFile( 'testy', 'Program.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - event_name = 'FileReadyToParse', - filetype = 'cs', - contents = contents ) - - results = app.post_json( '/event_notification', event_data ).json - - assert_that( results, has_items( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'text': contains_string( "Identifier expected" ), - 'location': LocationMatcher( filepath, 10, 12 ), - 'location_extent': RangeMatcher( filepath, ( 10, 12 ), ( 10, 12 ) ), - } ) - ) ) - - -@WithRetry -@SharedYcmd -def Diagnostics_WithRange_test( app ): - filepath = PathToTestFile( 'testy', 'DiagnosticRange.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - event_name = 'FileReadyToParse', - filetype = 'cs', - contents = contents ) - - results = app.post_json( '/event_notification', event_data ).json - - assert_that( results, contains_exactly( - has_entries( { - 'kind': equal_to( 'WARNING' ), - 'text': contains_string( - "The variable '\u4e5d' is assigned but its value is never used" ), - 'location': LocationMatcher( filepath, 6, 13 ), - 'location_extent': RangeMatcher( filepath, ( 6, 13 ), ( 6, 16 ) ) - } ) - ) ) - - -@IsolatedYcmd() -def Diagnostics_MultipleSolution_test( app ): - filepaths = [ PathToTestFile( 'testy', 'Program.cs' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-named-like-folder', - 'testy', 'Program.cs' ) ] - for filepath in filepaths: +class DiagnosticsTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_Diagnostics_Basic( self, app ): + filepath = PathToTestFile( 'testy', 'Program.cs' ) with WrapOmniSharpServer( app, filepath ): contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + event_name = 'FileReadyToParse', + filetype = 'cs', + contents = contents ) + app.post_json( '/event_notification', event_data ) + + diag_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + line_num = 10, + column_num = 2 ) + + results = app.post_json( '/detailed_diagnostic', diag_data ).json + assert_that( results, + has_entry( + 'message', + contains_string( + "Identifier expected" ) ) ) + + + @SharedYcmd + def test_Diagnostics_ZeroBasedLineAndColumn( self, app ): + filepath = PathToTestFile( 'testy', 'Program.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, event_name = 'FileReadyToParse', filetype = 'cs', contents = contents ) results = app.post_json( '/event_notification', event_data ).json + assert_that( results, has_items( has_entries( { 'kind': equal_to( 'ERROR' ), 'text': contains_string( "Identifier expected" ), 'location': LocationMatcher( filepath, 10, 12 ), - 'location_extent': RangeMatcher( - filepath, ( 10, 12 ), ( 10, 12 ) ) + 'location_extent': RangeMatcher( filepath, ( 10, 12 ), ( 10, 12 ) ), + } ) + ) ) + + + @WithRetry() + @SharedYcmd + def test_Diagnostics_WithRange( self, app ): + filepath = PathToTestFile( 'testy', 'DiagnosticRange.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + event_name = 'FileReadyToParse', + filetype = 'cs', + contents = contents ) + + results = app.post_json( '/event_notification', event_data ).json + + assert_that( results, contains_exactly( + has_entries( { + 'kind': equal_to( 'WARNING' ), + 'text': contains_string( + "The variable '\u4e5d' is assigned but its value is never used" ), + 'location': LocationMatcher( filepath, 6, 13 ), + 'location_extent': RangeMatcher( filepath, ( 6, 13 ), ( 6, 16 ) ) } ) ) ) -@IsolatedYcmd( { 'max_diagnostics_to_display': 1 } ) -def Diagnostics_MaximumDiagnosticsNumberExceeded_test( app ): - filepath = PathToTestFile( 'testy', 'MaxDiagnostics.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - event_name = 'FileReadyToParse', - filetype = 'cs', - contents = contents ) - - results = app.post_json( '/event_notification', event_data ).json - - assert_that( results, contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'text': contains_string( "The type 'MaxDiagnostics' already contains " - "a definition for 'test'" ), - 'location': LocationMatcher( filepath, 4, 16 ), - 'location_extent': RangeMatcher( filepath, ( 4, 16 ), ( 4, 20 ) ) - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'text': contains_string( 'Maximum number of diagnostics exceeded.' ), - 'location': LocationMatcher( filepath, 1, 1 ), - 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) - ) - } ) - ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd() + def test_Diagnostics_MultipleSolution( self, app ): + filepaths = [ PathToTestFile( 'testy', 'Program.cs' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-named-like-folder', + 'testy', 'Program.cs' ) ] + for filepath in filepaths: + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, + event_name = 'FileReadyToParse', + filetype = 'cs', + contents = contents ) + + results = app.post_json( '/event_notification', event_data ).json + assert_that( results, has_items( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'text': contains_string( "Identifier expected" ), + 'location': LocationMatcher( filepath, 10, 12 ), + 'location_extent': RangeMatcher( + filepath, ( 10, 12 ), ( 10, 12 ) ) + } ) + ) ) + + + @IsolatedYcmd( { 'max_diagnostics_to_display': 1 } ) + def test_Diagnostics_MaximumDiagnosticsNumberExceeded( self, app ): + filepath = PathToTestFile( 'testy', 'MaxDiagnostics.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + event_name = 'FileReadyToParse', + filetype = 'cs', + contents = contents ) + + results = app.post_json( '/event_notification', event_data ).json + + assert_that( results, contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'text': contains_string( "The type 'MaxDiagnostics' already contains " + "a definition for 'test'" ), + 'location': LocationMatcher( filepath, 4, 16 ), + 'location_extent': RangeMatcher( filepath, ( 4, 16 ), ( 4, 20 ) ) + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'text': contains_string( 'Maximum number of diagnostics exceeded.' ), + 'location': LocationMatcher( filepath, 1, 1 ), + 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) + ) + } ) + ) ) diff --git a/ycmd/tests/cs/get_completions_test.py b/ycmd/tests/cs/get_completions_test.py index faea241116..7ec411e99c 100644 --- a/ycmd/tests/cs/get_completions_test.py +++ b/ycmd/tests/cs/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -21,99 +21,42 @@ has_entries, has_items, raises ) +from unittest import TestCase from webtest import AppError -from ycmd.tests.cs import PathToTestFile, SharedYcmd, WrapOmniSharpServer +from ycmd.tests.cs import PathToTestFile, SharedYcmd, WrapOmniSharpServer, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import BuildRequest, CompletionEntryMatcher from ycmd.utils import ReadFile -@SharedYcmd -def GetCompletions_DefaultToIdentifier_test( app ): - filepath = PathToTestFile( 'testy', 'Program.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) +class GetCompletionsTest( TestCase ): + @SharedYcmd + def test_GetCompletions_DefaultToIdentifier( self, app ): + filepath = PathToTestFile( 'testy', 'Program.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - line_num = 10, - column_num = 7 ) - response_data = app.post_json( '/completions', completion_data ).json - print( 'Response: ', response_data ) - assert_that( - response_data, - has_entries( { - 'completion_start_column': 4, - 'completions': has_items( - CompletionEntryMatcher( 'Console', '[ID]' ), - ), - 'errors': empty(), - } ) ) - - -@SharedYcmd -def GetCompletions_Basic_test( app ): - filepath = PathToTestFile( 'testy', 'Program.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + line_num = 10, + column_num = 7 ) + response_data = app.post_json( '/completions', completion_data ).json + print( 'Response: ', response_data ) + assert_that( + response_data, + has_entries( { + 'completion_start_column': 4, + 'completions': has_items( + CompletionEntryMatcher( 'Console', '[ID]' ), + ), + 'errors': empty(), + } ) ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - line_num = 10, - column_num = 12 ) - response_data = app.post_json( '/completions', completion_data ).json - print( 'Response: ', response_data ) - assert_that( - response_data, - has_entries( { - 'completion_start_column': 12, - 'completions': has_items( - CompletionEntryMatcher( 'CursorLeft', - 'CursorLeft', - { 'kind': 'Property' } ), - CompletionEntryMatcher( 'CursorSize', - 'CursorSize', - { 'kind': 'Property' } ), - ), - 'errors': empty(), - } ) ) - - -@SharedYcmd -def GetCompletions_Unicode_test( app ): - filepath = PathToTestFile( 'testy', 'Unicode.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - line_num = 43, - column_num = 26 ) - response_data = app.post_json( '/completions', completion_data ).json - assert_that( response_data, - has_entries( { - 'completion_start_column': 26, - 'completions': has_items( - CompletionEntryMatcher( 'DoATest' ), - CompletionEntryMatcher( 'an_int' ), - CompletionEntryMatcher( 'a_unicøde' ), - CompletionEntryMatcher( 'øøø' ), - ), - 'errors': empty(), - } ) ) - - -@SharedYcmd -def GetCompletions_MultipleSolution_test( app ): - filepaths = [ PathToTestFile( 'testy', 'Program.cs' ), - PathToTestFile( 'testy-multiple-solutions', - 'solution-named-like-folder', - 'testy', - 'Program.cs' ) ] - for filepath in filepaths: + @SharedYcmd + def test_GetCompletions_Basic( self, app ): + filepath = PathToTestFile( 'testy', 'Program.cs' ) with WrapOmniSharpServer( app, filepath ): contents = ReadFile( filepath ) @@ -122,9 +65,97 @@ def GetCompletions_MultipleSolution_test( app ): contents = contents, line_num = 10, column_num = 12 ) - response_data = app.post_json( '/completions', - completion_data ).json + response_data = app.post_json( '/completions', completion_data ).json + print( 'Response: ', response_data ) + assert_that( + response_data, + has_entries( { + 'completion_start_column': 12, + 'completions': has_items( + CompletionEntryMatcher( 'CursorLeft', + 'CursorLeft', + { 'kind': 'Property' } ), + CompletionEntryMatcher( 'CursorSize', + 'CursorSize', + { 'kind': 'Property' } ), + ), + 'errors': empty(), + } ) ) + + + @SharedYcmd + def test_GetCompletions_Unicode( self, app ): + filepath = PathToTestFile( 'testy', 'Unicode.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + completion_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + line_num = 43, + column_num = 26 ) + response_data = app.post_json( '/completions', completion_data ).json + assert_that( response_data, + has_entries( { + 'completion_start_column': 26, + 'completions': has_items( + CompletionEntryMatcher( 'DoATest' ), + CompletionEntryMatcher( 'an_int' ), + CompletionEntryMatcher( 'a_unicøde' ), + CompletionEntryMatcher( 'øøø' ), + ), + 'errors': empty(), + } ) ) + + + @SharedYcmd + def test_GetCompletions_MultipleSolution( self, app ): + filepaths = [ PathToTestFile( 'testy', 'Program.cs' ), + PathToTestFile( 'testy-multiple-solutions', + 'solution-named-like-folder', + 'testy', + 'Program.cs' ) ] + for filepath in filepaths: + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + completion_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + line_num = 10, + column_num = 12 ) + response_data = app.post_json( '/completions', + completion_data ).json + + print( 'Response: ', response_data ) + assert_that( + response_data, + has_entries( { + 'completion_start_column': 12, + 'completions': has_items( + CompletionEntryMatcher( 'CursorLeft', + 'CursorLeft', + { 'kind': 'Property' } ), + CompletionEntryMatcher( 'CursorSize', + 'CursorSize', + { 'kind': 'Property' } ), + ), + 'errors': empty(), + } ) ) + + + @SharedYcmd + def test_GetCompletions_PathWithSpace( self, app ): + filepath = PathToTestFile( 'неприличное слово', 'Program.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + line_num = 9, + column_num = 12 ) + response_data = app.post_json( '/completions', completion_data ).json print( 'Response: ', response_data ) assert_that( response_data, @@ -142,55 +173,22 @@ def GetCompletions_MultipleSolution_test( app ): } ) ) -@SharedYcmd -def GetCompletions_PathWithSpace_test( app ): - filepath = PathToTestFile( 'неприличное слово', 'Program.cs' ) - with WrapOmniSharpServer( app, filepath ): + @SharedYcmd + def test_GetCompletions_DoesntStartWithAmbiguousMultipleSolutions( + self, app ): + filepath = PathToTestFile( 'testy-multiple-solutions', + 'solution-not-named-like-folder', + 'testy', 'Program.cs' ) contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + event_name = 'FileReadyToParse' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - line_num = 9, - column_num = 12 ) - response_data = app.post_json( '/completions', completion_data ).json - print( 'Response: ', response_data ) assert_that( - response_data, - has_entries( { - 'completion_start_column': 12, - 'completions': has_items( - CompletionEntryMatcher( 'CursorLeft', - 'CursorLeft', - { 'kind': 'Property' } ), - CompletionEntryMatcher( 'CursorSize', - 'CursorSize', - { 'kind': 'Property' } ), - ), - 'errors': empty(), - } ) ) - - -@SharedYcmd -def GetCompletions_DoesntStartWithAmbiguousMultipleSolutions_test( app ): - filepath = PathToTestFile( 'testy-multiple-solutions', - 'solution-not-named-like-folder', - 'testy', 'Program.cs' ) - contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - event_name = 'FileReadyToParse' ) - - assert_that( - calling( app.post_json ).with_args( '/event_notification', event_data ), - raises( AppError, 'Autodetection of solution file failed' ), - "The Omnisharp server started, despite us not being able to find a " - "suitable solution file to feed it. Did you fiddle with the solution " - "finding code in cs_completer.py? Hopefully you've enhanced it: you need " - "to update this test then :)" ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + calling( app.post_json ).with_args( '/event_notification', event_data ), + raises( AppError, 'Autodetection of solution file failed' ), + "The Omnisharp server started, despite us not being able to find a " + "suitable solution file to feed it. Did you fiddle with the solution " + "finding code in cs_completer.py? Hopefully you've enhanced it: you need " + "to update this test then :)" ) diff --git a/ycmd/tests/cs/signature_help_test.py b/ycmd/tests/cs/signature_help_test.py index f02546ae7d..e4f404a9d2 100644 --- a/ycmd/tests/cs/signature_help_test.py +++ b/ycmd/tests/cs/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -21,12 +21,13 @@ has_entries, has_items ) from unittest.mock import patch +from unittest import TestCase from ycmd import handlers from ycmd.utils import ReadFile, LOGGER -from ycmd.tests.cs import ( PathToTestFile, +from ycmd.tests.cs import ( PathToTestFile, # noqa IsolatedYcmd, SharedYcmd, - WrapOmniSharpServer ) + WrapOmniSharpServer, setUpModule, tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, ParameterMatcher, SignatureMatcher, @@ -34,197 +35,193 @@ CompletionEntryMatcher ) -@SharedYcmd -def Signature_Help_Available_test( app ): - response = app.get( '/signature_help_available', - { 'subserver': 'cs' } ).json - assert_that( response, SignatureAvailableMatcher( 'YES' ) ) - - -@SharedYcmd -def Signature_Help_Available_Server_Not_Ready_test( app ): - completer = handlers._server_state.GetFiletypeCompleter( [ 'cs' ] ) - with patch.object( completer, 'ServerIsHealthy', return_value = False ): +class SignatureHelpTest( TestCase ): + @SharedYcmd + def test_Signature_Help_Available( self, app ): response = app.get( '/signature_help_available', { 'subserver': 'cs' } ).json - assert_that( response, SignatureAvailableMatcher( 'PENDING' ) ) - - -@SharedYcmd -def SignatureHelp_TriggerComma_test( app ): - filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) - contents = ReadFile( filepath ) - request = BuildRequest( - line_num = 17, - column_num = 16, - filetypes = [ 'cs' ], - filepath = filepath, - contents = contents ) - with WrapOmniSharpServer( app, filepath ): - response = app.post_json( '/signature_help', request ).json - LOGGER.debug( 'response = %s', response ) - assert_that( response, has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( 'void ContinuousTest.MultiArg(int i, string s)', - [ ParameterMatcher( 29, 34 ), - ParameterMatcher( 36, 44 ) ] ) - ) - } ) - } ) ) - - -@SharedYcmd -def SignatureHelp_TriggerParen_test( app ): - filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) - contents = ReadFile( filepath ) - request = BuildRequest( - line_num = 10, - column_num = 9, - filetypes = [ 'cs' ], - filepath = filepath, - contents = contents ) - with WrapOmniSharpServer( app, filepath ): - response = app.post_json( '/signature_help', request ).json - LOGGER.debug( 'response = %s', response ) - assert_that( response, has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'void ContinuousTest.Main(string[] args)', - [ ParameterMatcher( 25, 38 ) ] ) - ) - } ) - } ) ) - - -@IsolatedYcmd( { 'disable_signature_help': True } ) -def SignatureHelp_TriggerParen_Disabled_test( app ): - filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) - contents = ReadFile( filepath ) - request = BuildRequest( - line_num = 10, - column_num = 9, - filetypes = [ 'cs' ], - filepath = filepath, - contents = contents ) - with WrapOmniSharpServer( app, filepath ): - response = app.post_json( '/signature_help', request ).json - LOGGER.debug( 'response = %s', response ) - assert_that( response, has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty() - } ) - } ) ) - - -@SharedYcmd -def SignatureHelp_MultipleSignatures_test( app ): - filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) - contents = ReadFile( filepath ) - request = BuildRequest( - line_num = 18, - column_num = 15, - filetypes = [ 'cs' ], - filepath = filepath, - contents = contents ) - with WrapOmniSharpServer( app, filepath ): - response = app.post_json( '/signature_help', request ).json - LOGGER.debug( 'response = %s', response ) - assert_that( response, has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'void ContinuousTest.Overloaded(int i, int a)', - [ ParameterMatcher( 31, 36 ), - ParameterMatcher( 38, 43 ) ] ), - SignatureMatcher( 'void ContinuousTest.Overloaded(string s)', - [ ParameterMatcher( 31, 39 ) ] ), - ) - } ) - } ) ) - request[ 'column_num' ] = 20 - with WrapOmniSharpServer( app, filepath ): - response = app.post_json( '/signature_help', request ).json - LOGGER.debug( 'response = %s', response ) - assert_that( response, has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( 'void ContinuousTest.Overloaded(int i, int a)', - [ ParameterMatcher( 31, 36 ), - ParameterMatcher( 38, 43 ) ] ), - SignatureMatcher( 'void ContinuousTest.Overloaded(string s)', - [ ParameterMatcher( 31, 39 ) ] ), - ) - } ) - } ) ) - - -@SharedYcmd -def SignatureHelp_NotAFunction_NoError_test( app ): - filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) - contents = ReadFile( filepath ) - request = BuildRequest( - line_num = 19, - column_num = 7, - filetypes = [ 'cs' ], - filepath = filepath, - contents = contents ) - with WrapOmniSharpServer( app, filepath ): - response = app.post_json( '/signature_help', request ).json - LOGGER.debug( 'response = %s', response ) - assert_that( response, has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty() - } ) - } ) ) - - -@IsolatedYcmd( { 'disable_signature_help': True } ) -def GetCompletions_Basic_NoSigHelp_test( app ): - filepath = PathToTestFile( 'testy', 'Program.cs' ) - with WrapOmniSharpServer( app, filepath ): + assert_that( response, SignatureAvailableMatcher( 'YES' ) ) + + + @SharedYcmd + def test_Signature_Help_Available_Server_Not_Ready( self, app ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'cs' ] ) + with patch.object( completer, 'ServerIsHealthy', return_value = False ): + response = app.get( '/signature_help_available', + { 'subserver': 'cs' } ).json + assert_that( response, SignatureAvailableMatcher( 'PENDING' ) ) + + + @SharedYcmd + def test_SignatureHelp_TriggerComma( self, app ): + filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) + contents = ReadFile( filepath ) + request = BuildRequest( + line_num = 17, + column_num = 16, + filetypes = [ 'cs' ], + filepath = filepath, + contents = contents ) + with WrapOmniSharpServer( app, filepath ): + response = app.post_json( '/signature_help', request ).json + LOGGER.debug( 'response = %s', response ) + assert_that( response, has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( 'void ContinuousTest.MultiArg(int i, string s)', + [ ParameterMatcher( 29, 34 ), + ParameterMatcher( 36, 44 ) ] ) + ) + } ) + } ) ) + + + @SharedYcmd + def test_SignatureHelp_TriggerParen( self, app ): + filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) contents = ReadFile( filepath ) + request = BuildRequest( + line_num = 10, + column_num = 9, + filetypes = [ 'cs' ], + filepath = filepath, + contents = contents ) + with WrapOmniSharpServer( app, filepath ): + response = app.post_json( '/signature_help', request ).json + LOGGER.debug( 'response = %s', response ) + assert_that( response, has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'void ContinuousTest.Main(string[] args)', + [ ParameterMatcher( 25, 38 ) ] ) + ) + } ) + } ) ) + - completion_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - line_num = 10, - column_num = 12 ) - response_data = app.post_json( '/completions', completion_data ).json - print( 'Response: ', response_data ) - assert_that( - response_data, - has_entries( { - 'completion_start_column': 12, - 'completions': has_items( - CompletionEntryMatcher( 'CursorLeft', - 'CursorLeft', - { 'kind': 'Property' } ), - CompletionEntryMatcher( 'CursorSize', - 'CursorSize', - { 'kind': 'Property' } ), - ), + @IsolatedYcmd( { 'disable_signature_help': True } ) + def test_SignatureHelp_TriggerParen_Disabled( self, app ): + filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) + contents = ReadFile( filepath ) + request = BuildRequest( + line_num = 10, + column_num = 9, + filetypes = [ 'cs' ], + filepath = filepath, + contents = contents ) + with WrapOmniSharpServer( app, filepath ): + response = app.post_json( '/signature_help', request ).json + LOGGER.debug( 'response = %s', response ) + assert_that( response, has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty() + } ) + } ) ) + + + @SharedYcmd + def test_SignatureHelp_MultipleSignatures( self, app ): + filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) + contents = ReadFile( filepath ) + request = BuildRequest( + line_num = 18, + column_num = 15, + filetypes = [ 'cs' ], + filepath = filepath, + contents = contents ) + with WrapOmniSharpServer( app, filepath ): + response = app.post_json( '/signature_help', request ).json + LOGGER.debug( 'response = %s', response ) + assert_that( response, has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'void ContinuousTest.Overloaded(int i, int a)', + [ ParameterMatcher( 31, 36 ), + ParameterMatcher( 38, 43 ) ] ), + SignatureMatcher( 'void ContinuousTest.Overloaded(string s)', + [ ParameterMatcher( 31, 39 ) ] ), + ) + } ) + } ) ) + request[ 'column_num' ] = 20 + with WrapOmniSharpServer( app, filepath ): + response = app.post_json( '/signature_help', request ).json + LOGGER.debug( 'response = %s', response ) + assert_that( response, has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( 'void ContinuousTest.Overloaded(int i, int a)', + [ ParameterMatcher( 31, 36 ), + ParameterMatcher( 38, 43 ) ] ), + SignatureMatcher( 'void ContinuousTest.Overloaded(string s)', + [ ParameterMatcher( 31, 39 ) ] ), + ) + } ) + } ) ) + + + @SharedYcmd + def test_SignatureHelp_NotAFunction_NoError( self, app ): + filepath = PathToTestFile( 'testy', 'ContinuousTest.cs' ) + contents = ReadFile( filepath ) + request = BuildRequest( + line_num = 19, + column_num = 7, + filetypes = [ 'cs' ], + filepath = filepath, + contents = contents ) + with WrapOmniSharpServer( app, filepath ): + response = app.post_json( '/signature_help', request ).json + LOGGER.debug( 'response = %s', response ) + assert_that( response, has_entries( { 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty() + } ) } ) ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd( { 'disable_signature_help': True } ) + def test_GetCompletions_Basic_NoSigHelp( self, app ): + filepath = PathToTestFile( 'testy', 'Program.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + completion_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + line_num = 10, + column_num = 12 ) + response_data = app.post_json( '/completions', completion_data ).json + print( 'Response: ', response_data ) + assert_that( + response_data, + has_entries( { + 'completion_start_column': 12, + 'completions': has_items( + CompletionEntryMatcher( 'CursorLeft', + 'CursorLeft', + { 'kind': 'Property' } ), + CompletionEntryMatcher( 'CursorSize', + 'CursorSize', + { 'kind': 'Property' } ), + ), + 'errors': empty(), + } ) ) diff --git a/ycmd/tests/cs/subcommands_test.py b/ycmd/tests/cs/subcommands_test.py index 25d87f2d14..55294898e5 100644 --- a/ycmd/tests/cs/subcommands_test.py +++ b/ycmd/tests/cs/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -22,12 +22,12 @@ has_items, contains_exactly ) from unittest.mock import patch +from unittest import TestCase import os.path -import pytest from ycmd import user_options_store -from ycmd.tests.cs import ( IsolatedYcmd, PathToTestFile, SharedYcmd, - WrapOmniSharpServer ) +from ycmd.tests.cs import ( IsolatedYcmd, PathToTestFile, SharedYcmd, # noqa + WrapOmniSharpServer, setUpModule, tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, ErrorMatcher, @@ -39,834 +39,840 @@ from ycmd.utils import ReadFile -@SharedYcmd -def Subcommands_FixIt_NoFixitsFound_test( app ): - fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' ) - with WrapOmniSharpServer( app, fixit_test ): - contents = ReadFile( fixit_test ) - - request = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'FixIt' ], - line_num = 1, - column_num = 1, - contents = contents, - filetype = 'cs', - filepath = fixit_test ) - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entries( { 'fixits': empty() } ) ) - - -@SharedYcmd -def Subcommands_FixIt_Multi_test( app ): - fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' ) - with WrapOmniSharpServer( app, fixit_test ): - contents = ReadFile( fixit_test ) - - request = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'FixIt' ], - line_num = 5, - column_num = 27, - contents = contents, - filetype = 'cs', - filepath = fixit_test ) - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': 'Introduce constant', - 'command': has_entries( { 'index': 0 } ), - 'resolve': True } ), - has_entries( { - 'text': 'Convert to binary', - 'command': has_entries( { 'index': 1 } ), - 'resolve': True } ), - has_entries( { - 'text': 'Convert to hex', - 'command': has_entries( { 'index': 2 } ), - 'resolve': True } ), - ) } ) ) - request.pop( 'command_arguments' ) - request.update( { 'fixit': response[ 'fixits' ][ 1 ] } ) - response = app.post_json( '/resolve_fixit', request ).json - assert_that( response, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'location': LocationMatcher( fixit_test, 5, 27 ), - 'chunks': contains_exactly( - has_entries( { 'replacement_text': '0b101', } ) ) - } ) ) } ) ) - - -@SharedYcmd -def Subcommands_FixIt_Range_test( app ): - fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' ) - with WrapOmniSharpServer( app, fixit_test ): - contents = ReadFile( fixit_test ) - - request = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'FixIt' ], - line_num = 5, - column_num = 23, - contents = contents, - filetype = 'cs', - filepath = fixit_test ) - request.update( { 'range': { - 'start': { 'line_num': 5, 'column_num': 23 }, - 'end': { 'line_num': 5, 'column_num': 27 } - } } ) - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': 'Extract method', - 'command': has_entries( { 'index': 0 } ), - 'resolve': True } ), - has_entries( { - 'text': 'Extract local function', - 'command': has_entries( { 'index': 1 } ), - 'resolve': True } ), - ) - } ) ) - - -@SharedYcmd -def Subcommands_FixIt_Single_test( app ): - fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' ) - with WrapOmniSharpServer( app, fixit_test ): - contents = ReadFile( fixit_test ) - - request = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'FixIt' ], - line_num = 4, - column_num = 17, - contents = contents, - filetype = 'cs', - filepath = fixit_test ) - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'location': LocationMatcher( fixit_test, 4, 17 ), - 'chunks': contains_exactly( - has_entries( { - 'replacement_text': 'var', - 'range': RangeMatcher( fixit_test, ( 4, 13 ), ( 4, 16 ) ) } ) - ) } ) ) } ) ) - - -@SharedYcmd -def Subcommands_RefactorRename_MissingNewName_test( app ): - continuous_test = PathToTestFile( 'testy', 'ContinuousTest.cs' ) - with WrapOmniSharpServer( app, continuous_test ): - contents = ReadFile( continuous_test ) - - request = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'RefactorRename' ], - line_num = 5, - column_num = 15, - contents = contents, - filetype = 'cs', - filepath = continuous_test ) - response = app.post_json( '/run_completer_command', - request, - expect_errors = True ).json - assert_that( response, ErrorMatcher( ValueError, - 'Please specify a new name to rename it to.\n' - 'Usage: RefactorRename ' ) ) - - -@SharedYcmd -def Subcommands_RefactorRename_Unicode_test( app ): - unicode_test = PathToTestFile( 'testy', 'Unicode.cs' ) - with WrapOmniSharpServer( app, unicode_test ): - contents = ReadFile( unicode_test ) - - request = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'RefactorRename', 'x' ], - line_num = 30, - column_num = 31, - contents = contents, - filetype = 'cs', - filepath = unicode_test ) - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'location': LocationMatcher( unicode_test, 30, 31 ), - 'chunks': contains_exactly( - has_entries( { - 'replacement_text': 'x', - 'range': RangeMatcher( unicode_test, ( 30, 29 ), ( 30, 35 ) ) } ) - ) } ) ) } ) ) - - -@SharedYcmd -def Subcommands_RefactorRename_Basic_test( app ): - continuous_test = PathToTestFile( 'testy', 'ContinuousTest.cs' ) - with WrapOmniSharpServer( app, continuous_test ): - contents = ReadFile( continuous_test ) - - request = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'RefactorRename', 'x' ], - line_num = 5, - column_num = 15, - contents = contents, - filetype = 'cs', - filepath = continuous_test ) - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'location': LocationMatcher( continuous_test, 5, 15 ), - 'chunks': contains_exactly( - has_entries( { - 'replacement_text': 'x', - 'range': RangeMatcher( continuous_test, ( 5, 15 ), ( 5, 29 ) ) } ) - ) } ) ) } ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GoTo_Basic_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - WaitUntilCompleterServerReady( app, 'cs' ) - destination = PathToTestFile( 'testy', 'Program.cs' ) - - goto_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GoTo' ], - line_num = 10, - column_num = 15, - contents = contents, - filetype = 'cs', - filepath = filepath ) - - response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, LocationMatcher( destination, 7, 22 ) ) - - -@SharedYcmd -@pytest.mark.parametrize( 'identifier,expected', [ - ( 'IGotoTestMultiple', - LocationMatcher( PathToTestFile( 'testy', 'GotoTestCase.cs' ), 39, 12 ) ), - ( 'DoSomething', - has_items( - LocationMatcher( PathToTestFile( 'testy', 'GotoTestCase.cs' ), 27, 8 ), - LocationMatcher( PathToTestFile( 'testy', 'GotoTestCase.cs' ), 31, 15 ), - LocationMatcher( PathToTestFile( 'testy', 'GotoTestCase.cs' ), 36, 8 ), - LocationMatcher( PathToTestFile( 'testy', 'GotoTestCase.cs' ), 40, 8 ), - LocationMatcher( PathToTestFile( 'testy', 'GotoTestCase.cs' ), 44, 15 ), - LocationMatcher( PathToTestFile( 'testy', - 'GotoTestCase.cs' ), 49, 15 ) ) ), - ( 'asd', ErrorMatcher( RuntimeError, 'No symbols found' ) ) -] ) -def Subcommands_GoToSymbol_test( app, identifier, expected ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - goto_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GoToSymbol', identifier ], - line_num = 1, - column_num = 1, - contents = contents, - filetype = 'cs', - filepath = filepath ) - response = app.post_json( '/run_completer_command', - goto_data, - expect_errors = True ).json - assert_that( response, expected ) - - -@SharedYcmd -def Subcommands_GoTo_Unicode_test( app ): - filepath = PathToTestFile( 'testy', 'Unicode.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - - goto_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GoTo' ], - line_num = 45, - column_num = 43, - contents = contents, - filetype = 'cs', - filepath = filepath ) - - response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, LocationMatcher( filepath, 30, 54 ) ) - - -@SharedYcmd -def Subcommands_GoToImplementation_Basic_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToImplementation' ], - line_num = 14, - column_num = 13, - contents = contents, - filetype = 'cs', - filepath = filepath - ) - - response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, LocationMatcher( filepath, 31, 15 ) ) - - -@SharedYcmd -def Subcommands_GoToImplementation_NoImplementation_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToImplementation' ], - line_num = 18, - column_num = 13, - contents = contents, - filetype = 'cs', - filepath = filepath - ) - - response = app.post_json( '/run_completer_command', - goto_data, - expect_errors = True ).json - assert_that( response, ErrorMatcher( RuntimeError, - 'No implementations found' ) ) - - -@SharedYcmd -def Subcommands_CsCompleter_InvalidLocation_test( app ): +def StopServer_KeepLogFiles( app ): filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToImplementation' ], - line_num = 3, - column_num = 1, - contents = contents, - filetype = 'cs', - filepath = filepath - ) - - response = app.post_json( '/run_completer_command', - goto_data, - expect_errors = True ).json - assert_that( response, ErrorMatcher( RuntimeError, - "Can't jump to implementation" ) ) - + event_data = BuildRequest( filetype = 'cs', filepath = filepath ) -@SharedYcmd -def Subcommands_GoToImplementationElseDeclaration_NoImplementation_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + response = app.post_json( '/debug_info', event_data ).json - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToImplementationElseDeclaration' ], - line_num = 18, - column_num = 13, - contents = contents, - filetype = 'cs', - filepath = filepath - ) + logfiles = [] + for server in response[ 'completer' ][ 'servers' ]: + logfiles.extend( server[ 'logfiles' ] ) - response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, LocationMatcher( filepath, 36, 8 ) ) + try: + for logfile in logfiles: + assert_that( os.path.exists( logfile ), + f'Logfile should exist at { logfile }' ) + finally: + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'cs', + filepath = filepath, + command_arguments = [ 'StopServer' ] + ) + ) + if user_options_store.Value( 'server_keep_logfiles' ): + for logfile in logfiles: + assert_that( os.path.exists( logfile ), + f'Logfile should still exist at { logfile }' ) + else: + for logfile in logfiles: + assert_that( not os.path.exists( logfile ), + f'Logfile should no longer exist at { logfile }' ) -@SharedYcmd -def Subcommands_GoToImplementationElseDeclaration_SingleImplementation_test( - app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToImplementationElseDeclaration' ], - line_num = 14, - column_num = 13, - contents = contents, - filetype = 'cs', - filepath = filepath - ) +class SubcommandsTest( TestCase ): + @SharedYcmd + def test_Subcommands_FixIt_NoFixitsFound( self, app ): + fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' ) + with WrapOmniSharpServer( app, fixit_test ): + contents = ReadFile( fixit_test ) - response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, LocationMatcher( filepath, 31, 15 ) ) + request = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'FixIt' ], + line_num = 1, + column_num = 1, + contents = contents, + filetype = 'cs', + filepath = fixit_test ) + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, has_entries( { 'fixits': empty() } ) ) -@SharedYcmd -def Subcommands_GoToImplementationElseDeclaration_MultipleImplementations_test( - app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_FixIt_Multi( self, app ): + fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' ) + with WrapOmniSharpServer( app, fixit_test ): + contents = ReadFile( fixit_test ) - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToImplementationElseDeclaration' ], - line_num = 22, - column_num = 13, - contents = contents, - filetype = 'cs', - filepath = filepath - ) + request = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'FixIt' ], + line_num = 5, + column_num = 27, + contents = contents, + filetype = 'cs', + filepath = fixit_test ) + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': 'Introduce constant', + 'command': has_entries( { 'index': 0 } ), + 'resolve': True } ), + has_entries( { + 'text': 'Convert to binary', + 'command': has_entries( { 'index': 1 } ), + 'resolve': True } ), + has_entries( { + 'text': 'Convert to hex', + 'command': has_entries( { 'index': 2 } ), + 'resolve': True } ), + ) } ) ) + request.pop( 'command_arguments' ) + request.update( { 'fixit': response[ 'fixits' ][ 1 ] } ) + response = app.post_json( '/resolve_fixit', request ).json + assert_that( response, has_entries( { + 'fixits': contains_exactly( has_entries( { + 'location': LocationMatcher( fixit_test, 5, 27 ), + 'chunks': contains_exactly( + has_entries( { 'replacement_text': '0b101', } ) ) + } ) ) } ) ) + + + @SharedYcmd + def test_Subcommands_FixIt_Range( self, app ): + fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' ) + with WrapOmniSharpServer( app, fixit_test ): + contents = ReadFile( fixit_test ) + + request = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'FixIt' ], + line_num = 5, + column_num = 23, + contents = contents, + filetype = 'cs', + filepath = fixit_test ) + request.update( { 'range': { + 'start': { 'line_num': 5, 'column_num': 23 }, + 'end': { 'line_num': 5, 'column_num': 27 } + } } ) + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': 'Extract method', + 'command': has_entries( { 'index': 0 } ), + 'resolve': True } ), + has_entries( { + 'text': 'Extract local function', + 'command': has_entries( { 'index': 1 } ), + 'resolve': True } ), + ) + } ) ) - response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, - contains_exactly( LocationMatcher( filepath, 44, 15 ), - LocationMatcher( filepath, 49, 15 ) ) ) + @SharedYcmd + def test_Subcommands_FixIt_Single( self, app ): + fixit_test = PathToTestFile( 'testy', 'FixItTestCase.cs' ) + with WrapOmniSharpServer( app, fixit_test ): + contents = ReadFile( fixit_test ) -@SharedYcmd -def Subcommands_GoToReferences_InvalidLocation_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): + request = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'FixIt' ], + line_num = 4, + column_num = 17, + contents = contents, + filetype = 'cs', + filepath = fixit_test ) + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, has_entries( { + 'fixits': contains_exactly( has_entries( { + 'location': LocationMatcher( fixit_test, 4, 17 ), + 'chunks': contains_exactly( + has_entries( { + 'replacement_text': 'var', + 'range': RangeMatcher( fixit_test, ( 4, 13 ), ( 4, 16 ) ) } ) + ) } ) ) } ) ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_MissingNewName( self, app ): + continuous_test = PathToTestFile( 'testy', 'ContinuousTest.cs' ) + with WrapOmniSharpServer( app, continuous_test ): + contents = ReadFile( continuous_test ) + + request = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'RefactorRename' ], + line_num = 5, + column_num = 15, + contents = contents, + filetype = 'cs', + filepath = continuous_test ) + response = app.post_json( '/run_completer_command', + request, + expect_errors = True ).json + assert_that( response, ErrorMatcher( ValueError, + 'Please specify a new name to rename it to.\n' + 'Usage: RefactorRename ' ) ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_Unicode( self, app ): + unicode_test = PathToTestFile( 'testy', 'Unicode.cs' ) + with WrapOmniSharpServer( app, unicode_test ): + contents = ReadFile( unicode_test ) + + request = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'RefactorRename', 'x' ], + line_num = 30, + column_num = 31, + contents = contents, + filetype = 'cs', + filepath = unicode_test ) + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, has_entries( { + 'fixits': contains_exactly( has_entries( { + 'location': LocationMatcher( unicode_test, 30, 31 ), + 'chunks': contains_exactly( + has_entries( { + 'replacement_text': 'x', + 'range': RangeMatcher( unicode_test, ( 30, 29 ), ( 30, 35 ) ) } ) + ) } ) ) } ) ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_Basic( self, app ): + continuous_test = PathToTestFile( 'testy', 'ContinuousTest.cs' ) + with WrapOmniSharpServer( app, continuous_test ): + contents = ReadFile( continuous_test ) + + request = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'RefactorRename', 'x' ], + line_num = 5, + column_num = 15, + contents = contents, + filetype = 'cs', + filepath = continuous_test ) + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, has_entries( { + 'fixits': contains_exactly( has_entries( { + 'location': LocationMatcher( continuous_test, 5, 15 ), + 'chunks': contains_exactly( + has_entries( { + 'replacement_text': 'x', + 'range': RangeMatcher( continuous_test, ( 5, 15 ), ( 5, 29 ) ) } ) + ) } ) ) } ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoTo_Basic( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + event_name = 'FileReadyToParse' ) - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToReferences' ], - line_num = 3, - column_num = 1, - contents = contents, - filetype = 'cs', - filepath = filepath - ) - - response = app.post_json( '/run_completer_command', - goto_data, - expect_errors = True ).json - assert_that( response, ErrorMatcher( RuntimeError, 'No references found' ) ) - - -@SharedYcmd -def Subcommands_GoToReferences_MultipleReferences_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + app.post_json( '/event_notification', event_data ) + WaitUntilCompleterServerReady( app, 'cs' ) + destination = PathToTestFile( 'testy', 'Program.cs' ) - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToReferences' ], - line_num = 18, - column_num = 4, - contents = contents, - filetype = 'cs', - filepath = filepath - ) + goto_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GoTo' ], + line_num = 10, + column_num = 15, + contents = contents, + filetype = 'cs', + filepath = filepath ) response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, - contains_exactly( LocationMatcher( filepath, 17, 54 ), - LocationMatcher( filepath, 18, 4 ) ) ) + assert_that( response, LocationMatcher( destination, 7, 22 ) ) + + + @SharedYcmd + def test_Subcommands_GoToSymbol( self, app ): + for identifier, expected in [ + ( 'IGotoTestMultiple', + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 39, 12 ) ), + ( 'DoSomething', + has_items( + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 27, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 31, 15 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 36, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 40, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 44, 15 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 49, 15 ) ) ), + ( 'asd', ErrorMatcher( RuntimeError, 'No symbols found' ) ) + ]: + with self.subTest( identifier = identifier, expected = expected ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + goto_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GoToSymbol', + identifier ], + line_num = 1, + column_num = 1, + contents = contents, + filetype = 'cs', + filepath = filepath ) + response = app.post_json( '/run_completer_command', + goto_data, + expect_errors = True ).json + assert_that( response, expected ) + + + @SharedYcmd + def test_Subcommands_GoTo_Unicode( self, app ): + filepath = PathToTestFile( 'testy', 'Unicode.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + goto_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GoTo' ], + line_num = 45, + column_num = 43, + contents = contents, + filetype = 'cs', + filepath = filepath ) + response = app.post_json( '/run_completer_command', goto_data ).json + assert_that( response, LocationMatcher( filepath, 30, 54 ) ) -@SharedYcmd -def Subcommands_GoToReferences_Basic_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToReferences' ], - line_num = 21, - column_num = 29, - contents = contents, - filetype = 'cs', - filepath = filepath - ) - - response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, LocationMatcher( filepath, 21, 15 ) ) + @SharedYcmd + def test_Subcommands_GoToImplementation_Basic( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToImplementation' ], + line_num = 14, + column_num = 13, + contents = contents, + filetype = 'cs', + filepath = filepath + ) -@SharedYcmd -def Subcommands_GetToImplementation_Unicode_test( app ): - filepath = PathToTestFile( 'testy', 'Unicode.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + response = app.post_json( '/run_completer_command', goto_data ).json + assert_that( response, LocationMatcher( filepath, 31, 15 ) ) - goto_data = BuildRequest( - completer_target = 'filetype_default', - command_arguments = [ 'GoToImplementation' ], - line_num = 48, - column_num = 44, - contents = contents, - filetype = 'cs', - filepath = filepath - ) - response = app.post_json( '/run_completer_command', goto_data ).json - assert_that( response, - contains_exactly( LocationMatcher( filepath, 49, 66 ), - LocationMatcher( filepath, 50, 62 ) ) ) + @SharedYcmd + def test_Subcommands_GoToImplementation_NoImplementation( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToImplementation' ], + line_num = 18, + column_num = 13, + contents = contents, + filetype = 'cs', + filepath = filepath + ) -@SharedYcmd -def Subcommands_GetType_EmptyMessage_test( app ): - filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + response = app.post_json( '/run_completer_command', + goto_data, + expect_errors = True ).json + assert_that( response, ErrorMatcher( RuntimeError, + 'No implementations found' ) ) + + + @SharedYcmd + def test_Subcommands_CsCompleter_InvalidLocation( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToImplementation' ], + line_num = 3, + column_num = 1, + contents = contents, + filetype = 'cs', + filepath = filepath + ) - gettype_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GetType' ], - line_num = 1, - column_num = 1, - contents = contents, - filetype = 'cs', - filepath = filepath ) + response = app.post_json( '/run_completer_command', + goto_data, + expect_errors = True ).json + assert_that( response, ErrorMatcher( RuntimeError, + "Can't jump to implementation" ) ) + + + @SharedYcmd + def test_Subcommands_GoToImplementationElseDeclaration_NoImplementation( + self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToImplementationElseDeclaration' ], + line_num = 18, + column_num = 13, + contents = contents, + filetype = 'cs', + filepath = filepath + ) - response = app.post_json( '/run_completer_command', - gettype_data, - expect_errors = True ).json - assert_that( response, ErrorMatcher( RuntimeError, - 'No type info available.' ) ) + response = app.post_json( '/run_completer_command', goto_data ).json + assert_that( response, LocationMatcher( filepath, 36, 8 ) ) -@SharedYcmd -def Subcommands_GetType_VariableDeclaration_test( app ): - filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GoToImplementationElseDeclaration_SingleImplementation( + self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) - gettype_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GetType' ], - line_num = 5, - column_num = 5, - contents = contents, - filetype = 'cs', - filepath = filepath ) + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToImplementationElseDeclaration' ], + line_num = 14, + column_num = 13, + contents = contents, + filetype = 'cs', + filepath = filepath + ) - response = app.post_json( '/run_completer_command', gettype_data ).json - assert_that( response, has_entry( 'message', 'System.String' ) ) + response = app.post_json( '/run_completer_command', goto_data ).json + assert_that( response, LocationMatcher( filepath, 31, 15 ) ) -@SharedYcmd -def Subcommands_GetType_VariableUsage_test( app ): - filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GoToImplementationElseDeclaration_Multiple( + self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) - gettype_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GetType' ], - line_num = 6, - column_num = 5, - contents = contents, - filetype = 'cs', - filepath = filepath ) + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToImplementationElseDeclaration' ], + line_num = 22, + column_num = 13, + contents = contents, + filetype = 'cs', + filepath = filepath + ) - response = app.post_json( '/run_completer_command', gettype_data ).json - assert_that( response, has_entry( 'message', 'string str' ) ) + response = app.post_json( '/run_completer_command', goto_data ).json + assert_that( response, + contains_exactly( LocationMatcher( filepath, 44, 15 ), + LocationMatcher( filepath, 49, 15 ) ) ) -@SharedYcmd -def Subcommands_GetType_DocsIgnored_test( app ): - filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GoToReferences_InvalidLocation( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) - gettype_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GetType' ], - line_num = 10, - column_num = 34, - contents = contents, - filetype = 'cs', - filepath = filepath ) + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToReferences' ], + line_num = 3, + column_num = 1, + contents = contents, + filetype = 'cs', + filepath = filepath + ) - response = app.post_json( '/run_completer_command', gettype_data ).json - assert_that( response, has_entry( - 'message', 'int GetTypeTestCase.an_int_with_docs' ) ) + response = app.post_json( '/run_completer_command', + goto_data, + expect_errors = True ).json + assert_that( response, ErrorMatcher( + RuntimeError, 'No references found' ) ) + + + @SharedYcmd + def test_Subcommands_GoToReferences_MultipleReferences( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToReferences' ], + line_num = 18, + column_num = 4, + contents = contents, + filetype = 'cs', + filepath = filepath + ) + response = app.post_json( '/run_completer_command', goto_data ).json + assert_that( response, + contains_exactly( LocationMatcher( filepath, 17, 54 ), + LocationMatcher( filepath, 18, 4 ) ) ) -@SharedYcmd -def Subcommands_GetDoc_Invalid_test( app ): - filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - getdoc_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GetDoc' ], - line_num = 1, - column_num = 1, - contents = contents, - filetype = 'cs', - filepath = filepath ) + @SharedYcmd + def test_Subcommands_GoToReferences_Basic( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) - response = app.post_json( '/run_completer_command', - getdoc_data, - expect_errors = True ).json - assert_that( response, ErrorMatcher( RuntimeError, - 'No documentation available.' ) ) + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToReferences' ], + line_num = 21, + column_num = 29, + contents = contents, + filetype = 'cs', + filepath = filepath + ) + response = app.post_json( '/run_completer_command', goto_data ).json + assert_that( response, LocationMatcher( filepath, 21, 15 ) ) -@SharedYcmd -def Subcommands_GetDoc_Variable_test( app ): - filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) - getdoc_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GetDoc' ], - line_num = 13, - column_num = 28, - contents = contents, - filetype = 'cs', - filepath = filepath ) + @SharedYcmd + def test_Subcommands_GetToImplementation_Unicode( self, app ): + filepath = PathToTestFile( 'testy', 'Unicode.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) - response = app.post_json( '/run_completer_command', getdoc_data ).json - assert_that( response, - has_entry( 'detailed_info', - 'int GetDocTestCase.an_int\n' - 'an integer, or something' ) ) + goto_data = BuildRequest( + completer_target = 'filetype_default', + command_arguments = [ 'GoToImplementation' ], + line_num = 48, + column_num = 44, + contents = contents, + filetype = 'cs', + filepath = filepath + ) + response = app.post_json( '/run_completer_command', goto_data ).json + assert_that( response, + contains_exactly( LocationMatcher( filepath, 49, 66 ), + LocationMatcher( filepath, 50, 62 ) ) ) + + + @SharedYcmd + def test_Subcommands_GetType_EmptyMessage( self, app ): + filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + gettype_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GetType' ], + line_num = 1, + column_num = 1, + contents = contents, + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', + gettype_data, + expect_errors = True ).json + assert_that( response, ErrorMatcher( RuntimeError, + 'No type info available.' ) ) + + + @SharedYcmd + def test_Subcommands_GetType_VariableDeclaration( self, app ): + filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + gettype_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GetType' ], + line_num = 5, + column_num = 5, + contents = contents, + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', gettype_data ).json + assert_that( response, has_entry( 'message', 'System.String' ) ) + + + @SharedYcmd + def test_Subcommands_GetType_VariableUsage( self, app ): + filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + gettype_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GetType' ], + line_num = 6, + column_num = 5, + contents = contents, + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', gettype_data ).json + assert_that( response, has_entry( 'message', 'string str' ) ) + + + @SharedYcmd + def test_Subcommands_GetType_DocsIgnored( self, app ): + filepath = PathToTestFile( 'testy', 'GetTypeTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + gettype_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GetType' ], + line_num = 10, + column_num = 34, + contents = contents, + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', gettype_data ).json + assert_that( response, has_entry( + 'message', 'int GetTypeTestCase.an_int_with_docs' ) ) + + + @SharedYcmd + def test_Subcommands_GetDoc_Invalid( self, app ): + filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + getdoc_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GetDoc' ], + line_num = 1, + column_num = 1, + contents = contents, + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', + getdoc_data, + expect_errors = True ).json + assert_that( response, ErrorMatcher( RuntimeError, + 'No documentation available.' ) ) + + + @SharedYcmd + def test_Subcommands_GetDoc_Variable( self, app ): + filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + getdoc_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GetDoc' ], + line_num = 13, + column_num = 28, + contents = contents, + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', getdoc_data ).json + assert_that( response, + has_entry( 'detailed_info', + 'int GetDocTestCase.an_int\n' + 'an integer, or something' ) ) + + + @SharedYcmd + def test_Subcommands_GetDoc_Function( self, app ): + filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + getdoc_data = BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'GetDoc' ], + line_num = 33, + column_num = 27, + contents = contents, + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', getdoc_data ).json + assert_that( response, has_entry( 'detailed_info', + 'int GetDocTestCase.DoATest()\n' + 'Very important method.\n\nWith multiple lines of ' + 'commentary\nAnd Format-\n-ting' ) ) + + + @IsolatedYcmd() + def test_Subcommands_StopServer_NoErrorIfNotStarted( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + # Don't wrap the server - we don't want to start it! + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'cs', + filepath = filepath, + command_arguments = [ 'StopServer' ] + ) + ) -@SharedYcmd -def Subcommands_GetDoc_Function_test( app ): - filepath = PathToTestFile( 'testy', 'GetDocTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + request_data = BuildRequest( filetype = 'cs', filepath = filepath ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) + ) ) - getdoc_data = BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'GetDoc' ], - line_num = 33, - column_num = 27, - contents = contents, - filetype = 'cs', - filepath = filepath ) - response = app.post_json( '/run_completer_command', getdoc_data ).json - assert_that( response, has_entry( 'detailed_info', - 'int GetDocTestCase.DoATest()\n' - 'Very important method.\n\nWith multiple lines of ' - 'commentary\nAnd Format-\n-ting' ) ) + @IsolatedYcmd( { 'server_keep_logfiles': 1 } ) + def test_Subcommands_StopServer_KeepLogFiles( self, app ): + StopServer_KeepLogFiles( app ) -@IsolatedYcmd() -def Subcommands_StopServer_NoErrorIfNotStarted_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - # Don't wrap the server - we don't want to start it! - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'cs', - filepath = filepath, - command_arguments = [ 'StopServer' ] - ) - ) - - request_data = BuildRequest( filetype = 'cs', filepath = filepath ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) - ) ) - ) ) + @IsolatedYcmd( { 'server_keep_logfiles': 0 } ) + def test_Subcommands_StopServer_DoNotKeepLogFiles( self, app ): + StopServer_KeepLogFiles( app ) -def StopServer_KeepLogFiles( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - event_data = BuildRequest( filetype = 'cs', filepath = filepath ) + @IsolatedYcmd() + def test_Subcommands_RestartServer_PidChanges( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): - response = app.post_json( '/debug_info', event_data ).json + def GetPid(): + request_data = BuildRequest( filetype = 'cs', filepath = filepath ) + debug_info = app.post_json( '/debug_info', request_data ).json + return debug_info[ "completer" ][ "servers" ][ 0 ][ "pid" ] - logfiles = [] - for server in response[ 'completer' ][ 'servers' ]: - logfiles.extend( server[ 'logfiles' ] ) + old_pid = GetPid() - try: - for logfile in logfiles: - assert_that( os.path.exists( logfile ), - f'Logfile should exist at { logfile }' ) - finally: app.post_json( '/run_completer_command', BuildRequest( filetype = 'cs', filepath = filepath, - command_arguments = [ 'StopServer' ] + command_arguments = [ 'RestartServer' ] ) ) + WaitUntilCompleterServerReady( app, 'cs' ) - if user_options_store.Value( 'server_keep_logfiles' ): - for logfile in logfiles: - assert_that( os.path.exists( logfile ), - f'Logfile should still exist at { logfile }' ) - else: - for logfile in logfiles: - assert_that( not os.path.exists( logfile ), - f'Logfile should no longer exist at { logfile }' ) + new_pid = GetPid() + assert old_pid != new_pid, '%r == %r' % ( old_pid, new_pid ) -@IsolatedYcmd( { 'server_keep_logfiles': 1 } ) -def Subcommands_StopServer_KeepLogFiles_test( app ): - StopServer_KeepLogFiles( app ) + @IsolatedYcmd() + @patch( 'ycmd.utils.WaitUntilProcessIsTerminated', + MockProcessTerminationTimingOut ) + def test_Subcommands_StopServer_Timeout( self, app ): + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, + filetype = 'cs', + contents = contents, + event_name = 'FileReadyToParse' ) -@IsolatedYcmd( { 'server_keep_logfiles': 0 } ) -def Subcommands_StopServer_DoNotKeepLogFiles_test( app ): - StopServer_KeepLogFiles( app ) - - -@IsolatedYcmd() -def Subcommands_RestartServer_PidChanges_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - - def GetPid(): - request_data = BuildRequest( filetype = 'cs', filepath = filepath ) - debug_info = app.post_json( '/debug_info', request_data ).json - return debug_info[ "completer" ][ "servers" ][ 0 ][ "pid" ] - - old_pid = GetPid() + app.post_json( '/event_notification', event_data ) + WaitUntilCompleterServerReady( app, 'cs' ) app.post_json( '/run_completer_command', BuildRequest( filetype = 'cs', filepath = filepath, - command_arguments = [ 'RestartServer' ] + command_arguments = [ 'StopServer' ] ) ) - WaitUntilCompleterServerReady( app, 'cs' ) - - new_pid = GetPid() - - assert old_pid != new_pid, '%r == %r' % ( old_pid, new_pid ) - -@IsolatedYcmd() -@patch( 'ycmd.utils.WaitUntilProcessIsTerminated', - MockProcessTerminationTimingOut ) -def Subcommands_StopServer_Timeout_test( app ): - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'cs', - contents = contents, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - WaitUntilCompleterServerReady( app, 'cs' ) - - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'cs', - filepath = filepath, - command_arguments = [ 'StopServer' ] - ) - ) - - request_data = BuildRequest( filetype = 'cs', filepath = filepath ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) + request_data = BuildRequest( filetype = 'cs', filepath = filepath ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) ) ) - ) ) -@SharedYcmd -def Subcommands_Format_Works_test( app ): - filepath = PathToTestFile( 'testy', 'Program.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_Format_Works( self, app ): + filepath = PathToTestFile( 'testy', 'Program.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) - request = BuildRequest( command_arguments = [ 'Format' ], - line_num = 1, - column_num = 1, - contents = contents, - filetype = 'cs', - filepath = filepath ) - - response = app.post_json( '/run_completer_command', request ).json - print( 'completer response = ', response ) - assert_that( response, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'location': LocationMatcher( filepath, 1, 1 ), - 'chunks': contains_exactly( - ChunkMatcher( - '\n }\n ', - LocationMatcher( filepath, 11, 1 ), - LocationMatcher( filepath, 12, 2 ) - ), - ChunkMatcher( - ' ', - LocationMatcher( filepath, 10, 1 ), - LocationMatcher( filepath, 10, 4 ) - ), - ChunkMatcher( - ' {\n ', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 9, 4 ) - ), - ChunkMatcher( - '', - LocationMatcher( filepath, 7, 26 ), - LocationMatcher( filepath, 7, 27 ) - ), - ChunkMatcher( - ' class MainClass\n {\n ', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 7, 3 ) - ), - ) } ) ) } ) ) - - -@SharedYcmd -def Subcommands_RangeFormat_Works_test( app ): - filepath = PathToTestFile( 'testy', 'Program.cs' ) - with WrapOmniSharpServer( app, filepath ): - contents = ReadFile( filepath ) + request = BuildRequest( command_arguments = [ 'Format' ], + line_num = 1, + column_num = 1, + contents = contents, + filetype = 'cs', + filepath = filepath ) - request = BuildRequest( command_arguments = [ 'Format' ], - line_num = 11, - column_num = 2, - contents = contents, - filetype = 'cs', - filepath = filepath ) - request[ 'range' ] = { - 'start': { 'line_num': 8, 'column_num': 1 }, - 'end': { 'line_num': 11, 'column_num': 4 } - } - response = app.post_json( '/run_completer_command', request ).json - print( 'completer response = ', response ) - assert_that( response, has_entries( { - 'fixits': contains_exactly( has_entries( { - 'location': LocationMatcher( filepath, 11, 2 ), - 'chunks': contains_exactly( - ChunkMatcher( - '\n ', - LocationMatcher( filepath, 11, 1 ), - LocationMatcher( filepath, 11, 3 ) - ), - ChunkMatcher( - ' ', - LocationMatcher( filepath, 10, 1 ), - LocationMatcher( filepath, 10, 4 ) - ), - ChunkMatcher( - ' {\n ', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 9, 4 ) - ), - ) } ) ) } ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + response = app.post_json( '/run_completer_command', request ).json + print( 'completer response = ', response ) + assert_that( response, has_entries( { + 'fixits': contains_exactly( has_entries( { + 'location': LocationMatcher( filepath, 1, 1 ), + 'chunks': contains_exactly( + ChunkMatcher( + '\n }\n ', + LocationMatcher( filepath, 11, 1 ), + LocationMatcher( filepath, 12, 2 ) + ), + ChunkMatcher( + ' ', + LocationMatcher( filepath, 10, 1 ), + LocationMatcher( filepath, 10, 4 ) + ), + ChunkMatcher( + ' {\n ', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 9, 4 ) + ), + ChunkMatcher( + '', + LocationMatcher( filepath, 7, 26 ), + LocationMatcher( filepath, 7, 27 ) + ), + ChunkMatcher( + ' class MainClass\n {\n ', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 7, 3 ) + ), + ) } ) ) } ) ) + + + @SharedYcmd + def test_Subcommands_RangeFormat_Works( self, app ): + filepath = PathToTestFile( 'testy', 'Program.cs' ) + with WrapOmniSharpServer( app, filepath ): + contents = ReadFile( filepath ) + + request = BuildRequest( command_arguments = [ 'Format' ], + line_num = 11, + column_num = 2, + contents = contents, + filetype = 'cs', + filepath = filepath ) + request[ 'range' ] = { + 'start': { 'line_num': 8, 'column_num': 1 }, + 'end': { 'line_num': 11, 'column_num': 4 } + } + response = app.post_json( '/run_completer_command', request ).json + print( 'completer response = ', response ) + assert_that( response, has_entries( { + 'fixits': contains_exactly( has_entries( { + 'location': LocationMatcher( filepath, 11, 2 ), + 'chunks': contains_exactly( + ChunkMatcher( + '\n ', + LocationMatcher( filepath, 11, 1 ), + LocationMatcher( filepath, 11, 3 ) + ), + ChunkMatcher( + ' ', + LocationMatcher( filepath, 10, 1 ), + LocationMatcher( filepath, 10, 4 ) + ), + ChunkMatcher( + ' {\n ', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 9, 4 ) + ), + ) } ) ) } ) ) diff --git a/ycmd/tests/diagnostics_test.py b/ycmd/tests/diagnostics_test.py index 3c34400ca7..12dccb74d3 100644 --- a/ycmd/tests/diagnostics_test.py +++ b/ycmd/tests/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -16,6 +16,7 @@ # along with ycmd. If not, see . from hamcrest import assert_that, equal_to +from unittest import TestCase from unittest.mock import patch import requests @@ -25,34 +26,30 @@ MessageMatcher, PatchCompleter ) -@SharedYcmd -def Diagnostics_DoesntWork_test( app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): - diag_data = BuildRequest( contents = "foo = 5", - line_num = 2, - filetype = 'dummy_filetype' ) +class DiagnosticsTest( TestCase ): + @SharedYcmd + def test_Diagnostics_DoesntWork( self, app ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + diag_data = BuildRequest( contents = "foo = 5", + line_num = 2, + filetype = 'dummy_filetype' ) - response = app.post_json( '/detailed_diagnostic', - diag_data, - expect_errors = True ) + response = app.post_json( '/detailed_diagnostic', + diag_data, + expect_errors = True ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, ErrorMatcher( NoDiagnosticSupport ) ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, ErrorMatcher( NoDiagnosticSupport ) ) -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.GetDetailedDiagnostic', - return_value = BuildDisplayMessageResponse( 'detailed diagnostic' ) ) -def Diagnostics_DoesWork_test( get_detailed_diag, app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): - diag_data = BuildRequest( contents = 'foo = 5', - filetype = 'dummy_filetype' ) + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.GetDetailedDiagnostic', + return_value = BuildDisplayMessageResponse( 'detailed diagnostic' ) ) + def test_Diagnostics_DoesWork( self, app, *args ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + diag_data = BuildRequest( contents = 'foo = 5', + filetype = 'dummy_filetype' ) - response = app.post_json( '/detailed_diagnostic', diag_data ) - assert_that( response.json, MessageMatcher( 'detailed diagnostic' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + response = app.post_json( '/detailed_diagnostic', diag_data ) + assert_that( response.json, MessageMatcher( 'detailed diagnostic' ) ) diff --git a/ycmd/tests/extra_conf_store_test.py b/ycmd/tests/extra_conf_store_test.py index ea923808c0..e3fcbb4ce9 100644 --- a/ycmd/tests/extra_conf_store_test.py +++ b/ycmd/tests/extra_conf_store_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -17,6 +17,7 @@ import inspect from unittest.mock import patch +from unittest import TestCase from hamcrest import ( assert_that, calling, equal_to, has_length, has_property, none, raises, same_instance ) @@ -33,221 +34,223 @@ '.ycm_extra_conf.py' ) -@IsolatedYcmd() -def ExtraConfStore_ModuleForSourceFile_UnknownExtraConf_test( app ): - filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) - assert_that( - calling( extra_conf_store.ModuleForSourceFile ).with_args( filename ), - raises( UnknownExtraConf, 'Found .*\\.ycm_extra_conf\\.py\\. Load?' ) - ) - - -@IsolatedYcmd( { 'confirm_extra_conf': 0 } ) -def ExtraConfStore_ModuleForSourceFile_NoConfirmation_test( app ): - filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) - module = extra_conf_store.ModuleForSourceFile( filename ) - assert_that( inspect.ismodule( module ) ) - assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) - assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) - assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) - assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), - equal_to( False ) ) - - -@IsolatedYcmd( { 'extra_conf_globlist': [ PROJECT_EXTRA_CONF ] } ) -def ExtraConfStore_ModuleForSourceFile_Whitelisted_test( app ): - filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) - module = extra_conf_store.ModuleForSourceFile( filename ) - assert_that( inspect.ismodule( module ) ) - assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) - assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) - assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) - assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), - equal_to( False ) ) - - -@IsolatedYcmd( { 'extra_conf_globlist': [ '!' + PROJECT_EXTRA_CONF ] } ) -def ExtraConfStore_ModuleForSourceFile_Blacklisted_test( app ): - filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) - assert_that( extra_conf_store.ModuleForSourceFile( filename ), none() ) - - -@patch.dict( 'os.environ', { 'YCMD_TEST': PROJECT_EXTRA_CONF } ) -@IsolatedYcmd( { 'extra_conf_globlist': [ '$YCMD_TEST' ] } ) -def ExtraConfStore_ModuleForSourceFile_UnixVarEnv_test( app ): - filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) - module = extra_conf_store.ModuleForSourceFile( filename ) - assert_that( inspect.ismodule( module ) ) - assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) - assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) - assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) - assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), - equal_to( False ) ) - - -@WindowsOnly -@patch.dict( 'os.environ', { 'YCMD_TEST': PROJECT_EXTRA_CONF } ) -@IsolatedYcmd( { 'extra_conf_globlist': [ '%YCMD_TEST%' ] } ) -def ExtraConfStore_ModuleForSourceFile_WinVarEnv_test( app ): - filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) - module = extra_conf_store.ModuleForSourceFile( filename ) - assert_that( inspect.ismodule( module ) ) - assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) - assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) - assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) - assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), - equal_to( False ) ) - - -@UnixOnly -@IsolatedYcmd( { 'extra_conf_globlist': [ - PathToTestFile( 'extra_conf', 'symlink', '*' ) ] } ) -def ExtraConfStore_ModuleForSourceFile_SupportSymlink_test( app ): - with TemporarySymlink( PathToTestFile( 'extra_conf', 'project' ), - PathToTestFile( 'extra_conf', 'symlink' ) ): +class ExtraConfStoreTest( TestCase ): + @IsolatedYcmd() + def test_ExtraConfStore_ModuleForSourceFile_UnknownExtraConf( self, app ): + filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) + assert_that( + calling( extra_conf_store.ModuleForSourceFile ).with_args( filename ), + raises( UnknownExtraConf, 'Found .*\\.ycm_extra_conf\\.py\\. Load?' ) + ) + + + @IsolatedYcmd( { 'confirm_extra_conf': 0 } ) + def test_ExtraConfStore_ModuleForSourceFile_NoConfirmation( self, app ): filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) module = extra_conf_store.ModuleForSourceFile( filename ) assert_that( inspect.ismodule( module ) ) assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) - assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), - equal_to( False ) ) - - -@IsolatedYcmd( { 'global_ycm_extra_conf': GLOBAL_EXTRA_CONF } ) -def ExtraConfStore_ModuleForSourceFile_GlobalExtraConf_test( app ): - filename = PathToTestFile( 'extra_conf', 'some_file' ) - module = extra_conf_store.ModuleForSourceFile( filename ) - assert_that( inspect.ismodule( module ) ) - assert_that( inspect.getfile( module ), equal_to( GLOBAL_EXTRA_CONF ) ) - assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) - assert_that( module.is_global_ycm_extra_conf, equal_to( True ) ) - assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), - equal_to( True ) ) - - -@patch.dict( 'os.environ', { 'YCMD_TEST': GLOBAL_EXTRA_CONF } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': '$YCMD_TEST' } ) -def ExtraConfStore_ModuleForSourceFile_GlobalExtraConf_UnixEnvVar_test( app ): - filename = PathToTestFile( 'extra_conf', 'some_file' ) - module = extra_conf_store.ModuleForSourceFile( filename ) - assert_that( inspect.ismodule( module ) ) - assert_that( inspect.getfile( module ), equal_to( GLOBAL_EXTRA_CONF ) ) - assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) - assert_that( module.is_global_ycm_extra_conf, equal_to( True ) ) - assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), - equal_to( True ) ) - - -@WindowsOnly -@patch.dict( 'os.environ', { 'YCMD_TEST': GLOBAL_EXTRA_CONF } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': '%YCMD_TEST%' } ) -def ExtraConfStore_ModuleForSourceFile_GlobalExtraConf_WinEnvVar_test( app ): - filename = PathToTestFile( 'extra_conf', 'some_file' ) - module = extra_conf_store.ModuleForSourceFile( filename ) - assert_that( inspect.ismodule( module ) ) - assert_that( inspect.getfile( module ), equal_to( GLOBAL_EXTRA_CONF ) ) - assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) - assert_that( module.is_global_ycm_extra_conf, equal_to( True ) ) - assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), - equal_to( True ) ) - - -@IsolatedYcmd( { 'global_ycm_extra_conf': NO_EXTRA_CONF } ) -@patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) -def ExtraConfStore_CallGlobalExtraConfMethod_NoGlobalExtraConf_test( logger, - app ): - extra_conf_store._CallGlobalExtraConfMethod( 'SomeMethod' ) - assert_that( logger.method_calls, has_length( 1 ) ) - logger.debug.assert_called_with( - 'No global extra conf, not calling method %s', - 'SomeMethod' ) - - -@IsolatedYcmd( { 'global_ycm_extra_conf': GLOBAL_EXTRA_CONF } ) -@patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) -def CallGlobalExtraConfMethod_NoMethodInGlobalExtraConf_test( logger, app ): - extra_conf_store._CallGlobalExtraConfMethod( 'MissingMethod' ) - assert_that( logger.method_calls, has_length( 1 ) ) - logger.debug.assert_called_with( - 'Global extra conf not loaded or no function %s', - 'MissingMethod' ) - - -@IsolatedYcmd( { 'global_ycm_extra_conf': GLOBAL_EXTRA_CONF } ) -@patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) -def CallGlobalExtraConfMethod_NoExceptionFromMethod_test( logger, app ): - extra_conf_store._CallGlobalExtraConfMethod( 'NoException' ) - assert_that( logger.method_calls, has_length( 1 ) ) - logger.info.assert_called_with( - 'Calling global extra conf method %s on conf file %s', - 'NoException', - GLOBAL_EXTRA_CONF ) - - -@IsolatedYcmd( { 'global_ycm_extra_conf': GLOBAL_EXTRA_CONF } ) -@patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) -def CallGlobalExtraConfMethod_CatchExceptionFromMethod_test( logger, app ): - extra_conf_store._CallGlobalExtraConfMethod( 'RaiseException' ) - assert_that( logger.method_calls, has_length( 2 ) ) - logger.info.assert_called_with( - 'Calling global extra conf method %s on conf file %s', - 'RaiseException', - GLOBAL_EXTRA_CONF ) - logger.exception.assert_called_with( - 'Error occurred while calling global extra conf method %s on conf file %s', - 'RaiseException', - GLOBAL_EXTRA_CONF ) - - -@IsolatedYcmd( { 'global_ycm_extra_conf': ERRONEOUS_EXTRA_CONF } ) -@patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) -def CallGlobalExtraConfMethod_CatchExceptionFromExtraConf_test( logger, app ): - extra_conf_store._CallGlobalExtraConfMethod( 'NoException' ) - assert_that( logger.method_calls, has_length( 1 ) ) - logger.exception.assert_called_with( - 'Error occurred while loading global extra conf %s', - ERRONEOUS_EXTRA_CONF ) - - -@IsolatedYcmd() -def Load_DoNotReloadExtraConf_NoForce_test( app ): - with patch( 'ycmd.extra_conf_store._ShouldLoad', return_value = True ): - module = extra_conf_store.Load( PROJECT_EXTRA_CONF ) + assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), + equal_to( False ) ) + + + @IsolatedYcmd( { 'extra_conf_globlist': [ PROJECT_EXTRA_CONF ] } ) + def test_ExtraConfStore_ModuleForSourceFile_Whitelisted( self, app ): + filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) + module = extra_conf_store.ModuleForSourceFile( filename ) assert_that( inspect.ismodule( module ) ) assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), equal_to( False ) ) - assert_that( - extra_conf_store.Load( PROJECT_EXTRA_CONF ), - same_instance( module ) - ) -@IsolatedYcmd() -def Load_DoNotReloadExtraConf_ForceEqualsTrue_test( app ): - with patch( 'ycmd.extra_conf_store._ShouldLoad', return_value = True ): - module = extra_conf_store.Load( PROJECT_EXTRA_CONF ) + @IsolatedYcmd( { 'extra_conf_globlist': [ '!' + PROJECT_EXTRA_CONF ] } ) + def test_ExtraConfStore_ModuleForSourceFile_Blacklisted( self, app ): + filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) + assert_that( extra_conf_store.ModuleForSourceFile( filename ), none() ) + + + @patch.dict( 'os.environ', { 'test_YCMD': PROJECT_EXTRA_CONF } ) + @IsolatedYcmd( { 'extra_conf_globlist': [ '$test_YCMD' ] } ) + def test_ExtraConfStore_ModuleForSourceFile_UnixVarEnv( self, app ): + filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) + module = extra_conf_store.ModuleForSourceFile( filename ) assert_that( inspect.ismodule( module ) ) assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), equal_to( False ) ) - assert_that( - extra_conf_store.Load( PROJECT_EXTRA_CONF, force = True ), - same_instance( module ) - ) -def ExtraConfStore_IsGlobalExtraConfStore_NotAExtraConf_test(): - assert_that( calling( extra_conf_store.IsGlobalExtraConfModule ).with_args( - extra_conf_store ), raises( AttributeError ) ) + @WindowsOnly + @patch.dict( 'os.environ', { 'test_YCMD': PROJECT_EXTRA_CONF } ) + @IsolatedYcmd( { 'extra_conf_globlist': [ '%test_YCMD%' ] } ) + def test_ExtraConfStore_ModuleForSourceFile_WinVarEnv( self, app ): + filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) + module = extra_conf_store.ModuleForSourceFile( filename ) + assert_that( inspect.ismodule( module ) ) + assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) + assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) + assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) + assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), + equal_to( False ) ) + + + @UnixOnly + @IsolatedYcmd( { 'extra_conf_globlist': [ + PathToTestFile( 'extra_conf', 'symlink', '*' ) ] } ) + def test_ExtraConfStore_ModuleForSourceFile_SupportSymlink( self, app ): + with TemporarySymlink( PathToTestFile( 'extra_conf', 'project' ), + PathToTestFile( 'extra_conf', 'symlink' ) ): + filename = PathToTestFile( 'extra_conf', 'project', 'some_file' ) + module = extra_conf_store.ModuleForSourceFile( filename ) + assert_that( inspect.ismodule( module ) ) + assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) + assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) + assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) + assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), + equal_to( False ) ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': GLOBAL_EXTRA_CONF } ) + def test_ExtraConfStore_ModuleForSourceFile_GlobalExtraConf( self, app ): + filename = PathToTestFile( 'extra_conf', 'some_file' ) + module = extra_conf_store.ModuleForSourceFile( filename ) + assert_that( inspect.ismodule( module ) ) + assert_that( inspect.getfile( module ), equal_to( GLOBAL_EXTRA_CONF ) ) + assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) + assert_that( module.is_global_ycm_extra_conf, equal_to( True ) ) + assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), + equal_to( True ) ) + + + @patch.dict( 'os.environ', { 'test_YCMD': GLOBAL_EXTRA_CONF } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': '$test_YCMD' } ) + def test_ExtraConfStore_ModuleForSourceFile_GlobalExtraConf_UnixEnvVar( + self, app ): + filename = PathToTestFile( 'extra_conf', 'some_file' ) + module = extra_conf_store.ModuleForSourceFile( filename ) + assert_that( inspect.ismodule( module ) ) + assert_that( inspect.getfile( module ), equal_to( GLOBAL_EXTRA_CONF ) ) + assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) + assert_that( module.is_global_ycm_extra_conf, equal_to( True ) ) + assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), + equal_to( True ) ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @WindowsOnly + @patch.dict( 'os.environ', { 'test_YCMD': GLOBAL_EXTRA_CONF } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': '%test_YCMD%' } ) + def test_ExtraConfStore_ModuleForSourceFile_GlobalExtraConf_WinEnvVar( + self, app ): + filename = PathToTestFile( 'extra_conf', 'some_file' ) + module = extra_conf_store.ModuleForSourceFile( filename ) + assert_that( inspect.ismodule( module ) ) + assert_that( inspect.getfile( module ), equal_to( GLOBAL_EXTRA_CONF ) ) + assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) + assert_that( module.is_global_ycm_extra_conf, equal_to( True ) ) + assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), + equal_to( True ) ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': NO_EXTRA_CONF } ) + @patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) + def test_ExtraConfStore_CallGlobalExtraConfMethod_NoGlobalExtraConf( + self, app, logger ): + extra_conf_store._CallGlobalExtraConfMethod( 'SomeMethod' ) + assert_that( logger.method_calls, has_length( 1 ) ) + logger.debug.assert_called_with( + 'No global extra conf, not calling method %s', + 'SomeMethod' ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': GLOBAL_EXTRA_CONF } ) + @patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) + def test_CallGlobalExtraConfMethod_NoMethodInGlobalExtraConf( + self, app, logger ): + extra_conf_store._CallGlobalExtraConfMethod( 'MissingMethod' ) + assert_that( logger.method_calls, has_length( 1 ) ) + logger.debug.assert_called_with( + 'Global extra conf not loaded or no function %s', + 'MissingMethod' ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': GLOBAL_EXTRA_CONF } ) + @patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) + def test_CallGlobalExtraConfMethod_NoExceptionFromMethod( self, app, logger ): + extra_conf_store._CallGlobalExtraConfMethod( 'NoException' ) + assert_that( logger.method_calls, has_length( 1 ) ) + logger.info.assert_called_with( + 'Calling global extra conf method %s on conf file %s', + 'NoException', + GLOBAL_EXTRA_CONF ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': GLOBAL_EXTRA_CONF } ) + @patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) + def test_CallGlobalExtraConfMethod_CatchExceptionFromMethod( + self, app, logger ): + extra_conf_store._CallGlobalExtraConfMethod( 'RaiseException' ) + assert_that( logger.method_calls, has_length( 2 ) ) + logger.info.assert_called_with( + 'Calling global extra conf method %s on conf file %s', + 'RaiseException', + GLOBAL_EXTRA_CONF ) + logger.exception.assert_called_with( + 'Error occurred while calling global extra conf ' + 'method %s on conf file %s', + 'RaiseException', + GLOBAL_EXTRA_CONF ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': ERRONEOUS_EXTRA_CONF } ) + @patch( 'ycmd.extra_conf_store.LOGGER', autospec = True ) + def test_CallGlobalExtraConfMethod_CatchExceptionFromExtraConf( + self, app, logger ): + extra_conf_store._CallGlobalExtraConfMethod( 'NoException' ) + assert_that( logger.method_calls, has_length( 1 ) ) + logger.exception.assert_called_with( + 'Error occurred while loading global extra conf %s', + ERRONEOUS_EXTRA_CONF ) + + + @IsolatedYcmd() + def test_Load_DoNotReloadExtraConf_NoForce( self, app ): + with patch( 'ycmd.extra_conf_store._ShouldLoad', return_value = True ): + module = extra_conf_store.Load( PROJECT_EXTRA_CONF ) + assert_that( inspect.ismodule( module ) ) + assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) + assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) + assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) + assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), + equal_to( False ) ) + assert_that( + extra_conf_store.Load( PROJECT_EXTRA_CONF ), + same_instance( module ) + ) + + + @IsolatedYcmd() + def test_Load_DoNotReloadExtraConf_ForceEqualsTrue( self, app ): + with patch( 'ycmd.extra_conf_store._ShouldLoad', return_value = True ): + module = extra_conf_store.Load( PROJECT_EXTRA_CONF ) + assert_that( inspect.ismodule( module ) ) + assert_that( inspect.getfile( module ), equal_to( PROJECT_EXTRA_CONF ) ) + assert_that( module, has_property( 'is_global_ycm_extra_conf' ) ) + assert_that( module.is_global_ycm_extra_conf, equal_to( False ) ) + assert_that( extra_conf_store.IsGlobalExtraConfModule( module ), + equal_to( False ) ) + assert_that( + extra_conf_store.Load( PROJECT_EXTRA_CONF, force = True ), + same_instance( module ) + ) + + + def test_ExtraConfStore_IsGlobalExtraConfStore_NotAExtraConf( self ): + assert_that( calling( extra_conf_store.IsGlobalExtraConfModule ).with_args( + extra_conf_store ), raises( AttributeError ) ) diff --git a/ycmd/tests/filename_completer_test.py b/ycmd/tests/filename_completer_test.py index a541bb5674..4080fe5ea5 100644 --- a/ycmd/tests/filename_completer_test.py +++ b/ycmd/tests/filename_completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2014-2020 ycmd contributors +# Copyright (C) 2014-2021 ycmd contributors # # This file is part of ycmd. # @@ -16,9 +16,9 @@ # along with ycmd. If not, see . import os -import pytest from hamcrest import assert_that, contains_inanyorder, empty, is_not from unittest.mock import patch +from unittest import TestCase from ycmd.tests import IsolatedYcmd from ycmd.tests.test_utils import ( BuildRequest, @@ -62,238 +62,226 @@ def FilenameCompleter_Completion( app, assert_that( results, expected_results ) +class FilenameCompleterTest( TestCase ): # A series of tests represented by tuples whose elements are: # - the line to complete; # - the environment variables; # - the expected completions. -@pytest.mark.parametrize( 'contents,env,expected', [ - ( '/', - {}, - ROOT_FOLDER_COMPLETIONS ), - ( '//', - {}, - () ), - ( 'const char* c = "/', - {}, - ROOT_FOLDER_COMPLETIONS ), - ( 'const char* c = "./', - {}, - ( ( 'dir with spaces (x64)', '[Dir]' ), - ( 'foo漢字.txt', '[File]' ), - ( 'test.cpp', '[File]' ), - ( 'test.hpp', '[File]' ) ) ), - ( 'const char* c = "./漢', - {}, - ( ( 'foo漢字.txt', '[File]' ), ) ), - ( 'const char* c = "./dir with spaces (x64)/', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = "./dir with spaces (x64)//', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = "../', - {}, - ( ( 'inner_dir', '[Dir]' ), - ( '∂†∫', '[Dir]' ) ) ), - ( 'const char* c = "../inner_dir/', - {}, - ( ( 'dir with spaces (x64)', '[Dir]' ), - ( 'foo漢字.txt', '[File]' ), - ( 'test.cpp', '[File]' ), - ( 'test.hpp', '[File]' ) ) ), - ( 'const char* c = "../inner_dir/dir with spaces (x64)/', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = "~/', - { 'HOME': DATA_DIR , 'USERPROFILE': DATA_DIR }, - ( ( 'dir with spaces (x64)', '[Dir]' ), - ( 'foo漢字.txt', '[File]' ), - ( 'test.cpp', '[File]' ), - ( 'test.hpp', '[File]' ) ) ), - ( 'const char* c = "~/dir with spaces (x64)/', - { 'HOME': DATA_DIR, 'USERPROFILE': DATA_DIR }, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = "~/dir with spaces (x64)/Qt/', - { 'HOME': DATA_DIR, 'USERPROFILE': DATA_DIR }, - ( ( 'QtGui', '[File]' ), ) ), - ( 'const char* c = "dir with spaces (x64)/', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = "dir with spaces (x64)/Qt/', - {}, - ( ( 'QtGui', '[File]' ), ) ), - ( 'const char* c = "dir with spaces (x64)/Qt/QtGui dir with spaces (x64)/', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = "dir with spaces (x64)/Qt/QtGui/', - {}, - () ), - ( 'const char* c = "dir with spaces (x64)/Qt/QtGui /', - {}, - () ), - ( 'set x = $YCM_TEST_DATA_DIR/dir with spaces (x64)/QtGui/', - { 'YCM_TEST_DATA_DIR': DATA_DIR }, - ( ( 'QDialog', '[File]' ), - ( 'QWidget', '[File]' ) ) ), - ( 'set x = $YCM_TEST_DIR/testdata/filename_completer/inner_dir/test', - { 'YCM_TEST_DIR': TEST_DIR }, - ( ( 'test.cpp', '[File]' ), - ( 'test.hpp', '[File]' ) ) ), - ( 'set x = $YCMTESTDIR/testdata/filename_completer/', - { 'YCMTESTDIR': TEST_DIR }, - ( ( 'inner_dir', '[Dir]' ), - ( '∂†∫', '[Dir]' ) ) ), - ( 'set x = $ycm_test_dir/testdata/filename_completer/inn', - { 'ycm_test_dir': TEST_DIR }, - ( ( 'inner_dir', '[Dir]' ), ) ), - ( 'set x = ' + TEST_DIR + - '/testdata/filename_completer/$YCM_TEST_filename_completer/', - { 'YCM_TEST_filename_completer': 'inner_dir' }, - ( ( 'dir with spaces (x64)', '[Dir]' ), - ( 'foo漢字.txt', '[File]' ), - ( 'test.cpp', '[File]' ), - ( 'test.hpp', '[File]' ) ) ), - ( 'set x = ' + TEST_DIR + - '/testdata/filename_completer/$YCM_TEST_filename_c0mpleter/test', - { 'YCM_TEST_filename_c0mpleter': 'inner_dir' }, - ( ( 'test.cpp', '[File]' ), - ( 'test.hpp', '[File]' ) ) ), - ( 'set x = ' + TEST_DIR + '/${YCM_TEST_td}ata/filename_completer/', - { 'YCM_TEST_td': 'testd' }, - ( ( 'inner_dir', '[Dir]' ), - ( '∂†∫', '[Dir]' ) ) ), - ( 'set x = ' + TEST_DIR + '/tes${YCM_TEST_td}/filename_completer/', - { 'YCM_TEST_td': 'tdata' }, - ( ( 'inner_dir', '[Dir]' ), - ( '∂†∫', '[Dir]' ) ) ), - ( 'set x = ' + TEST_DIR + '/testdata/filename_completer${YCM_TEST_td}/', - {}, - () ), - ( 'set x = ' + TEST_DIR + '/testdata/filename_completer${YCM_empty_var}/', - { 'YCM_empty_var': '' }, - ( ( 'inner_dir', '[Dir]' ), - ( '∂†∫', '[Dir]' ) ) ), - ( 'set x = ' + TEST_DIR + '/$YCM_TEST_td}/', - { 'YCM_TEST_td': 'testdata/filename_completer' }, - () ), - ( 'set x = ' + TEST_DIR + '/${YCM_TEST_td/', - { 'YCM_TEST_td': 'testdata/filename_completer' }, - () ), - ( 'set x = ' + TEST_DIR + '/$ YCM_TEST_td/', - { 'YCM_TEST_td': 'testdata/filename_completer' }, - () ), - ( 'test ' + DATA_DIR + '/../∂', - {}, - ( ( '∂†∫', '[Dir]' ), ) ), - ] ) -@IsolatedYcmd( { 'max_num_candidates': 0 } ) -def FilenameCompleter_Completion_test( app, contents, env, expected ): - FilenameCompleter_Completion( app, contents, env, 'foo', expected ) - - -@pytest.mark.parametrize( 'contents,env,expected', [ - ( '\\', - {}, - ROOT_FOLDER_COMPLETIONS ), - ( '/\\', - {}, - () ), - ( '\\\\', - {}, - () ), - ( '\\/', - {}, - () ), - ( 'const char* c = "\\', - {}, - ROOT_FOLDER_COMPLETIONS ), - ( 'const char* c = "' + DRIVE + '/', - {}, - ROOT_FOLDER_COMPLETIONS ), - ( 'const char* c = "' + DRIVE + '\\', - {}, - ROOT_FOLDER_COMPLETIONS ), - ( 'const char* c = ".\\', - {}, - ( ( 'dir with spaces (x64)', '[Dir]' ), - ( 'foo漢字.txt', '[File]' ), - ( 'test.cpp', '[File]' ), - ( 'test.hpp', '[File]' ) ) ), - ( 'const char* c = ".\\dir with spaces (x64)\\', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = ".\\dir with spaces (x64)\\\\', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = ".\\dir with spaces (x64)/\\', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = ".\\dir with spaces (x64)\\/', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'const char* c = ".\\dir with spaces (x64)/Qt\\', - {}, - ( ( 'QtGui', '[File]' ), ) ), - ( 'const char* c = "dir with spaces (x64)\\Qt\\', - {}, - ( ( 'QtGui', '[File]' ), ) ), - ( 'dir with spaces (x64)\\Qt/QtGui dir with spaces (x64)\\', - {}, - ( ( 'Qt', '[Dir]' ), - ( 'QtGui', '[Dir]' ) ) ), - ( 'set x = %YCM_TEST_DIR%\\testdata/filename_completer\\inner_dir/test', - { 'YCM_TEST_DIR': TEST_DIR }, - ( ( 'test.cpp', '[File]' ), - ( 'test.hpp', '[File]' ) ) ), - ( 'set x = YCM_TEST_DIR%\\testdata/filename_completer\\inner_dir/test', - { 'YCM_TEST_DIR': TEST_DIR }, - () ), - ( 'set x = %YCM_TEST_DIR\\testdata/filename_completer\\inner_dir/test', - { 'YCM_TEST_DIR': TEST_DIR }, - () ), - ] ) -@WindowsOnly -@IsolatedYcmd( { 'max_num_candidates': 0 } ) -def FilenameCompleter_Completion_Windows_test( app, contents, env, expected ): - FilenameCompleter_Completion( app, contents, env, 'foo', expected ) - - -@IsolatedYcmd( { 'filepath_completion_use_working_dir': 0 } ) -def WorkingDir_UseFilePath_test( app ): - assert_that( GetCurrentDirectory() != DATA_DIR, 'Please run this test from a ' - 'different directory' ) - - completion_data = BuildRequest( contents = 'ls ./dir with spaces (x64)/', - filepath = PATH_TO_TEST_FILE, - column_num = 28 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, contains_inanyorder( - CompletionEntryMatcher( 'Qt', '[Dir]' ), - CompletionEntryMatcher( 'QtGui', '[Dir]' ) - ) ) - - -@IsolatedYcmd( { 'filepath_completion_use_working_dir': 1 } ) -def WorkingDir_UseServerWorkingDirectory_test( app ): - test_dir = os.path.join( DATA_DIR, 'dir with spaces (x64)' ) - with CurrentWorkingDirectory( test_dir ) as old_current_dir: - assert_that( old_current_dir != test_dir, + @IsolatedYcmd( { 'max_num_candidates': 0 } ) + def test_FilenameCompleter_Completion( self, app ): + test_cases = [ + ( '/', + {}, + ROOT_FOLDER_COMPLETIONS ), + ( '//', + {}, + () ), + ( 'const char* c = "/', + {}, + ROOT_FOLDER_COMPLETIONS ), + ( 'const char* c = "./', + {}, + ( ( 'dir with spaces (x64)', '[Dir]' ), + ( 'foo漢字.txt', '[File]' ), + ( 'test.cpp', '[File]' ), + ( 'test.hpp', '[File]' ) ) ), + ( 'const char* c = "./漢', + {}, + ( ( 'foo漢字.txt', '[File]' ), ) ), + ( 'const char* c = "./dir with spaces (x64)/', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = "./dir with spaces (x64)//', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = "../', + {}, + ( ( 'inner_dir', '[Dir]' ), + ( '∂†∫', '[Dir]' ) ) ), + ( 'const char* c = "../inner_dir/', + {}, + ( ( 'dir with spaces (x64)', '[Dir]' ), + ( 'foo漢字.txt', '[File]' ), + ( 'test.cpp', '[File]' ), + ( 'test.hpp', '[File]' ) ) ), + ( 'const char* c = "../inner_dir/dir with spaces (x64)/', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = "~/', + { 'HOME': DATA_DIR , 'USERPROFILE': DATA_DIR }, + ( ( 'dir with spaces (x64)', '[Dir]' ), + ( 'foo漢字.txt', '[File]' ), + ( 'test.cpp', '[File]' ), + ( 'test.hpp', '[File]' ) ) ), + ( 'const char* c = "~/dir with spaces (x64)/', + { 'HOME': DATA_DIR, 'USERPROFILE': DATA_DIR }, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = "~/dir with spaces (x64)/Qt/', + { 'HOME': DATA_DIR, 'USERPROFILE': DATA_DIR }, + ( ( 'QtGui', '[File]' ), ) ), + ( 'const char* c = "dir with spaces (x64)/', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = "dir with spaces (x64)/Qt/', + {}, + ( ( 'QtGui', '[File]' ), ) ), + ( 'const char* c = "dir with spaces (x64)/Qt/QtGui ' + 'dir with spaces (x64)/', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = "dir with spaces (x64)/Qt/QtGui/', + {}, + () ), + ( 'const char* c = "dir with spaces (x64)/Qt/QtGui /', + {}, + () ), + ( 'set x = $YCM_TEST_DATA_DIR/dir with spaces (x64)/QtGui/', + { 'YCM_TEST_DATA_DIR': DATA_DIR }, + ( ( 'QDialog', '[File]' ), + ( 'QWidget', '[File]' ) ) ), + ( 'set x = $YCM_TEST_DIR/testdata/filename_completer/inner_dir/test', + { 'YCM_TEST_DIR': TEST_DIR }, + ( ( 'test.cpp', '[File]' ), + ( 'test.hpp', '[File]' ) ) ), + ( 'set x = $YCMTESTDIR/testdata/filename_completer/', + { 'YCMTESTDIR': TEST_DIR }, + ( ( 'inner_dir', '[Dir]' ), + ( '∂†∫', '[Dir]' ) ) ), + ( 'set x = $test_ycm_dir/testdata/filename_completer/inn', + { 'test_ycm_dir': TEST_DIR }, + ( ( 'inner_dir', '[Dir]' ), ) ), + ( 'set x = ' + TEST_DIR + + '/testdata/filename_completer/$YCM_TEST_filename_completer/', + { 'YCM_TEST_filename_completer': 'inner_dir' }, + ( ( 'dir with spaces (x64)', '[Dir]' ), + ( 'foo漢字.txt', '[File]' ), + ( 'test.cpp', '[File]' ), + ( 'test.hpp', '[File]' ) ) ), + ( 'set x = ' + TEST_DIR + + '/testdata/filename_completer/$YCM_TEST_filename_c0mpleter/test', + { 'YCM_TEST_filename_c0mpleter': 'inner_dir' }, + ( ( 'test.cpp', '[File]' ), + ( 'test.hpp', '[File]' ) ) ), + ( 'set x = ' + TEST_DIR + '/${YCM_TEST_td}ata/filename_completer/', + { 'YCM_TEST_td': 'testd' }, + ( ( 'inner_dir', '[Dir]' ), + ( '∂†∫', '[Dir]' ) ) ), + ( 'set x = ' + TEST_DIR + '/tes${YCM_TEST_td}/filename_completer/', + { 'YCM_TEST_td': 'tdata' }, + ( ( 'inner_dir', '[Dir]' ), + ( '∂†∫', '[Dir]' ) ) ), + ( 'set x = ' + TEST_DIR + '/testdata/filename_completer${YCM_TEST_td}/', + {}, + () ), + ( 'set x = ' + TEST_DIR + '/testdata/filename_completer${YCM_empty_var}/', + { 'YCM_empty_var': '' }, + ( ( 'inner_dir', '[Dir]' ), + ( '∂†∫', '[Dir]' ) ) ), + ( 'set x = ' + TEST_DIR + '/$YCM_TEST_td}/', + { 'YCM_TEST_td': 'testdata/filename_completer' }, + () ), + ( 'set x = ' + TEST_DIR + '/${YCM_TEST_td/', + { 'YCM_TEST_td': 'testdata/filename_completer' }, + () ), + ( 'set x = ' + TEST_DIR + '/$ YCM_TEST_td/', + { 'YCM_TEST_td': 'testdata/filename_completer' }, + () ), + ( 'test ' + DATA_DIR + '/../∂', + {}, + ( ( '∂†∫', '[Dir]' ), ) ), + ] + for contents, env, expected in test_cases: + with self.subTest( contents = contents, env = env, expected = expected ): + FilenameCompleter_Completion( app, contents, env, 'foo', expected ) + + + @WindowsOnly + @IsolatedYcmd( { 'max_num_candidates': 0 } ) + def test_FilenameCompleter_Completion_Windows( self, app ): + test_cases = [ + ( '\\', + {}, + ROOT_FOLDER_COMPLETIONS ), + ( '/\\', + {}, + () ), + ( '\\\\', + {}, + () ), + ( '\\/', + {}, + () ), + ( 'const char* c = "\\', + {}, + ROOT_FOLDER_COMPLETIONS ), + ( 'const char* c = "' + DRIVE + '/', + {}, + ROOT_FOLDER_COMPLETIONS ), + ( 'const char* c = "' + DRIVE + '\\', + {}, + ROOT_FOLDER_COMPLETIONS ), + ( 'const char* c = ".\\', + {}, + ( ( 'dir with spaces (x64)', '[Dir]' ), + ( 'foo漢字.txt', '[File]' ), + ( 'test.cpp', '[File]' ), + ( 'test.hpp', '[File]' ) ) ), + ( 'const char* c = ".\\dir with spaces (x64)\\', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = ".\\dir with spaces (x64)\\\\', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = ".\\dir with spaces (x64)/\\', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = ".\\dir with spaces (x64)\\/', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'const char* c = ".\\dir with spaces (x64)/Qt\\', + {}, + ( ( 'QtGui', '[File]' ), ) ), + ( 'const char* c = "dir with spaces (x64)\\Qt\\', + {}, + ( ( 'QtGui', '[File]' ), ) ), + ( 'dir with spaces (x64)\\Qt/QtGui dir with spaces (x64)\\', + {}, + ( ( 'Qt', '[Dir]' ), + ( 'QtGui', '[Dir]' ) ) ), + ( 'set x = %YCM_TEST_DIR%\\testdata/filename_completer\\inner_dir/test', + { 'YCM_TEST_DIR': TEST_DIR }, + ( ( 'test.cpp', '[File]' ), + ( 'test.hpp', '[File]' ) ) ), + ( 'set x = YCM_TEST_DIR%\\testdata/filename_completer\\inner_dir/test', + { 'YCM_TEST_DIR': TEST_DIR }, + () ), + ( 'set x = %YCM_TEST_DIR\\testdata/filename_completer\\inner_dir/test', + { 'YCM_TEST_DIR': TEST_DIR }, + () ), + ] + for contents, env, expected in test_cases: + with self.subTest( contents = contents, env = env, expected = expected ): + FilenameCompleter_Completion( app, contents, env, 'foo', expected ) + + + @IsolatedYcmd( { 'filepath_completion_use_working_dir': 0 } ) + def test_WorkingDir_UseFilePath( self, app ): + assert_that( GetCurrentDirectory() != DATA_DIR, 'Please run this test from a different directory' ) - completion_data = BuildRequest( contents = 'ls ./', + completion_data = BuildRequest( contents = 'ls ./dir with spaces (x64)/', filepath = PATH_TO_TEST_FILE, - column_num = 6 ) + column_num = 28 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] assert_that( results, contains_inanyorder( @@ -302,85 +290,98 @@ def WorkingDir_UseServerWorkingDirectory_test( app ): ) ) -@IsolatedYcmd( { 'filepath_completion_use_working_dir': 1 } ) -def WorkingDir_UseServerWorkingDirectory_Unicode_test( app ): - test_dir = os.path.join( TEST_DIR, 'testdata', 'filename_completer', '∂†∫' ) - with CurrentWorkingDirectory( test_dir ) as old_current_dir: - assert_that( old_current_dir != test_dir, + @IsolatedYcmd( { 'filepath_completion_use_working_dir': 1 } ) + def test_WorkingDir_UseServerWorkingDirectory( self, app ): + test_dir = os.path.join( DATA_DIR, 'dir with spaces (x64)' ) + with CurrentWorkingDirectory( test_dir ) as old_current_dir: + assert_that( old_current_dir != test_dir, + 'Please run this test from a different directory' ) + + completion_data = BuildRequest( contents = 'ls ./', + filepath = PATH_TO_TEST_FILE, + column_num = 6 ) + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, contains_inanyorder( + CompletionEntryMatcher( 'Qt', '[Dir]' ), + CompletionEntryMatcher( 'QtGui', '[Dir]' ) + ) ) + + + @IsolatedYcmd( { 'filepath_completion_use_working_dir': 1 } ) + def test_WorkingDir_UseServerWorkingDirectory_Unicode( self, app ): + test_dir = os.path.join( TEST_DIR, 'testdata', 'filename_completer', '∂†∫' ) + with CurrentWorkingDirectory( test_dir ) as old_current_dir: + assert_that( old_current_dir != test_dir, + 'Please run this test from a different directory' ) + + # We don't supply working_dir in the request, so the current working + # directory is used. + completion_data = BuildRequest( contents = 'ls ./', + filepath = PATH_TO_TEST_FILE, + column_num = 6 ) + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, contains_inanyorder( + CompletionEntryMatcher( '†es†.txt', '[File]' ) + ) ) + + + @IsolatedYcmd( { 'filepath_completion_use_working_dir': 1 } ) + def test_WorkingDir_UseClientWorkingDirectory( self, app ): + test_dir = os.path.join( DATA_DIR, 'dir with spaces (x64)' ) + assert_that( GetCurrentDirectory() != test_dir, 'Please run this test from a different directory' ) - # We don't supply working_dir in the request, so the current working - # directory is used. + # We supply working_dir in the request, so we expect results to be + # relative to the supplied path. completion_data = BuildRequest( contents = 'ls ./', filepath = PATH_TO_TEST_FILE, - column_num = 6 ) + column_num = 6, + working_dir = test_dir ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] assert_that( results, contains_inanyorder( - CompletionEntryMatcher( '†es†.txt', '[File]' ) + CompletionEntryMatcher( 'Qt', '[Dir]' ), + CompletionEntryMatcher( 'QtGui', '[Dir]' ) ) ) -@IsolatedYcmd( { 'filepath_completion_use_working_dir': 1 } ) -def WorkingDir_UseClientWorkingDirectory_test( app ): - test_dir = os.path.join( DATA_DIR, 'dir with spaces (x64)' ) - assert_that( GetCurrentDirectory() != test_dir, - 'Please run this test from a different directory' ) - - # We supply working_dir in the request, so we expect results to be - # relative to the supplied path. - completion_data = BuildRequest( contents = 'ls ./', - filepath = PATH_TO_TEST_FILE, - column_num = 6, - working_dir = test_dir ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, contains_inanyorder( - CompletionEntryMatcher( 'Qt', '[Dir]' ), - CompletionEntryMatcher( 'QtGui', '[Dir]' ) - ) ) - - -@IsolatedYcmd( { 'filepath_blacklist': {} } ) -def FilenameCompleter_NoFiletypeBlacklisted_test( app ): - completion_data = BuildRequest( filetypes = [ 'foo', 'bar' ], - contents = './', - column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, is_not( empty() ) ) - - -@IsolatedYcmd( { 'filepath_blacklist': { 'foo': 1 } } ) -def FilenameCompleter_FirstFiletypeBlacklisted_test( app ): - completion_data = BuildRequest( filetypes = [ 'foo', 'bar' ], - contents = './', - column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, empty() ) + @IsolatedYcmd( { 'filepath_blacklist': {} } ) + def test_FilenameCompleter_NoFiletypeBlacklisted( self, app ): + completion_data = BuildRequest( filetypes = [ 'foo', 'bar' ], + contents = './', + column_num = 3 ) + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, is_not( empty() ) ) -@IsolatedYcmd( { 'filepath_blacklist': { 'bar': 1 } } ) -def FilenameCompleter_SecondFiletypeBlacklisted_test( app ): - completion_data = BuildRequest( filetypes = [ 'foo', 'bar' ], - contents = './', - column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, empty() ) + @IsolatedYcmd( { 'filepath_blacklist': { 'foo': 1 } } ) + def test_FilenameCompleter_FirstFiletypeBlacklisted( self, app ): + completion_data = BuildRequest( filetypes = [ 'foo', 'bar' ], + contents = './', + column_num = 3 ) + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, empty() ) -@IsolatedYcmd( { 'filepath_blacklist': { '*': 1 } } ) -def FilenameCompleter_AllFiletypesBlacklisted_test( app ): - completion_data = BuildRequest( filetypes = [ 'foo', 'bar' ], - contents = './', - column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, empty() ) + @IsolatedYcmd( { 'filepath_blacklist': { 'bar': 1 } } ) + def test_FilenameCompleter_SecondFiletypeBlacklisted( self, app ): + completion_data = BuildRequest( filetypes = [ 'foo', 'bar' ], + contents = './', + column_num = 3 ) + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, empty() ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd( { 'filepath_blacklist': { '*': 1 } } ) + def test_FilenameCompleter_AllFiletypesBlacklisted( self, app ): + completion_data = BuildRequest( filetypes = [ 'foo', 'bar' ], + contents = './', + column_num = 3 ) + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, empty() ) diff --git a/ycmd/tests/get_completions_test.py b/ycmd/tests/get_completions_test.py index 269aeb74d7..65052efb50 100644 --- a/ycmd/tests/get_completions_test.py +++ b/ycmd/tests/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2020 ycmd contributors +# Copyright (C) 2013-2021 ycmd contributors # # This file is part of ycmd. # @@ -23,771 +23,773 @@ has_entries, has_items ) from unittest.mock import patch +from unittest import TestCase from ycmd.tests import IsolatedYcmd, SharedYcmd, PathToTestFile from ycmd.tests.test_utils import ( BuildRequest, CompletionEntryMatcher, DummyCompleter, PatchCompleter ) -@SharedYcmd -def GetCompletions_RequestValidation_NoLineNumException_test( app ): - response = app.post_json( '/semantic_completion_available', { - 'column_num': 0, - 'filepath': '/foo', - 'file_data': { - '/foo': { - 'filetypes': [ 'text' ], - 'contents': 'zoo' +class GetCompletionsTest( TestCase ): + @SharedYcmd + def test_GetCompletions_RequestValidation_NoLineNumException( self, app ): + response = app.post_json( '/semantic_completion_available', { + 'column_num': 0, + 'filepath': '/foo', + 'file_data': { + '/foo': { + 'filetypes': [ 'text' ], + 'contents': 'zoo' + } } - } - }, status = '5*', expect_errors = True ) - response.mustcontain( 'missing', 'line_num' ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_Works_test( app ): - event_data = BuildRequest( contents = 'foo foogoo ba', - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - - # query is 'oo' - completion_data = BuildRequest( contents = 'oo foo foogoo ba', - column_num = 3 ) - response_data = app.post_json( '/completions', completion_data ).json - - assert_that( 1, equal_to( response_data[ 'completion_start_column' ] ) ) - assert_that( - response_data[ 'completions' ], - has_items( CompletionEntryMatcher( 'foo', '[ID]' ), - CompletionEntryMatcher( 'foogoo', '[ID]' ) ) - ) - - -@IsolatedYcmd( { 'min_num_identifier_candidate_chars': 4 } ) -def GetCompletions_IdentifierCompleter_FilterShortCandidates_test( app ): - event_data = BuildRequest( contents = 'foo foogoo gooo', - event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo', column_num = 3 ) - response = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - - assert_that( response, - contains_inanyorder( CompletionEntryMatcher( 'foogoo' ), - CompletionEntryMatcher( 'gooo' ) ) ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_StartColumn_AfterWord_test( app ): - completion_data = BuildRequest( contents = 'oo foo foogoo ba', - column_num = 11 ) - response_data = app.post_json( '/completions', completion_data ).json - assert_that( 8, equal_to( response_data[ 'completion_start_column' ] ) ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_WorksForSpecialIdentifierChars_test( - app ): - contents = """ - textarea { - font-family: sans-serif; - font-size: 12px; - }""" - event_data = BuildRequest( contents = contents, - filetype = 'css', - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - - # query is 'fo' - completion_data = BuildRequest( contents = 'fo ' + contents, - filetype = 'css', - column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - - assert_that( - results, - has_items( CompletionEntryMatcher( 'font-size', '[ID]' ), - CompletionEntryMatcher( 'font-family', '[ID]' ) ) - ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_Unicode_InLine_test( app ): - contents = """ - This is some text cøntaining unicøde - """ - - event_data = BuildRequest( contents = contents, - filetype = 'css', - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - - # query is 'tx' - completion_data = BuildRequest( contents = 'tx ' + contents, - filetype = 'css', - column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - - assert_that( - results, - has_items( CompletionEntryMatcher( 'text', '[ID]' ) ) - ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_UnicodeQuery_InLine_test( app ): - contents = """ - This is some text cøntaining unicøde - """ - - event_data = BuildRequest( contents = contents, - filetype = 'css', - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - - # query is 'cø' - completion_data = BuildRequest( contents = 'cø ' + contents, - filetype = 'css', - column_num = 4 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - - assert_that( - results, - has_items( CompletionEntryMatcher( 'cøntaining', '[ID]' ), - CompletionEntryMatcher( 'unicøde', '[ID]' ) ) - ) - - -@IsolatedYcmd() -def GetCompletions_IdentifierCompleter_Unicode_MultipleCodePoints_test( app ): - # The first ō is on one code point while the second is on two ("o" + combining - # macron character). - event_data = BuildRequest( contents = 'fōo\nfōo', - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - - # query is 'fo' - completion_data = BuildRequest( contents = 'fōo\nfōo\nfo', - line_num = 3, - column_num = 3 ) - response_data = app.post_json( '/completions', completion_data ).json - - assert_that( 1, equal_to( response_data[ 'completion_start_column' ] ) ) - assert_that( - response_data[ 'completions' ], - has_items( CompletionEntryMatcher( 'fōo', '[ID]' ), - CompletionEntryMatcher( 'fōo', '[ID]' ) ) - ) - - -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - return_value = [ 'foo', 'bar', 'qux' ] ) -def GetCompletions_ForceSemantic_Works_test( candidates, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - force_semantic = True ) + }, status = '5*', expect_errors = True ) + response.mustcontain( 'missing', 'line_num' ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, has_items( CompletionEntryMatcher( 'foo' ), - CompletionEntryMatcher( 'bar' ), - CompletionEntryMatcher( 'qux' ) ) ) - - -@SharedYcmd -def GetCompletions_ForceSemantic_NoSemanticCompleter_test( app, *args ): - event_data = BuildRequest( event_name = 'FileReadyToParse', - filetype = 'dummy_filetype', - contents = 'complete_this_word\ncom' ) - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( filetype = 'dummy_filetype', - force_semantic = True, - contents = 'complete_this_word\ncom', - line_number = 2, - column_num = 4 ) - results = app.post_json( '/completions', completion_data ).json - assert_that( results, has_entries( { - 'completions': empty(), - 'errors': empty(), - } ) ) - - # For proof, show that non-forced completion would return identifiers - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'complete_this_word\ncom', - line_number = 2, - column_num = 4 ) - results = app.post_json( '/completions', completion_data ).json - assert_that( results, has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'com' ), - CompletionEntryMatcher( 'complete_this_word' ) ), - 'errors': empty(), - } ) ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_SyntaxKeywordsAdded_test( app ): - event_data = BuildRequest( event_name = 'FileReadyToParse', - syntax_keywords = [ 'foo', 'bar', 'zoo' ] ) - - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo ', - column_num = 3 ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - has_items( CompletionEntryMatcher( 'foo' ), - CompletionEntryMatcher( 'zoo' ) ) ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_TagsAdded_test( app ): - event_data = BuildRequest( event_name = 'FileReadyToParse', - tag_files = [ PathToTestFile( 'basic.tags' ) ] ) - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo', - column_num = 3, - filetype = 'cpp' ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - has_items( CompletionEntryMatcher( 'foosy' ), - CompletionEntryMatcher( 'fooaaa' ) ) ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_JustFinishedIdentifier_test( app ): - event_data = BuildRequest( event_name = 'CurrentIdentifierFinished', - column_num = 4, - contents = 'foo' ) - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo', column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - has_items( CompletionEntryMatcher( 'foo' ) ) ) - - -@IsolatedYcmd() -def GetCompletions_IdentifierCompleter_IgnoreFinishedIdentifierInString_test( - app ): - - event_data = BuildRequest( event_name = 'CurrentIdentifierFinished', - column_num = 6, - contents = '"foo"' ) - - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo', column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, empty() ) - - -@SharedYcmd -def GetCompletions_IdentifierCompleter_IdentifierUnderCursor_test( app ): - event_data = BuildRequest( event_name = 'InsertLeave', - column_num = 2, - contents = 'foo' ) - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo', column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - has_items( CompletionEntryMatcher( 'foo' ) ) ) - - -@IsolatedYcmd() -def GetCompletions_IdentifierCompleter_IgnoreCursorIdentifierInString_test( - app ): - - event_data = BuildRequest( event_name = 'InsertLeave', - column_num = 3, - contents = '"foo"' ) - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo', column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, empty() ) - - -@SharedYcmd -def GetCompletions_FilenameCompleter_Works_test( app ): - filepath = PathToTestFile( 'filename_completer', 'test.foo' ) - completion_data = BuildRequest( filepath = filepath, - contents = './', - column_num = 3 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - has_items( CompletionEntryMatcher( 'inner_dir', '[Dir]' ) ) ) - - -@SharedYcmd -def GetCompletions_FilenameCompleter_FallBackToIdentifierCompleter_test( app ): - filepath = PathToTestFile( 'filename_completer', 'test.foo' ) - event_data = BuildRequest( filepath = filepath, - contents = './nonexisting_dir', - filetype = 'foo', - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( filepath = filepath, - contents = './nonexisting_dir nd', - filetype = 'foo', - column_num = 21 ) - - assert_that( - app.post_json( '/completions', completion_data ).json[ 'completions' ], - has_items( CompletionEntryMatcher( 'nonexisting_dir', '[ID]' ) ) - ) - - -@SharedYcmd -def GetCompletions_UltiSnipsCompleter_Works_test( app ): - event_data = BuildRequest( - event_name = 'BufferVisit', - ultisnips_snippets = [ - { 'trigger': 'foo', 'description': 'bar' }, - { 'trigger': 'zoo', 'description': 'goo' }, - ] ) - - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo ', - column_num = 3 ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( - CompletionEntryMatcher( 'foo', extra_menu_info=' bar' ), - CompletionEntryMatcher( 'zoo', extra_menu_info=' goo' ) - ) - ) + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_Works( self, app ): + event_data = BuildRequest( contents = 'foo foogoo ba', + event_name = 'FileReadyToParse' ) -@IsolatedYcmd( { 'use_ultisnips_completer': 0 } ) -def GetCompletions_UltiSnipsCompleter_UnusedWhenOffWithOption_test( app ): - event_data = BuildRequest( - event_name = 'BufferVisit', - ultisnips_snippets = [ - { 'trigger': 'foo', 'description': 'bar' }, - { 'trigger': 'zoo', 'description': 'goo' }, - ] ) + app.post_json( '/event_notification', event_data ) - app.post_json( '/event_notification', event_data ) - - completion_data = BuildRequest( contents = 'oo ', column_num = 3 ) - - assert_that( app.post_json( '/completions', completion_data ).json, - has_entries( { 'completions': empty() } ) ) + # query is 'oo' + completion_data = BuildRequest( contents = 'oo foo foogoo ba', + column_num = 3 ) + response_data = app.post_json( '/completions', completion_data ).json + assert_that( 1, equal_to( response_data[ 'completion_start_column' ] ) ) + assert_that( + response_data[ 'completions' ], + has_items( CompletionEntryMatcher( 'foo', '[ID]' ), + CompletionEntryMatcher( 'foogoo', '[ID]' ) ) + ) -@IsolatedYcmd( { 'semantic_triggers': { 'dummy_filetype': [ '_' ] } } ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - return_value = [ 'some_candidate' ] ) -def GetCompletions_SemanticCompleter_WorksWhenTriggerIsIdentifier_test( - candidates, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'some_can', - column_num = 9 ) + @IsolatedYcmd( { 'min_num_identifier_candidate_chars': 4 } ) + def test_GetCompletions_IdentifierCompleter_FilterShortCandidates( self, + app ): + event_data = BuildRequest( contents = 'foo foogoo gooo', + event_name = 'FileReadyToParse' ) + app.post_json( '/event_notification', event_data ) + + completion_data = BuildRequest( contents = 'oo', column_num = 3 ) + response = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + + assert_that( response, + contains_inanyorder( CompletionEntryMatcher( 'foogoo' ), + CompletionEntryMatcher( 'gooo' ) ) ) + + + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_StartColumn_AfterWord( self, + app ): + completion_data = BuildRequest( contents = 'oo foo foogoo ba', + column_num = 11 ) + response_data = app.post_json( '/completions', completion_data ).json + assert_that( 8, equal_to( response_data[ 'completion_start_column' ] ) ) + + + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_WorksForSpecialIdentifierChars( + self, app ): + contents = """ + textarea { + font-family: sans-serif; + font-size: 12px; + }""" + event_data = BuildRequest( contents = contents, + filetype = 'css', + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', event_data ) + + # query is 'fo' + completion_data = BuildRequest( contents = 'fo ' + contents, + filetype = 'css', + column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] + assert_that( results, - has_items( CompletionEntryMatcher( 'some_candidate' ) ) + has_items( CompletionEntryMatcher( 'font-size', '[ID]' ), + CompletionEntryMatcher( 'font-family', '[ID]' ) ) ) -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - return_value = [ 'attribute' ] ) -def GetCompletions_CacheIsValid_test( - candidates_list, should_use, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'object.attr', - line_num = 1, - column_num = 12 ) + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_Unicode_InLine( self, app ): + contents = """ + This is some text cøntaining unicøde + """ - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attribute' ) ) - ) + event_data = BuildRequest( contents = contents, + filetype = 'css', + event_name = 'FileReadyToParse' ) - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'object.attri', - line_num = 1, - column_num = 13 ) + app.post_json( '/event_notification', event_data ) + # query is 'tx' + completion_data = BuildRequest( contents = 'tx ' + contents, + filetype = 'css', + column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] + assert_that( results, - has_items( CompletionEntryMatcher( 'attribute' ) ) + has_items( CompletionEntryMatcher( 'text', '[ID]' ) ) ) - # We ask for candidates only once because of cache. - assert_that( candidates_list.call_count, equal_to( 1 ) ) + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_UnicodeQuery_InLine( self, app ): + contents = """ + This is some text cøntaining unicøde + """ -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) -def GetCompletions_CacheIsNotValid_DifferentLineNumber_test( - candidates_list, should_use, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr\n' - 'objectB.attr', - line_num = 1, - column_num = 12 ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeA' ) ) - ) + event_data = BuildRequest( contents = contents, + filetype = 'css', + event_name = 'FileReadyToParse' ) - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.\n' - 'objectB.', - line_num = 2, - column_num = 12 ) + app.post_json( '/event_notification', event_data ) + # query is 'cø' + completion_data = BuildRequest( contents = 'cø ' + contents, + filetype = 'css', + column_num = 4 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] + assert_that( results, - has_items( CompletionEntryMatcher( 'attributeB' ) ) + has_items( CompletionEntryMatcher( 'cøntaining', '[ID]' ), + CompletionEntryMatcher( 'unicøde', '[ID]' ) ) ) - # We ask for candidates twice because of cache invalidation: - # line numbers are different between requests. - assert_that( candidates_list.call_count, equal_to( 2 ) ) - -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) -def GetCompletions_CacheIsNotValid_DifferentStartColumn_test( - candidates_list, should_use, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - # Start column is 9 - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr', - line_num = 1, - column_num = 12 ) + @IsolatedYcmd() + def test_GetCompletions_IdentifierCompleter_Unicode_MultipleCodePoints( + self, app ): + # The first ō is on one code point while the second is on two + # ("o" + combining macron character). + event_data = BuildRequest( contents = 'fōo\nfōo', + event_name = 'FileReadyToParse' ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeA' ) ) - ) + app.post_json( '/event_notification', event_data ) - # Start column is 8 - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'object.attri', - line_num = 1, - column_num = 12 ) + # query is 'fo' + completion_data = BuildRequest( contents = 'fōo\nfōo\nfo', + line_num = 3, + column_num = 3 ) + response_data = app.post_json( '/completions', completion_data ).json - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] + assert_that( 1, equal_to( response_data[ 'completion_start_column' ] ) ) assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeB' ) ) + response_data[ 'completions' ], + has_items( CompletionEntryMatcher( 'fōo', '[ID]' ), + CompletionEntryMatcher( 'fōo', '[ID]' ) ) ) - # We ask for candidates twice because of cache invalidation: - # start columns are different between requests. - assert_that( candidates_list.call_count, equal_to( 2 ) ) + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + return_value = [ 'foo', 'bar', 'qux' ] ) + def test_GetCompletions_ForceSemantic_Works( self, app, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + force_semantic = True ) -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) -def GetCompletions_CacheIsNotValid_DifferentForceSemantic_test( - candidates_list, should_use, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr', - line_num = 1, - column_num = 12, - force_semantic = True ) + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, has_items( CompletionEntryMatcher( 'foo' ), + CompletionEntryMatcher( 'bar' ), + CompletionEntryMatcher( 'qux' ) ) ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeA' ) ) - ) + @SharedYcmd + def test_GetCompletions_ForceSemantic_NoSemanticCompleter( self, app, *args ): + event_data = BuildRequest( event_name = 'FileReadyToParse', + filetype = 'dummy_filetype', + contents = 'complete_this_word\ncom' ) + app.post_json( '/event_notification', event_data ) + + completion_data = BuildRequest( filetype = 'dummy_filetype', + force_semantic = True, + contents = 'complete_this_word\ncom', + line_number = 2, + column_num = 4 ) + results = app.post_json( '/completions', completion_data ).json + assert_that( results, has_entries( { + 'completions': empty(), + 'errors': empty(), + } ) ) + + # For proof, show that non-forced completion would return identifiers completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr', - line_num = 1, - column_num = 12 ) + contents = 'complete_this_word\ncom', + line_number = 2, + column_num = 4 ) + results = app.post_json( '/completions', completion_data ).json + assert_that( results, has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'com' ), + CompletionEntryMatcher( 'complete_this_word' ) ), + 'errors': empty(), + } ) ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeB' ) ) - ) - # We ask for candidates twice because of cache invalidation: - # semantic completion is forced for one of the request, not the other. - assert_that( candidates_list.call_count, equal_to( 2 ) ) + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_SyntaxKeywordsAdded( self, app ): + event_data = BuildRequest( event_name = 'FileReadyToParse', + syntax_keywords = [ 'foo', 'bar', 'zoo' ] ) + app.post_json( '/event_notification', event_data ) -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) -def GetCompletions_CacheIsNotValid_DifferentContents_test( - candidates_list, should_use, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA = foo\n' - 'objectA.attr', - line_num = 2, - column_num = 12 ) + completion_data = BuildRequest( contents = 'oo ', + column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeA' ) ) - ) + assert_that( results, + has_items( CompletionEntryMatcher( 'foo' ), + CompletionEntryMatcher( 'zoo' ) ) ) - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA = bar\n' - 'objectA.attr', - line_num = 2, - column_num = 12 ) + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_TagsAdded( self, app ): + event_data = BuildRequest( event_name = 'FileReadyToParse', + tag_files = [ PathToTestFile( 'basic.tags' ) ] ) + app.post_json( '/event_notification', event_data ) + + completion_data = BuildRequest( contents = 'oo', + column_num = 3, + filetype = 'cpp' ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeB' ) ) - ) - - # We ask for candidates twice because of cache invalidation: - # both requests have the same cursor position and current line but file - # contents are different. - assert_that( candidates_list.call_count, equal_to( 2 ) ) + assert_that( results, + has_items( CompletionEntryMatcher( 'foosy' ), + CompletionEntryMatcher( 'fooaaa' ) ) ) -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) -def GetCompletions_CacheIsNotValid_DifferentNumberOfLines_test( - candidates_list, should_use, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr\n' - 'objectB.attr', - line_num = 1, - column_num = 12 ) + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_JustFinishedIdentifier( + self, app ): + event_data = BuildRequest( event_name = 'CurrentIdentifierFinished', + column_num = 4, + contents = 'foo' ) + app.post_json( '/event_notification', event_data ) + completion_data = BuildRequest( contents = 'oo', column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeA' ) ) - ) - - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr', - line_num = 1, - column_num = 12 ) + assert_that( results, + has_items( CompletionEntryMatcher( 'foo' ) ) ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeB' ) ) - ) - # We ask for candidates twice because of cache invalidation: - # both requests have the same cursor position and current line but the - # number of lines in the current file is different. - assert_that( candidates_list.call_count, equal_to( 2 ) ) + @IsolatedYcmd() + def test_GetCompletions_IdentifierCompleter_IgnoreFinishedIdentifierInString( + self, app ): + event_data = BuildRequest( event_name = 'CurrentIdentifierFinished', + column_num = 6, + contents = '"foo"' ) -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) -def GetCompletions_CacheIsNotValid_DifferentFileData_test( - candidates_list, should_use, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr', - line_num = 1, - column_num = 12, - file_data = { - '/bar': { - 'contents': 'objectA = foo', - 'filetypes': [ 'dummy_filetype' ] - } - } ) + app.post_json( '/event_notification', event_data ) + completion_data = BuildRequest( contents = 'oo', column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeA' ) ) - ) + assert_that( results, empty() ) - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr', - line_num = 1, - column_num = 12, - file_data = { - '/bar': { - 'contents': 'objectA = bar', - 'filetypes': [ 'dummy_filetype' ] - } - } ) + @SharedYcmd + def test_GetCompletions_IdentifierCompleter_IdentifierUnderCursor( + self, app ): + event_data = BuildRequest( event_name = 'InsertLeave', + column_num = 2, + contents = 'foo' ) + app.post_json( '/event_notification', event_data ) + + completion_data = BuildRequest( contents = 'oo', column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeB' ) ) - ) + assert_that( results, + has_items( CompletionEntryMatcher( 'foo' ) ) ) - # We ask for candidates twice because of cache invalidation: - # both requests have the same cursor position and contents for the current - # file but different contents for another file. - assert_that( candidates_list.call_count, equal_to( 2 ) ) + @IsolatedYcmd() + def test_GetCompletions_IdentifierCompleter_IgnoreCursorIdentifierInString( + self, app ): -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) -def GetCompletions_CacheIsNotValid_DifferentExtraConfData_test( - candidates_list, should_use, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr', - line_num = 1, - column_num = 12 ) + event_data = BuildRequest( event_name = 'InsertLeave', + column_num = 3, + contents = '"foo"' ) + app.post_json( '/event_notification', event_data ) + completion_data = BuildRequest( contents = 'oo', column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeA' ) ) - ) + assert_that( results, empty() ) - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.attr', - line_num = 1, - column_num = 12, - extra_conf_data = { 'key': 'value' } ) + @SharedYcmd + def test_GetCompletions_FilenameCompleter_Works( self, app ): + filepath = PathToTestFile( 'filename_completer', 'test.foo' ) + completion_data = BuildRequest( filepath = filepath, + contents = './', + column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] - assert_that( - results, - has_items( CompletionEntryMatcher( 'attributeB' ) ) - ) + assert_that( results, + has_items( CompletionEntryMatcher( 'inner_dir', '[Dir]' ) ) ) - # We ask for candidates twice because of cache invalidation: - # both requests are identical except the extra conf data. - assert_that( candidates_list.call_count, equal_to( 2 ) ) + @SharedYcmd + def test_GetCompletions_FilenameCompleter_FallBackToIdentifierCompleter( + self, app ): + filepath = PathToTestFile( 'filename_completer', 'test.foo' ) + event_data = BuildRequest( filepath = filepath, + contents = './nonexisting_dir', + filetype = 'foo', + event_name = 'FileReadyToParse' ) -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', - return_value = True ) -@patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', - return_value = [ 'aba', 'cbc' ] ) -def GetCompletions_FilterThenReturnFromCache_test( candidates_list, - should_use, - app ): + app.post_json( '/event_notification', event_data ) - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - # First, fill the cache with an empty query - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.', - line_num = 1, - column_num = 9 ) + completion_data = BuildRequest( filepath = filepath, + contents = './nonexisting_dir nd', + filetype = 'foo', + column_num = 21 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - has_items( CompletionEntryMatcher( 'aba' ), - CompletionEntryMatcher( 'cbc' ) ) ) + assert_that( + app.post_json( '/completions', completion_data ).json[ 'completions' ], + has_items( CompletionEntryMatcher( 'nonexisting_dir', '[ID]' ) ) + ) - # Now, filter them. This causes them to be converted to bytes and back - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.c', - line_num = 1, - column_num = 10 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - has_items( CompletionEntryMatcher( 'cbc' ) ) ) + @SharedYcmd + def test_GetCompletions_UltiSnipsCompleter_Works( self, app ): + event_data = BuildRequest( + event_name = 'BufferVisit', + ultisnips_snippets = [ + { 'trigger': 'foo', 'description': 'bar' }, + { 'trigger': 'zoo', 'description': 'goo' }, + ] ) - # Finally, request the original (unfiltered) set again. Ensure that we get - # proper results (not some bytes objects) - completion_data = BuildRequest( filetype = 'dummy_filetype', - contents = 'objectA.', - line_num = 1, - column_num = 9 ) + app.post_json( '/event_notification', event_data ) + + completion_data = BuildRequest( contents = 'oo ', + column_num = 3 ) results = app.post_json( '/completions', completion_data ).json[ 'completions' ] - assert_that( results, - has_items( CompletionEntryMatcher( 'aba' ), - CompletionEntryMatcher( 'cbc' ) ) ) - - assert_that( candidates_list.call_count, equal_to( 1 ) ) + assert_that( + results, + has_items( + CompletionEntryMatcher( 'foo', extra_menu_info=' bar' ), + CompletionEntryMatcher( 'zoo', extra_menu_info=' goo' ) + ) + ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd( { 'use_ultisnips_completer': 0 } ) + def test_GetCompletions_UltiSnipsCompleter_UnusedWhenOffWithOption( + self, app ): + event_data = BuildRequest( + event_name = 'BufferVisit', + ultisnips_snippets = [ + { 'trigger': 'foo', 'description': 'bar' }, + { 'trigger': 'zoo', 'description': 'goo' }, + ] ) + + app.post_json( '/event_notification', event_data ) + + completion_data = BuildRequest( contents = 'oo ', column_num = 3 ) + + assert_that( app.post_json( '/completions', completion_data ).json, + has_entries( { 'completions': empty() } ) ) + + + @IsolatedYcmd( { 'semantic_triggers': { 'dummy_filetype': [ '_' ] } } ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + return_value = [ 'some_candidate' ] ) + def test_GetCompletions_SemanticCompleter_WorksWhenTriggerIsIdentifier( + self, app, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'some_can', + column_num = 9 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'some_candidate' ) ) + ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + return_value = [ 'attribute' ] ) + def test_GetCompletions_CacheIsValid( self, app, candidates_list, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'object.attr', + line_num = 1, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attribute' ) ) + ) + + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'object.attri', + line_num = 1, + column_num = 13 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attribute' ) ) + ) + + # We ask for candidates only once because of cache. + assert_that( candidates_list.call_count, equal_to( 1 ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) + def test_GetCompletions_CacheIsNotValid_DifferentLineNumber( + self, app, candidates_list, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr\n' + 'objectB.attr', + line_num = 1, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeA' ) ) + ) + + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.\n' + 'objectB.', + line_num = 2, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeB' ) ) + ) + + # We ask for candidates twice because of cache invalidation: + # line numbers are different between requests. + assert_that( candidates_list.call_count, equal_to( 2 ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) + def test_GetCompletions_CacheIsNotValid_DifferentStartColumn( + self, app, candidates_list, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + # Start column is 9 + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr', + line_num = 1, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeA' ) ) + ) + + # Start column is 8 + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'object.attri', + line_num = 1, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeB' ) ) + ) + + # We ask for candidates twice because of cache invalidation: + # start columns are different between requests. + assert_that( candidates_list.call_count, equal_to( 2 ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) + def test_GetCompletions_CacheIsNotValid_DifferentForceSemantic( + self, app, candidates_list, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr', + line_num = 1, + column_num = 12, + force_semantic = True ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeA' ) ) + ) + + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr', + line_num = 1, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeB' ) ) + ) + + # We ask for candidates twice because of cache invalidation: + # semantic completion is forced for one of the request, not the other. + assert_that( candidates_list.call_count, equal_to( 2 ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) + def test_GetCompletions_CacheIsNotValid_DifferentContents( + self, app, candidates_list, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA = foo\n' + 'objectA.attr', + line_num = 2, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeA' ) ) + ) + + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA = bar\n' + 'objectA.attr', + line_num = 2, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeB' ) ) + ) + + # We ask for candidates twice because of cache invalidation: + # both requests have the same cursor position and current line but file + # contents are different. + assert_that( candidates_list.call_count, equal_to( 2 ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) + def test_GetCompletions_CacheIsNotValid_DifferentNumberOfLines( + self, app, candidates_list, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr\n' + 'objectB.attr', + line_num = 1, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeA' ) ) + ) + + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr', + line_num = 1, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeB' ) ) + ) + + # We ask for candidates twice because of cache invalidation: + # both requests have the same cursor position and current line but the + # number of lines in the current file is different. + assert_that( candidates_list.call_count, equal_to( 2 ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) + def test_GetCompletions_CacheIsNotValid_DifferentFileData( + self, app, candidates_list, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr', + line_num = 1, + column_num = 12, + file_data = { + '/bar': { + 'contents': 'objectA = foo', + 'filetypes': [ 'dummy_filetype' ] + } + } ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeA' ) ) + ) + + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr', + line_num = 1, + column_num = 12, + file_data = { + '/bar': { + 'contents': 'objectA = bar', + 'filetypes': [ 'dummy_filetype' ] + } + } ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeB' ) ) + ) + + # We ask for candidates twice because of cache invalidation: + # both requests have the same cursor position and contents for the current + # file but different contents for another file. + assert_that( candidates_list.call_count, equal_to( 2 ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + side_effect = [ [ 'attributeA' ], [ 'attributeB' ] ] ) + def test_GetCompletions_CacheIsNotValid_DifferentExtraConfData( + self, app, candidates_list, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr', + line_num = 1, + column_num = 12 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeA' ) ) + ) + + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.attr', + line_num = 1, + column_num = 12, + extra_conf_data = { 'key': 'value' } ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( + results, + has_items( CompletionEntryMatcher( 'attributeB' ) ) + ) + + # We ask for candidates twice because of cache invalidation: + # both requests are identical except the extra conf data. + assert_that( candidates_list.call_count, equal_to( 2 ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.ShouldUseNowInner', + return_value = True ) + @patch( 'ycmd.tests.test_utils.DummyCompleter.CandidatesList', + return_value = [ 'aba', 'cbc' ] ) + def test_GetCompletions_FilterThenReturnFromCache( + self, app, candidates_list, *args ): + + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + # First, fill the cache with an empty query + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.', + line_num = 1, + column_num = 9 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, + has_items( CompletionEntryMatcher( 'aba' ), + CompletionEntryMatcher( 'cbc' ) ) ) + + # Now, filter them. This causes them to be converted to bytes and back + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.c', + line_num = 1, + column_num = 10 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, + has_items( CompletionEntryMatcher( 'cbc' ) ) ) + + # Finally, request the original (unfiltered) set again. Ensure that we get + # proper results (not some bytes objects) + completion_data = BuildRequest( filetype = 'dummy_filetype', + contents = 'objectA.', + line_num = 1, + column_num = 9 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, + has_items( CompletionEntryMatcher( 'aba' ), + CompletionEntryMatcher( 'cbc' ) ) ) + + assert_that( candidates_list.call_count, equal_to( 1 ) ) diff --git a/ycmd/tests/go/__init__.py b/ycmd/tests/go/__init__.py index 5b9411966a..ff7a46b66f 100644 --- a/ycmd/tests/go/__init__.py +++ b/ycmd/tests/go/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,4 +15,65 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -from ycmd.tests.go.conftest import * # noqa +import functools +import os +from ycmd.tests.test_utils import ( BuildRequest, + ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp, + StopCompleterServer, + WaitUntilCompleterServerReady ) # noqa + +shared_app = None + + +def setUpModule(): + global shared_app + shared_app = SetUpApp() + with IgnoreExtraConfOutsideTestsFolder(): + StartGoCompleterServerInDirectory( shared_app, PathToTestFile() ) + + +def tearDownModule(): + global shared_app + StopCompleterServer( shared_app, 'go' ) + + +def StartGoCompleterServerInDirectory( app, directory ): + app.post_json( '/event_notification', + BuildRequest( + filepath = os.path.join( directory, 'goto.go' ), + event_name = 'FileReadyToParse', + filetype = 'go' ) ) + WaitUntilCompleterServerReady( app, 'go' ) + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'go' ) + return Wrapper + return Decorator + + +def PathToTestFile( *args ): + dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) + # GOPLS doesn't work if any parent directory is named "testdata" + return os.path.join( dir_of_current_script, 'go_module', *args ) diff --git a/ycmd/tests/go/conftest.py b/ycmd/tests/go/conftest.py deleted file mode 100644 index 66c42b10ad..0000000000 --- a/ycmd/tests/go/conftest.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import os -import pytest -from ycmd.tests.test_utils import ( BuildRequest, - ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - SetUpApp, - StopCompleterServer, - WaitUntilCompleterServerReady ) -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - shared_app = SetUpApp() - with IgnoreExtraConfOutsideTestsFolder(): - StartGoCompleterServerInDirectory( shared_app, PathToTestFile() ) - yield - StopCompleterServer( shared_app, 'go' ) - - -def StartGoCompleterServerInDirectory( app, directory ): - app.post_json( '/event_notification', - BuildRequest( - filepath = os.path.join( directory, 'goto.go' ), - event_name = 'FileReadyToParse', - filetype = 'go' ) ) - WaitUntilCompleterServerReady( app, 'go' ) - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with IsolatedApp( {} ) as app: - yield app - StopCompleterServer( app, 'go' ) - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes a unique ycmd application as a parameter. It should be used on tests -that change the server state in a irreversible way (ex: a semantic subserver -is stopped or restarted) or expect a clean state (ex: no semantic subserver -started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter -|custom_options| to give additional options and/or override the default ones. - -Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... -""" -IsolatedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def PathToTestFile( *args ): - dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) - # GOPLS doesn't work if any parent directory is named "testdata" - return os.path.join( dir_of_current_script, 'go_module', *args ) diff --git a/ycmd/tests/go/debug_info_test.py b/ycmd/tests/go/debug_info_test.py index 71950e4b4a..926854e459 100644 --- a/ycmd/tests/go/debug_info_test.py +++ b/ycmd/tests/go/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -21,89 +21,88 @@ has_entry, instance_of, matches_regexp ) +from unittest import TestCase -from ycmd.tests.go import ( IsolatedYcmd, +from ycmd.tests.go import ( IsolatedYcmd, # noqa PathToTestFile, SharedYcmd, - StartGoCompleterServerInDirectory ) + StartGoCompleterServerInDirectory, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import BuildRequest -@SharedYcmd -def DebugInfo_test( app ): - request_data = BuildRequest( filetype = 'go' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'Go', - 'servers': contains_exactly( has_entries( { - 'name': 'gopls', - 'is_running': instance_of( bool ), - 'executable': contains_exactly( instance_of( str ), - instance_of( str ), - instance_of( str ), - instance_of( str ) ), - 'address': None, - 'port': None, - 'pid': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': instance_of( str ), - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile(), - } ), - has_entries( { - 'key': 'Settings', - 'value': matches_regexp( '{\n "hoverKind": "Structured"\n}' ) - } ), - ) - } ) ), - } ) ) - ) +class DebugInfoTest( TestCase ): + @SharedYcmd + def test_DebugInfo( self, app ): + request_data = BuildRequest( filetype = 'go' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'Go', + 'servers': contains_exactly( has_entries( { + 'name': 'gopls', + 'is_running': instance_of( bool ), + 'executable': contains_exactly( instance_of( str ), + instance_of( str ), + instance_of( str ), + instance_of( str ) ), + 'address': None, + 'port': None, + 'pid': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': instance_of( str ), + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile(), + } ), + has_entries( { + 'key': 'Settings', + 'value': matches_regexp( '{\n "hoverKind": "Structured"\n}' ) + } ), + ) + } ) ), + } ) ) + ) -@IsolatedYcmd -def DebugInfo_ProjectDirectory_test( app ): - project_dir = PathToTestFile( 'td' ) - StartGoCompleterServerInDirectory( app, project_dir ) - assert_that( - app.post_json( '/debug_info', BuildRequest( filetype = 'go' ) ).json, - has_entry( 'completer', has_entries( { - 'name': 'Go', - 'servers': contains_exactly( has_entries( { - 'name': 'gopls', - 'is_running': instance_of( bool ), - 'executable': contains_exactly( instance_of( str ), - instance_of( str ), - instance_of( str ), - instance_of( str ) ), - 'address': None, - 'port': None, - 'pid': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': instance_of( str ), - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile(), - } ), - has_entries( { - 'key': 'Settings', - 'value': matches_regexp( '{\n "hoverKind": "Structured"\n}' ) - } ), - ) - } ) ), - } ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd() + def test_DebugInfo_ProjectDirectory( self, app ): + project_dir = PathToTestFile( 'td' ) + StartGoCompleterServerInDirectory( app, project_dir ) + assert_that( + app.post_json( '/debug_info', BuildRequest( filetype = 'go' ) ).json, + has_entry( 'completer', has_entries( { + 'name': 'Go', + 'servers': contains_exactly( has_entries( { + 'name': 'gopls', + 'is_running': instance_of( bool ), + 'executable': contains_exactly( instance_of( str ), + instance_of( str ), + instance_of( str ), + instance_of( str ) ), + 'address': None, + 'port': None, + 'pid': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': instance_of( str ), + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile(), + } ), + has_entries( { + 'key': 'Settings', + 'value': matches_regexp( '{\n "hoverKind": "Structured"\n}' ) + } ), + ) + } ) ), + } ) ) + ) diff --git a/ycmd/tests/go/diagnostics_test.py b/ycmd/tests/go/diagnostics_test.py index 31dc2a0917..a09a8372d4 100644 --- a/ycmd/tests/go/diagnostics_test.py +++ b/ycmd/tests/go/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -22,9 +22,10 @@ has_entry, starts_with ) from pprint import pformat +from unittest import TestCase import json -from ycmd.tests.go import PathToTestFile, SharedYcmd +from ycmd.tests.go import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, LocationMatcher, PollForMessages, @@ -52,73 +53,69 @@ } -@WithRetry -@SharedYcmd -def Diagnostics_DetailedDiags_test( app ): - filepath = PathToTestFile( 'goto.go' ) - contents = ReadFile( filepath ) - WaitForDiagnosticsToBeReady( app, filepath, contents, 'go' ) - request_data = BuildRequest( contents = contents, - filepath = filepath, - filetype = 'go', - line_num = 12, - column_num = 5 ) - - results = app.post_json( '/detailed_diagnostic', request_data ).json - assert_that( results, - has_entry( 'message', 'undeclared name: diagnostics_test' ) ) - - -@WithRetry -@SharedYcmd -def Diagnostics_FileReadyToParse_test( app ): - filepath = PathToTestFile( 'goto.go' ) - contents = ReadFile( filepath ) - - # It can take a while for the diagnostics to be ready. - results = WaitForDiagnosticsToBeReady( app, filepath, contents, 'go' ) - print( f'completer response: { pformat( results ) }' ) - - assert_that( results, DIAG_MATCHERS_PER_FILE[ filepath ] ) - - -@WithRetry -@SharedYcmd -def Diagnostics_Poll_test( app ): - filepath = PathToTestFile( 'goto.go' ) - contents = ReadFile( filepath ) - - # Poll until we receive _all_ the diags asynchronously. - to_see = sorted( DIAG_MATCHERS_PER_FILE.keys() ) - seen = {} - - try: - for message in PollForMessages( app, - { 'filepath': filepath, - 'contents': contents, - 'filetype': 'go' } ): - if 'diagnostics' in message: - if message[ 'filepath' ] not in DIAG_MATCHERS_PER_FILE: - continue - seen[ message[ 'filepath' ] ] = True - assert_that( message, has_entries( { - 'diagnostics': DIAG_MATCHERS_PER_FILE[ message[ 'filepath' ] ], - 'filepath': message[ 'filepath' ] - } ) ) - - if sorted( seen.keys() ) == to_see: - break - - # Eventually PollForMessages will throw a timeout exception and we'll fail - # if we don't see all of the expected diags. - except PollForMessagesTimeoutException as e: - raise AssertionError( - str( e ) + - 'Timed out waiting for full set of diagnostics. ' - f'Expected to see diags for { json.dumps( to_see, indent = 2 ) }, ' - f'but only saw { json.dumps( sorted( seen.keys() ), indent = 2 ) }.' ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class DiagnosticsTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_Diagnostics_DetailedDiags( self, app ): + filepath = PathToTestFile( 'goto.go' ) + contents = ReadFile( filepath ) + WaitForDiagnosticsToBeReady( app, filepath, contents, 'go' ) + request_data = BuildRequest( contents = contents, + filepath = filepath, + filetype = 'go', + line_num = 12, + column_num = 5 ) + + results = app.post_json( '/detailed_diagnostic', request_data ).json + assert_that( results, + has_entry( 'message', 'undeclared name: diagnostics_test' ) ) + + + @WithRetry() + @SharedYcmd + def test_Diagnostics_FileReadyToParse( self, app ): + filepath = PathToTestFile( 'goto.go' ) + contents = ReadFile( filepath ) + + # It can take a while for the diagnostics to be ready. + results = WaitForDiagnosticsToBeReady( app, filepath, contents, 'go' ) + print( f'completer response: { pformat( results ) }' ) + + assert_that( results, DIAG_MATCHERS_PER_FILE[ filepath ] ) + + + @WithRetry() + @SharedYcmd + def test_Diagnostics_Poll( self, app ): + filepath = PathToTestFile( 'goto.go' ) + contents = ReadFile( filepath ) + + # Poll until we receive _all_ the diags asynchronously. + to_see = sorted( DIAG_MATCHERS_PER_FILE.keys() ) + seen = {} + + try: + for message in PollForMessages( app, + { 'filepath': filepath, + 'contents': contents, + 'filetype': 'go' } ): + if 'diagnostics' in message: + if message[ 'filepath' ] not in DIAG_MATCHERS_PER_FILE: + continue + seen[ message[ 'filepath' ] ] = True + assert_that( message, has_entries( { + 'diagnostics': DIAG_MATCHERS_PER_FILE[ message[ 'filepath' ] ], + 'filepath': message[ 'filepath' ] + } ) ) + + if sorted( seen.keys() ) == to_see: + break + + # Eventually PollForMessages will throw a timeout exception and we'll fail + # if we don't see all of the expected diags. + except PollForMessagesTimeoutException as e: + raise AssertionError( + str( e ) + + 'Timed out waiting for full set of diagnostics. ' + f'Expected to see diags for { json.dumps( to_see, indent = 2 ) }, ' + f'but only saw { json.dumps( sorted( seen.keys() ), indent = 2 ) }.' ) diff --git a/ycmd/tests/go/get_completions_test.py b/ycmd/tests/go/get_completions_test.py index f8a79440ad..0b998bb892 100644 --- a/ycmd/tests/go/get_completions_test.py +++ b/ycmd/tests/go/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -20,61 +20,58 @@ has_items, has_key, is_not ) +from unittest import TestCase -from ycmd.tests.go import PathToTestFile, SharedYcmd +from ycmd.tests.go import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, CompletionEntryMatcher, WithRetry ) from ycmd.utils import ReadFile -@WithRetry( reruns = 100 ) -@SharedYcmd -def GetCompletions_Basic_test( app ): - filepath = PathToTestFile( 'td', 'test.go' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'go', - contents = ReadFile( filepath ), - force_semantic = True, - line_num = 10, - column_num = 9 ) +class GetCompletionsTest( TestCase ): + @WithRetry( reruns = 100 ) + @SharedYcmd + def test_GetCompletions_Basic( self, app ): + filepath = PathToTestFile( 'td', 'test.go' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'go', + contents = ReadFile( filepath ), + force_semantic = True, + line_num = 10, + column_num = 9 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - all_of( - has_items( - CompletionEntryMatcher( - 'Llongfile', - 'int', - { - 'detailed_info': 'Llongfile\n\n' - 'These flags define which text to' - ' prefix to each log entry generated' - ' by the Logger.', - 'menu_text': 'Llongfile', - 'kind': 'Constant', - } - ), - CompletionEntryMatcher( - 'Logger', - 'struct{...}', - { - 'detailed_info': 'Logger\n\n' - 'A Logger represents an active logging' - ' object that generates lines of output' - ' to an io.Writer.', - 'menu_text': 'Logger', - 'kind': 'Struct', - } - ) ) ) ) + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, + all_of( + has_items( + CompletionEntryMatcher( + 'Llongfile', + 'int', + { + 'detailed_info': 'Llongfile\n\n' + 'These flags define which text to' + ' prefix to each log entry generated' + ' by the Logger.', + 'menu_text': 'Llongfile', + 'kind': 'Constant', + } + ), + CompletionEntryMatcher( + 'Logger', + 'struct{...}', + { + 'detailed_info': 'Logger\n\n' + 'A Logger represents an active ' + 'logging object that generates lines ' + 'of output to an io.Writer.', + 'menu_text': 'Logger', + 'kind': 'Struct', + } + ) ) ) ) - # This completer does not require or support resolve - assert_that( results[ 0 ], is_not( has_key( 'resolve' ) ) ) - assert_that( results[ 0 ], is_not( has_key( 'item' ) ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + # This completer does not require or support resolve + assert_that( results[ 0 ], is_not( has_key( 'resolve' ) ) ) + assert_that( results[ 0 ], is_not( has_key( 'item' ) ) ) diff --git a/ycmd/tests/go/go_completer_test.py b/ycmd/tests/go/go_completer_test.py index c934e12b44..d742a7e50a 100644 --- a/ycmd/tests/go/go_completer_test.py +++ b/ycmd/tests/go/go_completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -16,29 +16,28 @@ # along with ycmd. If not, see . from unittest.mock import patch +from unittest import TestCase from hamcrest import assert_that, equal_to from ycmd import user_options_store from ycmd.completers.go.hook import GetCompleter +from ycmd.tests.go import setUpModule, tearDownModule # noqa -def GetCompleter_GoplsFound_test(): - assert_that( GetCompleter( user_options_store.GetAll() ) ) +class GoCompleterTest( TestCase ): + def test_GetCompleter_GoplsFound( self ): + assert_that( GetCompleter( user_options_store.GetAll() ) ) -@patch( 'ycmd.completers.go.go_completer.PATH_TO_GOPLS', None ) -def GetCompleter_GoplsNotFound_test( *args ): - assert_that( not GetCompleter( user_options_store.GetAll() ) ) + @patch( 'ycmd.completers.go.go_completer.PATH_TO_GOPLS', None ) + def test_GetCompleter_GoplsNotFound( self, *args ): + assert_that( not GetCompleter( user_options_store.GetAll() ) ) -@patch( 'ycmd.utils.FindExecutableWithFallback', - wraps = lambda x, fb: x if x == 'gopls' else None ) -@patch( 'os.path.isfile', return_value = True ) -def GetCompleter_GoplsFromUserOption_test( *args ): - user_options = user_options_store.GetAll().copy( gopls_binary_path = 'gopls' ) - assert_that( GetCompleter( user_options )._gopls_path, equal_to( 'gopls' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @patch( 'ycmd.utils.FindExecutableWithFallback', + wraps = lambda x, fb: x if x == 'gopls' else None ) + @patch( 'os.path.isfile', return_value = True ) + def test_GetCompleter_GoplsFromUserOption( self, *args ): + user_options = user_options_store.GetAll().copy( + gopls_binary_path = 'gopls' ) + assert_that( GetCompleter( user_options )._gopls_path, equal_to( 'gopls' ) ) diff --git a/ycmd/tests/go/server_management_test.py b/ycmd/tests/go/server_management_test.py index e55c4288ba..7aa8faf22a 100644 --- a/ycmd/tests/go/server_management_test.py +++ b/ycmd/tests/go/server_management_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -17,6 +17,7 @@ from hamcrest import assert_that, contains_exactly, equal_to, has_entry from unittest.mock import patch +from unittest import TestCase from ycmd.completers.language_server.language_server_completer import ( LanguageServerConnectionTimeout ) @@ -39,92 +40,42 @@ def AssertGoCompleterServerIsRunning( app, is_running ): ) ) -@IsolatedYcmd -def ServerManagement_RestartServer_test( app ): - filepath = PathToTestFile( 'goto.go' ) - StartGoCompleterServerInDirectory( app, filepath ) +class ServerManagementTest( TestCase ): + @IsolatedYcmd() + def test_ServerManagement_RestartServer( self, app ): + filepath = PathToTestFile( 'goto.go' ) + StartGoCompleterServerInDirectory( app, filepath ) - AssertGoCompleterServerIsRunning( app, True ) + AssertGoCompleterServerIsRunning( app, True ) - app.post_json( - '/run_completer_command', - BuildRequest( - filepath = filepath, - filetype = 'go', - command_arguments = [ 'RestartServer' ], - ), - ) + app.post_json( + '/run_completer_command', + BuildRequest( + filepath = filepath, + filetype = 'go', + command_arguments = [ 'RestartServer' ], + ), + ) - WaitUntilCompleterServerReady( app, 'go' ) + WaitUntilCompleterServerReady( app, 'go' ) - AssertGoCompleterServerIsRunning( app, True ) + AssertGoCompleterServerIsRunning( app, True ) -@IsolatedYcmd -@patch( 'shutil.rmtree', side_effect = OSError ) -@patch( 'ycmd.utils.WaitUntilProcessIsTerminated', - MockProcessTerminationTimingOut ) -def ServerManagement_CloseServer_Unclean_test( rm_tree, app ): - StartGoCompleterServerInDirectory( app, PathToTestFile() ) + @IsolatedYcmd() + @patch( 'shutil.rmtree', side_effect = OSError ) + @patch( 'ycmd.utils.WaitUntilProcessIsTerminated', + MockProcessTerminationTimingOut ) + def test_ServerManagement_CloseServer_Unclean( self, app, *args ): + StartGoCompleterServerInDirectory( app, PathToTestFile() ) - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'go', - command_arguments = [ 'StopServer' ] + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'go', + command_arguments = [ 'StopServer' ] + ) ) - ) - - request_data = BuildRequest( filetype = 'go' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) - ) ) - ) ) - - -@IsolatedYcmd -def ServerManagement_StopServerTwice_test( app ): - StartGoCompleterServerInDirectory( app, PathToTestFile() ) - - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'go', - command_arguments = [ 'StopServer' ], - ), - ) - - AssertGoCompleterServerIsRunning( app, False ) - - # Stopping a stopped server is a no-op - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'go', - command_arguments = [ 'StopServer' ], - ), - ) - - AssertGoCompleterServerIsRunning( app, False ) - - -@IsolatedYcmd -def ServerManagement_StartServer_Fails_test( app ): - with patch( 'ycmd.completers.language_server.language_server_completer.' - 'LanguageServerConnection.AwaitServerConnection', - side_effect = LanguageServerConnectionTimeout ): - resp = app.post_json( '/event_notification', - BuildRequest( - event_name = 'FileReadyToParse', - filetype = 'go', - filepath = PathToTestFile( 'goto.go' ), - contents = "" - ) ) - - assert_that( resp.status_code, equal_to( 200 ) ) request_data = BuildRequest( filetype = 'go' ) assert_that( app.post_json( '/debug_info', request_data ).json, @@ -136,6 +87,52 @@ def ServerManagement_StartServer_Fails_test( app ): ) ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd() + def test_ServerManagement_StopServerTwice( self, app ): + StartGoCompleterServerInDirectory( app, PathToTestFile() ) + + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'go', + command_arguments = [ 'StopServer' ], + ), + ) + + AssertGoCompleterServerIsRunning( app, False ) + + # Stopping a stopped server is a no-op + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'go', + command_arguments = [ 'StopServer' ], + ), + ) + + AssertGoCompleterServerIsRunning( app, False ) + + + @IsolatedYcmd + def test_ServerManagement_StartServer_Fails( self, app ): + with patch( 'ycmd.completers.language_server.language_server_completer.' + 'LanguageServerConnection.AwaitServerConnection', + side_effect = LanguageServerConnectionTimeout ): + resp = app.post_json( '/event_notification', + BuildRequest( + event_name = 'FileReadyToParse', + filetype = 'go', + filepath = PathToTestFile( 'goto.go' ), + contents = "" + ) ) + + assert_that( resp.status_code, equal_to( 200 ) ) + + request_data = BuildRequest( filetype = 'go' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) + ) ) diff --git a/ycmd/tests/go/signature_help_test.py b/ycmd/tests/go/signature_help_test.py index f46db721e3..fdb50b0286 100644 --- a/ycmd/tests/go/signature_help_test.py +++ b/ycmd/tests/go/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -16,10 +16,11 @@ # along with ycmd. If not, see . from hamcrest import assert_that, contains_exactly, empty, equal_to, has_entries +from unittest import TestCase import requests from ycmd.utils import ReadFile -from ycmd.tests.go import PathToTestFile, SharedYcmd +from ycmd.tests.go import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( CombineRequest, ParameterMatcher, SignatureMatcher, @@ -68,100 +69,96 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def SignatureHelp_NoParams_test( app ): - RunTest( app, { - 'description': 'Trigger after (', - 'request': { - 'filetype' : 'go', - 'filepath' : PathToTestFile( 'goto.go' ), - 'line_num' : 8, - 'column_num': 11, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'dummy()', [] ) - ), - } ), - } ) - } - } ) - - -@SharedYcmd -def SignatureHelp_NullResponse_test( app ): - RunTest( app, { - 'description': 'No error on null response', - 'request': { - 'filetype' : 'go', - 'filepath' : PathToTestFile( 'td', 'signature_help.go' ), - 'line_num' : 11, - 'column_num': 17, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty(), - } ), - } ) - } - } ) - - -@SharedYcmd -def SignatureHelp_MethodTrigger_test( app ): - RunTest( app, { - 'description': 'Trigger after (', - 'request': { - 'filetype' : 'go', - 'filepath' : PathToTestFile( 'td', 'signature_help.go' ), - 'line_num' : 10, - 'column_num': 18, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'add(x int, y int) int', - [ ParameterMatcher( 4, 9 ), - ParameterMatcher( 11, 16 ) ] ) - ), - } ), - } ) - } - } ) - - -@SharedYcmd -def Signature_Help_Available_test( app ): - request = { 'filepath' : PathToTestFile( 'td', 'signature_help.go' ) } - app.post_json( '/event_notification', - CombineRequest( request, { - 'event_name': 'FileReadyToParse', - 'filetype': 'go' - } ), - expect_errors = True ) - WaitUntilCompleterServerReady( app, 'go' ) - - response = app.get( '/signature_help_available', - { 'subserver': 'go' } ).json - assert_that( response, SignatureAvailableMatcher( 'YES' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class SignatureHelpTest( TestCase ): + @SharedYcmd + def test_SignatureHelp_NoParams( self, app ): + RunTest( app, { + 'description': 'Trigger after (', + 'request': { + 'filetype' : 'go', + 'filepath' : PathToTestFile( 'goto.go' ), + 'line_num' : 8, + 'column_num': 11, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'dummy()', [] ) + ), + } ), + } ) + } + } ) + + + @SharedYcmd + def test_SignatureHelp_NullResponse( self, app ): + RunTest( app, { + 'description': 'No error on null response', + 'request': { + 'filetype' : 'go', + 'filepath' : PathToTestFile( 'td', 'signature_help.go' ), + 'line_num' : 11, + 'column_num': 17, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty(), + } ), + } ) + } + } ) + + + @SharedYcmd + def test_SignatureHelp_MethodTrigger( self, app ): + RunTest( app, { + 'description': 'Trigger after (', + 'request': { + 'filetype' : 'go', + 'filepath' : PathToTestFile( 'td', 'signature_help.go' ), + 'line_num' : 10, + 'column_num': 18, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'add(x int, y int) int', + [ ParameterMatcher( 4, 9 ), + ParameterMatcher( 11, 16 ) ] ) + ), + } ), + } ) + } + } ) + + + @SharedYcmd + def test_Signature_Help_Available( self, app ): + request = { 'filepath' : PathToTestFile( 'td', 'signature_help.go' ) } + app.post_json( '/event_notification', + CombineRequest( request, { + 'event_name': 'FileReadyToParse', + 'filetype': 'go' + } ), + expect_errors = True ) + WaitUntilCompleterServerReady( app, 'go' ) + + response = app.get( '/signature_help_available', + { 'subserver': 'go' } ).json + assert_that( response, SignatureAvailableMatcher( 'YES' ) ) diff --git a/ycmd/tests/go/subcommands_test.py b/ycmd/tests/go/subcommands_test.py index ebc6010e11..73a19a3b93 100644 --- a/ycmd/tests/go/subcommands_test.py +++ b/ycmd/tests/go/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -24,18 +24,21 @@ has_entry, matches_regexp ) from unittest.mock import patch +from unittest import TestCase from pprint import pformat +import itertools import os -import pytest import requests from ycmd import handlers from ycmd.completers.language_server.language_server_completer import ( ResponseFailedException ) -from ycmd.tests.go import ( PathToTestFile, +from ycmd.tests.go import ( PathToTestFile, # noqa SharedYcmd, - StartGoCompleterServerInDirectory ) + StartGoCompleterServerInDirectory, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, ErrorMatcher, @@ -105,228 +108,6 @@ def RunFixItTest( app, description, filepath, line, col, fixits_for_line ): } ) -@SharedYcmd -def Subcommands_DefinedSubcommands_test( app ): - subcommands_data = BuildRequest( completer_target = 'go' ) - - assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_inanyorder( 'Format', - 'GetDoc', - 'GetType', - 'RefactorRename', - 'GoTo', - 'GoToDeclaration', - 'GoToDefinition', - 'GoToDocumentOutline', - 'GoToReferences', - 'GoToImplementation', - 'GoToType', - 'GoToSymbol', - 'FixIt', - 'RestartServer', - 'ExecuteCommand' ) ) - - -@SharedYcmd -def Subcommands_ServerNotInitialized_test( app ): - filepath = PathToTestFile( 'goto.go' ) - - completer = handlers._server_state.GetFiletypeCompleter( [ 'go' ] ) - - @patch.object( completer, '_ServerIsInitialized', return_value = False ) - def Test( app, cmd, arguments, *args ): - RunTest( app, { - 'description': 'Subcommand ' + cmd + ' handles server not ready', - 'request': { - 'command': cmd, - 'line_num': 1, - 'column_num': 1, - 'filepath': filepath, - 'arguments': arguments, - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, - 'Server is initializing. Please wait.' ), - } - } ) - - Test( app, 'Format', [] ) - Test( app, 'GetDoc', [] ) - Test( app, 'GetType', [] ) - Test( app, 'GoTo', [] ) - Test( app, 'GoToDeclaration', [] ) - Test( app, 'GoToDefinition', [] ) - Test( app, 'GoToType', [] ) - Test( app, 'FixIt', [] ) - - -@SharedYcmd -def Subcommands_Format_WholeFile_test( app ): - # RLS can't execute textDocument/formatting if any file - # under the project root has errors, so we need to use - # a different project just for formatting. - # For further details check https://github.com/go-lang/rls/issues/1397 - project_dir = PathToTestFile() - StartGoCompleterServerInDirectory( app, project_dir ) - - filepath = os.path.join( project_dir, 'goto.go' ) - - RunTest( app, { - 'description': 'Formatting is applied on the whole file', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 2, - 'insert_spaces': True - } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 5 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 8, 5 ), - LocationMatcher( filepath, 8, 5 ) ), - ChunkMatcher( '', - LocationMatcher( filepath, 12, 1 ), - LocationMatcher( filepath, 12, 5 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 12, 5 ), - LocationMatcher( filepath, 12, 5 ) ), - ) - } ) ) - } ) - } - } ) - - -@ExpectedFailure( 'rangeFormat is not yet implemented', - matches_regexp( '\nExpected: <200>\n but: was <500>\n' ) ) -@SharedYcmd -def Subcommands_Format_Range_test( app ): - project_dir = PathToTestFile() - StartGoCompleterServerInDirectory( app, project_dir ) - - filepath = os.path.join( project_dir, 'goto.go' ) - - RunTest( app, { - 'description': 'Formatting is applied on some part of the file', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'range': { - 'start': { - 'line_num': 7, - 'column_num': 1, - }, - 'end': { - 'line_num': 9, - 'column_num': 2 - } - }, - 'options': { - 'tab_size': 4, - 'insert_spaces': False - } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( 'fn unformatted_function(param: bool) -> bool {\n' - '\treturn param;\n' - '}\n' - '\n' - 'fn \n' - 'main()\n' - ' {\n' - ' unformatted_function( false );\n' - - '}\n', - LocationMatcher( filepath, 1, 1 ), - LocationMatcher( filepath, 9, 1 ) ), - ) - } ) ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GetDoc_UnknownType_test( app ): - RunTest( app, { - 'description': 'GetDoc on a unknown type raises an error', - 'request': { - 'command': 'GetDoc', - 'line_num': 2, - 'column_num': 4, - 'filepath': PathToTestFile( 'td', 'test.go' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'No documentation available.' ) - } - } ) - - -@SharedYcmd -def Subcommands_GetDoc_Function_test( app ): - RunTest( app, { - 'description': 'GetDoc on a function returns its type', - 'request': { - 'command': 'GetDoc', - 'line_num': 9, - 'column_num': 6, - 'filepath': PathToTestFile( 'td', 'test.go' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entry( 'detailed_info', 'func Hello()\nNow with doc!' ), - } - } ) - - -@SharedYcmd -def Subcommands_GetType_UnknownType_test( app ): - RunTest( app, { - 'description': 'GetType on a unknown type raises an error', - 'request': { - 'command': 'GetType', - 'line_num': 2, - 'column_num': 4, - 'filepath': PathToTestFile( 'td', 'test.go' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'Unknown type.' ) - } - } ) - - -@SharedYcmd -def Subcommands_GetType_Function_test( app ): - RunTest( app, { - 'description': 'GetType on a function returns its type', - 'request': { - 'command': 'GetType', - 'line_num': 9, - 'column_num': 6, - 'filepath': PathToTestFile( 'td', 'test.go' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entry( 'message', 'func Hello()' ), - } - } ) - - def RunGoToTest( app, command, test ): folder = PathToTestFile() filepath = PathToTestFile( test[ 'req' ][ 0 ] ) @@ -372,113 +153,334 @@ def RunGoToTest( app, command, test ): } ) -@pytest.mark.parametrize( 'command', [ 'GoToDeclaration', - 'GoToDefinition', - 'GoTo' ] ) -@pytest.mark.parametrize( 'test', [ - # Struct - { 'req': ( os.path.join( 'unicode', 'unicode.go' ), 13, 5 ), - 'res': ( os.path.join( 'unicode', 'unicode.go' ), 10, 5 ) }, - # Function - { 'req': ( 'goto.go', 8, 5 ), 'res': ( 'goto.go', 3, 6 ) }, - # Keyword - { 'req': ( 'goto.go', 3, 2 ), 'res': 'Cannot jump to location' }, - ] ) -@SharedYcmd -def Subcommands_GoTo_test( app, command, test ): - RunGoToTest( app, command, test ) - - -@pytest.mark.parametrize( 'test', [ - # Works - { 'req': ( os.path.join( 'unicode', 'unicode.go' ), 13, 5 ), - 'res': ( os.path.join( 'unicode', 'unicode.go' ), 3, 6 ) }, - # Fails - { 'req': ( os.path.join( 'unicode', 'unicode.go' ), 11, 7 ), - 'res': 'Cannot jump to location' } ] ) -@SharedYcmd -def Subcommands_GoToType_test( app, test ): - RunGoToTest( app, 'GoToType', test ) - - -@pytest.mark.parametrize( 'test', [ - # Works - { 'req': ( 'thing.go', 3, 8 ), - 'res': ( 'thing.go', 7, 6 ) }, - # Fails - { 'req': ( 'thing.go', 12, 7 ), - 'res': 'Cannot jump to location' } ] ) -@SharedYcmd -def Subcommands_GoToImplementation_test( app, test ): - RunGoToTest( app, 'GoToImplementation', test ) - - -@SharedYcmd -def Subcommands_FixIt_NullResponse_test( app ): - filepath = PathToTestFile( 'td', 'test.go' ) - RunFixItTest( app, - 'Gopls returned NULL for response[ \'result\' ]', - filepath, 1, 1, has_entry( 'fixits', empty() ) ) - - -@SharedYcmd -def Subcommands_FixIt_Simple_test( app ): - filepath = PathToTestFile( 'fixit.go' ) - fixit = has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': "Organize Imports", - 'chunks': contains_exactly( - ChunkMatcher( '', - LocationMatcher( filepath, 2, 1 ), - LocationMatcher( filepath, 3, 1 ) ), - ), - 'kind': 'source.organizeImports', - } ), - ) - } ) - RunFixItTest( app, 'Only one fixit returned', filepath, 1, 1, fixit ) +class SubcommandsTest( TestCase ): + @SharedYcmd + def test_Subcommands_DefinedSubcommands( self, app ): + subcommands_data = BuildRequest( completer_target = 'go' ) + + assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, + contains_inanyorder( 'Format', + 'GetDoc', + 'GetType', + 'RefactorRename', + 'GoTo', + 'GoToDeclaration', + 'GoToDefinition', + 'GoToDocumentOutline', + 'GoToReferences', + 'GoToImplementation', + 'GoToType', + 'GoToSymbol', + 'FixIt', + 'RestartServer', + 'ExecuteCommand' ) ) + + + @SharedYcmd + def test_Subcommands_ServerNotInitialized( self, app ): + filepath = PathToTestFile( 'goto.go' ) + + completer = handlers._server_state.GetFiletypeCompleter( [ 'go' ] ) + + @patch.object( completer, '_ServerIsInitialized', return_value = False ) + def Test( app, cmd, arguments, *args ): + RunTest( app, { + 'description': 'Subcommand ' + cmd + ' handles server not ready', + 'request': { + 'command': cmd, + 'line_num': 1, + 'column_num': 1, + 'filepath': filepath, + 'arguments': arguments, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, + 'Server is initializing. Please wait.' ), + } + } ) + Test( app, 'Format', [] ) + Test( app, 'GetDoc', [] ) + Test( app, 'GetType', [] ) + Test( app, 'GoTo', [] ) + Test( app, 'GoToDeclaration', [] ) + Test( app, 'GoToDefinition', [] ) + Test( app, 'GoToType', [] ) + Test( app, 'FixIt', [] ) -@SharedYcmd -def Subcommands_RefactorRename_test( app ): - filepath = PathToTestFile( 'unicode', 'unicode.go' ) - RunTest( app, { - 'description': 'RefactorRename on a function renames all its occurences', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'xxx' ], - 'line_num': 10, - 'column_num': 17, - 'filepath': filepath - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'text': '', + + @SharedYcmd + def test_Subcommands_Format_WholeFile( self, app ): + # RLS can't execute textDocument/formatting if any file + # under the project root has errors, so we need to use + # a different project just for formatting. + # For further details check https://github.com/go-lang/rls/issues/1397 + project_dir = PathToTestFile() + StartGoCompleterServerInDirectory( app, project_dir ) + + filepath = os.path.join( project_dir, 'goto.go' ) + + RunTest( app, { + 'description': 'Formatting is applied on the whole file', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 2, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 5 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 8, 5 ), + LocationMatcher( filepath, 8, 5 ) ), + ChunkMatcher( '', + LocationMatcher( filepath, 12, 1 ), + LocationMatcher( filepath, 12, 5 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 12, 5 ), + LocationMatcher( filepath, 12, 5 ) ), + ) + } ) ) + } ) + } + } ) + + + @ExpectedFailure( + 'rangeFormat is not yet implemented', + matches_regexp( '\nExpected: <200>\n but: was <500>\n' ) ) + @SharedYcmd + def test_Subcommands_Format_Range( self, app ): + project_dir = PathToTestFile() + StartGoCompleterServerInDirectory( app, project_dir ) + + filepath = os.path.join( project_dir, 'goto.go' ) + + RunTest( app, { + 'description': 'Formatting is applied on some part of the file', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'range': { + 'start': { + 'line_num': 7, + 'column_num': 1, + }, + 'end': { + 'line_num': 9, + 'column_num': 2 + } + }, + 'options': { + 'tab_size': 4, + 'insert_spaces': False + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( 'fn unformatted_function(param: bool) -> bool {\n' + '\treturn param;\n' + '}\n' + '\n' + 'fn \n' + 'main()\n' + ' {\n' + ' unformatted_function( false );\n' + + '}\n', + LocationMatcher( filepath, 1, 1 ), + LocationMatcher( filepath, 9, 1 ) ), + ) + } ) ) + } ) + } + } ) + + + @SharedYcmd + def test_Subcommands_GetDoc_UnknownType( self, app ): + RunTest( app, { + 'description': 'GetDoc on a unknown type raises an error', + 'request': { + 'command': 'GetDoc', + 'line_num': 2, + 'column_num': 4, + 'filepath': PathToTestFile( 'td', 'test.go' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, 'No documentation available.' ) + } + } ) + + + @SharedYcmd + def test_Subcommands_GetDoc_Function( self, app ): + RunTest( app, { + 'description': 'GetDoc on a function returns its type', + 'request': { + 'command': 'GetDoc', + 'line_num': 9, + 'column_num': 6, + 'filepath': PathToTestFile( 'td', 'test.go' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entry( 'detailed_info', 'func Hello()\nNow with doc!' ), + } + } ) + + + @SharedYcmd + def test_Subcommands_GetType_UnknownType( self, app ): + RunTest( app, { + 'description': 'GetType on a unknown type raises an error', + 'request': { + 'command': 'GetType', + 'line_num': 2, + 'column_num': 4, + 'filepath': PathToTestFile( 'td', 'test.go' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, 'Unknown type.' ) + } + } ) + + + @SharedYcmd + def test_Subcommands_GetType_Function( self, app ): + RunTest( app, { + 'description': 'GetType on a function returns its type', + 'request': { + 'command': 'GetType', + 'line_num': 9, + 'column_num': 6, + 'filepath': PathToTestFile( 'td', 'test.go' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entry( 'message', 'func Hello()' ), + } + } ) + + + @SharedYcmd + def test_Subcommands_GoTo( self, app ): + for command, test in itertools.product( + [ 'GoTo', 'GoToDeclaration', 'GoToDefinition' ], + [ + # Struct + { 'req': ( os.path.join( 'unicode', 'unicode.go' ), 13, 5 ), + 'res': ( os.path.join( 'unicode', 'unicode.go' ), 10, 5 ) }, + # Function + { 'req': ( 'goto.go', 8, 5 ), 'res': ( 'goto.go', 3, 6 ) }, + # Keyword + { 'req': ( 'goto.go', 3, 2 ), 'res': 'Cannot jump to location' }, + ] ): + with self.subTest( command = command, test = test ): + RunGoToTest( app, command, test ) + + + @SharedYcmd + def test_Subcommands_GoToType( self, app ): + for test in [ + # Works + { 'req': ( os.path.join( 'unicode', 'unicode.go' ), 13, 5 ), + 'res': ( os.path.join( 'unicode', 'unicode.go' ), 3, 6 ) }, + # Fails + { 'req': ( os.path.join( 'unicode', 'unicode.go' ), 11, 7 ), + 'res': 'Cannot jump to location' } ]: + with self.subTest( test = test ): + RunGoToTest( app, 'GoToType', test ) + + + @SharedYcmd + def test_Subcommands_GoToImplementation( self, app ): + for test in [ + # Works + { 'req': ( 'thing.go', 3, 8 ), + 'res': ( 'thing.go', 7, 6 ) }, + # Fails + { 'req': ( 'thing.go', 12, 7 ), + 'res': 'Cannot jump to location' } ]: + with self.subTest( test = test ): + RunGoToTest( app, 'GoToImplementation', test ) + + + @SharedYcmd + def test_Subcommands_FixIt_NullResponse( self, app ): + filepath = PathToTestFile( 'td', 'test.go' ) + RunFixItTest( app, + 'Gopls returned NULL for response[ \'result\' ]', + filepath, 1, 1, has_entry( 'fixits', empty() ) ) + + + @SharedYcmd + def test_Subcommands_FixIt_Simple( self, app ): + filepath = PathToTestFile( 'fixit.go' ) + fixit = has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': "Organize Imports", 'chunks': contains_exactly( - ChunkMatcher( 'xxx', - LocationMatcher( filepath, 3, 6 ), - LocationMatcher( filepath, 3, 10 ) ), - ChunkMatcher( 'xxx', - LocationMatcher( filepath, 10, 16 ), - LocationMatcher( filepath, 10, 20 ) ), - ) - } ) ) - } ) - } - } ) + ChunkMatcher( '', + LocationMatcher( filepath, 2, 1 ), + LocationMatcher( filepath, 3, 1 ) ), + ), + 'kind': 'source.organizeImports', + } ), + ) + } ) + RunFixItTest( app, 'Only one fixit returned', filepath, 1, 1, fixit ) -@SharedYcmd -def Subcommands_GoToReferences_test( app ): - filepath = os.path.join( 'unicode', 'unicode.go' ) - test = { 'req': ( filepath, 10, 5 ), 'res': [ ( filepath, 10, 5 ), - ( filepath, 13, 5 ) ] } - RunGoToTest( app, 'GoToReferences', test ) + @SharedYcmd + def test_Subcommands_RefactorRename( self, app ): + filepath = PathToTestFile( 'unicode', 'unicode.go' ) + RunTest( app, { + 'description': 'RefactorRename on a function renames all its occurences', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'xxx' ], + 'line_num': 10, + 'column_num': 17, + 'filepath': filepath + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( 'xxx', + LocationMatcher( filepath, 3, 6 ), + LocationMatcher( filepath, 3, 10 ) ), + ChunkMatcher( 'xxx', + LocationMatcher( filepath, 10, 16 ), + LocationMatcher( filepath, 10, 20 ) ), + ) + } ) ) + } ) + } + } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_Subcommands_GoToReferences( self, app ): + filepath = os.path.join( 'unicode', 'unicode.go' ) + test = { 'req': ( filepath, 10, 5 ), 'res': [ ( filepath, 10, 5 ), + ( filepath, 13, 5 ) ] } + RunGoToTest( app, 'GoToReferences', test ) diff --git a/ycmd/tests/hmac_utils_test.py b/ycmd/tests/hmac_utils_test.py index 6d6a7b6d81..894f78b92b 100644 --- a/ycmd/tests/hmac_utils_test.py +++ b/ycmd/tests/hmac_utils_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -18,56 +18,53 @@ from binascii import hexlify from hamcrest import raises, assert_that, calling, equal_to from ycmd import hmac_utils as hu +from unittest import TestCase -def CreateHmac_ArgsNotBytes_test(): - assert_that( calling( hu.CreateHmac ).with_args( u'foo', bytes( b'foo' ) ), - raises( TypeError, '.*content*' ) ) - assert_that( calling( hu.CreateHmac ).with_args( bytes( b'foo' ), u'foo' ), - raises( TypeError, '.*hmac_secret*' ) ) +class HmacUtilsTest( TestCase ): + def test_CreateHmac_ArgsNotBytes( self ): + assert_that( calling( hu.CreateHmac ).with_args( u'foo', bytes( b'foo' ) ), + raises( TypeError, '.*content*' ) ) + assert_that( calling( hu.CreateHmac ).with_args( bytes( b'foo' ), u'foo' ), + raises( TypeError, '.*hmac_secret*' ) ) -def CreateHmac_WithBytes_test(): - # Test vectors from Wikipedia (HMAC_SHA256): https://goo.gl/cvX0Tn - assert_that( hexlify( hu.CreateHmac( - bytes( b'The quick brown fox jumps over the lazy dog' ), - bytes( b'key' ) ) ), - equal_to( bytes( b'f7bc83f430538424b13298e6aa6fb143' - b'ef4d59a14946175997479dbc2d1a3cd8' ) ) ) + def test_CreateHmac_WithBytes( self ): + # Test vectors from Wikipedia (HMAC_SHA256): https://goo.gl/cvX0Tn + assert_that( hexlify( hu.CreateHmac( + bytes( b'The quick brown fox jumps over the lazy dog' ), + bytes( b'key' ) ) ), + equal_to( bytes( b'f7bc83f430538424b13298e6aa6fb143' + b'ef4d59a14946175997479dbc2d1a3cd8' ) ) ) -def CreateRequestHmac_ArgsNotBytes_test(): - assert_that( - calling( hu.CreateRequestHmac ).with_args( - u'foo', bytes( b'foo' ), bytes( b'foo' ), bytes( b'foo' ) ), - raises( TypeError, '.*method*' ) ) + def test_CreateRequestHmac_ArgsNotBytes( self ): + assert_that( + calling( hu.CreateRequestHmac ).with_args( + u'foo', bytes( b'foo' ), bytes( b'foo' ), bytes( b'foo' ) ), + raises( TypeError, '.*method*' ) ) - assert_that( - calling( hu.CreateRequestHmac ).with_args( - bytes( b'foo' ), u'foo', bytes( b'foo' ), bytes( b'foo' ) ), - raises( TypeError, '.*path*' ) ) + assert_that( + calling( hu.CreateRequestHmac ).with_args( + bytes( b'foo' ), u'foo', bytes( b'foo' ), bytes( b'foo' ) ), + raises( TypeError, '.*path*' ) ) - assert_that( - calling( hu.CreateRequestHmac ).with_args( - bytes( b'foo' ), bytes( b'foo' ), u'foo', bytes( b'foo' ) ), - raises( TypeError, '.*body*' ) ) + assert_that( + calling( hu.CreateRequestHmac ).with_args( + bytes( b'foo' ), bytes( b'foo' ), u'foo', bytes( b'foo' ) ), + raises( TypeError, '.*body*' ) ) - assert_that( - calling( hu.CreateRequestHmac ).with_args( - bytes( b'foo' ), bytes( b'foo' ), bytes( b'foo' ), u'foo' ), - raises( TypeError, '.*hmac_secret*' ) ) + assert_that( + calling( hu.CreateRequestHmac ).with_args( + bytes( b'foo' ), bytes( b'foo' ), bytes( b'foo' ), u'foo' ), + raises( TypeError, '.*hmac_secret*' ) ) -def CreateRequestHmac_WithBytes_test(): - assert_that( hexlify( hu.CreateRequestHmac( - bytes( b'GET' ), - bytes( b'/foo' ), - bytes( b'body' ), - bytes( b'key' ) ) ), - equal_to( bytes( b'bfbb6bc7a2b3eca2a78f4e7ec8a7dfa7' - b'e58bb8974166eaf20e0224d999894b34' ) ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + def test_CreateRequestHmac_WithBytes( self ): + assert_that( hexlify( hu.CreateRequestHmac( + bytes( b'GET' ), + bytes( b'/foo' ), + bytes( b'body' ), + bytes( b'key' ) ) ), + equal_to( bytes( b'bfbb6bc7a2b3eca2a78f4e7ec8a7dfa7' + b'e58bb8974166eaf20e0224d999894b34' ) ) ) diff --git a/ycmd/tests/identifier_completer_test.py b/ycmd/tests/identifier_completer_test.py index 99eaaf9dd2..c1c0c020ab 100644 --- a/ycmd/tests/identifier_completer_test.py +++ b/ycmd/tests/identifier_completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -17,6 +17,7 @@ import os from hamcrest import assert_that, empty, equal_to, contains_exactly +from unittest import TestCase from ycmd.user_options_store import DefaultOptions from ycmd.completers.all import identifier_completer as ic from ycmd.completers.all.identifier_completer import IdentifierCompleter @@ -31,279 +32,276 @@ def BuildRequestWrap( contents, column_num, line_num = 1 ): contents = contents ) ) -def GetCursorIdentifier_StartOfLine_test(): - assert_that( 'foo', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'foo', - 1 ) ) ) ) - assert_that( 'fooBar', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'fooBar', - 1 ) ) ) ) +class IdentifierCompleterTest( TestCase ): + def test_GetCursorIdentifier_StartOfLine( self ): + assert_that( 'foo', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'foo', + 1 ) ) ) ) + assert_that( 'fooBar', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'fooBar', + 1 ) ) ) ) -def GetCursorIdentifier_EndOfLine_test(): - assert_that( 'foo', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'foo', - 3 ) ) ) ) + def test_GetCursorIdentifier_EndOfLine( self ): + assert_that( 'foo', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'foo', + 3 ) ) ) ) -def GetCursorIdentifier_PastEndOfLine_test(): - assert_that( '', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'foo', - 11 ) ) ) ) + def test_GetCursorIdentifier_PastEndOfLine( self ): + assert_that( '', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'foo', + 11 ) ) ) ) -def GetCursorIdentifier_NegativeColumn_test(): - assert_that( 'foo', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'foo', - -10 ) ) ) ) + def test_GetCursorIdentifier_NegativeColumn( self ): + assert_that( 'foo', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'foo', + -10 ) ) ) ) -def GetCursorIdentifier_StartOfLine_StopsAtNonIdentifierChar_test(): - assert_that( 'foo', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'foo(goo)', - 1 ) ) ) ) + def test_GetCursorIdentifier_StartOfLine_StopsAtNonIdentifierChar( self ): + assert_that( 'foo', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'foo(goo)', + 1 ) ) ) ) -def GetCursorIdentifier_AtNonIdentifier_test(): - assert_that( 'goo', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'foo(goo)', - 4 ) ) ) ) + def test_GetCursorIdentifier_AtNonIdentifier( self ): + assert_that( 'goo', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'foo(goo)', + 4 ) ) ) ) -def GetCursorIdentifier_WalksForwardForIdentifier_test(): - assert_that( 'foo', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( ' foo', - 1 ) ) ) ) + def test_GetCursorIdentifier_WalksForwardForIdentifier( self ): + assert_that( 'foo', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( ' foo', + 1 ) ) ) ) -def GetCursorIdentifier_FindsNothingForward_test(): - assert_that( '', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'foo ()***()', - 5 ) ) ) ) + def test_GetCursorIdentifier_FindsNothingForward( self ): + assert_that( '', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'foo ()***()', + 5 ) ) ) ) -def GetCursorIdentifier_SingleCharIdentifier_test(): - assert_that( 'f', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( ' f ', - 1 ) ) ) ) + def test_GetCursorIdentifier_SingleCharIdentifier( self ): + assert_that( 'f', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( ' f ', + 1 ) ) ) ) -def GetCursorIdentifier_StartsInMiddleOfIdentifier_test(): - assert_that( 'foobar', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( 'foobar', - 4 ) ) ) ) + def test_GetCursorIdentifier_StartsInMiddleOfIdentifier( self ): + assert_that( 'foobar', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( 'foobar', + 4 ) ) ) ) -def GetCursorIdentifier_LineEmpty_test(): - assert_that( '', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( '', - 12 ) ) ) ) + def test_GetCursorIdentifier_LineEmpty( self ): + assert_that( '', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( '', + 12 ) ) ) ) -def GetCursorIdentifier_IgnoreIdentifierFromCommentsAndStrings_test(): - assert_that( '', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( '"foobar"', - 4 ) ) ) ) - assert_that( '', equal_to( - ic._GetCursorIdentifier( False, - BuildRequestWrap( '/*\n' ' * foobar\n' ' */', - 5, - 2 ) ) ) ) + def test_GetCursorIdentifier_IgnoreIdentifierFromCommentsAndStrings( self ): + assert_that( '', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( '"foobar"', + 4 ) ) ) ) + assert_that( '', equal_to( + ic._GetCursorIdentifier( False, + BuildRequestWrap( '/*\n' ' * foobar\n' ' */', + 5, + 2 ) ) ) ) -def GetCursorIdentifier_CollectIdentifierFromCommentsAndStrings_test(): - assert_that( 'foobar', equal_to( - ic._GetCursorIdentifier( True, - BuildRequestWrap( '"foobar"', - 4 ) ) ) ) - assert_that( 'foobar', equal_to( - ic._GetCursorIdentifier( True, - BuildRequestWrap( '/*\n' ' * foobar\n' ' */', - 5, - 2 ) ) ) ) - - -def PreviousIdentifier_Simple_test(): - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foo', - 4 ) ) ) ) - - -def PreviousIdentifier_WholeIdentShouldBeBeforeColumn_test(): - assert_that( '', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foobar', - column_num = 4 ) ) ) ) - - -def PreviousIdentifier_DoNotWrap_test(): - assert_that( '', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foobar\n bar', - column_num = 4 ) ) ) ) - - -def PreviousIdentifier_IgnoreForwardIdents_test(): - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foo bar zoo', - 4 ) ) ) ) - - -def PreviousIdentifier_IgnoreTooSmallIdent_test(): - assert_that( '', equal_to( - ic._PreviousIdentifier( 4, - False, - BuildRequestWrap( 'foo', - 4 ) ) ) ) - - -def PreviousIdentifier_IgnoreTooSmallIdent_DontContinueLooking_test(): - assert_that( '', equal_to( - ic._PreviousIdentifier( 4, - False, - BuildRequestWrap( 'abcde foo', - 10 ) ) ) ) - - -def PreviousIdentifier_WhitespaceAfterIdent_test(): - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foo ', - 6 ) ) ) ) - - -def PreviousIdentifier_JunkAfterIdent_test(): - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foo ;;()** ', - 13 ) ) ) ) - - -def PreviousIdentifier_IdentInMiddleOfJunk_test(): - assert_that( 'aa', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foo ;;(aa)** ', - 13 ) ) ) ) - - -def PreviousIdentifier_IdentOnPreviousLine_test(): - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foo\n ', - column_num = 3, - line_num = 2 ) ) ) ) - - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foo\n', - column_num = 1, - line_num = 2 ) ) ) ) - - -def PreviousIdentifier_IdentOnPreviousLine_JunkAfterIdent_test(): - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( 'foo **;()\n ', - column_num = 3, - line_num = 2 ) ) ) ) - - -def PreviousIdentifier_NoGoodIdentFound_test(): - assert_that( '', equal_to( - ic._PreviousIdentifier( 5, - False, - BuildRequestWrap( 'foo\n ', - column_num = 2, - line_num = 2 ) ) ) ) - - -def PreviousIdentifier_IgnoreIdentifierFromCommentsAndStrings_test(): - assert_that( '', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( '"foo"\n', - column_num = 1, - line_num = 2 ) ) ) ) - assert_that( '', equal_to( - ic._PreviousIdentifier( 2, - False, - BuildRequestWrap( '/*\n' ' * foo\n' ' */', - column_num = 2, - line_num = 3 ) ) ) ) - - -def PreviousIdentifier_CollectIdentifierFromCommentsAndStrings_test(): - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - True, - BuildRequestWrap( '"foo"\n', - column_num = 1, - line_num = 2 ) ) ) ) - assert_that( 'foo', equal_to( - ic._PreviousIdentifier( 2, - True, - BuildRequestWrap( '/*\n' ' * foo\n' ' */', - column_num = 2, - line_num = 3 ) ) ) ) - - -def FilterUnchangedTagFiles_NoFiles_test(): - ident_completer = IdentifierCompleter( DefaultOptions() ) - assert_that( list( ident_completer._FilterUnchangedTagFiles( [] ) ), - empty() ) - - -def FilterUnchangedTagFiles_SkipBadFiles_test(): - ident_completer = IdentifierCompleter( DefaultOptions() ) - assert_that( list( ident_completer._FilterUnchangedTagFiles( - [ '/some/tags' ] ) ), - empty() ) - - -def FilterUnchangedTagFiles_KeepGoodFiles_test(): - ident_completer = IdentifierCompleter( DefaultOptions() ) - tag_file = PathToTestFile( 'basic.tags' ) - assert_that( ident_completer._FilterUnchangedTagFiles( [ tag_file ] ), - contains_exactly( tag_file ) ) - - -def FilterUnchangedTagFiles_SkipUnchangesFiles_test(): - ident_completer = IdentifierCompleter( DefaultOptions() ) - - # simulate an already open tags file that didn't change in the meantime. - tag_file = PathToTestFile( 'basic.tags' ) - ident_completer._tags_file_last_mtime[ tag_file ] = os.path.getmtime( - tag_file ) - - assert_that( list( ident_completer._FilterUnchangedTagFiles( [ tag_file ] ) ), - empty() ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + def test_GetCursorIdentifier_CollectIdentifierFromCommentsAndStrings( self ): + assert_that( 'foobar', equal_to( + ic._GetCursorIdentifier( True, + BuildRequestWrap( '"foobar"', + 4 ) ) ) ) + assert_that( 'foobar', equal_to( + ic._GetCursorIdentifier( True, + BuildRequestWrap( '/*\n' ' * foobar\n' ' */', + 5, + 2 ) ) ) ) + + + def test_PreviousIdentifier_Simple( self ): + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foo', + 4 ) ) ) ) + + + def test_PreviousIdentifier_WholeIdentShouldBeBeforeColumn( self ): + assert_that( '', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foobar', + column_num = 4 ) ) ) ) + + + def test_PreviousIdentifier_DoNotWrap( self ): + assert_that( '', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foobar\n bar', + column_num = 4 ) ) ) ) + + + def test_PreviousIdentifier_IgnoreForwardIdents( self ): + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foo bar zoo', + 4 ) ) ) ) + + + def test_PreviousIdentifier_IgnoreTooSmallIdent( self ): + assert_that( '', equal_to( + ic._PreviousIdentifier( 4, + False, + BuildRequestWrap( 'foo', + 4 ) ) ) ) + + + def test_PreviousIdentifier_IgnoreTooSmallIdent_DontContinueLooking( self ): + assert_that( '', equal_to( + ic._PreviousIdentifier( 4, + False, + BuildRequestWrap( 'abcde foo', + 10 ) ) ) ) + + + def test_PreviousIdentifier_WhitespaceAfterIdent( self ): + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foo ', + 6 ) ) ) ) + + + def test_PreviousIdentifier_JunkAfterIdent( self ): + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foo ;;()** ', + 13 ) ) ) ) + + + def test_PreviousIdentifier_IdentInMiddleOfJunk( self ): + assert_that( 'aa', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foo ;;(aa)** ', + 13 ) ) ) ) + + + def test_PreviousIdentifier_IdentOnPreviousLine( self ): + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foo\n ', + column_num = 3, + line_num = 2 ) ) ) ) + + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foo\n', + column_num = 1, + line_num = 2 ) ) ) ) + + + def test_PreviousIdentifier_IdentOnPreviousLine_JunkAfterIdent( self ): + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( 'foo **;()\n ', + column_num = 3, + line_num = 2 ) ) ) ) + + + def test_PreviousIdentifier_NoGoodIdentFound( self ): + assert_that( '', equal_to( + ic._PreviousIdentifier( 5, + False, + BuildRequestWrap( 'foo\n ', + column_num = 2, + line_num = 2 ) ) ) ) + + + def test_PreviousIdentifier_IgnoreIdentifierFromCommentsAndStrings( self ): + assert_that( '', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( '"foo"\n', + column_num = 1, + line_num = 2 ) ) ) ) + assert_that( '', equal_to( + ic._PreviousIdentifier( 2, + False, + BuildRequestWrap( '/*\n' ' * foo\n' ' */', + column_num = 2, + line_num = 3 ) ) ) ) + + + def test_PreviousIdentifier_CollectIdentifierFromCommentsAndStrings( self ): + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + True, + BuildRequestWrap( '"foo"\n', + column_num = 1, + line_num = 2 ) ) ) ) + assert_that( 'foo', equal_to( + ic._PreviousIdentifier( 2, + True, + BuildRequestWrap( '/*\n' ' * foo\n' ' */', + column_num = 2, + line_num = 3 ) ) ) ) + + + def test_FilterUnchangedTagFiles_NoFiles( self ): + ident_completer = IdentifierCompleter( DefaultOptions() ) + assert_that( list( ident_completer._FilterUnchangedTagFiles( [] ) ), + empty() ) + + + def test_FilterUnchangedTagFiles_SkipBadFiles( self ): + ident_completer = IdentifierCompleter( DefaultOptions() ) + assert_that( list( ident_completer._FilterUnchangedTagFiles( + [ '/some/tags' ] ) ), + empty() ) + + + def test_FilterUnchangedTagFiles_KeepGoodFiles( self ): + ident_completer = IdentifierCompleter( DefaultOptions() ) + tag_file = PathToTestFile( 'basic.tags' ) + assert_that( ident_completer._FilterUnchangedTagFiles( [ tag_file ] ), + contains_exactly( tag_file ) ) + + + def test_FilterUnchangedTagFiles_SkipUnchangesFiles( self ): + ident_completer = IdentifierCompleter( DefaultOptions() ) + + # simulate an already open tags file that didn't change in the meantime. + tag_file = PathToTestFile( 'basic.tags' ) + ident_completer._tags_file_last_mtime[ tag_file ] = os.path.getmtime( + tag_file ) + + assert_that( + list( ident_completer._FilterUnchangedTagFiles( [ tag_file ] ) ), + empty() ) diff --git a/ycmd/tests/identifier_utils_test.py b/ycmd/tests/identifier_utils_test.py index 40727f4d51..1dc27e20ca 100644 --- a/ycmd/tests/identifier_utils_test.py +++ b/ycmd/tests/identifier_utils_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2020 ycmd contributors +# Copyright (C) 2013-2021 ycmd contributors # # This file is part of ycmd. # @@ -15,449 +15,446 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -import pytest from ycmd import identifier_utils as iu from hamcrest import assert_that, equal_to, has_item +from unittest import TestCase -def RemoveIdentifierFreeText_CppComments_test(): - assert_that( "foo \nbar \nqux", - equal_to( iu.RemoveIdentifierFreeText( - "foo \nbar //foo \nqux" ) ) ) - - -def RemoveIdentifierFreeText_PythonComments_test(): - assert_that( "foo \nbar \nqux", - equal_to( iu.RemoveIdentifierFreeText( - "foo \nbar #foo \nqux" ) ) ) - - -def RemoveIdentifierFreeText_CstyleComments_test(): - assert_that( "\n bar", - equal_to( iu.RemoveIdentifierFreeText( - "/* foo\n */ bar" ) ) ) - assert_that( "foo \nbar \nqux", - equal_to( iu.RemoveIdentifierFreeText( - "foo \nbar /* foo */\nqux" ) ) ) - assert_that( "foo \nbar \n\nqux", - equal_to( iu.RemoveIdentifierFreeText( - "foo \nbar /* foo \n foo2 */\nqux" ) ) ) - - -def RemoveIdentifierFreeText_SimpleSingleQuoteString_test(): - assert_that( "foo \nbar \nqux", - equal_to( iu.RemoveIdentifierFreeText( - "foo \nbar 'foo'\nqux" ) ) ) - - -def RemoveIdentifierFreeText_SimpleDoubleQuoteString_test(): - assert_that( "foo \nbar \nqux", - equal_to( iu.RemoveIdentifierFreeText( - 'foo \nbar "foo"\nqux' ) ) ) - - -def RemoveIdentifierFreeText_EscapedQuotes_test(): - assert_that( "foo \nbar \nqux", - equal_to( iu.RemoveIdentifierFreeText( - "foo \nbar 'fo\\'oz\\nfoo'\nqux" ) ) ) - assert_that( "foo \nbar \nqux", - equal_to( iu.RemoveIdentifierFreeText( - 'foo \nbar "fo\\"oz\\nfoo"\nqux' ) ) ) - +def LoopExpectIdentfierAtIndex( ident, index, expected ): + assert_that( expected, equal_to( iu.IdentifierAtIndex( ident, index ) ) ) -def RemoveIdentifierFreeText_SlashesInStrings_test(): - assert_that( "foo \nbar baz\nqux ", - equal_to( iu.RemoveIdentifierFreeText( - 'foo \nbar "fo\\\\"baz\nqux "qwe"' ) ) ) - assert_that( "foo \nbar \nqux ", - equal_to( iu.RemoveIdentifierFreeText( - "foo '\\\\'\nbar '\\\\'\nqux '\\\\'" ) ) ) +def LoopExpectLongestIdentifier( ident, expected, end_index ): + assert_that( expected, equal_to( + iu.StartOfLongestIdentifierEndingAtIndex( ident, end_index ) ) ) -def RemoveIdentifierFreeText_EscapedQuotesStartStrings_test(): - assert_that( "\\\"foo\\\" zoo", - equal_to( iu.RemoveIdentifierFreeText( - "\\\"foo\\\"'\"''bar' zoo'test'" ) ) ) - assert_that( "\\'foo\\' zoo", - equal_to( iu.RemoveIdentifierFreeText( - "\\'foo\\'\"'\"\"bar\" zoo\"test\"" ) ) ) +class IdentifierUtilsTest( TestCase ): + def test_RemoveIdentifierFreeText_CppComments( self ): + assert_that( "foo \nbar \nqux", + equal_to( iu.RemoveIdentifierFreeText( + "foo \nbar //foo \nqux" ) ) ) -def RemoveIdentifierFreeText_NoMultilineString_test(): - assert_that( "'\nlet x = \nlet y = ", - equal_to( iu.RemoveIdentifierFreeText( - "'\nlet x = 'foo'\nlet y = 'bar'" ) ) ) - assert_that( "\"\nlet x = \nlet y = ", - equal_to( iu.RemoveIdentifierFreeText( - "\"\nlet x = \"foo\"\nlet y = \"bar\"" ) ) ) + def test_RemoveIdentifierFreeText_PythonComments( self ): + assert_that( "foo \nbar \nqux", + equal_to( iu.RemoveIdentifierFreeText( + "foo \nbar #foo \nqux" ) ) ) -def RemoveIdentifierFreeText_PythonMultilineString_test(): - assert_that( "\n\n\nzoo", - equal_to( iu.RemoveIdentifierFreeText( - "\"\"\"\nfoobar\n\"\"\"\nzoo" ) ) ) - assert_that( "\n\n\nzoo", - equal_to( iu.RemoveIdentifierFreeText( - "'''\nfoobar\n'''\nzoo" ) ) ) + def test_RemoveIdentifierFreeText_CstyleComments( self ): + assert_that( "\n bar", + equal_to( iu.RemoveIdentifierFreeText( + "/* foo\n */ bar" ) ) ) + assert_that( "foo \nbar \nqux", + equal_to( iu.RemoveIdentifierFreeText( + "foo \nbar /* foo */\nqux" ) ) ) + assert_that( "foo \nbar \n\nqux", + equal_to( iu.RemoveIdentifierFreeText( + "foo \nbar /* foo \n foo2 */\nqux" ) ) ) -def RemoveIdentifierFreeText_GoBackQuoteString_test(): - assert_that( "foo \nbar `foo`\nqux", - equal_to( iu.RemoveIdentifierFreeText( - "foo \nbar `foo`\nqux" ) ) ) - assert_that( "foo \nbar \nqux", - equal_to( iu.RemoveIdentifierFreeText( - "foo \nbar `foo`\nqux", filetype = 'go' ) ) ) + def test_RemoveIdentifierFreeText_SimpleSingleQuoteString( self ): + assert_that( "foo \nbar \nqux", + equal_to( iu.RemoveIdentifierFreeText( + "foo \nbar 'foo'\nqux" ) ) ) -def ExtractIdentifiersFromText_test(): - assert_that( [ "foo", "_bar", "BazGoo", "FOO", "_", "x", - "one", "two", "moo", "qqq" ], - equal_to( iu.ExtractIdentifiersFromText( - "foo $_bar \n&BazGoo\n FOO= !!! '-' " - "- _ (x) one-two !moo [qqq]" ) ) ) - - -def ExtractIdentifiersFromText_Css_test(): - assert_that( [ "foo", "-zoo", "font-size", "px", "a99" ], - equal_to( iu.ExtractIdentifiersFromText( - "foo -zoo {font-size: 12px;} a99", "css" ) ) ) - - -def ExtractIdentifiersFromText_Html_test(): - assert_that( [ "foo", "goo-foo", "zoo", "bar", "aa", "z", "b@g", "fo", "ba" ], - equal_to( iu.ExtractIdentifiersFromText( - ' b@g fo.ba', "html" ) ) ) - - -def ExtractIdentifiersFromText_Html_TemplateChars_test(): - assert_that( iu.ExtractIdentifiersFromText( '{{goo}}', 'html' ), - has_item( 'goo' ) ) - - -def ExtractIdentifiersFromText_JavaScript_test(): - assert_that( [ "var", "foo", "require", "bar" ], - equal_to( iu.ExtractIdentifiersFromText( - "var foo = require('bar');", 'javascript' ) ) ) - - -def IsIdentifier_Default_test(): - assert_that( iu.IsIdentifier( 'foo' ) ) - assert_that( iu.IsIdentifier( 'foo129' ) ) - assert_that( iu.IsIdentifier( 'f12' ) ) - assert_that( iu.IsIdentifier( 'f12' ) ) - - assert_that( iu.IsIdentifier( '_foo' ) ) - assert_that( iu.IsIdentifier( '_foo129' ) ) - assert_that( iu.IsIdentifier( '_f12' ) ) - assert_that( iu.IsIdentifier( '_f12' ) ) - - assert_that( iu.IsIdentifier( 'uniçode' ) ) - assert_that( iu.IsIdentifier( 'uç' ) ) - assert_that( iu.IsIdentifier( 'ç' ) ) - assert_that( iu.IsIdentifier( 'çode' ) ) - - assert_that( not iu.IsIdentifier( '1foo129' ) ) - assert_that( not iu.IsIdentifier( '-foo' ) ) - assert_that( not iu.IsIdentifier( 'foo-' ) ) - assert_that( not iu.IsIdentifier( 'font-face' ) ) - assert_that( not iu.IsIdentifier( None ) ) - assert_that( not iu.IsIdentifier( '' ) ) - - -def IsIdentifier_JavaScript_test(): - assert_that( iu.IsIdentifier( '_føo1', 'javascript' ) ) - assert_that( iu.IsIdentifier( 'fø_o1', 'javascript' ) ) - assert_that( iu.IsIdentifier( '$føo1', 'javascript' ) ) - assert_that( iu.IsIdentifier( 'fø$o1', 'javascript' ) ) - - assert_that( not iu.IsIdentifier( '1føo', 'javascript' ) ) - - -def IsIdentifier_TypeScript_test(): - assert_that( iu.IsIdentifier( '_føo1', 'typescript' ) ) - assert_that( iu.IsIdentifier( 'fø_o1', 'typescript' ) ) - assert_that( iu.IsIdentifier( '$føo1', 'typescript' ) ) - assert_that( iu.IsIdentifier( 'fø$o1', 'typescript' ) ) - - assert_that( not iu.IsIdentifier( '1føo', 'typescript' ) ) - - -def IsIdentifier_Css_test(): - assert_that( iu.IsIdentifier( 'foo' , 'css' ) ) - assert_that( iu.IsIdentifier( 'a' , 'css' ) ) - assert_that( iu.IsIdentifier( 'a1' , 'css' ) ) - assert_that( iu.IsIdentifier( 'a-' , 'css' ) ) - assert_that( iu.IsIdentifier( 'a-b' , 'css' ) ) - assert_that( iu.IsIdentifier( '_b' , 'css' ) ) - assert_that( iu.IsIdentifier( '-ms-foo' , 'css' ) ) - assert_that( iu.IsIdentifier( '-_o' , 'css' ) ) - assert_that( iu.IsIdentifier( 'font-face', 'css' ) ) - assert_that( iu.IsIdentifier( 'αβγ' , 'css' ) ) - - assert_that( not iu.IsIdentifier( '-3b', 'css' ) ) - assert_that( not iu.IsIdentifier( '-3' , 'css' ) ) - assert_that( not iu.IsIdentifier( '--' , 'css' ) ) - assert_that( not iu.IsIdentifier( '3' , 'css' ) ) - assert_that( not iu.IsIdentifier( '' , 'css' ) ) - assert_that( not iu.IsIdentifier( '€' , 'css' ) ) - - -def IsIdentifier_R_test(): - assert_that( iu.IsIdentifier( 'a' , 'r' ) ) - assert_that( iu.IsIdentifier( 'a.b' , 'r' ) ) - assert_that( iu.IsIdentifier( 'a.b.c', 'r' ) ) - assert_that( iu.IsIdentifier( 'a_b' , 'r' ) ) - assert_that( iu.IsIdentifier( 'a1' , 'r' ) ) - assert_that( iu.IsIdentifier( 'a_1' , 'r' ) ) - assert_that( iu.IsIdentifier( '.a' , 'r' ) ) - assert_that( iu.IsIdentifier( '.a_b' , 'r' ) ) - assert_that( iu.IsIdentifier( '.a1' , 'r' ) ) - assert_that( iu.IsIdentifier( '...' , 'r' ) ) - assert_that( iu.IsIdentifier( '..1' , 'r' ) ) - - assert_that( not iu.IsIdentifier( '.1a', 'r' ) ) - assert_that( not iu.IsIdentifier( '.1' , 'r' ) ) - assert_that( not iu.IsIdentifier( '1a' , 'r' ) ) - assert_that( not iu.IsIdentifier( '123', 'r' ) ) - assert_that( not iu.IsIdentifier( '_1a', 'r' ) ) - assert_that( not iu.IsIdentifier( '_a' , 'r' ) ) - assert_that( not iu.IsIdentifier( '' , 'r' ) ) - - -def IsIdentifier_Clojure_test(): - assert_that( iu.IsIdentifier( 'foo' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'f9' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a.b.c', 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a.c' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a/c' , 'clojure' ) ) - assert_that( iu.IsIdentifier( '*' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a*b' , 'clojure' ) ) - assert_that( iu.IsIdentifier( '?' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a?b' , 'clojure' ) ) - assert_that( iu.IsIdentifier( ':' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a:b' , 'clojure' ) ) - assert_that( iu.IsIdentifier( '+' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a+b' , 'clojure' ) ) - assert_that( iu.IsIdentifier( '-' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a-b' , 'clojure' ) ) - assert_that( iu.IsIdentifier( '!' , 'clojure' ) ) - assert_that( iu.IsIdentifier( 'a!b' , 'clojure' ) ) - - assert_that( not iu.IsIdentifier( '9f' , 'clojure' ) ) - assert_that( not iu.IsIdentifier( '9' , 'clojure' ) ) - assert_that( not iu.IsIdentifier( 'a/b/c', 'clojure' ) ) - assert_that( not iu.IsIdentifier( '(a)' , 'clojure' ) ) - assert_that( not iu.IsIdentifier( '' , 'clojure' ) ) - - -def IsIdentifier_Elisp_test(): - # elisp is using the clojure regexes, so we're testing this more lightly - assert_that( iu.IsIdentifier( 'foo' , 'elisp' ) ) - assert_that( iu.IsIdentifier( 'f9' , 'elisp' ) ) - assert_that( iu.IsIdentifier( 'a.b.c', 'elisp' ) ) - assert_that( iu.IsIdentifier( 'a/c' , 'elisp' ) ) - - assert_that( not iu.IsIdentifier( '9f' , 'elisp' ) ) - assert_that( not iu.IsIdentifier( '9' , 'elisp' ) ) - assert_that( not iu.IsIdentifier( 'a/b/c', 'elisp' ) ) - assert_that( not iu.IsIdentifier( '(a)' , 'elisp' ) ) - assert_that( not iu.IsIdentifier( '' , 'elisp' ) ) - - -def IsIdentifier_Haskell_test(): - assert_that( iu.IsIdentifier( 'foo' , 'haskell' ) ) - assert_that( iu.IsIdentifier( "foo'", 'haskell' ) ) - assert_that( iu.IsIdentifier( "x'" , 'haskell' ) ) - assert_that( iu.IsIdentifier( "_x'" , 'haskell' ) ) - assert_that( iu.IsIdentifier( "_x" , 'haskell' ) ) - assert_that( iu.IsIdentifier( "x9" , 'haskell' ) ) - - assert_that( not iu.IsIdentifier( "'x", 'haskell' ) ) - assert_that( not iu.IsIdentifier( "9x", 'haskell' ) ) - assert_that( not iu.IsIdentifier( "9" , 'haskell' ) ) - assert_that( not iu.IsIdentifier( '' , 'haskell' ) ) - - -def IsIdentifier_Tex_test(): - assert_that( iu.IsIdentifier( 'foo' , 'tex' ) ) - assert_that( iu.IsIdentifier( 'fig:foo' , 'tex' ) ) - assert_that( iu.IsIdentifier( 'fig:foo-bar', 'tex' ) ) - assert_that( iu.IsIdentifier( 'sec:summary', 'tex' ) ) - assert_that( iu.IsIdentifier( 'eq:bar_foo' , 'tex' ) ) - assert_that( iu.IsIdentifier( 'fōo' , 'tex' ) ) - assert_that( iu.IsIdentifier( 'some8' , 'tex' ) ) - - assert_that( not iu.IsIdentifier( '\\section', 'tex' ) ) - assert_that( not iu.IsIdentifier( 'foo:' , 'tex' ) ) - assert_that( not iu.IsIdentifier( '-bar' , 'tex' ) ) - assert_that( not iu.IsIdentifier( '' , 'tex' ) ) - - -def IsIdentifier_Perl6_test(): - assert_that( iu.IsIdentifier( 'foo' , 'perl6' ) ) - assert_that( iu.IsIdentifier( "f-o" , 'perl6' ) ) - assert_that( iu.IsIdentifier( "x'y" , 'perl6' ) ) - assert_that( iu.IsIdentifier( "_x-y" , 'perl6' ) ) - assert_that( iu.IsIdentifier( "x-y'a", 'perl6' ) ) - assert_that( iu.IsIdentifier( "x-_" , 'perl6' ) ) - assert_that( iu.IsIdentifier( "x-_7" , 'perl6' ) ) - assert_that( iu.IsIdentifier( "_x" , 'perl6' ) ) - assert_that( iu.IsIdentifier( "x9" , 'perl6' ) ) - - assert_that( not iu.IsIdentifier( "'x" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x'" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "-x" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x-" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x-1" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x--" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x--a", 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x-'" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x-'a", 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x-a-", 'perl6' ) ) - assert_that( not iu.IsIdentifier( "x+" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "9x" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( "9" , 'perl6' ) ) - assert_that( not iu.IsIdentifier( '' , 'perl6' ) ) - - -def IsIdentifier_Scheme_test(): - assert_that( iu.IsIdentifier( 'λ' , 'scheme' ) ) - assert_that( iu.IsIdentifier( '_' , 'scheme' ) ) - assert_that( iu.IsIdentifier( '+' , 'scheme' ) ) - assert_that( iu.IsIdentifier( '-' , 'scheme' ) ) - assert_that( iu.IsIdentifier( '...' , 'scheme' ) ) - assert_that( iu.IsIdentifier( r'\x01;' , 'scheme' ) ) - assert_that( iu.IsIdentifier( r'h\x65;lle', 'scheme' ) ) - assert_that( iu.IsIdentifier( 'foo' , 'scheme' ) ) - assert_that( iu.IsIdentifier( 'foo+-*/1-1', 'scheme' ) ) - assert_that( iu.IsIdentifier( 'call/cc' , 'scheme' ) ) - - assert_that( not iu.IsIdentifier( '.' , 'scheme' ) ) - assert_that( not iu.IsIdentifier( '..' , 'scheme' ) ) - assert_that( not iu.IsIdentifier( '--' , 'scheme' ) ) - assert_that( not iu.IsIdentifier( '++' , 'scheme' ) ) - assert_that( not iu.IsIdentifier( '+1' , 'scheme' ) ) - assert_that( not iu.IsIdentifier( '-1' , 'scheme' ) ) - assert_that( not iu.IsIdentifier( '-abc' , 'scheme' ) ) - assert_that( not iu.IsIdentifier( '- b@g fo.ba', "html" ) ) ) + + + def test_ExtractIdentifiersFromText_Html_TemplateChars( self ): + assert_that( iu.ExtractIdentifiersFromText( '{{goo}}', 'html' ), + has_item( 'goo' ) ) + + + def test_ExtractIdentifiersFromText_JavaScript( self ): + assert_that( [ "var", "foo", "require", "bar" ], + equal_to( iu.ExtractIdentifiersFromText( + "var foo = require('bar');", 'javascript' ) ) ) + + + def test_IsIdentifier_Default( self ): + assert_that( iu.IsIdentifier( 'foo' ) ) + assert_that( iu.IsIdentifier( 'foo129' ) ) + assert_that( iu.IsIdentifier( 'f12' ) ) + assert_that( iu.IsIdentifier( 'f12' ) ) + + assert_that( iu.IsIdentifier( '_foo' ) ) + assert_that( iu.IsIdentifier( '_foo129' ) ) + assert_that( iu.IsIdentifier( '_f12' ) ) + assert_that( iu.IsIdentifier( '_f12' ) ) + + assert_that( iu.IsIdentifier( 'uniçode' ) ) + assert_that( iu.IsIdentifier( 'uç' ) ) + assert_that( iu.IsIdentifier( 'ç' ) ) + assert_that( iu.IsIdentifier( 'çode' ) ) + + assert_that( not iu.IsIdentifier( '1foo129' ) ) + assert_that( not iu.IsIdentifier( '-foo' ) ) + assert_that( not iu.IsIdentifier( 'foo-' ) ) + assert_that( not iu.IsIdentifier( 'font-face' ) ) + assert_that( not iu.IsIdentifier( None ) ) + assert_that( not iu.IsIdentifier( '' ) ) + + + def test_IsIdentifier_JavaScript( self ): + assert_that( iu.IsIdentifier( '_føo1', 'javascript' ) ) + assert_that( iu.IsIdentifier( 'fø_o1', 'javascript' ) ) + assert_that( iu.IsIdentifier( '$føo1', 'javascript' ) ) + assert_that( iu.IsIdentifier( 'fø$o1', 'javascript' ) ) + + assert_that( not iu.IsIdentifier( '1føo', 'javascript' ) ) + + + def test_IsIdentifier_TypeScript( self ): + assert_that( iu.IsIdentifier( '_føo1', 'typescript' ) ) + assert_that( iu.IsIdentifier( 'fø_o1', 'typescript' ) ) + assert_that( iu.IsIdentifier( '$føo1', 'typescript' ) ) + assert_that( iu.IsIdentifier( 'fø$o1', 'typescript' ) ) + + assert_that( not iu.IsIdentifier( '1føo', 'typescript' ) ) + + + def test_IsIdentifier_Css( self ): + assert_that( iu.IsIdentifier( 'foo' , 'css' ) ) + assert_that( iu.IsIdentifier( 'a' , 'css' ) ) + assert_that( iu.IsIdentifier( 'a1' , 'css' ) ) + assert_that( iu.IsIdentifier( 'a-' , 'css' ) ) + assert_that( iu.IsIdentifier( 'a-b' , 'css' ) ) + assert_that( iu.IsIdentifier( '_b' , 'css' ) ) + assert_that( iu.IsIdentifier( '-ms-foo' , 'css' ) ) + assert_that( iu.IsIdentifier( '-_o' , 'css' ) ) + assert_that( iu.IsIdentifier( 'font-face', 'css' ) ) + assert_that( iu.IsIdentifier( 'αβγ' , 'css' ) ) + + assert_that( not iu.IsIdentifier( '-3b', 'css' ) ) + assert_that( not iu.IsIdentifier( '-3' , 'css' ) ) + assert_that( not iu.IsIdentifier( '--' , 'css' ) ) + assert_that( not iu.IsIdentifier( '3' , 'css' ) ) + assert_that( not iu.IsIdentifier( '' , 'css' ) ) + assert_that( not iu.IsIdentifier( '€' , 'css' ) ) + + + def test_IsIdentifier_R( self ): + assert_that( iu.IsIdentifier( 'a' , 'r' ) ) + assert_that( iu.IsIdentifier( 'a.b' , 'r' ) ) + assert_that( iu.IsIdentifier( 'a.b.c', 'r' ) ) + assert_that( iu.IsIdentifier( 'a_b' , 'r' ) ) + assert_that( iu.IsIdentifier( 'a1' , 'r' ) ) + assert_that( iu.IsIdentifier( 'a_1' , 'r' ) ) + assert_that( iu.IsIdentifier( '.a' , 'r' ) ) + assert_that( iu.IsIdentifier( '.a_b' , 'r' ) ) + assert_that( iu.IsIdentifier( '.a1' , 'r' ) ) + assert_that( iu.IsIdentifier( '...' , 'r' ) ) + assert_that( iu.IsIdentifier( '..1' , 'r' ) ) + + assert_that( not iu.IsIdentifier( '.1a', 'r' ) ) + assert_that( not iu.IsIdentifier( '.1' , 'r' ) ) + assert_that( not iu.IsIdentifier( '1a' , 'r' ) ) + assert_that( not iu.IsIdentifier( '123', 'r' ) ) + assert_that( not iu.IsIdentifier( '_1a', 'r' ) ) + assert_that( not iu.IsIdentifier( '_a' , 'r' ) ) + assert_that( not iu.IsIdentifier( '' , 'r' ) ) + + + def test_IsIdentifier_Clojure( self ): + assert_that( iu.IsIdentifier( 'foo' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'f9' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a.b.c', 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a.c' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a/c' , 'clojure' ) ) + assert_that( iu.IsIdentifier( '*' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a*b' , 'clojure' ) ) + assert_that( iu.IsIdentifier( '?' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a?b' , 'clojure' ) ) + assert_that( iu.IsIdentifier( ':' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a:b' , 'clojure' ) ) + assert_that( iu.IsIdentifier( '+' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a+b' , 'clojure' ) ) + assert_that( iu.IsIdentifier( '-' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a-b' , 'clojure' ) ) + assert_that( iu.IsIdentifier( '!' , 'clojure' ) ) + assert_that( iu.IsIdentifier( 'a!b' , 'clojure' ) ) + + assert_that( not iu.IsIdentifier( '9f' , 'clojure' ) ) + assert_that( not iu.IsIdentifier( '9' , 'clojure' ) ) + assert_that( not iu.IsIdentifier( 'a/b/c', 'clojure' ) ) + assert_that( not iu.IsIdentifier( '(a)' , 'clojure' ) ) + assert_that( not iu.IsIdentifier( '' , 'clojure' ) ) + + + def test_IsIdentifier_Elisp( self ): + # elisp is using the clojure regexes, so we're testing this more lightly + assert_that( iu.IsIdentifier( 'foo' , 'elisp' ) ) + assert_that( iu.IsIdentifier( 'f9' , 'elisp' ) ) + assert_that( iu.IsIdentifier( 'a.b.c', 'elisp' ) ) + assert_that( iu.IsIdentifier( 'a/c' , 'elisp' ) ) + + assert_that( not iu.IsIdentifier( '9f' , 'elisp' ) ) + assert_that( not iu.IsIdentifier( '9' , 'elisp' ) ) + assert_that( not iu.IsIdentifier( 'a/b/c', 'elisp' ) ) + assert_that( not iu.IsIdentifier( '(a)' , 'elisp' ) ) + assert_that( not iu.IsIdentifier( '' , 'elisp' ) ) + + + def test_IsIdentifier_Haskell( self ): + assert_that( iu.IsIdentifier( 'foo' , 'haskell' ) ) + assert_that( iu.IsIdentifier( "foo'", 'haskell' ) ) + assert_that( iu.IsIdentifier( "x'" , 'haskell' ) ) + assert_that( iu.IsIdentifier( "_x'" , 'haskell' ) ) + assert_that( iu.IsIdentifier( "_x" , 'haskell' ) ) + assert_that( iu.IsIdentifier( "x9" , 'haskell' ) ) + + assert_that( not iu.IsIdentifier( "'x", 'haskell' ) ) + assert_that( not iu.IsIdentifier( "9x", 'haskell' ) ) + assert_that( not iu.IsIdentifier( "9" , 'haskell' ) ) + assert_that( not iu.IsIdentifier( '' , 'haskell' ) ) + + + def test_IsIdentifier_Tex( self ): + assert_that( iu.IsIdentifier( 'foo' , 'tex' ) ) + assert_that( iu.IsIdentifier( 'fig:foo' , 'tex' ) ) + assert_that( iu.IsIdentifier( 'fig:foo-bar', 'tex' ) ) + assert_that( iu.IsIdentifier( 'sec:summary', 'tex' ) ) + assert_that( iu.IsIdentifier( 'eq:bar_foo' , 'tex' ) ) + assert_that( iu.IsIdentifier( 'fōo' , 'tex' ) ) + assert_that( iu.IsIdentifier( 'some8' , 'tex' ) ) + + assert_that( not iu.IsIdentifier( '\\section', 'tex' ) ) + assert_that( not iu.IsIdentifier( 'foo:' , 'tex' ) ) + assert_that( not iu.IsIdentifier( '-bar' , 'tex' ) ) + assert_that( not iu.IsIdentifier( '' , 'tex' ) ) + + + def test_IsIdentifier_Perl6( self ): + assert_that( iu.IsIdentifier( 'foo' , 'perl6' ) ) + assert_that( iu.IsIdentifier( "f-o" , 'perl6' ) ) + assert_that( iu.IsIdentifier( "x'y" , 'perl6' ) ) + assert_that( iu.IsIdentifier( "_x-y" , 'perl6' ) ) + assert_that( iu.IsIdentifier( "x-y'a", 'perl6' ) ) + assert_that( iu.IsIdentifier( "x-_" , 'perl6' ) ) + assert_that( iu.IsIdentifier( "x-_7" , 'perl6' ) ) + assert_that( iu.IsIdentifier( "_x" , 'perl6' ) ) + assert_that( iu.IsIdentifier( "x9" , 'perl6' ) ) + + assert_that( not iu.IsIdentifier( "'x" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x'" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "-x" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x-" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x-1" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x--" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x--a", 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x-'" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x-'a", 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x-a-", 'perl6' ) ) + assert_that( not iu.IsIdentifier( "x+" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "9x" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( "9" , 'perl6' ) ) + assert_that( not iu.IsIdentifier( '' , 'perl6' ) ) + + + def test_IsIdentifier_Scheme( self ): + assert_that( iu.IsIdentifier( 'λ' , 'scheme' ) ) + assert_that( iu.IsIdentifier( '_' , 'scheme' ) ) + assert_that( iu.IsIdentifier( '+' , 'scheme' ) ) + assert_that( iu.IsIdentifier( '-' , 'scheme' ) ) + assert_that( iu.IsIdentifier( '...' , 'scheme' ) ) + assert_that( iu.IsIdentifier( r'\x01;' , 'scheme' ) ) + assert_that( iu.IsIdentifier( r'h\x65;lle', 'scheme' ) ) + assert_that( iu.IsIdentifier( 'foo' , 'scheme' ) ) + assert_that( iu.IsIdentifier( 'foo+-*/1-1', 'scheme' ) ) + assert_that( iu.IsIdentifier( 'call/cc' , 'scheme' ) ) + + assert_that( not iu.IsIdentifier( '.' , 'scheme' ) ) + assert_that( not iu.IsIdentifier( '..' , 'scheme' ) ) + assert_that( not iu.IsIdentifier( '--' , 'scheme' ) ) + assert_that( not iu.IsIdentifier( '++' , 'scheme' ) ) + assert_that( not iu.IsIdentifier( '+1' , 'scheme' ) ) + assert_that( not iu.IsIdentifier( '-1' , 'scheme' ) ) + assert_that( not iu.IsIdentifier( '-abc' , 'scheme' ) ) + assert_that( not iu.IsIdentifier( '-. -from ycmd.tests.java.conftest import * # noqa +import contextlib +import os +from ycmd.tests.test_utils import ( BuildRequest, + ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp, + StopCompleterServer, + WaitUntilCompleterServerReady ) +import functools + +shared_app = None +SERVER_STARTUP_TIMEOUT = 120 # seconds + + +DEFAULT_PROJECT_DIR = 'simple_eclipse_project' + + +def PathToTestFile( *args ): + dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) + return os.path.join( dir_of_current_script, 'testdata', *args ) + + +def setUpModule(): + global shared_app + shared_app = SetUpApp() + with IgnoreExtraConfOutsideTestsFolder(): + StartJavaCompleterServerInDirectory( shared_app, + PathToTestFile( DEFAULT_PROJECT_DIR ) ) + + +def tearDownModule(): + global shared_app + StopCompleterServer( shared_app, 'java' ) + + +def StartJavaCompleterServerInDirectory( app, directory ): + StartJavaCompleterServerWithFile( app, + os.path.join( directory, 'test.java' ) ) + + +def StartJavaCompleterServerWithFile( app, file_path ): + app.post_json( '/event_notification', + BuildRequest( + event_name = 'FileReadyToParse', + filepath = file_path, + filetype = 'java' ) ) + WaitUntilCompleterServerReady( app, 'java', SERVER_STARTUP_TIMEOUT ) + + +@contextlib.contextmanager +def isolated_app( custom_options = {} ): + """Defines a context manager to be used in cases where it is easier to + specify user options of the isolated ycmdat some point inside the function. + + Example usage: + + def some_test( isolated_app ): + with TemporaryTestDir() as tmp_dir: + with isolated_app( user_options ) as app: + + """ + with IsolatedApp( custom_options ) as app: + try: + yield app + finally: + StopCompleterServer( app, 'java' ) + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'java' ) + return Wrapper + return Decorator diff --git a/ycmd/tests/java/conftest.py b/ycmd/tests/java/conftest.py deleted file mode 100644 index 9fa9e7be5a..0000000000 --- a/ycmd/tests/java/conftest.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import contextlib -import os -import pytest -from ycmd.tests.test_utils import ( BuildRequest, - ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - SetUpApp, - StopCompleterServer, - WaitUntilCompleterServerReady ) -shared_app = None -SERVER_STARTUP_TIMEOUT = 120 # seconds - - -DEFAULT_PROJECT_DIR = 'simple_eclipse_project' - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - """Initializes the ycmd server as a WebTest application that will be shared - by all tests using the SharedYcmd decorator in this package. Additional - configuration that is common to these tests, like starting a semantic - subserver, should be done here.""" - global shared_app - shared_app = SetUpApp() - with IgnoreExtraConfOutsideTestsFolder(): - StartJavaCompleterServerInDirectory( - shared_app, PathToTestFile( DEFAULT_PROJECT_DIR ) ) - yield - StopCompleterServer( shared_app, 'java' ) - - -def StartJavaCompleterServerInDirectory( app, directory ): - StartJavaCompleterServerWithFile( app, - os.path.join( directory, 'test.java' ) ) - - -def StartJavaCompleterServerWithFile( app, file_path ): - app.post_json( '/event_notification', - BuildRequest( - event_name = 'FileReadyToParse', - filepath = file_path, - filetype = 'java' ) ) - WaitUntilCompleterServerReady( app, 'java', SERVER_STARTUP_TIMEOUT ) - - -@pytest.fixture -def isolated_app(): - """Defines a pytest fixture to be used in cases where it is easier to - specify user options of the isolated ycmdat some point inside the function. - - Example usage: - - def some_test( isolated_app ): - with TemporaryTestDir() as tmp_dir: - with isolated_app( user_options ) as app: - - """ - @contextlib.contextmanager - def manager( custom_options = {} ): - with IsolatedApp( custom_options ) as app: - try: - yield app - finally: - StopCompleterServer( app, 'java' ) - - return manager - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with IsolatedApp( request.param[ 1 ] ) as app: - yield app - StopCompleterServer( app, 'java' ) - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def PathToTestFile( *args ): - dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) - return os.path.join( dir_of_current_script, 'testdata', *args ) diff --git a/ycmd/tests/java/debug_info_test.py b/ycmd/tests/java/debug_info_test.py index 5d0401fdb4..39c1765db2 100644 --- a/ycmd/tests/java/debug_info_test.py +++ b/ycmd/tests/java/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -27,11 +27,14 @@ starts_with ) from unittest.mock import patch -from ycmd.tests.java import ( DEFAULT_PROJECT_DIR, +from unittest import TestCase +from ycmd.tests.java import ( DEFAULT_PROJECT_DIR, # noqa IsolatedYcmd, PathToTestFile, SharedYcmd, - StartJavaCompleterServerInDirectory ) + StartJavaCompleterServerInDirectory, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, WaitUntilCompleterServerReady, WithRetry ) @@ -42,222 +45,221 @@ import threading -@IsolatedYcmd() -def DebugInfo_HandleNotificationInPollThread_Throw_test( app ): - filepath = PathToTestFile( DEFAULT_PROJECT_DIR, - 'src', - 'com', - 'youcompleteme', - 'Test.java' ) - StartJavaCompleterServerInDirectory( app, filepath ) +class DebugInfoTest( TestCase ): + @IsolatedYcmd() + def test_DebugInfo_HandleNotificationInPollThread_Throw( self, app ): + filepath = PathToTestFile( DEFAULT_PROJECT_DIR, + 'src', + 'com', + 'youcompleteme', + 'Test.java' ) + StartJavaCompleterServerInDirectory( app, filepath ) - # This mock will be called in the message pump thread, so synchronize the - # result (thrown) using an Event - thrown = threading.Event() + # This mock will be called in the message pump thread, so synchronize the + # result (thrown) using an Event + thrown = threading.Event() - def ThrowOnLogMessage( msg ): - thrown.set() - raise RuntimeError( "ThrowOnLogMessage" ) + def ThrowOnLogMessage( msg ): + thrown.set() + raise RuntimeError( "ThrowOnLogMessage" ) - with patch.object( lsc.LanguageServerCompleter, - 'HandleNotificationInPollThread', - side_effect = ThrowOnLogMessage ): - app.post_json( - '/run_completer_command', - BuildRequest( - filepath = filepath, - filetype = 'java', - command_arguments = [ 'RestartServer' ], - ), - ) + with patch.object( lsc.LanguageServerCompleter, + 'HandleNotificationInPollThread', + side_effect = ThrowOnLogMessage ): + app.post_json( + '/run_completer_command', + BuildRequest( + filepath = filepath, + filetype = 'java', + command_arguments = [ 'RestartServer' ], + ), + ) - # Ensure that we still process and handle messages even though a - # message-pump-thread-handler raised an error. - WaitUntilCompleterServerReady( app, 'java' ) + # Ensure that we still process and handle messages even though a + # message-pump-thread-handler raised an error. + WaitUntilCompleterServerReady( app, 'java' ) - # Prove that the exception was thrown. - assert_that( thrown.is_set(), equal_to( True ) ) + # Prove that the exception was thrown. + assert_that( thrown.is_set(), equal_to( True ) ) -@SharedYcmd -def DebugInfo_test( app ): - request_data = BuildRequest( filetype = 'java' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'Java', - 'servers': contains_exactly( has_entries( { - 'name': 'jdt.ls', - 'is_running': instance_of( bool ), - 'executable': instance_of( list ), - 'pid': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ), instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { 'key': 'Server State', - 'value': 'Initialized' } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'simple_eclipse_project' ) - } ), - has_entries( { - 'key': 'Settings', - 'value': json.dumps( - { 'bundles': [] }, - indent = 2, - sort_keys = True ) - } ), - has_entries( { 'key': 'Startup Status', - 'value': 'Ready' } ), - has_entries( { 'key': 'Java Path', - 'value': instance_of( str ) } ), - has_entries( { 'key': 'Launcher Config.', - 'value': instance_of( str ) } ), - has_entries( { 'key': 'Workspace Path', - 'value': instance_of( str ) } ), - has_entries( { 'key': 'Extension Path', - 'value': contains_exactly( instance_of( str ) ) } ), - ) + @SharedYcmd + def test_DebugInfo( self, app ): + request_data = BuildRequest( filetype = 'java' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'Java', + 'servers': contains_exactly( has_entries( { + 'name': 'jdt.ls', + 'is_running': instance_of( bool ), + 'executable': instance_of( list ), + 'pid': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ), + instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { 'key': 'Server State', + 'value': 'Initialized' } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'simple_eclipse_project' ) + } ), + has_entries( { + 'key': 'Settings', + 'value': json.dumps( + { 'bundles': [] }, + indent = 2, + sort_keys = True ) + } ), + has_entries( { 'key': 'Startup Status', + 'value': 'Ready' } ), + has_entries( { 'key': 'Java Path', + 'value': instance_of( str ) } ), + has_entries( { 'key': 'Launcher Config.', + 'value': instance_of( str ) } ), + has_entries( { 'key': 'Workspace Path', + 'value': instance_of( str ) } ), + has_entries( { 'key': 'Extension Path', + 'value': contains_exactly( instance_of( str ) ) } ), + ) + } ) ) } ) ) - } ) ) - ) + ) -@IsolatedYcmd( { 'extra_conf_globlist': PathToTestFile( 'extra_confs', '*' ) } ) -def DebugInfo_ExtraConf_SettingsValid_test( app ): - StartJavaCompleterServerInDirectory( - app, - PathToTestFile( 'extra_confs', 'simple_extra_conf_project' ) ) + @IsolatedYcmd( { 'extra_conf_globlist': + PathToTestFile( 'extra_confs', '*' ) } ) + def test_DebugInfo_ExtraConf_SettingsValid( self, app ): + StartJavaCompleterServerInDirectory( + app, + PathToTestFile( 'extra_confs', 'simple_extra_conf_project' ) ) - filepath = PathToTestFile( 'extra_confs', - 'simple_extra_conf_project', - 'src', - 'ExtraConf.java' ) + filepath = PathToTestFile( 'extra_confs', + 'simple_extra_conf_project', + 'src', + 'ExtraConf.java' ) - request_data = BuildRequest( filepath = filepath, - filetype = 'java' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'Java', - 'servers': contains_exactly( has_entries( { - 'name': 'jdt.ls', - 'is_running': instance_of( bool ), - 'executable': instance_of( list ), - 'pid': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ), instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { 'key': 'Server State', - 'value': 'Initialized' } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'extra_confs', - 'simple_extra_conf_project' ) - } ), - has_entries( { - 'key': 'Settings', - 'value': json.dumps( - { 'java.rename.enabled': False, 'bundles': [] }, - indent = 2, - sort_keys = True ) - } ), - has_entries( { 'key': 'Startup Status', - 'value': 'Ready' } ), - has_entries( { 'key': 'Java Path', - 'value': instance_of( str ) } ), - has_entries( { 'key': 'Launcher Config.', - 'value': instance_of( str ) } ), - has_entries( { 'key': 'Workspace Path', - 'value': instance_of( str ) } ), - has_entries( { 'key': 'Extension Path', - 'value': contains_exactly( instance_of( str ) ) } ), - ) + request_data = BuildRequest( filepath = filepath, + filetype = 'java' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'Java', + 'servers': contains_exactly( has_entries( { + 'name': 'jdt.ls', + 'is_running': instance_of( bool ), + 'executable': instance_of( list ), + 'pid': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ), + instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { 'key': 'Server State', + 'value': 'Initialized' } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'extra_confs', + 'simple_extra_conf_project' ) + } ), + has_entries( { + 'key': 'Settings', + 'value': json.dumps( + { 'java.rename.enabled': False, 'bundles': [] }, + indent = 2, + sort_keys = True ) + } ), + has_entries( { 'key': 'Startup Status', + 'value': 'Ready' } ), + has_entries( { 'key': 'Java Path', + 'value': instance_of( str ) } ), + has_entries( { 'key': 'Launcher Config.', + 'value': instance_of( str ) } ), + has_entries( { 'key': 'Workspace Path', + 'value': instance_of( str ) } ), + has_entries( { 'key': 'Extension Path', + 'value': contains_exactly( instance_of( str ) ) } ), + ) + } ) ) } ) ) - } ) ) - ) - # Make sure a didSave notification doesn't cause anything to error. - event_data = BuildRequest( event_name = 'FileSave', - contents = 'asd', - filepath = filepath, - filetype = 'java' ) - app.post_json( '/event_notification', event_data ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'Java', - 'servers': contains_exactly( has_entries( { - 'name': 'jdt.ls', - 'is_running': instance_of( bool ) } ) ) } ) ) ) + ) + # Make sure a didSave notification doesn't cause anything to error. + event_data = BuildRequest( event_name = 'FileSave', + contents = 'asd', + filepath = filepath, + filetype = 'java' ) + app.post_json( '/event_notification', event_data ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'Java', + 'servers': contains_exactly( has_entries( { + 'name': 'jdt.ls', + 'is_running': instance_of( bool ) } ) ) } ) ) ) -@WithRetry -@IsolatedYcmd( { - 'extra_conf_globlist': PathToTestFile( 'lombok_project', '*' ) -} ) -def DebugInfo_JvmArgs_test( app ): - StartJavaCompleterServerInDirectory( - app, PathToTestFile( 'lombok_project', 'src' ) ) + @WithRetry() + @IsolatedYcmd( { + 'extra_conf_globlist': PathToTestFile( 'lombok_project', '*' ) + } ) + def test_DebugInfo_JvmArgs( self, app ): + StartJavaCompleterServerInDirectory( + app, PathToTestFile( 'lombok_project', 'src' ) ) - filepath = PathToTestFile( 'lombok_project', - 'src', - 'main', - 'java', - 'com', - 'ycmd', - 'App.java' ) + filepath = PathToTestFile( 'lombok_project', + 'src', + 'main', + 'java', + 'com', + 'ycmd', + 'App.java' ) - request_data = BuildRequest( filepath = filepath, - filetype = 'java' ) + request_data = BuildRequest( filepath = filepath, + filetype = 'java' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'servers': contains_exactly( has_entries( { - 'executable': has_items( starts_with( '-javaagent:' ) ), + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'servers': contains_exactly( has_entries( { + 'executable': has_items( starts_with( '-javaagent:' ) ), + } ) ) } ) ) - } ) ) - ) + ) -@IsolatedYcmd() -@patch( 'watchdog.observers.api.BaseObserver.schedule', - side_effect = RuntimeError ) -def DebugInfo_WorksAfterWatchdogErrors_test( watchdog_schedule, app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) + @IsolatedYcmd() + @patch( 'watchdog.observers.api.BaseObserver.schedule', + side_effect = RuntimeError ) + def test_DebugInfo_WorksAfterWatchdogErrors( self, app, *args ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) - StartJavaCompleterServerInDirectory( app, filepath ) - request_data = BuildRequest( filepath = filepath, - filetype = 'java' ) - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) - connection = completer.GetConnection() - assert_that( calling( connection._HandleDynamicRegistrations ).with_args( - { - 'params': { 'registrations': [ - { - 'method': 'workspace/didChangeWatchedFiles', - 'registerOptions': { - 'watchers': [ { 'globPattern': 'whatever' } ] + StartJavaCompleterServerInDirectory( app, filepath ) + request_data = BuildRequest( filepath = filepath, + filetype = 'java' ) + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + connection = completer.GetConnection() + assert_that( calling( connection._HandleDynamicRegistrations ).with_args( + { + 'params': { 'registrations': [ + { + 'method': 'workspace/didChangeWatchedFiles', + 'registerOptions': { + 'watchers': [ { 'globPattern': 'whatever' } ] + } } - } - ] } - } - ), - raises( RuntimeError ) ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'Java', - 'servers': has_items( has_entries( { - 'name': 'jdt.ls', - 'is_running': True + ] } + } + ), + raises( RuntimeError ) ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'Java', + 'servers': has_items( has_entries( { + 'name': 'jdt.ls', + 'is_running': True + } ) ) } ) ) - } ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + ) diff --git a/ycmd/tests/java/diagnostics_test.py b/ycmd/tests/java/diagnostics_test.py index 8a9d18a1e1..45e6da5729 100644 --- a/ycmd/tests/java/diagnostics_test.py +++ b/ycmd/tests/java/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2020 ycmd contributors +# Copyright (C) 2017-2021 ycmd contributors # # This file is part of ycmd. # @@ -27,12 +27,15 @@ has_entry, has_item, matches_regexp ) +from unittest import TestCase -from ycmd.tests.java import ( DEFAULT_PROJECT_DIR, +from ycmd.tests.java import ( DEFAULT_PROJECT_DIR, # noqa IsolatedYcmd, PathToTestFile, SharedYcmd, - StartJavaCompleterServerInDirectory ) + StartJavaCompleterServerInDirectory, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, LocationMatcher, @@ -250,429 +253,356 @@ def _WaitForDiagnosticsForFile( app, return diags -@WithRetry -@SharedYcmd -def Diagnostics_DetailedDiags_test( app ): - filepath = TestFactory - contents = ReadFile( filepath ) - WaitForDiagnosticsToBeReady( app, filepath, contents, 'java' ) - request_data = BuildRequest( contents = contents, - filepath = filepath, - filetype = 'java', - line_num = 15, - column_num = 19 ) +@contextlib.contextmanager +def PollingThread( app, + messages_for_filepath, + filepath, + contents ): + + done = False + + def PollForMessagesInAnotherThread(): + try: + for message in PollForMessages( app, + { 'filepath': filepath, + 'contents': contents, + 'filetype': 'java' } ): + if done: + return + + if 'filepath' in message and message[ 'filepath' ] == filepath: + messages_for_filepath.append( message ) + except PollForMessagesTimeoutException: + pass + + try: + poller = StartThread( PollForMessagesInAnotherThread ) + yield + finally: + done = True + poller.join( 120 ) + assert not poller.is_alive() + + +class DiagnosticsTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_Diagnostics_DetailedDiags( self, app ): + filepath = TestFactory + contents = ReadFile( filepath ) + WaitForDiagnosticsToBeReady( app, filepath, contents, 'java' ) + request_data = BuildRequest( contents = contents, + filepath = filepath, + filetype = 'java', + line_num = 15, + column_num = 19 ) - results = app.post_json( '/detailed_diagnostic', request_data ).json - assert_that( results, has_entry( - 'message', - 'The value of the field TestFactory.Bar.testString is not used' ) ) + results = app.post_json( '/detailed_diagnostic', request_data ).json + assert_that( results, has_entry( + 'message', + 'The value of the field TestFactory.Bar.testString is not used' ) ) -@WithRetry -@SharedYcmd -def FileReadyToParse_Diagnostics_Simple_test( app ): - filepath = ProjectPath( 'TestFactory.java' ) - contents = ReadFile( filepath ) + @WithRetry() + @SharedYcmd + def test_FileReadyToParse_Diagnostics_Simple( self, app ): + filepath = ProjectPath( 'TestFactory.java' ) + contents = ReadFile( filepath ) - # It can take a while for the diagnostics to be ready - results = WaitForDiagnosticsToBeReady( app, filepath, contents, 'java' ) - print( f'completer response: { pformat( results ) }' ) + # It can take a while for the diagnostics to be ready + results = WaitForDiagnosticsToBeReady( app, filepath, contents, 'java' ) + print( f'completer response: { pformat( results ) }' ) - assert_that( results, DIAG_MATCHERS_PER_FILE[ filepath ] ) + assert_that( results, DIAG_MATCHERS_PER_FILE[ filepath ] ) -@IsolatedYcmd() -def FileReadyToParse_Diagnostics_FileNotOnDisk_test( app ): - StartJavaCompleterServerInDirectory( app, - PathToTestFile( DEFAULT_PROJECT_DIR ) ) + @IsolatedYcmd() + def test_FileReadyToParse_Diagnostics_FileNotOnDisk( self, app ): + StartJavaCompleterServerInDirectory( app, + PathToTestFile( DEFAULT_PROJECT_DIR ) ) - contents = ''' + contents = ''' package com.test; class Test { public String test } ''' - filepath = ProjectPath( 'Test.java' ) - - event_data = BuildRequest( event_name = 'FileReadyToParse', - contents = contents, - filepath = filepath, - filetype = 'java' ) - - results = app.post_json( '/event_notification', event_data ).json - - # This is a new file, so the diagnostics can't possibly be available when the - # initial parse request is sent. We receive these asynchronously. - assert_that( results, empty() ) - - diag_matcher = contains_exactly( has_entries( { - 'kind': 'ERROR', - 'text': 'Syntax error, insert ";" to complete ClassBodyDeclarations ' - '[1610612976]', - 'location': LocationMatcher( filepath, 4, 21 ), - 'location_extent': RangeMatcher( filepath, ( 4, 21 ), ( 4, 25 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 4, 21 ), ( 4, 25 ) ) ), - 'fixit_available': False - } ) ) - - # Poll until we receive the diags - for message in PollForMessages( app, - { 'filepath': filepath, - 'contents': contents, - 'filetype': 'java' } ): - if 'diagnostics' in message and message[ 'filepath' ] == filepath: - print( f'Message { pformat( message ) }' ) - assert_that( message, has_entries( { - 'diagnostics': diag_matcher, - 'filepath': filepath - } ) ) - break - - # Now confirm that we _also_ get these from the FileReadyToParse request - for tries in range( 0, 60 ): - results = app.post_json( '/event_notification', event_data ).json - if results: - break - time.sleep( 0.5 ) - - print( f'completer response: { pformat( results ) }' ) - - assert_that( results, diag_matcher ) + filepath = ProjectPath( 'Test.java' ) + event_data = BuildRequest( event_name = 'FileReadyToParse', + contents = contents, + filepath = filepath, + filetype = 'java' ) -@WithRetry -@IsolatedYcmd() -def Poll_Diagnostics_ProjectWide_Eclipse_test( app ): - StartJavaCompleterServerInDirectory( app, - PathToTestFile( DEFAULT_PROJECT_DIR ) ) + results = app.post_json( '/event_notification', event_data ).json - filepath = TestLauncher - contents = ReadFile( filepath ) + # This is a new file, so the diagnostics can't possibly be available when + # the initial parse request is sent. We receive these asynchronously. + assert_that( results, empty() ) - # Poll until we receive _all_ the diags asynchronously - to_see = sorted( DIAG_MATCHERS_PER_FILE.keys() ) - seen = {} + diag_matcher = contains_exactly( has_entries( { + 'kind': 'ERROR', + 'text': 'Syntax error, insert ";" to complete ClassBodyDeclarations ' + '[1610612976]', + 'location': LocationMatcher( filepath, 4, 21 ), + 'location_extent': RangeMatcher( filepath, ( 4, 21 ), ( 4, 25 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 4, 21 ), ( 4, 25 ) ) ), + 'fixit_available': False + } ) ) - try: + # Poll until we receive the diags for message in PollForMessages( app, { 'filepath': filepath, 'contents': contents, 'filetype': 'java' } ): - print( f'Message { pformat( message ) }' ) - if 'diagnostics' in message: - seen[ message[ 'filepath' ] ] = True - if message[ 'filepath' ] not in DIAG_MATCHERS_PER_FILE: - raise AssertionError( 'Received diagnostics for unexpected file ' - f'{ message[ "filepath" ] }. Only expected { to_see }' ) + if 'diagnostics' in message and message[ 'filepath' ] == filepath: + print( f'Message { pformat( message ) }' ) assert_that( message, has_entries( { - 'diagnostics': DIAG_MATCHERS_PER_FILE[ message[ 'filepath' ] ], - 'filepath': message[ 'filepath' ] + 'diagnostics': diag_matcher, + 'filepath': filepath } ) ) + break - if sorted( seen.keys() ) == to_see: + # Now confirm that we _also_ get these from the FileReadyToParse request + for tries in range( 0, 60 ): + results = app.post_json( '/event_notification', event_data ).json + if results: break - else: - print( 'Seen diagnostics for {0}, still waiting for {1}'.format( - json.dumps( sorted( seen.keys() ), indent = 2 ), - json.dumps( [ x for x in to_see if x not in seen ], indent = 2 ) ) ) - - # Eventually PollForMessages will throw - # a timeout exception and we'll fail - # if we don't see all of the expected diags - except PollForMessagesTimeoutException as e: - raise AssertionError( - str( e ) + - 'Timed out waiting for full set of diagnostics. ' - f'Expected to see diags for { json.dumps( to_see, indent = 2 ) }, ' - f'but only saw { json.dumps( sorted( seen.keys() ), indent = 2 ) }.' ) + time.sleep( 0.5 ) + print( f'completer response: { pformat( results ) }' ) -@contextlib.contextmanager -def PollingThread( app, - messages_for_filepath, - filepath, - contents ): + assert_that( results, diag_matcher ) - done = False - def PollForMessagesInAnotherThread(): + @WithRetry() + @IsolatedYcmd() + def test_Poll_Diagnostics_ProjectWide_Eclipse( self, app ): + StartJavaCompleterServerInDirectory( app, + PathToTestFile( DEFAULT_PROJECT_DIR ) ) + + filepath = TestLauncher + contents = ReadFile( filepath ) + + # Poll until we receive _all_ the diags asynchronously + to_see = sorted( DIAG_MATCHERS_PER_FILE.keys() ) + seen = {} + try: for message in PollForMessages( app, { 'filepath': filepath, 'contents': contents, 'filetype': 'java' } ): - if done: - return - - if 'filepath' in message and message[ 'filepath' ] == filepath: - messages_for_filepath.append( message ) - except PollForMessagesTimeoutException: - pass - - try: - poller = StartThread( PollForMessagesInAnotherThread ) - yield - finally: - done = True - poller.join( 120 ) - assert not poller.is_alive() - - -@WithRetry -@IsolatedYcmd() -def Poll_Diagnostics_ChangeFileContents_test( app ): - StartJavaCompleterServerInDirectory( app, - PathToTestFile( DEFAULT_PROJECT_DIR ) ) + print( f'Message { pformat( message ) }' ) + if 'diagnostics' in message: + seen[ message[ 'filepath' ] ] = True + if message[ 'filepath' ] not in DIAG_MATCHERS_PER_FILE: + raise AssertionError( 'Received diagnostics for unexpected file ' + f'{ message[ "filepath" ] }. Only expected { to_see }' ) + assert_that( message, has_entries( { + 'diagnostics': DIAG_MATCHERS_PER_FILE[ message[ 'filepath' ] ], + 'filepath': message[ 'filepath' ] + } ) ) - filepath = youcompleteme_Test - old_contents = """package com.youcompleteme; + if sorted( seen.keys() ) == to_see: + break + else: + print( 'Seen diagnostics for {0}, still waiting for {1}'.format( + json.dumps( sorted( seen.keys() ), indent = 2 ), + json.dumps( [ x for x in to_see if x not in seen ], indent = 2 ) ) ) + + # Eventually PollForMessages will throw + # a timeout exception and we'll fail + # if we don't see all of the expected diags + except PollForMessagesTimeoutException as e: + raise AssertionError( + str( e ) + + 'Timed out waiting for full set of diagnostics. ' + f'Expected to see diags for { json.dumps( to_see, indent = 2 ) }, ' + f'but only saw { json.dumps( sorted( seen.keys() ), indent = 2 ) }.' ) + + + @WithRetry() + @IsolatedYcmd() + def test_Poll_Diagnostics_ChangeFileContents( self, app ): + StartJavaCompleterServerInDirectory( app, + PathToTestFile( DEFAULT_PROJECT_DIR ) ) + + filepath = youcompleteme_Test + old_contents = """package com.youcompleteme; public class Test { public String test; }""" - messages_for_filepath = [] + messages_for_filepath = [] - with PollingThread( app, - messages_for_filepath, - filepath, - old_contents ): + with PollingThread( app, + messages_for_filepath, + filepath, + old_contents ): - new_contents = """package com.youcompleteme; + new_contents = """package com.youcompleteme; public class Test { public String test; public String test; }""" - event_data = BuildRequest( event_name = 'FileReadyToParse', - contents = new_contents, - filepath = filepath, - filetype = 'java' ) - app.post_json( '/event_notification', event_data ).json - - expiration = time.time() + 10 - while True: - try: - assert_that( - messages_for_filepath, - has_item( has_entries( { - 'filepath': filepath, - 'diagnostics': contains_exactly( - has_entries( { - 'kind': 'ERROR', - 'text': 'Duplicate field Test.test [33554772]', - 'location': LocationMatcher( youcompleteme_Test, 4, 17 ), - 'location_extent': RangeMatcher( youcompleteme_Test, - ( 4, 17 ), - ( 4, 21 ) ), - 'ranges': contains_exactly( RangeMatcher( youcompleteme_Test, - ( 4, 17 ), - ( 4, 21 ) ) ), - 'fixit_available': False - } ), - has_entries( { - 'kind': 'ERROR', - 'text': 'Duplicate field Test.test [33554772]', - 'location': LocationMatcher( youcompleteme_Test, 5, 17 ), - 'location_extent': RangeMatcher( youcompleteme_Test, - ( 5, 17 ), - ( 5, 21 ) ), - 'ranges': contains_exactly( RangeMatcher( youcompleteme_Test, - ( 5, 17 ), - ( 5, 21 ) ) ), - 'fixit_available': False - } ) - ) - } ) ) - ) - break - except AssertionError: - if time.time() > expiration: - raise + event_data = BuildRequest( event_name = 'FileReadyToParse', + contents = new_contents, + filepath = filepath, + filetype = 'java' ) + app.post_json( '/event_notification', event_data ).json + + expiration = time.time() + 10 + while True: + try: + assert_that( + messages_for_filepath, + has_item( has_entries( { + 'filepath': filepath, + 'diagnostics': contains_exactly( + has_entries( { + 'kind': 'ERROR', + 'text': 'Duplicate field Test.test [33554772]', + 'location': LocationMatcher( youcompleteme_Test, 4, 17 ), + 'location_extent': RangeMatcher( youcompleteme_Test, + ( 4, 17 ), + ( 4, 21 ) ), + 'ranges': contains_exactly( RangeMatcher( youcompleteme_Test, + ( 4, 17 ), + ( 4, 21 ) ) ), + 'fixit_available': False + } ), + has_entries( { + 'kind': 'ERROR', + 'text': 'Duplicate field Test.test [33554772]', + 'location': LocationMatcher( youcompleteme_Test, 5, 17 ), + 'location_extent': RangeMatcher( youcompleteme_Test, + ( 5, 17 ), + ( 5, 21 ) ), + 'ranges': contains_exactly( RangeMatcher( youcompleteme_Test, + ( 5, 17 ), + ( 5, 21 ) ) ), + 'fixit_available': False + } ) + ) + } ) ) + ) + break + except AssertionError: + if time.time() > expiration: + raise - time.sleep( 0.25 ) + time.sleep( 0.25 ) -@IsolatedYcmd() -def FileReadyToParse_ServerNotReady_test( app ): - filepath = TestFactory - contents = ReadFile( filepath ) + @IsolatedYcmd() + def test_FileReadyToParse_ServerNotReady( self, app ): + filepath = TestFactory + contents = ReadFile( filepath ) - StartJavaCompleterServerInDirectory( app, ProjectPath() ) + StartJavaCompleterServerInDirectory( app, ProjectPath() ) - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) - # It can take a while for the diagnostics to be ready - for tries in range( 0, 60 ): - event_data = BuildRequest( event_name = 'FileReadyToParse', - contents = contents, - filepath = filepath, - filetype = 'java' ) + # It can take a while for the diagnostics to be ready + for tries in range( 0, 60 ): + event_data = BuildRequest( event_name = 'FileReadyToParse', + contents = contents, + filepath = filepath, + filetype = 'java' ) - results = app.post_json( '/event_notification', event_data ).json + results = app.post_json( '/event_notification', event_data ).json - if results: - break + if results: + break - time.sleep( 0.5 ) + time.sleep( 0.5 ) - # To make the test fair, we make sure there are some results prior to the - # 'server not running' call - assert results + # To make the test fair, we make sure there are some results prior to the + # 'server not running' call + assert results - # Call the FileReadyToParse handler but pretend that the server isn't running - with patch.object( completer, 'ServerIsHealthy', return_value = False ): - event_data = BuildRequest( event_name = 'FileReadyToParse', - contents = contents, - filepath = filepath, - filetype = 'java' ) - results = app.post_json( '/event_notification', event_data ).json - assert_that( results, empty() ) + # Call the FileReadyToParse handler but pretend that the server isn't + # running + with patch.object( completer, 'ServerIsHealthy', return_value = False ): + event_data = BuildRequest( event_name = 'FileReadyToParse', + contents = contents, + filepath = filepath, + filetype = 'java' ) + results = app.post_json( '/event_notification', event_data ).json + assert_that( results, empty() ) + + + @IsolatedYcmd() + def test_FileReadyToParse_ChangeFileContents( self, app ): + filepath = TestFactory + contents = ReadFile( filepath ) + + StartJavaCompleterServerInDirectory( app, ProjectPath() ) + # It can take a while for the diagnostics to be ready + for tries in range( 0, 60 ): + event_data = BuildRequest( event_name = 'FileReadyToParse', + contents = contents, + filepath = filepath, + filetype = 'java' ) -@IsolatedYcmd() -def FileReadyToParse_ChangeFileContents_test( app ): - filepath = TestFactory - contents = ReadFile( filepath ) + results = app.post_json( '/event_notification', event_data ).json - StartJavaCompleterServerInDirectory( app, ProjectPath() ) + if results: + break + + time.sleep( 0.5 ) - # It can take a while for the diagnostics to be ready - for tries in range( 0, 60 ): + # To make the test fair, we make sure there are some results prior to the + # 'server not running' call + assert results + + # Call the FileReadyToParse handler but pretend that the server isn't + # running + contents = 'package com.test; class TestFactory {}' + # It can take a while for the diagnostics to be ready event_data = BuildRequest( event_name = 'FileReadyToParse', contents = contents, filepath = filepath, filetype = 'java' ) - results = app.post_json( '/event_notification', event_data ).json + app.post_json( '/event_notification', event_data ) - if results: - break - - time.sleep( 0.5 ) - - # To make the test fair, we make sure there are some results prior to the - # 'server not running' call - assert results + diags = None + try: + for message in PollForMessages( app, + { 'filepath': filepath, + 'contents': contents, + 'filetype': 'java' } ): + print( f'Message { pformat( message ) }' ) + if 'diagnostics' in message and message[ 'filepath' ] == filepath: + diags = message[ 'diagnostics' ] + if not diags: + break - # Call the FileReadyToParse handler but pretend that the server isn't running - contents = 'package com.test; class TestFactory {}' - # It can take a while for the diagnostics to be ready - event_data = BuildRequest( event_name = 'FileReadyToParse', - contents = contents, - filepath = filepath, - filetype = 'java' ) + # Eventually PollForMessages will throw a timeout exception and we'll fail + # if we don't see the diagnostics go empty + except PollForMessagesTimeoutException as e: + raise AssertionError( + f'{ e }. Timed out waiting for diagnostics to clear for updated file. ' + f'Expected to see none, but diags were: { diags }' ) - app.post_json( '/event_notification', event_data ) + assert_that( diags, empty() ) - diags = None - try: - for message in PollForMessages( app, - { 'filepath': filepath, - 'contents': contents, - 'filetype': 'java' } ): - print( f'Message { pformat( message ) }' ) - if 'diagnostics' in message and message[ 'filepath' ] == filepath: - diags = message[ 'diagnostics' ] - if not diags: - break + # Close the file (ensuring no exception) + event_data = BuildRequest( event_name = 'BufferUnload', + contents = contents, + filepath = filepath, + filetype = 'java' ) + result = app.post_json( '/event_notification', event_data ).json + assert_that( result, equal_to( {} ) ) - # Eventually PollForMessages will throw a timeout exception and we'll fail - # if we don't see the diagnostics go empty - except PollForMessagesTimeoutException as e: - raise AssertionError( - f'{ e }. Timed out waiting for diagnostics to clear for updated file. ' - f'Expected to see none, but diags were: { diags }' ) - - assert_that( diags, empty() ) - - # Close the file (ensuring no exception) - event_data = BuildRequest( event_name = 'BufferUnload', - contents = contents, - filepath = filepath, - filetype = 'java' ) - result = app.post_json( '/event_notification', event_data ).json - assert_that( result, equal_to( {} ) ) - - # Close the file again, someone erroneously (ensuring no exception) - event_data = BuildRequest( event_name = 'BufferUnload', - contents = contents, - filepath = filepath, - filetype = 'java' ) - result = app.post_json( '/event_notification', event_data ).json - assert_that( result, equal_to( {} ) ) - - -@IsolatedYcmd() -def FileReadyToParse_ChangeFileContentsFileData_test( app ): - filepath = TestFactory - contents = ReadFile( filepath ) - unsaved_buffer_path = TestLauncher - file_data = { - unsaved_buffer_path: { - 'contents': 'package com.test; public class TestLauncher {}', - 'filetypes': [ 'java' ], - } - } - - StartJavaCompleterServerInDirectory( app, ProjectPath() ) - - # It can take a while for the diagnostics to be ready - results = WaitForDiagnosticsToBeReady( app, filepath, contents, 'java' ) - assert results - - # Check that we have diagnostics for the saved file - diags = _WaitForDiagnosticsForFile( app, - filepath, - contents, - unsaved_buffer_path, - lambda d: d ) - assert_that( diags, DIAG_MATCHERS_PER_FILE[ unsaved_buffer_path ] ) - - # Now update the unsaved file with new contents - event_data = BuildRequest( event_name = 'FileReadyToParse', - contents = contents, - filepath = filepath, - filetype = 'java', - file_data = file_data ) - app.post_json( '/event_notification', event_data ) - - # Check that we have no diagnostics for the dirty file - diags = _WaitForDiagnosticsForFile( app, - filepath, - contents, - unsaved_buffer_path, - lambda d: not d ) - assert_that( diags, empty() ) - - # Now send the request again, but don't include the unsaved file. It should be - # read from disk, causing the diagnostics for that file to appear. - event_data = BuildRequest( event_name = 'FileReadyToParse', - contents = contents, - filepath = filepath, - filetype = 'java' ) - app.post_json( '/event_notification', event_data ) - - # Check that we now have diagnostics for the previously-dirty file - diags = _WaitForDiagnosticsForFile( app, - filepath, - contents, - unsaved_buffer_path, - lambda d: d ) - - assert_that( diags, DIAG_MATCHERS_PER_FILE[ unsaved_buffer_path ] ) - - -@WithRetry -@SharedYcmd -def OnBufferUnload_ServerNotRunning_test( app ): - filepath = TestFactory - contents = ReadFile( filepath ) - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) - - with patch.object( completer, 'ServerIsHealthy', return_value = False ): + # Close the file again, someone erroneously (ensuring no exception) event_data = BuildRequest( event_name = 'BufferUnload', contents = contents, filepath = filepath, @@ -681,106 +611,177 @@ def OnBufferUnload_ServerNotRunning_test( app ): assert_that( result, equal_to( {} ) ) -@IsolatedYcmd() -def PollForMessages_InvalidUri_test( app, *args ): - StartJavaCompleterServerInDirectory( - app, - PathToTestFile( 'simple_eclipse_project' ) ) + @IsolatedYcmd() + def test_FileReadyToParse_ChangeFileContentsFileData( self, app ): + filepath = TestFactory + contents = ReadFile( filepath ) + unsaved_buffer_path = TestLauncher + file_data = { + unsaved_buffer_path: { + 'contents': 'package com.test; public class TestLauncher {}', + 'filetypes': [ 'java' ], + } + } - filepath = TestFactory - contents = ReadFile( filepath ) + StartJavaCompleterServerInDirectory( app, ProjectPath() ) - with patch( - 'ycmd.completers.language_server.language_server_protocol.UriToFilePath', - side_effect = lsp.InvalidUriException ): + # It can take a while for the diagnostics to be ready + results = WaitForDiagnosticsToBeReady( app, filepath, contents, 'java' ) + assert results - for tries in range( 0, 5 ): - response = app.post_json( '/receive_messages', - BuildRequest( - filetype = 'java', - filepath = filepath, - contents = contents ) ).json - if response is True: - break - elif response is False: - raise AssertionError( 'Message poll was aborted unexpectedly' ) - elif 'diagnostics' in response: - raise AssertionError( 'Did not expect diagnostics when file paths ' - 'are invalid' ) + # Check that we have diagnostics for the saved file + diags = _WaitForDiagnosticsForFile( app, + filepath, + contents, + unsaved_buffer_path, + lambda d: d ) + assert_that( diags, DIAG_MATCHERS_PER_FILE[ unsaved_buffer_path ] ) - time.sleep( 0.5 ) + # Now update the unsaved file with new contents + event_data = BuildRequest( event_name = 'FileReadyToParse', + contents = contents, + filepath = filepath, + filetype = 'java', + file_data = file_data ) + app.post_json( '/event_notification', event_data ) + + # Check that we have no diagnostics for the dirty file + diags = _WaitForDiagnosticsForFile( app, + filepath, + contents, + unsaved_buffer_path, + lambda d: not d ) + assert_that( diags, empty() ) + + # Now send the request again, but don't include the unsaved file. It should + # be read from disk, causing the diagnostics for that file to appear. + event_data = BuildRequest( event_name = 'FileReadyToParse', + contents = contents, + filepath = filepath, + filetype = 'java' ) + app.post_json( '/event_notification', event_data ) + + # Check that we now have diagnostics for the previously-dirty file + diags = _WaitForDiagnosticsForFile( app, + filepath, + contents, + unsaved_buffer_path, + lambda d: d ) + + assert_that( diags, DIAG_MATCHERS_PER_FILE[ unsaved_buffer_path ] ) + + + @WithRetry() + @SharedYcmd + def test_OnBufferUnload_ServerNotRunning( self, app ): + filepath = TestFactory + contents = ReadFile( filepath ) + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + + with patch.object( completer, 'ServerIsHealthy', return_value = False ): + event_data = BuildRequest( event_name = 'BufferUnload', + contents = contents, + filepath = filepath, + filetype = 'java' ) + result = app.post_json( '/event_notification', event_data ).json + assert_that( result, equal_to( {} ) ) + + + @IsolatedYcmd() + def test_PollForMessages_InvalidUri( self, app ): + StartJavaCompleterServerInDirectory( + app, + PathToTestFile( 'simple_eclipse_project' ) ) + + filepath = TestFactory + contents = ReadFile( filepath ) + + with patch( + 'ycmd.completers.language_server.language_server_protocol.UriToFilePath', + side_effect = lsp.InvalidUriException ): + + for tries in range( 0, 5 ): + response = app.post_json( '/receive_messages', + BuildRequest( + filetype = 'java', + filepath = filepath, + contents = contents ) ).json + if response is True: + break + elif response is False: + raise AssertionError( 'Message poll was aborted unexpectedly' ) + elif 'diagnostics' in response: + raise AssertionError( 'Did not expect diagnostics when file paths ' + 'are invalid' ) + + time.sleep( 0.5 ) + + assert_that( response, equal_to( True ) ) + + + @IsolatedYcmd() + @patch.object( completer, 'MESSAGE_POLL_TIMEOUT', 2 ) + def test_PollForMessages_ServerNotRunning( self, app ): + StartJavaCompleterServerInDirectory( + app, + PathToTestFile( 'simple_eclipse_project' ) ) + + filepath = TestFactory + contents = ReadFile( filepath ) + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'java', + command_arguments = [ 'StopServer' ], + ), + ) - assert_that( response, equal_to( True ) ) - - -@IsolatedYcmd() -@patch.object( completer, 'MESSAGE_POLL_TIMEOUT', 2 ) -def PollForMessages_ServerNotRunning_test( app ): - StartJavaCompleterServerInDirectory( - app, - PathToTestFile( 'simple_eclipse_project' ) ) - - filepath = TestFactory - contents = ReadFile( filepath ) - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'java', - command_arguments = [ 'StopServer' ], - ), - ) - - response = app.post_json( '/receive_messages', - BuildRequest( - filetype = 'java', - filepath = filepath, - contents = contents ) ).json - - assert_that( response, equal_to( False ) ) - - -@IsolatedYcmd() -def PollForMessages_AbortedWhenServerDies_test( app ): - StartJavaCompleterServerInDirectory( - app, - PathToTestFile( 'simple_eclipse_project' ) ) - - filepath = TestFactory - contents = ReadFile( filepath ) - - state = { - 'aborted': False - } - - def AwaitMessages(): - max_tries = 20 - for tries in range( 0, max_tries ): - response = app.post_json( '/receive_messages', - BuildRequest( - filetype = 'java', - filepath = filepath, - contents = contents ) ).json - if response is False: - state[ 'aborted' ] = True - return + response = app.post_json( '/receive_messages', + BuildRequest( + filetype = 'java', + filepath = filepath, + contents = contents ) ).json - raise AssertionError( - f'The poll request was not aborted in { max_tries } tries' ) + assert_that( response, equal_to( False ) ) - message_poll_task = StartThread( AwaitMessages ) - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'java', - command_arguments = [ 'StopServer' ], - ), - ) + @IsolatedYcmd() + def test_PollForMessages_AbortedWhenServerDies( self, app ): + StartJavaCompleterServerInDirectory( + app, + PathToTestFile( 'simple_eclipse_project' ) ) - message_poll_task.join() - assert_that( state[ 'aborted' ] ) + filepath = TestFactory + contents = ReadFile( filepath ) + state = { + 'aborted': False + } + + def AwaitMessages(): + max_tries = 20 + for tries in range( 0, max_tries ): + response = app.post_json( '/receive_messages', + BuildRequest( + filetype = 'java', + filepath = filepath, + contents = contents ) ).json + if response is False: + state[ 'aborted' ] = True + return + + raise AssertionError( + f'The poll request was not aborted in { max_tries } tries' ) + + message_poll_task = StartThread( AwaitMessages ) + + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'java', + command_arguments = [ 'StopServer' ], + ), + ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + message_poll_task.join() + assert_that( state[ 'aborted' ] ) diff --git a/ycmd/tests/java/get_completions_test.py b/ycmd/tests/java/get_completions_test.py index df87c9d34a..1cd487fcca 100644 --- a/ycmd/tests/java/get_completions_test.py +++ b/ycmd/tests/java/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2020 ycmd contributors +# Copyright (C) 2017-2021 ycmd contributors # # This file is part of ycmd. # @@ -27,16 +27,19 @@ has_key, instance_of, is_not ) +from unittest import TestCase from pprint import pformat import requests import os from ycmd import handlers -from ycmd.tests.java import ( DEFAULT_PROJECT_DIR, +from ycmd.tests.java import ( DEFAULT_PROJECT_DIR, # noqa IsolatedYcmd, PathToTestFile, - SharedYcmd ) + SharedYcmd, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import ( ClearCompletionsCache, CombineRequest, ChunkMatcher, @@ -48,6 +51,7 @@ from ycmd.utils import ReadFile from unittest.mock import patch from ycmd.completers.completer import CompletionsChanged +from ycmd.completers.language_server import language_server_protocol as lsapi def ProjectPath( *args ): @@ -142,90 +146,60 @@ def WithObjectMethods( *args ): return list( PUBLIC_OBJECT_METHODS ) + list( args ) -@WithRetry -@SharedYcmd -def GetCompletions_NoQuery_test( app ): - RunTest( app, { - 'description': 'semantic completion works for builtin types (no query)', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestFactory.java' ), - 'line_num' : 27, - 'column_num': 12, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_items( - CompletionEntryMatcher( 'test', 'TestFactory.Bar.test : int', { - 'kind': 'Field' - } ), - CompletionEntryMatcher( 'testString', - 'TestFactory.Bar.testString : String', - { - 'kind': 'Field' - } ) - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_WithQuery_test( app ): - RunTest( app, { - 'description': 'semantic completion works for builtin types (with query)', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestFactory.java' ), - 'line_num' : 27, - 'column_num': 15, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'test', 'TestFactory.Bar.test : int', { - 'kind': 'Field' - } ), - CompletionEntryMatcher( 'testString', - 'TestFactory.Bar.testString : String', - { - 'kind': 'Field' - } ) - ), - 'errors': empty(), - } ) - }, - } ) +class GetCompletionsTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_GetCompletions_NoQuery( self, app ): + RunTest( app, { + 'description': 'semantic completion works for builtin types (no query)', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestFactory.java' ), + 'line_num' : 27, + 'column_num': 12, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_items( + CompletionEntryMatcher( 'test', 'TestFactory.Bar.test : int', { + 'kind': 'Field' + } ), + CompletionEntryMatcher( 'testString', + 'TestFactory.Bar.testString : String', + { + 'kind': 'Field' + } ) + ), + 'errors': empty(), + } ) + }, + } ) -@WithRetry -@SharedYcmd -def GetCompletions_DetailFromCache_test( app ): - for i in range( 0, 2 ): + @WithRetry() + @SharedYcmd + def test_GetCompletions_WithQuery( self, app ): RunTest( app, { - 'description': 'completion works when the elements come from the cache', + 'description': 'semantic completion works for builtin types (with query)', 'request': { 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestLauncher.java' ), - 'line_num' : 32, + 'filepath' : ProjectPath( 'TestFactory.java' ), + 'line_num' : 27, 'column_num': 15, }, 'expect': { 'response': requests.codes.ok, 'data': has_entries( { - 'completion_start_column': 11, - 'completions': has_item( - CompletionEntryMatcher( - 'doSomethingVaguelyUseful', - 'AbstractTestWidget.doSomethingVaguelyUseful() : void', - { - 'kind': 'Method', - 'menu_text': 'doSomethingVaguelyUseful() : void', - } ) + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'test', 'TestFactory.Bar.test : int', { + 'kind': 'Field' + } ), + CompletionEntryMatcher( 'testString', + 'TestFactory.Bar.testString : String', + { + 'kind': 'Field' + } ) ), 'errors': empty(), } ) @@ -233,266 +207,247 @@ def GetCompletions_DetailFromCache_test( app ): } ) -@WithRetry -@SharedYcmd -def GetCompletions_Package_test( app ): - RunTest( app, { - 'description': 'completion works for package statements', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'wobble', 'Wibble.java' ), - 'line_num' : 1, - 'column_num': 18, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 9, - 'completions': contains_exactly( - CompletionEntryMatcher( 'com.test.wobble', None, { - 'kind': 'Module' - } ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_Import_Class_test( app ): - RunTest( app, { - 'description': 'completion works for import statements with a single class', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestLauncher.java' ), - 'line_num' : 3, - 'column_num': 34, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 34, - 'completions': contains_exactly( - CompletionEntryMatcher( 'Tset', 'com.youcompleteme.testing.Tset', { - 'menu_text': 'Tset - com.youcompleteme.testing', - 'kind': 'Class', + @WithRetry() + @SharedYcmd + def test_GetCompletions_DetailFromCache( self, app ): + for i in range( 0, 2 ): + RunTest( app, { + 'description': 'completion works when the elements come from the cache', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestLauncher.java' ), + 'line_num' : 32, + 'column_num': 15, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 11, + 'completions': has_item( + CompletionEntryMatcher( + 'doSomethingVaguelyUseful', + 'AbstractTestWidget.doSomethingVaguelyUseful() : void', + { + 'kind': 'Method', + 'menu_text': 'doSomethingVaguelyUseful() : void', + } ) + ), + 'errors': empty(), } ) - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_Import_Classes_test( app ): - filepath = ProjectPath( 'TestLauncher.java' ) - RunTest( app, { - 'description': 'completion works for imports with multiple classes', - 'request': { - 'filetype' : 'java', - 'filepath' : filepath, - 'line_num' : 4, - 'column_num': 52, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 52, - 'completions': contains_exactly( - CompletionEntryMatcher( 'A;', None, { - 'menu_text': 'A - com.test.wobble', - 'kind': 'Class', - } ), - CompletionEntryMatcher( 'A_Very_Long_Class_Here;', None, { - 'menu_text': 'A_Very_Long_Class_Here - com.test.wobble', - 'kind': 'Class', - } ), - CompletionEntryMatcher( 'Waggle;', None, { - 'menu_text': 'Waggle - com.test.wobble', - 'kind': 'Interface', - } ), - CompletionEntryMatcher( 'Wibble;', None, { - 'menu_text': 'Wibble - com.test.wobble', - 'kind': 'Enum', - } ), - ), - 'errors': empty(), + }, } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_Import_ModuleAndClass_test( app ): - filepath = ProjectPath( 'TestLauncher.java' ) - RunTest( app, { - 'description': 'completion works for imports of classes and modules', - 'request': { - 'filetype' : 'java', - 'filepath' : filepath, - 'line_num' : 3, - 'column_num': 26, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 26, - 'completions': contains_exactly( - CompletionEntryMatcher( 'testing.*;', None, { - 'menu_text': 'com.youcompleteme.testing', - 'kind': 'Module', - } ), - CompletionEntryMatcher( 'Test;', None, { - 'menu_text': 'Test - com.youcompleteme', - 'kind': 'Class', - } ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_WithFixIt_test( app ): - filepath = ProjectPath( 'TestFactory.java' ) - RunTest( app, { - 'description': 'semantic completion with when additional textEdit', - 'request': { - 'filetype' : 'java', - 'filepath' : filepath, - 'line_num' : 19, - 'column_num': 25, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 22, - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'CUTHBERT', - 'com.test.wobble.Wibble.CUTHBERT : Wibble', - { - 'kind': 'EnumMember', - 'extra_data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( 'Wibble', - LocationMatcher( filepath, 19, 15 ), - LocationMatcher( filepath, 19, 21 ) ), - # OK, so it inserts the import - ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n', - LocationMatcher( filepath, 1, 18 ), - LocationMatcher( filepath, 3, 1 ) ), - ), - } ) ), - } ), + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_Package( self, app ): + RunTest( app, { + 'description': 'completion works for package statements', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'wobble', 'Wibble.java' ), + 'line_num' : 1, + 'column_num': 18, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 9, + 'completions': contains_exactly( + CompletionEntryMatcher( 'com.test.wobble', None, { + 'kind': 'Module' } ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_RejectMultiLineInsertion_test( app ): - filepath = ProjectPath( 'TestLauncher.java' ) - RunTest( app, { - 'description': 'completion item discarded when not valid', - 'request': { - 'filetype' : 'java', - 'filepath' : filepath, - 'line_num' : 28, - 'column_num' : 16, - 'force_semantic': True - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 16, - 'completions': contains_exactly( - CompletionEntryMatcher( 'TestLauncher', - 'com.test.TestLauncher.TestLauncher(int test)', - { - 'kind': 'Constructor' + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_Import_Class( self, app ): + RunTest( app, { + 'description': 'completion works for import ' + 'statements with a single class', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestLauncher.java' ), + 'line_num' : 3, + 'column_num': 34, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 34, + 'completions': contains_exactly( + CompletionEntryMatcher( 'Tset', 'com.youcompleteme.testing.Tset', { + 'menu_text': 'Tset - com.youcompleteme.testing', + 'kind': 'Class', } ) - # Note: There would be a suggestion here for the _real_ thing we want, - # which is a TestLauncher.Launchable, but this would generate the code - # for an anonymous inner class via a completion TextEdit (not - # AdditionalTextEdit) which we don't support. - ), - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_UnicodeIdentifier_test( app ): - filepath = PathToTestFile( DEFAULT_PROJECT_DIR, - 'src', - 'com', - 'youcompleteme', - 'Test.java' ) - RunTest( app, { - 'description': 'Completion works for unicode identifier', - 'request': { - 'filetype' : 'java', - 'filepath' : filepath, - 'line_num' : 16, - 'column_num' : 35, - 'force_semantic': True - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 35, - 'completions': has_items( - CompletionEntryMatcher( 'a_test', 'Test.TéstClass.a_test : int', { - 'kind': 'Field', - 'detailed_info': 'a_test : int\n\n', - } ), - CompletionEntryMatcher( 'åtest', 'Test.TéstClass.åtest : boolean', { - 'kind': 'Field', - 'detailed_info': 'åtest : boolean\n\n', - } ), - CompletionEntryMatcher( 'testywesty', - 'Test.TéstClass.testywesty : String', - { - 'kind': 'Field', - } ), - ), - 'errors': empty(), - } ) - }, - } ) + ), + 'errors': empty(), + } ) + }, + } ) + + @WithRetry() + @SharedYcmd + def test_GetCompletions_Import_Classes( self, app ): + filepath = ProjectPath( 'TestLauncher.java' ) + RunTest( app, { + 'description': 'completion works for imports with multiple classes', + 'request': { + 'filetype' : 'java', + 'filepath' : filepath, + 'line_num' : 4, + 'column_num': 52, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 52, + 'completions': contains_exactly( + CompletionEntryMatcher( 'A;', None, { + 'menu_text': 'A - com.test.wobble', + 'kind': 'Class', + } ), + CompletionEntryMatcher( 'A_Very_Long_Class_Here;', None, { + 'menu_text': 'A_Very_Long_Class_Here - com.test.wobble', + 'kind': 'Class', + } ), + CompletionEntryMatcher( 'Waggle;', None, { + 'menu_text': 'Waggle - com.test.wobble', + 'kind': 'Interface', + } ), + CompletionEntryMatcher( 'Wibble;', None, { + 'menu_text': 'Wibble - com.test.wobble', + 'kind': 'Enum', + } ), + ), + 'errors': empty(), + } ) + }, + } ) -@WithRetry -@SharedYcmd -def GetCompletions_ResolveFailed_test( app ): - filepath = PathToTestFile( DEFAULT_PROJECT_DIR, - 'src', - 'com', - 'youcompleteme', - 'Test.java' ) - from ycmd.completers.language_server import language_server_protocol as lsapi + @WithRetry() + @SharedYcmd + def test_GetCompletions_Import_ModuleAndClass( self, app ): + filepath = ProjectPath( 'TestLauncher.java' ) + RunTest( app, { + 'description': 'completion works for imports of classes and modules', + 'request': { + 'filetype' : 'java', + 'filepath' : filepath, + 'line_num' : 3, + 'column_num': 26, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 26, + 'completions': contains_exactly( + CompletionEntryMatcher( 'testing.*;', None, { + 'menu_text': 'com.youcompleteme.testing', + 'kind': 'Module', + } ), + CompletionEntryMatcher( 'Test;', None, { + 'menu_text': 'Test - com.youcompleteme', + 'kind': 'Class', + } ), + ), + 'errors': empty(), + } ) + }, + } ) - def BrokenResolveCompletion( request_id, completion ): - return lsapi.BuildRequest( request_id, 'completionItem/FAIL', completion ) - with patch( 'ycmd.completers.language_server.language_server_protocol.' - 'ResolveCompletion', - side_effect = BrokenResolveCompletion ): + @WithRetry() + @SharedYcmd + def test_GetCompletions_WithFixIt( self, app ): + filepath = ProjectPath( 'TestFactory.java' ) + RunTest( app, { + 'description': 'semantic completion with when additional textEdit', + 'request': { + 'filetype' : 'java', + 'filepath' : filepath, + 'line_num' : 19, + 'column_num': 25, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 22, + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'CUTHBERT', + 'com.test.wobble.Wibble.CUTHBERT : Wibble', + { + 'kind': 'EnumMember', + 'extra_data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( 'Wibble', + LocationMatcher( filepath, 19, 15 ), + LocationMatcher( filepath, 19, 21 ) ), + # OK, so it inserts the import + ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n', + LocationMatcher( filepath, 1, 18 ), + LocationMatcher( filepath, 3, 1 ) ), + ), + } ) ), + } ), + } ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_RejectMultiLineInsertion( self, app ): + filepath = ProjectPath( 'TestLauncher.java' ) + RunTest( app, { + 'description': 'completion item discarded when not valid', + 'request': { + 'filetype' : 'java', + 'filepath' : filepath, + 'line_num' : 28, + 'column_num' : 16, + 'force_semantic': True + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 16, + 'completions': contains_exactly( + CompletionEntryMatcher( 'TestLauncher', + 'com.test.TestLauncher.TestLauncher(int test)', + { + 'kind': 'Constructor' + } ) + # Note: There would be a suggestion here for the _real_ thing we + # want, which is a TestLauncher.Launchable, but this would generate + # the code for an anonymous inner class via a completion TextEdit + # (not AdditionalTextEdit) which we don't support. + ), + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_UnicodeIdentifier( self, app ): + filepath = PathToTestFile( DEFAULT_PROJECT_DIR, + 'src', + 'com', + 'youcompleteme', + 'Test.java' ) RunTest( app, { 'description': 'Completion works for unicode identifier', 'request': { @@ -527,140 +482,150 @@ def BrokenResolveCompletion( request_id, completion ): } ) -@WithRetry -@IsolatedYcmd() -def GetCompletions_ServerNotInitialized_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) - - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + @WithRetry() + @SharedYcmd + def test_GetCompletions_ResolveFailed( self, app ): + filepath = PathToTestFile( DEFAULT_PROJECT_DIR, + 'src', + 'com', + 'youcompleteme', + 'Test.java' ) + + + def BrokenResolveCompletion( request_id, completion ): + return lsapi.BuildRequest( request_id, 'completionItem/FAIL', completion ) + + with patch( 'ycmd.completers.language_server.language_server_protocol.' + 'ResolveCompletion', + side_effect = BrokenResolveCompletion ): + RunTest( app, { + 'description': 'Completion works for unicode identifier', + 'request': { + 'filetype' : 'java', + 'filepath' : filepath, + 'line_num' : 16, + 'column_num' : 35, + 'force_semantic': True + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 35, + 'completions': has_items( + CompletionEntryMatcher( 'a_test', 'Test.TéstClass.a_test : int', { + 'kind': 'Field', + 'detailed_info': 'a_test : int\n\n', + } ), + CompletionEntryMatcher( + 'åtest', 'Test.TéstClass.åtest : boolean', { + 'kind': 'Field', + 'detailed_info': 'åtest : boolean\n\n', + } ), + CompletionEntryMatcher( 'testywesty', + 'Test.TéstClass.testywesty : String', + { + 'kind': 'Field', + } ), + ), + 'errors': empty(), + } ) + }, + } ) - def MockHandleInitializeInPollThread( self, response ): - pass + @WithRetry() + @IsolatedYcmd() + def test_GetCompletions_ServerNotInitialized( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) + + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + + + def MockHandleInitializeInPollThread( self, response ): + pass + + + with patch.object( completer, + '_HandleInitializeInPollThread', + MockHandleInitializeInPollThread ): + RunTest( app, { + 'description': 'Completion works for unicode identifier', + 'request': { + 'filetype' : 'java', + 'filepath' : filepath, + 'line_num' : 16, + 'column_num' : 35, + 'force_semantic': True + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'completions': empty(), + 'completion_start_column': 6 + } ), + } + } ) - with patch.object( completer, - '_HandleInitializeInPollThread', - MockHandleInitializeInPollThread ): - RunTest( app, { - 'description': 'Completion works for unicode identifier', + @WithRetry() + @SharedYcmd + def test_GetCompletions_MoreThan10_NoResolve_ThenResolve( self, app ): + ClearCompletionsCache() + request, response = RunTest( app, { + 'description': "More than 10 candiates after filtering, don't resolve", 'request': { - 'filetype' : 'java', - 'filepath' : filepath, - 'line_num' : 16, - 'column_num' : 35, - 'force_semantic': True + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestWithDocumentation.java' ), + 'line_num' : 6, + 'column_num': 7, }, 'expect': { 'response': requests.codes.ok, 'data': has_entries( { + 'completions': has_item( + CompletionEntryMatcher( + 'useAString', + 'MethodsWithDocumentation.useAString(String s) : void', + { + 'kind': 'Method', + # This is the un-resolved info (no documentation) + 'detailed_info': 'useAString(String s) : void\n\n', + 'extra_data': has_entries( { + 'resolve': instance_of( int ) + } ) + } + ), + ), + 'completion_start_column': 7, 'errors': empty(), - 'completions': empty(), - 'completion_start_column': 6 - } ), - } + } ) + }, } ) + # We know the item we want is there, pull out the resolve ID + resolve = None + for item in response[ 'completions' ]: + if item[ 'insertion_text' ] == 'useAString': + resolve = item[ 'extra_data' ][ 'resolve' ] + break -@WithRetry -@SharedYcmd -def GetCompletions_MoreThan10_NoResolve_ThenResolve_test( app ): - ClearCompletionsCache() - request, response = RunTest( app, { - 'description': "More than 10 candiates after filtering, don't resolve", - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestWithDocumentation.java' ), - 'line_num' : 6, - 'column_num': 7, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( - CompletionEntryMatcher( - 'useAString', - 'MethodsWithDocumentation.useAString(String s) : void', - { - 'kind': 'Method', - # This is the un-resolved info (no documentation) - 'detailed_info': 'useAString(String s) : void\n\n', - 'extra_data': has_entries( { - 'resolve': instance_of( int ) - } ) - } - ), - ), - 'completion_start_column': 7, - 'errors': empty(), - } ) - }, - } ) - - # We know the item we want is there, pull out the resolve ID - resolve = None - for item in response[ 'completions' ]: - if item[ 'insertion_text' ] == 'useAString': - resolve = item[ 'extra_data' ][ 'resolve' ] - break - - assert resolve is not None - - request[ 'resolve' ] = resolve - # Do this twice to prove that the request is idempotent - for i in range( 2 ): - response = app.post_json( '/resolve_completion', request ).json - - print( f"Resolve response: { pformat( response ) }" ) - - nl = os.linesep - assert_that( response, has_entries( { - 'completion': CompletionEntryMatcher( - 'useAString', - 'MethodsWithDocumentation.useAString(String s) : void', - { - 'kind': 'Method', - # This is the resolved info (no documentation) - 'detailed_info': 'useAString(String s) : void\n' - '\n' - f'Multiple lines of description here.{ nl }' - f'{ nl }' - f' * **Parameters:**{ nl }' - f' { nl }' - f' * **s** a string' - } - ), - 'errors': empty(), - } ) ) - - # The item is resoled - assert_that( response[ 'completion' ], is_not( has_key( 'resolve' ) ) ) - assert_that( response[ 'completion' ], is_not( has_key( 'item' ) ) ) + assert resolve is not None + request[ 'resolve' ] = resolve + # Do this twice to prove that the request is idempotent + for i in range( 2 ): + response = app.post_json( '/resolve_completion', request ).json + print( f"Resolve response: { pformat( response ) }" ) -@WithRetry -@SharedYcmd -def GetCompletions_FewerThan10_Resolved_test( app ): - ClearCompletionsCache() - nl = os.linesep - request, response = RunTest( app, { - 'description': "More than 10 candiates after filtering, don't resolve", - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestWithDocumentation.java' ), - 'line_num' : 6, - 'column_num': 10, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( - CompletionEntryMatcher( + nl = os.linesep + assert_that( response, has_entries( { + 'completion': CompletionEntryMatcher( 'useAString', 'MethodsWithDocumentation.useAString(String s) : void', { @@ -675,232 +640,273 @@ def GetCompletions_FewerThan10_Resolved_test( app ): f' * **s** a string' } ), - ), - 'completion_start_column': 7, 'errors': empty(), - } ) - }, - } ) - # All items are resolved - assert_that( response[ 'completions' ][ 0 ], is_not( has_key( 'resolve' ) ) ) - assert_that( response[ 'completions' ][ 0 ], is_not( has_key( 'item' ) ) ) - assert_that( response[ 'completions' ][ -1 ], is_not( has_key( 'resolve' ) ) ) - assert_that( response[ 'completions' ][ -1 ], is_not( has_key( 'item' ) ) ) + } ) ) + # The item is resoled + assert_that( response[ 'completion' ], is_not( has_key( 'resolve' ) ) ) + assert_that( response[ 'completion' ], is_not( has_key( 'item' ) ) ) -@WithRetry -@SharedYcmd -def GetCompletions_MoreThan10_NoResolve_ThenResolveCacheBad_test( app ): - ClearCompletionsCache() - request, response = RunTest( app, { - 'description': "More than 10 candiates after filtering, don't resolve", - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestWithDocumentation.java' ), - 'line_num' : 6, - 'column_num': 7, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( - CompletionEntryMatcher( - 'useAString', - 'MethodsWithDocumentation.useAString(String s) : void', - { - 'kind': 'Method', - # This is the un-resolved info (no documentation) - 'detailed_info': 'useAString(String s) : void\n\n', - 'extra_data': has_entries( { - 'resolve': instance_of( int ) - } ) - } + + @WithRetry() + @SharedYcmd + def test_GetCompletions_FewerThan10_Resolved( self, app ): + ClearCompletionsCache() + nl = os.linesep + request, response = RunTest( app, { + 'description': "More than 10 candiates after filtering, don't resolve", + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestWithDocumentation.java' ), + 'line_num' : 6, + 'column_num': 10, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( + CompletionEntryMatcher( + 'useAString', + 'MethodsWithDocumentation.useAString(String s) : void', + { + 'kind': 'Method', + # This is the resolved info (no documentation) + 'detailed_info': 'useAString(String s) : void\n' + '\n' + f'Multiple lines of description here.{ nl }' + f'{ nl }' + f' * **Parameters:**{ nl }' + f' { nl }' + f' * **s** a string' + } + ), ), - ), - 'completion_start_column': 7, - 'errors': empty(), - } ) - }, - } ) + 'completion_start_column': 7, + 'errors': empty(), + } ) + }, + } ) + # All items are resolved + assert_that( response[ 'completions' ][ 0 ], + is_not( has_key( 'resolve' ) ) ) + assert_that( response[ 'completions' ][ 0 ], + is_not( has_key( 'item' ) ) ) + assert_that( response[ 'completions' ][ -1 ], + is_not( has_key( 'resolve' ) ) ) + assert_that( response[ 'completions' ][ -1 ], + is_not( has_key( 'item' ) ) ) + + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_MoreThan10_NoResolve_ThenResolveCacheBad( self, app ): + ClearCompletionsCache() + request, response = RunTest( app, { + 'description': "More than 10 candiates after filtering, don't resolve", + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestWithDocumentation.java' ), + 'line_num' : 6, + 'column_num': 7, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( + CompletionEntryMatcher( + 'useAString', + 'MethodsWithDocumentation.useAString(String s) : void', + { + 'kind': 'Method', + # This is the un-resolved info (no documentation) + 'detailed_info': 'useAString(String s) : void\n\n', + 'extra_data': has_entries( { + 'resolve': instance_of( int ) + } ) + } + ), + ), + 'completion_start_column': 7, + 'errors': empty(), + } ) + }, + } ) - # We know the item we want is there, pull out the resolve ID - resolve = None - for item in response[ 'completions' ]: - if item[ 'insertion_text' ] == 'useAString': - resolve = item[ 'extra_data' ][ 'resolve' ] - break + # We know the item we want is there, pull out the resolve ID + resolve = None + for item in response[ 'completions' ]: + if item[ 'insertion_text' ] == 'useAString': + resolve = item[ 'extra_data' ][ 'resolve' ] + break - assert resolve is not None + assert resolve is not None - request[ 'resolve' ] = resolve - # Use a different position - should mean the cache is not valid for request - request[ 'column_num' ] = 20 - response = app.post_json( '/resolve_completion', request ).json + request[ 'resolve' ] = resolve + # Use a different position - should mean the cache is not valid for request + request[ 'column_num' ] = 20 + response = app.post_json( '/resolve_completion', request ).json - print( f"Resolve response: { pformat( response ) }" ) + print( f"Resolve response: { pformat( response ) }" ) - assert_that( response, has_entries( { - 'completion': None, - 'errors': contains_exactly( ErrorMatcher( CompletionsChanged ) ) - } ) ) + assert_that( response, has_entries( { + 'completion': None, + 'errors': contains_exactly( ErrorMatcher( CompletionsChanged ) ) + } ) ) -@WithRetry -@UnixOnly -@SharedYcmd -def GetCompletions_MoreThan10ForceSemantic_test( app ): - ClearCompletionsCache() - RunTest( app, { - 'description': 'When forcing we pass the query, which reduces candidates', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestLauncher.java' ), - 'line_num' : 4, - 'column_num': 15, - 'force_semantic': True - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'com.youcompleteme.*;', None, { - 'kind': 'Module', - 'detailed_info': 'com.youcompleteme\n\n', - } ), - CompletionEntryMatcher( 'com.youcompleteme.testing.*;', None, { - 'kind': 'Module', - 'detailed_info': 'com.youcompleteme.testing\n\n', - } ), - ), - 'completion_start_column': 8, - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_ForceAtTopLevel_NoImport_test( app ): - RunTest( app, { - 'description': 'When forcing semantic completion, pass the query to server', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestWidgetImpl.java' ), - 'line_num' : 30, - 'column_num': 20, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'TestFactory', None, { - 'kind': 'Class', - 'menu_text': 'TestFactory - com.test', - } ), - ), - 'completion_start_column': 12, - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_NoForceAtTopLevel_NoImport_test( app ): - RunTest( app, { - 'description': 'When not forcing semantic completion, use no context', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'TestWidgetImpl.java' ), - 'line_num' : 30, - 'column_num': 20, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'TestFactory', '[ID]', {} ), - ), - 'completion_start_column': 12, - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_ForceAtTopLevel_WithImport_test( app ): - filepath = ProjectPath( 'TestWidgetImpl.java' ) - RunTest( app, { - 'description': 'Top level completions have import FixIts', - 'request': { - 'filetype' : 'java', - 'filepath' : filepath, - 'line_num' : 34, - 'column_num': 16, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( - CompletionEntryMatcher( 'InputStreamReader', None, { - 'kind': 'Class', - 'menu_text': 'InputStreamReader - java.io', - 'extra_data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\n\nimport java.io.InputStreamReader;\n\n', - LocationMatcher( filepath, 1, 18 ), - LocationMatcher( filepath, 3, 1 ) ), - ), - } ) ), + @WithRetry() + @UnixOnly + @SharedYcmd + def test_GetCompletions_MoreThan10ForceSemantic( self, app ): + ClearCompletionsCache() + RunTest( app, { + 'description': 'When forcing we pass the query, which reduces candidates', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestLauncher.java' ), + 'line_num' : 4, + 'column_num': 15, + 'force_semantic': True + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'com.youcompleteme.*;', None, { + 'kind': 'Module', + 'detailed_info': 'com.youcompleteme\n\n', } ), - } ), - ), - 'completion_start_column': 12, - 'errors': empty(), - } ) - }, - } ) - - -@WithRetry -@SharedYcmd -def GetCompletions_UseServerTriggers_test( app ): - filepath = ProjectPath( 'TestWidgetImpl.java' ) - - RunTest( app, { - 'description': 'We use the semantic triggers from the server (@ here)', - 'request': { - 'filetype' : 'java', - 'filepath' : filepath, - 'line_num' : 24, - 'column_num': 7, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completion_start_column': 4, - 'completions': has_item( - CompletionEntryMatcher( 'Override', None, { - 'kind': 'Interface', - 'menu_text': 'Override - java.lang', - } ) - ) - } ) - } - } ) + CompletionEntryMatcher( 'com.youcompleteme.testing.*;', None, { + 'kind': 'Module', + 'detailed_info': 'com.youcompleteme.testing\n\n', + } ), + ), + 'completion_start_column': 8, + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_ForceAtTopLevel_NoImport( self, app ): + RunTest( app, { + 'description': 'When forcing semantic completion, ' + 'pass the query to server', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestWidgetImpl.java' ), + 'line_num' : 30, + 'column_num': 20, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'TestFactory', None, { + 'kind': 'Class', + 'menu_text': 'TestFactory - com.test', + } ), + ), + 'completion_start_column': 12, + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_NoForceAtTopLevel_NoImport( self, app ): + RunTest( app, { + 'description': 'When not forcing semantic completion, use no context', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'TestWidgetImpl.java' ), + 'line_num' : 30, + 'column_num': 20, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'TestFactory', '[ID]', {} ), + ), + 'completion_start_column': 12, + 'errors': empty(), + } ) + }, + } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @WithRetry() + @SharedYcmd + def test_GetCompletions_ForceAtTopLevel_WithImport( self, app ): + filepath = ProjectPath( 'TestWidgetImpl.java' ) + RunTest( app, { + 'description': 'Top level completions have import FixIts', + 'request': { + 'filetype' : 'java', + 'filepath' : filepath, + 'line_num' : 34, + 'column_num': 16, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( + CompletionEntryMatcher( 'InputStreamReader', None, { + 'kind': 'Class', + 'menu_text': 'InputStreamReader - java.io', + 'extra_data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\n\nimport java.io.InputStreamReader;\n\n', + LocationMatcher( filepath, 1, 18 ), + LocationMatcher( filepath, 3, 1 ) ), + ), + } ) ), + } ), + } ), + ), + 'completion_start_column': 12, + 'errors': empty(), + } ) + }, + } ) + + + @WithRetry() + @SharedYcmd + def test_GetCompletions_UseServerTriggers( self, app ): + filepath = ProjectPath( 'TestWidgetImpl.java' ) + + RunTest( app, { + 'description': 'We use the semantic triggers from the server (@ here)', + 'request': { + 'filetype' : 'java', + 'filepath' : filepath, + 'line_num' : 24, + 'column_num': 7, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completion_start_column': 4, + 'completions': has_item( + CompletionEntryMatcher( 'Override', None, { + 'kind': 'Interface', + 'menu_text': 'Override - java.lang', + } ) + ) + } ) + } + } ) diff --git a/ycmd/tests/java/java_completer_test.py b/ycmd/tests/java/java_completer_test.py index fcf6cdd868..639a271ce2 100644 --- a/ycmd/tests/java/java_completer_test.py +++ b/ycmd/tests/java/java_completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -20,10 +20,11 @@ from hamcrest import assert_that, equal_to, calling, has_entries, is_not, raises from unittest.mock import patch +from unittest import TestCase from ycmd import handlers, user_options_store from ycmd.tests.test_utils import BuildRequest, ErrorMatcher -from ycmd.tests.java import SharedYcmd +from ycmd.tests.java import SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.completers.java import java_completer, hook from ycmd.completers.java.java_completer import NO_DOCUMENTATION_MESSAGE from ycmd.tests import IsolatedYcmd as IsolatedYcmdWithoutJava @@ -32,210 +33,209 @@ DEFAULT_OPTIONS = user_options_store.DefaultOptions() -@patch( 'ycmd.completers.java.java_completer.utils.FindExecutable', - return_value = '' ) -def ShouldEnableJavaCompleter_NoJava_test( *args ): - assert_that( java_completer.ShouldEnableJavaCompleter( DEFAULT_OPTIONS ), - equal_to( False ) ) - - -@IsolatedYcmdWithoutJava( { 'java_binary_path': '/this/path/does/not/exist' } ) -def ShouldEnableJavaCompleter_JavaNotFound_test( app ): - request_data = BuildRequest( filetype = 'java' ) - response = app.post_json( '/defined_subcommands', - request_data, - expect_errors = True ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, - ErrorMatcher( ValueError, - 'No semantic completer exists for filetypes: ' - "['java']" ) ) +class JavaCompleterTest( TestCase ): + @patch( 'ycmd.completers.java.java_completer.utils.FindExecutable', + return_value = '' ) + def test_ShouldEnableJavaCompleter_NoJava( *args ): + assert_that( java_completer.ShouldEnableJavaCompleter( DEFAULT_OPTIONS ), + equal_to( False ) ) -def ShouldEnableJavaCompleter_NotInstalled_test(): - orig_language_server_home = java_completer.LANGUAGE_SERVER_HOME - try: - java_completer.LANGUAGE_SERVER_HOME = '' + @IsolatedYcmdWithoutJava( { + 'java_binary_path': '/this/path/does/not/exist' } ) + def test_ShouldEnableJavaCompleter_JavaNotFound( self, app ): + request_data = BuildRequest( filetype = 'java' ) + response = app.post_json( '/defined_subcommands', + request_data, + expect_errors = True ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, + ErrorMatcher( ValueError, + 'No semantic completer exists for filetypes: ' + "['java']" ) ) + + + def test_ShouldEnableJavaCompleter_NotInstalled( self ): + orig_language_server_home = java_completer.LANGUAGE_SERVER_HOME + try: + java_completer.LANGUAGE_SERVER_HOME = '' + assert_that( java_completer.ShouldEnableJavaCompleter( DEFAULT_OPTIONS ), + equal_to( False ) ) + finally: + java_completer.LANGUAGE_SERVER_HOME = orig_language_server_home + + + @patch( 'glob.glob', return_value = [] ) + def test_ShouldEnableJavaCompleter_NoLauncherJar( self, glob ): assert_that( java_completer.ShouldEnableJavaCompleter( DEFAULT_OPTIONS ), equal_to( False ) ) - finally: - java_completer.LANGUAGE_SERVER_HOME = orig_language_server_home - - -@patch( 'glob.glob', return_value = [] ) -def ShouldEnableJavaCompleter_NoLauncherJar_test( glob ): - assert_that( java_completer.ShouldEnableJavaCompleter( DEFAULT_OPTIONS ), - equal_to( False ) ) - glob.assert_called() - - -def WorkspaceDirForProject_HashProjectDir_test(): - assert_that( - java_completer._WorkspaceDirForProject( os.getcwd(), - os.getcwd(), - False ), - equal_to( java_completer._WorkspaceDirForProject( os.getcwd(), - os.getcwd(), - False ) ) - ) - - -def WorkspaceDirForProject_UniqueDir_test(): - assert_that( - java_completer._WorkspaceDirForProject( os.getcwd(), - os.getcwd(), - True ), - is_not( equal_to( java_completer._WorkspaceDirForProject( os.getcwd(), - os.getcwd(), - True ) ) ) - ) - - -@SharedYcmd -def JavaCompleter_GetType_test( app ): - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) - - # The LSP defines the hover response as either: - # - a string - # - a list of strings - # - an object with keys language, value - # - a list of objects with keys language, value - # = an object with keys kind, value - - with patch.object( completer, 'GetHoverResponse', return_value = '' ): - assert_that( calling( completer.GetType ).with_args( BuildRequest() ), - raises( RuntimeError, 'Unknown type' ) ) - - with patch.object( completer, 'GetHoverResponse', return_value = 'string' ): - assert_that( calling( completer.GetType ).with_args( BuildRequest() ), - raises( RuntimeError, 'Unknown type' ) ) - - with patch.object( completer, 'GetHoverResponse', return_value = 'value' ): - assert_that( calling( completer.GetType ).with_args( BuildRequest() ), - raises( RuntimeError, 'Unknown type' ) ) - - with patch.object( completer, 'GetHoverResponse', return_value = [] ): - assert_that( calling( completer.GetType ).with_args( BuildRequest() ), - raises( RuntimeError, 'Unknown type' ) ) - - with patch.object( completer, - 'GetHoverResponse', - return_value = [ 'a', 'b' ] ): - assert_that( calling( completer.GetType ).with_args( BuildRequest() ), - raises( RuntimeError, 'Unknown type' ) ) - - with patch.object( completer, - 'GetHoverResponse', - return_value = { 'language': 'java', 'value': 'test' } ): - assert_that( completer.GetType( BuildRequest() ), - has_entries( { 'message': 'test' } ) ) - - with patch.object( - completer, - 'GetHoverResponse', - return_value = [ { 'language': 'java', 'value': 'test' } ] ): - assert_that( completer.GetType( BuildRequest() ), - has_entries( { 'message': 'test' } ) ) - - with patch.object( - completer, - 'GetHoverResponse', - return_value = [ { 'language': 'java', 'value': 'test' }, - { 'language': 'java', 'value': 'not test' } ] ): - assert_that( completer.GetType( BuildRequest() ), - has_entries( { 'message': 'test' } ) ) - - with patch.object( - completer, - 'GetHoverResponse', - return_value = [ { 'language': 'java', 'value': 'test' }, - 'line 1', - 'line 2' ] ): - assert_that( completer.GetType( BuildRequest() ), - has_entries( { 'message': 'test' } ) ) - - - with patch.object( completer, - 'GetHoverResponse', - return_value = { 'kind': 'plaintext', 'value': 'test' } ): - assert_that( calling( completer.GetType ).with_args( BuildRequest() ), - raises( RuntimeError, 'Unknown type' ) ) - - -@SharedYcmd -def JavaCompleter_GetDoc_test( app ): - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) - - # The LSP defines the hover response as either: - # - a string - # - a list of strings - # - an object with keys language, value - # - a list of objects with keys language, value - # = an object with keys kind, value - - with patch.object( completer, 'GetHoverResponse', return_value = '' ): - assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), - raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) - - with patch.object( completer, 'GetHoverResponse', return_value = 'string' ): - assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), - raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) - - with patch.object( completer, 'GetHoverResponse', return_value = [] ): - assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), - raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) - - with patch.object( completer, - 'GetHoverResponse', - return_value = [ 'a', 'b' ] ): - assert_that( completer.GetDoc( BuildRequest() ), - has_entries( { 'detailed_info': 'a\nb' } ) ) - - with patch.object( completer, - 'GetHoverResponse', - return_value = { 'language': 'java', 'value': 'test' } ): - assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), - raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) - - with patch.object( - completer, - 'GetHoverResponse', - return_value = [ { 'language': 'java', 'value': 'test' } ] ): - assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), - raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) - - with patch.object( - completer, - 'GetHoverResponse', - return_value = [ { 'language': 'java', 'value': 'test' }, - { 'language': 'java', 'value': 'not test' } ] ): - assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), - raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) - - with patch.object( - completer, - 'GetHoverResponse', - return_value = [ { 'language': 'java', 'value': 'test' }, - 'line 1', - 'line 2' ] ): - assert_that( completer.GetDoc( BuildRequest() ), - has_entries( { 'detailed_info': 'line 1\nline 2' } ) ) - - - with patch.object( completer, - 'GetHoverResponse', - return_value = { 'kind': 'plaintext', 'value': 'test' } ): - assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), - raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) - - -@patch( 'ycmd.completers.java.hook.ShouldEnableJavaCompleter', - return_value = False ) -def JavaHook_JavaNotEnabled_test( *args ): - assert_that( hook.GetCompleter( {} ), equal_to( None ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + glob.assert_called() + + + def test_WorkspaceDirForProject_HashProjectDir( self ): + assert_that( + java_completer._WorkspaceDirForProject( os.getcwd(), + os.getcwd(), + False ), + equal_to( java_completer._WorkspaceDirForProject( os.getcwd(), + os.getcwd(), + False ) ) + ) + + + def test_WorkspaceDirForProject_UniqueDir( self ): + assert_that( + java_completer._WorkspaceDirForProject( os.getcwd(), + os.getcwd(), + True ), + is_not( equal_to( java_completer._WorkspaceDirForProject( os.getcwd(), + os.getcwd(), + True ) ) ) + ) + + + @SharedYcmd + def test_JavaCompleter_GetType( self, app ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + + # The LSP defines the hover response as either: + # - a string + # - a list of strings + # - an object with keys language, value + # - a list of objects with keys language, value + # = an object with keys kind, value + + with patch.object( completer, 'GetHoverResponse', return_value = '' ): + assert_that( calling( completer.GetType ).with_args( BuildRequest() ), + raises( RuntimeError, 'Unknown type' ) ) + + with patch.object( completer, 'GetHoverResponse', return_value = 'string' ): + assert_that( calling( completer.GetType ).with_args( BuildRequest() ), + raises( RuntimeError, 'Unknown type' ) ) + + with patch.object( completer, 'GetHoverResponse', return_value = 'value' ): + assert_that( calling( completer.GetType ).with_args( BuildRequest() ), + raises( RuntimeError, 'Unknown type' ) ) + + with patch.object( completer, 'GetHoverResponse', return_value = [] ): + assert_that( calling( completer.GetType ).with_args( BuildRequest() ), + raises( RuntimeError, 'Unknown type' ) ) + + with patch.object( completer, + 'GetHoverResponse', + return_value = [ 'a', 'b' ] ): + assert_that( calling( completer.GetType ).with_args( BuildRequest() ), + raises( RuntimeError, 'Unknown type' ) ) + + with patch.object( completer, + 'GetHoverResponse', + return_value = { 'language': 'java', 'value': 'test' } ): + assert_that( completer.GetType( BuildRequest() ), + has_entries( { 'message': 'test' } ) ) + + with patch.object( + completer, + 'GetHoverResponse', + return_value = [ { 'language': 'java', 'value': 'test' } ] ): + assert_that( completer.GetType( BuildRequest() ), + has_entries( { 'message': 'test' } ) ) + + with patch.object( + completer, + 'GetHoverResponse', + return_value = [ { 'language': 'java', 'value': 'test' }, + { 'language': 'java', 'value': 'not test' } ] ): + assert_that( completer.GetType( BuildRequest() ), + has_entries( { 'message': 'test' } ) ) + + with patch.object( + completer, + 'GetHoverResponse', + return_value = [ { 'language': 'java', 'value': 'test' }, + 'line 1', + 'line 2' ] ): + assert_that( completer.GetType( BuildRequest() ), + has_entries( { 'message': 'test' } ) ) + + + with patch.object( + completer, + 'GetHoverResponse', + return_value = { 'kind': 'plaintext', 'value': 'test' } ): + assert_that( calling( completer.GetType ).with_args( BuildRequest() ), + raises( RuntimeError, 'Unknown type' ) ) + + + @SharedYcmd + def test_JavaCompleter_GetDoc( self, app ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + + # The LSP defines the hover response as either: + # - a string + # - a list of strings + # - an object with keys language, value + # - a list of objects with keys language, value + # = an object with keys kind, value + + with patch.object( completer, 'GetHoverResponse', return_value = '' ): + assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), + raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) + + with patch.object( completer, 'GetHoverResponse', return_value = 'string' ): + assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), + raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) + + with patch.object( completer, 'GetHoverResponse', return_value = [] ): + assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), + raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) + + with patch.object( completer, + 'GetHoverResponse', + return_value = [ 'a', 'b' ] ): + assert_that( completer.GetDoc( BuildRequest() ), + has_entries( { 'detailed_info': 'a\nb' } ) ) + + with patch.object( completer, + 'GetHoverResponse', + return_value = { 'language': 'java', 'value': 'test' } ): + assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), + raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) + + with patch.object( + completer, + 'GetHoverResponse', + return_value = [ { 'language': 'java', 'value': 'test' } ] ): + assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), + raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) + + with patch.object( + completer, + 'GetHoverResponse', + return_value = [ { 'language': 'java', 'value': 'test' }, + { 'language': 'java', 'value': 'not test' } ] ): + assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), + raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) + + with patch.object( + completer, + 'GetHoverResponse', + return_value = [ { 'language': 'java', 'value': 'test' }, + 'line 1', + 'line 2' ] ): + assert_that( completer.GetDoc( BuildRequest() ), + has_entries( { 'detailed_info': 'line 1\nline 2' } ) ) + + + with patch.object( + completer, + 'GetHoverResponse', + return_value = { 'kind': 'plaintext', 'value': 'test' } ): + assert_that( calling( completer.GetDoc ).with_args( BuildRequest() ), + raises( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) + + + @patch( 'ycmd.completers.java.hook.ShouldEnableJavaCompleter', + return_value = False ) + def test_JavaHook_JavaNotEnabled( *args ): + assert_that( hook.GetCompleter( {} ), equal_to( None ) ) diff --git a/ycmd/tests/java/server_management_test.py b/ycmd/tests/java/server_management_test.py index a1c369c661..124acb3cce 100644 --- a/ycmd/tests/java/server_management_test.py +++ b/ycmd/tests/java/server_management_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2020 ycmd contributors +# Copyright (C) 2017-2021 ycmd contributors # # This file is part of ycmd. # @@ -22,6 +22,7 @@ import time from unittest.mock import patch +from unittest import TestCase from hamcrest import ( assert_that, contains_exactly, equal_to, @@ -31,11 +32,14 @@ starts_with ) from ycmd.completers.language_server.language_server_completer import ( LanguageServerConnectionTimeout ) -from ycmd.tests.java import ( PathToTestFile, +from ycmd.tests.java import ( PathToTestFile, # noqa + isolated_app, IsolatedYcmd, SharedYcmd, StartJavaCompleterServerInDirectory, - StartJavaCompleterServerWithFile ) + StartJavaCompleterServerWithFile, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, CompleterProjectDirectoryMatcher, ErrorMatcher, @@ -67,399 +71,317 @@ def Wrapper( *args, **kwargs ): return decorator -@TidyJDTProjectFiles( PathToTestFile( 'simple_maven_project' ) ) -@IsolatedYcmd() -def ServerManagement_RestartServer_test( app ): - StartJavaCompleterServerInDirectory( - app, PathToTestFile( 'simple_eclipse_project' ) ) - - eclipse_project = PathToTestFile( 'simple_eclipse_project' ) - maven_project = PathToTestFile( 'simple_maven_project' ) - - # Run the debug info to check that we have the correct project dir - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - CompleterProjectDirectoryMatcher( eclipse_project ) ) - - # Restart the server with a different client working directory - filepath = PathToTestFile( 'simple_maven_project', - 'src', - 'main', - 'java', - 'com', - 'test', - 'TestFactory.java' ) - - app.post_json( - '/run_completer_command', - BuildRequest( - filepath = filepath, - filetype = 'java', - working_dir = maven_project, - command_arguments = [ 'RestartServer' ], - ), - ) - - WaitUntilCompleterServerReady( app, 'java' ) - - app.post_json( - '/event_notification', - BuildRequest( - filepath = filepath, - filetype = 'java', - working_dir = maven_project, - event_name = 'FileReadyToParse', +class ServerManagementTest( TestCase ): + @TidyJDTProjectFiles( PathToTestFile( 'simple_maven_project' ) ) + @IsolatedYcmd() + def test_ServerManagement_RestartServer( self, app ): + StartJavaCompleterServerInDirectory( + app, PathToTestFile( 'simple_eclipse_project' ) ) + + eclipse_project = PathToTestFile( 'simple_eclipse_project' ) + maven_project = PathToTestFile( 'simple_maven_project' ) + + # Run the debug info to check that we have the correct project dir + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + CompleterProjectDirectoryMatcher( eclipse_project ) ) + + # Restart the server with a different client working directory + filepath = PathToTestFile( 'simple_maven_project', + 'src', + 'main', + 'java', + 'com', + 'test', + 'TestFactory.java' ) + + app.post_json( + '/run_completer_command', + BuildRequest( + filepath = filepath, + filetype = 'java', + working_dir = maven_project, + command_arguments = [ 'RestartServer' ], + ), ) - ) - - # Run the debug info to check that we have the correct project dir - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - CompleterProjectDirectoryMatcher( maven_project ) ) - - -def ServerManagement_WipeWorkspace_NoConfig_test( isolated_app ): - with TemporaryTestDir() as tmp_dir: - with isolated_app( { - 'java_jdtls_use_clean_workspace': 0, - 'java_jdtls_workspace_root_path': tmp_dir - } ) as app: - StartJavaCompleterServerInDirectory( - app, PathToTestFile( 'simple_eclipse_project', 'src' ) ) - - project = PathToTestFile( 'simple_eclipse_project' ) - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'youcompleteme', - 'Test.java' ) - app.post_json( - '/run_completer_command', - BuildRequest( - filepath = filepath, - filetype = 'java', - command_arguments = [ 'WipeWorkspace' ], - ), + WaitUntilCompleterServerReady( app, 'java' ) + + app.post_json( + '/event_notification', + BuildRequest( + filepath = filepath, + filetype = 'java', + working_dir = maven_project, + event_name = 'FileReadyToParse', ) + ) - WaitUntilCompleterServerReady( app, 'java' ) - - assert_that( - app.post_json( '/debug_info', - BuildRequest( filetype = 'java', - filepath = filepath ) ).json, - CompleterProjectDirectoryMatcher( project ) ) - - assert_that( - app.post_json( '/debug_info', - BuildRequest( filetype = 'java', - filepath = filepath ) ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'extras', has_item( - has_entries( { - 'key': 'Workspace Path', - 'value': starts_with( tmp_dir ), - } ) + # Run the debug info to check that we have the correct project dir + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + CompleterProjectDirectoryMatcher( maven_project ) ) + + + def test_ServerManagement_WipeWorkspace_NoConfig( self ): + with TemporaryTestDir() as tmp_dir: + with isolated_app( { + 'java_jdtls_use_clean_workspace': 0, + 'java_jdtls_workspace_root_path': tmp_dir + } ) as app: + StartJavaCompleterServerInDirectory( + app, PathToTestFile( 'simple_eclipse_project', 'src' ) ) + + project = PathToTestFile( 'simple_eclipse_project' ) + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'youcompleteme', + 'Test.java' ) + + app.post_json( + '/run_completer_command', + BuildRequest( + filepath = filepath, + filetype = 'java', + command_arguments = [ 'WipeWorkspace' ], + ), + ) + + WaitUntilCompleterServerReady( app, 'java' ) + + assert_that( + app.post_json( '/debug_info', + BuildRequest( filetype = 'java', + filepath = filepath ) ).json, + CompleterProjectDirectoryMatcher( project ) ) + + assert_that( + app.post_json( '/debug_info', + BuildRequest( filetype = 'java', + filepath = filepath ) ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'extras', has_item( + has_entries( { + 'key': 'Workspace Path', + 'value': starts_with( tmp_dir ), + } ) + ) ) ) ) ) ) - ) ) -def ServerManagement_WipeWorkspace_WithConfig_test( isolated_app ): - with TemporaryTestDir() as tmp_dir: - with isolated_app( { - 'java_jdtls_use_clean_workspace': 0, - 'java_jdtls_workspace_root_path': tmp_dir - } ) as app: - StartJavaCompleterServerInDirectory( - app, PathToTestFile( 'simple_eclipse_project', 'src' ) ) + def test_ServerManagement_WipeWorkspace_WithConfig( self ): + with TemporaryTestDir() as tmp_dir: + with isolated_app( { + 'java_jdtls_use_clean_workspace': 0, + 'java_jdtls_workspace_root_path': tmp_dir + } ) as app: + StartJavaCompleterServerInDirectory( + app, PathToTestFile( 'simple_eclipse_project', 'src' ) ) + + project = PathToTestFile( 'simple_eclipse_project' ) + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'youcompleteme', + 'Test.java' ) + + app.post_json( + '/run_completer_command', + BuildRequest( + filepath = filepath, + filetype = 'java', + command_arguments = [ 'WipeWorkspace', '--with-config' ], + ), + ) + + WaitUntilCompleterServerReady( app, 'java' ) + + assert_that( + app.post_json( '/debug_info', + BuildRequest( filetype = 'java', + filepath = filepath ) ).json, + CompleterProjectDirectoryMatcher( project ) ) + + assert_that( + app.post_json( '/debug_info', + BuildRequest( filetype = 'java', + filepath = filepath ) ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'extras', has_item( + has_entries( { + 'key': 'Workspace Path', + 'value': starts_with( tmp_dir ), + } ) + ) ) + ) ) + ) ) - project = PathToTestFile( 'simple_eclipse_project' ) - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'youcompleteme', - 'Test.java' ) - app.post_json( - '/run_completer_command', - BuildRequest( - filepath = filepath, - filetype = 'java', - command_arguments = [ 'WipeWorkspace', '--with-config' ], - ), - ) + @IsolatedYcmd( { + 'extra_conf_globlist': PathToTestFile( 'multiple_projects', '*' ) + } ) + def test_ServerManagement_ProjectDetection_MultipleProjects( self, app ): + # The ycm_extra_conf.py file should set the project path to + # multiple_projects/src + project = PathToTestFile( 'multiple_projects', 'src' ) + StartJavaCompleterServerWithFile( app, + os.path.join( project, + 'core', + 'java', + 'com', + 'puremourning', + 'widget', + 'core', + 'Utils.java' ) ) + + # Run the debug info to check that we have the correct project dir + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + CompleterProjectDirectoryMatcher( project ) ) - WaitUntilCompleterServerReady( app, 'java' ) - - assert_that( - app.post_json( '/debug_info', - BuildRequest( filetype = 'java', - filepath = filepath ) ).json, - CompleterProjectDirectoryMatcher( project ) ) - - assert_that( - app.post_json( '/debug_info', - BuildRequest( filetype = 'java', - filepath = filepath ) ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'extras', has_item( - has_entries( { - 'key': 'Workspace Path', - 'value': starts_with( tmp_dir ), - } ) - ) ) - ) ) - ) ) - - -@IsolatedYcmd( { - 'extra_conf_globlist': PathToTestFile( 'multiple_projects', '*' ) -} ) -def ServerManagement_ProjectDetection_MultipleProjects_test( app ): - # The ycm_extra_conf.py file should set the project path to - # multiple_projects/src - project = PathToTestFile( 'multiple_projects', 'src' ) - StartJavaCompleterServerWithFile( app, - os.path.join( project, - 'core', - 'java', - 'com', - 'puremourning', - 'widget', - 'core', - 'Utils.java' ) ) - - # Run the debug info to check that we have the correct project dir - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - CompleterProjectDirectoryMatcher( project ) ) - - -@IsolatedYcmd() -def ServerManagement_ProjectDetection_EclipseParent_test( app ): - StartJavaCompleterServerInDirectory( - app, PathToTestFile( 'simple_eclipse_project', 'src' ) ) - - project = PathToTestFile( 'simple_eclipse_project' ) - - # Run the debug info to check that we have the correct project dir - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - CompleterProjectDirectoryMatcher( project ) ) - - -@TidyJDTProjectFiles( PathToTestFile( 'simple_maven_project' ) ) -@IsolatedYcmd() -def ServerManagement_ProjectDetection_MavenParent_test( app ): - StartJavaCompleterServerInDirectory( app, - PathToTestFile( 'simple_maven_project', - 'src', - 'main', - 'java', - 'com', - 'test' ) ) - - project = PathToTestFile( 'simple_maven_project' ) - - # Run the debug info to check that we have the correct project dir - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - CompleterProjectDirectoryMatcher( project ) ) - - -@TidyJDTProjectFiles( PathToTestFile( 'simple_maven_project', - 'simple_submodule' ) ) -@TidyJDTProjectFiles( PathToTestFile( 'simple_maven_project' ) ) -@IsolatedYcmd() -def ServerManagement_ProjectDetection_MavenParent_Submodule_test( app ): - StartJavaCompleterServerInDirectory( app, - PathToTestFile( 'simple_maven_project', - 'simple_submodule', - 'src', - 'main', - 'java', - 'com', - 'test' ) ) - - project = PathToTestFile( 'simple_maven_project' ) - - # Run the debug info to check that we have the correct project dir - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - CompleterProjectDirectoryMatcher( project ) ) - - -@SharedYcmd -def ServerManagement_OpenProject_RelativePathNoWD_test( app ): - response = app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'java', - command_arguments = [ - 'OpenProject', - os.path.join( '..', 'simple_maven_project' ), - ], - ), - expect_errors = True, - ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, - ErrorMatcher( ValueError, - 'Project directory must be absolute' ) ) - - -@SharedYcmd -def ServerManagement_OpenProject_RelativePathNoPath_test( app ): - response = app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'java', - command_arguments = [ - 'OpenProject', - ], - ), - expect_errors = True, - ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, - ErrorMatcher( ValueError, - 'Usage: OpenProject ' ) ) - - -def ServerManagement_ProjectDetection_NoParent_test( isolated_app ): - with TemporaryTestDir() as tmp_dir: - with isolated_app() as app: - StartJavaCompleterServerInDirectory( app, tmp_dir ) - # Run the debug info to check that we have the correct project dir (cwd) - request_data = BuildRequest( - filetype = 'java', - filepath = os.path.join( tmp_dir, 'foo.java' ) ) - assert_that( app.post_json( '/debug_info', request_data ).json, - CompleterProjectDirectoryMatcher( tmp_dir ) ) - - -@IsolatedYcmd() -@patch( 'shutil.rmtree', side_effect = OSError ) -@patch( 'ycmd.utils.WaitUntilProcessIsTerminated', - MockProcessTerminationTimingOut ) -def ServerManagement_CloseServer_Unclean_test( rm, app ): - StartJavaCompleterServerInDirectory( - app, PathToTestFile( 'simple_eclipse_project' ) ) - - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'java', - command_arguments = [ 'StopServer' ] - ) - ) - - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) - ) ) - ) ) - - -@IsolatedYcmd() -def ServerManagement_StopServerTwice_test( app ): - StartJavaCompleterServerInDirectory( - app, PathToTestFile( 'simple_eclipse_project' ) ) - - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'java', - command_arguments = [ 'StopServer' ], - ), - ) - - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) - ) ) - ) ) - - - # Stopping a stopped server is a no-op - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'java', - command_arguments = [ 'StopServer' ], - ), - ) - - request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) - ) ) - ) ) + @IsolatedYcmd() + def test_ServerManagement_ProjectDetection_EclipseParent( self, app ): + StartJavaCompleterServerInDirectory( + app, PathToTestFile( 'simple_eclipse_project', 'src' ) ) + + project = PathToTestFile( 'simple_eclipse_project' ) + + # Run the debug info to check that we have the correct project dir + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + CompleterProjectDirectoryMatcher( project ) ) -@IsolatedYcmd() -def ServerManagement_ServerDies_test( app ): - StartJavaCompleterServerInDirectory( - app, - PathToTestFile( 'simple_eclipse_project' ) ) - request_data = BuildRequest( filetype = 'java' ) - debug_info = app.post_json( '/debug_info', request_data ).json - print( f'Debug info: { debug_info }' ) - pid = debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'pid' ] - print( f'pid: { pid }' ) - process = psutil.Process( pid ) - process.terminate() + @TidyJDTProjectFiles( PathToTestFile( 'simple_maven_project' ) ) + @IsolatedYcmd() + def test_ServerManagement_ProjectDetection_MavenParent( self, app ): + StartJavaCompleterServerInDirectory( app, + PathToTestFile( 'simple_maven_project', + 'src', + 'main', + 'java', + 'com', + 'test' ) ) - for tries in range( 0, 10 ): + project = PathToTestFile( 'simple_maven_project' ) + + # Run the debug info to check that we have the correct project dir request_data = BuildRequest( filetype = 'java' ) - debug_info = app.post_json( '/debug_info', request_data ).json - if not debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'is_running' ]: - break + assert_that( app.post_json( '/debug_info', request_data ).json, + CompleterProjectDirectoryMatcher( project ) ) + + + @TidyJDTProjectFiles( PathToTestFile( 'simple_maven_project', + 'simple_submodule' ) ) + @TidyJDTProjectFiles( PathToTestFile( 'simple_maven_project' ) ) + @IsolatedYcmd() + def test_ServerManagement_ProjectDetection_MavenParent_Submodule( self, app ): + StartJavaCompleterServerInDirectory( app, + PathToTestFile( 'simple_maven_project', + 'simple_submodule', + 'src', + 'main', + 'java', + 'com', + 'test' ) ) + + project = PathToTestFile( 'simple_maven_project' ) + + # Run the debug info to check that we have the correct project dir + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + CompleterProjectDirectoryMatcher( project ) ) - time.sleep( 0.5 ) - assert_that( debug_info, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) - ) ) - ) ) + @SharedYcmd + def test_ServerManagement_OpenProject_RelativePathNoWD( self, app ): + response = app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'java', + command_arguments = [ + 'OpenProject', + os.path.join( '..', 'simple_maven_project' ), + ], + ), + expect_errors = True, + ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, + ErrorMatcher( ValueError, + 'Project directory must be absolute' ) ) -@IsolatedYcmd() -def ServerManagement_ServerDiesWhileShuttingDown_test( app ): - StartJavaCompleterServerInDirectory( - app, - PathToTestFile( 'simple_eclipse_project' ) ) + @SharedYcmd + def test_ServerManagement_OpenProject_RelativePathNoPath( self, app ): + response = app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'java', + command_arguments = [ + 'OpenProject', + ], + ), + expect_errors = True, + ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, + ErrorMatcher( ValueError, + 'Usage: OpenProject ' ) ) + + + def test_ServerManagement_ProjectDetection_NoParent( self ): + with TemporaryTestDir() as tmp_dir: + with isolated_app() as app: + StartJavaCompleterServerInDirectory( app, tmp_dir ) + # Run the debug info to check that we have the correct project dir (cwd) + request_data = BuildRequest( + filetype = 'java', + filepath = os.path.join( tmp_dir, 'foo.java' ) ) + assert_that( app.post_json( '/debug_info', request_data ).json, + CompleterProjectDirectoryMatcher( tmp_dir ) ) - request_data = BuildRequest( filetype = 'java' ) - debug_info = app.post_json( '/debug_info', request_data ).json - print( f'Debug info: { debug_info }' ) - pid = debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'pid' ] - print( f'pid: { pid }' ) - process = psutil.Process( pid ) + @IsolatedYcmd() + @patch( 'shutil.rmtree', side_effect = OSError ) + @patch( 'ycmd.utils.WaitUntilProcessIsTerminated', + MockProcessTerminationTimingOut ) + def test_ServerManagement_CloseServer_Unclean( self, app, *args ): + StartJavaCompleterServerInDirectory( + app, PathToTestFile( 'simple_eclipse_project' ) ) + + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'java', + command_arguments = [ 'StopServer' ] + ) + ) + + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) + ) ) + + + @IsolatedYcmd() + def test_ServerManagement_StopServerTwice( self, app ): + StartJavaCompleterServerInDirectory( + app, PathToTestFile( 'simple_eclipse_project' ) ) - def StopServerInAnotherThread(): app.post_json( '/run_completer_command', BuildRequest( @@ -468,51 +390,17 @@ def StopServerInAnotherThread(): ), ) - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) - - # In this test we mock out the sending method so that we don't actually send - # the shutdown request. We then assisted-suicide the downstream server, which - # causes the shutdown request to be aborted. This is interpreted by the - # shutdown code as a successful shutdown. We need to do the shutdown and - # terminate in parallel as the post_json is a blocking call. - with patch.object( completer.GetConnection(), 'WriteData' ): - stop_server_task = utils.StartThread( StopServerInAnotherThread ) - process.terminate() - stop_server_task.join() - - request_data = BuildRequest( filetype = 'java' ) - debug_info = app.post_json( '/debug_info', request_data ).json - assert_that( debug_info, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) ) ) - ) ) - - -@IsolatedYcmd() -def ServerManagement_ConnectionRaisesWhileShuttingDown_test( app ): - StartJavaCompleterServerInDirectory( - app, - PathToTestFile( 'simple_eclipse_project' ) ) - - request_data = BuildRequest( filetype = 'java' ) - debug_info = app.post_json( '/debug_info', request_data ).json - print( f'Debug info: { debug_info }' ) - pid = debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'pid' ] - print( f'pid: { pid }' ) - process = psutil.Process( pid ) - - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) - - # In this test we mock out the GetResponse method, which is used to send the - # shutdown request. This means we only send the exit notification. It's - # possible that the server won't like this, but it seems reasonable for it to - # actually exit at that point. - with patch.object( completer.GetConnection(), - 'GetResponse', - side_effect = RuntimeError ): + + + # Stopping a stopped server is a no-op app.post_json( '/run_completer_command', BuildRequest( @@ -521,43 +409,86 @@ def ServerManagement_ConnectionRaisesWhileShuttingDown_test( app ): ), ) - request_data = BuildRequest( filetype = 'java' ) - debug_info = app.post_json( '/debug_info', request_data ).json - assert_that( debug_info, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) ) ) - ) ) - if process.is_running(): + + @IsolatedYcmd() + def test_ServerManagement_ServerDies( self, app ): + StartJavaCompleterServerInDirectory( + app, + PathToTestFile( 'simple_eclipse_project' ) ) + + request_data = BuildRequest( filetype = 'java' ) + debug_info = app.post_json( '/debug_info', request_data ).json + print( f'Debug info: { debug_info }' ) + pid = debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'pid' ] + print( f'pid: { pid }' ) + process = psutil.Process( pid ) process.terminate() - raise AssertionError( 'jst.ls process is still running after exit handler' ) - - -@IsolatedYcmd() -def ServerManagement_StartServer_Fails_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'youcompleteme', - 'Test.java' ) - with patch( 'ycmd.completers.language_server.language_server_completer.' - 'LanguageServerConnection.AwaitServerConnection', - side_effect = LanguageServerConnectionTimeout ): - resp = app.post_json( '/event_notification', - BuildRequest( - event_name = 'FileReadyToParse', - filetype = 'java', - filepath = filepath, - contents = "" + + for tries in range( 0, 10 ): + request_data = BuildRequest( filetype = 'java' ) + debug_info = app.post_json( '/debug_info', request_data ).json + if not debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'is_running' ]: + break + + time.sleep( 0.5 ) + + assert_that( debug_info, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) ) ) + ) ) + - assert_that( resp.status_code, equal_to( 200 ) ) + @IsolatedYcmd() + def test_ServerManagement_ServerDiesWhileShuttingDown( self, app ): + StartJavaCompleterServerInDirectory( + app, + PathToTestFile( 'simple_eclipse_project' ) ) request_data = BuildRequest( filetype = 'java' ) - assert_that( app.post_json( '/debug_info', request_data ).json, + debug_info = app.post_json( '/debug_info', request_data ).json + print( f'Debug info: { debug_info }' ) + pid = debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'pid' ] + print( f'pid: { pid }' ) + process = psutil.Process( pid ) + + + def StopServerInAnotherThread(): + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'java', + command_arguments = [ 'StopServer' ], + ), + ) + + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + + # In this test we mock out the sending method so that we don't actually + # send the shutdown request. We then assisted-suicide the downstream + # server, which causes the shutdown request to be aborted. This is + # interpreted by the shutdown code as a successful shutdown. We need to do + # the shutdown and terminate in parallel as the post_json is a blocking + # call. + with patch.object( completer.GetConnection(), 'WriteData' ): + stop_server_task = utils.StartThread( StopServerInAnotherThread ) + process.terminate() + stop_server_task.join() + + request_data = BuildRequest( filetype = 'java' ) + debug_info = app.post_json( '/debug_info', request_data ).json + assert_that( debug_info, has_entry( 'completer', has_entry( 'servers', contains_exactly( @@ -566,6 +497,77 @@ def ServerManagement_StartServer_Fails_test( app ): ) ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd() + def test_ServerManagement_ConnectionRaisesWhileShuttingDown( self, app ): + StartJavaCompleterServerInDirectory( + app, + PathToTestFile( 'simple_eclipse_project' ) ) + + request_data = BuildRequest( filetype = 'java' ) + debug_info = app.post_json( '/debug_info', request_data ).json + print( f'Debug info: { debug_info }' ) + pid = debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'pid' ] + print( f'pid: { pid }' ) + process = psutil.Process( pid ) + + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + + # In this test we mock out the GetResponse method, which is used to send + # the shutdown request. This means we only send the exit notification. It's + # possible that the server won't like this, but it seems reasonable for it + # to actually exit at that point. + with patch.object( completer.GetConnection(), + 'GetResponse', + side_effect = RuntimeError ): + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'java', + command_arguments = [ 'StopServer' ], + ), + ) + + request_data = BuildRequest( filetype = 'java' ) + debug_info = app.post_json( '/debug_info', request_data ).json + assert_that( debug_info, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) + ) ) + + if process.is_running(): + process.terminate() + raise AssertionError( + 'jst.ls process is still running after exit handler' ) + + + @IsolatedYcmd() + def test_ServerManagement_StartServer_Fails( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'youcompleteme', + 'Test.java' ) + with patch( 'ycmd.completers.language_server.language_server_completer.' + 'LanguageServerConnection.AwaitServerConnection', + side_effect = LanguageServerConnectionTimeout ): + resp = app.post_json( '/event_notification', + BuildRequest( + event_name = 'FileReadyToParse', + filetype = 'java', + filepath = filepath, + contents = "" + ) ) + + assert_that( resp.status_code, equal_to( 200 ) ) + + request_data = BuildRequest( filetype = 'java' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) + ) ) diff --git a/ycmd/tests/java/signature_help_test.py b/ycmd/tests/java/signature_help_test.py index 8400fbd46c..2ef6526d3d 100644 --- a/ycmd/tests/java/signature_help_test.py +++ b/ycmd/tests/java/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -20,10 +20,11 @@ empty, equal_to, has_entries ) +from unittest import TestCase import requests from ycmd.utils import ReadFile -from ycmd.tests.java import PathToTestFile, SharedYcmd +from ycmd.tests.java import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( CombineRequest, ParameterMatcher, SignatureMatcher, @@ -74,110 +75,106 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@WithRetry -@SharedYcmd -def SignatureHelp_MethodTrigger_test( app ): - RunTest( app, { - 'description': 'Trigger after (', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'SignatureHelp.java' ), - 'line_num' : 9, - 'column_num': 17, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'unique(double d) : void', - [ ParameterMatcher( 7, 15 ) ] ) - ), - } ), - } ) - } - } ) - - -@WithRetry -@SharedYcmd -def SignatureHelp_ArgTrigger_test( app ): - RunTest( app, { - 'description': 'Trigger after ,', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'SignatureHelp.java' ), - 'line_num' : 5, - 'column_num': 23, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 1, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( 'test(int i, String s) : void', - [ ParameterMatcher( 5, 10 ), - ParameterMatcher( 12, 20 ) ] ), - SignatureMatcher( 'test(String s, String s1) : void', - [ ParameterMatcher( 5, 13 ), - ParameterMatcher( 15, 24 ) ] ) - ), - } ), - } ) - } - } ) - - -@WithRetry -@SharedYcmd -def SignatureHelp_Constructor_test( app ): - RunTest( app, { - 'description': 'Constructor', - 'request': { - 'filetype' : 'java', - 'filepath' : ProjectPath( 'SignatureHelp.java' ), - 'line_num' : 17, - 'column_num': 41, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'SignatureHelp(String signature)', - [ ParameterMatcher( 14, 30 ) ] ) - ), - } ), - } ) - } - } ) - - -@SharedYcmd -def Signature_Help_Available_test( app ): - request = { 'filepath' : ProjectPath( 'SignatureHelp.java' ) } - app.post_json( '/event_notification', - CombineRequest( request, { - 'event_name': 'FileReadyToParse', - 'filetype': 'java' - } ), - expect_errors = True ) - WaitUntilCompleterServerReady( app, 'java' ) - - response = app.get( '/signature_help_available', - { 'subserver': 'java' } ).json - assert_that( response, SignatureAvailableMatcher( 'YES' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class SignatureHelpTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_SignatureHelp_MethodTrigger( self, app ): + RunTest( app, { + 'description': 'Trigger after (', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'SignatureHelp.java' ), + 'line_num' : 9, + 'column_num': 17, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'unique(double d) : void', + [ ParameterMatcher( 7, 15 ) ] ) + ), + } ), + } ) + } + } ) + + + @WithRetry() + @SharedYcmd + def test_SignatureHelp_ArgTrigger( self, app ): + RunTest( app, { + 'description': 'Trigger after ,', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'SignatureHelp.java' ), + 'line_num' : 5, + 'column_num': 23, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 1, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( 'test(int i, String s) : void', + [ ParameterMatcher( 5, 10 ), + ParameterMatcher( 12, 20 ) ] ), + SignatureMatcher( 'test(String s, String s1) : void', + [ ParameterMatcher( 5, 13 ), + ParameterMatcher( 15, 24 ) ] ) + ), + } ), + } ) + } + } ) + + + @WithRetry() + @SharedYcmd + def test_SignatureHelp_Constructor( self, app ): + RunTest( app, { + 'description': 'Constructor', + 'request': { + 'filetype' : 'java', + 'filepath' : ProjectPath( 'SignatureHelp.java' ), + 'line_num' : 17, + 'column_num': 41, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'SignatureHelp(String signature)', + [ ParameterMatcher( 14, 30 ) ] ) + ), + } ), + } ) + } + } ) + + + @SharedYcmd + def test_Signature_Help_Available( self, app ): + request = { 'filepath' : ProjectPath( 'SignatureHelp.java' ) } + app.post_json( '/event_notification', + CombineRequest( request, { + 'event_name': 'FileReadyToParse', + 'filetype': 'java' + } ), + expect_errors = True ) + WaitUntilCompleterServerReady( app, 'java' ) + + response = app.get( '/signature_help_available', + { 'subserver': 'java' } ).json + assert_that( response, SignatureAvailableMatcher( 'YES' ) ) diff --git a/ycmd/tests/java/subcommands_test.py b/ycmd/tests/java/subcommands_test.py index 8284bd42e3..adf1ac42ab 100644 --- a/ycmd/tests/java/subcommands_test.py +++ b/ycmd/tests/java/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2020 ycmd contributors +# Copyright (C) 2017-2021 ycmd contributors # # This file is part of ycmd. # @@ -27,16 +27,18 @@ is_not, matches_regexp ) from pprint import pformat +import itertools import requests -import pytest import json from ycmd.utils import ReadFile from ycmd.completers.java.java_completer import NO_DOCUMENTATION_MESSAGE -from ycmd.tests.java import ( PathToTestFile, +from ycmd.tests.java import ( PathToTestFile, # noqa SharedYcmd, StartJavaCompleterServerWithFile, - IsolatedYcmd ) + IsolatedYcmd, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, CombineRequest, @@ -45,6 +47,7 @@ LocationMatcher, WithRetry ) from unittest.mock import patch +from unittest import TestCase from ycmd import handlers from ycmd.completers.language_server import language_server_protocol as lsp from ycmd.completers.language_server.language_server_completer import ( @@ -73,78 +76,6 @@ 'Tset.java' ) -@WithRetry -@SharedYcmd -def Subcommands_DefinedSubcommands_test( app ): - subcommands_data = BuildRequest( completer_target = 'java' ) - - assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_inanyorder( - 'FixIt', - 'ExecuteCommand', - 'Format', - 'GoToDeclaration', - 'GoToDefinition', - 'GoToDocumentOutline', - 'GoTo', - 'GetDoc', - 'GetType', - 'GoToImplementation', - 'GoToReferences', - 'GoToType', - 'GoToSymbol', - 'OpenProject', - 'OrganizeImports', - 'RefactorRename', - 'RestartServer', - 'WipeWorkspace' ) ) - - -@pytest.mark.parametrize( 'cmd,arguments', [ - ( 'GoTo', [] ), - ( 'GoToDeclaration', [] ), - ( 'GoToDefinition', [] ), - ( 'GoToReferences', [] ), - ( 'GoToDocumentOutline', [] ), - ( 'GoToSymbol', [ 'test' ] ), - ( 'GetType', [] ), - ( 'GetDoc', [] ), - ( 'FixIt', [] ), - ( 'Format', [] ), - ( 'OrganizeImports', [] ), - ( 'RefactorRename', [ 'test' ] ), -] ) -@SharedYcmd -def Subcommands_ServerNotInitialized_test( app, cmd, arguments ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) - - completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) - - @patch.object( completer, '_ServerIsInitialized', return_value = False ) - def Test( app, cmd, arguments, *args ): - RunTest( app, { - 'description': 'Subcommand ' + cmd + ' handles server not ready', - 'request': { - 'command': cmd, - 'line_num': 1, - 'column_num': 1, - 'filepath': filepath, - 'arguments': arguments, - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, - 'Server is initializing. Please wait.' ), - } - } ) - - Test( app, cmd, arguments ) - - def RunTest( app, test, contents = None ): if not contents: contents = ReadFile( test[ 'request' ][ 'filepath' ] ) @@ -191,2121 +122,2205 @@ def RunTest( app, test, contents = None ): time.sleep( 0.25 ) -@WithRetry -@SharedYcmd -def Subcommands_GetDoc_NoDoc_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 18, - column_num = 1, - contents = contents, - command_arguments = [ 'GetDoc' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( response.json, - ErrorMatcher( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetDoc_Method_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 17, - column_num = 17, - contents = contents, - command_arguments = [ 'GetDoc' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( 'detailed_info', - 'Return runtime debugging info. Useful for finding the ' - 'actual code which is useful.' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetDoc_Class_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 11, - column_num = 7, - contents = contents, - command_arguments = [ 'GetDoc' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( 'detailed_info', - 'This is the actual code that matters. This concrete ' - 'implementation is the equivalent of the main function ' - 'in other languages' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_NoKnownType_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 28, - column_num = 1, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( response.json, - ErrorMatcher( RuntimeError, 'Unknown type' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_Class_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 11, - column_num = 7, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( 'message', 'com.test.TestWidgetImpl' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_Constructor_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 14, - column_num = 3, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( - 'message', 'com.test.TestWidgetImpl.TestWidgetImpl(String info)' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_ClassMemberVariable_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 12, - column_num = 18, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( 'message', 'String info' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_MethodArgument_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 16, - column_num = 17, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( - 'message', 'String info - com.test.TestWidgetImpl.TestWidgetImpl(String)' - ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_MethodVariable_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 15, - column_num = 9, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( - 'message', 'int a - com.test.TestWidgetImpl.TestWidgetImpl(String)' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_Method_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 20, - column_num = 15, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( - 'message', 'void com.test.TestWidgetImpl.doSomethingVaguelyUseful()' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_Unicode_test( app ): - contents = ReadFile( TEST_JAVA ) - - app.post_json( '/event_notification', - BuildRequest( filepath = TEST_JAVA, - filetype = 'java', - contents = contents, - event_name = 'FileReadyToParse' ) ) - - event_data = BuildRequest( filepath = TEST_JAVA, - filetype = 'java', - line_num = 7, - column_num = 17, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entry( - 'message', 'String whåtawîdgé - com.youcompleteme.Test.doUnicødeTes()' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_LiteralValue_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 15, - column_num = 13, - contents = contents, - command_arguments = [ 'GetType' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( response.json, - ErrorMatcher( RuntimeError, 'Unknown type' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GoTo_NoLocation_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 18, - column_num = 1, - contents = contents, - command_arguments = [ 'GoTo' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( response.json, - ErrorMatcher( RuntimeError, 'Cannot jump to location' ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GoToReferences_NoReferences_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 2, - column_num = 1, - contents = contents, - command_arguments = [ 'GoToReferences' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( response.json, - ErrorMatcher( RuntimeError, - 'Cannot jump to location' ) ) - - -@WithRetry -@IsolatedYcmd( { - 'extra_conf_globlist': PathToTestFile( 'multiple_projects', '*' ) -} ) -def Subcommands_GoToReferences_MultipleProjects_test( app ): - filepath = PathToTestFile( 'multiple_projects', - 'src', - 'core', - 'java', - 'com', - 'puremourning', - 'widget', - 'core', - 'Utils.java' ) - StartJavaCompleterServerWithFile( app, filepath ) - - +def RunFixItTest( app, description, filepath, line, col, fixits_for_line ): RunTest( app, { - 'description': 'GoToReferences works across multiple projects', + 'description': description, 'request': { - 'command': 'GoToReferences', + 'command': 'FixIt', + 'line_num': line, + 'column_num': col, 'filepath': filepath, - 'line_num': 5, - 'column_num': 22, }, 'expect': { 'response': requests.codes.ok, - 'data': contains_inanyorder( - LocationMatcher( filepath, 8, 35 ), - LocationMatcher( PathToTestFile( 'multiple_projects', - 'src', - 'input', - 'java', - 'com', - 'puremourning', - 'widget', - 'input', - 'InputApp.java' ), - 8, - 16 ) - ) + 'data': fixits_for_line, } } ) - -@WithRetry -@SharedYcmd -def Subcommands_GoToReferences_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'java', - line_num = 10, - column_num = 15, - contents = contents, - command_arguments = [ 'GoToReferences' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, contains_exactly( has_entries( { - 'filepath': PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ), - 'column_num': 9, - 'description': " w.doSomethingVaguelyUseful();", - 'line_num': 28 - } ), - has_entries( { - 'filepath': PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestLauncher.java' ), - 'column_num': 11, - 'description': " w.doSomethingVaguelyUseful();", - 'line_num': 32 - } ) ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GoToSymbol_SingleSameFile_test( app ): - contents = ReadFile( TEST_JAVA ) - - event_data = BuildRequest( filepath = TEST_JAVA, - filetype = 'java', - line_num = 1, - column_num = 1, - contents = contents, - command_arguments = [ 'GoToSymbol', 'TéstClass' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, has_entries( { - 'filepath': TEST_JAVA, - 'description': "Class: TéstClass", - 'line_num': 20, - 'column_num': 16, - } ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GoToSymbol_Multiple_test( app ): - contents = ReadFile( TEST_JAVA ) - - event_data = BuildRequest( filepath = TEST_JAVA, - filetype = 'java', - line_num = 1, - column_num = 1, - contents = contents, - command_arguments = [ 'GoToSymbol', 'test' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', event_data ).json - - assert_that( response, contains_inanyorder( - has_entries( { - 'filepath': PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ) , - 'description': "Class: TestFactory", - 'line_num': 12, - 'column_num': 14, - } ), - has_entries( { - 'filepath': PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWidgetImpl.java' ) , - 'description': "Class: TestWidgetImpl", - 'line_num': 11, - 'column_num': 7, - } ), - has_entries( { - 'filepath': PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestLauncher.java' ) , - 'description': "Class: TestLauncher", - 'line_num': 6, - 'column_num': 7, - } ), - has_entries( { - 'filepath': PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'youcompleteme', - 'Test.java' ) , - 'description': "Class: Test", - 'line_num': 3, - 'column_num': 14, - } ), - has_entries( { - 'filepath': PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestWithDocumentation.java' ) , - 'description': "Class: TestWithDocumentation", - 'line_num': 3, - 'column_num': 14, - } ) - ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_GoToSymbol_None_test( app ): - contents = ReadFile( TEST_JAVA ) - - event_data = BuildRequest( filepath = TEST_JAVA, - filetype = 'java', - line_num = 1, - column_num = 1, - contents = contents, - command_arguments = [ 'GoToSymbol', 'abcd' ], - completer_target = 'filetype_default' ) - - response = app.post_json( '/run_completer_command', - event_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( response.json, - ErrorMatcher( RuntimeError, 'Symbol not found' ) ) - - - -@WithRetry -@SharedYcmd -def Subcommands_RefactorRename_Simple_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestLauncher.java' ) +@WithRetry() +def RunGoToTest( app, description, filepath, line, col, cmd, goto_response ): RunTest( app, { - 'description': 'RefactorRename works within a single scope/file', + 'description': description, 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'renamed_l' ], - 'filepath': filepath, - 'line_num': 28, - 'column_num': 5, + 'command': cmd, + 'line_num': line, + 'column_num': col, + 'filepath': filepath }, 'expect': { 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( 'renamed_l = new TestLauncher( 10 );' - '\n renamed_l', - LocationMatcher( filepath, 27, 18 ), - LocationMatcher( filepath, 28, 6 ) ), - ), - 'location': LocationMatcher( filepath, 28, 5 ) - } ) ) - } ) + 'data': goto_response, } } ) -@ExpectedFailure( 'Renaming does not work on overridden methods ' - 'since jdt.ls 0.21.0', - matches_regexp( 'No item matched:.*TestWidgetImpl.java' ) ) -@WithRetry -@SharedYcmd -def Subcommands_RefactorRename_MultipleFiles_test( app ): - AbstractTestWidget = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'AbstractTestWidget.java' ) - TestFactory = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ) - TestLauncher = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestLauncher.java' ) - TestWidgetImpl = PathToTestFile( 'simple_eclipse_project', +class SubcommandsTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_Subcommands_DefinedSubcommands( self, app ): + subcommands_data = BuildRequest( completer_target = 'java' ) + + assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, + contains_inanyorder( + 'FixIt', + 'ExecuteCommand', + 'Format', + 'GoToDeclaration', + 'GoToDefinition', + 'GoToDocumentOutline', + 'GoTo', + 'GetDoc', + 'GetType', + 'GoToImplementation', + 'GoToReferences', + 'GoToType', + 'GoToSymbol', + 'OpenProject', + 'OrganizeImports', + 'RefactorRename', + 'RestartServer', + 'WipeWorkspace' ) ) + + + @SharedYcmd + def test_Subcommands_ServerNotInitialized( self, app ): + for cmd, arguments in [ + ( 'GoTo', [] ), + ( 'GoToDeclaration', [] ), + ( 'GoToDefinition', [] ), + ( 'GoToReferences', [] ), + ( 'GoToDocumentOutline', [] ), + ( 'GoToSymbol', [ 'test' ] ), + ( 'GetType', [] ), + ( 'GetDoc', [] ), + ( 'FixIt', [] ), + ( 'Format', [] ), + ( 'OrganizeImports', [] ), + ( 'RefactorRename', [ 'test' ] ), + ]: + with self.subTest( cmd = cmd, arguments = arguments ): + filepath = PathToTestFile( 'simple_eclipse_project', 'src', 'com', 'test', - 'TestWidgetImpl.java' ) + 'AbstractTestWidget.java' ) + + completer = handlers._server_state.GetFiletypeCompleter( [ 'java' ] ) + + @patch.object( completer, '_ServerIsInitialized', return_value = False ) + def Test( app, cmd, arguments, *args ): + RunTest( app, { + 'description': 'Subcommand ' + cmd + ' handles server not ready', + 'request': { + 'command': cmd, + 'line_num': 1, + 'column_num': 1, + 'filepath': filepath, + 'arguments': arguments, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, + 'Server is initializing. Please wait.' ), + } + } ) + + Test( app, cmd, arguments ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GetDoc_NoDoc( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 18, + column_num = 1, + contents = contents, + command_arguments = [ 'GetDoc' ], + completer_target = 'filetype_default' ) - RunTest( app, { - 'description': 'RefactorRename works across files', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'a_quite_long_string' ], - 'filepath': TestLauncher, - 'line_num': 32, - 'column_num': 13, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( - 'a_quite_long_string', - LocationMatcher( AbstractTestWidget, 10, 15 ), - LocationMatcher( AbstractTestWidget, 10, 39 ) ), - ChunkMatcher( - 'a_quite_long_string', - LocationMatcher( TestFactory, 28, 9 ), - LocationMatcher( TestFactory, 28, 33 ) ), - ChunkMatcher( - 'a_quite_long_string', - LocationMatcher( TestLauncher, 32, 11 ), - LocationMatcher( TestLauncher, 32, 35 ) ), - ChunkMatcher( - 'a_quite_long_string', - LocationMatcher( TestWidgetImpl, 20, 15 ), - LocationMatcher( TestWidgetImpl, 20, 39 ) ), - ), - 'location': LocationMatcher( TestLauncher, 32, 13 ) - } ) ) - } ) - } - } ) + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) -@WithRetry -@SharedYcmd -def Subcommands_RefactorRename_Missing_New_Name_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestLauncher.java' ) - RunTest( app, { - 'description': 'RefactorRename raises an error without new name', - 'request': { - 'command': 'RefactorRename', - 'line_num': 15, - 'column_num': 5, - 'filepath': filepath, - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( ValueError, - 'Please specify a new name to rename it to.\n' - 'Usage: RefactorRename ' ), - } - } ) + assert_that( response.json, + ErrorMatcher( RuntimeError, NO_DOCUMENTATION_MESSAGE ) ) -@WithRetry -@SharedYcmd -def Subcommands_RefactorRename_Unicode_test( app ): - RunTest( app, { - 'description': 'Rename works for unicode identifier', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'shorter' ], - 'line_num': 7, - 'column_num': 21, - 'filepath': TEST_JAVA, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( - 'shorter = "Test";\n return shorter', - LocationMatcher( TEST_JAVA, 7, 12 ), - LocationMatcher( TEST_JAVA, 8, 25 ) - ), - ), - } ) ), - } ), - }, - } ) + @WithRetry() + @SharedYcmd + def test_Subcommands_GetDoc_Method( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) + contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 17, + column_num = 17, + contents = contents, + command_arguments = [ 'GetDoc' ], + completer_target = 'filetype_default' ) + response = app.post_json( '/run_completer_command', event_data ).json -def RunFixItTest( app, description, filepath, line, col, fixits_for_line ): - RunTest( app, { - 'description': description, - 'request': { - 'command': 'FixIt', - 'line_num': line, - 'column_num': col, - 'filepath': filepath, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': fixits_for_line, - } - } ) + assert_that( response, has_entry( 'detailed_info', + 'Return runtime debugging info. Useful for finding the ' + 'actual code which is useful.' ) ) -@WithRetry -@pytest.mark.parametrize( 'description,column', [ - ( 'FixIt works at the firtst char of the line', 1 ), - ( 'FixIt works at the begin of the range of the diag.', 15 ), - ( 'FixIt works at the end of the range of the diag.', 20 ), - ( 'FixIt works at the end of the line', 34 ), -] ) -@SharedYcmd -def Subcommands_FixIt_SingleDiag_MultipleOption_Insertion_test( app, - description, - column ): - import os - wibble_path = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'Wibble.java' ) - wibble_text = 'package com.test;{0}{0}public {1} Wibble {{{0}{0}}}{0}' - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ) - - # Note: The code actions for creating variables are really not very useful. - # The import is, however, and the FixIt almost exactly matches the one - # supplied when completing 'CUTHBERT' and auto-inserting. - fixits_for_line = has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': "Import 'Wibble' (com.test.wobble)", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( 'package com.test;\n\n' - 'import com.test.wobble.Wibble;\n\n', - LocationMatcher( filepath, 1, 1 ), - LocationMatcher( filepath, 3, 1 ) ), - ), - } ), - has_entries( { - 'text': "Create constant 'Wibble'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( '\n\nprivate static final String Wibble = null;', - LocationMatcher( filepath, 16, 4 ), - LocationMatcher( filepath, 16, 4 ) ), - ), - } ), - has_entries( { - 'text': "Create class 'Wibble'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( wibble_text.format( os.linesep, 'class' ), - LocationMatcher( wibble_path, 1, 1 ), - LocationMatcher( wibble_path, 1, 1 ) ), - ), - } ), - has_entries( { - 'text': "Create interface 'Wibble'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( wibble_text.format( os.linesep, 'interface' ), - LocationMatcher( wibble_path, 1, 1 ), - LocationMatcher( wibble_path, 1, 1 ) ), - ), - } ), - has_entries( { - 'text': "Create enum 'Wibble'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( wibble_text.format( os.linesep, 'enum' ), - LocationMatcher( wibble_path, 1, 1 ), - LocationMatcher( wibble_path, 1, 1 ) ), - ), - } ), - has_entries( { - 'text': "Create local variable 'Wibble'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( 'Object Wibble;\n\t', - LocationMatcher( filepath, 19, 5 ), - LocationMatcher( filepath, 19, 5 ) ), - ), - } ), - has_entries( { - 'text': "Create field 'Wibble'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( '\n\nprivate Object Wibble;', - LocationMatcher( filepath, 16, 4 ), - LocationMatcher( filepath, 16, 4 ) ), - ), - } ), - has_entries( { - 'text': "Create parameter 'Wibble'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( ', Object Wibble', - LocationMatcher( filepath, 18, 32 ), - LocationMatcher( filepath, 18, 32 ) ), - ), - } ), - has_entries( { - 'text': 'Generate toString()...', - 'kind': 'source.generate.toString', - 'chunks': contains_exactly( - ChunkMatcher( '\n\n@Override\npublic String toString() {' - '\n\treturn "TestFactory []";\n}', - LocationMatcher( filepath, 32, 4 ), - LocationMatcher( filepath, 32, 4 ) ), - ), - } ), - has_entries( { - 'text': 'Organize imports', - 'kind': 'source.organizeImports', - 'chunks': contains_exactly( - ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n', - LocationMatcher( filepath, 1, 18 ), - LocationMatcher( filepath, 3, 1 ) ), - ), - } ), - has_entries( { - 'text': 'Change modifiers to final where possible', - 'kind': 'source.generate.finalModifiers', - 'chunks': contains_exactly( - ChunkMatcher( 'final Wibble w ) {\n if ( w == Wibble.CUTHBERT ) {' - '\n }\n }\n\n public AbstractTestWidget getWidget' - '( final String info ) {\n final AbstractTestWidget' - ' w = new TestWidgetImpl( info );\n final ', - LocationMatcher( filepath, 18, 24 ), - LocationMatcher( filepath, 25, 5 ) ), - ), - } ), - ) - } ) + @WithRetry() + @SharedYcmd + def test_Subcommands_GetDoc_Class( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) - RunFixItTest( app, description, filepath, 19, column, fixits_for_line ) + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 11, + column_num = 7, + contents = contents, + command_arguments = [ 'GetDoc' ], + completer_target = 'filetype_default' ) + response = app.post_json( '/run_completer_command', event_data ).json -@WithRetry -@SharedYcmd -def Subcommands_FixIt_SingleDiag_SingleOption_Modify_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ) + assert_that( response, has_entry( 'detailed_info', + 'This is the actual code that matters. This concrete ' + 'implementation is the equivalent of the main function ' + 'in other languages' ) ) - # TODO: As there is only one option, we automatically apply it. - # In Java case this might not be the right thing. It's a code assist, not a - # FixIt really. Perhaps we should change the client to always ask for - # confirmation? - fixits = has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': "Change type of 'test' to 'boolean'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( 'boolean', - LocationMatcher( filepath, 14, 12 ), - LocationMatcher( filepath, 14, 15 ) ), - ), - } ), - has_entries( { - 'text': 'Generate toString()...', - 'kind': 'source.generate.toString', - 'chunks': contains_exactly( - ChunkMatcher( '\n\n@Override\npublic String toString() {' - '\n\treturn "TestFactory []";\n}', - LocationMatcher( filepath, 32, 4 ), - LocationMatcher( filepath, 32, 4 ) ), - ), - } ), - has_entries( { - 'text': 'Organize imports', - 'kind': 'source.organizeImports', - 'chunks': contains_exactly( - ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n', - LocationMatcher( filepath, 1, 18 ), - LocationMatcher( filepath, 3, 1 ) ), - ), - } ), - has_entries( { - 'text': 'Change modifiers to final where possible', - 'kind': 'source.generate.finalModifiers', - 'chunks': contains_exactly( - ChunkMatcher( 'final Wibble w ) {\n if ( w == Wibble.CUTHBERT ) {' - '\n }\n }\n\n public AbstractTestWidget getWidget' - '( final String info ) {\n final AbstractTestWidget' - ' w = new TestWidgetImpl( info );\n final ', - LocationMatcher( filepath, 18, 24 ), - LocationMatcher( filepath, 25, 5 ) ), - ), - } ), - ) - } ) - RunFixItTest( app, 'FixIts can change lines as well as add them', - filepath, 27, 12, fixits ) + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_NoKnownType( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 28, + column_num = 1, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) -@WithRetry -@SharedYcmd -def Subcommands_FixIt_SingleDiag_MultiOption_Delete_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ) + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) - fixits = has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': "Remove 'testString', keep assignments with side effects", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( '', - LocationMatcher( filepath, 14, 21 ), - LocationMatcher( filepath, 15, 30 ) ), - ), - } ), - # The edit reported for this is huge and uninteresting really. Manual - # testing can show that it works. This test is really about the previous - # FixIt (and nonetheless, the previous tests ensure that we correctly - # populate the chunks list; the contents all come from jdt.ls) - has_entries( { - 'text': "Create getter and setter for 'testString'", - 'chunks': instance_of( list ) - } ), - has_entries( { - 'text': "Organize imports", - 'chunks': instance_of( list ) - } ), - has_entries( { - 'text': "Generate Getters and Setters", - 'chunks': instance_of( list ) - } ), - has_entries( { - 'text': 'Change modifiers to final where possible', - 'chunks': instance_of( list ) - } ), - ) - } ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) - RunFixItTest( app, 'FixIts can change lines as well as add them', - filepath, 15, 29, fixits ) - - -@WithRetry -@pytest.mark.parametrize( 'description,column,expect_fixits', [ - ( 'diags are merged in FixIt options - start of line', 1, 'MERGE' ), - ( 'diags are not merged in FixIt options - start of diag 1', 10, 'FIRST' ), - ( 'diags are not merged in FixIt options - end of diag 1', 15, 'FIRST' ), - ( 'diags are not merged in FixIt options - start of diag 2', 23, 'SECOND' ), - ( 'diags are not merged in FixIt options - end of diag 2', 46, 'SECOND' ), - ( 'diags are merged in FixIt options - end of line', 55, 'MERGE' ), -] ) -@SharedYcmd -def Subcommands_FixIt_MultipleDiags_test( app, - description, - column, - expect_fixits ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ) - - FIRST = [ - has_entries( { - 'text': "Change type of 'test' to 'boolean'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( 'boolean', - LocationMatcher( filepath, 14, 12 ), - LocationMatcher( filepath, 14, 15 ) ), - ), - } ), - ] - SECOND = [ - has_entries( { - 'text': "Remove argument to match 'doSomethingVaguelyUseful()'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( '', - LocationMatcher( filepath, 30, 48 ), - LocationMatcher( filepath, 30, 50 ) ), - ), - } ), - has_entries( { - 'text': "Change method 'doSomethingVaguelyUseful()': Add parameter " - "'Bar'", - 'chunks': instance_of( list ), - } ), - has_entries( { - 'text': "Create method 'doSomethingVaguelyUseful(Bar)' in type " - "'AbstractTestWidget'", - 'chunks': instance_of( list ), - } ), - ] - - ACTIONS = [ - has_entries( { - 'text': "Generate toString()...", - 'chunks': instance_of( list ), - } ), - has_entries( { - 'text': "Organize imports", - 'chunks': instance_of( list ), - } ), - has_entries( { - 'text': 'Change modifiers to final where possible', - 'chunks': instance_of( list ), - } ), - ] - - FIXITS = { - 'FIRST': FIRST + ACTIONS, - 'SECOND': SECOND + ACTIONS, - 'MERGE': FIRST + SECOND + ACTIONS, - } - - fixits = has_entries( { - 'fixits': contains_inanyorder( *FIXITS[ expect_fixits ] ) - } ) + assert_that( response.json, + ErrorMatcher( RuntimeError, 'Unknown type' ) ) - RunFixItTest( app, description, filepath, 30, column, fixits ) + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_Class( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) -@SharedYcmd -def Subcommands_FixIt_Range_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestLauncher.java' ) - RunTest( app, { - 'description': 'Formatting is applied on some part of the file ' - 'with tabs composed of 4 spaces', - 'request': { - 'command': 'FixIt', - 'filepath': filepath, - 'range': { - 'start': { - 'line_num': 34, - 'column_num': 28, - }, - 'end': { - 'line_num': 34, - 'column_num': 73 - } - }, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': 'Extract to field', - 'kind': 'refactor.extract.field', - 'chunks': contains_exactly( - ChunkMatcher( - matches_regexp( - 'private String \\w+;\n' - '\n' - '\t@Override\n' - ' public void launch\\(\\) {\n' - ' AbstractTestWidget w = ' - 'factory.getWidget\\( "Test" \\);\n' - ' ' - 'w.doSomethingVaguelyUseful\\(\\);\n' - '\n' - ' \\w+ = "Did something ' - 'useful: " \\+ w.getWidgetInfo\\(\\);\n' - '\t\tSystem.out.println\\( \\w+' ), - LocationMatcher( filepath, 29, 7 ), - LocationMatcher( filepath, 34, 73 ) ), - ), - } ), - has_entries( { - 'text': 'Extract to method', - 'kind': 'refactor.extract.function', - 'chunks': contains_exactly( - # This one is a wall of text that rewrites 35 lines - ChunkMatcher( instance_of( str ), - LocationMatcher( filepath, 1, 1 ), - LocationMatcher( filepath, 35, 8 ) ), - ), - } ), - has_entries( { - 'text': 'Extract to local variable (replace all occurrences)', - 'kind': 'refactor.extract.variable', - 'chunks': contains_exactly( - ChunkMatcher( - matches_regexp( - 'String \\w+ = "Did something ' - 'useful: " \\+ w.getWidgetInfo\\(\\);\n' - '\t\tSystem.out.println\\( \\w+' ), - LocationMatcher( filepath, 34, 9 ), - LocationMatcher( filepath, 34, 73 ) ), - ), - } ), - has_entries( { - 'text': 'Extract to local variable', - 'kind': 'refactor.extract.variable', - 'chunks': contains_exactly( - ChunkMatcher( - matches_regexp( - 'String \\w+ = "Did something ' - 'useful: " \\+ w.getWidgetInfo\\(\\);\n' - '\t\tSystem.out.println\\( \\w+' ), - LocationMatcher( filepath, 34, 9 ), - LocationMatcher( filepath, 34, 73 ) ), - ), - } ), - has_entries( { - 'text': 'Introduce Parameter...', - 'kind': 'refactor.introduce.parameter', - 'chunks': contains_exactly( - ChunkMatcher( - 'String string) {\n' - ' AbstractTestWidget w = factory.getWidget( "Test" );\n' - ' w.doSomethingVaguelyUseful();\n' - '\n' - ' System.out.println( string', - LocationMatcher( filepath, 30, 26 ), - LocationMatcher( filepath, 34, 73 ) ), - ), - } ), - has_entries( { - 'text': 'Organize imports', - 'chunks': instance_of( list ), - } ), - has_entries( { - 'text': 'Change modifiers to final where possible', - 'chunks': instance_of( list ), - } ), - ) - } ) - } - } ) + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 11, + column_num = 7, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) + response = app.post_json( '/run_completer_command', event_data ).json + assert_that( response, has_entry( 'message', 'com.test.TestWidgetImpl' ) ) -@WithRetry -@SharedYcmd -def Subcommands_FixIt_NoDiagnostics_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ) - - RunFixItTest( app, "no FixIts means you gotta code it yo' self", - filepath, 1, 1, has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': 'Change modifiers to final where possible', - 'chunks': instance_of( list ) } ), - has_entries( { 'text': 'Organize imports', - 'chunks': instance_of( list ) } ), - has_entries( { 'text': 'Generate toString()...', - 'chunks': instance_of( list ) } ) ) } ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_FixIt_Unicode_test( app ): - fixits = has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': "Remove argument to match 'doUnicødeTes()'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( '', - LocationMatcher( TEST_JAVA, 13, 24 ), - LocationMatcher( TEST_JAVA, 13, 29 ) ), - ), - } ), - has_entries( { - 'text': "Change method 'doUnicødeTes()': Add parameter 'String'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( 'String test2', - LocationMatcher( TEST_JAVA, 6, 31 ), - LocationMatcher( TEST_JAVA, 6, 31 ) ), - ), - } ), - has_entries( { - 'text': "Create method 'doUnicødeTes(String)'", - 'kind': 'quickfix', - 'chunks': contains_exactly( - ChunkMatcher( 'private void doUnicødeTes(String test2) {\n}\n\n\n', - LocationMatcher( TEST_JAVA, 20, 3 ), - LocationMatcher( TEST_JAVA, 20, 3 ) ), - ), - } ), - has_entries( { - 'text': 'Change modifiers to final where possible', - 'chunks': instance_of( list ), - } ), - has_entries( { - 'text': "Generate Getters and Setters", - 'chunks': instance_of( list ), - } ), - ) - } ) - RunFixItTest( app, 'FixIts and diagnostics work with unicode strings', - TEST_JAVA, 13, 1, fixits ) + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_Constructor( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 14, + column_num = 3, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) -@WithRetry -@IsolatedYcmd() -def Subcommands_FixIt_InvalidURI_test( app ): - filepath = PathToTestFile( 'simple_eclipse_project', - 'src', - 'com', - 'test', - 'TestFactory.java' ) + response = app.post_json( '/run_completer_command', event_data ).json - fixits = has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'kind': 'quickfix', - 'text': "Change type of 'test' to 'boolean'", - 'chunks': contains_exactly( - ChunkMatcher( 'boolean', - LocationMatcher( '', 14, 12 ), - LocationMatcher( '', 14, 15 ) ), - ), - } ), - has_entries( { - 'text': 'Organize imports', - 'kind': 'source.organizeImports', - 'chunks': contains_exactly( - ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n', - LocationMatcher( '', 1, 1 ), - LocationMatcher( '', 3, 1 ) ), - ), - } ), - has_entries( { - 'text': 'Change modifiers to final where possible', - 'kind': 'source.generate.finalModifiers', - 'chunks': contains_exactly( - ChunkMatcher( "final Wibble w ) {\n if ( w == Wibble.CUTHBERT ) {" - "\n }\n }\n\n public AbstractTestWidget getWidget" - "( final String info ) {\n final AbstractTestWidget" - " w = new TestWidgetImpl( info );\n final ", - LocationMatcher( '', 18, 24 ), - LocationMatcher( '', 25, 5 ) ), - ), - } ), - has_entries( { - 'text': 'Generate toString()...', - 'kind': 'source.generate.toString', - 'chunks': contains_exactly( - ChunkMatcher( '\n\n@Override\npublic String toString() {' - '\n\treturn "TestFactory []";\n}', - LocationMatcher( '', 32, 4 ), - LocationMatcher( '', 32, 4 ) ), - ), - } ), - ) - } ) + assert_that( response, has_entry( + 'message', 'com.test.TestWidgetImpl.TestWidgetImpl(String info)' ) ) - contents = ReadFile( filepath ) - # Wait for jdt.ls to have parsed the file and returned some diagnostics - for tries in range( 0, 60 ): - results = app.post_json( '/event_notification', - BuildRequest( filepath = filepath, - filetype = 'java', - contents = contents, - event_name = 'FileReadyToParse' ) ) - if results.json: - break - time.sleep( .25 ) + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_ClassMemberVariable( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) - with patch( - 'ycmd.completers.language_server.language_server_protocol.UriToFilePath', - side_effect = lsp.InvalidUriException ): - RunTest( app, { - 'description': 'Invalid URIs do not make us crash', - 'request': { - 'command': 'FixIt', - 'line_num': 27, - 'column_num': 12, - 'filepath': filepath, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': fixits, - } - } ) + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 12, + column_num = 18, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) + response = app.post_json( '/run_completer_command', event_data ).json -@WithRetry -@SharedYcmd -def Subcommands_Format_WholeFile_Spaces_test( app ): - RunTest( app, { - 'description': 'Formatting is applied on the whole file ' - 'with tabs composed of 4 spaces', - 'request': { - 'command': 'Format', - 'filepath': TEST_JAVA, - 'options': { - 'tab_size': 4, - 'insert_spaces': True - } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 3, 20 ), - LocationMatcher( TEST_JAVA, 4, 3 ) ), - ChunkMatcher( '\n\n ', - LocationMatcher( TEST_JAVA, 4, 22 ), - LocationMatcher( TEST_JAVA, 6, 3 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 6, 34 ), - LocationMatcher( TEST_JAVA, 7, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 7, 35 ), - LocationMatcher( TEST_JAVA, 8, 5 ) ), + assert_that( response, has_entry( 'message', 'String info' ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_MethodArgument( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 16, + column_num = 17, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', event_data ).json + + assert_that( response, has_entry( + 'message', 'String info - com.test.TestWidgetImpl.TestWidgetImpl(String)' + ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_MethodVariable( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 15, + column_num = 9, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', event_data ).json + + assert_that( response, has_entry( + 'message', 'int a - com.test.TestWidgetImpl.TestWidgetImpl(String)' ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_Method( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 20, + column_num = 15, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', event_data ).json + + assert_that( response, has_entry( + 'message', 'void com.test.TestWidgetImpl.doSomethingVaguelyUseful()' ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_Unicode( self, app ): + contents = ReadFile( TEST_JAVA ) + + app.post_json( '/event_notification', + BuildRequest( filepath = TEST_JAVA, + filetype = 'java', + contents = contents, + event_name = 'FileReadyToParse' ) ) + + event_data = BuildRequest( filepath = TEST_JAVA, + filetype = 'java', + line_num = 7, + column_num = 17, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', event_data ).json + + assert_that( response, has_entry( + 'message', 'String whåtawîdgé - com.youcompleteme.Test.doUnicødeTes()' ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_LiteralValue( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 15, + column_num = 13, + contents = contents, + command_arguments = [ 'GetType' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( response.json, + ErrorMatcher( RuntimeError, 'Unknown type' ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoTo_NoLocation( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 18, + column_num = 1, + contents = contents, + command_arguments = [ 'GoTo' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( response.json, + ErrorMatcher( RuntimeError, 'Cannot jump to location' ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoToReferences_NoReferences( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 2, + column_num = 1, + contents = contents, + command_arguments = [ 'GoToReferences' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( response.json, + ErrorMatcher( RuntimeError, + 'Cannot jump to location' ) ) + + + @WithRetry() + @IsolatedYcmd( { + 'extra_conf_globlist': PathToTestFile( 'multiple_projects', '*' ) + } ) + def test_Subcommands_GoToReferences_MultipleProjects( self, app ): + filepath = PathToTestFile( 'multiple_projects', + 'src', + 'core', + 'java', + 'com', + 'puremourning', + 'widget', + 'core', + 'Utils.java' ) + StartJavaCompleterServerWithFile( app, filepath ) + + + RunTest( app, { + 'description': 'GoToReferences works across multiple projects', + 'request': { + 'command': 'GoToReferences', + 'filepath': filepath, + 'line_num': 5, + 'column_num': 22, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_inanyorder( + LocationMatcher( filepath, 8, 35 ), + LocationMatcher( PathToTestFile( 'multiple_projects', + 'src', + 'input', + 'java', + 'com', + 'puremourning', + 'widget', + 'input', + 'InputApp.java' ), + 8, + 16 ) + ) + } + } ) + + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoToReferences( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 10, + column_num = 15, + contents = contents, + command_arguments = [ 'GoToReferences' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', event_data ).json + + assert_that( response, contains_exactly( has_entries( { + 'filepath': PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ), + 'column_num': 9, + 'description': " w.doSomethingVaguelyUseful();", + 'line_num': 28 + } ), + has_entries( { + 'filepath': PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestLauncher.java' ), + 'column_num': 11, + 'description': " w.doSomethingVaguelyUseful();", + 'line_num': 32 + } ) ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoToSymbol_SingleSameFile( self, app ): + contents = ReadFile( TEST_JAVA ) + + event_data = BuildRequest( filepath = TEST_JAVA, + filetype = 'java', + line_num = 1, + column_num = 1, + contents = contents, + command_arguments = [ 'GoToSymbol', + 'TéstClass' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', event_data ).json + + assert_that( response, has_entries( { + 'filepath': TEST_JAVA, + 'description': "Class: TéstClass", + 'line_num': 20, + 'column_num': 16, + } ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoToSymbol_Multiple( self, app ): + contents = ReadFile( TEST_JAVA ) + + event_data = BuildRequest( filepath = TEST_JAVA, + filetype = 'java', + line_num = 1, + column_num = 1, + contents = contents, + command_arguments = [ 'GoToSymbol', 'test' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', event_data ).json + + assert_that( response, contains_inanyorder( + has_entries( { + 'filepath': PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ) , + 'description': "Class: TestFactory", + 'line_num': 12, + 'column_num': 14, + } ), + has_entries( { + 'filepath': PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) , + 'description': "Class: TestWidgetImpl", + 'line_num': 11, + 'column_num': 7, + } ), + has_entries( { + 'filepath': PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestLauncher.java' ) , + 'description': "Class: TestLauncher", + 'line_num': 6, + 'column_num': 7, + } ), + has_entries( { + 'filepath': PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'youcompleteme', + 'Test.java' ) , + 'description': "Class: Test", + 'line_num': 3, + 'column_num': 14, + } ), + has_entries( { + 'filepath': PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWithDocumentation.java' ) , + 'description': "Class: TestWithDocumentation", + 'line_num': 3, + 'column_num': 14, + } ) + ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoToSymbol_None( self, app ): + contents = ReadFile( TEST_JAVA ) + + event_data = BuildRequest( filepath = TEST_JAVA, + filetype = 'java', + line_num = 1, + column_num = 1, + contents = contents, + command_arguments = [ 'GoToSymbol', 'abcd' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( response.json, + ErrorMatcher( RuntimeError, 'Symbol not found' ) ) + + + + @WithRetry() + @SharedYcmd + def test_Subcommands_RefactorRename_Simple( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestLauncher.java' ) + RunTest( app, { + 'description': 'RefactorRename works within a single scope/file', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'renamed_l' ], + 'filepath': filepath, + 'line_num': 28, + 'column_num': 5, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( 'renamed_l = new TestLauncher( 10 );' + '\n renamed_l', + LocationMatcher( filepath, 27, 18 ), + LocationMatcher( filepath, 28, 6 ) ), + ), + 'location': LocationMatcher( filepath, 28, 5 ) + } ) ) + } ) + } + } ) + + + @ExpectedFailure( 'Renaming does not work on overridden methods ' + 'since jdt.ls 0.21.0', + matches_regexp( 'No item matched:.*TestWidgetImpl.java' ) ) + @WithRetry() + @SharedYcmd + def test_Subcommands_RefactorRename_MultipleFiles( self, app ): + AbstractTestWidget = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) + TestFactory = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ) + TestLauncher = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestLauncher.java' ) + TestWidgetImpl = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + + RunTest( app, { + 'description': 'RefactorRename works across files', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'a_quite_long_string' ], + 'filepath': TestLauncher, + 'line_num': 32, + 'column_num': 13, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( + 'a_quite_long_string', + LocationMatcher( AbstractTestWidget, 10, 15 ), + LocationMatcher( AbstractTestWidget, 10, 39 ) ), + ChunkMatcher( + 'a_quite_long_string', + LocationMatcher( TestFactory, 28, 9 ), + LocationMatcher( TestFactory, 28, 33 ) ), + ChunkMatcher( + 'a_quite_long_string', + LocationMatcher( TestLauncher, 32, 11 ), + LocationMatcher( TestLauncher, 32, 35 ) ), + ChunkMatcher( + 'a_quite_long_string', + LocationMatcher( TestWidgetImpl, 20, 15 ), + LocationMatcher( TestWidgetImpl, 20, 39 ) ), + ), + 'location': LocationMatcher( TestLauncher, 32, 13 ) + } ) ) + } ) + } + } ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_RefactorRename_Missing_New_Name( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestLauncher.java' ) + RunTest( app, { + 'description': 'RefactorRename raises an error without new name', + 'request': { + 'command': 'RefactorRename', + 'line_num': 15, + 'column_num': 5, + 'filepath': filepath, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( ValueError, + 'Please specify a new name to rename it to.\n' + 'Usage: RefactorRename ' ), + } + } ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_RefactorRename_Unicode( self, app ): + RunTest( app, { + 'description': 'Rename works for unicode identifier', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'shorter' ], + 'line_num': 7, + 'column_num': 21, + 'filepath': TEST_JAVA, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( + 'shorter = "Test";\n return shorter', + LocationMatcher( TEST_JAVA, 7, 12 ), + LocationMatcher( TEST_JAVA, 8, 25 ) + ), + ), + } ) ), + } ), + }, + } ) + + + + @WithRetry() + @SharedYcmd + def test_Subcommands_FixIt_SingleDiag_MultipleOption_Insertion( self, app ): + import os + for description, column in [ + ( 'FixIt works at the firtst char of the line', 1 ), + ( 'FixIt works at the begin of the range of the diag.', 15 ), + ( 'FixIt works at the end of the range of the diag.', 20 ), + ( 'FixIt works at the end of the line', 34 ), + ]: + with self.subTest( description = description, column = column ): + wibble_path = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'Wibble.java' ) + wibble_text = 'package com.test;{0}{0}public {1} Wibble {{{0}{0}}}{0}' + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ) + + # Note: The code actions for creating variables are really not very + # useful. The import is, however, and the FixIt almost exactly matches + # the one supplied when completing 'CUTHBERT' and auto-inserting. + fixits_for_line = has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': "Import 'Wibble' (com.test.wobble)", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( 'package com.test;\n\n' + 'import com.test.wobble.Wibble;\n\n', + LocationMatcher( filepath, 1, 1 ), + LocationMatcher( filepath, 3, 1 ) ), + ), + } ), + has_entries( { + 'text': "Create constant 'Wibble'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( '\n\nprivate static final String Wibble = null;', + LocationMatcher( filepath, 16, 4 ), + LocationMatcher( filepath, 16, 4 ) ), + ), + } ), + has_entries( { + 'text': "Create class 'Wibble'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( wibble_text.format( os.linesep, 'class' ), + LocationMatcher( wibble_path, 1, 1 ), + LocationMatcher( wibble_path, 1, 1 ) ), + ), + } ), + has_entries( { + 'text': "Create interface 'Wibble'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( wibble_text.format( os.linesep, 'interface' ), + LocationMatcher( wibble_path, 1, 1 ), + LocationMatcher( wibble_path, 1, 1 ) ), + ), + } ), + has_entries( { + 'text': "Create enum 'Wibble'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( wibble_text.format( os.linesep, 'enum' ), + LocationMatcher( wibble_path, 1, 1 ), + LocationMatcher( wibble_path, 1, 1 ) ), + ), + } ), + has_entries( { + 'text': "Create local variable 'Wibble'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( 'Object Wibble;\n\t', + LocationMatcher( filepath, 19, 5 ), + LocationMatcher( filepath, 19, 5 ) ), + ), + } ), + has_entries( { + 'text': "Create field 'Wibble'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( '\n\nprivate Object Wibble;', + LocationMatcher( filepath, 16, 4 ), + LocationMatcher( filepath, 16, 4 ) ), + ), + } ), + has_entries( { + 'text': "Create parameter 'Wibble'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( ', Object Wibble', + LocationMatcher( filepath, 18, 32 ), + LocationMatcher( filepath, 18, 32 ) ), + ), + } ), + has_entries( { + 'text': 'Generate toString()...', + 'kind': 'source.generate.toString', + 'chunks': contains_exactly( + ChunkMatcher( '\n\n@Override\npublic String toString() {' + '\n\treturn "TestFactory []";\n}', + LocationMatcher( filepath, 32, 4 ), + LocationMatcher( filepath, 32, 4 ) ), + ), + } ), + has_entries( { + 'text': 'Organize imports', + 'kind': 'source.organizeImports', + 'chunks': contains_exactly( + ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n', + LocationMatcher( filepath, 1, 18 ), + LocationMatcher( filepath, 3, 1 ) ), + ), + } ), + has_entries( { + 'text': 'Change modifiers to final where possible', + 'kind': 'source.generate.finalModifiers', + 'chunks': contains_exactly( + ChunkMatcher( + 'final Wibble w ) {\n if ( w == Wibble.CUTHBERT ) {' + '\n }\n }\n\n public AbstractTestWidget getWidget' + '( final String info ) {\n final AbstractTestWidget' + ' w = new TestWidgetImpl( info );\n final ', + LocationMatcher( filepath, 18, 24 ), + LocationMatcher( filepath, 25, 5 ) ), + ), + } ), + ) + } ) + + RunFixItTest( app, description, filepath, 19, column, fixits_for_line ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_FixIt_SingleDiag_SingleOption_Modify( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ) + + # TODO: As there is only one option, we automatically apply it. + # In Java case this might not be the right thing. It's a code assist, not a + # FixIt really. Perhaps we should change the client to always ask for + # confirmation? + fixits = has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': "Change type of 'test' to 'boolean'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( 'boolean', + LocationMatcher( filepath, 14, 12 ), + LocationMatcher( filepath, 14, 15 ) ), + ), + } ), + has_entries( { + 'text': 'Generate toString()...', + 'kind': 'source.generate.toString', + 'chunks': contains_exactly( + ChunkMatcher( '\n\n@Override\npublic String toString() {' + '\n\treturn "TestFactory []";\n}', + LocationMatcher( filepath, 32, 4 ), + LocationMatcher( filepath, 32, 4 ) ), + ), + } ), + has_entries( { + 'text': 'Organize imports', + 'kind': 'source.organizeImports', + 'chunks': contains_exactly( + ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n', + LocationMatcher( filepath, 1, 18 ), + LocationMatcher( filepath, 3, 1 ) ), + ), + } ), + has_entries( { + 'text': 'Change modifiers to final where possible', + 'kind': 'source.generate.finalModifiers', + 'chunks': contains_exactly( + ChunkMatcher( + 'final Wibble w ) {\n if ( w == Wibble.CUTHBERT ) {' + '\n }\n }\n\n public AbstractTestWidget getWidget' + '( final String info ) {\n final AbstractTestWidget' + ' w = new TestWidgetImpl( info );\n final ', + LocationMatcher( filepath, 18, 24 ), + LocationMatcher( filepath, 25, 5 ) ), + ), + } ), + ) + } ) + + RunFixItTest( app, 'FixIts can change lines as well as add them', + filepath, 27, 12, fixits ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_FixIt_SingleDiag_MultiOption_Delete( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ) + + fixits = has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': "Remove 'testString', keep assignments with side effects", + 'kind': 'quickfix', + 'chunks': contains_exactly( ChunkMatcher( '', - LocationMatcher( TEST_JAVA, 8, 25 ), - LocationMatcher( TEST_JAVA, 8, 26 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 8, 27 ), - LocationMatcher( TEST_JAVA, 9, 3 ) ), - ChunkMatcher( '\n\n ', - LocationMatcher( TEST_JAVA, 9, 4 ), - LocationMatcher( TEST_JAVA, 11, 3 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 11, 29 ), - LocationMatcher( TEST_JAVA, 12, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 12, 26 ), - LocationMatcher( TEST_JAVA, 13, 5 ) ), + LocationMatcher( filepath, 14, 21 ), + LocationMatcher( filepath, 15, 30 ) ), + ), + } ), + # The edit reported for this is huge and uninteresting really. Manual + # testing can show that it works. This test is really about the previous + # FixIt (and nonetheless, the previous tests ensure that we correctly + # populate the chunks list; the contents all come from jdt.ls) + has_entries( { + 'text': "Create getter and setter for 'testString'", + 'chunks': instance_of( list ) + } ), + has_entries( { + 'text': "Organize imports", + 'chunks': instance_of( list ) + } ), + has_entries( { + 'text': "Generate Getters and Setters", + 'chunks': instance_of( list ) + } ), + has_entries( { + 'text': 'Change modifiers to final where possible', + 'chunks': instance_of( list ) + } ), + ) + } ) + + RunFixItTest( app, 'FixIts can change lines as well as add them', + filepath, 15, 29, fixits ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_FixIt_MultipleDiags( self, app ): + for description, column, expect_fixits in [ + ( 'diags are merged in FixIt options - start of line', 1, 'MERGE' ), + ( 'diags are not merged in FixIt options - ' + 'start of diag 1', 10, 'FIRST' ), + ( 'diags are not merged in FixIt options - ' + 'end of diag 1', 15, 'FIRST' ), + ( 'diags are not merged in FixIt options - ' + 'start of diag 2', 23, 'SECOND' ), + ( 'diags are not merged in FixIt options - ' + 'end of diag 2', 46, 'SECOND' ), + ( 'diags are merged in FixIt options - ' + 'end of line', 55, 'MERGE' ), + ]: + with self.subTest( description = description, + column = column, + expect_fixits = expect_fixits ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ) + + FIRST = [ + has_entries( { + 'text': "Change type of 'test' to 'boolean'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( 'boolean', + LocationMatcher( filepath, 14, 12 ), + LocationMatcher( filepath, 14, 15 ) ), + ), + } ), + ] + SECOND = [ + has_entries( { + 'text': "Remove argument to match 'doSomethingVaguelyUseful()'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( '', + LocationMatcher( filepath, 30, 48 ), + LocationMatcher( filepath, 30, 50 ) ), + ), + } ), + has_entries( { + 'text': "Change method 'doSomethingVaguelyUseful()': Add parameter " + "'Bar'", + 'chunks': instance_of( list ), + } ), + has_entries( { + 'text': "Create method 'doSomethingVaguelyUseful(Bar)' in type " + "'AbstractTestWidget'", + 'chunks': instance_of( list ), + } ), + ] + + ACTIONS = [ + has_entries( { + 'text': "Generate toString()...", + 'chunks': instance_of( list ), + } ), + has_entries( { + 'text': "Organize imports", + 'chunks': instance_of( list ), + } ), + has_entries( { + 'text': 'Change modifiers to final where possible', + 'chunks': instance_of( list ), + } ), + ] + + FIXITS = { + 'FIRST': FIRST + ACTIONS, + 'SECOND': SECOND + ACTIONS, + 'MERGE': FIRST + SECOND + ACTIONS, + } + + fixits = has_entries( { + 'fixits': contains_inanyorder( *FIXITS[ expect_fixits ] ) + } ) + + RunFixItTest( app, description, filepath, 30, column, fixits ) + + + @SharedYcmd + def test_Subcommands_FixIt_Range( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestLauncher.java' ) + RunTest( app, { + 'description': 'Formatting is applied on some part of the file ' + 'with tabs composed of 4 spaces', + 'request': { + 'command': 'FixIt', + 'filepath': filepath, + 'range': { + 'start': { + 'line_num': 34, + 'column_num': 28, + }, + 'end': { + 'line_num': 34, + 'column_num': 73 + } + }, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': 'Extract to field', + 'kind': 'refactor.extract.field', + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( + 'private String \\w+;\n' + '\n' + '\t@Override\n' + ' public void launch\\(\\) {\n' + ' AbstractTestWidget w = ' + 'factory.getWidget\\( "Test" \\);\n' + ' ' + 'w.doSomethingVaguelyUseful\\(\\);\n' + '\n' + ' \\w+ = "Did something ' + 'useful: " \\+ w.getWidgetInfo\\(\\);\n' + '\t\tSystem.out.println\\( \\w+' ), + LocationMatcher( filepath, 29, 7 ), + LocationMatcher( filepath, 34, 73 ) ), + ), + } ), + has_entries( { + 'text': 'Extract to method', + 'kind': 'refactor.extract.function', + 'chunks': contains_exactly( + # This one is a wall of text that rewrites 35 lines + ChunkMatcher( instance_of( str ), + LocationMatcher( filepath, 1, 1 ), + LocationMatcher( filepath, 35, 8 ) ), + ), + } ), + has_entries( { + 'text': 'Extract to local variable (replace all occurrences)', + 'kind': 'refactor.extract.variable', + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( + 'String \\w+ = "Did something ' + 'useful: " \\+ w.getWidgetInfo\\(\\);\n' + '\t\tSystem.out.println\\( \\w+' ), + LocationMatcher( filepath, 34, 9 ), + LocationMatcher( filepath, 34, 73 ) ), + ), + } ), + has_entries( { + 'text': 'Extract to local variable', + 'kind': 'refactor.extract.variable', + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( + 'String \\w+ = "Did something ' + 'useful: " \\+ w.getWidgetInfo\\(\\);\n' + '\t\tSystem.out.println\\( \\w+' ), + LocationMatcher( filepath, 34, 9 ), + LocationMatcher( filepath, 34, 73 ) ), + ), + } ), + has_entries( { + 'text': 'Introduce Parameter...', + 'kind': 'refactor.introduce.parameter', + 'chunks': contains_exactly( + ChunkMatcher( + 'String string) {\n' + ' AbstractTestWidget w = ' + 'factory.getWidget( "Test" );\n' + ' w.doSomethingVaguelyUseful();\n' + '\n' + ' System.out.println( string', + LocationMatcher( filepath, 30, 26 ), + LocationMatcher( filepath, 34, 73 ) ), + ), + } ), + has_entries( { + 'text': 'Organize imports', + 'chunks': instance_of( list ), + } ), + has_entries( { + 'text': 'Change modifiers to final where possible', + 'chunks': instance_of( list ), + } ), + ) + } ) + } + } ) + + + + @WithRetry() + @SharedYcmd + def test_Subcommands_FixIt_NoDiagnostics( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ) + + RunFixItTest( app, "no FixIts means you gotta code it yo' self", + filepath, 1, 1, has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': 'Change modifiers to final where possible', + 'chunks': instance_of( list ) } ), + has_entries( { 'text': 'Organize imports', + 'chunks': instance_of( list ) } ), + has_entries( { 'text': 'Generate toString()...', + 'chunks': instance_of( list ) } ) ) } ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_FixIt_Unicode( self, app ): + fixits = has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': "Remove argument to match 'doUnicødeTes()'", + 'kind': 'quickfix', + 'chunks': contains_exactly( ChunkMatcher( '', LocationMatcher( TEST_JAVA, 13, 24 ), - LocationMatcher( TEST_JAVA, 13, 25 ) ), - ChunkMatcher( '', - LocationMatcher( TEST_JAVA, 13, 29 ), - LocationMatcher( TEST_JAVA, 13, 30 ) ), - ChunkMatcher( '\n\n ', - LocationMatcher( TEST_JAVA, 13, 32 ), - LocationMatcher( TEST_JAVA, 15, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 15, 58 ), - LocationMatcher( TEST_JAVA, 16, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 16, 42 ), - LocationMatcher( TEST_JAVA, 17, 3 ) ), - ChunkMatcher( '\n\n ', - LocationMatcher( TEST_JAVA, 17, 4 ), - LocationMatcher( TEST_JAVA, 20, 3 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 20, 28 ), - LocationMatcher( TEST_JAVA, 21, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 21, 28 ), - LocationMatcher( TEST_JAVA, 22, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 22, 30 ), - LocationMatcher( TEST_JAVA, 23, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 23, 23 ), - LocationMatcher( TEST_JAVA, 24, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 24, 27 ), - LocationMatcher( TEST_JAVA, 25, 3 ) ), - ) - } ) ) + LocationMatcher( TEST_JAVA, 13, 29 ) ), + ), + } ), + has_entries( { + 'text': "Change method 'doUnicødeTes()': Add parameter 'String'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( 'String test2', + LocationMatcher( TEST_JAVA, 6, 31 ), + LocationMatcher( TEST_JAVA, 6, 31 ) ), + ), + } ), + has_entries( { + 'text': "Create method 'doUnicødeTes(String)'", + 'kind': 'quickfix', + 'chunks': contains_exactly( + ChunkMatcher( 'private void doUnicødeTes(String test2) {\n}\n\n\n', + LocationMatcher( TEST_JAVA, 20, 3 ), + LocationMatcher( TEST_JAVA, 20, 3 ) ), + ), + } ), + has_entries( { + 'text': 'Change modifiers to final where possible', + 'chunks': instance_of( list ), + } ), + has_entries( { + 'text': "Generate Getters and Setters", + 'chunks': instance_of( list ), + } ), + ) + } ) + + RunFixItTest( app, 'FixIts and diagnostics work with unicode strings', + TEST_JAVA, 13, 1, fixits ) + + + @WithRetry() + @IsolatedYcmd() + def test_Subcommands_FixIt_InvalidURI( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestFactory.java' ) + + fixits = has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'kind': 'quickfix', + 'text': "Change type of 'test' to 'boolean'", + 'chunks': contains_exactly( + ChunkMatcher( 'boolean', + LocationMatcher( '', 14, 12 ), + LocationMatcher( '', 14, 15 ) ), + ), + } ), + has_entries( { + 'text': 'Organize imports', + 'kind': 'source.organizeImports', + 'chunks': contains_exactly( + ChunkMatcher( '\n\nimport com.test.wobble.Wibble;\n\n', + LocationMatcher( '', 1, 1 ), + LocationMatcher( '', 3, 1 ) ), + ), + } ), + has_entries( { + 'text': 'Change modifiers to final where possible', + 'kind': 'source.generate.finalModifiers', + 'chunks': contains_exactly( + ChunkMatcher( "final Wibble w ) {\n " + "if ( w == Wibble.CUTHBERT ) {" + "\n }\n }\n\n public " + "AbstractTestWidget getWidget" + "( final String info ) {\n final " + "AbstractTestWidget w = new TestWidgetImpl( info );" + "\n final ", + LocationMatcher( '', 18, 24 ), + LocationMatcher( '', 25, 5 ) ), + ), + } ), + has_entries( { + 'text': 'Generate toString()...', + 'kind': 'source.generate.toString', + 'chunks': contains_exactly( + ChunkMatcher( '\n\n@Override\npublic String toString() {' + '\n\treturn "TestFactory []";\n}', + LocationMatcher( '', 32, 4 ), + LocationMatcher( '', 32, 4 ) ), + ), + } ), + ) + } ) + + contents = ReadFile( filepath ) + # Wait for jdt.ls to have parsed the file and returned some diagnostics + for tries in range( 0, 60 ): + results = app.post_json( '/event_notification', + BuildRequest( filepath = filepath, + filetype = 'java', + contents = contents, + event_name = 'FileReadyToParse' ) ) + if results.json: + break + + time.sleep( .25 ) + + with patch( + 'ycmd.completers.language_server.language_server_protocol.UriToFilePath', + side_effect = lsp.InvalidUriException ): + RunTest( app, { + 'description': 'Invalid URIs do not make us crash', + 'request': { + 'command': 'FixIt', + 'line_num': 27, + 'column_num': 12, + 'filepath': filepath, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': fixits, + } } ) - } - } ) -@WithRetry -@SharedYcmd -def Subcommands_Format_WholeFile_Tabs_test( app ): - RunTest( app, { - 'description': 'Formatting is applied on the whole file ' - 'with tabs composed of 2 spaces', - 'request': { - 'command': 'Format', - 'filepath': TEST_JAVA, - 'options': { - 'tab_size': 4, - 'insert_spaces': False + @WithRetry() + @SharedYcmd + def test_Subcommands_Format_WholeFile_Spaces( self, app ): + RunTest( app, { + 'description': 'Formatting is applied on the whole file ' + 'with tabs composed of 4 spaces', + 'request': { + 'command': 'Format', + 'filepath': TEST_JAVA, + 'options': { + 'tab_size': 4, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 3, 20 ), + LocationMatcher( TEST_JAVA, 4, 3 ) ), + ChunkMatcher( '\n\n ', + LocationMatcher( TEST_JAVA, 4, 22 ), + LocationMatcher( TEST_JAVA, 6, 3 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 6, 34 ), + LocationMatcher( TEST_JAVA, 7, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 7, 35 ), + LocationMatcher( TEST_JAVA, 8, 5 ) ), + ChunkMatcher( '', + LocationMatcher( TEST_JAVA, 8, 25 ), + LocationMatcher( TEST_JAVA, 8, 26 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 8, 27 ), + LocationMatcher( TEST_JAVA, 9, 3 ) ), + ChunkMatcher( '\n\n ', + LocationMatcher( TEST_JAVA, 9, 4 ), + LocationMatcher( TEST_JAVA, 11, 3 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 11, 29 ), + LocationMatcher( TEST_JAVA, 12, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 12, 26 ), + LocationMatcher( TEST_JAVA, 13, 5 ) ), + ChunkMatcher( '', + LocationMatcher( TEST_JAVA, 13, 24 ), + LocationMatcher( TEST_JAVA, 13, 25 ) ), + ChunkMatcher( '', + LocationMatcher( TEST_JAVA, 13, 29 ), + LocationMatcher( TEST_JAVA, 13, 30 ) ), + ChunkMatcher( '\n\n ', + LocationMatcher( TEST_JAVA, 13, 32 ), + LocationMatcher( TEST_JAVA, 15, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 15, 58 ), + LocationMatcher( TEST_JAVA, 16, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 16, 42 ), + LocationMatcher( TEST_JAVA, 17, 3 ) ), + ChunkMatcher( '\n\n ', + LocationMatcher( TEST_JAVA, 17, 4 ), + LocationMatcher( TEST_JAVA, 20, 3 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 20, 28 ), + LocationMatcher( TEST_JAVA, 21, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 21, 28 ), + LocationMatcher( TEST_JAVA, 22, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 22, 30 ), + LocationMatcher( TEST_JAVA, 23, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 23, 23 ), + LocationMatcher( TEST_JAVA, 24, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 24, 27 ), + LocationMatcher( TEST_JAVA, 25, 3 ) ), + ) + } ) ) + } ) + } + } ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_Format_WholeFile_Tabs( self, app ): + RunTest( app, { + 'description': 'Formatting is applied on the whole file ' + 'with tabs composed of 2 spaces', + 'request': { + 'command': 'Format', + 'filepath': TEST_JAVA, + 'options': { + 'tab_size': 4, + 'insert_spaces': False + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\n\t', + LocationMatcher( TEST_JAVA, 3, 20 ), + LocationMatcher( TEST_JAVA, 4, 3 ) ), + ChunkMatcher( '\n\n\t', + LocationMatcher( TEST_JAVA, 4, 22 ), + LocationMatcher( TEST_JAVA, 6, 3 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 6, 34 ), + LocationMatcher( TEST_JAVA, 7, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 7, 35 ), + LocationMatcher( TEST_JAVA, 8, 5 ) ), + ChunkMatcher( '', + LocationMatcher( TEST_JAVA, 8, 25 ), + LocationMatcher( TEST_JAVA, 8, 26 ) ), + ChunkMatcher( '\n\t', + LocationMatcher( TEST_JAVA, 8, 27 ), + LocationMatcher( TEST_JAVA, 9, 3 ) ), + ChunkMatcher( '\n\n\t', + LocationMatcher( TEST_JAVA, 9, 4 ), + LocationMatcher( TEST_JAVA, 11, 3 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 11, 29 ), + LocationMatcher( TEST_JAVA, 12, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 12, 26 ), + LocationMatcher( TEST_JAVA, 13, 5 ) ), + ChunkMatcher( '', + LocationMatcher( TEST_JAVA, 13, 24 ), + LocationMatcher( TEST_JAVA, 13, 25 ) ), + ChunkMatcher( '', + LocationMatcher( TEST_JAVA, 13, 29 ), + LocationMatcher( TEST_JAVA, 13, 30 ) ), + ChunkMatcher( '\n\n\t\t', + LocationMatcher( TEST_JAVA, 13, 32 ), + LocationMatcher( TEST_JAVA, 15, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 15, 58 ), + LocationMatcher( TEST_JAVA, 16, 5 ) ), + ChunkMatcher( '\n\t', + LocationMatcher( TEST_JAVA, 16, 42 ), + LocationMatcher( TEST_JAVA, 17, 3 ) ), + ChunkMatcher( '\n\n\t', + LocationMatcher( TEST_JAVA, 17, 4 ), + LocationMatcher( TEST_JAVA, 20, 3 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 20, 28 ), + LocationMatcher( TEST_JAVA, 21, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 21, 28 ), + LocationMatcher( TEST_JAVA, 22, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 22, 30 ), + LocationMatcher( TEST_JAVA, 23, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 23, 23 ), + LocationMatcher( TEST_JAVA, 24, 5 ) ), + ChunkMatcher( '\n\t', + LocationMatcher( TEST_JAVA, 24, 27 ), + LocationMatcher( TEST_JAVA, 25, 3 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\n\t', - LocationMatcher( TEST_JAVA, 3, 20 ), - LocationMatcher( TEST_JAVA, 4, 3 ) ), - ChunkMatcher( '\n\n\t', - LocationMatcher( TEST_JAVA, 4, 22 ), - LocationMatcher( TEST_JAVA, 6, 3 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 6, 34 ), - LocationMatcher( TEST_JAVA, 7, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 7, 35 ), - LocationMatcher( TEST_JAVA, 8, 5 ) ), - ChunkMatcher( '', - LocationMatcher( TEST_JAVA, 8, 25 ), - LocationMatcher( TEST_JAVA, 8, 26 ) ), - ChunkMatcher( '\n\t', - LocationMatcher( TEST_JAVA, 8, 27 ), - LocationMatcher( TEST_JAVA, 9, 3 ) ), - ChunkMatcher( '\n\n\t', - LocationMatcher( TEST_JAVA, 9, 4 ), - LocationMatcher( TEST_JAVA, 11, 3 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 11, 29 ), - LocationMatcher( TEST_JAVA, 12, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 12, 26 ), - LocationMatcher( TEST_JAVA, 13, 5 ) ), - ChunkMatcher( '', - LocationMatcher( TEST_JAVA, 13, 24 ), - LocationMatcher( TEST_JAVA, 13, 25 ) ), - ChunkMatcher( '', - LocationMatcher( TEST_JAVA, 13, 29 ), - LocationMatcher( TEST_JAVA, 13, 30 ) ), - ChunkMatcher( '\n\n\t\t', - LocationMatcher( TEST_JAVA, 13, 32 ), - LocationMatcher( TEST_JAVA, 15, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 15, 58 ), - LocationMatcher( TEST_JAVA, 16, 5 ) ), - ChunkMatcher( '\n\t', - LocationMatcher( TEST_JAVA, 16, 42 ), - LocationMatcher( TEST_JAVA, 17, 3 ) ), - ChunkMatcher( '\n\n\t', - LocationMatcher( TEST_JAVA, 17, 4 ), - LocationMatcher( TEST_JAVA, 20, 3 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 20, 28 ), - LocationMatcher( TEST_JAVA, 21, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 21, 28 ), - LocationMatcher( TEST_JAVA, 22, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 22, 30 ), - LocationMatcher( TEST_JAVA, 23, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 23, 23 ), - LocationMatcher( TEST_JAVA, 24, 5 ) ), - ChunkMatcher( '\n\t', - LocationMatcher( TEST_JAVA, 24, 27 ), - LocationMatcher( TEST_JAVA, 25, 3 ) ), - ) - } ) ) - } ) - } - } ) + } ) -@WithRetry -@SharedYcmd -def Subcommands_Format_Range_Spaces_test( app ): - RunTest( app, { - 'description': 'Formatting is applied on some part of the file ' - 'with tabs composed of 4 spaces', - 'request': { - 'command': 'Format', - 'filepath': TEST_JAVA, - 'range': { - 'start': { - 'line_num': 20, - 'column_num': 1, + @WithRetry() + @SharedYcmd + def test_Subcommands_Format_Range_Spaces( self, app ): + RunTest( app, { + 'description': 'Formatting is applied on some part of the file ' + 'with tabs composed of 4 spaces', + 'request': { + 'command': 'Format', + 'filepath': TEST_JAVA, + 'range': { + 'start': { + 'line_num': 20, + 'column_num': 1, + }, + 'end': { + 'line_num': 25, + 'column_num': 4 + } }, - 'end': { - 'line_num': 25, - 'column_num': 4 + 'options': { + 'tab_size': 4, + 'insert_spaces': True } }, - 'options': { - 'tab_size': 4, - 'insert_spaces': True + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( ' ', + LocationMatcher( TEST_JAVA, 20, 1 ), + LocationMatcher( TEST_JAVA, 20, 3 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 20, 28 ), + LocationMatcher( TEST_JAVA, 21, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 21, 28 ), + LocationMatcher( TEST_JAVA, 22, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 22, 30 ), + LocationMatcher( TEST_JAVA, 23, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( TEST_JAVA, 23, 23 ), + LocationMatcher( TEST_JAVA, 24, 5 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( ' ', - LocationMatcher( TEST_JAVA, 20, 1 ), - LocationMatcher( TEST_JAVA, 20, 3 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 20, 28 ), - LocationMatcher( TEST_JAVA, 21, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 21, 28 ), - LocationMatcher( TEST_JAVA, 22, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 22, 30 ), - LocationMatcher( TEST_JAVA, 23, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( TEST_JAVA, 23, 23 ), - LocationMatcher( TEST_JAVA, 24, 5 ) ), - ) - } ) ) - } ) - } - } ) + } ) -@WithRetry -@SharedYcmd -def Subcommands_Format_Range_Tabs_test( app ): - RunTest( app, { - 'description': 'Formatting is applied on some part of the file ' - 'with tabs instead of spaces', - 'request': { - 'command': 'Format', - 'filepath': TEST_JAVA, - 'range': { - 'start': { - 'line_num': 20, - 'column_num': 1, + @WithRetry() + @SharedYcmd + def test_Subcommands_Format_Range_Tabs( self, app ): + RunTest( app, { + 'description': 'Formatting is applied on some part of the file ' + 'with tabs instead of spaces', + 'request': { + 'command': 'Format', + 'filepath': TEST_JAVA, + 'range': { + 'start': { + 'line_num': 20, + 'column_num': 1, + }, + 'end': { + 'line_num': 25, + 'column_num': 4 + } }, - 'end': { - 'line_num': 25, - 'column_num': 4 + 'options': { + 'tab_size': 4, + 'insert_spaces': False } }, - 'options': { - 'tab_size': 4, - 'insert_spaces': False + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\t', + LocationMatcher( TEST_JAVA, 20, 1 ), + LocationMatcher( TEST_JAVA, 20, 3 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 20, 28 ), + LocationMatcher( TEST_JAVA, 21, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 21, 28 ), + LocationMatcher( TEST_JAVA, 22, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 22, 30 ), + LocationMatcher( TEST_JAVA, 23, 5 ) ), + ChunkMatcher( '\n\t\t', + LocationMatcher( TEST_JAVA, 23, 23 ), + LocationMatcher( TEST_JAVA, 24, 5 ) ), + ChunkMatcher( '\n\t', + LocationMatcher( TEST_JAVA, 24, 27 ), + LocationMatcher( TEST_JAVA, 25, 3 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\t', - LocationMatcher( TEST_JAVA, 20, 1 ), - LocationMatcher( TEST_JAVA, 20, 3 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 20, 28 ), - LocationMatcher( TEST_JAVA, 21, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 21, 28 ), - LocationMatcher( TEST_JAVA, 22, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 22, 30 ), - LocationMatcher( TEST_JAVA, 23, 5 ) ), - ChunkMatcher( '\n\t\t', - LocationMatcher( TEST_JAVA, 23, 23 ), - LocationMatcher( TEST_JAVA, 24, 5 ) ), - ChunkMatcher( '\n\t', - LocationMatcher( TEST_JAVA, 24, 27 ), - LocationMatcher( TEST_JAVA, 25, 3 ) ), - ) - } ) ) + } ) + + + @SharedYcmd + def test_Subcommands_GoTo( self, app ): + for test, command in itertools.product( + [ + # Member function local variable + { 'request': { 'line': 28, 'col': 5, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 27, 'column_num': 18, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for member local variable' }, + # Member variable + { 'request': { 'line': 22, 'col': 7, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 8, 'column_num': 16, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for member variable' }, + # Method + { 'request': { 'line': 28, 'col': 7, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 21, 'column_num': 16, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for method' }, + # Constructor + { 'request': { 'line': 38, 'col': 26, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 10, 'column_num': 10, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for jumping to constructor' }, + # Jump to self - main() + { 'request': { 'line': 26, 'col': 22, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 26, 'column_num': 22, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for jumping to the same position' }, + # Static method + { 'request': { 'line': 37, 'col': 11, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 13, 'column_num': 21, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for static method' }, + # Static variable + { 'request': { 'line': 14, 'col': 11, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 12, 'column_num': 21, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for static variable' }, + # Argument variable + { 'request': { 'line': 23, 'col': 5, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 21, 'column_num': 32, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for argument variable' }, + # Class + { 'request': { 'line': 27, 'col': 10, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 6, 'column_num': 7, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoTo works for jumping to class declaration' }, + # Unicode + { 'request': { 'line': 8, 'col': 12, 'filepath': TEST_JAVA }, + 'response': { 'line_num': 7, 'column_num': 12, + 'filepath': TEST_JAVA }, + 'description': 'GoTo works for unicode identifiers' } + ], + [ 'GoTo', 'GoToDefinition', 'GoToDeclaration' ] ): + with self.subTest( command = command, test = test ): + RunGoToTest( app, + test[ 'description' ], + test[ 'request' ][ 'filepath' ], + test[ 'request' ][ 'line' ], + test[ 'request' ][ 'col' ], + command, + has_entries( test[ 'response' ] ) ) + + + @SharedYcmd + def test_Subcommands_GoToType( self, app ): + for test in [ + # Member function local variable + { 'request': { 'line': 28, 'col': 5, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 6, 'column_num': 7, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoToType works for member local variable' }, + # Member variable + { 'request': { 'line': 22, 'col': 7, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 6, 'column_num': 14, 'filepath': TSET_JAVA }, + 'description': 'GoToType works for member variable' }, + ]: + with self.subTest( test = test ): + RunGoToTest( app, + test[ 'description' ], + test[ 'request' ][ 'filepath' ], + test[ 'request' ][ 'line' ], + test[ 'request' ][ 'col' ], + 'GoToType', + has_entries( test[ 'response' ] ) ) + + + @SharedYcmd + def test_Subcommands_GoToImplementation( self, app ): + for test in [ + # Interface + { 'request': { 'line': 17, 'col': 25, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 28, 'column_num': 16, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoToImplementation on interface ' + 'jumps to its implementation' }, + # Interface reference + { 'request': { 'line': 21, 'col': 30, 'filepath': TESTLAUNCHER_JAVA }, + 'response': { 'line_num': 28, 'column_num': 16, + 'filepath': TESTLAUNCHER_JAVA }, + 'description': 'GoToImplementation on interface reference ' + 'jumpts to its implementation' }, + ]: + with self.subTest( test = test ): + RunGoToTest( app, + test[ 'description' ], + test[ 'request' ][ 'filepath' ], + test[ 'request' ][ 'line' ], + test[ 'request' ][ 'col' ], + 'GoToImplementation', + has_entries( test[ 'response' ] ) ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_OrganizeImports( self, app ): + RunTest( app, { + 'description': 'Imports are resolved and sorted, ' + 'and unused ones are removed', + 'request': { + 'command': 'OrganizeImports', + 'filepath': TESTLAUNCHER_JAVA + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( 'import com.youcompleteme.Test;\n' + 'import com.youcompleteme.testing.Tset;', + LocationMatcher( TESTLAUNCHER_JAVA, 3, 1 ), + LocationMatcher( TESTLAUNCHER_JAVA, 4, 54 ) ), + ) + } ) ) + } ) + } + } ) + + + @WithRetry() + @SharedYcmd + @patch( 'ycmd.completers.language_server.language_server_completer.' + 'REQUEST_TIMEOUT_COMMAND', + 5 ) + def test_Subcommands_RequestTimeout( self, app ): + with patch.object( + handlers._server_state.GetFiletypeCompleter( [ 'java' ] ).GetConnection(), + 'WriteData' ): + RunTest( app, { + 'description': 'Request timeout throws an error', + 'request': { + 'command': 'FixIt', + 'line_num': 1, + 'column_num': 1, + 'filepath': TEST_JAVA, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( ResponseTimeoutException, 'Response Timeout' ) + } } ) - } - } ) -@WithRetry -@SharedYcmd -def RunGoToTest( app, description, filepath, line, col, cmd, goto_response ): - RunTest( app, { - 'description': description, - 'request': { - 'command': cmd, - 'line_num': line, - 'column_num': col, - 'filepath': filepath - }, - 'expect': { - 'response': requests.codes.ok, - 'data': goto_response, - } - } ) + @WithRetry() + @SharedYcmd + def test_Subcommands_RequestFailed( self, app ): + connection = handlers._server_state.GetFiletypeCompleter( + [ 'java' ] ).GetConnection() + def WriteJunkToServer( data ): + junk = data.replace( bytes( b'textDocument/codeAction' ), + bytes( b'textDocument/codeFAILED' ) ) -@pytest.mark.parametrize( 'test', [ - # Member function local variable - { 'request': { 'line': 28, 'col': 5, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 27, 'column_num': 18, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for member local variable' }, - # Member variable - { 'request': { 'line': 22, 'col': 7, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 8, 'column_num': 16, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for member variable' }, - # Method - { 'request': { 'line': 28, 'col': 7, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 21, 'column_num': 16, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for method' }, - # Constructor - { 'request': { 'line': 38, 'col': 26, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 10, 'column_num': 10, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for jumping to constructor' }, - # Jump to self - main() - { 'request': { 'line': 26, 'col': 22, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 26, 'column_num': 22, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for jumping to the same position' }, - # Static method - { 'request': { 'line': 37, 'col': 11, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 13, 'column_num': 21, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for static method' }, - # Static variable - { 'request': { 'line': 14, 'col': 11, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 12, 'column_num': 21, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for static variable' }, - # Argument variable - { 'request': { 'line': 23, 'col': 5, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 21, 'column_num': 32, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for argument variable' }, - # Class - { 'request': { 'line': 27, 'col': 10, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 6, 'column_num': 7, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoTo works for jumping to class declaration' }, - # Unicode - { 'request': { 'line': 8, 'col': 12, 'filepath': TEST_JAVA }, - 'response': { 'line_num': 7, 'column_num': 12, 'filepath': TEST_JAVA }, - 'description': 'GoTo works for unicode identifiers' } - ] ) -@pytest.mark.parametrize( 'command', [ 'GoTo', - 'GoToDefinition', - 'GoToDeclaration' ] ) -@SharedYcmd -def Subcommands_GoTo_test( app, command, test ): - RunGoToTest( app, - test[ 'description' ], - test[ 'request' ][ 'filepath' ], - test[ 'request' ][ 'line' ], - test[ 'request' ][ 'col' ], - command, - has_entries( test[ 'response' ] ) ) - - -@pytest.mark.parametrize( 'test', [ - # Member function local variable - { 'request': { 'line': 28, 'col': 5, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 6, 'column_num': 7, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoToType works for member local variable' }, - # Member variable - { 'request': { 'line': 22, 'col': 7, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 6, 'column_num': 14, 'filepath': TSET_JAVA }, - 'description': 'GoToType works for member variable' }, - ] ) -@SharedYcmd -def Subcommands_GoToType_test( app, test ): - RunGoToTest( app, - test[ 'description' ], - test[ 'request' ][ 'filepath' ], - test[ 'request' ][ 'line' ], - test[ 'request' ][ 'col' ], - 'GoToType', - has_entries( test[ 'response' ] ) ) - - -@pytest.mark.parametrize( 'test', [ - # Interface - { 'request': { 'line': 17, 'col': 25, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 28, 'column_num': 16, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoToImplementation on interface ' - 'jumps to its implementation' }, - # Interface reference - { 'request': { 'line': 21, 'col': 30, 'filepath': TESTLAUNCHER_JAVA }, - 'response': { 'line_num': 28, 'column_num': 16, - 'filepath': TESTLAUNCHER_JAVA }, - 'description': 'GoToImplementation on interface reference ' - 'jumpts to its implementation' }, - ] ) -@SharedYcmd -def Subcommands_GoToImplementation_test( app, test ): - RunGoToTest( app, - test[ 'description' ], - test[ 'request' ][ 'filepath' ], - test[ 'request' ][ 'line' ], - test[ 'request' ][ 'col' ], - 'GoToImplementation', - has_entries( test[ 'response' ] ) ) - - -@WithRetry -@SharedYcmd -def Subcommands_OrganizeImports_test( app ): - RunTest( app, { - 'description': 'Imports are resolved and sorted, ' - 'and unused ones are removed', - 'request': { - 'command': 'OrganizeImports', - 'filepath': TESTLAUNCHER_JAVA - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( 'import com.youcompleteme.Test;\n' - 'import com.youcompleteme.testing.Tset;', - LocationMatcher( TESTLAUNCHER_JAVA, 3, 1 ), - LocationMatcher( TESTLAUNCHER_JAVA, 4, 54 ) ), - ) - } ) ) + with connection._stdin_lock: + connection._server_stdin.write( junk ) + connection._server_stdin.flush() + + + with patch.object( connection, + 'WriteData', + side_effect = WriteJunkToServer ): + RunTest( app, { + 'description': 'Response errors propagate to the client', + 'request': { + 'command': 'FixIt', + 'line_num': 1, + 'column_num': 1, + 'filepath': TEST_JAVA, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( ResponseFailedException ) + } } ) - } - } ) -@WithRetry -@SharedYcmd -@patch( 'ycmd.completers.language_server.language_server_completer.' - 'REQUEST_TIMEOUT_COMMAND', - 5 ) -def Subcommands_RequestTimeout_test( app ): - with patch.object( - handlers._server_state.GetFiletypeCompleter( [ 'java' ] ).GetConnection(), - 'WriteData' ): + @WithRetry() + @SharedYcmd + def test_Subcommands_IndexOutOfRange( self, app ): RunTest( app, { - 'description': 'Request timeout throws an error', + 'description': 'Request with invalid position does not crash', 'request': { 'command': 'FixIt', - 'line_num': 1, - 'column_num': 1, + 'line_num': 99, + 'column_num': 99, 'filepath': TEST_JAVA, }, 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( ResponseTimeoutException, 'Response Timeout' ) + 'response': requests.codes.ok, + 'data': has_entries( { 'fixits': contains_exactly( + has_entries( { 'text': 'Generate Getters and Setters', + 'chunks': instance_of( list ) } ), + has_entries( { 'text': 'Change modifiers to final where possible', + 'chunks': instance_of( list ) } ), + ) } ), } } ) -@WithRetry -@SharedYcmd -def Subcommands_RequestFailed_test( app ): - connection = handlers._server_state.GetFiletypeCompleter( - [ 'java' ] ).GetConnection() - - def WriteJunkToServer( data ): - junk = data.replace( bytes( b'textDocument/codeAction' ), - bytes( b'textDocument/codeFAILED' ) ) - - with connection._stdin_lock: - connection._server_stdin.write( junk ) - connection._server_stdin.flush() - - - with patch.object( connection, 'WriteData', side_effect = WriteJunkToServer ): + @WithRetry() + @SharedYcmd + def test_Subcommands_InvalidRange( self, app ): RunTest( app, { - 'description': 'Response errors propagate to the client', + 'description': 'Request with invalid visual range is rejected', 'request': { 'command': 'FixIt', - 'line_num': 1, - 'column_num': 1, + 'line_num': 99, + 'column_num': 99, 'filepath': TEST_JAVA, + 'range': { + 'start': { + 'line_num': 99, + 'column_num': 99 + }, + 'end': { + 'line_num': 100, + 'column_num': 100 + } + } }, 'expect': { 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( ResponseFailedException ) + 'data': ErrorMatcher( RuntimeError, 'Invalid range' ), } } ) -@WithRetry -@SharedYcmd -def Subcommands_IndexOutOfRange_test( app ): - RunTest( app, { - 'description': 'Request with invalid position does not crash', - 'request': { - 'command': 'FixIt', - 'line_num': 99, - 'column_num': 99, - 'filepath': TEST_JAVA, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { 'fixits': contains_exactly( - has_entries( { 'text': 'Generate Getters and Setters', - 'chunks': instance_of( list ) } ), - has_entries( { 'text': 'Change modifiers to final where possible', - 'chunks': instance_of( list ) } ), - ) } ), - } - } ) - - -@WithRetry -@SharedYcmd -def Subcommands_InvalidRange_test( app ): - RunTest( app, { - 'description': 'Request with invalid visual range is rejected', - 'request': { - 'command': 'FixIt', - 'line_num': 99, - 'column_num': 99, - 'filepath': TEST_JAVA, - 'range': { - 'start': { - 'line_num': 99, - 'column_num': 99 - }, - 'end': { - 'line_num': 100, - 'column_num': 100 + @WithRetry() + @SharedYcmd + def test_Subcommands_DifferentFileTypesUpdate( self, app ): + RunTest( app, { + 'description': 'Request error handles the error', + 'request': { + 'command': 'FixIt', + 'line_num': 99, + 'column_num': 99, + 'filepath': TEST_JAVA, + 'file_data': { + '!/bin/sh': { + 'filetypes': [], + 'contents': 'this should be ignored by the completer', + }, + '/path/to/non/project/file': { + 'filetypes': [ 'c' ], + 'contents': 'this should be ignored by the completer', + }, + TESTLAUNCHER_JAVA: { + 'filetypes': [ 'some', 'java', 'junk', 'also' ], + 'contents': ReadFile( TESTLAUNCHER_JAVA ), + }, + '!/usr/bin/sh': { + 'filetypes': [ 'java' ], + 'contents': '\n', + }, } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { 'fixits': contains_exactly( + has_entries( { 'text': 'Generate Getters and Setters', + 'chunks': instance_of( list ) } ), + has_entries( { 'text': 'Change modifiers to final where possible', + 'chunks': instance_of( list ) } ), + ) } ), } - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'Invalid range' ), - } - } ) + } ) -@WithRetry -@SharedYcmd -def Subcommands_DifferentFileTypesUpdate_test( app ): - RunTest( app, { - 'description': 'Request error handles the error', - 'request': { - 'command': 'FixIt', - 'line_num': 99, - 'column_num': 99, - 'filepath': TEST_JAVA, - 'file_data': { - '!/bin/sh': { - 'filetypes': [], - 'contents': 'this should be ignored by the completer', - }, - '/path/to/non/project/file': { - 'filetypes': [ 'c' ], - 'contents': 'this should be ignored by the completer', - }, - TESTLAUNCHER_JAVA: { - 'filetypes': [ 'some', 'java', 'junk', 'also' ], - 'contents': ReadFile( TESTLAUNCHER_JAVA ), - }, - '!/usr/bin/sh': { - 'filetypes': [ 'java' ], - 'contents': '\n', - }, + @WithRetry() + @IsolatedYcmd( { 'extra_conf_globlist': + PathToTestFile( 'extra_confs', '*' ) } ) + def test_Subcommands_ExtraConf_SettingsValid( self, app ): + filepath = PathToTestFile( 'extra_confs', + 'simple_extra_conf_project', + 'src', + 'ExtraConf.java' ) + RunTest( app, { + 'description': 'RefactorRename is disabled in extra conf.', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'renamed_l' ], + 'filepath': filepath, + 'line_num': 1, + 'column_num': 7, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': empty(), + 'location': LocationMatcher( filepath, 1, 7 ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { 'fixits': contains_exactly( - has_entries( { 'text': 'Generate Getters and Setters', - 'chunks': instance_of( list ) } ), - has_entries( { 'text': 'Change modifiers to final where possible', - 'chunks': instance_of( list ) } ), - ) } ), - } - } ) - - -@WithRetry -@IsolatedYcmd( { 'extra_conf_globlist': - PathToTestFile( 'extra_confs', '*' ) } ) -def Subcommands_ExtraConf_SettingsValid_test( app ): - filepath = PathToTestFile( 'extra_confs', - 'simple_extra_conf_project', - 'src', - 'ExtraConf.java' ) - RunTest( app, { - 'description': 'RefactorRename is disabled in extra conf.', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'renamed_l' ], - 'filepath': filepath, - 'line_num': 1, - 'column_num': 7, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': empty(), - 'location': LocationMatcher( filepath, 1, 7 ) - } ) ) - } ) - } - } ) + } ) -@WithRetry -@IsolatedYcmd( { 'extra_conf_globlist': - PathToTestFile( 'extra_confs', '*' ) } ) -def Subcommands_AdditionalFormatterOptions_test( app ): - filepath = PathToTestFile( 'extra_confs', - 'simple_extra_conf_project', - 'src', - 'ExtraConf.java' ) - RunTest( app, { - 'description': 'Format respects settings from extra conf.', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': True + @WithRetry() + @IsolatedYcmd( { 'extra_conf_globlist': + PathToTestFile( 'extra_confs', '*' ) } ) + def test_Subcommands_AdditionalFormatterOptions( self, app ): + filepath = PathToTestFile( 'extra_confs', + 'simple_extra_conf_project', + 'src', + 'ExtraConf.java' ) + RunTest( app, { + 'description': 'Format respects settings from extra conf.', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\n ', + LocationMatcher( filepath, 1, 18 ), + LocationMatcher( filepath, 2, 3 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 2, 20 ), + LocationMatcher( filepath, 2, 21 ) ), + ChunkMatcher( '', + LocationMatcher( filepath, 2, 29 ), + LocationMatcher( filepath, 2, 30 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 2, 33 ), + LocationMatcher( filepath, 2, 33 ) ), + ChunkMatcher( '\n\n ', + LocationMatcher( filepath, 2, 34 ), + LocationMatcher( filepath, 4, 3 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 4, 27 ), + LocationMatcher( filepath, 4, 28 ) ), + ChunkMatcher( '', + LocationMatcher( filepath, 4, 41 ), + LocationMatcher( filepath, 4, 42 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 4, 45 ), + LocationMatcher( filepath, 5, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 5, 33 ), + LocationMatcher( filepath, 5, 34 ) ), + ChunkMatcher( '', + LocationMatcher( filepath, 5, 36 ), + LocationMatcher( filepath, 5, 37 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 5, 39 ), + LocationMatcher( filepath, 6, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 6, 33 ), + LocationMatcher( filepath, 6, 34 ) ), + ChunkMatcher( '', + LocationMatcher( filepath, 6, 35 ), + LocationMatcher( filepath, 6, 36 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 6, 38 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 7, 11 ), + LocationMatcher( filepath, 8, 5 ) ), + ChunkMatcher( '\n ', + LocationMatcher( filepath, 8, 11 ), + LocationMatcher( filepath, 9, 3 ) ), + ), + 'location': LocationMatcher( filepath, 1, 1 ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\n ', - LocationMatcher( filepath, 1, 18 ), - LocationMatcher( filepath, 2, 3 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 2, 20 ), - LocationMatcher( filepath, 2, 21 ) ), - ChunkMatcher( '', - LocationMatcher( filepath, 2, 29 ), - LocationMatcher( filepath, 2, 30 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 2, 33 ), - LocationMatcher( filepath, 2, 33 ) ), - ChunkMatcher( '\n\n ', - LocationMatcher( filepath, 2, 34 ), - LocationMatcher( filepath, 4, 3 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 4, 27 ), - LocationMatcher( filepath, 4, 28 ) ), - ChunkMatcher( '', - LocationMatcher( filepath, 4, 41 ), - LocationMatcher( filepath, 4, 42 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 4, 45 ), - LocationMatcher( filepath, 5, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 5, 33 ), - LocationMatcher( filepath, 5, 34 ) ), - ChunkMatcher( '', - LocationMatcher( filepath, 5, 36 ), - LocationMatcher( filepath, 5, 37 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 5, 39 ), - LocationMatcher( filepath, 6, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 6, 33 ), - LocationMatcher( filepath, 6, 34 ) ), - ChunkMatcher( '', - LocationMatcher( filepath, 6, 35 ), - LocationMatcher( filepath, 6, 36 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 6, 38 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 7, 11 ), - LocationMatcher( filepath, 8, 5 ) ), - ChunkMatcher( '\n ', - LocationMatcher( filepath, 8, 11 ), - LocationMatcher( filepath, 9, 3 ) ), - ), - 'location': LocationMatcher( filepath, 1, 1 ) - } ) ) - } ) - } - } ) - - -@WithRetry -@IsolatedYcmd() -def Subcommands_ExtraConf_SettingsValid_UnknownExtraConf_test( app ): - filepath = PathToTestFile( 'extra_confs', - 'simple_extra_conf_project', - 'src', - 'ExtraConf.java' ) - contents = ReadFile( filepath ) - - response = app.post_json( '/event_notification', - BuildRequest( **{ - 'event_name': 'FileReadyToParse', - 'contents': contents, - 'filepath': filepath, - 'line_num': 1, - 'column_num': 7, - 'filetype': 'java', - } ), - expect_errors = True ) - - print( 'FileReadyToParse result: ' - f'{ json.dumps( response.json, indent = 2 ) }' ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, ErrorMatcher( UnknownExtraConf ) ) - - app.post_json( - '/ignore_extra_conf_file', - { 'filepath': PathToTestFile( 'extra_confs', '.ycm_extra_conf.py' ) } ) + } ) - RunTest( app, { - 'description': 'RefactorRename is disabled in extra conf but ignored.', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'renamed_l' ], - 'filepath': filepath, - 'contents': contents, - 'line_num': 1, - 'column_num': 7, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - # Just prove that we actually got a reasonable result - 'chunks': is_not( empty() ), - } ) ) - } ) - } - } ) + @WithRetry() + @IsolatedYcmd() + def test_Subcommands_ExtraConf_SettingsValid_UnknownExtraConf( self, app ): + filepath = PathToTestFile( 'extra_confs', + 'simple_extra_conf_project', + 'src', + 'ExtraConf.java' ) + contents = ReadFile( filepath ) + + response = app.post_json( '/event_notification', + BuildRequest( **{ + 'event_name': 'FileReadyToParse', + 'contents': contents, + 'filepath': filepath, + 'line_num': 1, + 'column_num': 7, + 'filetype': 'java', + } ), + expect_errors = True ) + + print( 'FileReadyToParse result: ' + f'{ json.dumps( response.json, indent = 2 ) }' ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, ErrorMatcher( UnknownExtraConf ) ) + + app.post_json( + '/ignore_extra_conf_file', + { 'filepath': PathToTestFile( 'extra_confs', '.ycm_extra_conf.py' ) } ) -@SharedYcmd -def Subcommands_ExecuteCommand_NoArguments_test( app ): - RunTest( app, { - 'description': 'Running a command without args fails', - 'request': { - 'command': 'ExecuteCommand', - 'line_num': 1, - 'column_num': 1, - 'filepath': TEST_JAVA, - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( ValueError, - 'Must specify a command to execute' ), - } - } ) + RunTest( app, { + 'description': 'RefactorRename is disabled in extra conf but ignored.', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'renamed_l' ], + 'filepath': filepath, + 'contents': contents, + 'line_num': 1, + 'column_num': 7, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + # Just prove that we actually got a reasonable result + 'chunks': is_not( empty() ), + } ) ) + } ) + } + } ) -@SharedYcmd -def Subcommands_ExecuteCommand_test( app ): - RunTest( app, { - 'description': 'Running a command does what it says it does', - 'request': { - 'command': 'ExecuteCommand', - 'arguments': [ 'java.edit.organizeImports' ], - 'line_num': 1, - 'column_num': 1, - 'filepath': TEST_JAVA, - }, - 'expect': { - # We don't specify the path for import organize, and jdt.ls returns shrug - 'response': requests.codes.ok, - 'data': '' - } - } ) + @SharedYcmd + def test_Subcommands_ExecuteCommand_NoArguments( self, app ): + RunTest( app, { + 'description': 'Running a command without args fails', + 'request': { + 'command': 'ExecuteCommand', + 'line_num': 1, + 'column_num': 1, + 'filepath': TEST_JAVA, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( ValueError, + 'Must specify a command to execute' ), + } + } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_Subcommands_ExecuteCommand( self, app ): + RunTest( app, { + 'description': 'Running a command does what it says it does', + 'request': { + 'command': 'ExecuteCommand', + 'arguments': [ 'java.edit.organizeImports' ], + 'line_num': 1, + 'column_num': 1, + 'filepath': TEST_JAVA, + }, + 'expect': { + # We don't specify the path for import organize, so jdt.ls returns shrug + 'response': requests.codes.ok, + 'data': '' + } + } ) diff --git a/ycmd/tests/javascript/__init__.py b/ycmd/tests/javascript/__init__.py index 3745eda978..41ff842afb 100644 --- a/ycmd/tests/javascript/__init__.py +++ b/ycmd/tests/javascript/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,10 +15,63 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . +import functools import os -from ycmd.tests.javascript.conftest import * # noqa +from unittest.mock import patch +from ycmd.tests.test_utils import ( ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp, + StopCompleterServer, + WaitUntilCompleterServerReady ) # noqa +shared_app = None def PathToTestFile( *args ): dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) return os.path.join( dir_of_current_script, 'testdata', *args ) + + +def setUpModule(): + """Initializes the ycmd server as a WebTest application that will be shared + by all tests using the SharedYcmd decorator in this package. Additional + configuration that is common to these tests, like starting a semantic + subserver, should be done here.""" + global shared_app + + with patch( 'ycmd.completers.javascript.hook.' + 'ShouldEnableTernCompleter', return_value = False ): + shared_app = SetUpApp() + WaitUntilCompleterServerReady( shared_app, 'javascript' ) + + +def tearDownModule(): + global shared_app + + StopCompleterServer( shared_app, 'javascript' ) + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with patch( 'ycmd.completers.javascript.hook.' + 'ShouldEnableTernCompleter', return_value = False ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'javascript' ) + return Wrapper + return Decorator diff --git a/ycmd/tests/javascript/conftest.py b/ycmd/tests/javascript/conftest.py deleted file mode 100644 index e8165eb734..0000000000 --- a/ycmd/tests/javascript/conftest.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import pytest - -from unittest.mock import patch -from ycmd.tests.test_utils import ( ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - SetUpApp, - StopCompleterServer, - WaitUntilCompleterServerReady ) -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - with patch( 'ycmd.completers.javascript.hook.' - 'ShouldEnableTernCompleter', return_value = False ): - shared_app = SetUpApp() - WaitUntilCompleterServerReady( shared_app, 'javascript' ) - yield - StopCompleterServer( shared_app, 'javascript' ) - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - print( which ) - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with patch( 'ycmd.completers.javascript.hook.' - 'ShouldEnableTernCompleter', return_value = False ): - with IsolatedApp( request.param[ 1 ] ) as app: - yield app - StopCompleterServer( app, 'javascript' ) - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) diff --git a/ycmd/tests/javascript/debug_info_test.py b/ycmd/tests/javascript/debug_info_test.py index a072f174e8..99952b29f7 100644 --- a/ycmd/tests/javascript/debug_info_test.py +++ b/ycmd/tests/javascript/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -16,6 +16,7 @@ # along with ycmd. If not, see . from unittest.mock import patch +from unittest import TestCase from hamcrest import ( any_of, assert_that, contains_exactly, @@ -24,47 +25,43 @@ instance_of, none ) -from ycmd.tests.javascript import IsolatedYcmd, SharedYcmd +from ycmd.tests.javascript import IsolatedYcmd, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import BuildRequest -@SharedYcmd -def DebugInfo_TypeScriptCompleter_test( app ): - request_data = BuildRequest( filetype = 'javascript' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'TypeScript', - 'servers': contains_exactly( has_entries( { - 'name': 'TSServer', - 'is_running': True, - 'executable': instance_of( str ), - 'pid': instance_of( int ), - 'address': None, - 'port': None, - 'logfiles': contains_exactly( instance_of( str ) ), - 'extras': contains_exactly( has_entries( { - 'key': 'version', - 'value': any_of( None, instance_of( str ) ) +class DebugInfoTest( TestCase ): + @SharedYcmd + def test_DebugInfo_TypeScriptCompleter( self, app ): + request_data = BuildRequest( filetype = 'javascript' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'TypeScript', + 'servers': contains_exactly( has_entries( { + 'name': 'TSServer', + 'is_running': True, + 'executable': instance_of( str ), + 'pid': instance_of( int ), + 'address': None, + 'port': None, + 'logfiles': contains_exactly( instance_of( str ) ), + 'extras': contains_exactly( has_entries( { + 'key': 'version', + 'value': any_of( None, instance_of( str ) ) + } ) ) } ) ) } ) ) - } ) ) - ) + ) -@patch( 'ycmd.completers.javascript.hook.' - 'ShouldEnableTypeScriptCompleter', return_value = False ) -@patch( 'ycmd.completers.javascript.hook.' - 'ShouldEnableTernCompleter', return_value = False ) -@IsolatedYcmd -def DebugInfo_NoCompleter_test( app, *args ): - request_data = BuildRequest( filetype = 'javascript' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', none() ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @patch( 'ycmd.completers.javascript.hook.' + 'ShouldEnableTypeScriptCompleter', return_value = False ) + @patch( 'ycmd.completers.javascript.hook.' + 'ShouldEnableTernCompleter', return_value = False ) + @IsolatedYcmd() + def test_DebugInfo_NoCompleter( self, app, *args ): + request_data = BuildRequest( filetype = 'javascript' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', none() ) + ) diff --git a/ycmd/tests/javascript/diagnostics_test.py b/ycmd/tests/javascript/diagnostics_test.py index c00c6b1528..020f0e936e 100644 --- a/ycmd/tests/javascript/diagnostics_test.py +++ b/ycmd/tests/javascript/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,98 +15,89 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -from __future__ import absolute_import -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from builtins import * # noqa - from hamcrest import ( assert_that, contains_exactly, contains_inanyorder, has_entries, has_entry ) +from unittest import TestCase -from ycmd.tests.javascript import PathToTestFile, SharedYcmd +from ycmd.tests.javascript import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import BuildRequest, LocationMatcher, RangeMatcher from ycmd.utils import ReadFile -@SharedYcmd -def Diagnostics_FileReadyToParse_test( app ): - filepath = PathToTestFile( 'test.js' ) - contents = ReadFile( filepath ) +class DiagnosticsTest( TestCase ): + @SharedYcmd + def test_Diagnostics_FileReadyToParse( self, app ): + filepath = PathToTestFile( 'test.js' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'javascript', - contents = contents, - event_name = 'BufferVisit' ) - app.post_json( '/event_notification', event_data ) + event_data = BuildRequest( filepath = filepath, + filetype = 'javascript', + contents = contents, + event_name = 'BufferVisit' ) + app.post_json( '/event_notification', event_data ) - event_data = BuildRequest( filepath = filepath, - filetype = 'javascript', - contents = contents, - event_name = 'FileReadyToParse' ) + event_data = BuildRequest( filepath = filepath, + filetype = 'javascript', + contents = contents, + event_name = 'FileReadyToParse' ) - assert_that( - app.post_json( '/event_notification', event_data ).json, - contains_inanyorder( - has_entries( { - 'kind': 'ERROR', - 'text': "Property 'm' does not exist on type 'Foo'.", - 'location': LocationMatcher( filepath, 14, 5 ), - 'location_extent': RangeMatcher( filepath, ( 14, 5 ), ( 14, 6 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 14, 5 ), ( 14, 6 ) ) ), - 'fixit_available': False - } ), - has_entries( { - 'kind': 'ERROR', - 'text': "Property 'nonExistingMethod' does not exist on type 'Bar'.", - 'location': LocationMatcher( filepath, 32, 5 ), - 'location_extent': RangeMatcher( filepath, ( 32, 5 ), ( 32, 22 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 32, 5 ), ( 32, 22 ) ) ), - 'fixit_available': True - } ), - has_entries( { - 'kind': 'ERROR', - 'text': "Cannot find name 'Bår'.", - 'location': LocationMatcher( filepath, 36, 1 ), - 'location_extent': RangeMatcher( filepath, ( 36, 1 ), ( 36, 5 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 36, 1 ), ( 36, 5 ) ) ), - 'fixit_available': True - } ), + assert_that( + app.post_json( '/event_notification', event_data ).json, + contains_inanyorder( + has_entries( { + 'kind': 'ERROR', + 'text': "Property 'm' does not exist on type 'Foo'.", + 'location': LocationMatcher( filepath, 14, 5 ), + 'location_extent': RangeMatcher( filepath, ( 14, 5 ), ( 14, 6 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 14, 5 ), ( 14, 6 ) ) ), + 'fixit_available': False + } ), + has_entries( { + 'kind': 'ERROR', + 'text': "Property 'nonExistingMethod' does not exist on type 'Bar'.", + 'location': LocationMatcher( filepath, 32, 5 ), + 'location_extent': RangeMatcher( filepath, ( 32, 5 ), ( 32, 22 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 32, 5 ), ( 32, 22 ) ) ), + 'fixit_available': True + } ), + has_entries( { + 'kind': 'ERROR', + 'text': "Cannot find name 'Bår'.", + 'location': LocationMatcher( filepath, 36, 1 ), + 'location_extent': RangeMatcher( filepath, ( 36, 1 ), ( 36, 5 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 36, 1 ), ( 36, 5 ) ) ), + 'fixit_available': True + } ), + ) ) - ) -@SharedYcmd -def Diagnostics_DetailedDiagnostics_test( app ): - filepath = PathToTestFile( 'test.js' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Diagnostics_DetailedDiagnostics( self, app ): + filepath = PathToTestFile( 'test.js' ) + contents = ReadFile( filepath ) - event_data = BuildRequest( filepath = filepath, - filetype = 'javascript', - contents = contents, - event_name = 'BufferVisit' ) - app.post_json( '/event_notification', event_data ) + event_data = BuildRequest( filepath = filepath, + filetype = 'javascript', + contents = contents, + event_name = 'BufferVisit' ) + app.post_json( '/event_notification', event_data ) - diagnostic_data = BuildRequest( filepath = filepath, - filetype = 'javascript', - contents = contents, - line_num = 32, - column_num = 13 ) + diagnostic_data = BuildRequest( filepath = filepath, + filetype = 'javascript', + contents = contents, + line_num = 32, + column_num = 13 ) - assert_that( - app.post_json( '/detailed_diagnostic', diagnostic_data ).json, - has_entry( - 'message', "Property 'nonExistingMethod' does not exist on type 'Bar'." + assert_that( + app.post_json( '/detailed_diagnostic', diagnostic_data ).json, + has_entry( + 'message', "Property 'nonExistingMethod' does not exist on type 'Bar'." + ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True diff --git a/ycmd/tests/javascript/get_completions_test.py b/ycmd/tests/javascript/get_completions_test.py index 5ca152d0c7..1dc2533b37 100644 --- a/ycmd/tests/javascript/get_completions_test.py +++ b/ycmd/tests/javascript/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -22,10 +22,11 @@ has_entries, has_item, matches_regexp ) -import pprint +from unittest import TestCase +import json import requests -from ycmd.tests.javascript import IsolatedYcmd, PathToTestFile, SharedYcmd +from ycmd.tests.javascript import IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, CompletionEntryMatcher, LocationMatcher ) from ycmd.utils import ReadFile @@ -57,7 +58,7 @@ def CombineRequest( request, data ): } ) ) - print( f'completer response: { pprint.pformat( response.json ) }' ) + print( 'completer response: ', json.dumps( response.json, indent = 2 ) ) assert_that( response.status_code, equal_to( test[ 'expect' ][ 'response' ] ) ) @@ -65,140 +66,137 @@ def CombineRequest( request, data ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def GetCompletions_Basic_test( app ): - RunTest( app, { - 'description': 'Extra and detailed info when completions are methods', - 'request': { - 'line_num': 14, - 'column_num': 6, - 'filepath': PathToTestFile( 'test.js' ) - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( - 'methodA', - '(method) Foo.methodA(): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodA(): void\n\n' - 'Unicode string: 说话' - } - ), - CompletionEntryMatcher( - 'methodB', - '(method) Foo.methodB(): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodB(): void' - } - ), - CompletionEntryMatcher( - 'methodC', - '(method) Foo.methodC(foo: any, bar: any): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodC(foo: any, bar: any): void' - } +class GetCompletionsTest( TestCase ): + @SharedYcmd + def test_GetCompletions_Basic( self, app ): + RunTest( app, { + 'description': 'Extra and detailed info when completions are methods', + 'request': { + 'line_num': 14, + 'column_num': 6, + 'filepath': PathToTestFile( 'test.js' ) + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( + 'methodA', + '(method) Foo.methodA(): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodA(): void\n\n' + 'Unicode string: 说话' + } + ), + CompletionEntryMatcher( + 'methodB', + '(method) Foo.methodB(): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodB(): void' + } + ), + CompletionEntryMatcher( + 'methodC', + '(method) Foo.methodC(foo: any, bar: any): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodC(foo: any, ' + 'bar: any): void' + } + ) ) - ) - } ) - } - } ) - - -@SharedYcmd -def GetCompletions_Keyword_test( app ): - RunTest( app, { - 'description': 'No extra and detailed info when completion is a keyword', - 'request': { - 'line_num': 1, - 'column_num': 5, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( { - 'insertion_text': 'class', - 'kind': 'keyword', - 'extra_data': {} } ) - } ) - } - } ) - - -@SharedYcmd -def GetCompletions_AutoImport_test( app ): - filepath = PathToTestFile( 'test.js' ) - RunTest( app, { - 'description': 'Symbol from external module can be completed and ' - 'its completion contains fixits to automatically import it', - 'request': { - 'line_num': 36, - 'column_num': 5, - 'filepath': filepath, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( has_entries( { - 'insertion_text': 'Bår', - 'extra_menu_info': 'class Bår', - 'detailed_info': 'class Bår', - 'kind': 'class', - 'extra_data': has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': 'Import \'Bår\' from module "./unicode"', - 'chunks': contains_exactly( - ChunkMatcher( - matches_regexp( '^import { Bår } from "./unicode";\r?\n' - '\r?\n' ), - LocationMatcher( filepath, 1, 1 ), - LocationMatcher( filepath, 1, 1 ) - ) - ), - 'location': LocationMatcher( filepath, 36, 5 ) - } ) - ) + } + } ) + + + @SharedYcmd + def test_GetCompletions_Keyword( self, app ): + RunTest( app, { + 'description': 'No extra and detailed info when completion is a keyword', + 'request': { + 'line_num': 1, + 'column_num': 5, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( { + 'insertion_text': 'class', + 'kind': 'keyword', + 'extra_data': {} } ) - } ) ) - } ) - } - } ) - - -@IsolatedYcmd() -def GetCompletions_IgnoreIdentifiers_test( app ): - RunTest( app, { - 'description': 'Identifier "test" is not returned as a suggestion', - 'request': { - 'line_num': 5, - 'column_num': 6, - 'filepath': PathToTestFile( 'identifier', 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( - 'foo', - '(property) foo: string', - extra_params = { - 'kind': 'property', - 'detailed_info': '(property) foo: string' - } - ) - ) - } ) - } - } ) + } ) + } + } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_GetCompletions_AutoImport( self, app ): + filepath = PathToTestFile( 'test.js' ) + RunTest( app, { + 'description': 'Symbol from external module can be completed and its ' + 'completion contains fixits to automatically import it', + 'request': { + 'line_num': 36, + 'column_num': 5, + 'filepath': filepath, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( has_entries( { + 'insertion_text': 'Bår', + 'extra_menu_info': 'class Bår', + 'detailed_info': 'class Bår', + 'kind': 'class', + 'extra_data': has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': 'Import \'Bår\' from module "./unicode"', + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( '^import { Bår } from "./unicode";\r?\n' + '\r?\n' ), + LocationMatcher( filepath, 1, 1 ), + LocationMatcher( filepath, 1, 1 ) + ) + ), + 'location': LocationMatcher( filepath, 36, 5 ) + } ) + ) + } ) + } ) ) + } ) + } + } ) + + + @IsolatedYcmd() + def test_GetCompletions_IgnoreIdentifiers( self, app ): + RunTest( app, { + 'description': 'Identifier "test" is not returned as a suggestion', + 'request': { + 'line_num': 5, + 'column_num': 6, + 'filepath': PathToTestFile( 'identifier', 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( + 'foo', + '(property) foo: string', + extra_params = { + 'kind': 'property', + 'detailed_info': '(property) foo: string' + } + ) + ) + } ) + } + } ) diff --git a/ycmd/tests/javascript/subcommands_test.py b/ycmd/tests/javascript/subcommands_test.py index a35e570357..933aa26e3e 100644 --- a/ycmd/tests/javascript/subcommands_test.py +++ b/ycmd/tests/javascript/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -21,11 +21,11 @@ equal_to, has_entries, matches_regexp ) +from unittest import TestCase import requests import pprint -import pytest -from ycmd.tests.javascript import IsolatedYcmd, PathToTestFile, SharedYcmd +from ycmd.tests.javascript import IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, CombineRequest, @@ -78,724 +78,721 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@IsolatedYcmd() -def Subcommands_DefinedSubcommands_test( app ): - subcommands_data = BuildRequest( completer_target = 'javascript' ) - - assert_that( - app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_inanyorder( - 'Format', - 'GoTo', - 'GoToDeclaration', - 'GoToDefinition', - 'GoToImplementation', - 'GoToType', - 'GetDoc', - 'GetType', - 'GoToReferences', - 'GoToSymbol', - 'FixIt', - 'OrganizeImports', - 'RefactorRename', - 'RestartServer' - ) - ) - - -@SharedYcmd -def Subcommands_Format_WholeFile_Spaces_test( app ): - filepath = PathToTestFile( 'test.js' ) +def Subcommands_GoTo( app, goto_command ): RunTest( app, { - 'description': 'Formatting is applied on the whole file ' - 'with tabs composed of 4 spaces', + 'description': goto_command + ' works within file', 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': True - } + 'command': goto_command, + 'line_num': 31, + 'column_num': 13, + 'filepath': PathToTestFile( 'test.js' ), }, 'expect': { 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( ' ', - LocationMatcher( filepath, 2, 1 ), - LocationMatcher( filepath, 2, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 3, 1 ), - LocationMatcher( filepath, 3, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 3, 14 ), - LocationMatcher( filepath, 3, 14 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 4, 1 ), - LocationMatcher( filepath, 4, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 4, 14 ), - LocationMatcher( filepath, 4, 14 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 5, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 6, 1 ), - LocationMatcher( filepath, 6, 5 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 7, 1 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 8, 6 ), - LocationMatcher( filepath, 8, 6 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 18, 1 ), - LocationMatcher( filepath, 18, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 19, 1 ), - LocationMatcher( filepath, 19, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 20, 1 ), - LocationMatcher( filepath, 20, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 21, 1 ), - LocationMatcher( filepath, 21, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 24, 1 ), - LocationMatcher( filepath, 24, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 25, 1 ), - LocationMatcher( filepath, 25, 4 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 26, 1 ), - LocationMatcher( filepath, 26, 4 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 27, 1 ), - LocationMatcher( filepath, 27, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 27, 17 ), - LocationMatcher( filepath, 27, 17 ) ), - ) - } ) ) - } ) + 'data': LocationMatcher( PathToTestFile( 'test.js' ), 27, 3 ) } } ) -@SharedYcmd -def Subcommands_Format_WholeFile_Tabs_test( app ): - filepath = PathToTestFile( 'test.js' ) - RunTest( app, { - 'description': 'Formatting is applied on the whole file ' - 'with tabs composed of 2 spaces', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': False - } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\t', - LocationMatcher( filepath, 2, 1 ), - LocationMatcher( filepath, 2, 3 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 3, 1 ), - LocationMatcher( filepath, 3, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 3, 14 ), - LocationMatcher( filepath, 3, 14 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 4, 1 ), - LocationMatcher( filepath, 4, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 4, 14 ), - LocationMatcher( filepath, 4, 14 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 5, 3 ) ), - ChunkMatcher( '\t\t', - LocationMatcher( filepath, 6, 1 ), - LocationMatcher( filepath, 6, 5 ) ), - ChunkMatcher( '\t\t', - LocationMatcher( filepath, 7, 1 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 8, 6 ), - LocationMatcher( filepath, 8, 6 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 18, 1 ), - LocationMatcher( filepath, 18, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 19, 1 ), - LocationMatcher( filepath, 19, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 20, 1 ), - LocationMatcher( filepath, 20, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 21, 1 ), - LocationMatcher( filepath, 21, 2 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 24, 1 ), - LocationMatcher( filepath, 24, 3 ) ), - ChunkMatcher( '\t ', - LocationMatcher( filepath, 25, 1 ), - LocationMatcher( filepath, 25, 4 ) ), - ChunkMatcher( '\t ', - LocationMatcher( filepath, 26, 1 ), - LocationMatcher( filepath, 26, 4 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 27, 1 ), - LocationMatcher( filepath, 27, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 27, 17 ), - LocationMatcher( filepath, 27, 17 ) ), - ) - } ) ) - } ) - } - } ) +class SubcommandsTest( TestCase ): + @IsolatedYcmd() + def test_Subcommands_DefinedSubcommands( self, app ): + subcommands_data = BuildRequest( completer_target = 'javascript' ) + + assert_that( + app.post_json( '/defined_subcommands', subcommands_data ).json, + contains_inanyorder( + 'Format', + 'GoTo', + 'GoToDeclaration', + 'GoToDefinition', + 'GoToImplementation', + 'GoToType', + 'GetDoc', + 'GetType', + 'GoToReferences', + 'GoToSymbol', + 'FixIt', + 'OrganizeImports', + 'RefactorRename', + 'RestartServer' + ) + ) -@SharedYcmd -def Subcommands_Format_Range_Spaces_test( app ): - filepath = PathToTestFile( 'test.js' ) - RunTest( app, { - 'description': 'Formatting is applied on some part of the file ' - 'with tabs composed of 4 spaces by default', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'range': { - 'start': { - 'line_num': 5, - 'column_num': 3, - }, - 'end': { - 'line_num': 8, - 'column_num': 6 + @SharedYcmd + def test_Subcommands_Format_WholeFile_Spaces( self, app ): + filepath = PathToTestFile( 'test.js' ) + RunTest( app, { + 'description': 'Formatting is applied on the whole file ' + 'with tabs composed of 4 spaces', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': True } }, - 'options': { - 'tab_size': 4, - 'insert_spaces': True + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( ' ', + LocationMatcher( filepath, 2, 1 ), + LocationMatcher( filepath, 2, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 3, 1 ), + LocationMatcher( filepath, 3, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 3, 14 ), + LocationMatcher( filepath, 3, 14 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 4, 1 ), + LocationMatcher( filepath, 4, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 4, 14 ), + LocationMatcher( filepath, 4, 14 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 5, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 6, 1 ), + LocationMatcher( filepath, 6, 5 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 7, 1 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 8, 6 ), + LocationMatcher( filepath, 8, 6 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 18, 1 ), + LocationMatcher( filepath, 18, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 19, 1 ), + LocationMatcher( filepath, 19, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 20, 1 ), + LocationMatcher( filepath, 20, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 21, 1 ), + LocationMatcher( filepath, 21, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 24, 1 ), + LocationMatcher( filepath, 24, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 25, 1 ), + LocationMatcher( filepath, 25, 4 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 26, 1 ), + LocationMatcher( filepath, 26, 4 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 27, 1 ), + LocationMatcher( filepath, 27, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 27, 17 ), + LocationMatcher( filepath, 27, 17 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( ' ', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 5, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 6, 1 ), - LocationMatcher( filepath, 6, 5 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 7, 1 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 3 ) ), - ) - } ) ) - } ) - } - } ) + } ) -@IsolatedYcmd() -def Subcommands_Format_Range_Tabs_test( app ): - filepath = PathToTestFile( 'test.js' ) - RunTest( app, { - 'description': 'Formatting is applied on some part of the file ' - 'with tabs instead of spaces', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'range': { - 'start': { - 'line_num': 5, - 'column_num': 3, - }, - 'end': { - 'line_num': 8, - 'column_num': 6 + @SharedYcmd + def test_Subcommands_Format_WholeFile_Tabs( self, app ): + filepath = PathToTestFile( 'test.js' ) + RunTest( app, { + 'description': 'Formatting is applied on the whole file ' + 'with tabs composed of 2 spaces', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': False } }, - 'options': { - 'tab_size': 4, - 'insert_spaces': False + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\t', + LocationMatcher( filepath, 2, 1 ), + LocationMatcher( filepath, 2, 3 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 3, 1 ), + LocationMatcher( filepath, 3, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 3, 14 ), + LocationMatcher( filepath, 3, 14 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 4, 1 ), + LocationMatcher( filepath, 4, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 4, 14 ), + LocationMatcher( filepath, 4, 14 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 5, 3 ) ), + ChunkMatcher( '\t\t', + LocationMatcher( filepath, 6, 1 ), + LocationMatcher( filepath, 6, 5 ) ), + ChunkMatcher( '\t\t', + LocationMatcher( filepath, 7, 1 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 8, 6 ), + LocationMatcher( filepath, 8, 6 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 18, 1 ), + LocationMatcher( filepath, 18, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 19, 1 ), + LocationMatcher( filepath, 19, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 20, 1 ), + LocationMatcher( filepath, 20, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 21, 1 ), + LocationMatcher( filepath, 21, 2 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 24, 1 ), + LocationMatcher( filepath, 24, 3 ) ), + ChunkMatcher( '\t ', + LocationMatcher( filepath, 25, 1 ), + LocationMatcher( filepath, 25, 4 ) ), + ChunkMatcher( '\t ', + LocationMatcher( filepath, 26, 1 ), + LocationMatcher( filepath, 26, 4 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 27, 1 ), + LocationMatcher( filepath, 27, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 27, 17 ), + LocationMatcher( filepath, 27, 17 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\t', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 5, 3 ) ), - ChunkMatcher( '\t\t', - LocationMatcher( filepath, 6, 1 ), - LocationMatcher( filepath, 6, 5 ) ), - ChunkMatcher( '\t\t', - LocationMatcher( filepath, 7, 1 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 3 ) ), - ) - } ) ) - } ) - } - } ) + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'extra_confs', 'brace_on_same_line.py' ) } ) -def Subcommands_Format_ExtraConf_BraceOnSameLine_test( app ): - WaitUntilCompleterServerReady( app, 'javascript' ) - filepath = PathToTestFile( 'extra_confs', 'func.js' ) - RunTest( app, { - 'description': 'Format with an extra conf, braces on new line', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': True + @SharedYcmd + def test_Subcommands_Format_Range_Spaces( self, app ): + filepath = PathToTestFile( 'test.js' ) + RunTest( app, { + 'description': 'Formatting is applied on some part of the file ' + 'with tabs composed of 4 spaces by default', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'range': { + 'start': { + 'line_num': 5, + 'column_num': 3, + }, + 'end': { + 'line_num': 8, + 'column_num': 6 + } + }, + 'options': { + 'tab_size': 4, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( ' ', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 5, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 6, 1 ), + LocationMatcher( filepath, 6, 5 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 7, 1 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 3 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( ' ', - LocationMatcher( filepath, 2, 1 ), - LocationMatcher( filepath, 2, 1 ) ), - ) - } ) ) - } ) - } - } ) + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'extra_confs', 'brace_on_new_line.py' ) } ) -def Subcommands_Format_ExtraConf_BraceOnNewLine_test( app ): - WaitUntilCompleterServerReady( app, 'javascript' ) - filepath = PathToTestFile( 'extra_confs', 'func.js' ) - RunTest( app, { - 'description': 'Format with an extra conf, braces on new line', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': True + @IsolatedYcmd() + def test_Subcommands_Format_Range_Tabs( self, app ): + filepath = PathToTestFile( 'test.js' ) + RunTest( app, { + 'description': 'Formatting is applied on some part of the file ' + 'with tabs instead of spaces', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'range': { + 'start': { + 'line_num': 5, + 'column_num': 3, + }, + 'end': { + 'line_num': 8, + 'column_num': 6 + } + }, + 'options': { + 'tab_size': 4, + 'insert_spaces': False + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\t', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 5, 3 ) ), + ChunkMatcher( '\t\t', + LocationMatcher( filepath, 6, 1 ), + LocationMatcher( filepath, 6, 5 ) ), + ChunkMatcher( '\t\t', + LocationMatcher( filepath, 7, 1 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 3 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( matches_regexp( '\r?\n' ), - LocationMatcher( filepath, 1, 19 ), - LocationMatcher( filepath, 1, 20 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 2, 1 ), - LocationMatcher( filepath, 2, 1 ) ), - ) - } ) ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GetType_test( app ): - RunTest( app, { - 'description': 'GetType works', - 'request': { - 'command': 'GetType', - 'line_num': 14, - 'column_num': 1, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': MessageMatcher( 'var foo: Foo' ) - } - } ) - - -@SharedYcmd -def Subcommands_GetDoc_Method_test( app ): - RunTest( app, { - 'description': 'GetDoc on a method returns its docstring', - 'request': { - 'command': 'GetDoc', - 'line_num': 31, - 'column_num': 5, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'detailed_info': '(method) Bar.testMethod(): void\n\n' - 'Method documentation' - } ) - } - } ) + } ) -@SharedYcmd -def Subcommands_GetDoc_Class_test( app ): - RunTest( app, { - 'description': 'GetDoc on a class returns its docstring', - 'request': { - 'command': 'GetDoc', - 'line_num': 34, - 'column_num': 3, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'detailed_info': 'class Bar\n\n' - 'Class documentation\n\n' - 'Multi-line' - } ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'extra_confs', 'brace_on_same_line.py' ) } ) + def test_Subcommands_Format_ExtraConf_BraceOnSameLine( self, app ): + WaitUntilCompleterServerReady( app, 'javascript' ) + filepath = PathToTestFile( 'extra_confs', 'func.js' ) + RunTest( app, { + 'description': 'Format with an extra conf, braces on new line', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( ' ', + LocationMatcher( filepath, 2, 1 ), + LocationMatcher( filepath, 2, 1 ) ), + ) + } ) ) + } ) + } + } ) -@SharedYcmd -def Subcommands_GoToReferences_test( app ): - RunTest( app, { - 'description': 'GoToReferences works', - 'request': { - 'command': 'GoToReferences', - 'line_num': 30, - 'column_num': 5, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': contains_inanyorder( - has_entries( { 'description': 'var bar = new Bar();', - 'line_num' : 30, - 'column_num' : 5, - 'filepath' : PathToTestFile( 'test.js' ) } ), - has_entries( { 'description': 'bar.testMethod();', - 'line_num' : 31, - 'column_num' : 1, - 'filepath' : PathToTestFile( 'test.js' ) } ), - has_entries( { 'description': 'bar.nonExistingMethod();', - 'line_num' : 32, - 'column_num' : 1, - 'filepath' : PathToTestFile( 'test.js' ) } ), - has_entries( { 'description': 'var bar = new Bar();', - 'line_num' : 1, - 'column_num' : 5, - 'filepath' : PathToTestFile( 'file3.js' ) } ), - has_entries( { 'description': 'bar.testMethod();', - 'line_num' : 2, - 'column_num' : 1, - 'filepath' : PathToTestFile( 'file3.js' ) } ) - ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'extra_confs', 'brace_on_new_line.py' ) } ) + def test_Subcommands_Format_ExtraConf_BraceOnNewLine( self, app ): + WaitUntilCompleterServerReady( app, 'javascript' ) + filepath = PathToTestFile( 'extra_confs', 'func.js' ) + RunTest( app, { + 'description': 'Format with an extra conf, braces on new line', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( matches_regexp( '\r?\n' ), + LocationMatcher( filepath, 1, 19 ), + LocationMatcher( filepath, 1, 20 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 2, 1 ), + LocationMatcher( filepath, 2, 1 ) ), + ) + } ) ) + } ) + } + } ) -@pytest.mark.parametrize( "req,rep", [ - ( ( 'file3.js', 1, 1, 'testMethod' ), ( 'test.js', 27, 3, 'testMethod' ) ), + @SharedYcmd + def test_Subcommands_GetType( self, app ): + RunTest( app, { + 'description': 'GetType works', + 'request': { + 'command': 'GetType', + 'line_num': 14, + 'column_num': 1, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': MessageMatcher( 'var foo: Foo' ) + } + } ) - ( ( 'file3.js', 1, 1, 'BAR' ), - [ ( 'file3.js', 1, 5, 'bar' ), - ( 'test.js', 30, 5, 'bar' ), - ( 'test.js', 22, 1, 'Bar' ) ] ), - ( ( 'file3.js', 1, 1, 'nothinghere' ), 'Symbol not found' ) -] ) -@SharedYcmd -def Subcommands_GoToSymbol_test( app, req, rep ): - if isinstance( rep, tuple ): - expect = { - 'response': requests.codes.ok, - 'data': LocationMatcher( PathToTestFile( rep[ 0 ] ), *rep[ 1: ] ) - } - elif isinstance( rep, list ): - expect = { - 'response': requests.codes.ok, - 'data': contains_inanyorder( *[ - LocationMatcher( PathToTestFile( r[ 0 ] ), *r[ 1: ] ) - for r in rep - ] ) - } - else: - expect = { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, rep ) - } + @SharedYcmd + def test_Subcommands_GetDoc_Method( self, app ): + RunTest( app, { + 'description': 'GetDoc on a method returns its docstring', + 'request': { + 'command': 'GetDoc', + 'line_num': 31, + 'column_num': 5, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'detailed_info': '(method) Bar.testMethod(): void\n\n' + 'Method documentation' + } ) + } + } ) - RunTest( app, { - 'request': { - 'command': 'GoToSymbol', - 'arguments': [ req[ 3 ] ], - 'line_num': req[ 1 ], - 'column_num': req[ 2 ], - 'filepath': PathToTestFile( req[ 0 ] ), - }, - 'expect': expect - } ) + @SharedYcmd + def test_Subcommands_GetDoc_Class( self, app ): + RunTest( app, { + 'description': 'GetDoc on a class returns its docstring', + 'request': { + 'command': 'GetDoc', + 'line_num': 34, + 'column_num': 3, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'detailed_info': 'class Bar\n\n' + 'Class documentation\n\n' + 'Multi-line' + } ) + } + } ) -def Subcommands_GoTo( app, goto_command ): - RunTest( app, { - 'description': goto_command + ' works within file', - 'request': { - 'command': goto_command, - 'line_num': 31, - 'column_num': 13, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': LocationMatcher( PathToTestFile( 'test.js' ), 27, 3 ) - } - } ) + @SharedYcmd + def test_Subcommands_GoToReferences( self, app ): + RunTest( app, { + 'description': 'GoToReferences works', + 'request': { + 'command': 'GoToReferences', + 'line_num': 30, + 'column_num': 5, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_inanyorder( + has_entries( { 'description': 'var bar = new Bar();', + 'line_num' : 30, + 'column_num' : 5, + 'filepath' : PathToTestFile( 'test.js' ) } ), + has_entries( { 'description': 'bar.testMethod();', + 'line_num' : 31, + 'column_num' : 1, + 'filepath' : PathToTestFile( 'test.js' ) } ), + has_entries( { 'description': 'bar.nonExistingMethod();', + 'line_num' : 32, + 'column_num' : 1, + 'filepath' : PathToTestFile( 'test.js' ) } ), + has_entries( { 'description': 'var bar = new Bar();', + 'line_num' : 1, + 'column_num' : 5, + 'filepath' : PathToTestFile( 'file3.js' ) } ), + has_entries( { 'description': 'bar.testMethod();', + 'line_num' : 2, + 'column_num' : 1, + 'filepath' : PathToTestFile( 'file3.js' ) } ) + ) + } + } ) -@pytest.mark.parametrize( 'command', [ 'GoTo', - 'GoToDefinition', - 'GoToDeclaration' ] ) -@SharedYcmd -def Subcommands_GoTo_test( app, command ): - Subcommands_GoTo( app, command ) + @SharedYcmd + def test_Subcommands_GoToSymbol( self, app ): + for req, rep in [ + ( ( 'file3.js', 1, 1, 'testMethod' ), + ( 'test.js', 27, 3, 'testMethod' ) ), + + ( ( 'file3.js', 1, 1, 'BAR' ), + [ ( 'file3.js', 1, 5, 'bar' ), + ( 'test.js', 30, 5, 'bar' ), + ( 'test.js', 22, 1, 'Bar' ) ] ), + + ( ( 'file3.js', 1, 1, 'nothinghere' ), 'Symbol not found' ) + ]: + with self.subTest( req = req, rep = rep ): + if isinstance( rep, tuple ): + expect = { + 'response': requests.codes.ok, + 'data': LocationMatcher( PathToTestFile( rep[ 0 ] ), *rep[ 1: ] ) + } + elif isinstance( rep, list ): + expect = { + 'response': requests.codes.ok, + 'data': contains_inanyorder( *[ + LocationMatcher( PathToTestFile( r[ 0 ] ), *r[ 1: ] ) + for r in rep + ] ) + } + else: + expect = { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, rep ) + } + + RunTest( app, { + 'request': { + 'command': 'GoToSymbol', + 'arguments': [ req[ 3 ] ], + 'line_num': req[ 1 ], + 'column_num': req[ 2 ], + 'filepath': PathToTestFile( req[ 0 ] ), + }, + 'expect': expect + } ) + + + + @SharedYcmd + def test_Subcommands_GoTo( self, app ): + for command in [ 'GoTo', 'GoToDefinition', 'GoToDeclaration' ]: + with self.subTest( command = command ): + Subcommands_GoTo( app, command ) + + + @SharedYcmd + def test_Subcommands_GoToType( self, app ): + RunTest( app, { + 'description': 'GoToType works', + 'request': { + 'command': 'GoToType', + 'line_num': 11, + 'column_num': 6, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': LocationMatcher( PathToTestFile( 'test.js' ), 1, 7 ) + } + } ) -@SharedYcmd -def Subcommands_GoToType_test( app ): - RunTest( app, { - 'description': 'GoToType works', - 'request': { - 'command': 'GoToType', - 'line_num': 11, - 'column_num': 6, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': LocationMatcher( PathToTestFile( 'test.js' ), 1, 7 ) - } - } ) + @SharedYcmd + def test_Subcommands_FixIt( self, app ): + filepath = PathToTestFile( 'test.js' ) + RunTest( app, { + 'description': 'FixIt works on a non-existing method', + 'request': { + 'command': 'FixIt', + 'line_num': 32, + 'column_num': 19, + 'filepath': filepath, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': "Declare method 'nonExistingMethod'", + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( + '^\r?\n' + ' nonExistingMethod\\(\\) {\r?\n' + ' throw new Error\\("Method not implemented."\\);\r?\n' + ' }$', + ), + LocationMatcher( filepath, 22, 12 ), + LocationMatcher( filepath, 22, 12 ) ) + ), + 'location': LocationMatcher( filepath, 32, 19 ) + } ) + ) + } ) + } + } ) -@SharedYcmd -def Subcommands_FixIt_test( app ): - filepath = PathToTestFile( 'test.js' ) - RunTest( app, { - 'description': 'FixIt works on a non-existing method', - 'request': { - 'command': 'FixIt', - 'line_num': 32, - 'column_num': 19, - 'filepath': filepath, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': "Declare method 'nonExistingMethod'", + @SharedYcmd + def test_Subcommands_OrganizeImports( self, app ): + filepath = PathToTestFile( 'imports.js' ) + RunTest( app, { + 'description': 'OrganizeImports removes unused imports, ' + 'coalesces imports from the same module, and sorts them', + 'request': { + 'command': 'OrganizeImports', + 'filepath': filepath, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { 'chunks': contains_exactly( ChunkMatcher( matches_regexp( - '^\r?\n' - ' nonExistingMethod\\(\\) {\r?\n' - ' throw new Error\\("Method not implemented."\\);\r?\n' - ' }$', - ), - LocationMatcher( filepath, 22, 12 ), - LocationMatcher( filepath, 22, 12 ) ) - ), - 'location': LocationMatcher( filepath, 32, 19 ) - } ) - ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_OrganizeImports_test( app ): - filepath = PathToTestFile( 'imports.js' ) - RunTest( app, { - 'description': 'OrganizeImports removes unused imports, ' - 'coalesces imports from the same module, and sorts them', - 'request': { - 'command': 'OrganizeImports', - 'filepath': filepath, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( - matches_regexp( - 'import \\* as lib from "library";\r?\n' - 'import func, { func1, func2 } from "library";\r?\n' ), - LocationMatcher( filepath, 1, 1 ), - LocationMatcher( filepath, 2, 1 ) ), - ChunkMatcher( - '', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 6, 1 ) ), - ChunkMatcher( - '', - LocationMatcher( filepath, 9, 1 ), - LocationMatcher( filepath, 10, 1 ) ), - ) - } ) ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_RefactorRename_Missing_test( app ): - RunTest( app, { - 'description': 'RefactorRename requires a parameter', - 'request': { - 'command': 'RefactorRename', - 'line_num': 27, - 'column_num': 8, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( ValueError, - 'Please specify a new name to rename it to.\n' - 'Usage: RefactorRename ' ) - } - } ) + 'import \\* as lib from "library";\r?\n' + 'import func, { func1, func2 } from "library";\r?\n' ), + LocationMatcher( filepath, 1, 1 ), + LocationMatcher( filepath, 2, 1 ) ), + ChunkMatcher( + '', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 6, 1 ) ), + ChunkMatcher( + '', + LocationMatcher( filepath, 9, 1 ), + LocationMatcher( filepath, 10, 1 ) ), + ) + } ) ) + } ) + } + } ) -@SharedYcmd -def Subcommands_RefactorRename_NotPossible_test( app ): - RunTest( app, { - 'description': 'RefactorRename cannot rename a non-existing method', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'whatever' ], - 'line_num': 35, - 'column_num': 5, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, - 'Value cannot be renamed: ' - 'You cannot rename this element.' ) - } - } ) + @SharedYcmd + def test_Subcommands_RefactorRename_Missing( self, app ): + RunTest( app, { + 'description': 'RefactorRename requires a parameter', + 'request': { + 'command': 'RefactorRename', + 'line_num': 27, + 'column_num': 8, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( ValueError, + 'Please specify a new name to rename it to.\n' + 'Usage: RefactorRename ' ) + } + } ) -@SharedYcmd -def Subcommands_RefactorRename_Simple_test( app ): - RunTest( app, { - 'description': 'RefactorRename works on a class name', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'test' ], - 'line_num': 1, - 'column_num': 7, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_inanyorder( - ChunkMatcher( - 'test', - LocationMatcher( PathToTestFile( 'test.js' ), 11, 15 ), - LocationMatcher( PathToTestFile( 'test.js' ), 11, 18 ) ), - ChunkMatcher( - 'test', - LocationMatcher( PathToTestFile( 'test.js' ), 1, 7 ), - LocationMatcher( PathToTestFile( 'test.js' ), 1, 10 ) ), - ), - 'location': LocationMatcher( PathToTestFile( 'test.js' ), 1, 7 ) - } ) ) - } ) - } - } ) + @SharedYcmd + def test_Subcommands_RefactorRename_NotPossible( self, app ): + RunTest( app, { + 'description': 'RefactorRename cannot rename a non-existing method', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'whatever' ], + 'line_num': 35, + 'column_num': 5, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, + 'Value cannot be renamed: ' + 'You cannot rename this element.' ) + } + } ) -@SharedYcmd -def Subcommands_RefactorRename_MultipleFiles_test( app ): - RunTest( app, { - 'description': 'RefactorRename works across files', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'this-is-a-longer-string' ], - 'line_num': 22, - 'column_num': 8, - 'filepath': PathToTestFile( 'test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_inanyorder( - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'test.js' ), 22, 7 ), - LocationMatcher( PathToTestFile( 'test.js' ), 22, 10 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'test.js' ), 30, 15 ), - LocationMatcher( PathToTestFile( 'test.js' ), 30, 18 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'test.js' ), 34, 1 ), - LocationMatcher( PathToTestFile( 'test.js' ), 34, 4 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'file2.js' ), 1, 5 ), - LocationMatcher( PathToTestFile( 'file2.js' ), 1, 8 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'file3.js' ), 1, 15 ), - LocationMatcher( PathToTestFile( 'file3.js' ), 1, 18 ) ), - ), - 'location': LocationMatcher( PathToTestFile( 'test.js' ), 22, 8 ) - } ) ) - } ) - } - } ) + @SharedYcmd + def test_Subcommands_RefactorRename_Simple( self, app ): + RunTest( app, { + 'description': 'RefactorRename works on a class name', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'test' ], + 'line_num': 1, + 'column_num': 7, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_inanyorder( + ChunkMatcher( + 'test', + LocationMatcher( PathToTestFile( 'test.js' ), 11, 15 ), + LocationMatcher( PathToTestFile( 'test.js' ), 11, 18 ) ), + ChunkMatcher( + 'test', + LocationMatcher( PathToTestFile( 'test.js' ), 1, 7 ), + LocationMatcher( PathToTestFile( 'test.js' ), 1, 10 ) ), + ), + 'location': LocationMatcher( PathToTestFile( 'test.js' ), 1, 7 ) + } ) ) + } ) + } + } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_Subcommands_RefactorRename_MultipleFiles( self, app ): + RunTest( app, { + 'description': 'RefactorRename works across files', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'this-is-a-longer-string' ], + 'line_num': 22, + 'column_num': 8, + 'filepath': PathToTestFile( 'test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_inanyorder( + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'test.js' ), 22, 7 ), + LocationMatcher( PathToTestFile( 'test.js' ), 22, 10 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'test.js' ), 30, 15 ), + LocationMatcher( PathToTestFile( 'test.js' ), 30, 18 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'test.js' ), 34, 1 ), + LocationMatcher( PathToTestFile( 'test.js' ), 34, 4 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'file2.js' ), 1, 5 ), + LocationMatcher( PathToTestFile( 'file2.js' ), 1, 8 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'file3.js' ), 1, 15 ), + LocationMatcher( PathToTestFile( 'file3.js' ), 1, 18 ) ), + ), + 'location': LocationMatcher( PathToTestFile( 'test.js' ), 22, 8 ) + } ) ) + } ) + } + } ) diff --git a/ycmd/tests/javascriptreact/__init__.py b/ycmd/tests/javascriptreact/__init__.py index 0a3f3037b2..183b4d23d3 100644 --- a/ycmd/tests/javascriptreact/__init__.py +++ b/ycmd/tests/javascriptreact/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,10 +15,67 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . +import functools import os -from ycmd.tests.javascriptreact.conftest import * # noqa +from unittest.mock import patch +from ycmd.tests.test_utils import ( ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp, + StopCompleterServer, + WaitUntilCompleterServerReady ) + +shared_app = None + + +def setUpModule(): + global shared_app + with patch( 'ycmd.completers.javascript.hook.' + 'ShouldEnableTernCompleter', return_value = False ): + shared_app = SetUpApp() + WaitUntilCompleterServerReady( shared_app, 'javascriptreact' ) + + +def tearDownModule(): + global shared_app + StopCompleterServer( shared_app, 'javascriptreact' ) def PathToTestFile( *args ): dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) return os.path.join( dir_of_current_script, 'testdata', *args ) + + +def SharedYcmd( test ): + """Defines a decorator to be attached to tests of this package. This decorator + passes the shared ycmd application as a parameter. + Do NOT attach it to test generators but directly to the yielded tests.""" + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + """Defines a decorator to be attached to tests of this package. This decorator + passes a unique ycmd application as a parameter. It should be used on tests + that change the server state in a irreversible way (ex: a semantic subserver + is stopped or restarted) or expect a clean state (ex: no semantic subserver + started, no .ycm_extra_conf.py loaded, etc). + Do NOT attach it to test generators but directly to the yielded tests.""" + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with patch( 'ycmd.completers.javascript.hook.' + 'ShouldEnableTernCompleter', return_value = False ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'javascriptreact' ) + return Wrapper + return Decorator diff --git a/ycmd/tests/javascriptreact/conftest.py b/ycmd/tests/javascriptreact/conftest.py deleted file mode 100644 index a8b4d37221..0000000000 --- a/ycmd/tests/javascriptreact/conftest.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import pytest - -from ycmd.tests.test_utils import ( ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - SetUpApp, - StopCompleterServer, - WaitUntilCompleterServerReady ) -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - shared_app = SetUpApp() - WaitUntilCompleterServerReady( shared_app, 'javascriptreact' ) - yield - StopCompleterServer( shared_app, 'javascriptreact' ) - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with IsolatedApp( request.param[ 1 ] ) as app: - yield app - StopCompleterServer( app, 'javascriptreact' ) - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) diff --git a/ycmd/tests/javascriptreact/get_completions_test.py b/ycmd/tests/javascriptreact/get_completions_test.py index b0048fb8f5..768acb15f4 100644 --- a/ycmd/tests/javascriptreact/get_completions_test.py +++ b/ycmd/tests/javascriptreact/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -18,9 +18,13 @@ import pprint import requests from hamcrest import assert_that, equal_to, has_entries, has_item -from ycmd.tests.javascriptreact import PathToTestFile, SharedYcmd +from ycmd.tests.javascriptreact import ( PathToTestFile, # noqa + SharedYcmd, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import CombineRequest from ycmd.utils import ReadFile +from unittest import TestCase def RunTest( app, test ): @@ -52,31 +56,27 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def GetCompletions_JavaScriptReact_DefaultTriggers_test( app ): - filepath = PathToTestFile( 'test.jsx' ) - RunTest( app, { - 'description': 'No need to force after a semantic trigger', - 'request': { - 'line_num': 7, - 'column_num': 12, - 'filepath': filepath, - 'filetype': 'javascriptreact' - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( has_entries( { - 'insertion_text': 'alinkColor', - 'extra_menu_info': '(property) Document.alinkColor: string', - 'detailed_info': '(property) Document.alinkColor: string', - 'kind': 'property', - } ) ) - } ) - } - } ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class GetCompletionsTest( TestCase ): + @SharedYcmd + def test_GetCompletions_JavaScriptReact_DefaultTriggers( self, app ): + filepath = PathToTestFile( 'test.jsx' ) + RunTest( app, { + 'description': 'No need to force after a semantic trigger', + 'request': { + 'line_num': 7, + 'column_num': 12, + 'filepath': filepath, + 'filetype': 'javascriptreact' + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( has_entries( { + 'insertion_text': 'alinkColor', + 'extra_menu_info': '(property) Document.alinkColor: string', + 'detailed_info': '(property) Document.alinkColor: string', + 'kind': 'property', + } ) ) + } ) + } + } ) diff --git a/ycmd/tests/javascriptreact/subcommands_test.py b/ycmd/tests/javascriptreact/subcommands_test.py index 83ed0f7a71..0bd072706f 100644 --- a/ycmd/tests/javascriptreact/subcommands_test.py +++ b/ycmd/tests/javascriptreact/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -21,8 +21,12 @@ has_entries ) import pprint import requests +from unittest import TestCase from ycmd.tests.test_utils import CombineRequest -from ycmd.tests.javascriptreact import PathToTestFile, SharedYcmd +from ycmd.tests.javascriptreact import ( PathToTestFile, # noqa + SharedYcmd, + setUpModule, + tearDownModule ) from ycmd.utils import ReadFile @@ -68,32 +72,28 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def Subcommands_GoToReferences_test( app ): - RunTest( app, { - 'description': 'GoToReferences works', - 'request': { - 'command': 'GoToReferences', - 'line_num': 6, - 'column_num': 4, - 'filepath': PathToTestFile( 'test.jsx' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': contains_inanyorder( - has_entries( { 'description': 'function HelloMessage({ name }) {', - 'line_num' : 1, - 'column_num' : 10, - 'filepath' : PathToTestFile( 'test.jsx' ) } ), - has_entries( { 'description': ' ,', - 'line_num' : 6, - 'column_num' : 4, - 'filepath' : PathToTestFile( 'test.jsx' ) } ), - ) - } - } ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class SubcommandsTest( TestCase ): + @SharedYcmd + def test_Subcommands_GoToReferences( self, app ): + RunTest( app, { + 'description': 'GoToReferences works', + 'request': { + 'command': 'GoToReferences', + 'line_num': 6, + 'column_num': 4, + 'filepath': PathToTestFile( 'test.jsx' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_inanyorder( + has_entries( { 'description': 'function HelloMessage({ name }) {', + 'line_num' : 1, + 'column_num' : 10, + 'filepath' : PathToTestFile( 'test.jsx' ) } ), + has_entries( { 'description': ' ,', + 'line_num' : 6, + 'column_num' : 4, + 'filepath' : PathToTestFile( 'test.jsx' ) } ), + ) + } + } ) diff --git a/ycmd/tests/language_server/__init__.py b/ycmd/tests/language_server/__init__.py index 84e671c56a..d424c8f7b4 100644 --- a/ycmd/tests/language_server/__init__.py +++ b/ycmd/tests/language_server/__init__.py @@ -15,10 +15,11 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . +import functools import os from ycmd.completers.language_server import language_server_completer as lsc -from ycmd.tests.language_server.conftest import * # noqa +from ycmd.tests.test_utils import IsolatedApp, StopCompleterServer def PathToTestFile( *args ): @@ -44,3 +45,16 @@ def WriteData( self, data ): def ReadData( self, size = -1 ): return bytes( b'' ) + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'foo' ) + return Wrapper + return Decorator diff --git a/ycmd/tests/language_server/conftest.py b/ycmd/tests/language_server/conftest.py deleted file mode 100644 index fb6a980fdf..0000000000 --- a/ycmd/tests/language_server/conftest.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import pytest -from ycmd.tests.test_utils import IsolatedApp, StopCompleterServer - - -@pytest.fixture -def app( request ): - with IsolatedApp( request.param ) as app: - try: - yield app - finally: - StopCompleterServer( app, 'foo' ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ custom_options ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) diff --git a/ycmd/tests/language_server/generic_completer_test.py b/ycmd/tests/language_server/generic_completer_test.py index 415cb1b65a..e9c8738a1d 100644 --- a/ycmd/tests/language_server/generic_completer_test.py +++ b/ycmd/tests/language_server/generic_completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -26,6 +26,7 @@ has_items, instance_of ) from unittest.mock import patch +from unittest import TestCase from os import path as p from ycmd.completers.language_server.language_server_completer import ( @@ -58,221 +59,96 @@ TEST_PORT = utils.GetUnusedLocalhostPort() - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_GetCompletions_NotACompletionProvider_test( app ): - completer = handlers._server_state.GetFiletypeCompleter( [ 'foo' ] ) - with patch.object( completer, '_is_completion_provider', False ): +class GenericCompleterTest( TestCase ): + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + def test_GenericLSPCompleter_GetCompletions_NotACompletionProvider( + self, app ): + completer = handlers._server_state.GetFiletypeCompleter( [ 'foo' ] ) + with patch.object( completer, '_is_completion_provider', False ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 3, + contents = 'Java', + event_name = 'FileReadyToParse' ) + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + response = app.post_json( '/completions', BuildRequest( **request ) ) + assert_that( + response.json, + has_entries( { 'completions': contains_exactly( + CompletionEntryMatcher( 'Java', '[ID]' ) ) } ) ) + + + @IsolatedYcmd( { 'semantic_triggers': { 'foo': [ 're!.' ] }, + 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + def test_GenericLSPCompleter_GetCompletions_FilteredNoForce( self, app ): request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 3, - contents = 'Java', - event_name = 'FileReadyToParse' ) + filetype = 'foo', + line_num = 1, + column_num = 3, + contents = 'Java', + event_name = 'FileReadyToParse' ) app.post_json( '/event_notification', request ) WaitUntilCompleterServerReady( app, 'foo' ) request.pop( 'event_name' ) response = app.post_json( '/completions', BuildRequest( **request ) ) - assert_that( response.json, has_entries( { 'completions': contains_exactly( - CompletionEntryMatcher( 'Java', '[ID]' ) ) } ) ) - - -@IsolatedYcmd( { 'semantic_triggers': { 'foo': [ 're!.' ] }, - 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_GetCompletions_FilteredNoForce_test( app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 3, - contents = 'Java', - event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - response = app.post_json( '/completions', BuildRequest( **request ) ) - assert_that( response.status_code, equal_to( 200 ) ) - print( f'Completer response: { json.dumps( response.json, indent = 2 ) }' ) - assert_that( response.json, has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'JavaScript', 'JavaScript details' ), - ) - } ) ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_GetCompletions_test( app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request[ 'force_semantic' ] = True - request.pop( 'event_name' ) - response = app.post_json( '/completions', BuildRequest( **request ) ) - assert_that( response.status_code, equal_to( 200 ) ) - print( f'Completer response: { json.dumps( response.json, indent = 2 ) }' ) - assert_that( response.json, has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'JavaScript', 'JavaScript details' ), - CompletionEntryMatcher( 'TypeScript', 'TypeScript details' ), - ) - } ) ) - - -@IsolatedYcmd( { - 'language_server': [ - { - 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', - PATH_TO_GENERIC_COMPLETER, - '--listen', - str( TEST_PORT ) ], - 'port': TEST_PORT - } - ] -} ) -def GenericLSPCompleter_GetCompletions_TCP_test( app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request[ 'force_semantic' ] = True - request.pop( 'event_name' ) - response = app.post_json( '/completions', BuildRequest( **request ) ) - assert_that( response.status_code, equal_to( 200 ) ) - print( f'Completer response: { json.dumps( response.json, indent = 2 ) }' ) - assert_that( response.json, has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'JavaScript', 'JavaScript details' ), - CompletionEntryMatcher( 'TypeScript', 'TypeScript details' ), - ) - } ) ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', - PATH_TO_GENERIC_COMPLETER, - '--listen', str( TEST_PORT ) ], - 'port': TEST_PORT } ] } ) -def GenericLSPCompleter_DebugInfo_TCP_test( app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - - request.pop( 'event_name' ) - response = app.post_json( '/debug_info', request ).json - assert_that( - response, - has_entry( 'completer', has_entries( { - 'name': 'GenericLSP', - 'servers': contains_exactly( has_entries( { - 'name': 'fooCompleter', - 'port': TEST_PORT, - 'pid': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ), - instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': instance_of( str ), - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'generic_server' ), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}' - } ), - ) - } ) ), + assert_that( response.status_code, equal_to( 200 ) ) + print( f'Completer response: { json.dumps( response.json, indent = 2 ) }' ) + assert_that( response.json, has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'JavaScript', 'JavaScript details' ), + ) } ) ) - ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', - PATH_TO_GENERIC_COMPLETER, - '--listen', '${port}' ], - 'port': '*' } ] } ) -def GenericLSPCompleter_DebugInfo_TCP_GeneratePort_test( app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - - request.pop( 'event_name' ) - response = app.post_json( '/debug_info', request ).json - assert_that( - response, - has_entry( 'completer', has_entries( { - 'name': 'GenericLSP', - 'servers': contains_exactly( has_entries( { - 'name': 'fooCompleter', - 'port': instance_of( int ), - 'pid': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ), - instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': instance_of( str ), - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'generic_server' ), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}' - } ), - ) - } ) ), + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + def test_GenericLSPCompleter_GetCompletions( self, app ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request[ 'force_semantic' ] = True + request.pop( 'event_name' ) + response = app.post_json( '/completions', BuildRequest( **request ) ) + assert_that( response.status_code, equal_to( 200 ) ) + print( f'Completer response: { json.dumps( response.json, indent = 2 ) }' ) + assert_that( response.json, has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'JavaScript', 'JavaScript details' ), + CompletionEntryMatcher( 'TypeScript', 'TypeScript details' ), + ) } ) ) - ) - - -@IsolatedYcmd( { - 'language_server': [ - { - 'name': 'foo', - 'filetypes': [ 'foo' ], - 'port': TEST_PORT - } - ] -} ) -def GenericLSPCompleter_ConnectTimeout_test( app ): - with patch.object( TCPSingleStreamConnection, 'TCP_CONNECT_TIMEOUT', 1 ): + + + @IsolatedYcmd( { + 'language_server': [ + { + 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', + PATH_TO_GENERIC_COMPLETER, + '--listen', + str( TEST_PORT ) ], + 'port': TEST_PORT + } + ] + } ) + def test_GenericLSPCompleter_GetCompletions_TCP( self, app ): request = BuildRequest( filepath = TEST_FILE, filetype = 'foo', line_num = 1, @@ -280,10 +156,36 @@ def GenericLSPCompleter_ConnectTimeout_test( app ): contents = TEST_FILE_CONTENT, event_name = 'FileReadyToParse' ) app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request[ 'force_semantic' ] = True + request.pop( 'event_name' ) + response = app.post_json( '/completions', BuildRequest( **request ) ) + assert_that( response.status_code, equal_to( 200 ) ) + print( f'Completer response: { json.dumps( response.json, indent = 2 ) }' ) + assert_that( response.json, has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'JavaScript', 'JavaScript details' ), + CompletionEntryMatcher( 'TypeScript', 'TypeScript details' ), + ) + } ) ) - import time - # We patched the timeout to 1s - time.sleep( 1.5 ) + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', + PATH_TO_GENERIC_COMPLETER, + '--listen', str( TEST_PORT ) ], + 'port': TEST_PORT } ] } ) + def test_GenericLSPCompleter_DebugInfo_TCP( self, app ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) request.pop( 'event_name' ) response = app.post_json( '/debug_info', request ).json @@ -294,16 +196,17 @@ def GenericLSPCompleter_ConnectTimeout_test( app ): 'servers': contains_exactly( has_entries( { 'name': 'fooCompleter', 'port': TEST_PORT, - 'pid': None, - 'logfiles': empty(), + 'pid': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ), + instance_of( str ) ), 'extras': contains_exactly( has_entries( { 'key': 'Server State', - 'value': 'Dead', + 'value': instance_of( str ), } ), has_entries( { 'key': 'Project Directory', - 'value': None, + 'value': PathToTestFile( 'generic_server' ), } ), has_entries( { 'key': 'Settings', @@ -315,320 +218,418 @@ def GenericLSPCompleter_ConnectTimeout_test( app ): ) + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', + PATH_TO_GENERIC_COMPLETER, + '--listen', '${port}' ], + 'port': '*' } ] } ) + def test_GenericLSPCompleter_DebugInfo_TCP_GeneratePort( self, app ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_Diagnostics_test( app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - response = app.post_json( '/receive_messages', request ) - assert_that( response.json, has_items( - has_entries( { 'diagnostics': contains_exactly( - has_entries( { - 'kind': equal_to( 'WARNING' ), - 'location': LocationMatcher( TEST_FILE, 2, 1 ), - 'location_extent': RangeMatcher( TEST_FILE, ( 2, 1 ), ( 2, 4 ) ), - 'text': equal_to( 'FOO is all uppercase.' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'WARNING' ), - 'location': LocationMatcher( TEST_FILE, 3, 1 ), - 'location_extent': RangeMatcher( TEST_FILE, ( 3, 1 ), ( 3, 4 ) ), - 'text': equal_to( 'FOO is all uppercase.' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'WARNING' ), - 'location': LocationMatcher( TEST_FILE, 4, 1 ), - 'location_extent': RangeMatcher( TEST_FILE, ( 4, 1 ), ( 4, 4 ) ), - 'text': equal_to( 'FOO is all uppercase.' ), - 'fixit_available': False + request.pop( 'event_name' ) + response = app.post_json( '/debug_info', request ).json + assert_that( + response, + has_entry( 'completer', has_entries( { + 'name': 'GenericLSP', + 'servers': contains_exactly( has_entries( { + 'name': 'fooCompleter', + 'port': instance_of( int ), + 'pid': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ), + instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': instance_of( str ), + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'generic_server' ), + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}' + } ), + ) + } ) ), + } ) ) + ) + + + @IsolatedYcmd( { + 'language_server': [ + { + 'name': 'foo', + 'filetypes': [ 'foo' ], + 'port': TEST_PORT + } + ] + } ) + def test_GenericLSPCompleter_ConnectTimeout( self, app ): + with patch.object( TCPSingleStreamConnection, 'TCP_CONNECT_TIMEOUT', 1 ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + app.post_json( '/event_notification', request ) + + import time + # We patched the timeout to 1s + time.sleep( 1.5 ) + + request.pop( 'event_name' ) + response = app.post_json( '/debug_info', request ).json + assert_that( + response, + has_entry( 'completer', has_entries( { + 'name': 'GenericLSP', + 'servers': contains_exactly( has_entries( { + 'name': 'fooCompleter', + 'port': TEST_PORT, + 'pid': None, + 'logfiles': empty(), + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': 'Dead', + } ), + has_entries( { + 'key': 'Project Directory', + 'value': None, + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}' + } ), + ) + } ) ), + } ) ) + ) + + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + def test_GenericLSPCompleter_Diagnostics( self, app ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + response = app.post_json( '/receive_messages', request ) + assert_that( response.json, has_items( + has_entries( { 'diagnostics': contains_exactly( + has_entries( { + 'kind': equal_to( 'WARNING' ), + 'location': LocationMatcher( TEST_FILE, 2, 1 ), + 'location_extent': RangeMatcher( TEST_FILE, ( 2, 1 ), ( 2, 4 ) ), + 'text': equal_to( 'FOO is all uppercase.' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'WARNING' ), + 'location': LocationMatcher( TEST_FILE, 3, 1 ), + 'location_extent': RangeMatcher( TEST_FILE, ( 3, 1 ), ( 3, 4 ) ), + 'text': equal_to( 'FOO is all uppercase.' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'WARNING' ), + 'location': LocationMatcher( TEST_FILE, 4, 1 ), + 'location_extent': RangeMatcher( TEST_FILE, ( 4, 1 ), ( 4, 4 ) ), + 'text': equal_to( 'FOO is all uppercase.' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'WARNING' ), + 'location': LocationMatcher( TEST_FILE, 5, 1 ), + 'location_extent': RangeMatcher( TEST_FILE, ( 5, 1 ), ( 5, 4 ) ), + 'text': equal_to( 'FOO is all uppercase.' ), + 'fixit_available': False + } ), + ) } ) + ) ) + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + def test_GenericLSPCompleter_Hover_RequestFails( self, app ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + request[ 'command_arguments' ] = [ 'GetHover' ] + response = app.post_json( '/run_completer_command', + request, + expect_errors = True ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( response.json, ErrorMatcher( ResponseFailedException, + 'Request failed: -32601: Unhandled method textDocument/hover' ) ) + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + @patch( 'ycmd.completers.language_server.generic_lsp_completer.' + 'GenericLSPCompleter.GetHoverResponse', return_value = 'asd' ) + def test_GenericLSPCompleter_HoverIsString( self, app, *args ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + request[ 'command_arguments' ] = [ 'GetHover' ] + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, has_entry( 'detailed_info', 'asd' ) ) + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + @patch( 'ycmd.completers.language_server.generic_lsp_completer.' + 'GenericLSPCompleter.GetHoverResponse', + return_value = { 'whatever': 'blah', 'value': 'asd' } ) + def test_GenericLSPCompleter_HoverIsDict( self, app, *args ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + request[ 'command_arguments' ] = [ 'GetHover' ] + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, has_entry( 'detailed_info', 'asd' ) ) + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + @patch( 'ycmd.completers.language_server.generic_lsp_completer.' + 'GenericLSPCompleter.GetHoverResponse', + return_value = [ { 'whatever': 'blah', 'value': 'asd' }, + 'qe', + { 'eh?': 'hover_sucks', 'value': 'yes, it does' } ] ) + def test_GenericLSPCompleter_HoverIsList( self, app, *args ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + request[ 'command_arguments' ] = [ 'GetHover' ] + response = app.post_json( '/run_completer_command', request ).json + assert_that( response, + has_entry( 'detailed_info', 'asd\nqe\nyes, it does' ) ) + + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'project_root_files': [ 'proj_root' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + def test_GenericLSPCompleter_DebugInfo_CustomRoot( self, app, *args ): + test_file = PathToTestFile( + 'generic_server', 'foo', 'bar', 'baz', 'test_file' ) + request = BuildRequest( filepath = test_file, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = '', + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + response = app.post_json( '/debug_info', request ).json + assert_that( + response, + has_entry( 'completer', has_entries( { + 'name': 'GenericLSP', + 'servers': contains_exactly( has_entries( { + 'name': 'fooCompleter', + 'is_running': instance_of( bool ), + 'executable': contains_exactly( instance_of( str ), + instance_of( str ), + instance_of( str ) ), + 'address': None, + 'port': None, + 'pid': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': instance_of( str ), + } ), + has_entries( { + 'key': 'Project Directory', + 'value': PathToTestFile( 'generic_server', 'foo' ), + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}' + } ), + ) + } ) ), + } ) ) + ) + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'project_root_files': [ 'proj_root' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + def test_GenericLSPCompleter_SignatureHelp_NoTriggers( self, app ): + test_file = PathToTestFile( + 'generic_server', 'foo', 'bar', 'baz', 'test_file' ) + request = BuildRequest( filepath = test_file, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = '', + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + response = app.post_json( '/signature_help', request ).json + assert_that( response, has_entries( { + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty() } ), - has_entries( { - 'kind': equal_to( 'WARNING' ), - 'location': LocationMatcher( TEST_FILE, 5, 1 ), - 'location_extent': RangeMatcher( TEST_FILE, ( 5, 1 ), ( 5, 4 ) ), - 'text': equal_to( 'FOO is all uppercase.' ), - 'fixit_available': False + 'errors': empty() + } ) ) + + + @IsolatedYcmd( { 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'project_root_files': [ 'proj_root' ], + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + @patch( 'ycmd.completers.completer.Completer.ShouldUseSignatureHelpNow', + return_value = True ) + def test_GenericLSPCompleter_SignatureHelp_NotASigHelpProvider( + self, app, *args ): + test_file = PathToTestFile( + 'generic_server', 'foo', 'bar', 'baz', 'test_file' ) + request = BuildRequest( filepath = test_file, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = '', + event_name = 'FileReadyToParse' ) + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + response = app.post_json( '/signature_help', request ).json + assert_that( response, has_entries( { + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty() } ), - ) } ) - ) ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_Hover_RequestFails_test( app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - request[ 'command_arguments' ] = [ 'GetHover' ] - response = app.post_json( '/run_completer_command', - request, - expect_errors = True ) - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( response.json, ErrorMatcher( ResponseFailedException, - 'Request failed: -32601: Unhandled method textDocument/hover' ) ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -@patch( 'ycmd.completers.language_server.generic_lsp_completer.' - 'GenericLSPCompleter.GetHoverResponse', return_value = 'asd' ) -def GenericLSPCompleter_HoverIsString_test( get_hover, app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - request[ 'command_arguments' ] = [ 'GetHover' ] - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entry( 'detailed_info', 'asd' ) ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -@patch( 'ycmd.completers.language_server.generic_lsp_completer.' - 'GenericLSPCompleter.GetHoverResponse', - return_value = { 'whatever': 'blah', 'value': 'asd' } ) -def GenericLSPCompleter_HoverIsDict_test( get_hover, app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - request[ 'command_arguments' ] = [ 'GetHover' ] - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entry( 'detailed_info', 'asd' ) ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -@patch( 'ycmd.completers.language_server.generic_lsp_completer.' - 'GenericLSPCompleter.GetHoverResponse', - return_value = [ { 'whatever': 'blah', 'value': 'asd' }, - 'qe', - { 'eh?': 'hover_sucks', 'value': 'yes, it does' } ] ) -def GenericLSPCompleter_HoverIsList_test( get_hover, app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - request[ 'command_arguments' ] = [ 'GetHover' ] - response = app.post_json( '/run_completer_command', request ).json - assert_that( response, has_entry( 'detailed_info', 'asd\nqe\nyes, it does' ) ) - - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'project_root_files': [ 'proj_root' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_DebugInfo_CustomRoot_test( app, *args ): - test_file = PathToTestFile( - 'generic_server', 'foo', 'bar', 'baz', 'test_file' ) - request = BuildRequest( filepath = test_file, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = '', - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - response = app.post_json( '/debug_info', request ).json - assert_that( - response, - has_entry( 'completer', has_entries( { - 'name': 'GenericLSP', - 'servers': contains_exactly( has_entries( { - 'name': 'fooCompleter', - 'is_running': instance_of( bool ), - 'executable': contains_exactly( instance_of( str ), - instance_of( str ), - instance_of( str ) ), - 'address': None, - 'port': None, - 'pid': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': instance_of( str ), - } ), - has_entries( { - 'key': 'Project Directory', - 'value': PathToTestFile( 'generic_server', 'foo' ), - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}' - } ), - ) - } ) ), + 'errors': empty() } ) ) - ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'project_root_files': [ 'proj_root' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_SignatureHelp_NoTriggers_test( app ): - test_file = PathToTestFile( - 'generic_server', 'foo', 'bar', 'baz', 'test_file' ) - request = BuildRequest( filepath = test_file, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = '', - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - response = app.post_json( '/signature_help', request ).json - assert_that( response, has_entries( { - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty() - } ), - 'errors': empty() - } ) ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'project_root_files': [ 'proj_root' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -@patch( 'ycmd.completers.completer.Completer.ShouldUseSignatureHelpNow', - return_value = True ) -def GenericLSPCompleter_SignatureHelp_NotASigHelpProvider_test( should_use_sig, - app ): - test_file = PathToTestFile( - 'generic_server', 'foo', 'bar', 'baz', 'test_file' ) - request = BuildRequest( filepath = test_file, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = '', - event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - response = app.post_json( '/signature_help', request ).json - assert_that( response, has_entries( { - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty() - } ), - 'errors': empty() - } ) ) - - -@IsolatedYcmd( { 'language_server': - [ { 'name': 'foo', - 'filetypes': [ 'foo' ], - 'project_root_files': [ 'proj_root' ], - 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_SignatureHelp_NotSupported_test( app ): - test_file = PathToTestFile( - 'generic_server', 'foo', 'bar', 'baz', 'test_file' ) - app.post_json( '/event_notification', - BuildRequest( **{ - 'filepath': test_file, - 'event_name': 'FileReadyToParse', - 'filetype': 'foo' - } ), - expect_errors = True ) - WaitUntilCompleterServerReady( app, 'foo' ) - - response = app.get( '/signature_help_available', - { 'subserver': 'foo' } ).json - assert_that( response, SignatureAvailableMatcher( 'NO' ) ) - - -@IsolatedYcmd( { - 'global_ycm_extra_conf': PathToTestFile( 'generic_server', 'single_diag.py' ), - 'language_server': + + + @IsolatedYcmd( { 'language_server': [ { 'name': 'foo', 'filetypes': [ 'foo' ], - 'capabilities': { 'workspace': { 'configuration': True } }, + 'project_root_files': [ 'proj_root' ], 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) -def GenericLSPCompleter_SingleDiagnostics_test( app ): - request = BuildRequest( filepath = TEST_FILE, - filetype = 'foo', - line_num = 1, - column_num = 1, - contents = TEST_FILE_CONTENT, - event_name = 'FileReadyToParse' ) - - app.post_json( '/event_notification', request ) - WaitUntilCompleterServerReady( app, 'foo' ) - request.pop( 'event_name' ) - response = app.post_json( '/receive_messages', request ) - assert_that( response.json, has_items( - has_entries( { 'diagnostics': contains_exactly( - has_entries( { - 'kind': equal_to( 'WARNING' ), - 'location': LocationMatcher( TEST_FILE, 2, 1 ), - 'location_extent': RangeMatcher( TEST_FILE, ( 2, 1 ), ( 2, 4 ) ), - 'text': equal_to( 'FOO is all uppercase.' ), - 'fixit_available': False - } ) - ) } ) - ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + def test_GenericLSPCompleter_SignatureHelp_NotSupported( self, app ): + test_file = PathToTestFile( + 'generic_server', 'foo', 'bar', 'baz', 'test_file' ) + app.post_json( '/event_notification', + BuildRequest( **{ + 'filepath': test_file, + 'event_name': 'FileReadyToParse', + 'filetype': 'foo' + } ), + expect_errors = True ) + WaitUntilCompleterServerReady( app, 'foo' ) + + response = app.get( '/signature_help_available', + { 'subserver': 'foo' } ).json + assert_that( response, SignatureAvailableMatcher( 'NO' ) ) + + + @IsolatedYcmd( { + 'global_ycm_extra_conf': PathToTestFile( 'generic_server', + 'single_diag.py' ), + 'language_server': + [ { 'name': 'foo', + 'filetypes': [ 'foo' ], + 'capabilities': { 'workspace': { 'configuration': True } }, + 'cmdline': [ 'node', PATH_TO_GENERIC_COMPLETER, '--stdio' ] } ] } ) + def test_GenericLSPCompleter_SingleDiagnostics( self, app ): + request = BuildRequest( filepath = TEST_FILE, + filetype = 'foo', + line_num = 1, + column_num = 1, + contents = TEST_FILE_CONTENT, + event_name = 'FileReadyToParse' ) + + app.post_json( '/event_notification', request ) + WaitUntilCompleterServerReady( app, 'foo' ) + request.pop( 'event_name' ) + response = app.post_json( '/receive_messages', request ) + assert_that( response.json, has_items( + has_entries( { 'diagnostics': contains_exactly( + has_entries( { + 'kind': equal_to( 'WARNING' ), + 'location': LocationMatcher( TEST_FILE, 2, 1 ), + 'location_extent': RangeMatcher( TEST_FILE, ( 2, 1 ), ( 2, 4 ) ), + 'text': equal_to( 'FOO is all uppercase.' ), + 'fixit_available': False + } ) + ) } ) + ) ) diff --git a/ycmd/tests/language_server/language_server_completer_test.py b/ycmd/tests/language_server/language_server_completer_test.py index 83dc570f19..560fec03dc 100644 --- a/ycmd/tests/language_server/language_server_completer_test.py +++ b/ycmd/tests/language_server/language_server_completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2020 ycmd contributors +# Copyright (C) 2017-2021 ycmd contributors # # This file is part of ycmd. # @@ -15,8 +15,8 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -import pytest from unittest.mock import patch +from unittest import TestCase from hamcrest import ( all_of, assert_that, calling, @@ -89,1410 +89,1422 @@ def GetServerName( self ): return 'mock_completer' -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'extra_confs', 'settings_extra_conf.py' ) } ) -def LanguageServerCompleter_ExtraConf_ServerReset_test( app ): - filepath = PathToTestFile( 'extra_confs', 'foo' ) - app.post_json( '/event_notification', - BuildRequest( filepath = filepath, - filetype = 'foo', - contents = '', - event_name = 'FileReadyToParse' ) ) +def _TupleToLSPRange( tuple ): + return { 'line': tuple[ 0 ], 'character': tuple[ 1 ] } - request_data = RequestWrap( BuildRequest() ) - completer = MockCompleter() +def _Check_Distance( point, start, end, expected ): + point = _TupleToLSPRange( point ) + start = _TupleToLSPRange( start ) + end = _TupleToLSPRange( end ) + range = { 'start': start, 'end': end } + result = lsc._DistanceOfPointToRange( point, range ) + assert_that( result, equal_to( expected ) ) - assert_that( None, equal_to( completer._project_directory ) ) - completer.OnFileReadyToParse( request_data ) - assert_that( completer._project_directory, is_not( None ) ) - assert_that( completer._settings.get( 'ls', {} ), is_not( empty() ) ) +class LanguageServerCompleterTest( TestCase ): + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'extra_confs', 'settings_extra_conf.py' ) } ) + def test_LanguageServerCompleter_ExtraConf_ServerReset( self, app ): + filepath = PathToTestFile( 'extra_confs', 'foo' ) + app.post_json( '/event_notification', + BuildRequest( filepath = filepath, + filetype = 'foo', + contents = '', + event_name = 'FileReadyToParse' ) ) - completer.ServerReset() - assert_that( completer._settings.get( 'ls', {} ), empty() ) - assert_that( None, equal_to( completer._project_directory ) ) + request_data = RequestWrap( BuildRequest() ) + completer = MockCompleter() -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'extra_confs', 'empty_extra_conf.py' ) } ) -def LanguageServerCompleter_ExtraConf_FileEmpty_test( app ): - filepath = PathToTestFile( 'extra_confs', 'foo' ) + assert_that( None, equal_to( completer._project_directory ) ) - completer = MockCompleter() - request_data = RequestWrap( BuildRequest( filepath = filepath, - filetype = 'ycmtest', - contents = '' ) ) - completer.OnFileReadyToParse( request_data ) - assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) + completer.OnFileReadyToParse( request_data ) + assert_that( completer._project_directory, is_not( None ) ) + assert_that( completer._settings.get( 'ls', {} ), is_not( empty() ) ) - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} - } - } - completer._HandleInitializeInPollThread( initialize_response ) - assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) - # We shouldn't have used the extra_conf path for the project directory, but - # that _also_ happens to be the path of the file we opened. - assert_that( PathToTestFile( 'extra_confs' ), - equal_to( completer._project_directory ) ) - - -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'extra_confs', - 'settings_none_extra_conf.py' ) } ) -def LanguageServerCompleter_ExtraConf_SettingsReturnsNone_test( app ): - filepath = PathToTestFile( 'extra_confs', 'foo' ) - - completer = MockCompleter() - request_data = RequestWrap( BuildRequest( filepath = filepath, - filetype = 'ycmtest', - contents = '' ) ) - completer.OnFileReadyToParse( request_data ) - assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) - # We shouldn't have used the extra_conf path for the project directory, but - # that _also_ happens to be the path of the file we opened. - assert_that( PathToTestFile( 'extra_confs' ), - equal_to( completer._project_directory ) ) - - -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'extra_confs', 'settings_extra_conf.py' ) } ) -def LanguageServerCompleter_ExtraConf_SettingValid_test( app ): - filepath = PathToTestFile( 'extra_confs', 'foo' ) - - completer = MockCompleter() - request_data = RequestWrap( BuildRequest( filepath = filepath, - filetype = 'ycmtest', - working_dir = PathToTestFile(), - contents = '' ) ) - - assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) - completer.OnFileReadyToParse( request_data ) - assert_that( { 'java.rename.enabled' : False }, - equal_to( completer._settings.get( 'ls', {} ) ) ) - # We use the working_dir not the path to the global extra conf (which is - # ignored) - assert_that( PathToTestFile(), equal_to( completer._project_directory ) ) - - -@IsolatedYcmd( { 'extra_conf_globlist': [ '!*' ] } ) -def LanguageServerCompleter_ExtraConf_NoExtraConf_test( app ): - filepath = PathToTestFile( 'extra_confs', 'foo' ) - - completer = MockCompleter() - request_data = RequestWrap( BuildRequest( filepath = filepath, - filetype = 'ycmtest', - working_dir = PathToTestFile(), - contents = '' ) ) - - assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) - completer.OnFileReadyToParse( request_data ) - assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) - - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} - } - } - completer._HandleInitializeInPollThread( initialize_response ) - assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) - # We use the client working directory - assert_that( PathToTestFile(), equal_to( completer._project_directory ) ) - - -@IsolatedYcmd( { 'extra_conf_globlist': [ '*' ] } ) -def LanguageServerCompleter_ExtraConf_NonGlobal_test( app ): - filepath = PathToTestFile( 'project', - 'settings_extra_conf', - 'foo' ) - - completer = MockCompleter() - request_data = RequestWrap( BuildRequest( filepath = filepath, - filetype = 'ycmtest', - # ignored; ycm conf path used - working_dir = 'ignore_this', - contents = '' ) ) - - assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) - completer.OnFileReadyToParse( request_data ) - assert_that( { 'java.rename.enabled' : False }, - equal_to( completer._settings.get( 'ls', {} ) ) ) - - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} - } - } - completer._HandleInitializeInPollThread( initialize_response ) - assert_that( PathToTestFile( 'project', 'settings_extra_conf' ), - equal_to( completer._project_directory ) ) + completer.ServerReset() + assert_that( completer._settings.get( 'ls', {} ), empty() ) + assert_that( None, equal_to( completer._project_directory ) ) -@IsolatedYcmd() -def LanguageServerCompleter_Initialise_Aborted_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest() ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'extra_confs', 'empty_extra_conf.py' ) } ) + def test_LanguageServerCompleter_ExtraConf_FileEmpty( self, app ): + filepath = PathToTestFile( 'extra_confs', 'foo' ) - with patch.object( completer.GetConnection(), - 'ReadData', - side_effect = RuntimeError ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest( filepath = filepath, + filetype = 'ycmtest', + contents = '' ) ) + completer.OnFileReadyToParse( request_data ) + assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) - assert_that( completer.ServerIsReady(), equal_to( False ) ) + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } + } + completer._HandleInitializeInPollThread( initialize_response ) + assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) + # We shouldn't have used the extra_conf path for the project directory, but + # that _also_ happens to be the path of the file we opened. + assert_that( PathToTestFile( 'extra_confs' ), + equal_to( completer._project_directory ) ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'extra_confs', + 'settings_none_extra_conf.py' ) } ) + def test_LanguageServerCompleter_ExtraConf_SettingsReturnsNone( self, app ): + filepath = PathToTestFile( 'extra_confs', 'foo' ) + + completer = MockCompleter() + request_data = RequestWrap( BuildRequest( filepath = filepath, + filetype = 'ycmtest', + contents = '' ) ) + completer.OnFileReadyToParse( request_data ) + assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) + # We shouldn't have used the extra_conf path for the project directory, but + # that _also_ happens to be the path of the file we opened. + assert_that( PathToTestFile( 'extra_confs' ), + equal_to( completer._project_directory ) ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'extra_confs', 'settings_extra_conf.py' ) } ) + def test_LanguageServerCompleter_ExtraConf_SettingValid( self, app ): + filepath = PathToTestFile( 'extra_confs', 'foo' ) + completer = MockCompleter() + request_data = RequestWrap( BuildRequest( filepath = filepath, + filetype = 'ycmtest', + working_dir = PathToTestFile(), + contents = '' ) ) + + assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) completer.OnFileReadyToParse( request_data ) + assert_that( { 'java.rename.enabled' : False }, + equal_to( completer._settings.get( 'ls', {} ) ) ) + # We use the working_dir not the path to the global extra conf (which is + # ignored) + assert_that( PathToTestFile(), equal_to( completer._project_directory ) ) - with patch.object( completer, '_HandleInitializeInPollThread' ) as handler: - completer.GetConnection().run() - handler.assert_not_called() - assert_that( completer._initialize_event.is_set(), equal_to( False ) ) - assert_that( completer.ServerIsReady(), equal_to( False ) ) + @IsolatedYcmd( { 'extra_conf_globlist': [ '!*' ] } ) + def test_LanguageServerCompleter_ExtraConf_NoExtraConf( self, app ): + filepath = PathToTestFile( 'extra_confs', 'foo' ) + completer = MockCompleter() + request_data = RequestWrap( BuildRequest( filepath = filepath, + filetype = 'ycmtest', + working_dir = PathToTestFile(), + contents = '' ) ) - with patch.object( completer, 'ServerIsHealthy', return_value = False ): - assert_that( completer.ServerIsReady(), equal_to( False ) ) + assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) + completer.OnFileReadyToParse( request_data ) + assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } + } + completer._HandleInitializeInPollThread( initialize_response ) + assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) + # We use the client working directory + assert_that( PathToTestFile(), equal_to( completer._project_directory ) ) -@IsolatedYcmd() -def LanguageServerCompleter_Initialise_Shutdown_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest() ) - with patch.object( completer.GetConnection(), - 'ReadData', - side_effect = lsc.LanguageServerConnectionStopped ): + @IsolatedYcmd( { 'extra_conf_globlist': [ '*' ] } ) + def test_LanguageServerCompleter_ExtraConf_NonGlobal( self, app ): + filepath = PathToTestFile( 'project', + 'settings_extra_conf', + 'foo' ) - assert_that( completer.ServerIsReady(), equal_to( False ) ) + completer = MockCompleter() + request_data = RequestWrap( BuildRequest( filepath = filepath, + filetype = 'ycmtest', + # ignored; ycm conf path used + working_dir = 'ignore_this', + contents = '' ) ) + assert_that( {}, equal_to( completer._settings.get( 'ls', {} ) ) ) completer.OnFileReadyToParse( request_data ) + assert_that( { 'java.rename.enabled' : False }, + equal_to( completer._settings.get( 'ls', {} ) ) ) + + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } + } + completer._HandleInitializeInPollThread( initialize_response ) + assert_that( PathToTestFile( 'project', 'settings_extra_conf' ), + equal_to( completer._project_directory ) ) + + + @IsolatedYcmd() + def test_LanguageServerCompleter_Initialise_Aborted( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest() ) - with patch.object( completer, '_HandleInitializeInPollThread' ) as handler: - completer.GetConnection().run() - handler.assert_not_called() - - assert_that( completer._initialize_event.is_set(), equal_to( False ) ) - assert_that( completer.ServerIsReady(), equal_to( False ) ) - - - with patch.object( completer, 'ServerIsHealthy', return_value = False ): - assert_that( completer.ServerIsReady(), equal_to( False ) ) - - -@IsolatedYcmd() -def LanguageServerCompleter_GoTo_test( app ): - if utils.OnWindows(): - filepath = 'C:\\test.test' - uri = 'file:///c:/test.test' - else: - filepath = '/test.test' - uri = 'file:/test.test' - - contents = 'line1\nline2\nline3' - - completer = MockCompleter() - # LSP server supports all code navigation features. - completer._server_capabilities = { - 'definitionProvider': True, - 'declarationProvider': True, - 'typeDefinitionProvider': True, - 'implementationProvider': True, - 'referencesProvider': True - } - request_data = RequestWrap( BuildRequest( - filetype = 'ycmtest', - filepath = filepath, - contents = contents, - line_num = 2, - column_num = 3 - ) ) - - @patch.object( completer, '_ServerIsInitialized', return_value = True ) - def Test( responses, command, exception, throws, *args ): with patch.object( completer.GetConnection(), - 'GetResponse', - side_effect = responses ): - if throws: - assert_that( - calling( completer.OnUserCommand ).with_args( [ command ], - request_data ), - raises( exception ) - ) - else: - result = completer.OnUserCommand( [ command ], request_data ) - print( f'Result: { result }' ) - assert_that( result, exception ) + 'ReadData', + side_effect = RuntimeError ): + + assert_that( completer.ServerIsReady(), equal_to( False ) ) + + completer.OnFileReadyToParse( request_data ) + + with patch.object( completer, + '_HandleInitializeInPollThread' ) as handler: + completer.GetConnection().run() + handler.assert_not_called() + + assert_that( completer._initialize_event.is_set(), equal_to( False ) ) + assert_that( completer.ServerIsReady(), equal_to( False ) ) + + + with patch.object( completer, 'ServerIsHealthy', return_value = False ): + assert_that( completer.ServerIsReady(), equal_to( False ) ) + + + @IsolatedYcmd() + def test_LanguageServerCompleter_Initialise_Shutdown( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest() ) + + with patch.object( completer.GetConnection(), + 'ReadData', + side_effect = lsc.LanguageServerConnectionStopped ): + + assert_that( completer.ServerIsReady(), equal_to( False ) ) + + completer.OnFileReadyToParse( request_data ) + + with patch.object( completer, + '_HandleInitializeInPollThread' ) as handler: + completer.GetConnection().run() + handler.assert_not_called() + + assert_that( completer._initialize_event.is_set(), equal_to( False ) ) + assert_that( completer.ServerIsReady(), equal_to( False ) ) + + with patch.object( completer, 'ServerIsHealthy', return_value = False ): + assert_that( completer.ServerIsReady(), equal_to( False ) ) - location = { - 'uri': uri, - 'range': { - 'start': { 'line': 0, 'character': 0 }, - 'end': { 'line': 0, 'character': 0 }, + + @IsolatedYcmd() + def test_LanguageServerCompleter_GoTo( self, app ): + if utils.OnWindows(): + filepath = 'C:\\test.test' + uri = 'file:///c:/test.test' + else: + filepath = '/test.test' + uri = 'file:/test.test' + + contents = 'line1\nline2\nline3' + + completer = MockCompleter() + # LSP server supports all code navigation features. + completer._server_capabilities = { + 'definitionProvider': True, + 'declarationProvider': True, + 'typeDefinitionProvider': True, + 'implementationProvider': True, + 'referencesProvider': True + } + request_data = RequestWrap( BuildRequest( + filetype = 'ycmtest', + filepath = filepath, + contents = contents, + line_num = 2, + column_num = 3 + ) ) + + @patch.object( completer, '_ServerIsInitialized', return_value = True ) + def Test( responses, command, exception, throws, *args ): + with patch.object( completer.GetConnection(), + 'GetResponse', + side_effect = responses ): + if throws: + assert_that( + calling( completer.OnUserCommand ).with_args( [ command ], + request_data ), + raises( exception ) + ) + else: + result = completer.OnUserCommand( [ command ], request_data ) + print( f'Result: { result }' ) + assert_that( result, exception ) + + + location = { + 'uri': uri, + 'range': { + 'start': { 'line': 0, 'character': 0 }, + 'end': { 'line': 0, 'character': 0 }, + } } - } - - goto_response = has_entries( { - 'filepath': filepath, - 'column_num': 1, - 'line_num': 1, - 'description': 'line1' - } ) - - cases = [ - ( [ { 'result': None } ], 'GoToDefinition', RuntimeError, True ), - ( [ { 'result': location } ], 'GoToDeclaration', goto_response, False ), - ( [ { 'result': {} } ], 'GoToType', RuntimeError, True ), - ( [ { 'result': [] } ], 'GoToImplementation', RuntimeError, True ), - ( [ { 'result': [ location ] } ], 'GoToReferences', goto_response, False ), - ( [ { 'result': [ location, location ] } ], - 'GoToReferences', - contains_exactly( goto_response, goto_response ), - False ), - ] - - for response, goto_handlers, exception, throws in cases: - Test( response, goto_handlers, exception, throws ) - - - # All requests return an invalid URI. - with patch( - 'ycmd.completers.language_server.language_server_protocol.UriToFilePath', - side_effect = lsp.InvalidUriException ): + + goto_response = has_entries( { + 'filepath': filepath, + 'column_num': 1, + 'line_num': 1, + 'description': 'line1' + } ) + + cases = [ + ( [ { 'result': None } ], 'GoToDefinition', RuntimeError, True ), + ( [ { 'result': location } ], 'GoToDeclaration', goto_response, False ), + ( [ { 'result': {} } ], 'GoToType', RuntimeError, True ), + ( [ { 'result': [] } ], 'GoToImplementation', RuntimeError, True ), + ( [ { 'result': [ location ] } ], + 'GoToReferences', goto_response, False ), + ( [ { 'result': [ location, location ] } ], + 'GoToReferences', + contains_exactly( goto_response, goto_response ), + False ), + ] + + for response, goto_handlers, exception, throws in cases: + Test( response, goto_handlers, exception, throws ) + + + # All requests return an invalid URI. + with patch( + 'ycmd.completers.language_server.language_server_protocol.UriToFilePath', + side_effect = lsp.InvalidUriException ): + Test( [ { + 'result': { + 'uri': uri, + 'range': { + 'start': { 'line': 0, 'character': 0 }, + 'end': { 'line': 0, 'character': 0 } } + } + } ], 'GoTo', LocationMatcher( '', 1, 1 ), False ) + + with patch( 'ycmd.completers.completer_utils.GetFileContents', + side_effect = IOError ): + Test( [ { + 'result': { + 'uri': uri, + 'range': { + 'start': { 'line': 0, 'character': 0 }, + 'end': { 'line': 0, 'character': 0 } } + } + } ], 'GoToDefinition', LocationMatcher( filepath, 1, 1 ), False ) + + # Both requests return the location where the cursor is. Test( [ { 'result': { + 'uri': uri, + 'range': { + 'start': { 'line': 1, 'character': 0 }, + 'end': { 'line': 1, 'character': 4 } } + } + }, { + 'result': { + 'uri': uri, + 'range': { + 'start': { 'line': 1, 'character': 0 }, + 'end': { 'line': 1, 'character': 4 }, + } + } + } ], 'GoTo', LocationMatcher( filepath, 2, 1 ), False ) + + # First request returns two locations. + Test( [ { + 'result': [ { 'uri': uri, 'range': { 'start': { 'line': 0, 'character': 0 }, - 'end': { 'line': 0, 'character': 0 } } + 'end': { 'line': 0, 'character': 4 } } + }, { + 'uri': uri, + 'range': { + 'start': { 'line': 1, 'character': 0 }, + 'end': { 'line': 1, 'character': 4 }, + } + } ], + } ], 'GoTo', contains_exactly( + LocationMatcher( filepath, 1, 1 ), + LocationMatcher( filepath, 2, 1 ) + ), False ) + + # First request returns the location where the cursor is and second request + # returns a different URI. + if utils.OnWindows(): + other_filepath = 'C:\\another.test' + other_uri = 'file:///c:/another.test' + else: + other_filepath = '/another.test' + other_uri = 'file:/another.test' + + Test( [ { + 'result': { + 'uri': uri, + 'range': { + 'start': { 'line': 1, 'character': 0 }, + 'end': { 'line': 1, 'character': 4 } } } - } ], 'GoTo', LocationMatcher( '', 1, 1 ), False ) + }, { + 'result': { + 'uri': other_uri, + 'range': { + 'start': { 'line': 1, 'character': 0 }, + 'end': { 'line': 1, 'character': 4 }, + } + } + } ], 'GoTo', LocationMatcher( other_filepath, 2, 1 ), False ) - with patch( 'ycmd.completers.completer_utils.GetFileContents', - side_effect = IOError ): + # First request returns a location before the cursor. Test( [ { 'result': { 'uri': uri, 'range': { - 'start': { 'line': 0, 'character': 0 }, - 'end': { 'line': 0, 'character': 0 } } + 'start': { 'line': 0, 'character': 1 }, + 'end': { 'line': 1, 'character': 1 } } } - } ], 'GoToDefinition', LocationMatcher( filepath, 1, 1 ), False ) + } ], 'GoTo', LocationMatcher( filepath, 1, 2 ), False ) - # Both requests return the location where the cursor is. - Test( [ { - 'result': { - 'uri': uri, - 'range': { - 'start': { 'line': 1, 'character': 0 }, - 'end': { 'line': 1, 'character': 4 } } - } - }, { - 'result': { - 'uri': uri, - 'range': { - 'start': { 'line': 1, 'character': 0 }, - 'end': { 'line': 1, 'character': 4 }, + # First request returns a location after the cursor. + Test( [ { + 'result': { + 'uri': uri, + 'range': { + 'start': { 'line': 1, 'character': 3 }, + 'end': { 'line': 2, 'character': 3 } } } - } - } ], 'GoTo', LocationMatcher( filepath, 2, 1 ), False ) + } ], 'GoTo', LocationMatcher( filepath, 2, 4 ), False ) - # First request returns two locations. - Test( [ { - 'result': [ { - 'uri': uri, - 'range': { - 'start': { 'line': 0, 'character': 0 }, - 'end': { 'line': 0, 'character': 4 } } - }, { - 'uri': uri, + + def test_GetCompletions_RejectInvalid( self ): + if utils.OnWindows(): + filepath = 'C:\\test.test' + else: + filepath = '/test.test' + + contents = 'line1.\nline2.\nline3.' + + request_data = RequestWrap( BuildRequest( + filetype = 'ycmtest', + filepath = filepath, + contents = contents, + line_num = 1, + column_num = 7 + ) ) + + text_edit = { + 'newText': 'blah', 'range': { - 'start': { 'line': 1, 'character': 0 }, - 'end': { 'line': 1, 'character': 4 }, + 'start': { 'line': 0, 'character': 6 }, + 'end': { 'line': 0, 'character': 6 }, } - } ], - } ], 'GoTo', contains_exactly( - LocationMatcher( filepath, 1, 1 ), - LocationMatcher( filepath, 2, 1 ) - ), False ) - - # First request returns the location where the cursor is and second request - # returns a different URI. - if utils.OnWindows(): - other_filepath = 'C:\\another.test' - other_uri = 'file:///c:/another.test' - else: - other_filepath = '/another.test' - other_uri = 'file:/another.test' - - Test( [ { - 'result': { - 'uri': uri, - 'range': { - 'start': { 'line': 1, 'character': 0 }, - 'end': { 'line': 1, 'character': 4 } } } - }, { - 'result': { - 'uri': other_uri, + + assert_that( lsc._GetCompletionItemStartCodepointOrReject( text_edit, + request_data ), + equal_to( 7 ) ) + + text_edit = { + 'newText': 'blah', 'range': { - 'start': { 'line': 1, 'character': 0 }, - 'end': { 'line': 1, 'character': 4 }, + 'start': { 'line': 0, 'character': 6 }, + 'end': { 'line': 1, 'character': 6 }, } } - } ], 'GoTo', LocationMatcher( other_filepath, 2, 1 ), False ) - # First request returns a location before the cursor. - Test( [ { - 'result': { - 'uri': uri, + assert_that( + calling( lsc._GetCompletionItemStartCodepointOrReject ).with_args( + text_edit, request_data ), + raises( lsc.IncompatibleCompletionException ) ) + + text_edit = { + 'newText': 'blah', 'range': { - 'start': { 'line': 0, 'character': 1 }, - 'end': { 'line': 1, 'character': 1 } } + 'start': { 'line': 0, 'character': 20 }, + 'end': { 'line': 0, 'character': 20 }, + } } - } ], 'GoTo', LocationMatcher( filepath, 1, 2 ), False ) - # First request returns a location after the cursor. - Test( [ { - 'result': { - 'uri': uri, + assert_that( + lsc._GetCompletionItemStartCodepointOrReject( text_edit, request_data ), + equal_to( 7 ) ) + + text_edit = { + 'newText': 'blah', 'range': { - 'start': { 'line': 1, 'character': 3 }, - 'end': { 'line': 2, 'character': 3 } } - } - } ], 'GoTo', LocationMatcher( filepath, 2, 4 ), False ) - - -def GetCompletions_RejectInvalid_test(): - if utils.OnWindows(): - filepath = 'C:\\test.test' - else: - filepath = '/test.test' - - contents = 'line1.\nline2.\nline3.' - - request_data = RequestWrap( BuildRequest( - filetype = 'ycmtest', - filepath = filepath, - contents = contents, - line_num = 1, - column_num = 7 - ) ) - - text_edit = { - 'newText': 'blah', - 'range': { - 'start': { 'line': 0, 'character': 6 }, - 'end': { 'line': 0, 'character': 6 }, + 'start': { 'line': 0, 'character': 6 }, + 'end': { 'line': 0, 'character': 5 }, + } } - } - assert_that( lsc._GetCompletionItemStartCodepointOrReject( text_edit, - request_data ), - equal_to( 7 ) ) + assert_that( + lsc._GetCompletionItemStartCodepointOrReject( text_edit, request_data ), + equal_to( 7 ) ) - text_edit = { - 'newText': 'blah', - 'range': { - 'start': { 'line': 0, 'character': 6 }, - 'end': { 'line': 1, 'character': 6 }, - } - } - - assert_that( - calling( lsc._GetCompletionItemStartCodepointOrReject ).with_args( - text_edit, request_data ), - raises( lsc.IncompatibleCompletionException ) ) - - text_edit = { - 'newText': 'blah', - 'range': { - 'start': { 'line': 0, 'character': 20 }, - 'end': { 'line': 0, 'character': 20 }, - } - } - assert_that( - lsc._GetCompletionItemStartCodepointOrReject( text_edit, request_data ), - equal_to( 7 ) ) + def test_WorkspaceEditToFixIt( self ): + if utils.OnWindows(): + filepath = 'C:\\test.test' + uri = 'file:///c:/test.test' + else: + filepath = '/test.test' + uri = 'file:/test.test' + + contents = 'line1\nline2\nline3' + + request_data = RequestWrap( BuildRequest( + filetype = 'ycmtest', + filepath = filepath, + contents = contents + ) ) + - text_edit = { - 'newText': 'blah', - 'range': { - 'start': { 'line': 0, 'character': 6 }, - 'end': { 'line': 0, 'character': 5 }, + # Null response to textDocument/codeActions is valid + assert_that( lsc.WorkspaceEditToFixIt( request_data, None ), + equal_to( None ) ) + # Empty WorkspaceEdit is not explicitly forbidden + assert_that( lsc.WorkspaceEditToFixIt( request_data, {} ), + equal_to( None ) ) + # We don't support versioned documentChanges + workspace_edit = { + 'documentChanges': [ + { + 'textDocument': { + 'version': 1, + 'uri': uri + }, + 'edits': [ + { + 'newText': 'blah', + 'range': { + 'start': { 'line': 0, 'character': 5 }, + 'end': { 'line': 0, 'character': 5 }, + } + } + ] + } + ] } - } - - assert_that( - lsc._GetCompletionItemStartCodepointOrReject( text_edit, request_data ), - equal_to( 7 ) ) - - -def WorkspaceEditToFixIt_test(): - if utils.OnWindows(): - filepath = 'C:\\test.test' - uri = 'file:///c:/test.test' - else: - filepath = '/test.test' - uri = 'file:/test.test' - - contents = 'line1\nline2\nline3' - - request_data = RequestWrap( BuildRequest( - filetype = 'ycmtest', - filepath = filepath, - contents = contents - ) ) - - - # Null response to textDocument/codeActions is valid - assert_that( lsc.WorkspaceEditToFixIt( request_data, None ), - equal_to( None ) ) - # Empty WorkspaceEdit is not explicitly forbidden - assert_that( lsc.WorkspaceEditToFixIt( request_data, {} ), equal_to( None ) ) - # We don't support versioned documentChanges - workspace_edit = { - 'documentChanges': [ - { - 'textDocument': { - 'version': 1, - 'uri': uri - }, - 'edits': [ + response = responses.BuildFixItResponse( [ + lsc.WorkspaceEditToFixIt( request_data, workspace_edit, 'test' ) + ] ) + + print( f'Response: { response }' ) + assert_that( + response, + has_entries( { + 'fixits': contains_exactly( has_entries( { + 'text': 'test', + 'chunks': contains_exactly( + ChunkMatcher( 'blah', + LocationMatcher( filepath, 1, 6 ), + LocationMatcher( filepath, 1, 6 ) ) ) + } ) ) + } ) + ) + + workspace_edit = { + 'changes': { + uri: [ { 'newText': 'blah', 'range': { 'start': { 'line': 0, 'character': 5 }, 'end': { 'line': 0, 'character': 5 }, } - } + }, ] } - ] - } - response = responses.BuildFixItResponse( [ - lsc.WorkspaceEditToFixIt( request_data, workspace_edit, 'test' ) - ] ) - - print( f'Response: { response }' ) - assert_that( - response, - has_entries( { - 'fixits': contains_exactly( has_entries( { - 'text': 'test', - 'chunks': contains_exactly( ChunkMatcher( 'blah', - LocationMatcher( filepath, 1, 6 ), - LocationMatcher( filepath, 1, 6 ) ) ) - } ) ) - } ) - ) - - workspace_edit = { - 'changes': { - uri: [ - { - 'newText': 'blah', - 'range': { - 'start': { 'line': 0, 'character': 5 }, - 'end': { 'line': 0, 'character': 5 }, - } - }, - ] } - } - - response = responses.BuildFixItResponse( [ - lsc.WorkspaceEditToFixIt( request_data, workspace_edit, 'test' ) - ] ) - - print( f'Response: { response }' ) - print( f'Type Response: { type( response ) }' ) - - assert_that( - response, - has_entries( { - 'fixits': contains_exactly( has_entries( { - 'text': 'test', - 'chunks': contains_exactly( ChunkMatcher( 'blah', - LocationMatcher( filepath, 1, 6 ), - LocationMatcher( filepath, 1, 6 ) ) ) - } ) ) - } ) - ) + response = responses.BuildFixItResponse( [ + lsc.WorkspaceEditToFixIt( request_data, workspace_edit, 'test' ) + ] ) -@IsolatedYcmd( { 'extra_conf_globlist': [ '!*' ] } ) -def LanguageServerCompleter_DelayedInitialization_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest( filepath = 'Test.ycmtest' ) ) + print( f'Response: { response }' ) + print( f'Type Response: { type( response ) }' ) - with patch.object( completer, '_UpdateServerWithFileContents' ) as update: - with patch.object( completer, '_PurgeFileFromServer' ) as purge: - completer.OnFileReadyToParse( request_data ) - completer.OnBufferUnload( request_data ) - update.assert_not_called() - purge.assert_not_called() + assert_that( + response, + has_entries( { + 'fixits': contains_exactly( has_entries( { + 'text': 'test', + 'chunks': contains_exactly( + ChunkMatcher( 'blah', + LocationMatcher( filepath, 1, 6 ), + LocationMatcher( filepath, 1, 6 ) ) ) + } ) ) + } ) + ) - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} - } - } - completer._HandleInitializeInPollThread( initialize_response ) - update.assert_called_with( request_data ) - purge.assert_called_with( 'Test.ycmtest' ) + @IsolatedYcmd( { 'extra_conf_globlist': [ '!*' ] } ) + def test_LanguageServerCompleter_DelayedInitialization( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest( filepath = 'Test.ycmtest' ) ) + with patch.object( completer, '_UpdateServerWithFileContents' ) as update: + with patch.object( completer, '_PurgeFileFromServer' ) as purge: + completer.OnFileReadyToParse( request_data ) + completer.OnBufferUnload( request_data ) + update.assert_not_called() + purge.assert_not_called() -@IsolatedYcmd() -def LanguageServerCompleter_RejectWorkspaceConfigurationRequest_test( app ): - completer = MockCompleter() - notification = { - 'jsonrpc': '2.0', - 'method': 'workspace/configuration', - 'id': 1234, - 'params': { - 'items': [ { 'section': 'whatever' } ] + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } + } + completer._HandleInitializeInPollThread( initialize_response ) + + update.assert_called_with( request_data ) + purge.assert_called_with( 'Test.ycmtest' ) + + + @IsolatedYcmd() + def test_LanguageServerCompleter_RejectWorkspaceConfigurationRequest( + self, app ): + completer = MockCompleter() + notification = { + 'jsonrpc': '2.0', + 'method': 'workspace/configuration', + 'id': 1234, + 'params': { + 'items': [ { 'section': 'whatever' } ] + } } - } - with patch( 'ycmd.completers.language_server.' - 'language_server_protocol.Reject' ) as reject: - completer.GetConnection()._DispatchMessage( notification ) - reject.assert_called_with( notification, lsp.Errors.MethodNotFound ) - - -@IsolatedYcmd() -def LanguageServerCompleter_ShowMessage_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest() ) - notification = { - 'method': 'window/showMessage', - 'params': { - 'message': 'this is a test' + with patch( 'ycmd.completers.language_server.' + 'language_server_protocol.Reject' ) as reject: + completer.GetConnection()._DispatchMessage( notification ) + reject.assert_called_with( notification, lsp.Errors.MethodNotFound ) + + + @IsolatedYcmd() + def test_LanguageServerCompleter_ShowMessage( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest() ) + notification = { + 'method': 'window/showMessage', + 'params': { + 'message': 'this is a test' + } } - } - assert_that( completer.ConvertNotificationToMessage( request_data, - notification ), - has_entries( { 'message': 'this is a test' } ) ) + assert_that( completer.ConvertNotificationToMessage( request_data, + notification ), + has_entries( { 'message': 'this is a test' } ) ) -@IsolatedYcmd() -def LanguageServerCompleter_GetCompletions_List_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest() ) + @IsolatedYcmd() + def test_LanguageServerCompleter_GetCompletions_List( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest() ) - completion_response = { 'result': [ { 'label': 'test' } ] } + completion_response = { 'result': [ { 'label': 'test' } ] } - resolve_responses = [ - { 'result': { 'label': 'test' } }, - ] + resolve_responses = [ + { 'result': { 'label': 'test' } }, + ] - with patch.object( completer, '_is_completion_provider', True ): - with patch.object( completer.GetConnection(), - 'GetResponse', - side_effect = [ completion_response ] + - resolve_responses ): - assert_that( - completer.ComputeCandidatesInner( request_data, 1 ), - contains_exactly( - has_items( has_entries( { 'insertion_text': 'test' } ) ), - False + with patch.object( completer, '_is_completion_provider', True ): + with patch.object( completer.GetConnection(), + 'GetResponse', + side_effect = [ completion_response ] + + resolve_responses ): + assert_that( + completer.ComputeCandidatesInner( request_data, 1 ), + contains_exactly( + has_items( has_entries( { 'insertion_text': 'test' } ) ), + False + ) ) - ) -@IsolatedYcmd() -def LanguageServerCompleter_GetCompletions_UnsupportedKinds_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest() ) + @IsolatedYcmd() + def test_LanguageServerCompleter_GetCompletions_UnsupportedKinds( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest() ) - completion_response = { 'result': [ { 'label': 'test', - 'kind': len( lsp.ITEM_KIND ) + 1 } ] } - - resolve_responses = [ - { 'result': { 'label': 'test' } }, - ] - - with patch.object( completer, '_is_completion_provider', True ): - with patch.object( completer.GetConnection(), - 'GetResponse', - side_effect = [ completion_response ] + - resolve_responses ): - assert_that( - completer.ComputeCandidatesInner( request_data, 1 ), - contains_exactly( - has_items( all_of( has_entry( 'insertion_text', 'test' ), - is_not( has_key( 'kind' ) ) ) ), - False - ) - ) + completion_response = { 'result': [ { 'label': 'test', + 'kind': len( lsp.ITEM_KIND ) + 1 } ] } + resolve_responses = [ + { 'result': { 'label': 'test' } }, + ] -@IsolatedYcmd() -def LanguageServerCompleter_GetCompletions_NullNoError_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest() ) - complete_response = { 'result': None } - resolve_responses = [] - with patch.object( completer, '_ServerIsInitialized', return_value = True ): - with patch.object( completer, - '_is_completion_provider', - return_value = True ): + with patch.object( completer, '_is_completion_provider', True ): with patch.object( completer.GetConnection(), 'GetResponse', - side_effect = [ complete_response ] + + side_effect = [ completion_response ] + resolve_responses ): assert_that( completer.ComputeCandidatesInner( request_data, 1 ), contains_exactly( - empty(), + has_items( all_of( has_entry( 'insertion_text', 'test' ), + is_not( has_key( 'kind' ) ) ) ), False ) ) -@IsolatedYcmd() -def LanguageServerCompleter_GetCompletions_CompleteOnStartColumn_test( app ): - completer = MockCompleter() - completer._resolve_completion_items = False - complete_response = { - 'result': { - 'items': [ - { 'label': 'aa' }, - { 'label': 'ac' }, - { 'label': 'ab' } - ], - 'isIncomplete': False + @IsolatedYcmd() + def test_LanguageServerCompleter_GetCompletions_NullNoError( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest() ) + complete_response = { 'result': None } + resolve_responses = [] + with patch.object( completer, '_ServerIsInitialized', return_value = True ): + with patch.object( completer, + '_is_completion_provider', + return_value = True ): + with patch.object( completer.GetConnection(), + 'GetResponse', + side_effect = [ complete_response ] + + resolve_responses ): + assert_that( + completer.ComputeCandidatesInner( request_data, 1 ), + contains_exactly( + empty(), + False + ) + ) + + + @IsolatedYcmd() + def test_LanguageServerCompleter_GetCompletions_CompleteOnStartColumn( + self, app ): + completer = MockCompleter() + completer._resolve_completion_items = False + complete_response = { + 'result': { + 'items': [ + { 'label': 'aa' }, + { 'label': 'ac' }, + { 'label': 'ab' } + ], + 'isIncomplete': False + } } - } - with patch.object( completer, '_is_completion_provider', True ): - request_data = RequestWrap( BuildRequest( - column_num = 2, - contents = 'a', - force_semantic = True - ) ) + with patch.object( completer, '_is_completion_provider', True ): + request_data = RequestWrap( BuildRequest( + column_num = 2, + contents = 'a', + force_semantic = True + ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - return_value = complete_response ) as response: - assert_that( - completer.ComputeCandidates( request_data ), - contains_exactly( - has_entry( 'insertion_text', 'aa' ), - has_entry( 'insertion_text', 'ab' ), - has_entry( 'insertion_text', 'ac' ) + with patch.object( completer.GetConnection(), + 'GetResponse', + return_value = complete_response ) as response: + assert_that( + completer.ComputeCandidates( request_data ), + contains_exactly( + has_entry( 'insertion_text', 'aa' ), + has_entry( 'insertion_text', 'ab' ), + has_entry( 'insertion_text', 'ac' ) + ) ) - ) - # Nothing cached yet. - assert_that( response.call_count, equal_to( 1 ) ) + # Nothing cached yet. + assert_that( response.call_count, equal_to( 1 ) ) - request_data = RequestWrap( BuildRequest( - column_num = 3, - contents = 'ab', - force_semantic = True - ) ) + request_data = RequestWrap( BuildRequest( + column_num = 3, + contents = 'ab', + force_semantic = True + ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - return_value = complete_response ) as response: - assert_that( - completer.ComputeCandidates( request_data ), - contains_exactly( - has_entry( 'insertion_text', 'ab' ) + with patch.object( completer.GetConnection(), + 'GetResponse', + return_value = complete_response ) as response: + assert_that( + completer.ComputeCandidates( request_data ), + contains_exactly( + has_entry( 'insertion_text', 'ab' ) + ) ) - ) - # Since the server returned a complete list of completions on the starting - # column, no request should be sent to the server and the cache should be - # used instead. - assert_that( response.call_count, equal_to( 0 ) ) - - -@IsolatedYcmd() -def LanguageServerCompleter_GetCompletions_CompleteOnCurrentColumn_test( app ): - completer = MockCompleter() - completer._resolve_completion_items = False - - a_response = { - 'result': { - 'items': [ - { 'label': 'aba' }, - { 'label': 'aab' }, - { 'label': 'aaa' } - ], - 'isIncomplete': True + # Since the server returned a complete list of completions on the + # starting column, no request should be sent to the server and the + # cache should be used instead. + assert_that( response.call_count, equal_to( 0 ) ) + + + @IsolatedYcmd() + def test_LanguageServerCompleter_GetCompletions_CompleteOnCurrentColumn( + self, app ): + completer = MockCompleter() + completer._resolve_completion_items = False + + a_response = { + 'result': { + 'items': [ + { 'label': 'aba' }, + { 'label': 'aab' }, + { 'label': 'aaa' } + ], + 'isIncomplete': True + } } - } - aa_response = { - 'result': { - 'items': [ - { 'label': 'aab' }, - { 'label': 'aaa' } - ], - 'isIncomplete': False + aa_response = { + 'result': { + 'items': [ + { 'label': 'aab' }, + { 'label': 'aaa' } + ], + 'isIncomplete': False + } } - } - aaa_response = { - 'result': { - 'items': [ - { 'label': 'aaa' } - ], - 'isIncomplete': False + aaa_response = { + 'result': { + 'items': [ + { 'label': 'aaa' } + ], + 'isIncomplete': False + } } - } - ab_response = { - 'result': { - 'items': [ - { 'label': 'abb' }, - { 'label': 'aba' } - ], - 'isIncomplete': False + ab_response = { + 'result': { + 'items': [ + { 'label': 'abb' }, + { 'label': 'aba' } + ], + 'isIncomplete': False + } } - } - with patch.object( completer, '_is_completion_provider', True ): - # User starts by typing the character "a". - request_data = RequestWrap( BuildRequest( - column_num = 2, - contents = 'a', - force_semantic = True - ) ) + with patch.object( completer, '_is_completion_provider', True ): + # User starts by typing the character "a". + request_data = RequestWrap( BuildRequest( + column_num = 2, + contents = 'a', + force_semantic = True + ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - return_value = a_response ) as response: - assert_that( - completer.ComputeCandidates( request_data ), - contains_exactly( - has_entry( 'insertion_text', 'aaa' ), - has_entry( 'insertion_text', 'aab' ), - has_entry( 'insertion_text', 'aba' ) + with patch.object( completer.GetConnection(), + 'GetResponse', + return_value = a_response ) as response: + assert_that( + completer.ComputeCandidates( request_data ), + contains_exactly( + has_entry( 'insertion_text', 'aaa' ), + has_entry( 'insertion_text', 'aab' ), + has_entry( 'insertion_text', 'aba' ) + ) ) - ) - # Nothing cached yet. - assert_that( response.call_count, equal_to( 1 ) ) + # Nothing cached yet. + assert_that( response.call_count, equal_to( 1 ) ) - # User types again the character "a". - request_data = RequestWrap( BuildRequest( - column_num = 3, - contents = 'aa', - force_semantic = True - ) ) + # User types again the character "a". + request_data = RequestWrap( BuildRequest( + column_num = 3, + contents = 'aa', + force_semantic = True + ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - return_value = aa_response ) as response: - assert_that( - completer.ComputeCandidates( request_data ), - contains_exactly( - has_entry( 'insertion_text', 'aaa' ), - has_entry( 'insertion_text', 'aab' ) + with patch.object( completer.GetConnection(), + 'GetResponse', + return_value = aa_response ) as response: + assert_that( + completer.ComputeCandidates( request_data ), + contains_exactly( + has_entry( 'insertion_text', 'aaa' ), + has_entry( 'insertion_text', 'aab' ) + ) ) - ) - # The server returned an incomplete list of completions the first time so - # a new completion request should have been sent. - assert_that( response.call_count, equal_to( 1 ) ) + # The server returned an incomplete list of completions the first time + # so a new completion request should have been sent. + assert_that( response.call_count, equal_to( 1 ) ) - # User types the character "a" a third time. - request_data = RequestWrap( BuildRequest( - column_num = 4, - contents = 'aaa', - force_semantic = True - ) ) + # User types the character "a" a third time. + request_data = RequestWrap( BuildRequest( + column_num = 4, + contents = 'aaa', + force_semantic = True + ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - return_value = aaa_response ) as response: + with patch.object( completer.GetConnection(), + 'GetResponse', + return_value = aaa_response ) as response: - assert_that( - completer.ComputeCandidates( request_data ), - contains_exactly( - has_entry( 'insertion_text', 'aaa' ) + assert_that( + completer.ComputeCandidates( request_data ), + contains_exactly( + has_entry( 'insertion_text', 'aaa' ) + ) ) - ) - # The server returned a complete list of completions the second time and - # the new query is a prefix of the cached one ("aa" is a prefix of "aaa") - # so the cache should be used. - assert_that( response.call_count, equal_to( 0 ) ) + # The server returned a complete list of completions the second time + # and the new query is a prefix of the cached one ("aa" is a prefix of + # "aaa") so the cache should be used. + assert_that( response.call_count, equal_to( 0 ) ) - # User deletes the third character. - request_data = RequestWrap( BuildRequest( - column_num = 3, - contents = 'aa', - force_semantic = True - ) ) + # User deletes the third character. + request_data = RequestWrap( BuildRequest( + column_num = 3, + contents = 'aa', + force_semantic = True + ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - return_value = aa_response ) as response: + with patch.object( completer.GetConnection(), + 'GetResponse', + return_value = aa_response ) as response: - assert_that( - completer.ComputeCandidates( request_data ), - contains_exactly( - has_entry( 'insertion_text', 'aaa' ), - has_entry( 'insertion_text', 'aab' ) + assert_that( + completer.ComputeCandidates( request_data ), + contains_exactly( + has_entry( 'insertion_text', 'aaa' ), + has_entry( 'insertion_text', 'aab' ) + ) ) - ) - # The new query is still a prefix of the cached one ("aa" is a prefix of - # "aa") so the cache should again be used. - assert_that( response.call_count, equal_to( 0 ) ) + # The new query is still a prefix of the cached one ("aa" is a prefix of + # "aa") so the cache should again be used. + assert_that( response.call_count, equal_to( 0 ) ) - # User deletes the second character. - request_data = RequestWrap( BuildRequest( - column_num = 2, - contents = 'a', - force_semantic = True - ) ) + # User deletes the second character. + request_data = RequestWrap( BuildRequest( + column_num = 2, + contents = 'a', + force_semantic = True + ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - return_value = a_response ) as response: + with patch.object( completer.GetConnection(), + 'GetResponse', + return_value = a_response ) as response: - assert_that( - completer.ComputeCandidates( request_data ), - contains_exactly( - has_entry( 'insertion_text', 'aaa' ), - has_entry( 'insertion_text', 'aab' ), - has_entry( 'insertion_text', 'aba' ) + assert_that( + completer.ComputeCandidates( request_data ), + contains_exactly( + has_entry( 'insertion_text', 'aaa' ), + has_entry( 'insertion_text', 'aab' ), + has_entry( 'insertion_text', 'aba' ) + ) ) - ) - # The new query is not anymore a prefix of the cached one ("aa" is not a - # prefix of "a") so the cache is invalidated and a new request is sent. - assert_that( response.call_count, equal_to( 1 ) ) + # The new query is not anymore a prefix of the cached one ("aa" is not a + # prefix of "a") so the cache is invalidated and a new request is sent. + assert_that( response.call_count, equal_to( 1 ) ) - # Finally, user inserts the "b" character. - request_data = RequestWrap( BuildRequest( - column_num = 3, - contents = 'ab', - force_semantic = True - ) ) + # Finally, user inserts the "b" character. + request_data = RequestWrap( BuildRequest( + column_num = 3, + contents = 'ab', + force_semantic = True + ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - return_value = ab_response ) as response: - - assert_that( - completer.ComputeCandidates( request_data ), - contains_exactly( - has_entry( 'insertion_text', 'aba' ), - has_entry( 'insertion_text', 'abb' ) - ) - ) + with patch.object( completer.GetConnection(), + 'GetResponse', + return_value = ab_response ) as response: - # Last response was incomplete so the cache should not be used. - assert_that( response.call_count, equal_to( 1 ) ) - - -@pytest.mark.parametrize( 'line,text,overlap', [ - ( '', '', 0 ), - ( 'a', 'a', 1 ), - ( 'a', 'b', 0 ), - ( 'abcdef', 'abcdefg', 6 ), - ( 'abcdefg', 'abcdef', 0 ), - ( 'aaab', 'aaab', 4 ), - ( 'abab', 'ab', 2 ), - ( 'aab', 'caab', 0 ), - ( 'abab', 'abababab', 4 ), - ( 'aaab', 'baaa', 1 ), - ( 'test.', 'test.test', 5 ), - ( 'test.', 'test', 0 ), - ( 'test', 'testtest', 4 ), - ( '', 'testtest', 0 ), - ( 'test', '', 0 ), - ( 'Some CoCo', 'CoCo Beans', 4 ), - ( 'Have some CoCo and CoCo', 'CoCo and CoCo is here.', 13 ), - ( 'TEST xyAzA', 'xyAzA test', 5 ), - ] ) -def FindOverlapLength_test( line, text, overlap ): - assert_that( lsc.FindOverlapLength( line, text ), equal_to( overlap ) ) - - -@IsolatedYcmd() -def LanguageServerCompleter_GetCodeActions_CursorOnEmptyLine_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest( line_num = 1, - column_num = 1, - contents = '' ) ) - - fixit_response = { 'result': [] } - - with patch.object( completer, '_ServerIsInitialized', return_value = True ): - with patch.object( completer.GetConnection(), - 'GetResponse', - side_effect = [ fixit_response ] ): - with patch( 'ycmd.completers.language_server.language_server_protocol.' - 'CodeAction' ) as code_action: - assert_that( completer.GetCodeActions( request_data, [] ), - has_entry( 'fixits', empty() ) ) assert_that( - # Range passed to lsp.CodeAction. - # LSP requires to use the start of the next line as the end position - # for a range that ends with a newline. - code_action.call_args[ 0 ][ 2 ], - has_entries( { - 'start': has_entries( { - 'line': 0, - 'character': 0 - } ), - 'end': has_entries( { - 'line': 1, - 'character': 0 - } ) - } ) + completer.ComputeCandidates( request_data ), + contains_exactly( + has_entry( 'insertion_text', 'aba' ), + has_entry( 'insertion_text', 'abb' ) + ) ) + # Last response was incomplete so the cache should not be used. + assert_that( response.call_count, equal_to( 1 ) ) + + + def test_FindOverlapLength( self ): + for line, text, overlap in [ + ( '', '', 0 ), + ( 'a', 'a', 1 ), + ( 'a', 'b', 0 ), + ( 'abcdef', 'abcdefg', 6 ), + ( 'abcdefg', 'abcdef', 0 ), + ( 'aaab', 'aaab', 4 ), + ( 'abab', 'ab', 2 ), + ( 'aab', 'caab', 0 ), + ( 'abab', 'abababab', 4 ), + ( 'aaab', 'baaa', 1 ), + ( 'test.', 'test.test', 5 ), + ( 'test.', 'test', 0 ), + ( 'test', 'testtest', 4 ), + ( '', 'testtest', 0 ), + ( 'test', '', 0 ), + ( 'Some CoCo', 'CoCo Beans', 4 ), + ( 'Have some CoCo and CoCo', 'CoCo and CoCo is here.', 13 ), + ( 'TEST xyAzA', 'xyAzA test', 5 ), + ]: + with self.subTest( line = line, text = text, overlap = overlap ): + assert_that( lsc.FindOverlapLength( line, text ), equal_to( overlap ) ) + + + @IsolatedYcmd() + def test_LanguageServerCompleter_GetCodeActions_CursorOnEmptyLine( + self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest( line_num = 1, + column_num = 1, + contents = '' ) ) + + fixit_response = { 'result': [] } + + with patch.object( completer, '_ServerIsInitialized', return_value = True ): + with patch.object( completer.GetConnection(), + 'GetResponse', + side_effect = [ fixit_response ] ): + with patch( 'ycmd.completers.language_server.language_server_protocol.' + 'CodeAction' ) as code_action: + assert_that( completer.GetCodeActions( request_data, [] ), + has_entry( 'fixits', empty() ) ) + assert_that( + # Range passed to lsp.CodeAction. + # LSP requires to use the start of the next line as the end position + # for a range that ends with a newline. + code_action.call_args[ 0 ][ 2 ], + has_entries( { + 'start': has_entries( { + 'line': 0, + 'character': 0 + } ), + 'end': has_entries( { + 'line': 1, + 'character': 0 + } ) + } ) + ) -@IsolatedYcmd() -def LanguageServerCompleter_Diagnostics_MaxDiagnosticsNumberExceeded_test( - app ): - completer = MockCompleter( { 'max_diagnostics_to_display': 1 } ) - filepath = os.path.realpath( '/foo' ) - uri = lsp.FilePathToUri( filepath ) - request_data = RequestWrap( BuildRequest( line_num = 1, - column_num = 1, - filepath = filepath, - contents = '' ) ) - notification = { - 'jsonrpc': '2.0', - 'method': 'textDocument/publishDiagnostics', - 'params': { - 'uri': uri, - 'diagnostics': [ { - 'range': { - 'start': { 'line': 3, 'character': 10 }, - 'end': { 'line': 3, 'character': 11 } - }, - 'severity': 1, - 'message': 'First error' - }, { - 'range': { - 'start': { 'line': 4, 'character': 7 }, - 'end': { 'line': 4, 'character': 13 } - }, - 'severity': 1, - 'message': 'Second error [8]' - } ] - } - } - completer.GetConnection()._notifications.put( notification ) - completer.HandleNotificationInPollThread( notification ) - with patch.object( completer, '_ServerIsInitialized', return_value = True ): - completer.OnFileReadyToParse( request_data ) - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} + @IsolatedYcmd() + def test_LanguageServerCompleter_Diagnostics_MaxDiagnosticsNumberExceeded( + self, app ): + completer = MockCompleter( { 'max_diagnostics_to_display': 1 } ) + filepath = os.path.realpath( '/foo' ) + uri = lsp.FilePathToUri( filepath ) + request_data = RequestWrap( BuildRequest( line_num = 1, + column_num = 1, + filepath = filepath, + contents = '' ) ) + notification = { + 'jsonrpc': '2.0', + 'method': 'textDocument/publishDiagnostics', + 'params': { + 'uri': uri, + 'diagnostics': [ { + 'range': { + 'start': { 'line': 3, 'character': 10 }, + 'end': { 'line': 3, 'character': 11 } + }, + 'severity': 1, + 'message': 'First error' + }, { + 'range': { + 'start': { 'line': 4, 'character': 7 }, + 'end': { 'line': 4, 'character': 13 } + }, + 'severity': 1, + 'message': 'Second error [8]' + } ] } } - completer._HandleInitializeInPollThread( initialize_response ) + completer.GetConnection()._notifications.put( notification ) + completer.HandleNotificationInPollThread( notification ) - diagnostics = contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 4, 11 ), - 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), - 'text': equal_to( 'First error' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 1, 1 ), - 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) ), - 'text': equal_to( 'Maximum number of diagnostics exceeded.' ), - 'fixit_available': False - } ) - ) + with patch.object( completer, '_ServerIsInitialized', return_value = True ): + completer.OnFileReadyToParse( request_data ) + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } + } + completer._HandleInitializeInPollThread( initialize_response ) - assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) + diagnostics = contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 4, 11 ), + 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), + 'text': equal_to( 'First error' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 1, 1 ), + 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) ), + 'text': equal_to( 'Maximum number of diagnostics exceeded.' ), + 'fixit_available': False + } ) + ) - assert_that( - completer.PollForMessages( request_data ), - contains_exactly( has_entries( { - 'diagnostics': diagnostics, - 'filepath': filepath - } ) ) - ) + assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) + assert_that( + completer.PollForMessages( request_data ), + contains_exactly( has_entries( { + 'diagnostics': diagnostics, + 'filepath': filepath + } ) ) + ) -@IsolatedYcmd() -def LanguageServerCompleter_Diagnostics_NoLimitToNumberOfDiagnostics_test( - app ): - completer = MockCompleter( { 'max_diagnostics_to_display': 0 } ) - filepath = os.path.realpath( '/foo' ) - uri = lsp.FilePathToUri( filepath ) - request_data = RequestWrap( BuildRequest( line_num = 1, - column_num = 1, - filepath = filepath, - contents = '' ) ) - notification = { - 'jsonrpc': '2.0', - 'method': 'textDocument/publishDiagnostics', - 'params': { - 'uri': uri, - 'diagnostics': [ { - 'range': { - 'start': { 'line': 3, 'character': 10 }, - 'end': { 'line': 3, 'character': 11 } - }, - 'severity': 1, - 'message': 'First error' - }, { - 'range': { - 'start': { 'line': 4, 'character': 7 }, - 'end': { 'line': 4, 'character': 13 } - }, - 'severity': 1, - 'message': 'Second error' - } ] - } - } - completer.GetConnection()._notifications.put( notification ) - completer.HandleNotificationInPollThread( notification ) - with patch.object( completer, '_ServerIsInitialized', return_value = True ): - completer.OnFileReadyToParse( request_data ) - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} + @IsolatedYcmd() + def test_LanguageServerCompleter_Diagnostics_NoLimitToNumberOfDiagnostics( + self, app ): + completer = MockCompleter( { 'max_diagnostics_to_display': 0 } ) + filepath = os.path.realpath( '/foo' ) + uri = lsp.FilePathToUri( filepath ) + request_data = RequestWrap( BuildRequest( line_num = 1, + column_num = 1, + filepath = filepath, + contents = '' ) ) + notification = { + 'jsonrpc': '2.0', + 'method': 'textDocument/publishDiagnostics', + 'params': { + 'uri': uri, + 'diagnostics': [ { + 'range': { + 'start': { 'line': 3, 'character': 10 }, + 'end': { 'line': 3, 'character': 11 } + }, + 'severity': 1, + 'message': 'First error' + }, { + 'range': { + 'start': { 'line': 4, 'character': 7 }, + 'end': { 'line': 4, 'character': 13 } + }, + 'severity': 1, + 'message': 'Second error' + } ] } } - completer._HandleInitializeInPollThread( initialize_response ) + completer.GetConnection()._notifications.put( notification ) + completer.HandleNotificationInPollThread( notification ) - diagnostics = contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 4, 11 ), - 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), - 'text': equal_to( 'First error' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 5, 8 ), - 'location_extent': RangeMatcher( filepath, ( 5, 8 ), ( 5, 14 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 5, 8 ), ( 5, 14 ) ) ), - 'text': equal_to( 'Second error' ), - 'fixit_available': False - } ) - ) - - assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) - - assert_that( - completer.PollForMessages( request_data ), - contains_exactly( has_entries( { - 'diagnostics': diagnostics, - 'filepath': filepath - } ) ) - ) + with patch.object( completer, '_ServerIsInitialized', return_value = True ): + completer.OnFileReadyToParse( request_data ) + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } + } + completer._HandleInitializeInPollThread( initialize_response ) + diagnostics = contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 4, 11 ), + 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), + 'text': equal_to( 'First error' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 5, 8 ), + 'location_extent': RangeMatcher( filepath, ( 5, 8 ), ( 5, 14 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 5, 8 ), ( 5, 14 ) ) ), + 'text': equal_to( 'Second error' ), + 'fixit_available': False + } ) + ) -@IsolatedYcmd() -def LanguageServerCompleter_GetHoverResponse_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest( line_num = 1, - column_num = 1, - contents = '' ) ) + assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) - with patch.object( completer, '_ServerIsInitialized', return_value = True ): - with patch.object( completer.GetConnection(), - 'GetResponse', - side_effect = [ { 'result': None } ] ): assert_that( - calling( completer.GetHoverResponse ).with_args( request_data ), - raises( NoHoverInfoException, NO_HOVER_INFORMATION ) + completer.PollForMessages( request_data ), + contains_exactly( has_entries( { + 'diagnostics': diagnostics, + 'filepath': filepath + } ) ) ) - with patch.object( completer.GetConnection(), - 'GetResponse', - side_effect = [ { 'result': { 'contents': 'test' } } ] ): - assert_that( completer.GetHoverResponse( request_data ), - equal_to( 'test' ) ) - - -@IsolatedYcmd() -def LanguageServerCompleter_Diagnostics_Code_test( app ): - completer = MockCompleter() - filepath = os.path.realpath( '/foo.cpp' ) - uri = lsp.FilePathToUri( filepath ) - request_data = RequestWrap( BuildRequest( line_num = 1, - column_num = 1, - filepath = filepath, - contents = '' ) ) - notification = { - 'jsonrpc': '2.0', - 'method': 'textDocument/publishDiagnostics', - 'params': { - 'uri': uri, - 'diagnostics': [ { - 'range': { - 'start': { 'line': 3, 'character': 10 }, - 'end': { 'line': 3, 'character': 11 } - }, - 'severity': 1, - 'message': 'First error', - 'code': 'random_error' - }, { - 'range': { - 'start': { 'line': 3, 'character': 10 }, - 'end': { 'line': 3, 'character': 11 } - }, - 'severity': 1, - 'message': 'Second error', - 'code': 8 - }, { - 'range': { - 'start': { 'line': 3, 'character': 10 }, - 'end': { 'line': 3, 'character': 11 } - }, - 'severity': 1, - 'message': 'Third error', - 'code': '8' - } ] - } - } - completer.GetConnection()._notifications.put( notification ) - completer.HandleNotificationInPollThread( notification ) - - with patch.object( completer, 'ServerIsReady', return_value = True ): - completer.OnFileReadyToParse( request_data ) - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} - } - } - completer._HandleInitializeInPollThread( initialize_response ) - - diagnostics = contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 4, 11 ), - 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), - 'text': equal_to( 'First error [random_error]' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 4, 11 ), - 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), - 'text': equal_to( 'Second error [8]' ), - 'fixit_available': False - } ), - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 4, 11 ), - 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), - 'text': equal_to( 'Third error [8]' ), - 'fixit_available': False - } ) - ) - - assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) - - assert_that( - completer.PollForMessages( request_data ), - contains_exactly( has_entries( { - 'diagnostics': diagnostics, - 'filepath': filepath - } ) ) - ) -@IsolatedYcmd() -def LanguageServerCompleter_Diagnostics_PercentEncodeCannonical_test( app ): - completer = MockCompleter() - filepath = os.path.realpath( '/foo?' ) - uri = lsp.FilePathToUri( filepath ) - assert_that( uri, ends_with( '%3F' ) ) - request_data = RequestWrap( BuildRequest( line_num = 1, - column_num = 1, - filepath = filepath, - contents = '' ) ) - notification = { - 'jsonrpc': '2.0', - 'method': 'textDocument/publishDiagnostics', - 'params': { - 'uri': uri.replace( '%3F', '%3f' ), - 'diagnostics': [ { - 'range': { - 'start': { 'line': 3, 'character': 10 }, - 'end': { 'line': 3, 'character': 11 } - }, - 'severity': 1, - 'message': 'First error' - } ] - } - } - completer.GetConnection()._notifications.put( notification ) - completer.HandleNotificationInPollThread( notification ) + @IsolatedYcmd() + def test_LanguageServerCompleter_GetHoverResponse( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest( line_num = 1, + column_num = 1, + contents = '' ) ) - with patch.object( completer, '_ServerIsInitialized', return_value = True ): - completer.OnFileReadyToParse( request_data ) - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} + with patch.object( completer, '_ServerIsInitialized', return_value = True ): + with patch.object( completer.GetConnection(), + 'GetResponse', + side_effect = [ { 'result': None } ] ): + assert_that( + calling( completer.GetHoverResponse ).with_args( request_data ), + raises( NoHoverInfoException, NO_HOVER_INFORMATION ) + ) + with patch.object( + completer.GetConnection(), + 'GetResponse', + side_effect = [ { 'result': { 'contents': 'test' } } ] ): + assert_that( completer.GetHoverResponse( request_data ), + equal_to( 'test' ) ) + + + @IsolatedYcmd() + def test_LanguageServerCompleter_Diagnostics_Code( self, app ): + completer = MockCompleter() + filepath = os.path.realpath( '/foo.cpp' ) + uri = lsp.FilePathToUri( filepath ) + request_data = RequestWrap( BuildRequest( line_num = 1, + column_num = 1, + filepath = filepath, + contents = '' ) ) + notification = { + 'jsonrpc': '2.0', + 'method': 'textDocument/publishDiagnostics', + 'params': { + 'uri': uri, + 'diagnostics': [ { + 'range': { + 'start': { 'line': 3, 'character': 10 }, + 'end': { 'line': 3, 'character': 11 } + }, + 'severity': 1, + 'message': 'First error', + 'code': 'random_error' + }, { + 'range': { + 'start': { 'line': 3, 'character': 10 }, + 'end': { 'line': 3, 'character': 11 } + }, + 'severity': 1, + 'message': 'Second error', + 'code': 8 + }, { + 'range': { + 'start': { 'line': 3, 'character': 10 }, + 'end': { 'line': 3, 'character': 11 } + }, + 'severity': 1, + 'message': 'Third error', + 'code': '8' + } ] } } - completer._HandleInitializeInPollThread( initialize_response ) + completer.GetConnection()._notifications.put( notification ) + completer.HandleNotificationInPollThread( notification ) - diagnostics = contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( filepath, 4, 11 ), - 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), - 'text': equal_to( 'First error' ), - 'fixit_available': False - } ) - ) - - assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) + with patch.object( completer, 'ServerIsReady', return_value = True ): + completer.OnFileReadyToParse( request_data ) + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } + } + completer._HandleInitializeInPollThread( initialize_response ) - assert_that( - completer.PollForMessages( request_data ), - contains_exactly( has_entries( { - 'diagnostics': diagnostics, - 'filepath': filepath - } ) ) - ) + diagnostics = contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 4, 11 ), + 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), + 'text': equal_to( 'First error [random_error]' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 4, 11 ), + 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), + 'text': equal_to( 'Second error [8]' ), + 'fixit_available': False + } ), + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 4, 11 ), + 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), + 'text': equal_to( 'Third error [8]' ), + 'fixit_available': False + } ) + ) + assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) -@IsolatedYcmd() -@patch.object( completer, 'MESSAGE_POLL_TIMEOUT', 0.01 ) -def LanguageServerCompleter_PollForMessages_ServerNotStarted_test( app ): - server = MockCompleter() - request_data = RequestWrap( BuildRequest() ) - assert_that( server.PollForMessages( request_data ), equal_to( True ) ) + assert_that( + completer.PollForMessages( request_data ), + contains_exactly( has_entries( { + 'diagnostics': diagnostics, + 'filepath': filepath + } ) ) + ) -@IsolatedYcmd() -def LanguageServerCompleter_OnFileSave_BeforeServerReady_test( app ): - completer = MockCompleter() - request_data = RequestWrap( BuildRequest() ) - with patch.object( completer, 'ServerIsReady', return_value = False ): - with patch.object( completer.GetConnection(), - 'SendNotification' ) as send_notification: - completer.OnFileSave( request_data ) - send_notification.assert_not_called() - - -@IsolatedYcmd() -def LanguageServerCompleter_OnFileReadyToParse_InvalidURI_test( app ): - completer = MockCompleter() - filepath = os.path.realpath( '/foo?' ) - uri = lsp.FilePathToUri( filepath ) - request_data = RequestWrap( BuildRequest( line_num = 1, - column_num = 1, - filepath = filepath, - contents = '' ) ) - notification = { - 'jsonrpc': '2.0', - 'method': 'textDocument/publishDiagnostics', - 'params': { - 'uri': uri, - 'diagnostics': [ { - 'range': { - 'start': { 'line': 3, 'character': 10 }, - 'end': { 'line': 3, 'character': 11 } - }, - 'severity': 1, - 'message': 'First error' - } ] + @IsolatedYcmd() + def test_LanguageServerCompleter_Diagnostics_PercentEncodeCannonical( + self, app ): + completer = MockCompleter() + filepath = os.path.realpath( '/foo?' ) + uri = lsp.FilePathToUri( filepath ) + assert_that( uri, ends_with( '%3F' ) ) + request_data = RequestWrap( BuildRequest( line_num = 1, + column_num = 1, + filepath = filepath, + contents = '' ) ) + notification = { + 'jsonrpc': '2.0', + 'method': 'textDocument/publishDiagnostics', + 'params': { + 'uri': uri.replace( '%3F', '%3f' ), + 'diagnostics': [ { + 'range': { + 'start': { 'line': 3, 'character': 10 }, + 'end': { 'line': 3, 'character': 11 } + }, + 'severity': 1, + 'message': 'First error' + } ] + } } - } - completer.GetConnection()._notifications.put( notification ) - completer.HandleNotificationInPollThread( notification ) + completer.GetConnection()._notifications.put( notification ) + completer.HandleNotificationInPollThread( notification ) - with patch.object( completer, '_ServerIsInitialized', return_value = True ): - completer.OnFileReadyToParse( request_data ) - # Simulate receipt of response and initialization complete - initialize_response = { - 'result': { - 'capabilities': {} + with patch.object( completer, '_ServerIsInitialized', return_value = True ): + completer.OnFileReadyToParse( request_data ) + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } } - } - completer._HandleInitializeInPollThread( initialize_response ) + completer._HandleInitializeInPollThread( initialize_response ) - diagnostics = contains_exactly( - has_entries( { - 'kind': equal_to( 'ERROR' ), - 'location': LocationMatcher( '', 4, 11 ), - 'location_extent': RangeMatcher( '', ( 4, 11 ), ( 4, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( '', ( 4, 11 ), ( 4, 12 ) ) ), - 'text': equal_to( 'First error' ), - 'fixit_available': False - } ) - ) + diagnostics = contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( filepath, 4, 11 ), + 'location_extent': RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 4, 11 ), ( 4, 12 ) ) ), + 'text': equal_to( 'First error' ), + 'fixit_available': False + } ) + ) - with patch( 'ycmd.completers.language_server.language_server_protocol.' - 'UriToFilePath', side_effect = lsp.InvalidUriException ) as \ - uri_to_filepath: assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) - uri_to_filepath.assert_called() - - -def _TupleToLSPRange( tuple ): - return { 'line': tuple[ 0 ], 'character': tuple[ 1 ] } + assert_that( + completer.PollForMessages( request_data ), + contains_exactly( has_entries( { + 'diagnostics': diagnostics, + 'filepath': filepath + } ) ) + ) -def _Check_Distance( point, start, end, expected ): - point = _TupleToLSPRange( point ) - start = _TupleToLSPRange( start ) - end = _TupleToLSPRange( end ) - range = { 'start': start, 'end': end } - result = lsc._DistanceOfPointToRange( point, range ) - assert_that( result, equal_to( expected ) ) + @IsolatedYcmd() + @patch.object( completer, 'MESSAGE_POLL_TIMEOUT', 0.01 ) + def test_LanguageServerCompleter_PollForMessages_ServerNotStarted( + self, app ): + server = MockCompleter() + request_data = RequestWrap( BuildRequest() ) + assert_that( server.PollForMessages( request_data ), equal_to( True ) ) -def LanguageServerCompleter_DistanceOfPointToRange_SingleLineRange_test(): - # Point to the left of range. - _Check_Distance( ( 0, 0 ), ( 0, 2 ), ( 0, 5 ) , 2 ) - # Point inside range. - _Check_Distance( ( 0, 4 ), ( 0, 2 ), ( 0, 5 ) , 0 ) - # Point to the right of range. - _Check_Distance( ( 0, 8 ), ( 0, 2 ), ( 0, 5 ) , 3 ) + @IsolatedYcmd() + def test_LanguageServerCompleter_OnFileSave_BeforeServerReady( self, app ): + completer = MockCompleter() + request_data = RequestWrap( BuildRequest() ) + with patch.object( completer, 'ServerIsReady', return_value = False ): + with patch.object( completer.GetConnection(), + 'SendNotification' ) as send_notification: + completer.OnFileSave( request_data ) + send_notification.assert_not_called() + + + @IsolatedYcmd() + def test_LanguageServerCompleter_OnFileReadyToParse_InvalidURI( self, app ): + completer = MockCompleter() + filepath = os.path.realpath( '/foo?' ) + uri = lsp.FilePathToUri( filepath ) + request_data = RequestWrap( BuildRequest( line_num = 1, + column_num = 1, + filepath = filepath, + contents = '' ) ) + notification = { + 'jsonrpc': '2.0', + 'method': 'textDocument/publishDiagnostics', + 'params': { + 'uri': uri, + 'diagnostics': [ { + 'range': { + 'start': { 'line': 3, 'character': 10 }, + 'end': { 'line': 3, 'character': 11 } + }, + 'severity': 1, + 'message': 'First error' + } ] + } + } + completer.GetConnection()._notifications.put( notification ) + completer.HandleNotificationInPollThread( notification ) -def LanguageServerCompleter_DistanceOfPointToRange_MultiLineRange_test(): - # Point to the left of range. - _Check_Distance( ( 0, 0 ), ( 0, 2 ), ( 3, 5 ) , 2 ) - # Point inside range. - _Check_Distance( ( 1, 4 ), ( 0, 2 ), ( 3, 5 ) , 0 ) - # Point to the right of range. - _Check_Distance( ( 3, 8 ), ( 0, 2 ), ( 3, 5 ) , 3 ) + with patch.object( completer, '_ServerIsInitialized', return_value = True ): + completer.OnFileReadyToParse( request_data ) + # Simulate receipt of response and initialization complete + initialize_response = { + 'result': { + 'capabilities': {} + } + } + completer._HandleInitializeInPollThread( initialize_response ) + diagnostics = contains_exactly( + has_entries( { + 'kind': equal_to( 'ERROR' ), + 'location': LocationMatcher( '', 4, 11 ), + 'location_extent': RangeMatcher( '', ( 4, 11 ), ( 4, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( '', ( 4, 11 ), ( 4, 12 ) ) ), + 'text': equal_to( 'First error' ), + 'fixit_available': False + } ) + ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + with patch( 'ycmd.completers.language_server.language_server_protocol.' + 'UriToFilePath', side_effect = lsp.InvalidUriException ) as \ + uri_to_filepath: + assert_that( completer.OnFileReadyToParse( request_data ), diagnostics ) + uri_to_filepath.assert_called() + + + def test_LanguageServerCompleter_DistanceOfPointToRange_SingleLineRange( + self ): + # Point to the left of range. + _Check_Distance( ( 0, 0 ), ( 0, 2 ), ( 0, 5 ) , 2 ) + # Point inside range. + _Check_Distance( ( 0, 4 ), ( 0, 2 ), ( 0, 5 ) , 0 ) + # Point to the right of range. + _Check_Distance( ( 0, 8 ), ( 0, 2 ), ( 0, 5 ) , 3 ) + + + def test_LanguageServerCompleter_DistanceOfPointToRange_MultiLineRange( + self ): + # Point to the left of range. + _Check_Distance( ( 0, 0 ), ( 0, 2 ), ( 3, 5 ) , 2 ) + # Point inside range. + _Check_Distance( ( 1, 4 ), ( 0, 2 ), ( 3, 5 ) , 0 ) + # Point to the right of range. + _Check_Distance( ( 3, 8 ), ( 0, 2 ), ( 3, 5 ) , 3 ) diff --git a/ycmd/tests/language_server/language_server_connection_test.py b/ycmd/tests/language_server/language_server_connection_test.py index b8d672665c..892a45f648 100644 --- a/ycmd/tests/language_server/language_server_connection_test.py +++ b/ycmd/tests/language_server/language_server_connection_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -18,164 +18,161 @@ from unittest.mock import patch, MagicMock from ycmd.completers.language_server import language_server_completer as lsc from hamcrest import assert_that, calling, equal_to, raises +from unittest import TestCase from ycmd.tests.language_server import MockConnection import queue -def LanguageServerConnection_ReadPartialMessage_test(): - connection = MockConnection() +class LanguageServerConnectionTest( TestCase ): + def test_LanguageServerConnection_ReadPartialMessage( self ): + connection = MockConnection() - return_values = [ - bytes( b'Content-Length: 10\n\n{"abc":' ), - bytes( b'""}' ), - lsc.LanguageServerConnectionStopped - ] - - with patch.object( connection, 'ReadData', side_effect = return_values ): - with patch.object( connection, '_DispatchMessage' ) as dispatch_message: - connection.run() - dispatch_message.assert_called_with( { 'abc': '' } ) + return_values = [ + bytes( b'Content-Length: 10\n\n{"abc":' ), + bytes( b'""}' ), + lsc.LanguageServerConnectionStopped + ] + with patch.object( connection, 'ReadData', side_effect = return_values ): + with patch.object( connection, '_DispatchMessage' ) as dispatch_message: + connection.run() + dispatch_message.assert_called_with( { 'abc': '' } ) -def LanguageServerConnection_MissingHeader_test(): - connection = MockConnection() - return_values = [ - bytes( b'Content-NOTLENGTH: 10\n\n{"abc":' ), - bytes( b'""}' ), - lsc.LanguageServerConnectionStopped - ] + def test_LanguageServerConnection_MissingHeader( self ): + connection = MockConnection() - with patch.object( connection, 'ReadData', side_effect = return_values ): - assert_that( calling( connection._ReadMessages ), raises( ValueError ) ) + return_values = [ + bytes( b'Content-NOTLENGTH: 10\n\n{"abc":' ), + bytes( b'""}' ), + lsc.LanguageServerConnectionStopped + ] + with patch.object( connection, 'ReadData', side_effect = return_values ): + assert_that( calling( connection._ReadMessages ), raises( ValueError ) ) -def LanguageServerConnection_RequestAbortCallback_test(): - connection = MockConnection() - return_values = [ - lsc.LanguageServerConnectionStopped - ] + def test_LanguageServerConnection_RequestAbortCallback( self ): + connection = MockConnection() - with patch.object( connection, 'ReadData', side_effect = return_values ): - callback = MagicMock() - response = connection.GetResponseAsync( 1, - bytes( b'{"test":"test"}' ), - response_callback = callback ) - connection.run() - callback.assert_called_with( response, None ) + return_values = [ + lsc.LanguageServerConnectionStopped + ] + with patch.object( connection, 'ReadData', side_effect = return_values ): + callback = MagicMock() + response = connection.GetResponseAsync( 1, + bytes( b'{"test":"test"}' ), + response_callback = callback ) + connection.run() + callback.assert_called_with( response, None ) -def LanguageServerConnection_RequestAbortAwait_test(): - connection = MockConnection() - - return_values = [ - lsc.LanguageServerConnectionStopped - ] - with patch.object( connection, 'ReadData', side_effect = return_values ): - response = connection.GetResponseAsync( 1, - bytes( b'{"test":"test"}' ) ) - connection.run() - assert_that( calling( response.AwaitResponse ).with_args( 10 ), - raises( lsc.ResponseAbortedException ) ) + def test_LanguageServerConnection_RequestAbortAwait( self ): + connection = MockConnection() + return_values = [ + lsc.LanguageServerConnectionStopped + ] -def LanguageServerConnection_ServerConnectionDies_test(): - connection = MockConnection() + with patch.object( connection, 'ReadData', side_effect = return_values ): + response = connection.GetResponseAsync( 1, + bytes( b'{"test":"test"}' ) ) + connection.run() + assert_that( calling( response.AwaitResponse ).with_args( 10 ), + raises( lsc.ResponseAbortedException ) ) - return_values = [ - IOError - ] - with patch.object( connection, 'ReadData', side_effect = return_values ): - # No exception is thrown - connection.run() + def test_LanguageServerConnection_ServerConnectionDies( self ): + connection = MockConnection() + return_values = [ + IOError + ] -@patch( 'ycmd.completers.language_server.language_server_completer.' - 'CONNECTION_TIMEOUT', - 0.5 ) -def LanguageServerConnection_ConnectionTimeout_test(): - connection = MockConnection() - with patch.object( connection, - 'TryServerConnectionBlocking', - side_effect=RuntimeError ): - connection.Start() - assert_that( calling( connection.AwaitServerConnection ), - raises( lsc.LanguageServerConnectionTimeout ) ) + with patch.object( connection, 'ReadData', side_effect = return_values ): + # No exception is thrown + connection.run() - assert_that( connection.is_alive(), equal_to( False ) ) + @patch( 'ycmd.completers.language_server.language_server_completer.' + 'CONNECTION_TIMEOUT', + 0.5 ) + def test_LanguageServerConnection_ConnectionTimeout( self ): + connection = MockConnection() + with patch.object( connection, + 'TryServerConnectionBlocking', + side_effect=RuntimeError ): + connection.Start() + assert_that( calling( connection.AwaitServerConnection ), + raises( lsc.LanguageServerConnectionTimeout ) ) -def LanguageServerConnection_CloseTwice_test(): - connection = MockConnection() - with patch.object( connection, - 'TryServerConnectionBlocking', - side_effect=RuntimeError ): - connection.Close() - connection.Close() + assert_that( connection.is_alive(), equal_to( False ) ) -@patch.object( lsc, 'MAX_QUEUED_MESSAGES', 2 ) -def LanguageServerConnection_AddNotificationToQueue_RingBuffer_test(): - connection = MockConnection() - notifications = connection._notifications + def test_LanguageServerConnection_CloseTwice( self ): + connection = MockConnection() + with patch.object( connection, + 'TryServerConnectionBlocking', + side_effect=RuntimeError ): + connection.Close() + connection.Close() - # Queue empty - assert_that( calling( notifications.get_nowait ), raises( queue.Empty ) ) + @patch.object( lsc, 'MAX_QUEUED_MESSAGES', 2 ) + def test_LanguageServerConnection_AddNotificationToQueue_RingBuffer( self ): + connection = MockConnection() + notifications = connection._notifications - # Queue partially full, then drained + # Queue empty - connection._AddNotificationToQueue( 'one' ) + assert_that( calling( notifications.get_nowait ), raises( queue.Empty ) ) - assert_that( notifications.get_nowait(), equal_to( 'one' ) ) - assert_that( calling( notifications.get_nowait ), raises( queue.Empty ) ) + # Queue partially full, then drained - # Queue full, then drained + connection._AddNotificationToQueue( 'one' ) - connection._AddNotificationToQueue( 'one' ) - connection._AddNotificationToQueue( 'two' ) + assert_that( notifications.get_nowait(), equal_to( 'one' ) ) + assert_that( calling( notifications.get_nowait ), raises( queue.Empty ) ) - assert_that( notifications.get_nowait(), equal_to( 'one' ) ) - assert_that( notifications.get_nowait(), equal_to( 'two' ) ) - assert_that( calling( notifications.get_nowait ), raises( queue.Empty ) ) + # Queue full, then drained - # Queue full, then new notification, then drained + connection._AddNotificationToQueue( 'one' ) + connection._AddNotificationToQueue( 'two' ) - connection._AddNotificationToQueue( 'one' ) - connection._AddNotificationToQueue( 'two' ) - connection._AddNotificationToQueue( 'three' ) + assert_that( notifications.get_nowait(), equal_to( 'one' ) ) + assert_that( notifications.get_nowait(), equal_to( 'two' ) ) + assert_that( calling( notifications.get_nowait ), raises( queue.Empty ) ) - assert_that( notifications.get_nowait(), equal_to( 'two' ) ) - assert_that( notifications.get_nowait(), equal_to( 'three' ) ) - assert_that( calling( notifications.get_nowait ), raises( queue.Empty ) ) + # Queue full, then new notification, then drained + connection._AddNotificationToQueue( 'one' ) + connection._AddNotificationToQueue( 'two' ) + connection._AddNotificationToQueue( 'three' ) -def LanguageServerConnection_RejectUnsupportedRequest_test(): - connection = MockConnection() + assert_that( notifications.get_nowait(), equal_to( 'two' ) ) + assert_that( notifications.get_nowait(), equal_to( 'three' ) ) + assert_that( calling( notifications.get_nowait ), raises( queue.Empty ) ) - return_values = [ - bytes( b'Content-Length: 26\r\n\r\n{"id":"1","method":"test"}' ), - lsc.LanguageServerConnectionStopped - ] - expected_response = bytes( b'Content-Length: 79\r\n\r\n' - b'{"error":{' - b'"code":-32601,' - b'"message":"Method not found"' - b'},' - b'"id":"1",' - b'"jsonrpc":"2.0"}' ) + def test_LanguageServerConnection_RejectUnsupportedRequest( self ): + connection = MockConnection() - with patch.object( connection, 'ReadData', side_effect = return_values ): - with patch.object( connection, 'WriteData' ) as write_data: - connection.run() - write_data.assert_called_with( expected_response ) + return_values = [ + bytes( b'Content-Length: 26\r\n\r\n{"id":"1","method":"test"}' ), + lsc.LanguageServerConnectionStopped + ] + expected_response = bytes( b'Content-Length: 79\r\n\r\n' + b'{"error":{' + b'"code":-32601,' + b'"message":"Method not found"' + b'},' + b'"id":"1",' + b'"jsonrpc":"2.0"}' ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + with patch.object( connection, 'ReadData', side_effect = return_values ): + with patch.object( connection, 'WriteData' ) as write_data: + connection.run() + write_data.assert_called_with( expected_response ) diff --git a/ycmd/tests/language_server/language_server_protocol_test.py b/ycmd/tests/language_server/language_server_protocol_test.py index e69a8f2ba0..15c91a0001 100644 --- a/ycmd/tests/language_server/language_server_protocol_test.py +++ b/ycmd/tests/language_server/language_server_protocol_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,194 +15,192 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -import pytest from ycmd.completers.language_server import language_server_protocol as lsp from hamcrest import assert_that, equal_to, calling, is_not, raises +from unittest import TestCase from ycmd.tests.test_utils import UnixOnly, WindowsOnly -def ServerFileStateStore_RetrieveDelete_test(): - store = lsp.ServerFileStateStore() - - # New state object created - file1_state = store[ 'file1' ] - assert_that( file1_state.version, equal_to( 0 ) ) - assert_that( file1_state.checksum, equal_to( None ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) - - # Retrieve again unchanged - file1_state = store[ 'file1' ] - assert_that( file1_state.version, equal_to( 0 ) ) - assert_that( file1_state.checksum, equal_to( None ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) - - # Retrieve/create another one (we don't actually open this one) - file2_state = store[ 'file2' ] - assert_that( file2_state.version, equal_to( 0 ) ) - assert_that( file2_state.checksum, equal_to( None ) ) - assert_that( file2_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) - - # Checking for refresh on closed file is no-op - assert_that( file1_state.GetSavedFileAction( 'blah' ), - equal_to( lsp.ServerFileState.NO_ACTION ) ) - assert_that( file1_state.version, equal_to( 0 ) ) - assert_that( file1_state.checksum, equal_to( None ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) - - - # Checking the next action progresses the state - assert_that( file1_state.GetDirtyFileAction( 'test contents' ), - equal_to( lsp.ServerFileState.OPEN_FILE ) ) - assert_that( file1_state.version, equal_to( 1 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # Replacing the same file is no-op - assert_that( file1_state.GetDirtyFileAction( 'test contents' ), - equal_to( lsp.ServerFileState.NO_ACTION ) ) - assert_that( file1_state.version, equal_to( 1 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # Changing the file creates a new version - assert_that( file1_state.GetDirtyFileAction( 'test contents changed' ), - equal_to( lsp.ServerFileState.CHANGE_FILE ) ) - assert_that( file1_state.version, equal_to( 2 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # Replacing the same file is no-op - assert_that( file1_state.GetDirtyFileAction( 'test contents changed' ), - equal_to( lsp.ServerFileState.NO_ACTION ) ) - assert_that( file1_state.version, equal_to( 2 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # Checking for refresh without change is no-op - assert_that( file1_state.GetSavedFileAction( 'test contents changed' ), - equal_to( lsp.ServerFileState.NO_ACTION ) ) - assert_that( file1_state.version, equal_to( 2 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # Changing the same file is a new version - assert_that( file1_state.GetDirtyFileAction( 'test contents changed again' ), - equal_to( lsp.ServerFileState.CHANGE_FILE ) ) - assert_that( file1_state.version, equal_to( 3 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # Checking for refresh with change is a new version - assert_that( file1_state.GetSavedFileAction( 'test changed back' ), - equal_to( lsp.ServerFileState.CHANGE_FILE ) ) - assert_that( file1_state.version, equal_to( 4 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # Closing an open file progressed the state - assert_that( file1_state.GetFileCloseAction(), - equal_to( lsp.ServerFileState.CLOSE_FILE ) ) - assert_that( file1_state.version, equal_to( 4 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) - - # Replacing a closed file opens it - assert_that( file1_state.GetDirtyFileAction( 'test contents again2' ), - equal_to( lsp.ServerFileState.OPEN_FILE ) ) - assert_that( file1_state.version, equal_to( 1 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # Closing an open file progressed the state - assert_that( file1_state.GetFileCloseAction(), - equal_to( lsp.ServerFileState.CLOSE_FILE ) ) - assert_that( file1_state.version, equal_to( 1 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) - - # You can del a closed file - del store[ file1_state.filename ] - - # Replacing a del'd file opens it again - file1_state = store[ 'file1' ] - assert_that( file1_state.GetDirtyFileAction( 'test contents again3' ), - equal_to( lsp.ServerFileState.OPEN_FILE ) ) - assert_that( file1_state.version, equal_to( 1 ) ) - assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) - assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) - - # You can del an open file (though you probably shouldn't) - del store[ file1_state.filename ] - - # Closing a closed file is a noop - assert_that( file2_state.GetFileCloseAction(), - equal_to( lsp.ServerFileState.NO_ACTION ) ) - assert_that( file2_state.version, equal_to( 0 ) ) - assert_that( file2_state.checksum, equal_to( None ) ) - assert_that( file2_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) - - -@UnixOnly -def UriToFilePath_Unix_test(): - assert_that( calling( lsp.UriToFilePath ).with_args( 'test' ), - raises( lsp.InvalidUriException ) ) - - assert_that( lsp.UriToFilePath( 'file:/usr/local/test/test.test' ), - equal_to( '/usr/local/test/test.test' ) ) - assert_that( lsp.UriToFilePath( 'file:///usr/local/test/test.test' ), - equal_to( '/usr/local/test/test.test' ) ) - - -@WindowsOnly -def UriToFilePath_Windows_test(): - assert_that( calling( lsp.UriToFilePath ).with_args( 'test' ), - raises( lsp.InvalidUriException ) ) - - assert_that( lsp.UriToFilePath( 'file:c:/usr/local/test/test.test' ), - equal_to( 'C:\\usr\\local\\test\\test.test' ) ) - assert_that( lsp.UriToFilePath( 'file:c%3a/usr/local/test/test.test' ), - equal_to( 'C:\\usr\\local\\test\\test.test' ) ) - assert_that( lsp.UriToFilePath( 'file:c%3A/usr/local/test/test.test' ), - equal_to( 'C:\\usr\\local\\test\\test.test' ) ) - assert_that( lsp.UriToFilePath( 'file:///c:/usr/local/test/test.test' ), - equal_to( 'C:\\usr\\local\\test\\test.test' ) ) - assert_that( lsp.UriToFilePath( 'file:///c%3a/usr/local/test/test.test' ), - equal_to( 'C:\\usr\\local\\test\\test.test' ) ) - assert_that( lsp.UriToFilePath( 'file:///c%3A/usr/local/test/test.test' ), - equal_to( 'C:\\usr\\local\\test\\test.test' ) ) - - -@UnixOnly -def FilePathToUri_Unix_test(): - assert_that( lsp.FilePathToUri( '/usr/local/test/test.test' ), - equal_to( 'file:///usr/local/test/test.test' ) ) - - -@WindowsOnly -def FilePathToUri_Windows_test(): - assert_that( lsp.FilePathToUri( 'C:\\usr\\local\\test\\test.test' ), - equal_to( 'file:///C:/usr/local/test/test.test' ) ) - - -@pytest.mark.parametrize( 'line_value,codepoints,code_units', [ ( '', 0, 0 ), - ( 'abcdef', 1, 1 ), - ( 'abcdef', 2, 2 ), - ( 'abc', 4, 4 ), - ( '😉test', len( '😉' ), 2 ), - ( '😉', len( '😉' ), 2 ), - ( '😉test', len( '😉' ) + 1, 3 ), - ( 'te😉st', 1, 1 ), - ( 'te😉st', 2 + len( '😉' ) + 1, 5 ), - ] ) -def CodepointsToUTF16CodeUnitsAndReverse_test( line_value, - codepoints, - code_units ): - assert_that( lsp.CodepointsToUTF16CodeUnits( line_value, codepoints ), - equal_to( code_units ) ) - assert_that( lsp.UTF16CodeUnitsToCodepoints( line_value, code_units ), - equal_to( codepoints ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class LanguageServerProtocolTest( TestCase ): + def test_ServerFileStateStore_RetrieveDelete( self ): + store = lsp.ServerFileStateStore() + + # New state object created + file1_state = store[ 'file1' ] + assert_that( file1_state.version, equal_to( 0 ) ) + assert_that( file1_state.checksum, equal_to( None ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) + + # Retrieve again unchanged + file1_state = store[ 'file1' ] + assert_that( file1_state.version, equal_to( 0 ) ) + assert_that( file1_state.checksum, equal_to( None ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) + + # Retrieve/create another one (we don't actually open this one) + file2_state = store[ 'file2' ] + assert_that( file2_state.version, equal_to( 0 ) ) + assert_that( file2_state.checksum, equal_to( None ) ) + assert_that( file2_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) + + # Checking for refresh on closed file is no-op + assert_that( file1_state.GetSavedFileAction( 'blah' ), + equal_to( lsp.ServerFileState.NO_ACTION ) ) + assert_that( file1_state.version, equal_to( 0 ) ) + assert_that( file1_state.checksum, equal_to( None ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) + + + # Checking the next action progresses the state + assert_that( file1_state.GetDirtyFileAction( 'test contents' ), + equal_to( lsp.ServerFileState.OPEN_FILE ) ) + assert_that( file1_state.version, equal_to( 1 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # Replacing the same file is no-op + assert_that( file1_state.GetDirtyFileAction( 'test contents' ), + equal_to( lsp.ServerFileState.NO_ACTION ) ) + assert_that( file1_state.version, equal_to( 1 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # Changing the file creates a new version + assert_that( file1_state.GetDirtyFileAction( 'test contents changed' ), + equal_to( lsp.ServerFileState.CHANGE_FILE ) ) + assert_that( file1_state.version, equal_to( 2 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # Replacing the same file is no-op + assert_that( file1_state.GetDirtyFileAction( 'test contents changed' ), + equal_to( lsp.ServerFileState.NO_ACTION ) ) + assert_that( file1_state.version, equal_to( 2 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # Checking for refresh without change is no-op + assert_that( file1_state.GetSavedFileAction( 'test contents changed' ), + equal_to( lsp.ServerFileState.NO_ACTION ) ) + assert_that( file1_state.version, equal_to( 2 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # Changing the same file is a new version + assert_that( file1_state.GetDirtyFileAction( + 'test contents changed again' ), + equal_to( lsp.ServerFileState.CHANGE_FILE ) ) + assert_that( file1_state.version, equal_to( 3 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # Checking for refresh with change is a new version + assert_that( file1_state.GetSavedFileAction( 'test changed back' ), + equal_to( lsp.ServerFileState.CHANGE_FILE ) ) + assert_that( file1_state.version, equal_to( 4 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # Closing an open file progressed the state + assert_that( file1_state.GetFileCloseAction(), + equal_to( lsp.ServerFileState.CLOSE_FILE ) ) + assert_that( file1_state.version, equal_to( 4 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) + + # Replacing a closed file opens it + assert_that( file1_state.GetDirtyFileAction( 'test contents again2' ), + equal_to( lsp.ServerFileState.OPEN_FILE ) ) + assert_that( file1_state.version, equal_to( 1 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # Closing an open file progressed the state + assert_that( file1_state.GetFileCloseAction(), + equal_to( lsp.ServerFileState.CLOSE_FILE ) ) + assert_that( file1_state.version, equal_to( 1 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) + + # You can del a closed file + del store[ file1_state.filename ] + + # Replacing a del'd file opens it again + file1_state = store[ 'file1' ] + assert_that( file1_state.GetDirtyFileAction( 'test contents again3' ), + equal_to( lsp.ServerFileState.OPEN_FILE ) ) + assert_that( file1_state.version, equal_to( 1 ) ) + assert_that( file1_state.checksum, is_not( equal_to( None ) ) ) + assert_that( file1_state.state, equal_to( lsp.ServerFileState.OPEN ) ) + + # You can del an open file (though you probably shouldn't) + del store[ file1_state.filename ] + + # Closing a closed file is a noop + assert_that( file2_state.GetFileCloseAction(), + equal_to( lsp.ServerFileState.NO_ACTION ) ) + assert_that( file2_state.version, equal_to( 0 ) ) + assert_that( file2_state.checksum, equal_to( None ) ) + assert_that( file2_state.state, equal_to( lsp.ServerFileState.CLOSED ) ) + + + @UnixOnly + def test_UriToFilePath_Unix( self ): + assert_that( calling( lsp.UriToFilePath ).with_args( 'test' ), + raises( lsp.InvalidUriException ) ) + + assert_that( lsp.UriToFilePath( 'file:/usr/local/test/test.test' ), + equal_to( '/usr/local/test/test.test' ) ) + assert_that( lsp.UriToFilePath( 'file:///usr/local/test/test.test' ), + equal_to( '/usr/local/test/test.test' ) ) + + + @WindowsOnly + def test_UriToFilePath_Windows( self ): + assert_that( calling( lsp.UriToFilePath ).with_args( 'test' ), + raises( lsp.InvalidUriException ) ) + + assert_that( lsp.UriToFilePath( 'file:c:/usr/local/test/test.test' ), + equal_to( 'C:\\usr\\local\\test\\test.test' ) ) + assert_that( lsp.UriToFilePath( 'file:c%3a/usr/local/test/test.test' ), + equal_to( 'C:\\usr\\local\\test\\test.test' ) ) + assert_that( lsp.UriToFilePath( 'file:c%3A/usr/local/test/test.test' ), + equal_to( 'C:\\usr\\local\\test\\test.test' ) ) + assert_that( lsp.UriToFilePath( 'file:///c:/usr/local/test/test.test' ), + equal_to( 'C:\\usr\\local\\test\\test.test' ) ) + assert_that( lsp.UriToFilePath( 'file:///c%3a/usr/local/test/test.test' ), + equal_to( 'C:\\usr\\local\\test\\test.test' ) ) + assert_that( lsp.UriToFilePath( 'file:///c%3A/usr/local/test/test.test' ), + equal_to( 'C:\\usr\\local\\test\\test.test' ) ) + + + @UnixOnly + def test_FilePathToUri_Unix( self ): + assert_that( lsp.FilePathToUri( '/usr/local/test/test.test' ), + equal_to( 'file:///usr/local/test/test.test' ) ) + + + @WindowsOnly + def test_FilePathToUri_Windows( self ): + assert_that( lsp.FilePathToUri( 'C:\\usr\\local\\test\\test.test' ), + equal_to( 'file:///C:/usr/local/test/test.test' ) ) + + + def test_CodepointsToUTF16CodeUnitsAndReverse( self ): + for line_value, codepoints, code_units in [ ( '', 0, 0 ), + ( 'abcdef', 1, 1 ), + ( 'abcdef', 2, 2 ), + ( 'abc', 4, 4 ), + ( '😉test', len( '😉' ), 2 ), + ( '😉', len( '😉' ), 2 ), + ( '😉test', len( '😉' ) + 1, 3 ), + ( 'te😉st', 1, 1 ), + ( 'te😉st', 2 + len( '😉' ) + 1, 5 ), + ]: + with self.subTest( line_value = line_value, + codepoints = codepoints, + code_units = code_units ): + assert_that( lsp.CodepointsToUTF16CodeUnits( line_value, codepoints ), + equal_to( code_units ) ) + assert_that( lsp.UTF16CodeUnitsToCodepoints( line_value, code_units ), + equal_to( codepoints ) ) diff --git a/ycmd/tests/misc_handlers_test.py b/ycmd/tests/misc_handlers_test.py index f3e04d514b..5c1ca3ed10 100644 --- a/ycmd/tests/misc_handlers_test.py +++ b/ycmd/tests/misc_handlers_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -18,6 +18,7 @@ from hamcrest import ( any_of, assert_that, contains_exactly, empty, equal_to, has_entries, instance_of ) from unittest.mock import patch +from unittest import TestCase import requests from ycmd.tests import IsolatedYcmd, PathToTestFile, SharedYcmd @@ -28,248 +29,247 @@ ErrorMatcher ) -@SharedYcmd -def MiscHandlers_Healthy_test( app ): - assert_that( app.get( '/healthy' ).json, equal_to( True ) ) +class MiscHandlersTest( TestCase ): + @SharedYcmd + def test_MiscHandlers_Healthy( self, app ): + assert_that( app.get( '/healthy' ).json, equal_to( True ) ) -@SharedYcmd -def MiscHandlers_Healthy_Subserver_test( app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): - assert_that( app.get( '/healthy', { 'subserver': 'dummy_filetype' } ).json, - equal_to( True ) ) + @SharedYcmd + def test_MiscHandlers_Healthy_Subserver( self, app ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + assert_that( app.get( '/healthy', + { 'subserver': 'dummy_filetype' } ).json, + equal_to( True ) ) + + + @SharedYcmd + def test_MiscHandlers_SignatureHelpAvailable( self, app ): + response = app.get( '/signature_help_available', expect_errors = True ).json + assert_that( response, + ErrorMatcher( RuntimeError, 'Subserver not specified' ) ) -@SharedYcmd -def MiscHandlers_SignatureHelpAvailable_test( app ): - response = app.get( '/signature_help_available', expect_errors = True ).json - assert_that( response, - ErrorMatcher( RuntimeError, 'Subserver not specified' ) ) + @SharedYcmd + def test_MiscHandlers_SignatureHelpAvailable_Subserver( self, app ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + assert_that( app.get( '/signature_help_available', + { 'subserver': 'dummy_filetype' } ).json, + SignatureAvailableMatcher( 'NO' ) ) -@SharedYcmd -def MiscHandlers_SignatureHelpAvailable_Subserver_test( app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + @SharedYcmd + def test_MiscHandlers_SignatureHelpAvailable_NoSemanticCompleter( self, app ): assert_that( app.get( '/signature_help_available', { 'subserver': 'dummy_filetype' } ).json, SignatureAvailableMatcher( 'NO' ) ) -@SharedYcmd -def MiscHandlers_SignatureHelpAvailable_NoSemanticCompleter_test( app ): - assert_that( app.get( '/signature_help_available', - { 'subserver': 'dummy_filetype' } ).json, - SignatureAvailableMatcher( 'NO' ) ) + @SharedYcmd + def test_MiscHandlers_Ready( self, app ): + assert_that( app.get( '/ready' ).json, equal_to( True ) ) + + + @SharedYcmd + def test_MiscHandlers_Ready_Subserver( self, app ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + assert_that( app.get( '/ready', { 'subserver': 'dummy_filetype' } ).json, + equal_to( True ) ) + + + @SharedYcmd + def test_MiscHandlers_SemanticCompletionAvailable( self, app ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + request_data = BuildRequest( filetype = 'dummy_filetype' ) + assert_that( app.post_json( '/semantic_completion_available', + request_data ).json, + equal_to( True ) ) + + + @SharedYcmd + def test_MiscHandlers_EventNotification_AlwaysJsonResponse( self, app ): + event_data = BuildRequest( contents = 'foo foogoo ba', + event_name = 'FileReadyToParse' ) + assert_that( app.post_json( '/event_notification', event_data ).json, + empty() ) -@SharedYcmd -def MiscHandlers_Ready_test( app ): - assert_that( app.get( '/ready' ).json, equal_to( True ) ) + @SharedYcmd + def test_MiscHandlers_EventNotification_ReturnJsonOnBigFileError( self, app ): + # We generate a content greater than Bottle.MEMFILE_MAX (10MB) + contents = "foo " * 5000000 + event_data = BuildRequest( contents = contents, + event_name = 'FileReadyToParse' ) -@SharedYcmd -def MiscHandlers_Ready_Subserver_test( app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): - assert_that( app.get( '/ready', { 'subserver': 'dummy_filetype' } ).json, + response = app.post_json( '/event_notification', + event_data, + expect_errors = True ) + assert_that( response.status_code, + equal_to( requests.codes.request_entity_too_large ) ) + assert_that( response.json, + has_entries( { 'traceback': None, + 'message': 'None', + 'exception': None } ) ) + + + @SharedYcmd + def test_MiscHandlers_FilterAndSortCandidates_Basic( self, app ): + candidate1 = { 'prop1': 'aoo', 'prop2': 'bar' } + candidate2 = { 'prop1': 'bfo', 'prop2': 'zoo' } + candidate3 = { 'prop1': 'cfo', 'prop2': 'moo' } + + data = { + 'candidates': [ candidate3, candidate1, candidate2 ], + 'sort_property': 'prop1', + 'query': 'fo' + } + + response_data = app.post_json( '/filter_and_sort_candidates', data ).json + + assert_that( response_data, contains_exactly( candidate2, candidate3 ) ) + + + @SharedYcmd + def test_MiscHandlers_LoadExtraConfFile_AlwaysJsonResponse( self, app ): + filepath = PathToTestFile( 'extra_conf', 'project', '.ycm_extra_conf.py' ) + extra_conf_data = BuildRequest( filepath = filepath ) + + assert_that( app.post_json( '/load_extra_conf_file', extra_conf_data ).json, equal_to( True ) ) -@SharedYcmd -def MiscHandlers_SemanticCompletionAvailable_test( app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): - request_data = BuildRequest( filetype = 'dummy_filetype' ) - assert_that( app.post_json( '/semantic_completion_available', - request_data ).json, + @SharedYcmd + def test_MiscHandlers_IgnoreExtraConfFile_AlwaysJsonResponse( self, app ): + filepath = PathToTestFile( 'extra_conf', 'project', '.ycm_extra_conf.py' ) + extra_conf_data = BuildRequest( filepath = filepath ) + + assert_that( app.post_json( '/ignore_extra_conf_file', + extra_conf_data ).json, equal_to( True ) ) -@SharedYcmd -def MiscHandlers_EventNotification_AlwaysJsonResponse_test( app ): - event_data = BuildRequest( contents = 'foo foogoo ba', - event_name = 'FileReadyToParse' ) - - assert_that( app.post_json( '/event_notification', event_data ).json, - empty() ) - - -@SharedYcmd -def MiscHandlers_EventNotification_ReturnJsonOnBigFileError_test( app ): - # We generate a content greater than Bottle.MEMFILE_MAX, which is set to 10MB. - contents = "foo " * 5000000 - event_data = BuildRequest( contents = contents, - event_name = 'FileReadyToParse' ) - - response = app.post_json( '/event_notification', - event_data, - expect_errors = True ) - assert_that( response.status_code, - equal_to( requests.codes.request_entity_too_large ) ) - assert_that( response.json, - has_entries( { 'traceback': None, - 'message': 'None', - 'exception': None } ) ) - - -@SharedYcmd -def MiscHandlers_FilterAndSortCandidates_Basic_test( app ): - candidate1 = { 'prop1': 'aoo', 'prop2': 'bar' } - candidate2 = { 'prop1': 'bfo', 'prop2': 'zoo' } - candidate3 = { 'prop1': 'cfo', 'prop2': 'moo' } - - data = { - 'candidates': [ candidate3, candidate1, candidate2 ], - 'sort_property': 'prop1', - 'query': 'fo' - } - - response_data = app.post_json( '/filter_and_sort_candidates', data ).json - - assert_that( response_data, contains_exactly( candidate2, candidate3 ) ) - - -@SharedYcmd -def MiscHandlers_LoadExtraConfFile_AlwaysJsonResponse_test( app ): - filepath = PathToTestFile( 'extra_conf', 'project', '.ycm_extra_conf.py' ) - extra_conf_data = BuildRequest( filepath = filepath ) - - assert_that( app.post_json( '/load_extra_conf_file', extra_conf_data ).json, - equal_to( True ) ) - - -@SharedYcmd -def MiscHandlers_IgnoreExtraConfFile_AlwaysJsonResponse_test( app ): - filepath = PathToTestFile( 'extra_conf', 'project', '.ycm_extra_conf.py' ) - extra_conf_data = BuildRequest( filepath = filepath ) - - assert_that( app.post_json( '/ignore_extra_conf_file', extra_conf_data ).json, - equal_to( True ) ) - - -@IsolatedYcmd() -def MiscHandlers_DebugInfo_ExtraConfLoaded_test( app ): - filepath = PathToTestFile( 'extra_conf', 'project', '.ycm_extra_conf.py' ) - app.post_json( '/load_extra_conf_file', { 'filepath': filepath } ) - - request_data = BuildRequest( filepath = filepath ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entries( { - 'python': has_entries( { - 'executable': instance_of( str ), - 'version': instance_of( str ), - } ), - 'clang': has_entries( { - 'has_support': instance_of( bool ), - 'version': any_of( None, instance_of( str ) ) - } ), - 'extra_conf': has_entries( { - 'path': instance_of( str ), - 'is_loaded': True - } ), - 'completer': None - } ) - ) - - -@SharedYcmd -def MiscHandlers_DebugInfo_NoExtraConfFound_test( app ): - request_data = BuildRequest() - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entries( { - 'python': has_entries( { - 'executable': instance_of( str ), - 'version': instance_of( str ), - } ), - 'clang': has_entries( { - 'has_support': instance_of( bool ), - 'version': any_of( None, instance_of( str ) ) - } ), - 'extra_conf': has_entries( { - 'path': None, - 'is_loaded': False - } ), - 'completer': None - } ) - ) - - -@IsolatedYcmd() -def MiscHandlers_DebugInfo_ExtraConfFoundButNotLoaded_test( app ): - filepath = PathToTestFile( 'extra_conf', 'project', '.ycm_extra_conf.py' ) - request_data = BuildRequest( filepath = filepath ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entries( { - 'python': has_entries( { - 'executable': instance_of( str ), - 'version': instance_of( str ), - } ), - 'clang': has_entries( { - 'has_support': instance_of( bool ), - 'version': any_of( None, instance_of( str ) ) - } ), - 'extra_conf': has_entries( { - 'path': instance_of( str ), - 'is_loaded': False - } ), - 'completer': None - } ) - ) - - -@SharedYcmd -def MiscHandlers_ReceiveMessages_NoCompleter_test( app ): - request_data = BuildRequest() - assert_that( app.post_json( '/receive_messages', request_data ).json, - equal_to( False ) ) - - -@SharedYcmd -def MiscHandlers_ReceiveMessages_NotSupportedByCompleter_test( app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): - request_data = BuildRequest( filetype = 'dummy_filetype' ) + @IsolatedYcmd() + def test_MiscHandlers_DebugInfo_ExtraConfLoaded( self, app ): + filepath = PathToTestFile( 'extra_conf', 'project', '.ycm_extra_conf.py' ) + app.post_json( '/load_extra_conf_file', { 'filepath': filepath } ) + + request_data = BuildRequest( filepath = filepath ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entries( { + 'python': has_entries( { + 'executable': instance_of( str ), + 'version': instance_of( str ), + } ), + 'clang': has_entries( { + 'has_support': instance_of( bool ), + 'version': any_of( None, instance_of( str ) ) + } ), + 'extra_conf': has_entries( { + 'path': instance_of( str ), + 'is_loaded': True + } ), + 'completer': None + } ) + ) + + + @SharedYcmd + def test_MiscHandlers_DebugInfo_NoExtraConfFound( self, app ): + request_data = BuildRequest() + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entries( { + 'python': has_entries( { + 'executable': instance_of( str ), + 'version': instance_of( str ), + } ), + 'clang': has_entries( { + 'has_support': instance_of( bool ), + 'version': any_of( None, instance_of( str ) ) + } ), + 'extra_conf': has_entries( { + 'path': None, + 'is_loaded': False + } ), + 'completer': None + } ) + ) + + + @IsolatedYcmd() + def test_MiscHandlers_DebugInfo_ExtraConfFoundButNotLoaded( self, app ): + filepath = PathToTestFile( 'extra_conf', 'project', '.ycm_extra_conf.py' ) + request_data = BuildRequest( filepath = filepath ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entries( { + 'python': has_entries( { + 'executable': instance_of( str ), + 'version': instance_of( str ), + } ), + 'clang': has_entries( { + 'has_support': instance_of( bool ), + 'version': any_of( None, instance_of( str ) ) + } ), + 'extra_conf': has_entries( { + 'path': instance_of( str ), + 'is_loaded': False + } ), + 'completer': None + } ) + ) + + + @SharedYcmd + def test_MiscHandlers_ReceiveMessages_NoCompleter( self, app ): + request_data = BuildRequest() assert_that( app.post_json( '/receive_messages', request_data ).json, equal_to( False ) ) -@SharedYcmd -@patch( 'ycmd.completers.completer.Completer.ShouldUseSignatureHelpNow', - return_value = True ) -def MiscHandlers_SignatureHelp_DefaultEmptyResponse_test( should_use, app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): - request_data = BuildRequest( filetype = 'dummy_filetype' ) - response = app.post_json( '/signature_help', request_data ).json - assert_that( response, has_entries( { - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty() - } ), - 'errors': empty() - } ) ) - - -@SharedYcmd -@patch( 'ycmd.completers.completer.Completer.ComputeSignatures', - side_effect = RuntimeError ) -def MiscHandlers_SignatureHelp_ComputeSignatureThrows_test( compute_sig, app ): - with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): - request_data = BuildRequest( filetype = 'dummy_filetype' ) - response = app.post_json( '/signature_help', request_data ).json - print( response ) - assert_that( response, has_entries( { - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty() - } ), - 'errors': contains_exactly( - ErrorMatcher( RuntimeError, '' ) - ) - } ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_MiscHandlers_ReceiveMessages_NotSupportedByCompleter( self, app ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + request_data = BuildRequest( filetype = 'dummy_filetype' ) + assert_that( app.post_json( '/receive_messages', request_data ).json, + equal_to( False ) ) + + + @SharedYcmd + @patch( 'ycmd.completers.completer.Completer.ShouldUseSignatureHelpNow', + return_value = True ) + def test_MiscHandlers_SignatureHelp_DefaultEmptyResponse( self, app, *args ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + request_data = BuildRequest( filetype = 'dummy_filetype' ) + response = app.post_json( '/signature_help', request_data ).json + assert_that( response, has_entries( { + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty() + } ), + 'errors': empty() + } ) ) + + + @SharedYcmd + @patch( 'ycmd.completers.completer.Completer.ComputeSignatures', + side_effect = RuntimeError ) + def test_MiscHandlers_SignatureHelp_ComputeSignatureThrows( + self, app, *args ): + with PatchCompleter( DummyCompleter, filetype = 'dummy_filetype' ): + request_data = BuildRequest( filetype = 'dummy_filetype' ) + response = app.post_json( '/signature_help', request_data ).json + print( response ) + assert_that( response, has_entries( { + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty() + } ), + 'errors': contains_exactly( + ErrorMatcher( RuntimeError, '' ) + ) + } ) ) diff --git a/ycmd/tests/python/__init__.py b/ycmd/tests/python/__init__.py index 2212457775..c3e4b307c6 100644 --- a/ycmd/tests/python/__init__.py +++ b/ycmd/tests/python/__init__.py @@ -15,10 +15,43 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . +import functools import os -from ycmd.tests.python.conftest import * # noqa + +from ycmd.tests.test_utils import ( ClearCompletionsCache, + IsolatedApp, + IgnoreExtraConfOutsideTestsFolder, + SetUpApp ) + +shared_app = None def PathToTestFile( *args ): dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) return os.path.join( dir_of_current_script, 'testdata', *args ) + + +def setUpModule(): + global shared_app + shared_app = SetUpApp() + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + return Wrapper + return Decorator diff --git a/ycmd/tests/python/conftest.py b/ycmd/tests/python/conftest.py deleted file mode 100644 index 736328c71c..0000000000 --- a/ycmd/tests/python/conftest.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import pytest - -from ycmd.tests.test_utils import ( ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - SetUpApp ) - -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - shared_app = SetUpApp() - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - custom_options = request.param[ 1 ] - with IsolatedApp( custom_options ) as app: - yield app - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) diff --git a/ycmd/tests/python/debug_info_test.py b/ycmd/tests/python/debug_info_test.py index 06ebff8af2..a805abea29 100644 --- a/ycmd/tests/python/debug_info_test.py +++ b/ycmd/tests/python/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -20,48 +20,45 @@ has_entry, has_entries, instance_of ) +from unittest import TestCase -from ycmd.tests.python import SharedYcmd +from ycmd.tests.python import SharedYcmd, setUpModule # noqa from ycmd.tests.test_utils import BuildRequest -@SharedYcmd -def DebugInfo_test( app ): - request_data = BuildRequest( filetype = 'python' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'Python', - 'items': contains_exactly( - has_entries( { - 'key': 'Python interpreter', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Python root', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Python path', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Python version', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Jedi version', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Parso version', - 'value': instance_of( str ) - } ) - ) - } ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class DebugInfoTest( TestCase ): + @SharedYcmd + def test_DebugInfo( self, app ): + request_data = BuildRequest( filetype = 'python' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'Python', + 'items': contains_exactly( + has_entries( { + 'key': 'Python interpreter', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Python root', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Python path', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Python version', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Jedi version', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Parso version', + 'value': instance_of( str ) + } ) + ) + } ) ) + ) diff --git a/ycmd/tests/python/get_completions_test.py b/ycmd/tests/python/get_completions_test.py index 5d20adbb23..9dbd715d0f 100644 --- a/ycmd/tests/python/get_completions_test.py +++ b/ycmd/tests/python/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -26,10 +26,11 @@ has_entry, has_entries, is_not ) +from unittest import TestCase import requests from ycmd.utils import ReadFile -from ycmd.tests.python import IsolatedYcmd, PathToTestFile, SharedYcmd +from ycmd.tests.python import IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule # noqa from ycmd.tests.test_utils import ( BuildRequest, CombineRequest, CompletionEntryMatcher, @@ -70,353 +71,354 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def GetCompletions_Basic_test( app ): - filepath = PathToTestFile( 'basic.py' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'python', - contents = ReadFile( filepath ), - line_num = 7, - column_num = 3 ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - has_items( - CompletionEntryMatcher( 'a', - 'self.a = 1', - { - 'detailed_info': '', - 'kind': 'statement' - } ), - CompletionEntryMatcher( 'b', - 'self.b = 2', - { - 'detailed_info': '', - 'kind': 'statement' - } ) - ) ) - - completion_data = BuildRequest( filepath = filepath, - filetype = 'python', - contents = ReadFile( filepath ), - line_num = 7, - column_num = 4 ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, - all_of( - has_item( +class GetCompletionsTest( TestCase ): + @SharedYcmd + def test_GetCompletions_Basic( self, app ): + filepath = PathToTestFile( 'basic.py' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'python', + contents = ReadFile( filepath ), + line_num = 7, + column_num = 3 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, + has_items( CompletionEntryMatcher( 'a', 'self.a = 1', { 'detailed_info': '', 'kind': 'statement' - } ) ), - is_not( has_item( CompletionEntryMatcher( 'b' ) ) ) - ) ) - - -@SharedYcmd -def GetCompletions_UnicodeDescription_test( app ): - filepath = PathToTestFile( 'unicode.py' ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'python', - contents = ReadFile( filepath ), - force_semantic = True, - line_num = 5, - column_num = 3 ) - - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - assert_that( results, has_item( - has_entry( 'detailed_info', contains_string( u'aafäö' ) ) ) ) - - -@SharedYcmd -def GetCompletions_NoSuggestions_Fallback_test( app ): - # Python completer doesn't raise NO_COMPLETIONS_MESSAGE, so this is a - # different code path to the Clang completer cases - - # TESTCASE2 (general_fallback/lang_python.py) - RunTest( app, { - 'description': 'param jedi does not know about (id). query="a_p"', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'general_fallback', - 'lang_python.py' ), - 'line_num' : 28, - 'column_num': 20, - 'force_semantic': False, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'a_parameter', '[ID]' ), - CompletionEntryMatcher( 'another_parameter', '[ID]' ), - ), - 'errors': empty() - } ) - } - } ) + } ), + CompletionEntryMatcher( 'b', + 'self.b = 2', + { + 'detailed_info': '', + 'kind': 'statement' + } ) + ) ) + + completion_data = BuildRequest( filepath = filepath, + filetype = 'python', + contents = ReadFile( filepath ), + line_num = 7, + column_num = 4 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, + all_of( + has_item( + CompletionEntryMatcher( 'a', + 'self.a = 1', + { + 'detailed_info': '', + 'kind': 'statement' + } ) ), + is_not( has_item( CompletionEntryMatcher( 'b' ) ) ) + ) ) + + + @SharedYcmd + def test_GetCompletions_UnicodeDescription( self, app ): + filepath = PathToTestFile( 'unicode.py' ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'python', + contents = ReadFile( filepath ), + force_semantic = True, + line_num = 5, + column_num = 3 ) + + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + assert_that( results, has_item( + has_entry( 'detailed_info', contains_string( u'aafäö' ) ) ) ) + + + @SharedYcmd + def test_GetCompletions_NoSuggestions_Fallback( self, app ): + # Python completer doesn't raise NO_COMPLETIONS_MESSAGE, so this is a + # different code path to the Clang completer cases + + # TESTCASE2 (general_fallback/lang_python.py) + RunTest( app, { + 'description': 'param jedi does not know about (id). query="a_p"', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'general_fallback', + 'lang_python.py' ), + 'line_num' : 28, + 'column_num': 20, + 'force_semantic': False, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'a_parameter', '[ID]' ), + CompletionEntryMatcher( 'another_parameter', '[ID]' ), + ), + 'errors': empty() + } ) + } + } ) -@SharedYcmd -def GetCompletions_Unicode_InLine_test( app ): - RunTest( app, { - 'description': 'return completions for strings with multi-byte chars', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'unicode.py' ), - 'line_num' : 7, - 'column_num': 14 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( - 'center', 'def center(width: int, fillchar: str=...)' ) - ), - 'errors': empty() - } ) - } - } ) + @SharedYcmd + def test_GetCompletions_Unicode_InLine( self, app ): + RunTest( app, { + 'description': 'return completions for strings with multi-byte chars', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'unicode.py' ), + 'line_num' : 7, + 'column_num': 14 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( + 'center', 'def center(width: int, fillchar: str=...)' ) + ), + 'errors': empty() + } ) + } + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'project', 'empty_extra_conf.py' ) } ) -def GetCompletions_SysPath_EmptyExtraConf_test( app ): - RunTest( app, { - 'description': 'Module is not added to sys.path through extra conf file', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'project', '__main__.py' ), - 'line_num' : 3, - 'column_num': 8 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': empty() - } ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'project', 'empty_extra_conf.py' ) } ) + def test_GetCompletions_SysPath_EmptyExtraConf( self, app ): + RunTest( app, { + 'description': 'Module is not added to sys.path through extra conf file', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'project', '__main__.py' ), + 'line_num' : 3, + 'column_num': 8 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': empty() + } ) + } + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'project', 'settings_extra_conf.py' ) } ) -def GetCompletions_SysPath_SettingsFunctionInExtraConf_test( app ): - RunTest( app, { - 'description': 'Module is added to sys.path through the Settings ' - 'function in extra conf file', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'project', '__main__.py' ), - 'line_num' : 3, - 'column_num': 8 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( - CompletionEntryMatcher( 'SOME_CONSTANT', 'SOME_CONSTANT = 1' ) - ), - 'errors': empty() - } ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'project', 'settings_extra_conf.py' ) } ) + def test_GetCompletions_SysPath_SettingsFunctionInExtraConf( self, app ): + RunTest( app, { + 'description': 'Module is added to sys.path through the Settings ' + 'function in extra conf file', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'project', '__main__.py' ), + 'line_num' : 3, + 'column_num': 8 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( + CompletionEntryMatcher( 'SOME_CONSTANT', 'SOME_CONSTANT = 1' ) + ), + 'errors': empty() + } ) + } + } ) -@IsolatedYcmd( { - 'global_ycm_extra_conf': PathToTestFile( 'project', - 'settings_extra_conf.py' ), - 'disable_signature_help': True -} ) -def GetCompletions_SysPath_SettingsFunctionInExtraConf_DisableSig_test( app ): - RunTest( app, { - 'description': 'Module is added to sys.path through the Settings ' - 'function in extra conf file', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'project', '__main__.py' ), - 'line_num' : 3, - 'column_num': 8 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( - CompletionEntryMatcher( 'SOME_CONSTANT', 'SOME_CONSTANT = 1' ) - ), - 'errors': empty() - } ) - } + @IsolatedYcmd( { + 'global_ycm_extra_conf': PathToTestFile( 'project', + 'settings_extra_conf.py' ), + 'disable_signature_help': True } ) + def test_GetCompletions_SysPath_SettingsFunctionInExtraConf_DisableSig( + self, app ): + RunTest( app, { + 'description': 'Module is added to sys.path through the Settings ' + 'function in extra conf file', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'project', '__main__.py' ), + 'line_num' : 3, + 'column_num': 8 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( + CompletionEntryMatcher( 'SOME_CONSTANT', 'SOME_CONSTANT = 1' ) + ), + 'errors': empty() + } ) + } + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'project', 'settings_empty_extra_conf.py' ) } ) -def GetCompletions_SysPath_SettingsEmptyInExtraConf_test( app ): - RunTest( app, { - 'description': 'The Settings function returns an empty dictionary ' - 'in extra conf file', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'project', '__main__.py' ), - 'line_num' : 3, - 'column_num': 8 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': empty() - } ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'project', + 'settings_empty_extra_conf.py' ) } ) + def test_GetCompletions_SysPath_SettingsEmptyInExtraConf( self, app ): + RunTest( app, { + 'description': 'The Settings function returns an empty dictionary ' + 'in extra conf file', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'project', '__main__.py' ), + 'line_num' : 3, + 'column_num': 8 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': empty() + } ) + } + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'project', 'settings_none_extra_conf.py' ) } ) -def GetCompletions_SysPath_SettingsNoneInExtraConf_test( app ): - RunTest( app, { - 'description': 'The Settings function returns None in extra conf file', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'project', '__main__.py' ), - 'line_num' : 3, - 'column_num': 8 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': empty() - } ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'project', + 'settings_none_extra_conf.py' ) } ) + def test_GetCompletions_SysPath_SettingsNoneInExtraConf( self, app ): + RunTest( app, { + 'description': 'The Settings function returns None in extra conf file', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'project', '__main__.py' ), + 'line_num' : 3, + 'column_num': 8 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': empty() + } ) + } + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'project', 'sys_path_extra_conf.py' ) } ) -def GetCompletions_SysPath_PythonSysPathInExtraConf_test( app ): - RunTest( app, { - 'description': 'Module is added to sys.path through the PythonSysPath ' - 'function in extra conf file', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'project', '__main__.py' ), - 'line_num' : 3, - 'column_num': 8 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( - CompletionEntryMatcher( 'SOME_CONSTANT', 'SOME_CONSTANT = 1' ) - ), - 'errors': empty() - } ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'project', 'sys_path_extra_conf.py' ) } ) + def test_GetCompletions_SysPath_PythonSysPathInExtraConf( self, app ): + RunTest( app, { + 'description': 'Module is added to sys.path through the PythonSysPath ' + 'function in extra conf file', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'project', '__main__.py' ), + 'line_num' : 3, + 'column_num': 8 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( + CompletionEntryMatcher( 'SOME_CONSTANT', 'SOME_CONSTANT = 1' ) + ), + 'errors': empty() + } ) + } + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'project', 'invalid_python_extra_conf.py' ) } ) -def GetCompletions_PythonInterpreter_InvalidPythonInExtraConf_test( app ): - RunTest( app, { - 'description': 'Python interpreter path specified in extra conf file ' - 'does not exist', - 'request': { + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'project', + 'invalid_python_extra_conf.py' ) } ) + def test_GetCompletions_PythonInterpreter_InvalidPythonInExtraConf( + self, app ): + RunTest( app, { + 'description': 'Python interpreter path specified in extra conf file ' + 'does not exist', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'project', '__main__.py' ), + 'line_num' : 3, + 'column_num': 8 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': empty(), + 'errors': contains_exactly( + ErrorMatcher( RuntimeError, + 'Cannot find Python interpreter path ' + '/non/existing/python.' ) + ) + } ) + } + } ) + + + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'project', 'client_data_extra_conf.py' ) } ) + def test_GetCompletions_PythonInterpreter_ExtraConfData( self, app ): + filepath = PathToTestFile( 'project', '__main__.py' ) + contents = ReadFile( filepath ) + request = { 'filetype' : 'python', - 'filepath' : PathToTestFile( 'project', '__main__.py' ), + 'filepath' : filepath, + 'contents' : contents, 'line_num' : 3, 'column_num': 8 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': empty(), - 'errors': contains_exactly( - ErrorMatcher( RuntimeError, - 'Cannot find Python interpreter path ' - '/non/existing/python.' ) - ) - } ) } - } ) - -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'project', 'client_data_extra_conf.py' ) } ) -def GetCompletions_PythonInterpreter_ExtraConfData_test( app ): - filepath = PathToTestFile( 'project', '__main__.py' ) - contents = ReadFile( filepath ) - request = { - 'filetype' : 'python', - 'filepath' : filepath, - 'contents' : contents, - 'line_num' : 3, - 'column_num': 8 - } - - # Complete with a sys.path specified by the client that contains the path to a - # third-party module. - completion_request = CombineRequest( request, { - 'extra_conf_data': { - 'sys_path': [ PathToTestFile( 'project', 'third_party' ) ] - } - } ) - - assert_that( - app.post_json( '/completions', completion_request ).json, - has_entries( { - 'completions': has_item( - CompletionEntryMatcher( 'SOME_CONSTANT', 'SOME_CONSTANT = 1' ) - ), - 'errors': empty() + # Complete with a sys.path specified by the client that contains the path + # to a third-party module. + completion_request = CombineRequest( request, { + 'extra_conf_data': { + 'sys_path': [ PathToTestFile( 'project', 'third_party' ) ] + } } ) - ) - - # Complete at the same position but no sys.path from the client. - completion_request = CombineRequest( request, {} ) - assert_that( - app.post_json( '/completions', completion_request ).json, - has_entries( { - 'completions': empty(), - 'errors': empty() - } ) - ) - - -@SharedYcmd -def GetCompletions_NumpyDoc_test( app ): - RunTest( app, { - 'description': 'Type hinting is working with docstrings ' - 'in the Numpy format', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'numpy_docstring.py' ), - 'line_num' : 11, - 'column_num': 18 - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'SomeMethod', 'def SomeMethod()' ), + assert_that( + app.post_json( '/completions', completion_request ).json, + has_entries( { + 'completions': has_item( + CompletionEntryMatcher( 'SOME_CONSTANT', 'SOME_CONSTANT = 1' ) ), 'errors': empty() } ) - } - } ) + ) + # Complete at the same position but no sys.path from the client. + completion_request = CombineRequest( request, {} ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + assert_that( + app.post_json( '/completions', completion_request ).json, + has_entries( { + 'completions': empty(), + 'errors': empty() + } ) + ) + + + @SharedYcmd + def test_GetCompletions_NumpyDoc( self, app ): + RunTest( app, { + 'description': 'Type hinting is working with docstrings ' + 'in the Numpy format', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'numpy_docstring.py' ), + 'line_num' : 11, + 'column_num': 18 + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'SomeMethod', 'def SomeMethod()' ), + ), + 'errors': empty() + } ) + } + } ) diff --git a/ycmd/tests/python/signature_help_test.py b/ycmd/tests/python/signature_help_test.py index 1148abb3b8..2c471bc414 100644 --- a/ycmd/tests/python/signature_help_test.py +++ b/ycmd/tests/python/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -20,10 +20,11 @@ empty, equal_to, has_entries ) +from unittest import TestCase import requests from ycmd.utils import ReadFile -from ycmd.tests.python import PathToTestFile, IsolatedYcmd, SharedYcmd +from ycmd.tests.python import PathToTestFile, IsolatedYcmd, SharedYcmd, setUpModule # noqa from ycmd.tests.test_utils import ( CombineRequest, ParameterMatcher, SignatureMatcher, @@ -64,234 +65,230 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def Signature_Help_Available_test( app ): - response = app.get( '/signature_help_available', - { 'subserver': 'python' } ).json - assert_that( response, SignatureAvailableMatcher( 'YES' ) ) +class SignatureHelpTest( TestCase ): + @SharedYcmd + def test_Signature_Help_Available( self, app ): + response = app.get( '/signature_help_available', + { 'subserver': 'python' } ).json + assert_that( response, SignatureAvailableMatcher( 'YES' ) ) -@SharedYcmd -def SignatureHelp_MethodTrigger_test( app ): - RunTest( app, { - 'description': 'Trigger after (', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'general_fallback', - 'lang_python.py' ), - 'line_num' : 6, - 'column_num': 10, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'def hack( obj )', - [ ParameterMatcher( 10, 13 ) ] ) - ), - } ), - } ) - } - } ) - - -@IsolatedYcmd( { 'disable_signature_help': True } ) -def SignatureHelp_MethodTrigger_Disabled_test( app ): - RunTest( app, { - 'description': 'do not Trigger after (', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'general_fallback', - 'lang_python.py' ), - 'line_num' : 6, - 'column_num': 10, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty(), - } ), - } ) - } - } ) + @SharedYcmd + def test_SignatureHelp_MethodTrigger( self, app ): + RunTest( app, { + 'description': 'Trigger after (', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'general_fallback', + 'lang_python.py' ), + 'line_num' : 6, + 'column_num': 10, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'def hack( obj )', + [ ParameterMatcher( 10, 13 ) ] ) + ), + } ), + } ) + } + } ) -@SharedYcmd -def SignatureHelp_MultipleParameters_test( app ): - RunTest( app, { - 'description': 'Trigger after ,', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'signature_help.py' ), - 'line_num' : 14, - 'column_num': 50, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( 'def MultipleArguments( a, b, c )', - [ ParameterMatcher( 23, 24 ), - ParameterMatcher( 26, 27 ), - ParameterMatcher( 29, 30 ) ] ) - ), - } ), - } ) - } - } ) + @IsolatedYcmd( { 'disable_signature_help': True } ) + def test_SignatureHelp_MethodTrigger_Disabled( self, app ): + RunTest( app, { + 'description': 'do not Trigger after (', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'general_fallback', + 'lang_python.py' ), + 'line_num' : 6, + 'column_num': 10, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty(), + } ), + } ) + } + } ) -@SharedYcmd -def SignatureHelp_CallWithinCall_test( app ): - RunTest( app, { - 'description': 'Trigger after , within a call-within-a-call', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'signature_help.py' ), - 'line_num' : 14, - 'column_num': 43, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( 'def center( width: int, fillchar: str=... )', - [ ParameterMatcher( 12, 22 ), - ParameterMatcher( 24, 41 ) ] ) - ), - } ), - } ) - } - } ) + @SharedYcmd + def test_SignatureHelp_MultipleParameters( self, app ): + RunTest( app, { + 'description': 'Trigger after ,', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'signature_help.py' ), + 'line_num' : 14, + 'column_num': 50, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( 'def MultipleArguments( a, b, c )', + [ ParameterMatcher( 23, 24 ), + ParameterMatcher( 26, 27 ), + ParameterMatcher( 29, 30 ) ] ) + ), + } ), + } ) + } + } ) -@SharedYcmd -def SignatureHelp_Constructor_test( app ): - RunTest( app, { - 'description': 'Trigger after , within a call-within-a-call', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'signature_help.py' ), - 'line_num' : 14, - 'column_num': 61, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'class Class( argument )', - [ ParameterMatcher( 13, 21 ) ] ) - ), - } ), - } ) - } - } ) + @SharedYcmd + def test_SignatureHelp_CallWithinCall( self, app ): + RunTest( app, { + 'description': 'Trigger after , within a call-within-a-call', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'signature_help.py' ), + 'line_num' : 14, + 'column_num': 43, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( 'def center( width: int, fillchar: str=... )', + [ ParameterMatcher( 12, 22 ), + ParameterMatcher( 24, 41 ) ] ) + ), + } ), + } ) + } + } ) -@SharedYcmd -def SignatureHelp_MultipleDefinitions_test( app ): - RunTest( app, { - 'description': 'Jedi returns multiple signatures - both active', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'signature_help.py' ), - 'line_num' : 30, - 'column_num': 27, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 2, - 'signatures': contains_exactly( - SignatureMatcher( 'def MultipleDefinitions( a, b, c )', - [ ParameterMatcher( 25, 26 ), - ParameterMatcher( 28, 29 ), - ParameterMatcher( 31, 32 ) ] ), + @SharedYcmd + def test_SignatureHelp_Constructor( self, app ): + RunTest( app, { + 'description': 'Trigger after , within a call-within-a-call', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'signature_help.py' ), + 'line_num' : 14, + 'column_num': 61, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'class Class( argument )', + [ ParameterMatcher( 13, 21 ) ] ) + ), + } ), + } ) + } + } ) - SignatureMatcher( 'def MultipleDefinitions( many,' - ' more,' - ' arguments,' - ' to,' - ' this,' - ' one )', - [ ParameterMatcher( 25, 29 ), - ParameterMatcher( 31, 35 ), - ParameterMatcher( 37, 46 ), - ParameterMatcher( 48, 50 ), - ParameterMatcher( 52, 56 ), - ParameterMatcher( 58, 61 ) ] ) - ), - } ), - } ) - } - } ) + @SharedYcmd + def test_SignatureHelp_MultipleDefinitions( self, app ): + RunTest( app, { + 'description': 'Jedi returns multiple signatures - both active', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'signature_help.py' ), + 'line_num' : 30, + 'column_num': 27, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 2, + 'signatures': contains_exactly( + SignatureMatcher( 'def MultipleDefinitions( a, b, c )', + [ ParameterMatcher( 25, 26 ), + ParameterMatcher( 28, 29 ), + ParameterMatcher( 31, 32 ) ] ), -@SharedYcmd -def SignatureHelp_MultipleDefinitions_OneActive_test( app ): - RunTest( app, { - 'description': 'Jedi returns multiple signatures - both active', - 'request': { - 'filetype' : 'python', - 'filepath' : PathToTestFile( 'signature_help.py' ), - 'line_num' : 30, - 'column_num': 30, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 1, - 'activeParameter': 3, - 'signatures': contains_exactly( - SignatureMatcher( 'def MultipleDefinitions( a, b, c )', - [ ParameterMatcher( 25, 26 ), - ParameterMatcher( 28, 29 ), - ParameterMatcher( 31, 32 ) ] ), + SignatureMatcher( 'def MultipleDefinitions( many,' + ' more,' + ' arguments,' + ' to,' + ' this,' + ' one )', + [ ParameterMatcher( 25, 29 ), + ParameterMatcher( 31, 35 ), + ParameterMatcher( 37, 46 ), + ParameterMatcher( 48, 50 ), + ParameterMatcher( 52, 56 ), + ParameterMatcher( 58, 61 ) ] ) + ), + } ), + } ) + } + } ) - SignatureMatcher( 'def MultipleDefinitions( many,' - ' more,' - ' arguments,' - ' to,' - ' this,' - ' one )', - [ ParameterMatcher( 25, 29 ), - ParameterMatcher( 31, 35 ), - ParameterMatcher( 37, 46 ), - ParameterMatcher( 48, 50 ), - ParameterMatcher( 52, 56 ), - ParameterMatcher( 58, 61 ) ] ) - ), - } ), - } ) - } - } ) + @SharedYcmd + def test_SignatureHelp_MultipleDefinitions_OneActive( self, app ): + RunTest( app, { + 'description': 'Jedi returns multiple signatures - both active', + 'request': { + 'filetype' : 'python', + 'filepath' : PathToTestFile( 'signature_help.py' ), + 'line_num' : 30, + 'column_num': 30, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 1, + 'activeParameter': 3, + 'signatures': contains_exactly( + SignatureMatcher( 'def MultipleDefinitions( a, b, c )', + [ ParameterMatcher( 25, 26 ), + ParameterMatcher( 28, 29 ), + ParameterMatcher( 31, 32 ) ] ), -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + SignatureMatcher( 'def MultipleDefinitions( many,' + ' more,' + ' arguments,' + ' to,' + ' this,' + ' one )', + [ ParameterMatcher( 25, 29 ), + ParameterMatcher( 31, 35 ), + ParameterMatcher( 37, 46 ), + ParameterMatcher( 48, 50 ), + ParameterMatcher( 52, 56 ), + ParameterMatcher( 58, 61 ) ] ) + ), + } ), + } ) + } + } ) diff --git a/ycmd/tests/python/subcommands_test.py b/ycmd/tests/python/subcommands_test.py index b7ad2dd6b1..7885bc1e46 100644 --- a/ycmd/tests/python/subcommands_test.py +++ b/ycmd/tests/python/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -25,12 +25,13 @@ matches_regexp ) from pprint import pformat from unittest.mock import patch +from unittest import TestCase +import itertools import os -import pytest import requests from ycmd.utils import ReadFile -from ycmd.tests.python import PathToTestFile, SharedYcmd +from ycmd.tests.python import PathToTestFile, SharedYcmd, setUpModule # noqa from ycmd.tests.test_utils import ( BuildRequest, CombineRequest, ChunkMatcher, @@ -119,105 +120,6 @@ def Subcommands_GoTo( app, test, command ): } ) -@pytest.mark.parametrize( 'cmd', [ 'GoTo', - 'GoToDefinition', - 'GoToDeclaration' ] ) -@pytest.mark.parametrize( 'test', [ - # Nothing - { 'request': ( 'basic.py', 3, 5 ), 'response': 'Can\'t jump to ' - 'definition.' }, - # Keyword - { 'request': ( 'basic.py', 4, 3 ), 'response': 'Can\'t jump to ' - 'definition.' }, - # Builtin - { 'request': ( 'basic.py', 1, 4 ), 'response': ( 'basic.py', 1, 1 ) }, - { 'request': ( 'basic.py', 1, 12 ), 'response': ( TYPESHED_PATH, 947, 7 ) }, - { 'request': ( 'basic.py', 2, 2 ), 'response': ( 'basic.py', 1, 1 ) }, - # Class - { 'request': ( 'basic.py', 4, 7 ), 'response': ( 'basic.py', 4, 7 ) }, - { 'request': ( 'basic.py', 4, 11 ), 'response': ( 'basic.py', 4, 7 ) }, - { 'request': ( 'basic.py', 7, 19 ), 'response': ( 'basic.py', 4, 7 ) }, - # Instance - { 'request': ( 'basic.py', 7, 1 ), 'response': ( 'basic.py', 7, 1 ) }, - { 'request': ( 'basic.py', 7, 11 ), 'response': ( 'basic.py', 7, 1 ) }, - { 'request': ( 'basic.py', 8, 23 ), 'response': ( 'basic.py', 7, 1 ) }, - # Instance reference - { 'request': ( 'basic.py', 8, 1 ), 'response': ( 'basic.py', 8, 1 ) }, - { 'request': ( 'basic.py', 8, 5 ), 'response': ( 'basic.py', 8, 1 ) }, - { 'request': ( 'basic.py', 9, 12 ), 'response': ( 'basic.py', 8, 1 ) }, - # Member access - { 'request': ( 'child.py', 4, 12 ), - 'response': ( 'parent.py', 2, 7 ) }, - # Builtin from different file - { 'request': ( 'multifile1.py', 2, 30 ), - 'response': ( 'multifile2.py', 1, 24 ) }, - { 'request': ( 'multifile1.py', 4, 5 ), - 'response': ( 'multifile1.py', 2, 24 ) }, - # Function from different file - { 'request': ( 'multifile1.py', 1, 24 ), - 'response': ( 'multifile3.py', 3, 5 ) }, - { 'request': ( 'multifile1.py', 5, 4 ), - 'response': ( 'multifile1.py', 1, 24 ) }, - # Alias from different file - { 'request': ( 'multifile1.py', 2, 47 ), - 'response': ( 'multifile2.py', 1, 51 ) }, - { 'request': ( 'multifile1.py', 6, 14 ), - 'response': ( 'multifile1.py', 2, 36 ) }, - # Absolute import from nested module - { 'request': ( os.path.join( 'nested_import', 'importer.py' ), 1, 19 ), - 'response': ( 'basic.py', 4, 7 ) }, - { 'request': ( os.path.join( 'nested_import', 'importer.py' ), 2, 40 ), - 'response': ( os.path.join( 'nested_import', 'to_import.py' ), 1, 5 ) }, - # Relative within nested module - { 'request': ( os.path.join( 'nested_import', 'importer.py' ), 3, 28 ), - 'response': ( os.path.join( 'nested_import', 'to_import.py' ), 4, 5 ) }, - ] ) -@SharedYcmd -def Subcommands_GoTo_test( app, cmd, test ): - Subcommands_GoTo( app, test, cmd ) - - -@pytest.mark.parametrize( 'test', [ - { 'request': ( 'basic.py', 1, 1, [ 'MyClass' ] ), - 'response': ( 'basic.py', 4, 7 ) }, - - { 'request': ( 'basic.py', 1, 1, [ 'class C' ] ), - 'response': ( 'child.py', 2, 7, { 'description': 'class C' } ) }, - - { 'request': ( 'basic.py', 1, 1, [ 'C.c' ] ), - 'response': [ - ( 'child.py', 3, 7, { 'description': 'def c' } ), - ( 'parent.py', 3, 7, { 'description': 'def c' } ) - ] }, - - { 'request': ( 'basic.py', 1, 1, [ 'nothing_here_mate' ] ), - 'response': 'Symbol not found' } -] ) -@SharedYcmd -def Subcommands_GoToSymbol_test( app, test ): - Subcommands_GoTo( app, test, 'GoToSymbol' ) - - -@pytest.mark.parametrize( 'test', [ - { 'request': ( 'basic.py', 1, 4 ), - 'response': 'Can\'t jump to definition.', 'cmd': 'GoTo' }, - { 'request': ( 'basic.py', 1, 4 ), - 'response': 'Can\'t find references.', 'cmd': 'GoToReferences' }, - { 'request': ( 'basic.py', 1, 4 ), - 'response': 'Can\'t jump to type definition.', 'cmd': 'GoToType' } -] ) -@SharedYcmd -def Subcommands_GoTo_SingleInvalidJediDefinition_test( app, test ): - with patch( 'ycmd.completers.python.python_completer.jedi.Script.infer', - return_value = [ JediDef() ] ): - with patch( 'ycmd.completers.python.python_completer.jedi.Script.goto', - return_value = [ JediDef() ] ): - with patch( 'ycmd.completers.python.python_completer.' - 'jedi.Script.get_references', - return_value = [ JediDef() ] ): - Subcommands_GoTo( app, test, test.pop( 'cmd' ) ) - - def Subcommands_GetType( app, position, expected_message ): filepath = PathToTestFile( 'GetType.py' ) contents = ReadFile( filepath ) @@ -235,227 +137,329 @@ def Subcommands_GetType( app, position, expected_message ): ) -@pytest.mark.parametrize( 'position,expected_message', [ - ( ( 11, 7 ), 'instance int' ), - ( ( 11, 20 ), 'def some_function()' ), - ( ( 12, 15 ), 'class SomeClass(*args, **kwargs)' ), - ( ( 13, 8 ), 'instance SomeClass' ), - ( ( 13, 17 ), 'def SomeMethod(first_param, second_param)' ), - ( ( 19, 4 ), matches_regexp( '^(instance str, instance int|' - 'instance int, instance str)$' ) ) - ] ) -@SharedYcmd -def Subcommands_GetType_test( app, position, expected_message ): - Subcommands_GetType( app, position, expected_message ) - - -@SharedYcmd -def Subcommands_GetType_NoTypeInformation_test( app ): - filepath = PathToTestFile( 'GetType.py' ) - contents = ReadFile( filepath ) +class SubcommandsTest( TestCase ): + @SharedYcmd + def test_Subcommands_GoTo( self, app ): + for cmd, test in itertools.product( + [ 'GoTo', 'GoToDefinition', 'GoToDeclaration' ], + [ + # Nothing + { 'request': ( 'basic.py', 3, 5 ), + 'response': 'Can\'t jump to definition.' }, + # Keyword + { 'request': ( 'basic.py', 4, 3 ), + 'response': 'Can\'t jump to definition.' }, + # Builtin + { 'request': ( 'basic.py', 1, 4 ), + 'response': ( 'basic.py', 1, 1 ) }, + { 'request': ( 'basic.py', 1, 12 ), + 'response': ( TYPESHED_PATH, 947, 7 ) }, + { 'request': ( 'basic.py', 2, 2 ), + 'response': ( 'basic.py', 1, 1 ) }, + # Class + { 'request': ( 'basic.py', 4, 7 ), + 'response': ( 'basic.py', 4, 7 ) }, + { 'request': ( 'basic.py', 4, 11 ), + 'response': ( 'basic.py', 4, 7 ) }, + { 'request': ( 'basic.py', 7, 19 ), + 'response': ( 'basic.py', 4, 7 ) }, + # Instance + { 'request': ( 'basic.py', 7, 1 ), + 'response': ( 'basic.py', 7, 1 ) }, + { 'request': ( 'basic.py', 7, 11 ), + 'response': ( 'basic.py', 7, 1 ) }, + { 'request': ( 'basic.py', 8, 23 ), + 'response': ( 'basic.py', 7, 1 ) }, + # Instance reference + { 'request': ( 'basic.py', 8, 1 ), + 'response': ( 'basic.py', 8, 1 ) }, + { 'request': ( 'basic.py', 8, 5 ), + 'response': ( 'basic.py', 8, 1 ) }, + { 'request': ( 'basic.py', 9, 12 ), + 'response': ( 'basic.py', 8, 1 ) }, + # Member access + { 'request': ( 'child.py', 4, 12 ), + 'response': ( 'parent.py', 2, 7 ) }, + # Builtin from different file + { 'request': ( 'multifile1.py', 2, 30 ), + 'response': ( 'multifile2.py', 1, 24 ) }, + { 'request': ( 'multifile1.py', 4, 5 ), + 'response': ( 'multifile1.py', 2, 24 ) }, + # Function from different file + { 'request': ( 'multifile1.py', 1, 24 ), + 'response': ( 'multifile3.py', 3, 5 ) }, + { 'request': ( 'multifile1.py', 5, 4 ), + 'response': ( 'multifile1.py', 1, 24 ) }, + # Alias from different file + { 'request': ( 'multifile1.py', 2, 47 ), + 'response': ( 'multifile2.py', 1, 51 ) }, + { 'request': ( 'multifile1.py', 6, 14 ), + 'response': ( 'multifile1.py', 2, 36 ) }, + # Absolute import from nested module + { 'request': ( os.path.join( 'nested_import', + 'importer.py' ), 1, 19 ), + 'response': ( 'basic.py', 4, 7 ) }, + { 'request': ( os.path.join( 'nested_import', + 'importer.py' ), 2, 40 ), + 'response': ( os.path.join( 'nested_import', + 'to_import.py' ), 1, 5 ) }, + # Relative within nested module + { 'request': ( os.path.join( 'nested_import', + 'importer.py' ), 3, 28 ), + 'response': ( os.path.join( 'nested_import', + 'to_import.py' ), 4, 5 ) }, + ] ): + with self.subTest( cmd = cmd, test = test ): + Subcommands_GoTo( app, test, cmd ) + + + @SharedYcmd + def test_Subcommands_GoToSymbol( self, app ): + for test in [ + { 'request': ( 'basic.py', 1, 1, [ 'MyClass' ] ), + 'response': ( 'basic.py', 4, 7 ) }, + + { 'request': ( 'basic.py', 1, 1, [ 'class C' ] ), + 'response': ( 'child.py', 2, 7, { 'description': 'class C' } ) }, + + { 'request': ( 'basic.py', 1, 1, [ 'C.c' ] ), + 'response': [ + ( 'child.py', 3, 7, { 'description': 'def c' } ), + ( 'parent.py', 3, 7, { 'description': 'def c' } ) + ] }, + + { 'request': ( 'basic.py', 1, 1, [ 'nothing_here_mate' ] ), + 'response': 'Symbol not found' } + ]: + with self.subTest( test = test ): + Subcommands_GoTo( app, test, 'GoToSymbol' ) + + + @SharedYcmd + def test_Subcommands_GoTo_SingleInvalidJediDefinition( self, app ): + for test in [ + { 'request': ( 'basic.py', 1, 4 ), + 'response': 'Can\'t jump to definition.', 'cmd': 'GoTo' }, + { 'request': ( 'basic.py', 1, 4 ), + 'response': 'Can\'t find references.', 'cmd': 'GoToReferences' }, + { 'request': ( 'basic.py', 1, 4 ), + 'response': 'Can\'t jump to type definition.', 'cmd': 'GoToType' } + ]: + with self.subTest( test = test ): + with patch( 'ycmd.completers.python.python_completer.' + 'jedi.Script.infer', + return_value = [ JediDef() ] ): + with patch( 'ycmd.completers.python.python_completer.' + 'jedi.Script.goto', + return_value = [ JediDef() ] ): + with patch( 'ycmd.completers.python.python_completer.' + 'jedi.Script.get_references', + return_value = [ JediDef() ] ): + Subcommands_GoTo( app, test, test.pop( 'cmd' ) ) + + + @SharedYcmd + def test_Subcommands_GetType( self, app ): + for position, expected_message in [ + ( ( 11, 7 ), 'instance int' ), + ( ( 11, 20 ), 'def some_function()' ), + ( ( 12, 15 ), 'class SomeClass(*args, **kwargs)' ), + ( ( 13, 8 ), 'instance SomeClass' ), + ( ( 13, 17 ), 'def SomeMethod(first_param, second_param)' ), + ( ( 19, 4 ), matches_regexp( '^(instance str, instance int|' + 'instance int, instance str)$' ) ) + ]: + with self.subTest( position = position, + expected_message = expected_message ): + Subcommands_GetType( app, position, expected_message ) + + + @SharedYcmd + def test_Subcommands_GetType_NoTypeInformation( self, app ): + filepath = PathToTestFile( 'GetType.py' ) + contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 6, - column_num = 3, - contents = contents, - command_arguments = [ 'GetType' ] ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 6, + column_num = 3, + contents = contents, + command_arguments = [ 'GetType' ] ) - response = app.post_json( '/run_completer_command', - command_data, - expect_errors = True ).json + response = app.post_json( '/run_completer_command', + command_data, + expect_errors = True ).json - assert_that( response, - ErrorMatcher( RuntimeError, 'No type information available.' ) ) + assert_that( response, + ErrorMatcher( RuntimeError, + 'No type information available.' ) ) -@SharedYcmd -def Subcommands_GetDoc_Method_test( app ): - # Testcase1 - filepath = PathToTestFile( 'GetDoc.py' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_Method( self, app ): + # Testcase1 + filepath = PathToTestFile( 'GetDoc.py' ) + contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 17, - column_num = 9, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 17, + column_num = 9, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - assert_that( - app.post_json( '/run_completer_command', command_data ).json, - has_entry( 'detailed_info', '_ModuleMethod()\n\n' - 'Module method docs\n' - 'Are dedented, like you might expect' ) - ) + assert_that( + app.post_json( '/run_completer_command', command_data ).json, + has_entry( 'detailed_info', '_ModuleMethod()\n\n' + 'Module method docs\n' + 'Are dedented, like you might expect' ) + ) -@SharedYcmd -def Subcommands_GetDoc_Class_test( app ): - filepath = PathToTestFile( 'GetDoc.py' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_Class( self, app ): + filepath = PathToTestFile( 'GetDoc.py' ) + contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 19, - column_num = 6, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 19, + column_num = 6, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', command_data ).json + response = app.post_json( '/run_completer_command', command_data ).json - assert_that( response, has_entry( - 'detailed_info', 'TestClass()\n\nClass Documentation', - ) ) + assert_that( response, has_entry( + 'detailed_info', 'TestClass()\n\nClass Documentation', + ) ) -@SharedYcmd -def Subcommands_GetDoc_WhitespaceOnly_test( app ): - filepath = PathToTestFile( 'GetDoc.py' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_WhitespaceOnly( self, app ): + filepath = PathToTestFile( 'GetDoc.py' ) + contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 27, - column_num = 10, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 27, + column_num = 10, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', - command_data, - expect_errors = True ).json + response = app.post_json( '/run_completer_command', + command_data, + expect_errors = True ).json - assert_that( response, - ErrorMatcher( RuntimeError, 'No documentation available.' ) ) + assert_that( response, + ErrorMatcher( RuntimeError, 'No documentation available.' ) ) -@SharedYcmd -def Subcommands_GetDoc_NoDocumentation_test( app ): - filepath = PathToTestFile( 'GetDoc.py' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GetDoc_NoDocumentation( self, app ): + filepath = PathToTestFile( 'GetDoc.py' ) + contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 8, - column_num = 23, - contents = contents, - command_arguments = [ 'GetDoc' ] ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 8, + column_num = 23, + contents = contents, + command_arguments = [ 'GetDoc' ] ) - response = app.post_json( '/run_completer_command', - command_data, - expect_errors = True ).json + response = app.post_json( '/run_completer_command', + command_data, + expect_errors = True ).json - assert_that( response, - ErrorMatcher( RuntimeError, 'No documentation available.' ) ) + assert_that( response, + ErrorMatcher( RuntimeError, 'No documentation available.' ) ) -@pytest.mark.parametrize( 'test', [ - { 'request': ( 'basic.py', 2, 1 ), 'response': ( TYPESHED_PATH, 947, 7 ) }, - { 'request': ( 'basic.py', 8, 1 ), 'response': ( 'basic.py', 4, 7 ) }, - { 'request': ( 'basic.py', 3, 1 ), - 'response': 'Can\'t jump to type definition.' }, - ] ) -@SharedYcmd -def Subcommands_GoToType_test( app, test ): - Subcommands_GoTo( app, test, 'GoToType' ) + @SharedYcmd + def test_Subcommands_GoToType( self, app ): + for test in [ + { 'request': ( 'basic.py', 2, 1 ), + 'response': ( TYPESHED_PATH, 947, 7 ) }, + { 'request': ( 'basic.py', 8, 1 ), + 'response': ( 'basic.py', 4, 7 ) }, + { 'request': ( 'basic.py', 3, 1 ), + 'response': 'Can\'t jump to type definition.' }, + ]: + with self.subTest( test = test ): + Subcommands_GoTo( app, test, 'GoToType' ) -@SharedYcmd -def Subcommands_GoToReferences_Function_test( app ): - filepath = PathToTestFile( 'goto', 'references.py' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GoToReferences_Function( self, app ): + filepath = PathToTestFile( 'goto', 'references.py' ) + contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 4, - column_num = 5, - contents = contents, - command_arguments = [ 'GoToReferences' ] ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 4, + column_num = 5, + contents = contents, + command_arguments = [ 'GoToReferences' ] ) - assert_that( - app.post_json( '/run_completer_command', command_data ).json, - contains_exactly( - has_entries( { - 'filepath': filepath, - 'line_num': 1, - 'column_num': 5, - 'description': 'def f' - } ), - has_entries( { - 'filepath': filepath, - 'line_num': 4, - 'column_num': 5, - 'description': 'f' - } ), - has_entries( { - 'filepath': filepath, - 'line_num': 5, - 'column_num': 5, - 'description': 'f' - } ), - has_entries( { - 'filepath': filepath, - 'line_num': 6, - 'column_num': 5, - 'description': 'f' - } ) + assert_that( + app.post_json( '/run_completer_command', command_data ).json, + contains_exactly( + has_entries( { + 'filepath': filepath, + 'line_num': 1, + 'column_num': 5, + 'description': 'def f' + } ), + has_entries( { + 'filepath': filepath, + 'line_num': 4, + 'column_num': 5, + 'description': 'f' + } ), + has_entries( { + 'filepath': filepath, + 'line_num': 5, + 'column_num': 5, + 'description': 'f' + } ), + has_entries( { + 'filepath': filepath, + 'line_num': 6, + 'column_num': 5, + 'description': 'f' + } ) + ) ) - ) -@SharedYcmd -def Subcommands_GoToReferences_Builtin_test( app ): - filepath = PathToTestFile( 'goto', 'references.py' ) - contents = ReadFile( filepath ) + @SharedYcmd + def test_Subcommands_GoToReferences_Builtin( self, app ): + filepath = PathToTestFile( 'goto', 'references.py' ) + contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 8, - column_num = 1, - contents = contents, - command_arguments = [ 'GoToReferences' ] ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 8, + column_num = 1, + contents = contents, + command_arguments = [ 'GoToReferences' ] ) - assert_that( - app.post_json( '/run_completer_command', command_data ).json, - has_item( - has_entries( { - 'filepath': filepath, - 'line_num': 8, - 'column_num': 1, - 'description': 'str' - } ) + assert_that( + app.post_json( '/run_completer_command', command_data ).json, + has_item( + has_entries( { + 'filepath': filepath, + 'line_num': 8, + 'column_num': 1, + 'description': 'str' + } ) + ) ) - ) - - -@SharedYcmd -def Subcommands_GoToReferences_NoReferences_test( app ): - filepath = PathToTestFile( 'goto', 'references.py' ) - contents = ReadFile( filepath ) - - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 2, - column_num = 5, - contents = contents, - command_arguments = [ 'GoToReferences' ] ) - - response = app.post_json( '/run_completer_command', - command_data, - expect_errors = True ).json - assert_that( response, - ErrorMatcher( RuntimeError, 'Can\'t find references.' ) ) - - -@SharedYcmd -def Subcommands_GoToReferences_InvalidJediReferences_test( app ): - with patch( 'ycmd.completers.python.python_completer.' - 'jedi.Script.get_references', - return_value = [ JediDef(), - JediDef( 1, 1, PathToTestFile( 'foo.py' ) ) ] ): + @SharedYcmd + def test_Subcommands_GoToReferences_NoReferences( self, app ): filepath = PathToTestFile( 'goto', 'references.py' ) contents = ReadFile( filepath ) @@ -470,287 +474,309 @@ def Subcommands_GoToReferences_InvalidJediReferences_test( app ): command_data, expect_errors = True ).json - assert_that( response, contains_exactly( has_entries( { - 'line_num': 1, - 'column_num': 2, # Jedi columns are 0 based - 'filepath': PathToTestFile( 'foo.py' ) } ) ) ) + assert_that( response, + ErrorMatcher( RuntimeError, 'Can\'t find references.' ) ) + @SharedYcmd + def test_Subcommands_GoToReferences_InvalidJediReferences( self, app ): + with patch( 'ycmd.completers.python.python_completer.' + 'jedi.Script.get_references', + return_value = [ JediDef(), + JediDef( 1, + 1, + PathToTestFile( 'foo.py' ) ) ] ): -@SharedYcmd -def Subcommands_RefactorRename_NoNewName_test( app ): - filepath = PathToTestFile( 'basic.py' ) - contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 3, - column_num = 10, - contents = contents, - command_arguments = [ 'RefactorRename' ] ) + filepath = PathToTestFile( 'goto', 'references.py' ) + contents = ReadFile( filepath ) - response = app.post_json( '/run_completer_command', - command_data, - expect_errors = True ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - assert_that( response.json, - ErrorMatcher( RuntimeError, 'Must specify a new name' ) ) - - -@SharedYcmd -def Subcommands_RefactorRename_Same_test( app ): - filepath = PathToTestFile( 'basic.py' ) - contents = ReadFile( filepath ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 2, + column_num = 5, + contents = contents, + command_arguments = [ 'GoToReferences' ] ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 3, - column_num = 10, - contents = contents, - command_arguments = [ 'RefactorRename', - 'c' ] ) - - response = app.post_json( '/run_completer_command', - command_data ).json - - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( 'c', - LocationMatcher( filepath, 3, 10 ), - LocationMatcher( filepath, 3, 11 ) ), - ChunkMatcher( 'c', - LocationMatcher( filepath, 7, 3 ), - LocationMatcher( filepath, 7, 4 ) ) - ) - } ) - ) - } ) ) + response = app.post_json( '/run_completer_command', + command_data, + expect_errors = True ).json + assert_that( response, contains_exactly( has_entries( { + 'line_num': 1, + 'column_num': 2, # Jedi columns are 0 based + 'filepath': PathToTestFile( 'foo.py' ) } ) ) ) -@SharedYcmd -def Subcommands_RefactorRename_Longer_test( app ): - filepath = PathToTestFile( 'basic.py' ) - contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 3, - column_num = 10, - contents = contents, - command_arguments = [ 'RefactorRename', - 'booo' ] ) - - response = app.post_json( '/run_completer_command', - command_data ).json - - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( 'booo', - LocationMatcher( filepath, 3, 10 ), - LocationMatcher( filepath, 3, 11 ) ), - ChunkMatcher( 'booo', - LocationMatcher( filepath, 7, 3 ), - LocationMatcher( filepath, 7, 4 ) ) - ) - } ) - ) - } ) ) + @SharedYcmd + def test_Subcommands_RefactorRename_NoNewName( self, app ): + filepath = PathToTestFile( 'basic.py' ) + contents = ReadFile( filepath ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 3, + column_num = 10, + contents = contents, + command_arguments = [ 'RefactorRename' ] ) -@SharedYcmd -def Subcommands_RefactorRename_ShortenDelete_test( app ): - filepath = PathToTestFile( 'basic.py' ) - contents = ReadFile( filepath ) + response = app.post_json( '/run_completer_command', + command_data, + expect_errors = True ) - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 1, - column_num = 8, - contents = contents, - command_arguments = [ 'RefactorRename', - 'F' ] ) - - response = app.post_json( '/run_completer_command', - command_data ).json - - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( '', - LocationMatcher( filepath, 1, 8 ), - LocationMatcher( filepath, 1, 10 ) ), - ChunkMatcher( '', - LocationMatcher( filepath, 6, 6 ), - LocationMatcher( filepath, 6, 8 ) ) - ) - } ) - ) - } ) ) + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + assert_that( response.json, + ErrorMatcher( RuntimeError, 'Must specify a new name' ) ) -@SharedYcmd -def Subcommands_RefactorRename_Shorten_test( app ): - filepath = PathToTestFile( 'basic.py' ) - contents = ReadFile( filepath ) - - command_data = BuildRequest( filepath = filepath, - filetype = 'python', - line_num = 1, - column_num = 8, - contents = contents, - command_arguments = [ 'RefactorRename', - 'G' ] ) - - response = app.post_json( '/run_completer_command', - command_data ).json - - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( 'G', - LocationMatcher( filepath, 1, 7 ), - LocationMatcher( filepath, 1, 10 ) ), - ChunkMatcher( 'G', - LocationMatcher( filepath, 6, 5 ), - LocationMatcher( filepath, 6, 8 ) ) - ) - } ) - ) - } ) ) + @SharedYcmd + def test_Subcommands_RefactorRename_Same( self, app ): + filepath = PathToTestFile( 'basic.py' ) + contents = ReadFile( filepath ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 3, + column_num = 10, + contents = contents, + command_arguments = [ 'RefactorRename', + 'c' ] ) -@SharedYcmd -def Subcommands_RefactorRename_StartOfFile_test( app ): - one = PathToTestFile( 'rename', 'one.py' ) - contents = ReadFile( one ) + response = app.post_json( '/run_completer_command', + command_data ).json + + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( 'c', + LocationMatcher( filepath, 3, 10 ), + LocationMatcher( filepath, 3, 11 ) ), + ChunkMatcher( 'c', + LocationMatcher( filepath, 7, 3 ), + LocationMatcher( filepath, 7, 4 ) ) + ) + } ) + ) + } ) ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_Longer( self, app ): + filepath = PathToTestFile( 'basic.py' ) + contents = ReadFile( filepath ) - command_data = BuildRequest( filepath = one, - filetype = 'python', - line_num = 8, - column_num = 44, - contents = contents, - command_arguments = [ 'RefactorRename', - 'myvariable' ] ) - - response = app.post_json( '/run_completer_command', - command_data ).json - - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( 'myvariable', - LocationMatcher( one, 1, 1 ), - LocationMatcher( one, 1, 13 ) ), - ChunkMatcher( 'myvariable', - LocationMatcher( one, 8, 33 ), - LocationMatcher( one, 8, 45 ) ), - ChunkMatcher( 'myvariable', - LocationMatcher( one, 16, 32 ), - LocationMatcher( one, 16, 44 ) ) - ) - } ) - ) - } ) ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 3, + column_num = 10, + contents = contents, + command_arguments = [ 'RefactorRename', + 'booo' ] ) + response = app.post_json( '/run_completer_command', + command_data ).json + + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( 'booo', + LocationMatcher( filepath, 3, 10 ), + LocationMatcher( filepath, 3, 11 ) ), + ChunkMatcher( 'booo', + LocationMatcher( filepath, 7, 3 ), + LocationMatcher( filepath, 7, 4 ) ) + ) + } ) + ) + } ) ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_ShortenDelete( self, app ): + filepath = PathToTestFile( 'basic.py' ) + contents = ReadFile( filepath ) -@SharedYcmd -def Subcommands_RefactorRename_MultiFIle_test( app ): - one = PathToTestFile( 'rename', 'one.py' ) - two = PathToTestFile( 'rename', 'two.py' ) - contents = ReadFile( one ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 1, + column_num = 8, + contents = contents, + command_arguments = [ 'RefactorRename', + 'F' ] ) - command_data = BuildRequest( filepath = one, - filetype = 'python', - line_num = 4, - column_num = 7, - contents = contents, - command_arguments = [ 'RefactorRename', - 'OneLove' ] ) - - response = app.post_json( '/run_completer_command', - command_data ).json - - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( 'eLov', - LocationMatcher( one, 4, 9 ), - LocationMatcher( one, 4, 9 ) ), - ChunkMatcher( 'eLov', - LocationMatcher( one, 9, 24 ), - LocationMatcher( one, 9, 24 ) ), - ChunkMatcher( 'Love', - LocationMatcher( one, 16, 15 ), - LocationMatcher( one, 16, 15 ) ), - ChunkMatcher( 'eLov', - LocationMatcher( two, 4, 18 ), - LocationMatcher( two, 4, 18 ) ), - ChunkMatcher( 'Love', - LocationMatcher( two, 11, 14 ), - LocationMatcher( two, 11, 14 ) ) - ) - } ) - ) - } ) ) + response = app.post_json( '/run_completer_command', + command_data ).json + + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( '', + LocationMatcher( filepath, 1, 8 ), + LocationMatcher( filepath, 1, 10 ) ), + ChunkMatcher( '', + LocationMatcher( filepath, 6, 6 ), + LocationMatcher( filepath, 6, 8 ) ) + ) + } ) + ) + } ) ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_Shorten( self, app ): + filepath = PathToTestFile( 'basic.py' ) + contents = ReadFile( filepath ) + command_data = BuildRequest( filepath = filepath, + filetype = 'python', + line_num = 1, + column_num = 8, + contents = contents, + command_arguments = [ 'RefactorRename', + 'G' ] ) -@ExpectedFailure( 'file renames not implemented yet' ) -@SharedYcmd -def Subcommands_RefactorRename_Module_test( app ): - one = PathToTestFile( 'rename', 'one.py' ) - two = PathToTestFile( 'rename', 'two.py' ) - contents = ReadFile( two ) + response = app.post_json( '/run_completer_command', + command_data ).json + + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( 'G', + LocationMatcher( filepath, 1, 7 ), + LocationMatcher( filepath, 1, 10 ) ), + ChunkMatcher( 'G', + LocationMatcher( filepath, 6, 5 ), + LocationMatcher( filepath, 6, 8 ) ) + ) + } ) + ) + } ) ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_StartOfFile( self, app ): + one = PathToTestFile( 'rename', 'one.py' ) + contents = ReadFile( one ) + + command_data = BuildRequest( filepath = one, + filetype = 'python', + line_num = 8, + column_num = 44, + contents = contents, + command_arguments = [ 'RefactorRename', + 'myvariable' ] ) - command_data = BuildRequest( filepath = two, - filetype = 'python', - line_num = 1, - column_num = 8, - contents = contents, - command_arguments = [ 'RefactorRename', - 'pfivr' ] ) - - response = app.post_json( '/run_completer_command', - command_data ).json - - assert_that( response, has_entries( { - 'fixits': contains_exactly( - has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( 'pfivr', - LocationMatcher( two, 1, 8 ), - LocationMatcher( two, 1, 11 ) ), - ChunkMatcher( 'pfivr', - LocationMatcher( two, 4, 12 ), - LocationMatcher( two, 4, 15 ) ) - ), - 'files': contains_exactly( - has_entries( { - 'operation': 'RENAME', - 'old_file': one, - 'new_file': PathToTestFile( 'rename', 'pfivr.py' ) - } ) - ) - } ) - ) - } ) ) + response = app.post_json( '/run_completer_command', + command_data ).json + + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( 'myvariable', + LocationMatcher( one, 1, 1 ), + LocationMatcher( one, 1, 13 ) ), + ChunkMatcher( 'myvariable', + LocationMatcher( one, 8, 33 ), + LocationMatcher( one, 8, 45 ) ), + ChunkMatcher( 'myvariable', + LocationMatcher( one, 16, 32 ), + LocationMatcher( one, 16, 44 ) ) + ) + } ) + ) + } ) ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_MultiFIle( self, app ): + one = PathToTestFile( 'rename', 'one.py' ) + two = PathToTestFile( 'rename', 'two.py' ) + contents = ReadFile( one ) + + command_data = BuildRequest( filepath = one, + filetype = 'python', + line_num = 4, + column_num = 7, + contents = contents, + command_arguments = [ 'RefactorRename', + 'OneLove' ] ) + response = app.post_json( '/run_completer_command', + command_data ).json + + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( 'eLov', + LocationMatcher( one, 4, 9 ), + LocationMatcher( one, 4, 9 ) ), + ChunkMatcher( 'eLov', + LocationMatcher( one, 9, 24 ), + LocationMatcher( one, 9, 24 ) ), + ChunkMatcher( 'Love', + LocationMatcher( one, 16, 15 ), + LocationMatcher( one, 16, 15 ) ), + ChunkMatcher( 'eLov', + LocationMatcher( two, 4, 18 ), + LocationMatcher( two, 4, 18 ) ), + ChunkMatcher( 'Love', + LocationMatcher( two, 11, 14 ), + LocationMatcher( two, 11, 14 ) ) + ) + } ) + ) + } ) ) + + + @ExpectedFailure( 'file renames not implemented yet' ) + @SharedYcmd + def test_Subcommands_RefactorRename_Module( self, app ): + one = PathToTestFile( 'rename', 'one.py' ) + two = PathToTestFile( 'rename', 'two.py' ) + contents = ReadFile( two ) + + command_data = BuildRequest( filepath = two, + filetype = 'python', + line_num = 1, + column_num = 8, + contents = contents, + command_arguments = [ 'RefactorRename', + 'pfivr' ] ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + response = app.post_json( '/run_completer_command', + command_data ).json + + assert_that( response, has_entries( { + 'fixits': contains_exactly( + has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( 'pfivr', + LocationMatcher( two, 1, 8 ), + LocationMatcher( two, 1, 11 ) ), + ChunkMatcher( 'pfivr', + LocationMatcher( two, 4, 12 ), + LocationMatcher( two, 4, 15 ) ) + ), + 'files': contains_exactly( + has_entries( { + 'operation': 'RENAME', + 'old_file': one, + 'new_file': PathToTestFile( 'rename', 'pfivr.py' ) + } ) + ) + } ) + ) + } ) ) diff --git a/ycmd/tests/python_support_test.py b/ycmd/tests/python_support_test.py index 7a0a432a0f..366e9f2630 100644 --- a/ycmd/tests/python_support_test.py +++ b/ycmd/tests/python_support_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -21,6 +21,7 @@ from ycmd.tests.test_utils import ClangOnly from ycmd.utils import ToBytes, OnWindows, ImportCore +from unittest import TestCase ycm_core = ImportCore() @@ -34,58 +35,54 @@ COMPILE_COMMANDS_WORKING_DIR = 'C:\\dir' if OnWindows() else '/dir' -def GetUtf8String_Str_test(): - assert_that( b'fo\xc3\xb8', equal_to( ycm_core.GetUtf8String( 'foø' ) ) ) +class PythonSupportTest( TestCase ): + def test_GetUtf8String_Str( self ): + assert_that( b'fo\xc3\xb8', equal_to( ycm_core.GetUtf8String( 'foø' ) ) ) -def GetUtf8String_Bytes_test(): - assert_that( b'fo\xc3\xb8', - equal_to( ycm_core.GetUtf8String( bytes( 'foø', 'utf8' ) ) ) ) + def test_GetUtf8String_Bytes( self ): + assert_that( b'fo\xc3\xb8', + equal_to( ycm_core.GetUtf8String( bytes( 'foø', 'utf8' ) ) ) ) -def GetUtf8String_Int_test(): - assert_that( b'123', equal_to( ycm_core.GetUtf8String( 123 ) ) ) + def test_GetUtf8String_Int( self ): + assert_that( b'123', equal_to( ycm_core.GetUtf8String( 123 ) ) ) -@ClangOnly -def CompilationDatabase_Py3Bytes_test(): - cc_dir = ToBytes( PATH_TO_COMPILE_COMMANDS ) - cc_filename = ToBytes( os.path.join( COMPILE_COMMANDS_WORKING_DIR, - 'example.cc' ) ) + @ClangOnly + def test_CompilationDatabase_Py3Bytes( self ): + cc_dir = ToBytes( PATH_TO_COMPILE_COMMANDS ) + cc_filename = ToBytes( os.path.join( COMPILE_COMMANDS_WORKING_DIR, + 'example.cc' ) ) - # Ctor reads ycmd/tests/testdata/[unix|windows]/compile_commands.json - db = ycm_core.CompilationDatabase( cc_dir ) - info = db.GetCompilationInfoForFile( cc_filename ) + # Ctor reads ycmd/tests/testdata/[unix|windows]/compile_commands.json + db = ycm_core.CompilationDatabase( cc_dir ) + info = db.GetCompilationInfoForFile( cc_filename ) - assert_that( str( info.compiler_working_dir_ ), - equal_to( COMPILE_COMMANDS_WORKING_DIR ) ) - assert_that( str( info.compiler_flags_[ 0 ] ), - equal_to( '/usr/bin/clang++' ) ) - assert_that( str( info.compiler_flags_[ 1 ] ), - equal_to( '--driver-mode=g++' ) ) - assert_that( str( info.compiler_flags_[ 2 ] ), - equal_to( 'example.cc' ) ) + assert_that( str( info.compiler_working_dir_ ), + equal_to( COMPILE_COMMANDS_WORKING_DIR ) ) + assert_that( str( info.compiler_flags_[ 0 ] ), + equal_to( '/usr/bin/clang++' ) ) + assert_that( str( info.compiler_flags_[ 1 ] ), + equal_to( '--driver-mode=g++' ) ) + assert_that( str( info.compiler_flags_[ 2 ] ), + equal_to( 'example.cc' ) ) -@ClangOnly -def CompilationDatabase_NativeString_test(): - cc_dir = PATH_TO_COMPILE_COMMANDS - cc_filename = os.path.join( COMPILE_COMMANDS_WORKING_DIR, 'example.cc' ) + @ClangOnly + def test_CompilationDatabase_NativeString( self ): + cc_dir = PATH_TO_COMPILE_COMMANDS + cc_filename = os.path.join( COMPILE_COMMANDS_WORKING_DIR, 'example.cc' ) - # Ctor reads ycmd/tests/testdata/[unix|windows]/compile_commands.json - db = ycm_core.CompilationDatabase( cc_dir ) - info = db.GetCompilationInfoForFile( cc_filename ) + # Ctor reads ycmd/tests/testdata/[unix|windows]/compile_commands.json + db = ycm_core.CompilationDatabase( cc_dir ) + info = db.GetCompilationInfoForFile( cc_filename ) - assert_that( str( info.compiler_working_dir_ ), - equal_to( COMPILE_COMMANDS_WORKING_DIR ) ) - assert_that( str( info.compiler_flags_[ 0 ] ), - equal_to( '/usr/bin/clang++' ) ) - assert_that( str( info.compiler_flags_[ 1 ] ), - equal_to( '--driver-mode=g++' ) ) - assert_that( str( info.compiler_flags_[ 2 ] ), - equal_to( 'example.cc' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + assert_that( str( info.compiler_working_dir_ ), + equal_to( COMPILE_COMMANDS_WORKING_DIR ) ) + assert_that( str( info.compiler_flags_[ 0 ] ), + equal_to( '/usr/bin/clang++' ) ) + assert_that( str( info.compiler_flags_[ 1 ] ), + equal_to( '--driver-mode=g++' ) ) + assert_that( str( info.compiler_flags_[ 2 ] ), + equal_to( 'example.cc' ) ) diff --git a/ycmd/tests/request_validation_test.py b/ycmd/tests/request_validation_test.py index 2923b10d12..5729c2620b 100644 --- a/ycmd/tests/request_validation_test.py +++ b/ycmd/tests/request_validation_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -18,6 +18,7 @@ from hamcrest import raises, assert_that, calling from ycmd.request_validation import EnsureRequestValid from ycmd.responses import ServerError +from unittest import TestCase def BasicData(): @@ -34,66 +35,62 @@ def BasicData(): } -def EnsureRequestValid_AllOk_test(): - assert_that( EnsureRequestValid( BasicData() ) ) +class RequestValidationTest( TestCase ): + def test_EnsureRequestValid_AllOk( self ): + assert_that( EnsureRequestValid( BasicData() ) ) -def EnsureRequestValid_MissingLineNum_test(): - data = BasicData() - del data[ 'line_num' ] - assert_that( calling( EnsureRequestValid ).with_args( data ), - raises( ServerError, ".*line_num.*" ) ) + def test_EnsureRequestValid_MissingLineNum( self ): + data = BasicData() + del data[ 'line_num' ] + assert_that( calling( EnsureRequestValid ).with_args( data ), + raises( ServerError, ".*line_num.*" ) ) -def EnsureRequestValid_MissingColumnNum_test(): - data = BasicData() - del data[ 'column_num' ] - assert_that( calling( EnsureRequestValid ).with_args( data ), - raises( ServerError, ".*column_num.*" ) ) + def test_EnsureRequestValid_MissingColumnNum( self ): + data = BasicData() + del data[ 'column_num' ] + assert_that( calling( EnsureRequestValid ).with_args( data ), + raises( ServerError, ".*column_num.*" ) ) -def EnsureRequestValid_MissingFilepath_test(): - data = BasicData() - del data[ 'filepath' ] - assert_that( calling( EnsureRequestValid ).with_args( data ), - raises( ServerError, ".*filepath.*" ) ) + def test_EnsureRequestValid_MissingFilepath( self ): + data = BasicData() + del data[ 'filepath' ] + assert_that( calling( EnsureRequestValid ).with_args( data ), + raises( ServerError, ".*filepath.*" ) ) -def EnsureRequestValid_MissingFileData_test(): - data = BasicData() - del data[ 'file_data' ] - assert_that( calling( EnsureRequestValid ).with_args( data ), - raises( ServerError, ".*file_data.*" ) ) + def test_EnsureRequestValid_MissingFileData( self ): + data = BasicData() + del data[ 'file_data' ] + assert_that( calling( EnsureRequestValid ).with_args( data ), + raises( ServerError, ".*file_data.*" ) ) -def EnsureRequestValid_MissingFileDataContents_test(): - data = BasicData() - del data[ 'file_data' ][ '/foo' ][ 'contents' ] - assert_that( calling( EnsureRequestValid ).with_args( data ), - raises( ServerError, ".*contents.*" ) ) + def test_EnsureRequestValid_MissingFileDataContents( self ): + data = BasicData() + del data[ 'file_data' ][ '/foo' ][ 'contents' ] + assert_that( calling( EnsureRequestValid ).with_args( data ), + raises( ServerError, ".*contents.*" ) ) -def EnsureRequestValid_MissingFileDataFiletypes_test(): - data = BasicData() - del data[ 'file_data' ][ '/foo' ][ 'filetypes' ] - assert_that( calling( EnsureRequestValid ).with_args( data ), - raises( ServerError, ".*filetypes.*" ) ) + def test_EnsureRequestValid_MissingFileDataFiletypes( self ): + data = BasicData() + del data[ 'file_data' ][ '/foo' ][ 'filetypes' ] + assert_that( calling( EnsureRequestValid ).with_args( data ), + raises( ServerError, ".*filetypes.*" ) ) -def EnsureRequestValid_EmptyFileDataFiletypes_test(): - data = BasicData() - del data[ 'file_data' ][ '/foo' ][ 'filetypes' ][ 0 ] - assert_that( calling( EnsureRequestValid ).with_args( data ), - raises( ServerError, ".*filetypes.*" ) ) + def test_EnsureRequestValid_EmptyFileDataFiletypes( self ): + data = BasicData() + del data[ 'file_data' ][ '/foo' ][ 'filetypes' ][ 0 ] + assert_that( calling( EnsureRequestValid ).with_args( data ), + raises( ServerError, ".*filetypes.*" ) ) -def EnsureRequestValid_MissingEntryForFileInFileData_test(): - data = BasicData() - data[ 'filepath' ] = '/bar' - assert_that( calling( EnsureRequestValid ).with_args( data ), - raises( ServerError, ".*/bar.*" ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + def test_EnsureRequestValid_MissingEntryForFileInFileData( self ): + data = BasicData() + data[ 'filepath' ] = '/bar' + assert_that( calling( EnsureRequestValid ).with_args( data ), + raises( ServerError, ".*/bar.*" ) ) diff --git a/ycmd/tests/request_wrap_test.py b/ycmd/tests/request_wrap_test.py index 513a1f9ab3..ba5460962d 100644 --- a/ycmd/tests/request_wrap_test.py +++ b/ycmd/tests/request_wrap_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,12 +15,12 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -import pytest from hamcrest import ( assert_that, calling, contains_exactly, empty, equal_to, has_entry, has_string, raises ) from ycmd.utils import ToBytes from ycmd.request_wrap import RequestWrap +from unittest import TestCase def PrepareJson( contents = '', @@ -48,365 +48,368 @@ def PrepareJson( contents = '', return message -@pytest.mark.parametrize( 'line,col,prefix', [ - ( 'abc.def', 5, 'abc.' ), - ( 'abc.def', 6, 'abc.' ), - ( 'abc.def', 8, 'abc.' ), - ( 'abc.def', 4, '' ), - ( 'abc.', 5, 'abc.' ), - ( 'abc.', 4, '' ), - ( '', 1, '' ), - ] ) -def Prefix_test( line, col, prefix ): - assert_that( prefix, - equal_to( RequestWrap( - PrepareJson( line_num = 1, - contents = line, - column_num = col ) )[ 'prefix' ] ) ) - +class RequestWrapTest( TestCase ): + def test_Prefix( self ): + for line, col, prefix in [ + ( 'abc.def', 5, 'abc.' ), + ( 'abc.def', 6, 'abc.' ), + ( 'abc.def', 8, 'abc.' ), + ( 'abc.def', 4, '' ), + ( 'abc.', 5, 'abc.' ), + ( 'abc.', 4, '' ), + ( '', 1, '' ), + ]: + with self.subTest( line = line, col = col, prefix = prefix ): + assert_that( prefix, + equal_to( RequestWrap( + PrepareJson( line_num = 1, + contents = line, + column_num = col ) )[ 'prefix' ] ) ) + + + def test_LineValue_OneLine( self ): + assert_that( 'zoo', + equal_to( RequestWrap( + PrepareJson( line_num = 1, + contents = 'zoo' ) )[ 'line_value' ] ) ) -def LineValue_OneLine_test(): - assert_that( 'zoo', - equal_to( RequestWrap( - PrepareJson( line_num = 1, - contents = 'zoo' ) )[ 'line_value' ] ) ) + def test_LineValue_LastLine( self ): + assert_that( + 'zoo', + equal_to( + RequestWrap( + PrepareJson( line_num = 3, + contents = 'goo\nbar\nzoo' ) )[ 'line_value' ] ) ) -def LineValue_LastLine_test(): - assert_that( 'zoo', - equal_to( RequestWrap( - PrepareJson( line_num = 3, - contents = 'goo\nbar\nzoo' ) )[ 'line_value' ] ) ) + def test_LineValue_MiddleLine( self ): + assert_that( + 'zoo', + equal_to( + RequestWrap( + PrepareJson( line_num = 2, + contents = 'goo\nzoo\nbar' ) )[ 'line_value' ] ) ) -def LineValue_MiddleLine_test(): - assert_that( 'zoo', - equal_to( RequestWrap( - PrepareJson( line_num = 2, - contents = 'goo\nzoo\nbar' ) )[ 'line_value' ] ) ) + def test_LineValue_WindowsLines( self ): + assert_that( 'zoo', + equal_to( RequestWrap( + PrepareJson( + line_num = 3, + contents = 'goo\r\nbar\r\nzoo' ) )[ 'line_value' ] ) ) -def LineValue_WindowsLines_test(): - assert_that( 'zoo', - equal_to( RequestWrap( - PrepareJson( - line_num = 3, - contents = 'goo\r\nbar\r\nzoo' ) )[ 'line_value' ] ) ) + def test_LineValue_MixedFormatLines( self ): + assert_that( 'zoo', + equal_to( RequestWrap( + PrepareJson( + line_num = 3, + contents = 'goo\nbar\r\nzoo' ) )[ 'line_value' ] ) ) -def LineValue_MixedFormatLines_test(): - assert_that( 'zoo', - equal_to( RequestWrap( - PrepareJson( - line_num = 3, - contents = 'goo\nbar\r\nzoo' ) )[ 'line_value' ] ) ) + def test_LineValue_EmptyContents( self ): + assert_that( '', + equal_to( RequestWrap( + PrepareJson( line_num = 1, + contents = '' ) )[ 'line_value' ] ) ) -def LineValue_EmptyContents_test(): - assert_that( '', - equal_to( RequestWrap( - PrepareJson( line_num = 1, - contents = '' ) )[ 'line_value' ] ) ) + def test_StartColumn_RightAfterDot( self ): + assert_that( 5, + equal_to( RequestWrap( + PrepareJson( column_num = 5, + contents = 'foo.' ) )[ 'start_column' ] ) ) -def StartColumn_RightAfterDot_test(): - assert_that( 5, - equal_to( RequestWrap( - PrepareJson( column_num = 5, - contents = 'foo.' ) )[ 'start_column' ] ) ) + def test_StartColumn_Dot( self ): + assert_that( 5, + equal_to( RequestWrap( + PrepareJson( column_num = 8, + contents = 'foo.bar' ) )[ 'start_column' ] ) ) -def StartColumn_Dot_test(): - assert_that( 5, - equal_to( RequestWrap( - PrepareJson( column_num = 8, - contents = 'foo.bar' ) )[ 'start_column' ] ) ) + def test_StartColumn_DotWithUnicode( self ): + assert_that( 7, + equal_to( RequestWrap( + PrepareJson( column_num = 11, + contents = 'fäö.bär' ) )[ 'start_column' ] ) ) -def StartColumn_DotWithUnicode_test(): - assert_that( 7, - equal_to( RequestWrap( - PrepareJson( column_num = 11, - contents = 'fäö.bär' ) )[ 'start_column' ] ) ) + def test_StartColumn_UnicodeNotIdentifier( self ): + contents = "var x = '†es†ing'." -def StartColumn_UnicodeNotIdentifier_test(): - contents = "var x = '†es†ing'." + # † not considered an identifier character - # † not considered an identifier character + for i in range( 13, 15 ): + print( ToBytes( contents )[ i - 1 : i ] ) + assert_that( 13, + equal_to( RequestWrap( + PrepareJson( column_num = i, + contents = contents ) )[ 'start_column' ] ) ) - for i in range( 13, 15 ): - print( ToBytes( contents )[ i - 1 : i ] ) assert_that( 13, equal_to( RequestWrap( - PrepareJson( column_num = i, + PrepareJson( column_num = 15, contents = contents ) )[ 'start_column' ] ) ) - assert_that( 13, - equal_to( RequestWrap( - PrepareJson( column_num = 15, - contents = contents ) )[ 'start_column' ] ) ) + for i in range( 18, 20 ): + print( ToBytes( contents )[ i - 1 : i ] ) + assert_that( 18, + equal_to( RequestWrap( + PrepareJson( column_num = i, + contents = contents ) )[ 'start_column' ] ) ) + - for i in range( 18, 20 ): - print( ToBytes( contents )[ i - 1 : i ] ) - assert_that( 18, + def test_StartColumn_QueryIsUnicode( self ): + contents = "var x = ålpha.alphå" + assert_that( 16, + equal_to( RequestWrap( + PrepareJson( column_num = 16, + contents = contents ) )[ 'start_column' ] ) ) + assert_that( 16, equal_to( RequestWrap( - PrepareJson( column_num = i, + PrepareJson( column_num = 19, contents = contents ) )[ 'start_column' ] ) ) -def StartColumn_QueryIsUnicode_test(): - contents = "var x = ålpha.alphå" - assert_that( 16, - equal_to( RequestWrap( - PrepareJson( column_num = 16, - contents = contents ) )[ 'start_column' ] ) ) - assert_that( 16, - equal_to( RequestWrap( - PrepareJson( column_num = 19, - contents = contents ) )[ 'start_column' ] ) ) + def test_StartColumn_QueryStartsWithUnicode( self ): + contents = "var x = ålpha.ålpha" + assert_that( 16, + equal_to( RequestWrap( + PrepareJson( column_num = 16, + contents = contents ) )[ 'start_column' ] ) ) + assert_that( 16, + equal_to( RequestWrap( + PrepareJson( column_num = 19, + contents = contents ) )[ 'start_column' ] ) ) -def StartColumn_QueryStartsWithUnicode_test(): - contents = "var x = ålpha.ålpha" - assert_that( 16, - equal_to( RequestWrap( - PrepareJson( column_num = 16, - contents = contents ) )[ 'start_column' ] ) ) - assert_that( 16, - equal_to( RequestWrap( - PrepareJson( column_num = 19, - contents = contents ) )[ 'start_column' ] ) ) + def test_StartColumn_ThreeByteUnicode( self ): + contents = "var x = '†'." + assert_that( 15, + equal_to( RequestWrap( + PrepareJson( column_num = 15, + contents = contents ) )[ 'start_column' ] ) ) -def StartColumn_ThreeByteUnicode_test(): - contents = "var x = '†'." - assert_that( 15, - equal_to( RequestWrap( - PrepareJson( column_num = 15, - contents = contents ) )[ 'start_column' ] ) ) + def test_StartColumn_Paren( self ): + assert_that( 5, + equal_to( RequestWrap( + PrepareJson( column_num = 8, + contents = 'foo(bar' ) )[ 'start_column' ] ) ) -def StartColumn_Paren_test(): - assert_that( 5, - equal_to( RequestWrap( - PrepareJson( column_num = 8, - contents = 'foo(bar' ) )[ 'start_column' ] ) ) + def test_StartColumn_AfterWholeWord( self ): + assert_that( 1, + equal_to( RequestWrap( + PrepareJson( column_num = 7, + contents = 'foobar' ) )[ 'start_column' ] ) ) -def StartColumn_AfterWholeWord_test(): - assert_that( 1, - equal_to( RequestWrap( - PrepareJson( column_num = 7, - contents = 'foobar' ) )[ 'start_column' ] ) ) + def test_StartColumn_AfterWholeWord_Html( self ): + assert_that( 1, + equal_to( RequestWrap( + PrepareJson( column_num = 7, filetype = 'html', + contents = 'fo-bar' ) )[ 'start_column' ] ) ) -def StartColumn_AfterWholeWord_Html_test(): - assert_that( 1, - equal_to( RequestWrap( - PrepareJson( column_num = 7, filetype = 'html', - contents = 'fo-bar' ) )[ 'start_column' ] ) ) + def test_StartColumn_AfterWholeUnicodeWord( self ): + assert_that( 1, equal_to( RequestWrap( + PrepareJson( column_num = 6, + contents = u'fäö' ) )[ 'start_column' ] ) ) -def StartColumn_AfterWholeUnicodeWord_test(): - assert_that( 1, equal_to( RequestWrap( - PrepareJson( column_num = 6, - contents = u'fäö' ) )[ 'start_column' ] ) ) + def test_StartColumn_InMiddleOfWholeWord( self ): + assert_that( 1, equal_to( RequestWrap( + PrepareJson( column_num = 4, + contents = 'foobar' ) )[ 'start_column' ] ) ) -def StartColumn_InMiddleOfWholeWord_test(): - assert_that( 1, equal_to( RequestWrap( - PrepareJson( column_num = 4, + def test_StartColumn_ColumnOne( self ): + assert_that( 1, + equal_to( RequestWrap( + PrepareJson( column_num = 1, contents = 'foobar' ) )[ 'start_column' ] ) ) -def StartColumn_ColumnOne_test(): - assert_that( 1, equal_to( RequestWrap( - PrepareJson( column_num = 1, - contents = 'foobar' ) )[ 'start_column' ] ) ) + def test_Query_AtWordEnd( self ): + assert_that( 'foo', equal_to( RequestWrap( + PrepareJson( column_num = 4, + contents = 'foo' ) )[ 'query' ] ) ) -def Query_AtWordEnd_test(): - assert_that( 'foo', equal_to( RequestWrap( - PrepareJson( column_num = 4, - contents = 'foo' ) )[ 'query' ] ) ) + def test_Query_InWordMiddle( self ): + assert_that( 'foo', equal_to( RequestWrap( + PrepareJson( column_num = 4, + contents = 'foobar' ) )[ 'query' ] ) ) -def Query_InWordMiddle_test(): - assert_that( 'foo', equal_to( RequestWrap( - PrepareJson( column_num = 4, + def test_Query_StartOfLine( self ): + assert_that( '', equal_to( RequestWrap( + PrepareJson( column_num = 1, contents = 'foobar' ) )[ 'query' ] ) ) -def Query_StartOfLine_test(): - assert_that( '', equal_to( RequestWrap( - PrepareJson( column_num = 1, - contents = 'foobar' ) )[ 'query' ] ) ) - - -def Query_StopsAtParen_test(): - assert_that( 'bar', equal_to( RequestWrap( - PrepareJson( column_num = 8, - contents = 'foo(bar' ) )[ 'query' ] ) ) - - -def Query_InWhiteSpace_test(): - assert_that( '', equal_to( RequestWrap( - PrepareJson( column_num = 8, - contents = 'foo ' ) )[ 'query' ] ) ) - - -def Query_UnicodeSinglecharInclusive_test(): - assert_that( 'ø', equal_to( RequestWrap( - PrepareJson( column_num = 7, - contents = 'abc.ø' ) )[ 'query' ] ) ) - - -def Query_UnicodeSinglecharExclusive_test(): - assert_that( '', equal_to( RequestWrap( - PrepareJson( column_num = 5, - contents = 'abc.ø' ) )[ 'query' ] ) ) - - -def StartColumn_Set_test(): - wrap = RequestWrap( PrepareJson( column_num = 11, - contents = 'this \'test', - filetype = 'javascript' ) ) - assert_that( wrap[ 'start_column' ], equal_to( 7 ) ) - assert_that( wrap[ 'start_codepoint' ], equal_to( 7 ) ) - assert_that( wrap[ 'query' ], equal_to( "test" ) ) - assert_that( wrap[ 'prefix' ], equal_to( "this '" ) ) - - wrap[ 'start_column' ] = 6 - assert_that( wrap[ 'start_column' ], equal_to( 6 ) ) - assert_that( wrap[ 'start_codepoint' ], equal_to( 6 ) ) - assert_that( wrap[ 'query' ], equal_to( "'test" ) ) - assert_that( wrap[ 'prefix' ], equal_to( "this " ) ) - - -def StartColumn_SetUnicode_test(): - wrap = RequestWrap( PrepareJson( column_num = 14, - contents = '†e߆ \'test', - filetype = 'javascript' ) ) - assert_that( 7, equal_to( wrap[ 'start_codepoint' ] ) ) - assert_that( 12, equal_to( wrap[ 'start_column' ] ) ) - assert_that( wrap[ 'query' ], equal_to( "te" ) ) - assert_that( wrap[ 'prefix' ], equal_to( "†e߆ \'" ) ) - - wrap[ 'start_column' ] = 11 - assert_that( wrap[ 'start_column' ], equal_to( 11 ) ) - assert_that( wrap[ 'start_codepoint' ], equal_to( 6 ) ) - assert_that( wrap[ 'query' ], equal_to( "'te" ) ) - assert_that( wrap[ 'prefix' ], equal_to( "†e߆ " ) ) - - -def StartCodepoint_Set_test(): - wrap = RequestWrap( PrepareJson( column_num = 11, - contents = 'this \'test', - filetype = 'javascript' ) ) - assert_that( wrap[ 'start_column' ], equal_to( 7 ) ) - assert_that( wrap[ 'start_codepoint' ], equal_to( 7 ) ) - assert_that( wrap[ 'query' ], equal_to( "test" ) ) - assert_that( wrap[ 'prefix' ], equal_to( "this '" ) ) - - wrap[ 'start_codepoint' ] = 6 - assert_that( wrap[ 'start_column' ], equal_to( 6 ) ) - assert_that( wrap[ 'start_codepoint' ], equal_to( 6 ) ) - assert_that( wrap[ 'query' ], equal_to( "'test" ) ) - assert_that( wrap[ 'prefix' ], equal_to( "this " ) ) - - -def StartCodepoint_SetUnicode_test(): - wrap = RequestWrap( PrepareJson( column_num = 14, - contents = '†e߆ \'test', - filetype = 'javascript' ) ) - assert_that( 7, equal_to( wrap[ 'start_codepoint' ] ) ) - assert_that( 12, equal_to( wrap[ 'start_column' ] ) ) - assert_that( wrap[ 'query' ], equal_to( "te" ) ) - assert_that( wrap[ 'prefix' ], equal_to( "†e߆ \'" ) ) - - wrap[ 'start_codepoint' ] = 6 - assert_that( wrap[ 'start_column' ], equal_to( 11 ) ) - assert_that( wrap[ 'start_codepoint' ], equal_to( 6 ) ) - assert_that( wrap[ 'query' ], equal_to( "'te" ) ) - assert_that( wrap[ 'prefix' ], equal_to( "†e߆ " ) ) - - -def Calculated_SetMethod_test(): - assert_that( - calling( RequestWrap( PrepareJson() ).__setitem__ ).with_args( - 'line_value', '' ), - raises( ValueError, 'Key "line_value" is read-only' ) ) - - -def Calculated_SetOperator_test(): - # Differs from the above in that it use [] operator rather than __setitem__ - # directly. And it uses a different property for extra credit. - wrap = RequestWrap( PrepareJson() ) - try: - wrap[ 'query' ] = 'test' - except ValueError as error: - assert_that( str( error ), - equal_to( 'Key "query" is read-only' ) ) - else: - raise AssertionError( 'Expected setting "query" to fail' ) - - -def NonCalculated_Set_test(): - # Differs from the above in that it use [] operator rather than __setitem__ - # directly. And it uses a different property for extra credit. - wrap = RequestWrap( PrepareJson() ) - try: - wrap[ 'column_num' ] = 10 - except ValueError as error: - assert_that( str( error ), - equal_to( 'Key "column_num" is read-only' ) ) - else: - raise AssertionError( 'Expected setting "column_num" to fail' ) - - -def ForceSemanticCompletion_test(): - wrap = RequestWrap( PrepareJson() ) - assert_that( wrap[ 'force_semantic' ], equal_to( False ) ) - - wrap = RequestWrap( PrepareJson( force_semantic = True ) ) - assert_that( wrap[ 'force_semantic' ], equal_to( True ) ) - - wrap = RequestWrap( PrepareJson( force_semantic = 1 ) ) - assert_that( wrap[ 'force_semantic' ], equal_to( True ) ) - - wrap = RequestWrap( PrepareJson( force_semantic = 0 ) ) - assert_that( wrap[ 'force_semantic' ], equal_to( False ) ) - - wrap = RequestWrap( PrepareJson( force_semantic = 'No' ) ) - assert_that( wrap[ 'force_semantic' ], equal_to( True ) ) - - -def ExtraConfData_test(): - wrap = RequestWrap( PrepareJson() ) - assert_that( wrap[ 'extra_conf_data' ], empty() ) - - wrap = RequestWrap( PrepareJson( extra_conf_data = { 'key': [ 'value' ] } ) ) - extra_conf_data = wrap[ 'extra_conf_data' ] - assert_that( extra_conf_data, - has_entry( 'key', contains_exactly( 'value' ) ) ) - assert_that( - extra_conf_data, - has_string( - equal_to( "" ) + def test_Query_StopsAtParen( self ): + assert_that( 'bar', equal_to( RequestWrap( + PrepareJson( column_num = 8, + contents = 'foo(bar' ) )[ 'query' ] ) ) + + + def test_Query_InWhiteSpace( self ): + assert_that( '', equal_to( RequestWrap( + PrepareJson( column_num = 8, + contents = 'foo ' ) )[ 'query' ] ) ) + + + def test_Query_UnicodeSinglecharInclusive( self ): + assert_that( 'ø', equal_to( RequestWrap( + PrepareJson( column_num = 7, + contents = 'abc.ø' ) )[ 'query' ] ) ) + + + def test_Query_UnicodeSinglecharExclusive( self ): + assert_that( '', equal_to( RequestWrap( + PrepareJson( column_num = 5, + contents = 'abc.ø' ) )[ 'query' ] ) ) + + + def test_StartColumn_Set( self ): + wrap = RequestWrap( PrepareJson( column_num = 11, + contents = 'this \'test', + filetype = 'javascript' ) ) + assert_that( wrap[ 'start_column' ], equal_to( 7 ) ) + assert_that( wrap[ 'start_codepoint' ], equal_to( 7 ) ) + assert_that( wrap[ 'query' ], equal_to( "test" ) ) + assert_that( wrap[ 'prefix' ], equal_to( "this '" ) ) + + wrap[ 'start_column' ] = 6 + assert_that( wrap[ 'start_column' ], equal_to( 6 ) ) + assert_that( wrap[ 'start_codepoint' ], equal_to( 6 ) ) + assert_that( wrap[ 'query' ], equal_to( "'test" ) ) + assert_that( wrap[ 'prefix' ], equal_to( "this " ) ) + + + def test_StartColumn_SetUnicode( self ): + wrap = RequestWrap( PrepareJson( column_num = 14, + contents = '†e߆ \'test', + filetype = 'javascript' ) ) + assert_that( 7, equal_to( wrap[ 'start_codepoint' ] ) ) + assert_that( 12, equal_to( wrap[ 'start_column' ] ) ) + assert_that( wrap[ 'query' ], equal_to( "te" ) ) + assert_that( wrap[ 'prefix' ], equal_to( "†e߆ \'" ) ) + + wrap[ 'start_column' ] = 11 + assert_that( wrap[ 'start_column' ], equal_to( 11 ) ) + assert_that( wrap[ 'start_codepoint' ], equal_to( 6 ) ) + assert_that( wrap[ 'query' ], equal_to( "'te" ) ) + assert_that( wrap[ 'prefix' ], equal_to( "†e߆ " ) ) + + + def test_StartCodepoint_Set( self ): + wrap = RequestWrap( PrepareJson( column_num = 11, + contents = 'this \'test', + filetype = 'javascript' ) ) + assert_that( wrap[ 'start_column' ], equal_to( 7 ) ) + assert_that( wrap[ 'start_codepoint' ], equal_to( 7 ) ) + assert_that( wrap[ 'query' ], equal_to( "test" ) ) + assert_that( wrap[ 'prefix' ], equal_to( "this '" ) ) + + wrap[ 'start_codepoint' ] = 6 + assert_that( wrap[ 'start_column' ], equal_to( 6 ) ) + assert_that( wrap[ 'start_codepoint' ], equal_to( 6 ) ) + assert_that( wrap[ 'query' ], equal_to( "'test" ) ) + assert_that( wrap[ 'prefix' ], equal_to( "this " ) ) + + + def test_StartCodepoint_SetUnicode( self ): + wrap = RequestWrap( PrepareJson( column_num = 14, + contents = '†e߆ \'test', + filetype = 'javascript' ) ) + assert_that( 7, equal_to( wrap[ 'start_codepoint' ] ) ) + assert_that( 12, equal_to( wrap[ 'start_column' ] ) ) + assert_that( wrap[ 'query' ], equal_to( "te" ) ) + assert_that( wrap[ 'prefix' ], equal_to( "†e߆ \'" ) ) + + wrap[ 'start_codepoint' ] = 6 + assert_that( wrap[ 'start_column' ], equal_to( 11 ) ) + assert_that( wrap[ 'start_codepoint' ], equal_to( 6 ) ) + assert_that( wrap[ 'query' ], equal_to( "'te" ) ) + assert_that( wrap[ 'prefix' ], equal_to( "†e߆ " ) ) + + + def test_Calculated_SetMethod( self ): + assert_that( + calling( RequestWrap( PrepareJson() ).__setitem__ ).with_args( + 'line_value', '' ), + raises( ValueError, 'Key "line_value" is read-only' ) ) + + + def test_Calculated_SetOperator( self ): + # Differs from the above in that it use [] operator rather than __setitem__ + # directly. And it uses a different property for extra credit. + wrap = RequestWrap( PrepareJson() ) + try: + wrap[ 'query' ] = 'test' + except ValueError as error: + assert_that( str( error ), + equal_to( 'Key "query" is read-only' ) ) + else: + raise AssertionError( 'Expected setting "query" to fail' ) + + + def test_NonCalculated_Set( self ): + # Differs from the above in that it use [] operator rather than __setitem__ + # directly. And it uses a different property for extra credit. + wrap = RequestWrap( PrepareJson() ) + try: + wrap[ 'column_num' ] = 10 + except ValueError as error: + assert_that( str( error ), + equal_to( 'Key "column_num" is read-only' ) ) + else: + raise AssertionError( 'Expected setting "column_num" to fail' ) + + + def test_ForceSemanticCompletion( self ): + wrap = RequestWrap( PrepareJson() ) + assert_that( wrap[ 'force_semantic' ], equal_to( False ) ) + + wrap = RequestWrap( PrepareJson( force_semantic = True ) ) + assert_that( wrap[ 'force_semantic' ], equal_to( True ) ) + + wrap = RequestWrap( PrepareJson( force_semantic = 1 ) ) + assert_that( wrap[ 'force_semantic' ], equal_to( True ) ) + + wrap = RequestWrap( PrepareJson( force_semantic = 0 ) ) + assert_that( wrap[ 'force_semantic' ], equal_to( False ) ) + + wrap = RequestWrap( PrepareJson( force_semantic = 'No' ) ) + assert_that( wrap[ 'force_semantic' ], equal_to( True ) ) + + + def test_ExtraConfData( self ): + wrap = RequestWrap( PrepareJson() ) + assert_that( wrap[ 'extra_conf_data' ], empty() ) + + wrap = RequestWrap( + PrepareJson( extra_conf_data = { 'key': [ 'value' ] } ) ) + extra_conf_data = wrap[ 'extra_conf_data' ] + assert_that( extra_conf_data, + has_entry( 'key', contains_exactly( 'value' ) ) ) + assert_that( + extra_conf_data, + has_string( + equal_to( "" ) + ) ) - ) - - # Check that extra_conf_data can be used as a dictionary's key. - assert_that( { extra_conf_data: 'extra conf data' }, - has_entry( extra_conf_data, 'extra conf data' ) ) - - # Check that extra_conf_data's values are immutable. - extra_conf_data[ 'key' ].append( 'another_value' ) - assert_that( extra_conf_data, - has_entry( 'key', contains_exactly( 'value' ) ) ) + # Check that extra_conf_data can be used as a dictionary's key. + assert_that( { extra_conf_data: 'extra conf data' }, + has_entry( extra_conf_data, 'extra conf data' ) ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + # Check that extra_conf_data's values are immutable. + extra_conf_data[ 'key' ].append( 'another_value' ) + assert_that( extra_conf_data, + has_entry( 'key', contains_exactly( 'value' ) ) ) diff --git a/ycmd/tests/rust/__init__.py b/ycmd/tests/rust/__init__.py index e522018a21..9042106369 100644 --- a/ycmd/tests/rust/__init__.py +++ b/ycmd/tests/rust/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -15,4 +15,66 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -from ycmd.tests.rust.conftest import * # noqa +import functools +import os + +from ycmd.tests.test_utils import ( BuildRequest, + ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp, + StopCompleterServer, + WaitUntilCompleterServerReady ) + +shared_app = None + + +def setUpModule(): + global shared_app + shared_app = SetUpApp() + with IgnoreExtraConfOutsideTestsFolder(): + StartRustCompleterServerInDirectory( shared_app, + PathToTestFile( 'common' ) ) + + +def tearDownModule(): + global shared_app + StopCompleterServer( shared_app, 'rust' ) + + +def StartRustCompleterServerInDirectory( app, directory ): + app.post_json( '/event_notification', + BuildRequest( + filepath = os.path.join( directory, 'src', 'main.rs' ), + event_name = 'FileReadyToParse', + filetype = 'rust' ) ) + WaitUntilCompleterServerReady( app, 'rust' ) + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'rust' ) + return Wrapper + return Decorator + + +def PathToTestFile( *args ): + dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) + return os.path.join( dir_of_current_script, 'testdata', *args ) diff --git a/ycmd/tests/rust/conftest.py b/ycmd/tests/rust/conftest.py deleted file mode 100644 index c399f754aa..0000000000 --- a/ycmd/tests/rust/conftest.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import os -import pytest -from ycmd.tests.test_utils import ( BuildRequest, - ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - SetUpApp, - StopCompleterServer, - WaitUntilCompleterServerReady ) -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - shared_app = SetUpApp() - with IgnoreExtraConfOutsideTestsFolder(): - StartRustCompleterServerInDirectory( shared_app, - PathToTestFile( 'common' ) ) - yield - StopCompleterServer( shared_app, 'rust' ) - - -def StartRustCompleterServerInDirectory( app, directory ): - app.post_json( '/event_notification', - BuildRequest( - filepath = os.path.join( directory, 'src', 'main.rs' ), - event_name = 'FileReadyToParse', - filetype = 'rust' ) ) - WaitUntilCompleterServerReady( app, 'rust' ) - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with IsolatedApp( {} ) as app: - yield app - StopCompleterServer( app, 'rust' ) - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes a unique ycmd application as a parameter. It should be used on tests -that change the server state in a irreversible way (ex: a semantic subserver -is stopped or restarted) or expect a clean state (ex: no semantic subserver -started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter -|custom_options| to give additional options and/or override the default ones. - -Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... -""" -IsolatedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def PathToTestFile( *args ): - dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) - return os.path.join( dir_of_current_script, 'testdata', *args ) diff --git a/ycmd/tests/rust/debug_info_test.py b/ycmd/tests/rust/debug_info_test.py index 1da1a481f1..0d07a0d792 100644 --- a/ycmd/tests/rust/debug_info_test.py +++ b/ycmd/tests/rust/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -18,110 +18,109 @@ from hamcrest import ( assert_that, contains_exactly, has_entries, has_entry, instance_of, none ) from unittest.mock import patch - -from ycmd.tests.rust import ( IsolatedYcmd, +from unittest import TestCase +from ycmd.tests.rust import ( IsolatedYcmd, # noqa PathToTestFile, SharedYcmd, - StartRustCompleterServerInDirectory ) + StartRustCompleterServerInDirectory, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import BuildRequest -@SharedYcmd -def DebugInfo_RlsVersion_test( app ): - request_data = BuildRequest( filetype = 'rust' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'Rust', - 'servers': contains_exactly( has_entries( { - 'name': 'Rust Language Server', - 'is_running': instance_of( bool ), - 'executable': contains_exactly( instance_of( str ) ), - 'pid': instance_of( int ), - 'address': none(), - 'port': none(), - 'logfiles': contains_exactly( instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Project Directory', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}' - } ), - has_entries( { - 'key': 'Project State', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Version', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Rust Root', - 'value': instance_of( str ) - } ) - ) +class DebugInfoTest( TestCase ): + @SharedYcmd + def test_DebugInfo_RlsVersion( self, app ): + request_data = BuildRequest( filetype = 'rust' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'Rust', + 'servers': contains_exactly( has_entries( { + 'name': 'Rust Language Server', + 'is_running': instance_of( bool ), + 'executable': contains_exactly( instance_of( str ) ), + 'pid': instance_of( int ), + 'address': none(), + 'port': none(), + 'logfiles': contains_exactly( instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Project Directory', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}' + } ), + has_entries( { + 'key': 'Project State', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Version', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Rust Root', + 'value': instance_of( str ) + } ) + ) + } ) ) } ) ) - } ) ) - ) + ) -@IsolatedYcmd -@patch( 'ycmd.completers.rust.rust_completer._GetCommandOutput', - return_value = '' ) -def DebugInfo_NoRlsVersion_test( get_command_output, app ): - StartRustCompleterServerInDirectory( app, PathToTestFile( 'common', 'src' ) ) + @IsolatedYcmd() + @patch( 'ycmd.completers.rust.rust_completer._GetCommandOutput', + return_value = '' ) + def test_DebugInfo_NoRlsVersion( self, app, *args ): + StartRustCompleterServerInDirectory( app, + PathToTestFile( 'common', 'src' ) ) - request_data = BuildRequest( filetype = 'rust' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'Rust', - 'servers': contains_exactly( has_entries( { - 'name': 'Rust Language Server', - 'is_running': instance_of( bool ), - 'executable': contains_exactly( instance_of( str ) ), - 'pid': instance_of( int ), - 'address': none(), - 'port': none(), - 'logfiles': contains_exactly( instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { - 'key': 'Server State', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Project Directory', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Settings', - 'value': '{}' - } ), - has_entries( { - 'key': 'Project State', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'Version', - 'value': none() - } ), - has_entries( { - 'key': 'Rust Root', - 'value': instance_of( str ) - } ) - ) + request_data = BuildRequest( filetype = 'rust' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'Rust', + 'servers': contains_exactly( has_entries( { + 'name': 'Rust Language Server', + 'is_running': instance_of( bool ), + 'executable': contains_exactly( instance_of( str ) ), + 'pid': instance_of( int ), + 'address': none(), + 'port': none(), + 'logfiles': contains_exactly( instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { + 'key': 'Server State', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Project Directory', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Settings', + 'value': '{}' + } ), + has_entries( { + 'key': 'Project State', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'Version', + 'value': none() + } ), + has_entries( { + 'key': 'Rust Root', + 'value': instance_of( str ) + } ) + ) + } ) ) } ) ) - } ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + ) diff --git a/ycmd/tests/rust/diagnostics_test.py b/ycmd/tests/rust/diagnostics_test.py index e0758345f6..0b47853da8 100644 --- a/ycmd/tests/rust/diagnostics_test.py +++ b/ycmd/tests/rust/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -21,10 +21,11 @@ has_entries, has_entry ) from pprint import pformat +from unittest import TestCase import json import os -from ycmd.tests.rust import PathToTestFile, SharedYcmd +from ycmd.tests.rust import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, LocationMatcher, PollForMessages, @@ -53,101 +54,97 @@ } -@WithRetry -@SharedYcmd -def Diagnostics_DetailedDiags_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) - contents = ReadFile( filepath ) - with open( filepath, 'w' ) as f: - f.write( contents ) - event_data = BuildRequest( event_name = 'FileSave', - contents = contents, - filepath = filepath, - filetype = 'rust' ) - app.post_json( '/event_notification', event_data ) - - WaitForDiagnosticsToBeReady( app, filepath, contents, 'rust' ) - request_data = BuildRequest( contents = contents, +class DiagnosticsTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_Diagnostics_DetailedDiags( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + contents = ReadFile( filepath ) + with open( filepath, 'w' ) as f: + f.write( contents ) + event_data = BuildRequest( event_name = 'FileSave', + contents = contents, filepath = filepath, - filetype = 'rust', - line_num = 14, - column_num = 13 ) - - results = app.post_json( '/detailed_diagnostic', request_data ).json - assert_that( results, has_entry( - 'message', - 'no field `build_` on type `test::Builder`\nunknown field' ) ) - - -@WithRetry -@SharedYcmd -def Diagnostics_FileReadyToParse_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) - contents = ReadFile( filepath ) - with open( filepath, 'w' ) as f: - f.write( contents ) - event_data = BuildRequest( event_name = 'FileSave', - contents = contents, - filepath = filepath, - filetype = 'rust' ) - app.post_json( '/event_notification', event_data ) - - # It can take a while for the diagnostics to be ready. - results = WaitForDiagnosticsToBeReady( app, filepath, contents, 'rust' ) - print( f'completer response: { pformat( results ) }' ) - - assert_that( results, DIAG_MATCHERS_PER_FILE[ filepath ] ) - - -@SharedYcmd -def Diagnostics_Poll_test( app ): - project_dir = PathToTestFile( 'common' ) - filepath = os.path.join( project_dir, 'src', 'main.rs' ) - contents = ReadFile( filepath ) - with open( filepath, 'w' ) as f: - f.write( contents ) - event_data = BuildRequest( event_name = 'FileSave', - contents = contents, - filepath = filepath, - filetype = 'rust' ) - app.post_json( '/event_notification', event_data ) - - # Poll until we receive _all_ the diags asynchronously. - to_see = sorted( DIAG_MATCHERS_PER_FILE.keys() ) - seen = {} - - try: - for message in PollForMessages( app, - { 'filepath': filepath, - 'contents': contents, - 'filetype': 'rust' } ): - print( f'Message { pformat( message ) }' ) - if 'diagnostics' in message: - if message[ 'diagnostics' ] == []: - # Sometimes we get empty diagnostics before the real ones. - continue - seen[ message[ 'filepath' ] ] = True - if message[ 'filepath' ] not in DIAG_MATCHERS_PER_FILE: - raise AssertionError( 'Received diagnostics for unexpected file ' - f'{ message[ "filepath" ] }. Only expected { to_see }' ) - assert_that( message, has_entries( { - 'diagnostics': DIAG_MATCHERS_PER_FILE[ message[ 'filepath' ] ], - 'filepath': message[ 'filepath' ] - } ) ) - - if sorted( seen.keys() ) == to_see: - break - - # Eventually PollForMessages will throw a timeout exception and we'll fail - # if we don't see all of the expected diags. - except PollForMessagesTimeoutException as e: - raise AssertionError( - str( e ) + - 'Timed out waiting for full set of diagnostics. ' - f'Expected to see diags for { json.dumps( to_see, indent = 2 ) }, ' - f'but only saw { json.dumps( sorted( seen.keys() ), indent = 2 ) }.' ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + filetype = 'rust' ) + app.post_json( '/event_notification', event_data ) + + WaitForDiagnosticsToBeReady( app, filepath, contents, 'rust' ) + request_data = BuildRequest( contents = contents, + filepath = filepath, + filetype = 'rust', + line_num = 14, + column_num = 13 ) + + results = app.post_json( '/detailed_diagnostic', request_data ).json + assert_that( results, has_entry( + 'message', + 'no field `build_` on type `test::Builder`\nunknown field' ) ) + + + @WithRetry() + @SharedYcmd + def test_Diagnostics_FileReadyToParse( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + contents = ReadFile( filepath ) + with open( filepath, 'w' ) as f: + f.write( contents ) + event_data = BuildRequest( event_name = 'FileSave', + contents = contents, + filepath = filepath, + filetype = 'rust' ) + app.post_json( '/event_notification', event_data ) + + # It can take a while for the diagnostics to be ready. + results = WaitForDiagnosticsToBeReady( app, filepath, contents, 'rust' ) + print( f'completer response: { pformat( results ) }' ) + + assert_that( results, DIAG_MATCHERS_PER_FILE[ filepath ] ) + + + @SharedYcmd + def test_Diagnostics_Poll( self, app ): + project_dir = PathToTestFile( 'common' ) + filepath = os.path.join( project_dir, 'src', 'main.rs' ) + contents = ReadFile( filepath ) + with open( filepath, 'w' ) as f: + f.write( contents ) + event_data = BuildRequest( event_name = 'FileSave', + contents = contents, + filepath = filepath, + filetype = 'rust' ) + app.post_json( '/event_notification', event_data ) + + # Poll until we receive _all_ the diags asynchronously. + to_see = sorted( DIAG_MATCHERS_PER_FILE.keys() ) + seen = {} + + try: + for message in PollForMessages( app, + { 'filepath': filepath, + 'contents': contents, + 'filetype': 'rust' } ): + print( f'Message { pformat( message ) }' ) + if 'diagnostics' in message: + if message[ 'diagnostics' ] == []: + # Sometimes we get empty diagnostics before the real ones. + continue + seen[ message[ 'filepath' ] ] = True + if message[ 'filepath' ] not in DIAG_MATCHERS_PER_FILE: + raise AssertionError( 'Received diagnostics for unexpected file ' + f'{ message[ "filepath" ] }. Only expected { to_see }' ) + assert_that( message, has_entries( { + 'diagnostics': DIAG_MATCHERS_PER_FILE[ message[ 'filepath' ] ], + 'filepath': message[ 'filepath' ] + } ) ) + + if sorted( seen.keys() ) == to_see: + break + + # Eventually PollForMessages will throw a timeout exception and we'll fail + # if we don't see all of the expected diags. + except PollForMessagesTimeoutException as e: + raise AssertionError( + str( e ) + + 'Timed out waiting for full set of diagnostics. ' + f'Expected to see diags for { json.dumps( to_see, indent = 2 ) }, ' + f'but only saw { json.dumps( sorted( seen.keys() ), indent = 2 ) }.' ) diff --git a/ycmd/tests/rust/get_completions_proc_macro_test.py b/ycmd/tests/rust/get_completions_proc_macro_test.py index bc940254d9..dd72bcc67d 100644 --- a/ycmd/tests/rust/get_completions_proc_macro_test.py +++ b/ycmd/tests/rust/get_completions_proc_macro_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -22,65 +22,64 @@ has_key, is_not ) from pprint import pformat +from unittest import TestCase -from ycmd.tests.rust import ( IsolatedYcmd, +from ycmd.tests.rust import ( IsolatedYcmd, # noqa PathToTestFile, - StartRustCompleterServerInDirectory ) + StartRustCompleterServerInDirectory, + setUpModule, + tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, CompletionEntryMatcher, WithRetry ) from ycmd.utils import ReadFile -@WithRetry -@IsolatedYcmd -def GetCompletions_ProcMacro_test( app ): - StartRustCompleterServerInDirectory( app, PathToTestFile( 'macro' ) ) +class GetCompletionsProcMacroTest( TestCase ): + @WithRetry() + @IsolatedYcmd() + def test_GetCompletions_ProcMacro( self, app ): + StartRustCompleterServerInDirectory( app, PathToTestFile( 'macro' ) ) - filepath = PathToTestFile( 'macro', 'src', 'main.rs' ) - contents = ReadFile( filepath ) + filepath = PathToTestFile( 'macro', 'src', 'main.rs' ) + contents = ReadFile( filepath ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'rust', - contents = contents, - line_num = 33, - column_num = 14 ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'rust', + contents = contents, + line_num = 33, + column_num = 14 ) - results = [] - expiration = time.time() + 60 - while time.time() < expiration: - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] - if len( results ) > 0: - break - time.sleep( 0.25 ) + results = [] + expiration = time.time() + 60 + while time.time() < expiration: + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] + if len( results ) > 0: + break + time.sleep( 0.25 ) - assert_that( - results, - has_item( - CompletionEntryMatcher( - 'checkpoint' + assert_that( + results, + has_item( + CompletionEntryMatcher( + 'checkpoint' + ) ) ) - ) - # This completer does not require or support resolve - assert_that( results[ 0 ], is_not( has_key( 'resolve' ) ) ) - assert_that( results[ 0 ], is_not( has_key( 'item' ) ) ) + # This completer does not require or support resolve + assert_that( results[ 0 ], is_not( has_key( 'resolve' ) ) ) + assert_that( results[ 0 ], is_not( has_key( 'item' ) ) ) - # So (erroneously) resolving an item returns the item - completion_data[ 'resolve' ] = 0 - response = app.post_json( '/resolve_completion', completion_data ).json - print( f"Resolve resolve: { pformat( response ) }" ) + # So (erroneously) resolving an item returns the item + completion_data[ 'resolve' ] = 0 + response = app.post_json( '/resolve_completion', completion_data ).json + print( f"Resolve resolve: { pformat( response ) }" ) - # We can't actually check the result because we don't know what completion - # resolve ID 0 actually is (could be anything), so we just check that we get 1 - # result, and that there are no errors. - assert_that( response[ 'completion' ], is_not( None ) ) - assert_that( response[ 'errors' ], empty() ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + # We can't actually check the result because we don't know what completion + # resolve ID 0 actually is (could be anything), so we just check that we + # get 1 result, and that there are no errors. + assert_that( response[ 'completion' ], is_not( None ) ) + assert_that( response[ 'errors' ], empty() ) diff --git a/ycmd/tests/rust/get_completions_test.py b/ycmd/tests/rust/get_completions_test.py index cab32445ca..8b33b4e702 100644 --- a/ycmd/tests/rust/get_completions_test.py +++ b/ycmd/tests/rust/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -21,70 +21,67 @@ has_key, is_not ) from pprint import pformat +from unittest import TestCase -from ycmd.tests.rust import PathToTestFile, SharedYcmd +from ycmd.tests.rust import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, CompletionEntryMatcher, WithRetry ) from ycmd.utils import ReadFile -@WithRetry -@SharedYcmd -def GetCompletions_Basic_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) - contents = ReadFile( filepath ) +class GetCompletionsTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_GetCompletions_Basic( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + contents = ReadFile( filepath ) - completion_data = BuildRequest( filepath = filepath, - filetype = 'rust', - contents = contents, - line_num = 14, - column_num = 19 ) + completion_data = BuildRequest( filepath = filepath, + filetype = 'rust', + contents = contents, + line_num = 14, + column_num = 19 ) - results = app.post_json( '/completions', - completion_data ).json[ 'completions' ] + results = app.post_json( '/completions', + completion_data ).json[ 'completions' ] - assert_that( - results, - contains_exactly( - CompletionEntryMatcher( - 'build_rocket', - 'fn(&self)', - { - 'detailed_info': 'build_rocket\n\nDo not try at home', - 'menu_text': 'build_rocket', - 'kind': 'Method' - } - ), - CompletionEntryMatcher( - 'build_shuttle', - 'fn(&self)', - { - 'detailed_info': 'build_shuttle\n\n', - 'menu_text': 'build_shuttle', - 'kind': 'Method' - } + assert_that( + results, + contains_exactly( + CompletionEntryMatcher( + 'build_rocket', + 'fn(&self)', + { + 'detailed_info': 'build_rocket\n\nDo not try at home', + 'menu_text': 'build_rocket', + 'kind': 'Method' + } + ), + CompletionEntryMatcher( + 'build_shuttle', + 'fn(&self)', + { + 'detailed_info': 'build_shuttle\n\n', + 'menu_text': 'build_shuttle', + 'kind': 'Method' + } + ) ) ) - ) - # This completer does not require or support resolve - assert_that( results[ 0 ], is_not( has_key( 'resolve' ) ) ) - assert_that( results[ 0 ], is_not( has_key( 'item' ) ) ) + # This completer does not require or support resolve + assert_that( results[ 0 ], is_not( has_key( 'resolve' ) ) ) + assert_that( results[ 0 ], is_not( has_key( 'item' ) ) ) - # So (erroneously) resolving an item returns the item - completion_data[ 'resolve' ] = 0 - response = app.post_json( '/resolve_completion', completion_data ).json - print( f"Resolve resolve: { pformat( response ) }" ) + # So (erroneously) resolving an item returns the item + completion_data[ 'resolve' ] = 0 + response = app.post_json( '/resolve_completion', completion_data ).json + print( f"Resolve resolve: { pformat( response ) }" ) - # We can't actually check the result because we don't know what completion - # resolve ID 0 actually is (could be anything), so we just check that we get 1 - # result, and that there are no errors. - assert_that( response[ 'completion' ], is_not( None ) ) - assert_that( response[ 'errors' ], empty() ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + # We can't actually check the result because we don't know what completion + # resolve ID 0 actually is (could be anything), so we just check that we + # get 1 result, and that there are no errors. + assert_that( response[ 'completion' ], is_not( None ) ) + assert_that( response[ 'errors' ], empty() ) diff --git a/ycmd/tests/rust/rust_completer_test.py b/ycmd/tests/rust/rust_completer_test.py index 5bf196e966..a3cde9edb4 100644 --- a/ycmd/tests/rust/rust_completer_test.py +++ b/ycmd/tests/rust/rust_completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -18,48 +18,46 @@ import os from unittest.mock import patch from hamcrest import assert_that, equal_to +from unittest import TestCase from ycmd import user_options_store from ycmd.completers.rust.hook import GetCompleter +from ycmd.tests.rust import setUpModule, tearDownModule # noqa -def GetCompleter_RAFound_test(): - assert_that( GetCompleter( user_options_store.GetAll() ) ) +class RustCompleterTest( TestCase ): + def test_GetCompleter_RAFound( self ): + assert_that( GetCompleter( user_options_store.GetAll() ) ) -@patch( 'ycmd.completers.rust.rust_completer.RA_EXECUTABLE', None ) -def GetCompleter_RANotFound_test( *args ): - assert_that( not GetCompleter( user_options_store.GetAll() ) ) + @patch( 'ycmd.completers.rust.rust_completer.RA_EXECUTABLE', None ) + def test_GetCompleter_RANotFound( self, *args ): + assert_that( not GetCompleter( user_options_store.GetAll() ) ) -@patch( 'ycmd.utils.FindExecutable', - wraps = lambda x: x if 'rust-analyzer' in x else None ) -@patch( 'os.access', return_value = True ) -def GetCompleter_RAFromUserOption_test( *args ): - user_options = user_options_store.GetAll().copy( - rust_toolchain_root = 'rust-analyzer' ) - assert_that( GetCompleter( user_options )._rust_root, - equal_to( 'rust-analyzer' ) ) - expected = os.path.join( 'rust-analyzer', 'bin', 'rust-analyzer' ) - assert_that( GetCompleter( user_options )._ra_path, - equal_to( expected ) ) + @patch( 'ycmd.utils.FindExecutable', + wraps = lambda x: x if 'rust-analyzer' in x else None ) + @patch( 'os.access', return_value = True ) + def test_GetCompleter_RAFromUserOption( self, *args ): + user_options = user_options_store.GetAll().copy( + rust_toolchain_root = 'rust-analyzer' ) + assert_that( GetCompleter( user_options )._rust_root, + equal_to( 'rust-analyzer' ) ) + expected = os.path.join( 'rust-analyzer', 'bin', 'rust-analyzer' ) + assert_that( GetCompleter( user_options )._ra_path, + equal_to( expected ) ) -def GetCompleter_InvalidRustRootFromUser_test( *args ): - user_options = user_options_store.GetAll().copy( - rust_toolchain_root = '/does/not/exist' ) - assert_that( not GetCompleter( user_options ) ) + def test_GetCompleter_InvalidRustRootFromUser( self, *args ): + user_options = user_options_store.GetAll().copy( + rust_toolchain_root = '/does/not/exist' ) + assert_that( not GetCompleter( user_options ) ) -@patch( 'ycmd.completers.rust.rust_completer.LOGGER', autospec = True ) -def GetCompleter_WarnsAboutOldConfig_test( logger ): - user_options = user_options_store.GetAll().copy( - rls_binary_path = '/does/not/exist' ) - GetCompleter( user_options ) - logger.warning.assert_called_with( - 'rls_binary_path detected. Did you mean rust_toolchain_root?' ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @patch( 'ycmd.completers.rust.rust_completer.LOGGER', autospec = True ) + def test_GetCompleter_WarnsAboutOldConfig( self, logger ): + user_options = user_options_store.GetAll().copy( + rls_binary_path = '/does/not/exist' ) + GetCompleter( user_options ) + logger.warning.assert_called_with( + 'rls_binary_path detected. Did you mean rust_toolchain_root?' ) diff --git a/ycmd/tests/rust/server_management_test.py b/ycmd/tests/rust/server_management_test.py index ed08e8386e..ed8d1953a0 100644 --- a/ycmd/tests/rust/server_management_test.py +++ b/ycmd/tests/rust/server_management_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -17,6 +17,7 @@ from hamcrest import assert_that, contains_exactly, equal_to, has_entry from unittest.mock import patch +from unittest import TestCase from ycmd.completers.language_server.language_server_completer import ( LanguageServerConnectionTimeout ) @@ -39,92 +40,43 @@ def AssertRustCompleterServerIsRunning( app, is_running ): ) ) -@IsolatedYcmd -def ServerManagement_RestartServer_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) - StartRustCompleterServerInDirectory( app, filepath ) +class ServerManagementTest( TestCase ): + @IsolatedYcmd() + def test_ServerManagement_RestartServer( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + StartRustCompleterServerInDirectory( app, filepath ) - AssertRustCompleterServerIsRunning( app, True ) + AssertRustCompleterServerIsRunning( app, True ) - app.post_json( - '/run_completer_command', - BuildRequest( - filepath = filepath, - filetype = 'rust', - command_arguments = [ 'RestartServer' ], - ), - ) + app.post_json( + '/run_completer_command', + BuildRequest( + filepath = filepath, + filetype = 'rust', + command_arguments = [ 'RestartServer' ], + ), + ) - WaitUntilCompleterServerReady( app, 'rust' ) + WaitUntilCompleterServerReady( app, 'rust' ) - AssertRustCompleterServerIsRunning( app, True ) + AssertRustCompleterServerIsRunning( app, True ) -@IsolatedYcmd -@patch( 'shutil.rmtree', side_effect = OSError ) -@patch( 'ycmd.utils.WaitUntilProcessIsTerminated', - MockProcessTerminationTimingOut ) -def ServerManagement_CloseServer_Unclean_test( wait_until, app ): - StartRustCompleterServerInDirectory( app, PathToTestFile( 'common', 'src' ) ) + @IsolatedYcmd() + @patch( 'shutil.rmtree', side_effect = OSError ) + @patch( 'ycmd.utils.WaitUntilProcessIsTerminated', + MockProcessTerminationTimingOut ) + def test_ServerManagement_CloseServer_Unclean( self, app, *args ): + StartRustCompleterServerInDirectory( app, + PathToTestFile( 'common', 'src' ) ) - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'rust', - command_arguments = [ 'StopServer' ] + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'rust', + command_arguments = [ 'StopServer' ] + ) ) - ) - - request_data = BuildRequest( filetype = 'rust' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) - ) ) - ) ) - - -@IsolatedYcmd -def ServerManagement_StopServerTwice_test( app ): - StartRustCompleterServerInDirectory( app, PathToTestFile( 'common', 'src' ) ) - - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'rust', - command_arguments = [ 'StopServer' ], - ), - ) - - AssertRustCompleterServerIsRunning( app, False ) - - # Stopping a stopped server is a no-op - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'rust', - command_arguments = [ 'StopServer' ], - ), - ) - - AssertRustCompleterServerIsRunning( app, False ) - - -@IsolatedYcmd -def ServerManagement_StartServer_Fails_test( app ): - with patch( 'ycmd.completers.language_server.language_server_completer.' - 'LanguageServerConnection.AwaitServerConnection', - side_effect = LanguageServerConnectionTimeout ): - resp = app.post_json( '/event_notification', - BuildRequest( - event_name = 'FileReadyToParse', - filetype = 'rust', - filepath = PathToTestFile( 'common', 'src', 'main.rs' ), - contents = "" - ) ) - - assert_that( resp.status_code, equal_to( 200 ) ) request_data = BuildRequest( filetype = 'rust' ) assert_that( app.post_json( '/debug_info', request_data ).json, @@ -136,6 +88,53 @@ def ServerManagement_StartServer_Fails_test( app ): ) ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd() + def test_ServerManagement_StopServerTwice( self, app ): + StartRustCompleterServerInDirectory( app, + PathToTestFile( 'common', 'src' ) ) + + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'rust', + command_arguments = [ 'StopServer' ], + ), + ) + + AssertRustCompleterServerIsRunning( app, False ) + + # Stopping a stopped server is a no-op + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'rust', + command_arguments = [ 'StopServer' ], + ), + ) + + AssertRustCompleterServerIsRunning( app, False ) + + + @IsolatedYcmd() + def test_ServerManagement_StartServer_Fails( self, app ): + with patch( 'ycmd.completers.language_server.language_server_completer.' + 'LanguageServerConnection.AwaitServerConnection', + side_effect = LanguageServerConnectionTimeout ): + resp = app.post_json( '/event_notification', + BuildRequest( + event_name = 'FileReadyToParse', + filetype = 'rust', + filepath = PathToTestFile( 'common', 'src', 'main.rs' ), + contents = "" + ) ) + + assert_that( resp.status_code, equal_to( 200 ) ) + + request_data = BuildRequest( filetype = 'rust' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) + ) ) diff --git a/ycmd/tests/rust/signature_help_test.py b/ycmd/tests/rust/signature_help_test.py index 0115323562..f5b4529e29 100644 --- a/ycmd/tests/rust/signature_help_test.py +++ b/ycmd/tests/rust/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -16,10 +16,11 @@ # along with ycmd. If not, see . from hamcrest import assert_that, contains_exactly, empty, equal_to, has_entries +from unittest import TestCase import requests from ycmd.utils import ReadFile -from ycmd.tests.rust import PathToTestFile, SharedYcmd +from ycmd.tests.rust import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( CombineRequest, SignatureMatcher, SignatureAvailableMatcher, @@ -61,78 +62,74 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@WithRetry -@SharedYcmd -def SignatureHelp_NoParams_test( app ): - RunTest( app, { - 'description': 'Trigger after (', - 'request': { - 'filetype' : 'rust', - 'filepath' : PathToTestFile( 'common', 'src', 'test.rs' ), - 'line_num' : 14, - 'column_num': 14, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'fn sig_test()', [] ) - ), - } ), - } ) - } - } ) - - -@WithRetry -@SharedYcmd -def SignatureHelp_MethodTrigger_test( app ): - RunTest( app, { - 'description': 'Trigger after (', - 'request': { - 'filetype' : 'rust', - 'filepath' : PathToTestFile( 'common', 'src', 'test.rs' ), - 'line_num' : 13, - 'column_num': 20, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'fn build_rocket(&self)', [] ) - ), - } ), - } ) - } - } ) - - - -@WithRetry -@SharedYcmd -def Signature_Help_Available_test( app ): - request = { 'filepath' : PathToTestFile( 'common', 'src', 'main.rs' ) } - app.post_json( '/event_notification', - CombineRequest( request, { - 'event_name': 'FileReadyToParse', - 'filetype': 'rust' - } ), - expect_errors = True ) - WaitUntilCompleterServerReady( app, 'rust' ) - - response = app.get( '/signature_help_available', - { 'subserver': 'rust' } ).json - assert_that( response, SignatureAvailableMatcher( 'YES' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class SignatureHelpTest( TestCase ): + @WithRetry() + @SharedYcmd + def test_SignatureHelp_NoParams( self, app ): + RunTest( app, { + 'description': 'Trigger after (', + 'request': { + 'filetype' : 'rust', + 'filepath' : PathToTestFile( 'common', 'src', 'test.rs' ), + 'line_num' : 14, + 'column_num': 14, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'fn sig_test()', [] ) + ), + } ), + } ) + } + } ) + + + @WithRetry() + @SharedYcmd + def test_SignatureHelp_MethodTrigger( self, app ): + RunTest( app, { + 'description': 'Trigger after (', + 'request': { + 'filetype' : 'rust', + 'filepath' : PathToTestFile( 'common', 'src', 'test.rs' ), + 'line_num' : 13, + 'column_num': 20, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( 'fn build_rocket(&self)', [] ) + ), + } ), + } ) + } + } ) + + + + @WithRetry() + @SharedYcmd + def test_Signature_Help_Available( self, app ): + request = { 'filepath' : PathToTestFile( 'common', 'src', 'main.rs' ) } + app.post_json( '/event_notification', + CombineRequest( request, { + 'event_name': 'FileReadyToParse', + 'filetype': 'rust' + } ), + expect_errors = True ) + WaitUntilCompleterServerReady( app, 'rust' ) + + response = app.get( '/signature_help_available', + { 'subserver': 'rust' } ).json + assert_that( response, SignatureAvailableMatcher( 'YES' ) ) diff --git a/ycmd/tests/rust/subcommands_test.py b/ycmd/tests/rust/subcommands_test.py index 2411d1c157..cb209cf37f 100644 --- a/ycmd/tests/rust/subcommands_test.py +++ b/ycmd/tests/rust/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -24,13 +24,14 @@ has_entry, matches_regexp ) from unittest.mock import patch +from unittest import TestCase from pprint import pformat +import itertools import os -import pytest import requests from ycmd import handlers -from ycmd.tests.rust import PathToTestFile, SharedYcmd +from ycmd.tests.rust import PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, ErrorMatcher, @@ -85,223 +86,6 @@ def CombineRequest( request, data ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def Subcommands_DefinedSubcommands_test( app ): - subcommands_data = BuildRequest( completer_target = 'rust' ) - - assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_inanyorder( 'FixIt', - 'Format', - 'GetDoc', - 'GetType', - 'GoTo', - 'GoToDeclaration', - 'GoToDefinition', - 'GoToDocumentOutline', - 'GoToImplementation', - 'GoToReferences', - 'GoToSymbol', - 'GoToType', - 'RefactorRename', - 'RestartServer' ) ) - - -@SharedYcmd -def Subcommands_ServerNotInitialized_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) - - completer = handlers._server_state.GetFiletypeCompleter( [ 'rust' ] ) - - @patch.object( completer, '_ServerIsInitialized', return_value = False ) - def Test( app, cmd, arguments, *args ): - RunTest( app, { - 'description': 'Subcommand ' + cmd + ' handles server not ready', - 'request': { - 'command': cmd, - 'line_num': 1, - 'column_num': 1, - 'filepath': filepath, - 'arguments': arguments, - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, - 'Server is initializing. Please wait.' ), - } - } ) - - Test( app, 'Format', [] ) - Test( app, 'FixIt', [] ) - Test( app, 'GetType', [] ) - Test( app, 'GetDoc', [] ) - Test( app, 'GoTo', [] ) - Test( app, 'GoToDeclaration', [] ) - Test( app, 'GoToDefinition', [] ) - Test( app, 'GoToImplementation', [] ) - Test( app, 'GoToReferences', [] ) - Test( app, 'RefactorRename', [ 'test' ] ) - - -@WithRetry -@SharedYcmd -def Subcommands_Format_WholeFile_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) - - RunTest( app, { - 'description': 'Formatting is applied on the whole file', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 2, - 'insert_spaces': True - } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( "", - LocationMatcher( filepath, 17, 4 ), - LocationMatcher( filepath, 17, 16 ) ), - ChunkMatcher( "", - LocationMatcher( filepath, 18, 1 ), - LocationMatcher( filepath, 19, 1 ) ), - ChunkMatcher( "", - LocationMatcher( filepath, 19, 8 ), - LocationMatcher( filepath, 20, 8 ) ), - ChunkMatcher( "", - LocationMatcher( filepath, 20, 10 ), - LocationMatcher( filepath, 20, 11 ) ), - ChunkMatcher( "", - LocationMatcher( filepath, 20, 13 ), - LocationMatcher( filepath, 21, 1 ) ), - ) - } ) ) - } ) - } - } ) - - -@ExpectedFailure( 'rangeFormat is not yet implemented', - matches_regexp( '\nExpected: <200>\n but: was <500>\n' ) ) -@SharedYcmd -def Subcommands_Format_Range_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) - - RunTest( app, { - 'description': 'Formatting is applied on some part of the file', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'range': { - 'start': { - 'line_num': 17, - 'column_num': 1, - }, - 'end': { - 'line_num': 22, - 'column_num': 2 - } - }, - 'options': { - 'tab_size': 4, - 'insert_spaces': False - } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( 'fn format_test() {\n' - '\tlet a: i32 = 5;\n', - LocationMatcher( filepath, 17, 1 ), - LocationMatcher( filepath, 22, 1 ) ), - ) - } ) ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GetDoc_NoDocumentation_test( app ): - RunTest( app, { - 'description': 'GetDoc on a function with no documentation ' - 'raises an error', - 'request': { - 'command': 'GetDoc', - 'line_num': 3, - 'column_num': 11, - 'filepath': PathToTestFile( 'common', 'src', 'test.rs' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, - 'No documentation available.' ) - } - } ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetDoc_Function_test( app ): - RunTest( app, { - 'description': 'GetDoc on a function returns its documentation', - 'request': { - 'command': 'GetDoc', - 'line_num': 2, - 'column_num': 8, - 'filepath': PathToTestFile( 'common', 'src', 'test.rs' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entry( 'detailed_info', - 'common::test\n' - 'pub fn create_universe()\n' - '---\n' - 'Be careful when using that function' ), - } - } ) - - -@SharedYcmd -def Subcommands_GetType_UnknownType_test( app ): - RunTest( app, { - 'description': 'GetType on a unknown type raises an error', - 'request': { - 'command': 'GetType', - 'line_num': 3, - 'column_num': 4, - 'filepath': PathToTestFile( 'common', 'src', 'test.rs' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'Unknown type.' ) - } - } ) - - -@WithRetry -@SharedYcmd -def Subcommands_GetType_Function_test( app ): - RunTest( app, { - 'description': 'GetType on a function returns its type', - 'request': { - 'command': 'GetType', - 'line_num': 2, - 'column_num': 22, - 'filepath': PathToTestFile( 'common', 'src', 'test.rs' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entry( 'message', 'pub fn create_universe()' ), - } - } ) - - def RunGoToTest( app, command, test ): folder = PathToTestFile( 'common', 'src' ) filepath = os.path.join( folder, test[ 'req' ][ 0 ] ) @@ -347,181 +131,399 @@ def RunGoToTest( app, command, test ): } ) -@pytest.mark.parametrize( 'test', [ - # Variable - { 'req': ( 'main.rs', 14, 5 ), 'res': ( 'test.rs', 4, 12 ) }, - # Type - { 'req': ( 'main.rs', 13, 19 ), 'res': ( 'test.rs', 4, 12 ) }, - # Function - { 'req': ( 'main.rs', 12, 14 ), 'res': 'Cannot jump to location' }, - # Keyword - { 'req': ( 'main.rs', 3, 2 ), 'res': 'Cannot jump to location' }, - ] ) -@SharedYcmd -def Subcommands_GoToType_Basic_test( app, test ): - RunGoToTest( app, 'GoToType', test ) - - -@pytest.mark.parametrize( 'test', [ - # Structure - { 'req': ( 'main.rs', 8, 24 ), 'res': ( 'main.rs', 5, 8 ) }, - # Function - { 'req': ( 'main.rs', 12, 14 ), 'res': ( 'test.rs', 2, 8 ) }, - # Implementation - { 'req': ( 'main.rs', 9, 12 ), 'res': ( 'main.rs', 7, 7 ) }, - # Keyword - { 'req': ( 'main.rs', 3, 2 ), 'res': 'Cannot jump to location' }, - ] ) -@pytest.mark.parametrize( 'command', [ 'GoToDeclaration', - 'GoToDefinition', - 'GoTo' ] ) -@WithRetry -@SharedYcmd -def Subcommands_GoTo_test( app, command, test ): - RunGoToTest( app, command, test ) - - -@pytest.mark.parametrize( 'test', [ - # Structure - { 'req': ( 'main.rs', 5, 9 ), 'res': ( 'main.rs', 8, 21 ) }, - # Trait - { 'req': ( 'main.rs', 7, 7 ), 'res': [ ( 'main.rs', 8, 21 ), - ( 'main.rs', 9, 21 ) ] }, - ] ) -@WithRetry -@SharedYcmd -def Subcommands_GoToImplementation_test( app, test ): - RunGoToTest( app, 'GoToImplementation', test ) - - -@WithRetry -@SharedYcmd -def Subcommands_GoToImplementation_Failure_test( app ): - RunGoToTest( app, - 'GoToImplementation', - { 'req': ( 'main.rs', 11, 2 ), - 'res': 'Cannot jump to location', - 'exc': RuntimeError } ) - - -@pytest.mark.parametrize( 'test', [ - # Struct - { 'req': ( 'main.rs', 9, 22 ), 'res': [ ( 'main.rs', 6, 8 ), - ( 'main.rs', 9, 21 ) ] }, - # Function - { 'req': ( 'main.rs', 12, 8 ), 'res': [ ( 'test.rs', 2, 8 ), - ( 'main.rs', 12, 5 ) ] }, - # Implementation - { 'req': ( 'main.rs', 8, 10 ), 'res': [ ( 'main.rs', 7, 7 ), - ( 'main.rs', 8, 6 ), - ( 'main.rs', 9, 6 ) ] }, - # Keyword - { 'req': ( 'main.rs', 1, 1 ), 'res': 'Cannot jump to location' } - ] ) -@SharedYcmd -def Subcommands_GoToReferences_test( app, test ): - RunGoToTest( app, 'GoToReferences', test ) - - -@WithRetry -@SharedYcmd -def Subcommands_RefactorRename_Works_test( app ): - main_filepath = PathToTestFile( 'common', 'src', 'main.rs' ) - test_filepath = PathToTestFile( 'common', 'src', 'test.rs' ) - - RunTest( app, { - 'description': 'RefactorRename on a function renames all its occurences', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'update_universe' ], - 'line_num': 12, - 'column_num': 16, - 'filepath': main_filepath - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'text': '', - 'chunks': contains_exactly( - ChunkMatcher( 'update_universe', - LocationMatcher( main_filepath, 12, 5 ), - LocationMatcher( main_filepath, 12, 20 ) ), - ChunkMatcher( 'update_universe', - LocationMatcher( test_filepath, 2, 8 ), - LocationMatcher( test_filepath, 2, 23 ) ), - ) - } ) ) +class SubcommandsTest( TestCase ): + @SharedYcmd + def test_Subcommands_DefinedSubcommands( self, app ): + subcommands_data = BuildRequest( completer_target = 'rust' ) + + assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, + contains_inanyorder( 'FixIt', + 'Format', + 'GetDoc', + 'GetType', + 'GoTo', + 'GoToDeclaration', + 'GoToDefinition', + 'GoToDocumentOutline', + 'GoToImplementation', + 'GoToReferences', + 'GoToSymbol', + 'GoToType', + 'RefactorRename', + 'RestartServer' ) ) + + + @SharedYcmd + def test_Subcommands_ServerNotInitialized( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + + completer = handlers._server_state.GetFiletypeCompleter( [ 'rust' ] ) + + @patch.object( completer, '_ServerIsInitialized', return_value = False ) + def Test( app, cmd, arguments, *args ): + RunTest( app, { + 'description': 'Subcommand ' + cmd + ' handles server not ready', + 'request': { + 'command': cmd, + 'line_num': 1, + 'column_num': 1, + 'filepath': filepath, + 'arguments': arguments, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, + 'Server is initializing. Please wait.' ), + } } ) - } - } ) + Test( app, 'Format', [] ) + Test( app, 'FixIt', [] ) + Test( app, 'GetType', [] ) + Test( app, 'GetDoc', [] ) + Test( app, 'GoTo', [] ) + Test( app, 'GoToDeclaration', [] ) + Test( app, 'GoToDefinition', [] ) + Test( app, 'GoToImplementation', [] ) + Test( app, 'GoToReferences', [] ) + Test( app, 'RefactorRename', [ 'test' ] ) -@SharedYcmd -def Subcommands_RefactorRename_Invalid_test( app ): - RunTest( app, { - 'description': 'RefactorRename raises an error when cursor is invalid', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'update_universe' ], - 'line_num': 15, - 'column_num': 7, - 'filepath': PathToTestFile( 'common', 'src', 'main.rs' ) - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, - 'Cannot rename the symbol under cursor.' ) - } - } ) + @WithRetry() + @SharedYcmd + def test_Subcommands_Format_WholeFile( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) -@SharedYcmd -def Subcommands_FixIt_EmptyResponse_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + RunTest( app, { + 'description': 'Formatting is applied on the whole file', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 2, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( "", + LocationMatcher( filepath, 17, 4 ), + LocationMatcher( filepath, 17, 16 ) ), + ChunkMatcher( "", + LocationMatcher( filepath, 18, 1 ), + LocationMatcher( filepath, 19, 1 ) ), + ChunkMatcher( "", + LocationMatcher( filepath, 19, 8 ), + LocationMatcher( filepath, 20, 8 ) ), + ChunkMatcher( "", + LocationMatcher( filepath, 20, 10 ), + LocationMatcher( filepath, 20, 11 ) ), + ChunkMatcher( "", + LocationMatcher( filepath, 20, 13 ), + LocationMatcher( filepath, 21, 1 ) ), + ) + } ) ) + } ) + } + } ) - RunTest( app, { - 'description': 'FixIt on a line with no codeAction returns empty response', - 'request': { - 'command': 'FixIt', - 'line_num': 22, - 'column_num': 1, - 'filepath': filepath - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entry( 'fixits', empty() ) - } - } ) + @ExpectedFailure( + 'rangeFormat is not yet implemented', + matches_regexp( '\nExpected: <200>\n but: was <500>\n' ) ) + @SharedYcmd + def test_Subcommands_Format_Range( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) -@SharedYcmd -def Subcommands_FixIt_Basic_test( app ): - filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + RunTest( app, { + 'description': 'Formatting is applied on some part of the file', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'range': { + 'start': { + 'line_num': 17, + 'column_num': 1, + }, + 'end': { + 'line_num': 22, + 'column_num': 2 + } + }, + 'options': { + 'tab_size': 4, + 'insert_spaces': False + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( 'fn format_test() {\n' + '\tlet a: i32 = 5;\n', + LocationMatcher( filepath, 17, 1 ), + LocationMatcher( filepath, 22, 1 ) ), + ) + } ) ) + } ) + } + } ) - RunTest( app, { - 'description': 'Simple FixIt test', - 'request': { - 'command': 'FixIt', - 'line_num': 17, - 'column_num': 2, - 'filepath': filepath - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( 'pub(crate) ', - LocationMatcher( filepath, 17, 1 ), - LocationMatcher( filepath, 17, 1 ) ) - ) - } ) ) - } ) - }, - } ) + + @SharedYcmd + def test_Subcommands_GetDoc_NoDocumentation( self, app ): + RunTest( app, { + 'description': 'GetDoc on a function with no documentation ' + 'raises an error', + 'request': { + 'command': 'GetDoc', + 'line_num': 3, + 'column_num': 11, + 'filepath': PathToTestFile( 'common', 'src', 'test.rs' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, + 'No documentation available.' ) + } + } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @WithRetry() + @SharedYcmd + def test_Subcommands_GetDoc_Function( self, app ): + RunTest( app, { + 'description': 'GetDoc on a function returns its documentation', + 'request': { + 'command': 'GetDoc', + 'line_num': 2, + 'column_num': 8, + 'filepath': PathToTestFile( 'common', 'src', 'test.rs' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entry( 'detailed_info', + 'common::test\n' + 'pub fn create_universe()\n' + '---\n' + 'Be careful when using that function' ), + } + } ) + + + @SharedYcmd + def test_Subcommands_GetType_UnknownType( self, app ): + RunTest( app, { + 'description': 'GetType on a unknown type raises an error', + 'request': { + 'command': 'GetType', + 'line_num': 3, + 'column_num': 4, + 'filepath': PathToTestFile( 'common', 'src', 'test.rs' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, 'Unknown type.' ) + } + } ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GetType_Function( self, app ): + RunTest( app, { + 'description': 'GetType on a function returns its type', + 'request': { + 'command': 'GetType', + 'line_num': 2, + 'column_num': 22, + 'filepath': PathToTestFile( 'common', 'src', 'test.rs' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entry( 'message', 'pub fn create_universe()' ), + } + } ) + + + @SharedYcmd + def test_Subcommands_GoToType_Basic( self, app ): + for test in [ + # Variable + { 'req': ( 'main.rs', 14, 5 ), 'res': ( 'test.rs', 4, 12 ) }, + # Type + { 'req': ( 'main.rs', 13, 19 ), 'res': ( 'test.rs', 4, 12 ) }, + # Function + { 'req': ( 'main.rs', 12, 14 ), 'res': 'Cannot jump to location' }, + # Keyword + { 'req': ( 'main.rs', 3, 2 ), 'res': 'Cannot jump to location' }, + ]: + with self.subTest( test = test ): + RunGoToTest( app, 'GoToType', test ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoTo( self, app ): + for test, command in itertools.product( + [ + # Structure + { 'req': ( 'main.rs', 8, 24 ), 'res': ( 'main.rs', 5, 8 ) }, + # Function + { 'req': ( 'main.rs', 12, 14 ), 'res': ( 'test.rs', 2, 8 ) }, + # Implementation + { 'req': ( 'main.rs', 9, 12 ), 'res': ( 'main.rs', 7, 7 ) }, + # Keyword + { 'req': ( 'main.rs', 3, 2 ), 'res': 'Cannot jump to location' }, + ], + [ 'GoToDefinition', 'GoToDeclaration', 'GoTo' ] ): + with self.subTest( test = test, command = command ): + RunGoToTest( app, command, test ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoToImplementation( self, app ): + for test in [ + # Structure + { 'req': ( 'main.rs', 5, 9 ), 'res': ( 'main.rs', 8, 21 ) }, + # Trait + { 'req': ( 'main.rs', 7, 7 ), 'res': [ ( 'main.rs', 8, 21 ), + ( 'main.rs', 9, 21 ) ] }, + ]: + with self.subTest( test = test ): + RunGoToTest( app, 'GoToImplementation', test ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_GoToImplementation_Failure( self, app ): + RunGoToTest( app, + 'GoToImplementation', + { 'req': ( 'main.rs', 11, 2 ), + 'res': 'Cannot jump to location', + 'exc': RuntimeError } ) + + + @SharedYcmd + def test_Subcommands_GoToReferences( self, app ): + for test in [ + # Struct + { 'req': ( 'main.rs', 9, 22 ), 'res': [ ( 'main.rs', 6, 8 ), + ( 'main.rs', 9, 21 ) ] }, + # Function + { 'req': ( 'main.rs', 12, 8 ), 'res': [ ( 'test.rs', 2, 8 ), + ( 'main.rs', 12, 5 ) ] }, + # Implementation + { 'req': ( 'main.rs', 8, 10 ), 'res': [ ( 'main.rs', 7, 7 ), + ( 'main.rs', 8, 6 ), + ( 'main.rs', 9, 6 ) ] }, + # Keyword + { 'req': ( 'main.rs', 1, 1 ), 'res': 'Cannot jump to location' } + ]: + with self.subTest( test = test ): + RunGoToTest( app, 'GoToReferences', test ) + + + @WithRetry() + @SharedYcmd + def test_Subcommands_RefactorRename_Works( self, app ): + main_filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + test_filepath = PathToTestFile( 'common', 'src', 'test.rs' ) + + RunTest( app, { + 'description': 'RefactorRename on a function renames all its occurences', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'update_universe' ], + 'line_num': 12, + 'column_num': 16, + 'filepath': main_filepath + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'text': '', + 'chunks': contains_exactly( + ChunkMatcher( 'update_universe', + LocationMatcher( main_filepath, 12, 5 ), + LocationMatcher( main_filepath, 12, 20 ) ), + ChunkMatcher( 'update_universe', + LocationMatcher( test_filepath, 2, 8 ), + LocationMatcher( test_filepath, 2, 23 ) ), + ) + } ) ) + } ) + } + } ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_Invalid( self, app ): + RunTest( app, { + 'description': 'RefactorRename raises an error when cursor is invalid', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'update_universe' ], + 'line_num': 15, + 'column_num': 7, + 'filepath': PathToTestFile( 'common', 'src', 'main.rs' ) + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, + 'Cannot rename the symbol under cursor.' ) + } + } ) + + + @SharedYcmd + def test_Subcommands_FixIt_EmptyResponse( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + + RunTest( app, { + 'description': 'FixIt on a line with no ' + 'codeAction returns empty response', + 'request': { + 'command': 'FixIt', + 'line_num': 22, + 'column_num': 1, + 'filepath': filepath + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entry( 'fixits', empty() ) + } + } ) + + + @SharedYcmd + def test_Subcommands_FixIt_Basic( self, app ): + filepath = PathToTestFile( 'common', 'src', 'main.rs' ) + + RunTest( app, { + 'description': 'Simple FixIt test', + 'request': { + 'command': 'FixIt', + 'line_num': 17, + 'column_num': 2, + 'filepath': filepath + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( 'pub(crate) ', + LocationMatcher( filepath, 17, 1 ), + LocationMatcher( filepath, 17, 1 ) ) + ) + } ) ) + } ) + }, + } ) diff --git a/ycmd/tests/rust/testdata/common/Cargo.lock b/ycmd/tests/rust/testdata/common/Cargo.lock new file mode 100644 index 0000000000..db83ec7346 --- /dev/null +++ b/ycmd/tests/rust/testdata/common/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "common" +version = "0.1.0" diff --git a/ycmd/tests/shutdown_test.py b/ycmd/tests/shutdown_test.py index dbf9400f4d..c9f6a0e433 100644 --- a/ycmd/tests/shutdown_test.py +++ b/ycmd/tests/shutdown_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -19,9 +19,10 @@ from threading import Event import time import requests -import pytest +import os +import unittest -from ycmd.tests.client_test import Client_test +from ycmd.tests.client_test import ClientTest from ycmd.utils import StartThread # Time to wait (int seconds) for all the servers to shutdown. Tweak for the CI @@ -29,10 +30,10 @@ SUBSERVER_SHUTDOWN_TIMEOUT = 120 -class Shutdown_test( Client_test ): +class ShutdownTest( ClientTest ): - @Client_test.CaptureLogfiles - def FromHandlerWithoutSubserver_test( self ): + @ClientTest.CaptureLogfiles + def test_FromHandlerWithoutSubserver( self ): self.Start() self.AssertServersAreRunning() @@ -48,9 +49,8 @@ def FromHandlerWithoutSubserver_test( self ): self.AssertLogfilesAreRemoved() - @pytest.mark.valgrind_skip - @Client_test.CaptureLogfiles - def FromHandlerWithSubservers_test( self ): + @ClientTest.CaptureLogfiles + def test_FromHandlerWithSubservers( self ): self.Start() filetypes = [ 'cpp', @@ -75,8 +75,8 @@ def FromHandlerWithSubservers_test( self ): self.AssertLogfilesAreRemoved() - @Client_test.CaptureLogfiles - def FromWatchdogWithoutSubserver_test( self ): + @ClientTest.CaptureLogfiles + def test_FromWatchdogWithoutSubserver( self ): self.Start( idle_suicide_seconds = 2, check_interval_seconds = 1 ) self.AssertServersAreRunning() @@ -84,9 +84,8 @@ def FromWatchdogWithoutSubserver_test( self ): self.AssertLogfilesAreRemoved() - @pytest.mark.valgrind_skip - @Client_test.CaptureLogfiles - def FromWatchdogWithSubservers_test( self ): + @ClientTest.CaptureLogfiles + def test_FromWatchdogWithSubservers( self ): all_servers_are_running = Event() def KeepServerAliveInAnotherThread(): @@ -119,6 +118,16 @@ def KeepServerAliveInAnotherThread(): self.AssertLogfilesAreRemoved() -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +def load_tests( loader: unittest.TestLoader, tests, pattern ): + suite = unittest.TestSuite() + test_names = loader.getTestCaseNames( ShutdownTest ) + if os.environ.get( 'YCM_VALGRIND_RUN' ): + def allowed_tests( name: str ): + return 'WithoutSubserver' in name + else: + def allowed_tests( name: str ): + return True + tests = loader.loadTestsFromNames( filter( allowed_tests, test_names ), + ShutdownTest ) + suite.addTests( tests ) + return suite diff --git a/ycmd/tests/signature_help_test.py b/ycmd/tests/signature_help_test.py index a9de229c30..5859ba8b59 100644 --- a/ycmd/tests/signature_help_test.py +++ b/ycmd/tests/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -16,48 +16,45 @@ # along with ycmd. If not, see . from hamcrest import ( assert_that, empty, has_entries ) +from unittest import TestCase from ycmd.tests import SharedYcmd, IsolatedYcmd from ycmd.tests.test_utils import ( EMPTY_SIGNATURE_HELP, BuildRequest ) -@SharedYcmd -def SignatureHelp_IdentifierCompleter_test( app ): - event_data = BuildRequest( contents = 'foo foogoo ba', - event_name = 'FileReadyToParse' ) +class SignatureHelpTest( TestCase ): + @SharedYcmd + def test_SignatureHelp_IdentifierCompleter( self, app ): + event_data = BuildRequest( contents = 'foo foogoo ba', + event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', event_data ) + app.post_json( '/event_notification', event_data ) - # query is 'oo' - request_data = BuildRequest( contents = 'oo foo foogoo ba', - column_num = 3 ) - response_data = app.post_json( '/signature_help', request_data ).json + # query is 'oo' + request_data = BuildRequest( contents = 'oo foo foogoo ba', + column_num = 3 ) + response_data = app.post_json( '/signature_help', request_data ).json - assert_that( response_data, has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP - } ) ) + assert_that( response_data, has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP + } ) ) -@IsolatedYcmd( { 'disable_signature_help': 1 } ) -def SignatureHelp_IdentifierCompleter_disabled_test( app ): - event_data = BuildRequest( contents = 'foo foogoo ba', - event_name = 'FileReadyToParse' ) + @IsolatedYcmd( { 'disable_signature_help': 1 } ) + def test_SignatureHelp_IdentifierCompleter_disabled( self, app ): + event_data = BuildRequest( contents = 'foo foogoo ba', + event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', event_data ) + app.post_json( '/event_notification', event_data ) - # query is 'oo' - request_data = BuildRequest( contents = 'oo foo foogoo ba', - column_num = 3 ) - response_data = app.post_json( '/signature_help', request_data ).json + # query is 'oo' + request_data = BuildRequest( contents = 'oo foo foogoo ba', + column_num = 3 ) + response_data = app.post_json( '/signature_help', request_data ).json - assert_that( response_data, has_entries( { - 'errors': empty(), - 'signature_help': EMPTY_SIGNATURE_HELP - } ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + assert_that( response_data, has_entries( { + 'errors': empty(), + 'signature_help': EMPTY_SIGNATURE_HELP + } ) ) diff --git a/ycmd/tests/subcommands_test.py b/ycmd/tests/subcommands_test.py index fee9af9968..dfdb120964 100644 --- a/ycmd/tests/subcommands_test.py +++ b/ycmd/tests/subcommands_test.py @@ -1,5 +1,4 @@ -# Copyright (C) 2013 Google Inc. -# 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -18,35 +17,34 @@ from hamcrest import assert_that, contains_exactly from unittest.mock import patch +from unittest import TestCase from ycmd.tests import SharedYcmd from ycmd.tests.test_utils import BuildRequest, DummyCompleter, PatchCompleter -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.GetSubcommandsMap', - return_value = { 'A': lambda x: x, - 'B': lambda x: x, - 'C': lambda x: x } ) -def Subcommands_Basic_test( get_subcmd_map, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - subcommands_data = BuildRequest( completer_target = 'dummy_filetype' ) - assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_exactly( 'A', 'B', 'C' ) ) - - -@SharedYcmd -@patch( 'ycmd.tests.test_utils.DummyCompleter.GetSubcommandsMap', - return_value = { 'A': lambda x: x, - 'B': lambda x: x, - 'C': lambda x: x } ) -def Subcommands_NoExplicitCompleterTargetSpecified_test( get_subcmd_map, app ): - with PatchCompleter( DummyCompleter, 'dummy_filetype' ): - subcommands_data = BuildRequest( filetype = 'dummy_filetype' ) - assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_exactly( 'A', 'B', 'C' ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class SubcommandsTest( TestCase ): + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.GetSubcommandsMap', + return_value = { 'A': lambda x: x, + 'B': lambda x: x, + 'C': lambda x: x } ) + def test_Subcommands_Basic( self, app, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + subcommands_data = BuildRequest( completer_target = 'dummy_filetype' ) + assert_that( app.post_json( '/defined_subcommands', + subcommands_data ).json, + contains_exactly( 'A', 'B', 'C' ) ) + + + @SharedYcmd + @patch( 'ycmd.tests.test_utils.DummyCompleter.GetSubcommandsMap', + return_value = { 'A': lambda x: x, + 'B': lambda x: x, + 'C': lambda x: x } ) + def test_Subcommands_NoExplicitCompleterTargetSpecified( self, app, *args ): + with PatchCompleter( DummyCompleter, 'dummy_filetype' ): + subcommands_data = BuildRequest( filetype = 'dummy_filetype' ) + assert_that( app.post_json( '/defined_subcommands', + subcommands_data ).json, + contains_exactly( 'A', 'B', 'C' ) ) diff --git a/ycmd/tests/tern/__init__.py b/ycmd/tests/tern/__init__.py index 5dac5a4a95..c54ee19d3d 100644 --- a/ycmd/tests/tern/__init__.py +++ b/ycmd/tests/tern/__init__.py @@ -15,4 +15,65 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -from ycmd.tests.tern.conftest import * # noqa +import functools +import os + +from ycmd.tests.test_utils import ( BuildRequest, + ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp, + StopCompleterServer, + WaitUntilCompleterServerReady ) + +shared_app = None + + +def setUpModule(): + global shared_app + shared_app = SetUpApp() + StartJavaScriptCompleterServerInDirectory( shared_app, PathToTestFile() ) + + +def tearDownModule(): + global shared_app + StopCompleterServer( shared_app, 'tern' ) + + +def StartJavaScriptCompleterServerInDirectory( app, directory ): + app.post_json( '/event_notification', + BuildRequest( + filepath = os.path.join( directory, 'test.js' ), + event_name = 'FileReadyToParse', + filetype = 'javascript' ) ) + WaitUntilCompleterServerReady( app, 'javascript' ) + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'javascript' ) + return Wrapper + return Decorator + + + +def PathToTestFile( *args ): + dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) + return os.path.join( dir_of_current_script, 'testdata', *args ) diff --git a/ycmd/tests/tern/conftest.py b/ycmd/tests/tern/conftest.py deleted file mode 100644 index 5b09fcf0f7..0000000000 --- a/ycmd/tests/tern/conftest.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import os -import pytest -from ycmd.tests.test_utils import ( BuildRequest, ClearCompletionsCache, - IsolatedApp, SetUpApp, StopCompleterServer, - WaitUntilCompleterServerReady ) -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - """Initializes the ycmd server as a WebTest application that will be shared - by all tests using the SharedYcmd decorator in this package. Additional - configuration that is common to these tests, like starting a semantic - subserver, should be done here.""" - global shared_app - shared_app = SetUpApp() - StartJavaScriptCompleterServerInDirectory( shared_app, PathToTestFile() ) - yield - StopCompleterServer( shared_app, 'java' ) - - -def StartJavaScriptCompleterServerInDirectory( app, directory ): - app.post_json( '/event_notification', - BuildRequest( - filepath = os.path.join( directory, 'test.js' ), - event_name = 'FileReadyToParse', - filetype = 'javascript' ) ) - WaitUntilCompleterServerReady( app, 'javascript' ) - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with IsolatedApp( {} ) as app: - yield app - StopCompleterServer( app, 'javascript' ) - else: - global shared_app - ClearCompletionsCache() - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes a unique ycmd application as a parameter. It should be used on tests -that change the server state in a irreversible way (ex: a semantic subserver -is stopped or restarted) or expect a clean state (ex: no semantic subserver -started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter -|custom_options| to give additional options and/or override the default ones. - -Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... -""" -IsolatedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def PathToTestFile( *args ): - dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) - return os.path.join( dir_of_current_script, 'testdata', *args ) diff --git a/ycmd/tests/tern/debug_info_test.py b/ycmd/tests/tern/debug_info_test.py index 1ac4aac8ee..ae874dec68 100644 --- a/ycmd/tests/tern/debug_info_test.py +++ b/ycmd/tests/tern/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -21,43 +21,40 @@ has_entries, has_entry, instance_of ) +from unittest import TestCase -from ycmd.tests.tern import SharedYcmd +from ycmd.tests.tern import SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import BuildRequest -@SharedYcmd -def DebugInfo_test( app ): - request_data = BuildRequest( filetype = 'javascript' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'JavaScript', - 'servers': contains_exactly( has_entries( { - 'name': 'Tern', - 'is_running': instance_of( bool ), - 'executable': instance_of( str ), - 'pid': instance_of( int ), - 'address': instance_of( str ), - 'port': instance_of( int ), - 'logfiles': contains_exactly( instance_of( str ), - instance_of( str ) ), - 'extras': contains_exactly( - has_entries( { - 'key': 'configuration file', - 'value': instance_of( str ) - } ), - has_entries( { - 'key': 'working directory', - 'value': instance_of( str ) - } ) - ), - } ) ), - 'items': empty() - } ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True +class DebugInfoTest( TestCase ): + @SharedYcmd + def test_DebugInfo( self, app ): + request_data = BuildRequest( filetype = 'javascript' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'JavaScript', + 'servers': contains_exactly( has_entries( { + 'name': 'Tern', + 'is_running': instance_of( bool ), + 'executable': instance_of( str ), + 'pid': instance_of( int ), + 'address': instance_of( str ), + 'port': instance_of( int ), + 'logfiles': contains_exactly( instance_of( str ), + instance_of( str ) ), + 'extras': contains_exactly( + has_entries( { + 'key': 'configuration file', + 'value': instance_of( str ) + } ), + has_entries( { + 'key': 'working directory', + 'value': instance_of( str ) + } ) + ), + } ) ), + 'items': empty() + } ) ) + ) diff --git a/ycmd/tests/tern/event_notification_test.py b/ycmd/tests/tern/event_notification_test.py index de6ba63112..7e7ee7a8b5 100644 --- a/ycmd/tests/tern/event_notification_test.py +++ b/ycmd/tests/tern/event_notification_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -22,6 +22,7 @@ has_entries, none ) from unittest.mock import patch +from unittest import TestCase from pprint import pformat import os import requests @@ -31,242 +32,239 @@ from ycmd import utils -@IsolatedYcmd -def EventNotification_OnFileReadyToParse_ProjectFile_cwd_test( app ): - response = app.post_json( '/event_notification', - BuildRequest( - filepath = PathToTestFile(), - event_name = 'FileReadyToParse', - filetype = 'javascript' ), - expect_errors = True ) - - assert_that( response.status_code, equal_to( requests.codes.ok ) ) - assert_that( response.json, empty() ) - - debug_info = app.post_json( '/debug_info', - BuildRequest( filetype = 'javascript' ) ).json - assert_that( - debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], - contains_exactly( - has_entries( { - 'key': 'configuration file', - 'value': PathToTestFile( '.tern-project' ) - } ), - has_entries( { - 'key': 'working directory', - 'value': PathToTestFile() - } ) +class EventNotificationTest( TestCase ): + @IsolatedYcmd() + def test_EventNotification_OnFileReadyToParse_ProjectFile_cwd( self, app ): + response = app.post_json( '/event_notification', + BuildRequest( + filepath = PathToTestFile(), + event_name = 'FileReadyToParse', + filetype = 'javascript' ), + expect_errors = True ) + + assert_that( response.status_code, equal_to( requests.codes.ok ) ) + assert_that( response.json, empty() ) + + debug_info = app.post_json( '/debug_info', + BuildRequest( filetype = 'javascript' ) ).json + assert_that( + debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], + contains_exactly( + has_entries( { + 'key': 'configuration file', + 'value': PathToTestFile( '.tern-project' ) + } ), + has_entries( { + 'key': 'working directory', + 'value': PathToTestFile() + } ) + ) ) - ) - - -@IsolatedYcmd -def EventNotification_OnFileReadyToParse_ProjectFile_parentdir_test( app ): - response = app.post_json( '/event_notification', - BuildRequest( - filepath = PathToTestFile( 'lamelib' ), - event_name = 'FileReadyToParse', - filetype = 'javascript' ), - expect_errors = True ) - - assert_that( response.status_code, equal_to( requests.codes.ok ) ) - assert_that( response.json, empty() ) - - debug_info = app.post_json( '/debug_info', - BuildRequest( filetype = 'javascript' ) ).json - assert_that( - debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], - contains_exactly( - has_entries( { - 'key': 'configuration file', - 'value': PathToTestFile( '.tern-project' ) - } ), - has_entries( { - 'key': 'working directory', - 'value': PathToTestFile() - } ) + + + @IsolatedYcmd() + def test_EventNotification_OnFileReadyToParse_ProjectFile_parentdir( + self, app ): + response = app.post_json( '/event_notification', + BuildRequest( + filepath = PathToTestFile( 'lamelib' ), + event_name = 'FileReadyToParse', + filetype = 'javascript' ), + expect_errors = True ) + + assert_that( response.status_code, equal_to( requests.codes.ok ) ) + assert_that( response.json, empty() ) + + debug_info = app.post_json( '/debug_info', + BuildRequest( filetype = 'javascript' ) ).json + assert_that( + debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], + contains_exactly( + has_entries( { + 'key': 'configuration file', + 'value': PathToTestFile( '.tern-project' ) + } ), + has_entries( { + 'key': 'working directory', + 'value': PathToTestFile() + } ) + ) ) - ) - - -@IsolatedYcmd -@patch( 'ycmd.completers.javascript.tern_completer.GlobalConfigExists', - return_value = False ) -def EventNotification_OnFileReadyToParse_NoProjectFile_test( - global_config_exists, app ): - # We raise an error if we can't detect a .tern-project file. - # We only do this on the first OnFileReadyToParse event after a - # server startup. - response = app.post_json( '/event_notification', - BuildRequest( filepath = PathToTestFile( '..' ), - event_name = 'FileReadyToParse', - filetype = 'javascript' ), - expect_errors = True ) - - - print( f'event response: { pformat( response.json ) }' ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( - response.json, - ErrorMatcher( RuntimeError, - 'Warning: Unable to detect a .tern-project file ' - 'in the hierarchy before ' + PathToTestFile( '..' ) + - ' and no global .tern-config file was found. ' - 'This is required for accurate JavaScript ' - 'completion. Please see the User Guide for ' - 'details.' ) - ) - - debug_info = app.post_json( '/debug_info', - BuildRequest( filetype = 'javascript' ) ).json - assert_that( - debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], - contains_exactly( - has_entries( { - 'key': 'configuration file', - 'value': none() - } ), - has_entries( { - 'key': 'working directory', - 'value': utils.GetCurrentDirectory() - } ) + + + @IsolatedYcmd() + @patch( 'ycmd.completers.javascript.tern_completer.GlobalConfigExists', + return_value = False ) + def test_EventNotification_OnFileReadyToParse_NoProjectFile( + self, app, *args ): + # We raise an error if we can't detect a .tern-project file. + # We only do this on the first OnFileReadyToParse event after a + # server startup. + response = app.post_json( '/event_notification', + BuildRequest( filepath = PathToTestFile( '..' ), + event_name = 'FileReadyToParse', + filetype = 'javascript' ), + expect_errors = True ) + + + print( f'event response: { pformat( response.json ) }' ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( + response.json, + ErrorMatcher( RuntimeError, + 'Warning: Unable to detect a .tern-project file ' + 'in the hierarchy before ' + PathToTestFile( '..' ) + + ' and no global .tern-config file was found. ' + 'This is required for accurate JavaScript ' + 'completion. Please see the User Guide for ' + 'details.' ) ) - ) - - # Check that a subsequent call does *not* raise the error. - response = app.post_json( '/event_notification', - BuildRequest( event_name = 'FileReadyToParse', - filetype = 'javascript' ), - expect_errors = True ) - - print( f'event response: { pformat( response.json ) }' ) - - assert_that( response.status_code, equal_to( requests.codes.ok ) ) - assert_that( response.json, empty() ) - - # Restart the server and check that it raises it again. - app.post_json( '/run_completer_command', - BuildRequest( filepath = PathToTestFile( '..' ), - command_arguments = [ 'RestartServer' ], - filetype = 'javascript' ) ) - - response = app.post_json( '/event_notification', - BuildRequest( filepath = PathToTestFile( '..' ), - event_name = 'FileReadyToParse', - filetype = 'javascript' ), - expect_errors = True ) - - print( f'event response: { pformat( response.json ) }' ) - - assert_that( response.status_code, - equal_to( requests.codes.internal_server_error ) ) - - assert_that( - response.json, - ErrorMatcher( RuntimeError, - 'Warning: Unable to detect a .tern-project file ' - 'in the hierarchy before ' + PathToTestFile( '..' ) + - ' and no global .tern-config file was found. ' - 'This is required for accurate JavaScript ' - 'completion. Please see the User Guide for ' - 'details.' ) - ) - - # Finally, restart the server in a folder containing a .tern-project file. We - # expect no error in that case. - app.post_json( '/run_completer_command', - BuildRequest( filepath = PathToTestFile(), - command_arguments = [ 'RestartServer' ], - filetype = 'javascript' ) ) - - response = app.post_json( '/event_notification', - BuildRequest( filepath = PathToTestFile(), - event_name = 'FileReadyToParse', - filetype = 'javascript' ), - expect_errors = True ) - - print( f'event response: { pformat( response.json ) }' ) - - assert_that( response.status_code, equal_to( requests.codes.ok ) ) - assert_that( response.json, empty() ) - - debug_info = app.post_json( '/debug_info', - BuildRequest( filetype = 'javascript' ) ).json - assert_that( - debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], - contains_exactly( - has_entries( { - 'key': 'configuration file', - 'value': PathToTestFile( '.tern-project' ) - } ), - has_entries( { - 'key': 'working directory', - 'value': PathToTestFile() - } ) + + debug_info = app.post_json( '/debug_info', + BuildRequest( filetype = 'javascript' ) ).json + assert_that( + debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], + contains_exactly( + has_entries( { + 'key': 'configuration file', + 'value': none() + } ), + has_entries( { + 'key': 'working directory', + 'value': utils.GetCurrentDirectory() + } ) + ) ) - ) - - -@IsolatedYcmd -@patch( 'ycmd.completers.javascript.tern_completer.GlobalConfigExists', - return_value = True ) -def EventNotification_OnFileReadyToParse_UseGlobalConfig_test( - global_config_exists, app ): - # No working directory is given. - response = app.post_json( '/event_notification', - BuildRequest( filepath = PathToTestFile( '..' ), - event_name = 'FileReadyToParse', - filetype = 'javascript' ), - expect_errors = True ) - - print( f'event response: { pformat( response.json ) }' ) - - assert_that( response.status_code, equal_to( requests.codes.ok ) ) - assert_that( response.json, empty() ) - - debug_info = app.post_json( '/debug_info', - BuildRequest( filetype = 'javascript' ) ).json - assert_that( - debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], - contains_exactly( - has_entries( { - 'key': 'configuration file', - 'value': os.path.join( os.path.expanduser( '~' ), '.tern-config' ) - } ), - has_entries( { - 'key': 'working directory', - 'value': utils.GetCurrentDirectory() - } ) + + # Check that a subsequent call does *not* raise the error. + response = app.post_json( '/event_notification', + BuildRequest( event_name = 'FileReadyToParse', + filetype = 'javascript' ), + expect_errors = True ) + + print( f'event response: { pformat( response.json ) }' ) + + assert_that( response.status_code, equal_to( requests.codes.ok ) ) + assert_that( response.json, empty() ) + + # Restart the server and check that it raises it again. + app.post_json( '/run_completer_command', + BuildRequest( filepath = PathToTestFile( '..' ), + command_arguments = [ 'RestartServer' ], + filetype = 'javascript' ) ) + + response = app.post_json( '/event_notification', + BuildRequest( filepath = PathToTestFile( '..' ), + event_name = 'FileReadyToParse', + filetype = 'javascript' ), + expect_errors = True ) + + print( f'event response: { pformat( response.json ) }' ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( + response.json, + ErrorMatcher( RuntimeError, + 'Warning: Unable to detect a .tern-project file ' + 'in the hierarchy before ' + PathToTestFile( '..' ) + + ' and no global .tern-config file was found. ' + 'This is required for accurate JavaScript ' + 'completion. Please see the User Guide for ' + 'details.' ) ) - ) - - # Restart the server with a working directory. - app.post_json( '/run_completer_command', - BuildRequest( filepath = PathToTestFile( '..' ), - command_arguments = [ 'RestartServer' ], - filetype = 'javascript', - working_dir = PathToTestFile() ) ) - - debug_info = app.post_json( '/debug_info', - BuildRequest( filetype = 'javascript' ) ).json - assert_that( - debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], - contains_exactly( - has_entries( { - 'key': 'configuration file', - 'value': os.path.join( os.path.expanduser( '~' ), '.tern-config' ) - } ), - has_entries( { - 'key': 'working directory', - 'value': PathToTestFile() - } ) + + # Finally, restart the server in a folder containing a .tern-project file. + # We expect no error in that case. + app.post_json( '/run_completer_command', + BuildRequest( filepath = PathToTestFile(), + command_arguments = [ 'RestartServer' ], + filetype = 'javascript' ) ) + + response = app.post_json( '/event_notification', + BuildRequest( filepath = PathToTestFile(), + event_name = 'FileReadyToParse', + filetype = 'javascript' ), + expect_errors = True ) + + print( f'event response: { pformat( response.json ) }' ) + + assert_that( response.status_code, equal_to( requests.codes.ok ) ) + assert_that( response.json, empty() ) + + debug_info = app.post_json( '/debug_info', + BuildRequest( filetype = 'javascript' ) ).json + assert_that( + debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], + contains_exactly( + has_entries( { + 'key': 'configuration file', + 'value': PathToTestFile( '.tern-project' ) + } ), + has_entries( { + 'key': 'working directory', + 'value': PathToTestFile() + } ) + ) ) - ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd() + @patch( 'ycmd.completers.javascript.tern_completer.GlobalConfigExists', + return_value = True ) + def test_EventNotification_OnFileReadyToParse_UseGlobalConfig( + self, app, *args ): + # No working directory is given. + response = app.post_json( '/event_notification', + BuildRequest( filepath = PathToTestFile( '..' ), + event_name = 'FileReadyToParse', + filetype = 'javascript' ), + expect_errors = True ) + + print( f'event response: { pformat( response.json ) }' ) + + assert_that( response.status_code, equal_to( requests.codes.ok ) ) + assert_that( response.json, empty() ) + + debug_info = app.post_json( '/debug_info', + BuildRequest( filetype = 'javascript' ) ).json + assert_that( + debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], + contains_exactly( + has_entries( { + 'key': 'configuration file', + 'value': os.path.join( os.path.expanduser( '~' ), '.tern-config' ) + } ), + has_entries( { + 'key': 'working directory', + 'value': utils.GetCurrentDirectory() + } ) + ) + ) + + # Restart the server with a working directory. + app.post_json( '/run_completer_command', + BuildRequest( filepath = PathToTestFile( '..' ), + command_arguments = [ 'RestartServer' ], + filetype = 'javascript', + working_dir = PathToTestFile() ) ) + + debug_info = app.post_json( '/debug_info', + BuildRequest( filetype = 'javascript' ) ).json + assert_that( + debug_info[ 'completer' ][ 'servers' ][ 0 ][ 'extras' ], + contains_exactly( + has_entries( { + 'key': 'configuration file', + 'value': os.path.join( os.path.expanduser( '~' ), '.tern-config' ) + } ), + has_entries( { + 'key': 'working directory', + 'value': PathToTestFile() + } ) + ) + ) diff --git a/ycmd/tests/tern/get_completions_test.py b/ycmd/tests/tern/get_completions_test.py index 890b1e65bd..302680e7fd 100644 --- a/ycmd/tests/tern/get_completions_test.py +++ b/ycmd/tests/tern/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -22,10 +22,12 @@ equal_to, has_entries ) from pprint import pformat +from unittest import TestCase import requests -from ycmd.tests.tern import ( IsolatedYcmd, PathToTestFile, SharedYcmd, - StartJavaScriptCompleterServerInDirectory ) +from ycmd.tests.tern import ( IsolatedYcmd, PathToTestFile, SharedYcmd, # noqa + StartJavaScriptCompleterServerInDirectory, + setUpModule, tearDownModule ) from ycmd.tests.test_utils import CombineRequest, CompletionEntryMatcher from ycmd.utils import ReadFile @@ -77,424 +79,420 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def GetCompletions_NoQuery_test( app ): - RunTest( app, { - 'description': 'semantic completion works for simple object no query', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'simple_test.js' ), - 'line_num' : 13, - 'column_num': 43, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'a_simple_function', - 'fn(param: ?) -> string' ), - CompletionEntryMatcher( 'basic_type', 'number' ), - CompletionEntryMatcher( 'object', 'object' ), - CompletionEntryMatcher( 'toString', 'fn() -> string' ), - CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), - CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), - CompletionEntryMatcher( 'hasOwnProperty', - 'fn(prop: string) -> bool' ), - CompletionEntryMatcher( 'isPrototypeOf', - 'fn(obj: ?) -> bool' ), - CompletionEntryMatcher( 'propertyIsEnumerable', - 'fn(prop: string) -> bool' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Query_test( app ): - RunTest( app, { - 'description': 'semantic completion works for simple object with query', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'simple_test.js' ), - 'line_num' : 14, - 'column_num': 45, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'basic_type', 'number' ), - CompletionEntryMatcher( 'isPrototypeOf', - 'fn(obj: ?) -> bool' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Require_NoQuery_test( app ): - RunTest( app, { - 'description': 'semantic completion works for simple object no query', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'requirejs_test.js' ), - 'line_num' : 2, - 'column_num': 15, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'mine_bitcoin', - 'fn(how_much: ?) -> number' ), - CompletionEntryMatcher( 'get_number', 'number' ), - CompletionEntryMatcher( 'get_string', 'string' ), - CompletionEntryMatcher( 'get_thing', - 'fn(a: ?) -> number|string' ), - CompletionEntryMatcher( 'toString', 'fn() -> string' ), - CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), - CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), - CompletionEntryMatcher( 'hasOwnProperty', - 'fn(prop: string) -> bool' ), - CompletionEntryMatcher( 'isPrototypeOf', - 'fn(obj: ?) -> bool' ), - CompletionEntryMatcher( 'propertyIsEnumerable', - 'fn(prop: string) -> bool' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Require_Query_test( app ): - RunTest( app, { - 'description': 'semantic completion works for require object with query', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'requirejs_test.js' ), - 'line_num' : 3, - 'column_num': 17, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'mine_bitcoin', - 'fn(how_much: ?) -> number' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Require_Query_LCS_test( app ): - RunTest( app, { - 'description': ( 'completion works for require object ' - 'with query not prefix' ), - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'requirejs_test.js' ), - 'line_num' : 4, - 'column_num': 17, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'get_number', 'number' ), - CompletionEntryMatcher( 'get_thing', - 'fn(a: ?) -> number|string' ), - CompletionEntryMatcher( 'get_string', 'string' ), - ), - 'errors': empty(), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_DirtyNamedBuffers_test( app ): - # This tests that when we have dirty buffers in our editor, tern actually - # uses them correctly - RunTest( app, { - 'description': ( 'completion works for require object ' - 'with query not prefix' ), - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'requirejs_test.js' ), - 'line_num' : 18, - 'column_num': 11, +class GetCompletionsTest( TestCase ): + @SharedYcmd + def test_GetCompletions_NoQuery( self, app ): + RunTest( app, { + 'description': 'semantic completion works for simple object no query', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'simple_test.js' ), + 'line_num' : 13, + 'column_num': 43, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'a_simple_function', + 'fn(param: ?) -> string' ), + CompletionEntryMatcher( 'basic_type', 'number' ), + CompletionEntryMatcher( 'object', 'object' ), + CompletionEntryMatcher( 'toString', 'fn() -> string' ), + CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), + CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), + CompletionEntryMatcher( 'hasOwnProperty', + 'fn(prop: string) -> bool' ), + CompletionEntryMatcher( 'isPrototypeOf', + 'fn(obj: ?) -> bool' ), + CompletionEntryMatcher( 'propertyIsEnumerable', + 'fn(prop: string) -> bool' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_Query( self, app ): + RunTest( app, { + 'description': 'semantic completion works for simple object with query', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'simple_test.js' ), + 'line_num' : 14, + 'column_num': 45, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'basic_type', 'number' ), + CompletionEntryMatcher( 'isPrototypeOf', + 'fn(obj: ?) -> bool' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_Require_NoQuery( self, app ): + RunTest( app, { + 'description': 'semantic completion works for simple object no query', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'requirejs_test.js' ), + 'line_num' : 2, + 'column_num': 15, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'mine_bitcoin', + 'fn(how_much: ?) -> number' ), + CompletionEntryMatcher( 'get_number', 'number' ), + CompletionEntryMatcher( 'get_string', 'string' ), + CompletionEntryMatcher( 'get_thing', + 'fn(a: ?) -> number|string' ), + CompletionEntryMatcher( 'toString', 'fn() -> string' ), + CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), + CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), + CompletionEntryMatcher( 'hasOwnProperty', + 'fn(prop: string) -> bool' ), + CompletionEntryMatcher( 'isPrototypeOf', + 'fn(obj: ?) -> bool' ), + CompletionEntryMatcher( 'propertyIsEnumerable', + 'fn(prop: string) -> bool' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_Require_Query( self, app ): + RunTest( app, { + 'description': 'semantic completion works for require object with query', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'requirejs_test.js' ), + 'line_num' : 3, + 'column_num': 17, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'mine_bitcoin', + 'fn(how_much: ?) -> number' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_Require_Query_LCS( self, app ): + RunTest( app, { + 'description': ( 'completion works for require object ' + 'with query not prefix' ), + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'requirejs_test.js' ), + 'line_num' : 4, + 'column_num': 17, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'get_number', 'number' ), + CompletionEntryMatcher( 'get_thing', + 'fn(a: ?) -> number|string' ), + CompletionEntryMatcher( 'get_string', 'string' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_DirtyNamedBuffers( self, app ): + # This tests that when we have dirty buffers in our editor, tern actually + # uses them correctly + RunTest( app, { + 'description': ( 'completion works for require object ' + 'with query not prefix' ), + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'requirejs_test.js' ), + 'line_num' : 18, + 'column_num': 11, + 'file_data': { + PathToTestFile( 'no_such_lib', 'no_such_file.js' ): { + 'contents': ( + 'define( [], function() { return { big_endian_node: 1 } } )' ), + 'filetypes': [ 'javascript' ] + } + }, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'big_endian_node', 'number' ), + CompletionEntryMatcher( 'toString', 'fn() -> string' ), + CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), + CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), + CompletionEntryMatcher( 'hasOwnProperty', + 'fn(prop: string) -> bool' ), + CompletionEntryMatcher( 'isPrototypeOf', + 'fn(obj: ?) -> bool' ), + CompletionEntryMatcher( 'propertyIsEnumerable', + 'fn(prop: string) -> bool' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_ReturnsDocsInCompletions( self, app ): + # This tests that we supply docs for completions + RunTest( app, { + 'description': 'completions supply docs', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'requirejs_test.js' ), + 'line_num' : 8, + 'column_num': 15, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( + 'a_function', + 'fn(bar: ?) -> {a_value: string}', { + 'detailed_info': ( 'fn(bar: ?) -> {a_value: string}\n' + 'This is a short documentation string' ), + } ), + CompletionEntryMatcher( 'options', 'options' ), + CompletionEntryMatcher( 'toString', 'fn() -> string' ), + CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), + CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), + CompletionEntryMatcher( 'hasOwnProperty', + 'fn(prop: string) -> bool' ), + CompletionEntryMatcher( 'isPrototypeOf', + 'fn(obj: ?) -> bool' ), + CompletionEntryMatcher( 'propertyIsEnumerable', + 'fn(prop: string) -> bool' ), + ), + 'errors': empty(), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_IgoreNonJSFiles( self, app ): + trivial1 = { + 'filetypes': [ 'python' ], + 'contents': ReadFile( PathToTestFile( 'trivial.js' ) ), + } + trivial2 = { + 'filetypes': [ 'javascript' ], + 'contents': ReadFile( PathToTestFile( 'trivial2.js' ) ), + } + + request = { + 'line_num': 1, + 'column_num': 3, 'file_data': { - PathToTestFile( 'no_such_lib', 'no_such_file.js' ): { - 'contents': ( - 'define( [], function() { return { big_endian_node: 1 } } )' ), - 'filetypes': [ 'javascript' ] - } + PathToTestFile( 'trivial.js' ): trivial1, + PathToTestFile( 'trivial2.js' ): trivial2, }, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'big_endian_node', 'number' ), - CompletionEntryMatcher( 'toString', 'fn() -> string' ), - CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), - CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), - CompletionEntryMatcher( 'hasOwnProperty', - 'fn(prop: string) -> bool' ), - CompletionEntryMatcher( 'isPrototypeOf', - 'fn(obj: ?) -> bool' ), - CompletionEntryMatcher( 'propertyIsEnumerable', - 'fn(prop: string) -> bool' ), - ), + } + + app.post_json( '/event_notification', CombineRequest( request, { + 'filepath': PathToTestFile( 'trivial2.js' ), + 'event_name': 'FileReadyToParse', + } ) ) + + response = app.post_json( '/completions', CombineRequest( request, { + 'filepath': PathToTestFile( 'trivial2.js' ), + } ) ).json + + print( f'completer response: { pformat( response ) }' ) + + assert_that( response, + has_entries( { + 'completion_start_column': 3, + # Note: we do *not* see X.y and X.z because tern is not told about + # the trivial.js file because we pretended it was Python + 'completions': empty(), 'errors': empty(), } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_ReturnsDocsInCompletions_test( app ): - # This tests that we supply docs for completions - RunTest( app, { - 'description': 'completions supply docs', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'requirejs_test.js' ), - 'line_num' : 8, - 'column_num': 15, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { + ) + + + @SharedYcmd + def test_GetCompletions_IncludeMultiFileType( self, app ): + trivial1 = { + 'filetypes': [ 'python', 'javascript' ], + 'contents': ReadFile( PathToTestFile( 'trivial.js' ) ), + } + trivial2 = { + 'filetypes': [ 'javascript' ], + 'contents': ReadFile( PathToTestFile( 'trivial2.js' ) ), + } + + request = { + 'line_num': 1, + 'column_num': 3, + 'file_data': { + PathToTestFile( 'trivial.js' ): trivial1, + PathToTestFile( 'trivial2.js' ): trivial2, + }, + } + + app.post_json( '/event_notification', CombineRequest( request, { + 'filepath': PathToTestFile( 'trivial2.js' ), + 'event_name': 'FileReadyToParse', + } ) ) + + response = app.post_json( '/completions', CombineRequest( request, { + 'filepath': PathToTestFile( 'trivial2.js' ), + # We must force the use of semantic engine because the previous test would + # have entered 'empty' results into the completion cache. + 'force_semantic': True, + } ) ).json + + print( f'completer response: { pformat( response, indent = 2 ) }' ) + + assert_that( response, + has_entries( { + 'completion_start_column': 3, + # Note: This time, we *do* see the completions, because one of the 2 + # filetypes for trivial.js is javascript. 'completions': contains_inanyorder( - CompletionEntryMatcher( - 'a_function', - 'fn(bar: ?) -> {a_value: string}', { - 'detailed_info': ( 'fn(bar: ?) -> {a_value: string}\n' - 'This is a short documentation string' ), - } ), - CompletionEntryMatcher( 'options', 'options' ), - CompletionEntryMatcher( 'toString', 'fn() -> string' ), - CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), - CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), - CompletionEntryMatcher( 'hasOwnProperty', - 'fn(prop: string) -> bool' ), - CompletionEntryMatcher( 'isPrototypeOf', - 'fn(obj: ?) -> bool' ), - CompletionEntryMatcher( 'propertyIsEnumerable', - 'fn(prop: string) -> bool' ), + CompletionEntryMatcher( 'y', 'string' ), + CompletionEntryMatcher( 'z', 'string' ), + CompletionEntryMatcher( 'toString', 'fn() -> string' ), + CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), + CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), + CompletionEntryMatcher( 'hasOwnProperty', + 'fn(prop: string) -> bool' ), + CompletionEntryMatcher( 'isPrototypeOf', + 'fn(obj: ?) -> bool' ), + CompletionEntryMatcher( 'propertyIsEnumerable', + 'fn(prop: string) -> bool' ), ), 'errors': empty(), } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_IgoreNonJSFiles_test( app ): - trivial1 = { - 'filetypes': [ 'python' ], - 'contents': ReadFile( PathToTestFile( 'trivial.js' ) ), - } - trivial2 = { - 'filetypes': [ 'javascript' ], - 'contents': ReadFile( PathToTestFile( 'trivial2.js' ) ), - } - - request = { - 'line_num': 1, - 'column_num': 3, - 'file_data': { - PathToTestFile( 'trivial.js' ): trivial1, - PathToTestFile( 'trivial2.js' ): trivial2, - }, - } - - app.post_json( '/event_notification', CombineRequest( request, { - 'filepath': PathToTestFile( 'trivial2.js' ), - 'event_name': 'FileReadyToParse', - } ) ) - - response = app.post_json( '/completions', CombineRequest( request, { - 'filepath': PathToTestFile( 'trivial2.js' ), - } ) ).json - - print( f'completer response: { pformat( response ) }' ) - - assert_that( response, - has_entries( { - 'completion_start_column': 3, - # Note: we do *not* see X.y and X.z because tern is not told about - # the trivial.js file because we pretended it was Python - 'completions': empty(), - 'errors': empty(), + ) + + + @SharedYcmd + def test_GetCompletions_Unicode_AfterLine( self, app ): + RunTest( app, { + 'description': 'completions work with unicode chars in the file', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'unicode.js' ), + 'line_num' : 1, + 'column_num': 16, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'charAt', 'fn(i: number) -> string' ), + CompletionEntryMatcher( 'charCodeAt', 'fn(i: number) -> number' ), + ), + 'completion_start_column': 13, + 'errors': empty(), + } ) + }, + } ) + + + @SharedYcmd + def test_GetCompletions_Unicode_InLine( self, app ): + RunTest( app, { + 'description': 'completions work with unicode chars in the file', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'unicode.js' ), + 'line_num' : 2, + 'column_num': 18, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'charAt', 'fn(i: number) -> string' ), + CompletionEntryMatcher( 'charCodeAt', 'fn(i: number) -> number' ), + ), + 'completion_start_column': 15, + 'errors': empty(), + } ) + }, } ) - ) - - -@SharedYcmd -def GetCompletions_IncludeMultiFileType_test( app ): - trivial1 = { - 'filetypes': [ 'python', 'javascript' ], - 'contents': ReadFile( PathToTestFile( 'trivial.js' ) ), - } - trivial2 = { - 'filetypes': [ 'javascript' ], - 'contents': ReadFile( PathToTestFile( 'trivial2.js' ) ), - } - - request = { - 'line_num': 1, - 'column_num': 3, - 'file_data': { - PathToTestFile( 'trivial.js' ): trivial1, - PathToTestFile( 'trivial2.js' ): trivial2, - }, - } - - app.post_json( '/event_notification', CombineRequest( request, { - 'filepath': PathToTestFile( 'trivial2.js' ), - 'event_name': 'FileReadyToParse', - } ) ) - - response = app.post_json( '/completions', CombineRequest( request, { - 'filepath': PathToTestFile( 'trivial2.js' ), - # We must force the use of semantic engine because the previous test would - # have entered 'empty' results into the completion cache. - 'force_semantic': True, - } ) ).json - - print( f'completer response: { pformat( response, indent = 2 ) }' ) - - assert_that( response, - has_entries( { - 'completion_start_column': 3, - # Note: This time, we *do* see the completions, because one of the 2 - # filetypes for trivial.js is javascript. - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'y', 'string' ), - CompletionEntryMatcher( 'z', 'string' ), - CompletionEntryMatcher( 'toString', 'fn() -> string' ), - CompletionEntryMatcher( 'toLocaleString', 'fn() -> string' ), - CompletionEntryMatcher( 'valueOf', 'fn() -> number' ), - CompletionEntryMatcher( 'hasOwnProperty', - 'fn(prop: string) -> bool' ), - CompletionEntryMatcher( 'isPrototypeOf', - 'fn(obj: ?) -> bool' ), - CompletionEntryMatcher( 'propertyIsEnumerable', - 'fn(prop: string) -> bool' ), - ), - 'errors': empty(), + + + @SharedYcmd + def test_GetCompletions_Unicode_InFile( self, app ): + RunTest( app, { + 'description': 'completions work with unicode chars in the file', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'unicode.js' ), + 'line_num' : 3, + 'column_num': 16, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( 'charAt', 'fn(i: number) -> string' ), + CompletionEntryMatcher( 'charCodeAt', 'fn(i: number) -> number' ), + ), + 'completion_start_column': 13, + 'errors': empty(), + } ) + }, } ) - ) - - -@SharedYcmd -def GetCompletions_Unicode_AfterLine_test( app ): - RunTest( app, { - 'description': 'completions work with unicode chars in the file', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'unicode.js' ), - 'line_num' : 1, - 'column_num': 16, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'charAt', 'fn(i: number) -> string' ), - CompletionEntryMatcher( 'charCodeAt', 'fn(i: number) -> number' ), - ), - 'completion_start_column': 13, - 'errors': empty(), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Unicode_InLine_test( app ): - RunTest( app, { - 'description': 'completions work with unicode chars in the file', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'unicode.js' ), - 'line_num' : 2, - 'column_num': 18, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'charAt', 'fn(i: number) -> string' ), - CompletionEntryMatcher( 'charCodeAt', 'fn(i: number) -> number' ), - ), - 'completion_start_column': 15, - 'errors': empty(), - } ) - }, - } ) - - -@SharedYcmd -def GetCompletions_Unicode_InFile_test( app ): - RunTest( app, { - 'description': 'completions work with unicode chars in the file', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'unicode.js' ), - 'line_num' : 3, - 'column_num': 16, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( 'charAt', 'fn(i: number) -> string' ), - CompletionEntryMatcher( 'charCodeAt', 'fn(i: number) -> number' ), - ), - 'completion_start_column': 13, - 'errors': empty(), - } ) - }, - } ) - - -@IsolatedYcmd -def GetCompletions_ChangeStartColumn_test( app ): - StartJavaScriptCompleterServerInDirectory( app, PathToTestFile( 'node' ) ) - RunTest( app, { - 'description': 'the completion_start_column is updated by tern', - 'request': { - 'filetype' : 'javascript', - 'filepath' : PathToTestFile( 'node', 'node_test.js' ), - 'line_num' : 1, - 'column_num' : 17, - 'force_semantic': True, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( '"path"', 'path' ) - ), - 'completion_start_column': 14, - 'errors': empty(), - } ) - }, - } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd() + def test_GetCompletions_ChangeStartColumn( self, app ): + StartJavaScriptCompleterServerInDirectory( app, PathToTestFile( 'node' ) ) + RunTest( app, { + 'description': 'the completion_start_column is updated by tern', + 'request': { + 'filetype' : 'javascript', + 'filepath' : PathToTestFile( 'node', 'node_test.js' ), + 'line_num' : 1, + 'column_num' : 17, + 'force_semantic': True, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( '"path"', 'path' ) + ), + 'completion_start_column': 14, + 'errors': empty(), + } ) + }, + } ) diff --git a/ycmd/tests/tern/subcommands_test.py b/ycmd/tests/tern/subcommands_test.py index 410aeaba81..37ee7aa7a6 100644 --- a/ycmd/tests/tern/subcommands_test.py +++ b/ycmd/tests/tern/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -22,11 +22,13 @@ has_entry, has_entries ) from unittest.mock import patch +from unittest import TestCase from pprint import pformat import requests -from ycmd.tests.tern import ( IsolatedYcmd, PathToTestFile, SharedYcmd, - StartJavaScriptCompleterServerInDirectory ) +from ycmd.tests.tern import ( IsolatedYcmd, PathToTestFile, SharedYcmd, # noqa + StartJavaScriptCompleterServerInDirectory, + setUpModule, tearDownModule ) from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, CombineRequest, @@ -36,21 +38,6 @@ from ycmd.utils import ReadFile -@SharedYcmd -def Subcommands_DefinedSubcommands_test( app ): - subcommands_data = BuildRequest( completer_target = 'javascript' ) - - assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_inanyorder( - 'GoToDefinition', - 'GoTo', - 'GetDoc', - 'GetType', - 'GoToReferences', - 'RefactorRename', - 'RestartServer' ) ) - - def RunTest( app, test, contents = None ): if not contents: contents = ReadFile( test[ 'request' ][ 'filepath' ] ) @@ -89,79 +76,71 @@ def RunTest( app, test, contents = None ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def Subcommands_GoToDefinition_test( app ): - RunTest( app, { - 'description': 'GoToDefinition works within file', - 'request': { - 'command': 'GoToDefinition', - 'line_num': 13, - 'column_num': 25, - 'filepath': PathToTestFile( 'simple_test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { +class SubcommandsTest( TestCase ): + @SharedYcmd + def test_Subcommands_DefinedSubcommands( self, app ): + subcommands_data = BuildRequest( completer_target = 'javascript' ) + + assert_that( app.post_json( '/defined_subcommands', subcommands_data ).json, + contains_inanyorder( + 'GoToDefinition', + 'GoTo', + 'GetDoc', + 'GetType', + 'GoToReferences', + 'RefactorRename', + 'RestartServer' ) ) + + + @SharedYcmd + def test_Subcommands_GoToDefinition( self, app ): + RunTest( app, { + 'description': 'GoToDefinition works within file', + 'request': { + 'command': 'GoToDefinition', + 'line_num': 13, + 'column_num': 25, 'filepath': PathToTestFile( 'simple_test.js' ), - 'line_num': 1, - 'column_num': 5, - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GoToDefinition_Unicode_test( app ): - RunTest( app, { - 'description': 'GoToDefinition works within file with unicode', - 'request': { - 'command': 'GoToDefinition', - 'line_num': 11, - 'column_num': 12, - 'filepath': PathToTestFile( 'unicode.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'filepath': PathToTestFile( 'simple_test.js' ), + 'line_num': 1, + 'column_num': 5, + } ) + } + } ) + + + @SharedYcmd + def test_Subcommands_GoToDefinition_Unicode( self, app ): + RunTest( app, { + 'description': 'GoToDefinition works within file with unicode', + 'request': { + 'command': 'GoToDefinition', + 'line_num': 11, + 'column_num': 12, 'filepath': PathToTestFile( 'unicode.js' ), - 'line_num': 6, - 'column_num': 26, - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GoTo_test( app ): - RunTest( app, { - 'description': 'GoTo works the same as GoToDefinition within file', - 'request': { - 'command': 'GoTo', - 'line_num': 13, - 'column_num': 25, - 'filepath': PathToTestFile( 'simple_test.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'filepath': PathToTestFile( 'simple_test.js' ), - 'line_num': 1, - 'column_num': 5, - } ) - } - } ) - - -@IsolatedYcmd -def Subcommands_GoTo_RelativePath_test( app ): - StartJavaScriptCompleterServerInDirectory( app, PathToTestFile() ) - RunTest( - app, - { - 'description': 'GoTo works when the buffer differs from the file on disk', + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'filepath': PathToTestFile( 'unicode.js' ), + 'line_num': 6, + 'column_num': 26, + } ) + } + } ) + + + @SharedYcmd + def test_Subcommands_GoTo( self, app ): + RunTest( app, { + 'description': 'GoTo works the same as GoToDefinition within file', 'request': { 'command': 'GoTo', - 'line_num': 43, + 'line_num': 13, 'column_num': 25, 'filepath': PathToTestFile( 'simple_test.js' ), }, @@ -169,369 +148,390 @@ def Subcommands_GoTo_RelativePath_test( app ): 'response': requests.codes.ok, 'data': has_entries( { 'filepath': PathToTestFile( 'simple_test.js' ), - 'line_num': 31, + 'line_num': 1, 'column_num': 5, } ) } - }, - contents = ReadFile( PathToTestFile( 'simple_test.modified.js' ) ) ) - - -@SharedYcmd -def Subcommands_GetDoc_test( app ): - RunTest( app, { - 'description': 'GetDoc works within file', - 'request': { - 'command': 'GetDoc', - 'line_num': 7, - 'column_num': 16, - 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'detailed_info': ( - 'Name: mine_bitcoin\n' - 'Type: fn(how_much: ?) -> number\n\n' - 'This function takes a number and invests it in bitcoin. It ' - 'returns\nthe expected value (in notional currency) after 1 year.' + } ) + + + @IsolatedYcmd() + def test_Subcommands_GoTo_RelativePath( self, app ): + StartJavaScriptCompleterServerInDirectory( app, PathToTestFile() ) + RunTest( + app, + { + 'description': 'GoTo works when the buffer differs ' + 'from the file on disk', + 'request': { + 'command': 'GoTo', + 'line_num': 43, + 'column_num': 25, + 'filepath': PathToTestFile( 'simple_test.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'filepath': PathToTestFile( 'simple_test.js' ), + 'line_num': 31, + 'column_num': 5, + } ) + } + }, + contents = ReadFile( PathToTestFile( 'simple_test.modified.js' ) ) ) + + + @SharedYcmd + def test_Subcommands_GetDoc( self, app ): + RunTest( app, { + 'description': 'GetDoc works within file', + 'request': { + 'command': 'GetDoc', + 'line_num': 7, + 'column_num': 16, + 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'detailed_info': ( + 'Name: mine_bitcoin\n' + 'Type: fn(how_much: ?) -> number\n\n' + 'This function takes a number and invests it in bitcoin. It ' + 'returns\nthe expected value (in notional currency) after 1 year.' + ) + } ) + } + } ) + + + @SharedYcmd + def test_Subcommands_GetType( self, app ): + RunTest( app, { + 'description': 'GetType works within file', + 'request': { + 'command': 'GetType', + 'line_num': 11, + 'column_num': 14, + 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'message': 'number' + } ) + } + } ) + + + @SharedYcmd + def test_Subcommands_GoToReferences( self, app ): + RunTest( app, { + 'description': 'GoToReferences works within file', + 'request': { + 'command': 'GoToReferences', + 'line_num': 17, + 'column_num': 29, + 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_inanyorder( + has_entries( { + 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), + 'line_num': 17, + 'column_num': 29, + } ), + has_entries( { + 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), + 'line_num': 12, + 'column_num': 9, + } ) + ) + } + } ) + + + @SharedYcmd + def test_Subcommands_GoToReferences_Unicode( self, app ): + RunTest( app, { + 'description': 'GoToReferences works within file with unicode chars', + 'request': { + 'command': 'GoToReferences', + 'line_num': 11, + 'column_num': 5, + 'filepath': PathToTestFile( 'unicode.js' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_inanyorder( + has_entries( { + 'filepath': PathToTestFile( 'unicode.js' ), + 'line_num': 5, + 'column_num': 5, + } ), + has_entries( { + 'filepath': PathToTestFile( 'unicode.js' ), + 'line_num': 9, + 'column_num': 1, + } ), + has_entries( { + 'filepath': PathToTestFile( 'unicode.js' ), + 'line_num': 11, + 'column_num': 1, + } ) ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GetType_test( app ): - RunTest( app, { - 'description': 'GetType works within file', - 'request': { - 'command': 'GetType', - 'line_num': 11, - 'column_num': 14, - 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'message': 'number' - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GoToReferences_test( app ): - RunTest( app, { - 'description': 'GoToReferences works within file', - 'request': { - 'command': 'GoToReferences', - 'line_num': 17, - 'column_num': 29, - 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': contains_inanyorder( - has_entries( { - 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), - 'line_num': 17, - 'column_num': 29, - } ), - has_entries( { - 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), - 'line_num': 12, - 'column_num': 9, + } + } ) + + + @SharedYcmd + def test_Subcommands_GetDocWithNoIdentifier( self, app ): + RunTest( app, { + 'description': 'GetDoc works when no identifier', + 'request': { + 'command': 'GetDoc', + 'filepath': PathToTestFile( 'simple_test.js' ), + 'line_num': 12, + 'column_num': 1, + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, 'TernError: No type found ' + 'at the given position.' ), + } + } ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_Simple( self, app ): + filepath = PathToTestFile( 'simple_test.js' ) + RunTest( app, { + 'description': 'RefactorRename works within a single scope/file', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'test' ], + 'filepath': filepath, + 'line_num': 15, + 'column_num': 32, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( 'test', + LocationMatcher( filepath, 1, 5 ), + LocationMatcher( filepath, 1, 22 ) ), + ChunkMatcher( 'test', + LocationMatcher( filepath, 13, 25 ), + LocationMatcher( filepath, 13, 42 ) ), + ChunkMatcher( 'test', + LocationMatcher( filepath, 14, 24 ), + LocationMatcher( filepath, 14, 41 ) ), + ChunkMatcher( 'test', + LocationMatcher( filepath, 15, 24 ), + LocationMatcher( filepath, 15, 41 ) ), + ChunkMatcher( 'test', + LocationMatcher( filepath, 21, 7 ), + LocationMatcher( filepath, 21, 24 ) ), + # On the same line, ensuring offsets are as expected (as + # unmodified source, similar to clang) + ChunkMatcher( 'test', + LocationMatcher( filepath, 21, 28 ), + LocationMatcher( filepath, 21, 45 ) ), + ), + 'location': LocationMatcher( filepath, 15, 32 ) + } ) ) } ) - ) - } - } ) - - -@SharedYcmd -def Subcommands_GoToReferences_Unicode_test( app ): - RunTest( app, { - 'description': 'GoToReferences works within file with unicode chars', - 'request': { - 'command': 'GoToReferences', - 'line_num': 11, - 'column_num': 5, - 'filepath': PathToTestFile( 'unicode.js' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': contains_inanyorder( - has_entries( { - 'filepath': PathToTestFile( 'unicode.js' ), - 'line_num': 5, - 'column_num': 5, - } ), - has_entries( { - 'filepath': PathToTestFile( 'unicode.js' ), - 'line_num': 9, - 'column_num': 1, - } ), - has_entries( { - 'filepath': PathToTestFile( 'unicode.js' ), - 'line_num': 11, - 'column_num': 1, + } + } ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_MultipleFiles( self, app ): + file1 = PathToTestFile( 'file1.js' ) + file2 = PathToTestFile( 'file2.js' ) + file3 = PathToTestFile( 'file3.js' ) + + RunTest( app, { + 'description': 'RefactorRename works across files', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'a-quite-long-string' ], + 'filepath': file1, + 'line_num': 3, + 'column_num': 14, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file1, 1, 5 ), + LocationMatcher( file1, 1, 11 ) ), + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file1, 3, 14 ), + LocationMatcher( file1, 3, 20 ) ), + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file2, 2, 14 ), + LocationMatcher( file2, 2, 20 ) ), + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file3, 3, 12 ), + LocationMatcher( file3, 3, 18 ) ) + ), + 'location': LocationMatcher( file1, 3, 14 ) + } ) ) } ) - ) - } - } ) - - -@SharedYcmd -def Subcommands_GetDocWithNoIdentifier_test( app ): - RunTest( app, { - 'description': 'GetDoc works when no identifier', - 'request': { - 'command': 'GetDoc', - 'filepath': PathToTestFile( 'simple_test.js' ), - 'line_num': 12, - 'column_num': 1, - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'TernError: No type found ' - 'at the given position.' ), - } - } ) - - -@SharedYcmd -def Subcommands_RefactorRename_Simple_test( app ): - filepath = PathToTestFile( 'simple_test.js' ) - RunTest( app, { - 'description': 'RefactorRename works within a single scope/file', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'test' ], - 'filepath': filepath, - 'line_num': 15, - 'column_num': 32, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( 'test', - LocationMatcher( filepath, 1, 5 ), - LocationMatcher( filepath, 1, 22 ) ), - ChunkMatcher( 'test', - LocationMatcher( filepath, 13, 25 ), - LocationMatcher( filepath, 13, 42 ) ), - ChunkMatcher( 'test', - LocationMatcher( filepath, 14, 24 ), - LocationMatcher( filepath, 14, 41 ) ), - ChunkMatcher( 'test', - LocationMatcher( filepath, 15, 24 ), - LocationMatcher( filepath, 15, 41 ) ), - ChunkMatcher( 'test', - LocationMatcher( filepath, 21, 7 ), - LocationMatcher( filepath, 21, 24 ) ), - # On the same line, ensuring offsets are as expected (as - # unmodified source, similar to clang) - ChunkMatcher( 'test', - LocationMatcher( filepath, 21, 28 ), - LocationMatcher( filepath, 21, 45 ) ), - ), - 'location': LocationMatcher( filepath, 15, 32 ) - } ) ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_RefactorRename_MultipleFiles_test( app ): - file1 = PathToTestFile( 'file1.js' ) - file2 = PathToTestFile( 'file2.js' ) - file3 = PathToTestFile( 'file3.js' ) - - RunTest( app, { - 'description': 'RefactorRename works across files', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'a-quite-long-string' ], - 'filepath': file1, - 'line_num': 3, - 'column_num': 14, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file1, 1, 5 ), - LocationMatcher( file1, 1, 11 ) ), - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file1, 3, 14 ), - LocationMatcher( file1, 3, 20 ) ), - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file2, 2, 14 ), - LocationMatcher( file2, 2, 20 ) ), - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file3, 3, 12 ), - LocationMatcher( file3, 3, 18 ) ) - ), - 'location': LocationMatcher( file1, 3, 14 ) - } ) ) - } ) - } - } ) - - -# Needs to be isolated to prevent interfering with other tests (this test loads -# an extra file into tern's project memory) -@IsolatedYcmd -def Subcommands_RefactorRename_MultipleFiles_OnFileReadyToParse_test( app ): - StartJavaScriptCompleterServerInDirectory( app, PathToTestFile() ) - - file1 = PathToTestFile( 'file1.js' ) - file2 = PathToTestFile( 'file2.js' ) - file3 = PathToTestFile( 'file3.js' ) - - # This test is roughly the same as the previous one, except here file4.js is - # pushed into the Tern engine via 'opening it in the editor' (i.e. - # FileReadyToParse event). The first 3 are loaded into the tern server - # because they are listed in the .tern-project file's loadEagerly option. - file4 = PathToTestFile( 'file4.js' ) + } + } ) + + + # Needs to be isolated to prevent interfering with other tests (this test + # loads an extra file into tern's project memory) + @IsolatedYcmd() + def test_Subcommands_RefactorRename_MultipleFiles_OnFileReadyToParse( + self, app ): + StartJavaScriptCompleterServerInDirectory( app, PathToTestFile() ) + + file1 = PathToTestFile( 'file1.js' ) + file2 = PathToTestFile( 'file2.js' ) + file3 = PathToTestFile( 'file3.js' ) + + # This test is roughly the same as the previous one, except here file4.js is + # pushed into the Tern engine via 'opening it in the editor' (i.e. + # FileReadyToParse event). The first 3 are loaded into the tern server + # because they are listed in the .tern-project file's loadEagerly option. + file4 = PathToTestFile( 'file4.js' ) + + app.post_json( '/event_notification', + BuildRequest( **{ + 'filetype': 'javascript', + 'event_name': 'FileReadyToParse', + 'contents': ReadFile( file4 ), + 'filepath': file4, + } ), + expect_errors = False ) + + RunTest( app, { + 'description': 'FileReadyToParse loads files into tern server', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'a-quite-long-string' ], + 'filepath': file1, + 'line_num': 3, + 'column_num': 14, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file1, 1, 5 ), + LocationMatcher( file1, 1, 11 ) ), + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file1, 3, 14 ), + LocationMatcher( file1, 3, 20 ) ), + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file2, 2, 14 ), + LocationMatcher( file2, 2, 20 ) ), + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file3, 3, 12 ), + LocationMatcher( file3, 3, 18 ) ), + ChunkMatcher( + 'a-quite-long-string', + LocationMatcher( file4, 4, 22 ), + LocationMatcher( file4, 4, 28 ) ) + ), + 'location': LocationMatcher( file1, 3, 14 ) + } ) ) + } ) + } + } ) - app.post_json( '/event_notification', - BuildRequest( **{ - 'filetype': 'javascript', - 'event_name': 'FileReadyToParse', - 'contents': ReadFile( file4 ), - 'filepath': file4, - } ), - expect_errors = False ) - - RunTest( app, { - 'description': 'FileReadyToParse loads files into tern server', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'a-quite-long-string' ], - 'filepath': file1, - 'line_num': 3, - 'column_num': 14, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file1, 1, 5 ), - LocationMatcher( file1, 1, 11 ) ), - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file1, 3, 14 ), - LocationMatcher( file1, 3, 20 ) ), - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file2, 2, 14 ), - LocationMatcher( file2, 2, 20 ) ), - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file3, 3, 12 ), - LocationMatcher( file3, 3, 18 ) ), - ChunkMatcher( - 'a-quite-long-string', - LocationMatcher( file4, 4, 22 ), - LocationMatcher( file4, 4, 28 ) ) - ), - 'location': LocationMatcher( file1, 3, 14 ) - } ) ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_RefactorRename_Missing_New_Name_test( app ): - RunTest( app, { - 'description': 'RefactorRename raises an error without new name', - 'request': { - 'command': 'RefactorRename', - 'line_num': 17, - 'column_num': 29, - 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( ValueError, - 'Please specify a new name to rename it to.\n' - 'Usage: RefactorRename ' ), - } - } ) - - -@SharedYcmd -def Subcommands_RefactorRename_Unicode_test( app ): - filepath = PathToTestFile( 'unicode.js' ) - RunTest( app, { - 'description': 'RefactorRename works with unicode identifiers', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ '†es†' ], - 'filepath': filepath, - 'line_num': 11, - 'column_num': 3, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '†es†', - LocationMatcher( filepath, 5, 5 ), - LocationMatcher( filepath, 5, 13 ) ), - ChunkMatcher( '†es†', - LocationMatcher( filepath, 9, 1 ), - LocationMatcher( filepath, 9, 9 ) ), - ChunkMatcher( '†es†', - LocationMatcher( filepath, 11, 1 ), - LocationMatcher( filepath, 11, 9 ) ) - ), - 'location': LocationMatcher( filepath, 11, 3 ) - } ) ) - } ) - } - } ) - - -@IsolatedYcmd -@patch( 'ycmd.utils.WaitUntilProcessIsTerminated', - MockProcessTerminationTimingOut ) -def Subcommands_StopServer_Timeout_test( app ): - StartJavaScriptCompleterServerInDirectory( app, PathToTestFile() ) - - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'javascript', - command_arguments = [ 'StopServer' ] - ) - ) - request_data = BuildRequest( filetype = 'javascript' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) - ) ) - ) ) + @SharedYcmd + def test_Subcommands_RefactorRename_Missing_New_Name( self, app ): + RunTest( app, { + 'description': 'RefactorRename raises an error without new name', + 'request': { + 'command': 'RefactorRename', + 'line_num': 17, + 'column_num': 29, + 'filepath': PathToTestFile( 'coollib', 'cool_object.js' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( ValueError, + 'Please specify a new name to rename it to.\n' + 'Usage: RefactorRename ' ), + } + } ) + + + @SharedYcmd + def test_Subcommands_RefactorRename_Unicode( self, app ): + filepath = PathToTestFile( 'unicode.js' ) + RunTest( app, { + 'description': 'RefactorRename works with unicode identifiers', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ '†es†' ], + 'filepath': filepath, + 'line_num': 11, + 'column_num': 3, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '†es†', + LocationMatcher( filepath, 5, 5 ), + LocationMatcher( filepath, 5, 13 ) ), + ChunkMatcher( '†es†', + LocationMatcher( filepath, 9, 1 ), + LocationMatcher( filepath, 9, 9 ) ), + ChunkMatcher( '†es†', + LocationMatcher( filepath, 11, 1 ), + LocationMatcher( filepath, 11, 9 ) ) + ), + 'location': LocationMatcher( filepath, 11, 3 ) + } ) ) + } ) + } + } ) + + @IsolatedYcmd() + @patch( 'ycmd.utils.WaitUntilProcessIsTerminated', + MockProcessTerminationTimingOut ) + def test_Subcommands_StopServer_Timeout( self, app ): + StartJavaScriptCompleterServerInDirectory( app, PathToTestFile() ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'javascript', + command_arguments = [ 'StopServer' ] + ) + ) + + request_data = BuildRequest( filetype = 'javascript' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) + ) ) diff --git a/ycmd/tests/test_utils.py b/ycmd/tests/test_utils.py index b099ec20ec..cf518a6bf3 100644 --- a/ycmd/tests/test_utils.py +++ b/ycmd/tests/test_utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2020 ycmd contributors +# Copyright (C) 2013-2021 ycmd contributors # # This file is part of ycmd. # @@ -29,8 +29,8 @@ from webtest import TestApp import bottle import contextlib -import pytest import functools +import logging import os import tempfile import time @@ -43,13 +43,14 @@ from ycmd.responses import BuildCompletionData from ycmd.utils import ( GetCurrentDirectory, ImportCore, + LOGGER, OnMac, OnWindows, ToUnicode, WaitUntilProcessIsTerminated ) ycm_core = ImportCore() -from unittest import skipIf +from unittest import skipIf, skip, expectedFailure TESTS_DIR = os.path.abspath( os.path.dirname( __file__ ) ) TEST_OPTIONS = { @@ -251,6 +252,7 @@ def TemporarySymlink( source, link ): def SetUpApp( custom_options = {} ): bottle.debug( True ) + LOGGER.setLevel( logging.DEBUG ) options = user_options_store.DefaultOptions() options.update( TEST_OPTIONS ) options.update( custom_options ) @@ -382,7 +384,7 @@ def Wrapper( *args, **kwargs ): raise test_exception # Failed for the right reason - pytest.skip( reason ) + skip( reason ) else: raise AssertionError( f'Test was expected to fail: { reason }' ) return Wrapper @@ -404,28 +406,33 @@ def TemporaryTestDir(): def WithRetry( *args, **kwargs ): - """Decorator to be applied to tests that retries the test over and over""" + opts = { 'reruns': 20, 'reruns_delay': 0.5 } + opts.update( kwargs ) + if os.environ.get( 'YCM_TEST_NO_RETRY' ) == 'XFAIL': + return expectedFailure - if len( args ) == 1 and callable( args[ 0 ] ): - # We are the decorator - f = args[ 0 ] + def Decorator( test ): + """Decorator to be applied to tests that retries the test over and over + until it passes or |timeout| seconds have passed.""" - def ReturnDecorator( wrapper ): - return wrapper( f ) - else: - # We need to return the decorator - def ReturnDecorator( wrapper ): - return wrapper + if 'YCM_TEST_NO_RETRY' in os.environ: + return test - if os.environ.get( 'YCM_TEST_NO_RETRY' ) == 'XFAIL': - return ReturnDecorator( pytest.mark.xfail( strict = False ) ) - elif os.environ.get( 'YCM_TEST_NO_RETRY' ): - # This is a "null" decorator - return ReturnDecorator( lambda f: f ) - else: - opts = { 'reruns': 20, 'reruns_delay': 0.5 } - opts.update( kwargs ) - return ReturnDecorator( pytest.mark.flaky( **opts ) ) + @functools.wraps( test ) + def wrapper( *args, **kwargs ): + run = 0 + while run < opts[ 'reruns' ]: + try: + test( *args, **kwargs ) + return + except Exception as test_exception: + run += 1 + if run == opts[ 'reruns' ]: + raise + print( f'Test failed, retrying: { test_exception }' ) + time.sleep( opts[ 'reruns_delay' ] ) + return wrapper + return Decorator @contextlib.contextmanager diff --git a/ycmd/tests/typescript/__init__.py b/ycmd/tests/typescript/__init__.py index 7f37e5c579..2a72d4e8e3 100644 --- a/ycmd/tests/typescript/__init__.py +++ b/ycmd/tests/typescript/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -15,10 +15,54 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . +import functools import os -from ycmd.tests.typescript.conftest import * # noqa + +from ycmd.tests.test_utils import ( ClearCompletionsCache, + IgnoreExtraConfOutsideTestsFolder, + IsolatedApp, + SetUpApp, + StopCompleterServer, + WaitUntilCompleterServerReady ) + +shared_app = None def PathToTestFile( *args ): dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) return os.path.join( dir_of_current_script, 'testdata', *args ) + + +def setUpModule(): + global shared_app + shared_app = SetUpApp() + WaitUntilCompleterServerReady( shared_app, 'typescript' ) + + +def tearDownModule(): + global shared_app + StopCompleterServer( shared_app, 'typescript' ) + + +def SharedYcmd( test ): + global shared_app + + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + ClearCompletionsCache() + with IgnoreExtraConfOutsideTestsFolder(): + return test( args[ 0 ], shared_app, *args[ 1: ], **kwargs ) + return Wrapper + + +def IsolatedYcmd( custom_options = {} ): + def Decorator( test ): + @functools.wraps( test ) + def Wrapper( *args, **kwargs ): + with IsolatedApp( custom_options ) as app: + try: + test( args[ 0 ], app, *args[ 1: ], **kwargs ) + finally: + StopCompleterServer( app, 'typescript' ) + return Wrapper + return Decorator diff --git a/ycmd/tests/typescript/conftest.py b/ycmd/tests/typescript/conftest.py deleted file mode 100644 index ba1f61beef..0000000000 --- a/ycmd/tests/typescript/conftest.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (C) 2020 ycmd contributors -# -# This file is part of ycmd. -# -# ycmd is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# ycmd is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with ycmd. If not, see . - -import pytest - -from ycmd.tests.test_utils import ( ClearCompletionsCache, - IgnoreExtraConfOutsideTestsFolder, - IsolatedApp, - SetUpApp, - StopCompleterServer, - WaitUntilCompleterServerReady ) -shared_app = None - - -@pytest.fixture( scope='module', autouse=True ) -def set_up_shared_app(): - global shared_app - shared_app = SetUpApp() - WaitUntilCompleterServerReady( shared_app, 'typescript' ) - yield - StopCompleterServer( shared_app, 'typescript' ) - - -@pytest.fixture -def app( request ): - which = request.param[ 0 ] - assert which == 'isolated' or which == 'shared' - if which == 'isolated': - with IsolatedApp( request.param[ 1 ] ) as app: - yield app - StopCompleterServer( app, 'typescript' ) - else: - global shared_app - ClearCompletionsCache() - with IgnoreExtraConfOutsideTestsFolder(): - yield shared_app - - -"""Defines a decorator to be attached to tests of this package. This decorator -passes the shared ycmd application as a parameter.""" -SharedYcmd = pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'shared', ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) - - -def IsolatedYcmd( custom_options = {} ): - """Defines a decorator to be attached to tests of this package. This decorator - passes a unique ycmd application as a parameter. It should be used on tests - that change the server state in a irreversible way (ex: a semantic subserver - is stopped or restarted) or expect a clean state (ex: no semantic subserver - started, no .ycm_extra_conf.py loaded, etc). Use the optional parameter - |custom_options| to give additional options and/or override the default ones. - - Example usage: - - from ycmd.tests.python import IsolatedYcmd - - @IsolatedYcmd( { 'python_binary_path': '/some/path' } ) - def CustomPythonBinaryPath_test( app ): - ... - """ - return pytest.mark.parametrize( - # Name of the fixture/function argument - 'app', - # Fixture parameters, passed to app() as request.param - [ ( 'isolated', custom_options ) ], - # Non-empty ids makes fixture parameters visible in pytest verbose output - ids = [ '' ], - # Execute the fixture, instead of passing parameters directly to the - # function argument - indirect = True ) diff --git a/ycmd/tests/typescript/debug_info_test.py b/ycmd/tests/typescript/debug_info_test.py index 2109f6a948..91f2c2e82f 100644 --- a/ycmd/tests/typescript/debug_info_test.py +++ b/ycmd/tests/typescript/debug_info_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -21,35 +21,32 @@ has_entries, has_entry, instance_of ) +from unittest import TestCase -from ycmd.tests.typescript import SharedYcmd +from ycmd.tests.typescript import SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import BuildRequest -@SharedYcmd -def DebugInfo_test( app ): - request_data = BuildRequest( filetype = 'typescript' ) - assert_that( - app.post_json( '/debug_info', request_data ).json, - has_entry( 'completer', has_entries( { - 'name': 'TypeScript', - 'servers': contains_exactly( has_entries( { - 'name': 'TSServer', - 'is_running': True, - 'executable': instance_of( str ), - 'pid': instance_of( int ), - 'address': None, - 'port': None, - 'logfiles': contains_exactly( instance_of( str ) ), - 'extras': contains_exactly( has_entries( { - 'key': 'version', - 'value': any_of( None, instance_of( str ) ) +class DebugInfoTest( TestCase ): + @SharedYcmd + def test_DebugInfo( self, app ): + request_data = BuildRequest( filetype = 'typescript' ) + assert_that( + app.post_json( '/debug_info', request_data ).json, + has_entry( 'completer', has_entries( { + 'name': 'TypeScript', + 'servers': contains_exactly( has_entries( { + 'name': 'TSServer', + 'is_running': True, + 'executable': instance_of( str ), + 'pid': instance_of( int ), + 'address': None, + 'port': None, + 'logfiles': contains_exactly( instance_of( str ) ), + 'extras': contains_exactly( has_entries( { + 'key': 'version', + 'value': any_of( None, instance_of( str ) ) + } ) ) } ) ) } ) ) - } ) ) - ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + ) diff --git a/ycmd/tests/typescript/diagnostics_test.py b/ycmd/tests/typescript/diagnostics_test.py index 9f124e1198..1eada94fec 100644 --- a/ycmd/tests/typescript/diagnostics_test.py +++ b/ycmd/tests/typescript/diagnostics_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2020 ycmd contributors +# Copyright (C) 2017-2021 ycmd contributors # # This file is part of ycmd. # @@ -15,148 +15,139 @@ # You should have received a copy of the GNU General Public License # along with ycmd. If not, see . -from __future__ import absolute_import -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from builtins import * # noqa - from hamcrest import ( assert_that, contains_exactly, contains_inanyorder, has_entries, has_entry ) +from unittest import TestCase -from ycmd.tests.typescript import IsolatedYcmd, PathToTestFile, SharedYcmd +from ycmd.tests.typescript import IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import BuildRequest, LocationMatcher, RangeMatcher from ycmd.utils import ReadFile -@SharedYcmd -def Diagnostics_FileReadyToParse_test( app ): - filepath = PathToTestFile( 'test.ts' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = contents, - event_name = 'BufferVisit' ) - app.post_json( '/event_notification', event_data ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = contents, - event_name = 'FileReadyToParse' ) - - assert_that( - app.post_json( '/event_notification', event_data ).json, - contains_inanyorder( - has_entries( { - 'kind': 'ERROR', - 'text': "Property 'mA' does not exist on type 'Foo'.", - 'location': LocationMatcher( filepath, 17, 5 ), - 'location_extent': RangeMatcher( filepath, ( 17, 5 ), ( 17, 7 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 17, 5 ), ( 17, 7 ) ) ), - 'fixit_available': True - } ), - has_entries( { - 'kind': 'ERROR', - 'text': "Property 'nonExistingMethod' does not exist on type 'Bar'.", - 'location': LocationMatcher( filepath, 35, 5 ), - 'location_extent': RangeMatcher( filepath, ( 35, 5 ), ( 35, 22 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 35, 5 ), ( 35, 22 ) ) ), - 'fixit_available': True - } ), - has_entries( { - 'kind': 'ERROR', - 'text': 'Expected 1-2 arguments, but got 0.', - 'location': LocationMatcher( filepath, 37, 5 ), - 'location_extent': RangeMatcher( filepath, ( 37, 5 ), ( 37, 12 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 37, 5 ), ( 37, 12 ) ) ), - 'fixit_available': False - } ), - has_entries( { - 'kind': 'ERROR', - 'text': "Cannot find name 'Bår'.", - 'location': LocationMatcher( filepath, 39, 1 ), - 'location_extent': RangeMatcher( filepath, ( 39, 1 ), ( 39, 5 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 39, 1 ), ( 39, 5 ) ) ), - 'fixit_available': True - } ), - ) - ) - - -@SharedYcmd -def Diagnostics_DetailedDiagnostics_test( app ): - filepath = PathToTestFile( 'test.ts' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = contents, - event_name = 'BufferVisit' ) - app.post_json( '/event_notification', event_data ) - - diagnostic_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = contents, - line_num = 35, - column_num = 6 ) - - assert_that( - app.post_json( '/detailed_diagnostic', diagnostic_data ).json, - has_entry( - 'message', "Property 'nonExistingMethod' does not exist on type 'Bar'." +class DiagnosticsTest( TestCase ): + @SharedYcmd + def test_Diagnostics_FileReadyToParse( self, app ): + filepath = PathToTestFile( 'test.ts' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = contents, + event_name = 'BufferVisit' ) + app.post_json( '/event_notification', event_data ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = contents, + event_name = 'FileReadyToParse' ) + + assert_that( + app.post_json( '/event_notification', event_data ).json, + contains_inanyorder( + has_entries( { + 'kind': 'ERROR', + 'text': "Property 'mA' does not exist on type 'Foo'.", + 'location': LocationMatcher( filepath, 17, 5 ), + 'location_extent': RangeMatcher( filepath, ( 17, 5 ), ( 17, 7 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 17, 5 ), ( 17, 7 ) ) ), + 'fixit_available': True + } ), + has_entries( { + 'kind': 'ERROR', + 'text': "Property 'nonExistingMethod' does not exist on type 'Bar'.", + 'location': LocationMatcher( filepath, 35, 5 ), + 'location_extent': RangeMatcher( filepath, ( 35, 5 ), ( 35, 22 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 35, 5 ), ( 35, 22 ) ) ), + 'fixit_available': True + } ), + has_entries( { + 'kind': 'ERROR', + 'text': 'Expected 1-2 arguments, but got 0.', + 'location': LocationMatcher( filepath, 37, 5 ), + 'location_extent': RangeMatcher( filepath, ( 37, 5 ), ( 37, 12 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 37, 5 ), ( 37, 12 ) ) ), + 'fixit_available': False + } ), + has_entries( { + 'kind': 'ERROR', + 'text': "Cannot find name 'Bår'.", + 'location': LocationMatcher( filepath, 39, 1 ), + 'location_extent': RangeMatcher( filepath, ( 39, 1 ), ( 39, 5 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 39, 1 ), ( 39, 5 ) ) ), + 'fixit_available': True + } ), + ) ) - ) - - -@IsolatedYcmd( { 'max_diagnostics_to_display': 1 } ) -def Diagnostics_MaximumDiagnosticsNumberExceeded_test( app ): - filepath = PathToTestFile( 'test.ts' ) - contents = ReadFile( filepath ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = contents, - event_name = 'BufferVisit' ) - app.post_json( '/event_notification', event_data ) - - event_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = contents, - event_name = 'FileReadyToParse' ) - - assert_that( - app.post_json( '/event_notification', event_data ).json, - contains_inanyorder( - has_entries( { - 'kind': 'ERROR', - 'text': "Property 'mA' does not exist on type 'Foo'.", - 'location': LocationMatcher( filepath, 17, 5 ), - 'location_extent': RangeMatcher( filepath, ( 17, 5 ), ( 17, 7 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 17, 5 ), ( 17, 7 ) ) ), - 'fixit_available': True - } ), - has_entries( { - 'kind': 'ERROR', - 'text': 'Maximum number of diagnostics exceeded.', - 'location': LocationMatcher( filepath, 1, 1 ), - 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), - 'ranges': contains_exactly( - RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) ), - 'fixit_available': False - } ), + + + @SharedYcmd + def test_Diagnostics_DetailedDiagnostics( self, app ): + filepath = PathToTestFile( 'test.ts' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = contents, + event_name = 'BufferVisit' ) + app.post_json( '/event_notification', event_data ) + + diagnostic_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = contents, + line_num = 35, + column_num = 6 ) + + assert_that( + app.post_json( '/detailed_diagnostic', diagnostic_data ).json, + has_entry( + 'message', "Property 'nonExistingMethod' does not exist on type 'Bar'." + ) ) - ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @IsolatedYcmd( { 'max_diagnostics_to_display': 1 } ) + def test_Diagnostics_MaximumDiagnosticsNumberExceeded( self, app ): + filepath = PathToTestFile( 'test.ts' ) + contents = ReadFile( filepath ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = contents, + event_name = 'BufferVisit' ) + app.post_json( '/event_notification', event_data ) + + event_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = contents, + event_name = 'FileReadyToParse' ) + + assert_that( + app.post_json( '/event_notification', event_data ).json, + contains_inanyorder( + has_entries( { + 'kind': 'ERROR', + 'text': "Property 'mA' does not exist on type 'Foo'.", + 'location': LocationMatcher( filepath, 17, 5 ), + 'location_extent': RangeMatcher( filepath, ( 17, 5 ), ( 17, 7 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 17, 5 ), ( 17, 7 ) ) ), + 'fixit_available': True + } ), + has_entries( { + 'kind': 'ERROR', + 'text': 'Maximum number of diagnostics exceeded.', + 'location': LocationMatcher( filepath, 1, 1 ), + 'location_extent': RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ), + 'ranges': contains_exactly( + RangeMatcher( filepath, ( 1, 1 ), ( 1, 1 ) ) ), + 'fixit_available': False + } ), + ) + ) diff --git a/ycmd/tests/typescript/event_notification_test.py b/ycmd/tests/typescript/event_notification_test.py index 835b76cd9b..e292e68a5c 100644 --- a/ycmd/tests/typescript/event_notification_test.py +++ b/ycmd/tests/typescript/event_notification_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors +# Copyright (C) 2016-2021 ycmd contributors # # This file is part of ycmd. # @@ -16,93 +16,92 @@ # along with ycmd. If not, see . from hamcrest import assert_that, contains_exactly, has_entries +from unittest import TestCase from ycmd.tests.typescript import IsolatedYcmd, PathToTestFile from ycmd.tests.test_utils import BuildRequest, CompletionEntryMatcher from ycmd.utils import ReadFile -@IsolatedYcmd() -def EventNotification_OnBufferUnload_CloseFile_test( app ): - # Open main.ts file in a buffer. - main_filepath = PathToTestFile( 'buffer_unload', 'main.ts' ) - main_contents = ReadFile( main_filepath ) +class EventNotificationTest( TestCase ): + @IsolatedYcmd() + def test_EventNotification_OnBufferUnload_CloseFile( self, app ): + # Open main.ts file in a buffer. + main_filepath = PathToTestFile( 'buffer_unload', 'main.ts' ) + main_contents = ReadFile( main_filepath ) - event_data = BuildRequest( filepath = main_filepath, - filetype = 'typescript', - contents = main_contents, - event_name = 'BufferVisit' ) - app.post_json( '/event_notification', event_data ) + event_data = BuildRequest( filepath = main_filepath, + filetype = 'typescript', + contents = main_contents, + event_name = 'BufferVisit' ) + app.post_json( '/event_notification', event_data ) - # Complete in main.ts buffer an object defined in imported.ts. - completion_data = BuildRequest( filepath = main_filepath, - filetype = 'typescript', - contents = main_contents, - line_num = 3, - column_num = 10 ) - response = app.post_json( '/completions', completion_data ) - assert_that( response.json, has_entries( { - 'completions': contains_exactly( CompletionEntryMatcher( 'method' ) ) } ) ) + # Complete in main.ts buffer an object defined in imported.ts. + completion_data = BuildRequest( filepath = main_filepath, + filetype = 'typescript', + contents = main_contents, + line_num = 3, + column_num = 10 ) + response = app.post_json( '/completions', completion_data ) + assert_that( response.json, has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'method' ) ) } ) ) - # Open imported.ts file in another buffer. - imported_filepath = PathToTestFile( 'buffer_unload', 'imported.ts' ) - imported_contents = ReadFile( imported_filepath ) + # Open imported.ts file in another buffer. + imported_filepath = PathToTestFile( 'buffer_unload', 'imported.ts' ) + imported_contents = ReadFile( imported_filepath ) - event_data = BuildRequest( filepath = imported_filepath, - filetype = 'typescript', - contents = imported_contents, - event_name = 'BufferVisit' ) - app.post_json( '/event_notification', event_data ) + event_data = BuildRequest( filepath = imported_filepath, + filetype = 'typescript', + contents = imported_contents, + event_name = 'BufferVisit' ) + app.post_json( '/event_notification', event_data ) - # Modify imported.ts buffer without writing the changes to disk. - modified_imported_contents = imported_contents.replace( 'method', - 'modified_method' ) + # Modify imported.ts buffer without writing the changes to disk. + modified_imported_contents = imported_contents.replace( 'method', + 'modified_method' ) - # FIXME: TypeScript completer should not rely on the FileReadyToParse events - # to synchronize the contents of dirty buffers but use instead the file_data - # field of the request. - event_data = BuildRequest( filepath = imported_filepath, - filetype = 'typescript', - contents = modified_imported_contents, - event_name = 'FileReadyToParse' ) - app.post_json( '/event_notification', event_data ) + # FIXME: TypeScript completer should not rely on the FileReadyToParse events + # to synchronize the contents of dirty buffers but use instead the file_data + # field of the request. + event_data = BuildRequest( filepath = imported_filepath, + filetype = 'typescript', + contents = modified_imported_contents, + event_name = 'FileReadyToParse' ) + app.post_json( '/event_notification', event_data ) - # Complete at same location in main.ts buffer. - imported_data = { - imported_filepath: { - 'filetypes': [ 'typescript' ], - 'contents': modified_imported_contents + # Complete at same location in main.ts buffer. + imported_data = { + imported_filepath: { + 'filetypes': [ 'typescript' ], + 'contents': modified_imported_contents + } } - } - completion_data = BuildRequest( filepath = main_filepath, - filetype = 'typescript', - contents = main_contents, - line_num = 3, - column_num = 10, - file_data = imported_data ) - response = app.post_json( '/completions', completion_data ) - assert_that( response.json, has_entries( { - 'completions': contains_exactly( - CompletionEntryMatcher( 'modified_method' ) ) } ) ) + completion_data = BuildRequest( filepath = main_filepath, + filetype = 'typescript', + contents = main_contents, + line_num = 3, + column_num = 10, + file_data = imported_data ) + response = app.post_json( '/completions', completion_data ) + assert_that( response.json, has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'modified_method' ) ) } ) ) - # Unload imported.ts buffer. - event_data = BuildRequest( filepath = imported_filepath, - filetype = 'typescript', - contents = imported_contents, - event_name = 'BufferUnload' ) - app.post_json( '/event_notification', event_data ) + # Unload imported.ts buffer. + event_data = BuildRequest( filepath = imported_filepath, + filetype = 'typescript', + contents = imported_contents, + event_name = 'BufferUnload' ) + app.post_json( '/event_notification', event_data ) - # Complete at same location in main.ts buffer. - completion_data = BuildRequest( filepath = main_filepath, - filetype = 'typescript', - contents = main_contents, - line_num = 3, - column_num = 10 ) - response = app.post_json( '/completions', completion_data ) - assert_that( response.json, has_entries( { - 'completions': contains_exactly( CompletionEntryMatcher( 'method' ) ) } ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + # Complete at same location in main.ts buffer. + completion_data = BuildRequest( filepath = main_filepath, + filetype = 'typescript', + contents = main_contents, + line_num = 3, + column_num = 10 ) + response = app.post_json( '/completions', completion_data ) + assert_that( response.json, has_entries( { + 'completions': contains_exactly( + CompletionEntryMatcher( 'method' ) ) } ) ) diff --git a/ycmd/tests/typescript/get_completions_test.py b/ycmd/tests/typescript/get_completions_test.py index 5f63a6879a..003c2db7b5 100644 --- a/ycmd/tests/typescript/get_completions_test.py +++ b/ycmd/tests/typescript/get_completions_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -25,10 +25,11 @@ matches_regexp, raises ) from webtest import AppError -import pprint +from unittest import TestCase +import json import requests -from ycmd.tests.typescript import IsolatedYcmd, PathToTestFile, SharedYcmd +from ycmd.tests.typescript import IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, CombineRequest, @@ -59,7 +60,7 @@ def RunTest( app, test ): } ) ) - print( f'completer response: { pprint.pformat( response.json ) }' ) + print( 'completer response: ', json.dumps( response.json, indent = 2 ) ) assert_that( response.status_code, equal_to( test[ 'expect' ][ 'response' ] ) ) @@ -67,99 +68,173 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def GetCompletions_Basic_test( app ): - RunTest( app, { - 'description': 'Extra and detailed info when completions are methods', - 'request': { - 'line_num': 17, - 'column_num': 6, - 'filepath': PathToTestFile( 'test.ts' ) - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( - 'methodA', - '(method) Foo.methodA(): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodA(): void\n\n' - 'Unicode string: 说话' - } - ), - CompletionEntryMatcher( - 'methodB', - '(method) Foo.methodB(): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodB(): void' - } - ), - CompletionEntryMatcher( - 'methodC', - '(method) Foo.methodC(a: { foo: string; bar: number; }): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodC(a: {\n' - ' foo: string;\n' - ' bar: number;\n' - '}): void' - } +class GetCompletionsTest( TestCase ): + @SharedYcmd + def test_GetCompletions_Basic( self, app ): + RunTest( app, { + 'description': 'Extra and detailed info when completions are methods', + 'request': { + 'line_num': 17, + 'column_num': 6, + 'filepath': PathToTestFile( 'test.ts' ) + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( + 'methodA', + '(method) Foo.methodA(): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodA(): void\n\n' + 'Unicode string: 说话' + } + ), + CompletionEntryMatcher( + 'methodB', + '(method) Foo.methodB(): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodB(): void' + } + ), + CompletionEntryMatcher( + 'methodC', + '(method) Foo.methodC(a: { foo: string; bar: number; }): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodC(a: {\n' + ' foo: string;\n' + ' bar: number;\n' + '}): void' + } + ) ) - ) - } ) - } - } ) + } ) + } + } ) - RunTest( app, { - 'description': 'Filtering works', - 'request': { - 'line_num': 17, - 'column_num': 7, - 'filepath': PathToTestFile( 'test.ts' ) - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( - 'methodA', - '(method) Foo.methodA(): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodA(): void\n\n' - 'Unicode string: 说话' - } + RunTest( app, { + 'description': 'Filtering works', + 'request': { + 'line_num': 17, + 'column_num': 7, + 'filepath': PathToTestFile( 'test.ts' ) + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( + 'methodA', + '(method) Foo.methodA(): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodA(): void\n\n' + 'Unicode string: 说话' + } + ) ) - ) - } ) - } - } ) + } ) + } + } ) -@IsolatedYcmd( { 'disable_signature_help': True } ) -def GetCompletions_Basic_NoSigHelp_test( app ): - RunTest( app, { - 'description': 'Extra and detailed info when completions are methods', - 'request': { - 'line_num': 17, - 'column_num': 6, - 'filepath': PathToTestFile( 'test.ts' ) - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { + @IsolatedYcmd( { 'disable_signature_help': True } ) + def test_GetCompletions_Basic_NoSigHelp( self, app ): + RunTest( app, { + 'description': 'Extra and detailed info when completions are methods', + 'request': { + 'line_num': 17, + 'column_num': 6, + 'filepath': PathToTestFile( 'test.ts' ) + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': contains_inanyorder( + CompletionEntryMatcher( + 'methodA', + '(method) Foo.methodA(): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodA(): void\n\n' + 'Unicode string: 说话' + } + ), + CompletionEntryMatcher( + 'methodB', + '(method) Foo.methodB(): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodB(): void' + } + ), + CompletionEntryMatcher( + 'methodC', + '(method) Foo.methodC(a: { foo: string; bar: number; }): void', + extra_params = { + 'kind': 'method', + 'detailed_info': '(method) Foo.methodC(a: {\n' + ' foo: string;\n' + ' bar: number;\n' + '}): void' + } + ) + ) + } ) + } + } ) + + + @SharedYcmd + def test_GetCompletions_Keyword( self, app ): + RunTest( app, { + 'description': 'No extra and detailed info when completion is a keyword', + 'request': { + 'line_num': 2, + 'column_num': 5, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( { + 'insertion_text': 'class', + 'kind': 'keyword', + 'extra_data': {} + } ) + } ) + } + } ) + + + @SharedYcmd + def test_GetCompletions_AfterRestart( self, app ): + filepath = PathToTestFile( 'test.ts' ) + + app.post_json( '/run_completer_command', + BuildRequest( completer_target = 'filetype_default', + command_arguments = [ 'RestartServer' ], + filetype = 'typescript', + filepath = filepath ) ) + + completion_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = ReadFile( filepath ), + force_semantic = True, + line_num = 17, + column_num = 6 ) + + assert_that( + app.post_json( '/completions', completion_data ).json, + has_entries( { 'completions': contains_inanyorder( CompletionEntryMatcher( 'methodA', '(method) Foo.methodA(): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodA(): void\n\n' - 'Unicode string: 说话' - } + extra_params = { 'kind': 'method' } ), CompletionEntryMatcher( 'methodB', @@ -182,179 +257,102 @@ def GetCompletions_Basic_NoSigHelp_test( app ): ) ) } ) - } - } ) - - -@SharedYcmd -def GetCompletions_Keyword_test( app ): - RunTest( app, { - 'description': 'No extra and detailed info when completion is a keyword', - 'request': { - 'line_num': 2, - 'column_num': 5, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( { - 'insertion_text': 'class', - 'kind': 'keyword', - 'extra_data': {} + ) + + + @IsolatedYcmd() + def test_GetCompletions_ServerIsNotRunning( self, app ): + StopCompleterServer( app, filetype = 'typescript' ) + + filepath = PathToTestFile( 'test.ts' ) + contents = ReadFile( filepath ) + + # Check that sending a request to TSServer (the response is ignored) raises + # the proper exception. + event_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = contents, + event_name = 'BufferVisit' ) + + assert_that( + calling( app.post_json ).with_args( '/event_notification', event_data ), + raises( AppError, 'TSServer is not running.' ) ) + + # Check that sending a command to TSServer (the response is processed) + # raises the proper exception. + completion_data = BuildRequest( filepath = filepath, + filetype = 'typescript', + contents = contents, + force_semantic = True, + line_num = 17, + column_num = 6 ) + + assert_that( + calling( app.post_json ).with_args( '/completions', completion_data ), + raises( AppError, 'TSServer is not running.' ) ) + + + @SharedYcmd + def test_GetCompletions_AutoImport( self, app ): + filepath = PathToTestFile( 'test.ts' ) + RunTest( app, { + 'description': 'Symbol from external module can be completed and its ' + 'completion contains fixits to automatically import it', + 'request': { + 'line_num': 39, + 'column_num': 5, + 'filepath': filepath, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( has_entries( { + 'insertion_text': 'Bår', + 'extra_menu_info': 'class Bår', + 'detailed_info': 'class Bår', + 'kind': 'class', + 'extra_data': has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': 'Import \'Bår\' from module "./unicode"', + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( '^import { Bår } from ' + '"./unicode";\r?\n' ), + LocationMatcher( filepath, 1, 1 ), + LocationMatcher( filepath, 1, 1 ) + ) + ), + 'location': LocationMatcher( filepath, 39, 5 ) + } ) + ) + } ) + } ) ) } ) - } ) - } - } ) - - -@SharedYcmd -def GetCompletions_AfterRestart_test( app ): - filepath = PathToTestFile( 'test.ts' ) - - app.post_json( '/run_completer_command', - BuildRequest( completer_target = 'filetype_default', - command_arguments = [ 'RestartServer' ], - filetype = 'typescript', - filepath = filepath ) ) - - completion_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = ReadFile( filepath ), - force_semantic = True, - line_num = 17, - column_num = 6 ) - - assert_that( - app.post_json( '/completions', completion_data ).json, - has_entries( { - 'completions': contains_inanyorder( - CompletionEntryMatcher( - 'methodA', - '(method) Foo.methodA(): void', - extra_params = { 'kind': 'method' } - ), - CompletionEntryMatcher( - 'methodB', - '(method) Foo.methodB(): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodB(): void' - } - ), - CompletionEntryMatcher( - 'methodC', - '(method) Foo.methodC(a: { foo: string; bar: number; }): void', - extra_params = { - 'kind': 'method', - 'detailed_info': '(method) Foo.methodC(a: {\n' - ' foo: string;\n' - ' bar: number;\n' - '}): void' - } - ) - ) + } } ) - ) - - -@IsolatedYcmd() -def GetCompletions_ServerIsNotRunning_test( app ): - StopCompleterServer( app, filetype = 'typescript' ) - - filepath = PathToTestFile( 'test.ts' ) - contents = ReadFile( filepath ) - - # Check that sending a request to TSServer (the response is ignored) raises - # the proper exception. - event_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = contents, - event_name = 'BufferVisit' ) - - assert_that( - calling( app.post_json ).with_args( '/event_notification', event_data ), - raises( AppError, 'TSServer is not running.' ) ) - - # Check that sending a command to TSServer (the response is processed) raises - # the proper exception. - completion_data = BuildRequest( filepath = filepath, - filetype = 'typescript', - contents = contents, - force_semantic = True, - line_num = 17, - column_num = 6 ) - - assert_that( - calling( app.post_json ).with_args( '/completions', completion_data ), - raises( AppError, 'TSServer is not running.' ) ) -@SharedYcmd -def GetCompletions_AutoImport_test( app ): - filepath = PathToTestFile( 'test.ts' ) - RunTest( app, { - 'description': 'Symbol from external module can be completed and ' - 'its completion contains fixits to automatically import it', - 'request': { - 'line_num': 39, - 'column_num': 5, - 'filepath': filepath, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( has_entries( { - 'insertion_text': 'Bår', - 'extra_menu_info': 'class Bår', - 'detailed_info': 'class Bår', - 'kind': 'class', - 'extra_data': has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': 'Import \'Bår\' from module "./unicode"', - 'chunks': contains_exactly( - ChunkMatcher( - matches_regexp( '^import { Bår } from "./unicode";\r?\n' ), - LocationMatcher( filepath, 1, 1 ), - LocationMatcher( filepath, 1, 1 ) - ) - ), - 'location': LocationMatcher( filepath, 39, 5 ) - } ) - ) - } ) - } ) ) - } ) - } - } ) - - -@SharedYcmd -def GetCompletions_TypeScriptReact_DefaultTriggers_test( app ): - filepath = PathToTestFile( 'test.tsx' ) - RunTest( app, { - 'description': 'No need to force after a semantic trigger', - 'request': { - 'line_num': 17, - 'column_num': 3, - 'filepath': filepath, - 'filetype': 'typescriptreact' - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'completions': has_item( has_entries( { - 'insertion_text': 'foo', - 'extra_menu_info': "(property) 'foo': number", - 'detailed_info': "(property) 'foo': number", - 'kind': 'property', - } ) ) - } ) - } - } ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_GetCompletions_TypeScriptReact_DefaultTriggers( self, app ): + filepath = PathToTestFile( 'test.tsx' ) + RunTest( app, { + 'description': 'No need to force after a semantic trigger', + 'request': { + 'line_num': 17, + 'column_num': 3, + 'filepath': filepath, + 'filetype': 'typescriptreact' + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( has_entries( { + 'insertion_text': 'foo', + 'extra_menu_info': "(property) 'foo': number", + 'detailed_info': "(property) 'foo': number", + 'kind': 'property', + } ) ) + } ) + } + } ) diff --git a/ycmd/tests/typescript/signature_help_test.py b/ycmd/tests/typescript/signature_help_test.py index 4585b05142..0072f74e1b 100644 --- a/ycmd/tests/typescript/signature_help_test.py +++ b/ycmd/tests/typescript/signature_help_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020 ycmd contributors +# Copyright (C) 2021 ycmd contributors # # This file is part of ycmd. # @@ -16,9 +16,10 @@ # along with ycmd. If not, see . from hamcrest import assert_that, contains_exactly, empty, equal_to, has_entries +from unittest import TestCase import requests -from ycmd.tests.typescript import PathToTestFile, IsolatedYcmd, SharedYcmd +from ycmd.tests.typescript import PathToTestFile, IsolatedYcmd, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( CombineRequest, ParameterMatcher, SignatureMatcher, @@ -66,233 +67,231 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def Signature_Help_Available_test( app ): - response = app.get( '/signature_help_available', - { 'subserver': 'typescript' } ).json - assert_that( response, SignatureAvailableMatcher( 'YES' ) ) +class SignatureHelpTest( TestCase ): + @SharedYcmd + def test_Signature_Help_Available( self, app ): + response = app.get( '/signature_help_available', + { 'subserver': 'typescript' } ).json + assert_that( response, SignatureAvailableMatcher( 'YES' ) ) -# Triggering on '(', ',' and '<' -@SharedYcmd -def Signature_Help_Trigger_Paren_test( app ): - RunTest( app, { - 'description': 'Trigger after (', - 'request': { - 'filetype' : 'typescript', - 'filepath' : PathToTestFile( 'signatures.ts' ), - 'line_num' : 27, - 'column_num': 29, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'single_argument_with_return(a: string): string', - [ ParameterMatcher( 28, 37, '' ) ], - '' ) - ), - } ), - } ) - } - } ) + # Triggering on '(', ',' and '<' + @SharedYcmd + def test_Signature_Help_Trigger_Paren( self, app ): + RunTest( app, { + 'description': 'Trigger after (', + 'request': { + 'filetype' : 'typescript', + 'filepath' : PathToTestFile( 'signatures.ts' ), + 'line_num' : 27, + 'column_num': 29, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( + 'single_argument_with_return(a: string): string', + [ ParameterMatcher( 28, 37, '' ) ], + '' ) + ), + } ), + } ) + } + } ) -@IsolatedYcmd( { 'disable_signature_help': True } ) -def Signature_Help_Trigger_Paren_Disabled_test( app ): - RunTest( app, { - 'description': 'Trigger after (', - 'request': { - 'filetype' : 'typescript', - 'filepath' : PathToTestFile( 'signatures.ts' ), - 'line_num' : 27, - 'column_num': 29, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty() - } ), - } ) - } - } ) + @IsolatedYcmd( { 'disable_signature_help': True } ) + def test_Signature_Help_Trigger_Paren_Disabled( self, app ): + RunTest( app, { + 'description': 'Trigger after (', + 'request': { + 'filetype' : 'typescript', + 'filepath' : PathToTestFile( 'signatures.ts' ), + 'line_num' : 27, + 'column_num': 29, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty() + } ), + } ) + } + } ) -@SharedYcmd -def Signature_Help_Trigger_Comma_test( app ): - RunTest( app, { - 'description': 'Trigger after ,', - 'request': { - 'filetype' : 'typescript', - 'filepath' : PathToTestFile( 'signatures.ts' ), - 'line_num' : 60, - 'column_num': 32, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( - ( 'multi_argument_no_return(løng_våriable_name: number, ' - 'untyped_argument: any): number' ), - [ ParameterMatcher( 25, 53, '' ), - ParameterMatcher( 55, 76, '' ) ], - '' ) - ), - } ), - } ) - } - } ) - - -@SharedYcmd -def Signature_Help_Trigger_AngleBracket_test( app ): - RunTest( app, { - 'description': 'Trigger after <', - 'request': { - 'filetype' : 'typescript', - 'filepath' : PathToTestFile( 'signatures.ts' ), - 'line_num' : 68, - 'column_num': 9, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( - 'generic(t: SomeClass): string', - [ ParameterMatcher( 8, 32, '' ) ], - '' ) - ), - } ), - } ) - } - } ) + @SharedYcmd + def test_Signature_Help_Trigger_Comma( self, app ): + RunTest( app, { + 'description': 'Trigger after ,', + 'request': { + 'filetype' : 'typescript', + 'filepath' : PathToTestFile( 'signatures.ts' ), + 'line_num' : 60, + 'column_num': 32, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( + ( 'multi_argument_no_return(løng_våriable_name: number, ' + 'untyped_argument: any): number' ), + [ ParameterMatcher( 25, 53, '' ), + ParameterMatcher( 55, 76, '' ) ], + '' ) + ), + } ), + } ) + } + } ) -@SharedYcmd -def Signature_Help_Multiple_Signatures_test( app ): - RunTest( app, { - 'description': 'Test overloaded methods', - 'request': { - 'filetype' : 'typescript', - 'filepath' : PathToTestFile( 'signatures.ts' ), - 'line_num' : 89, - 'column_num': 18, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 1, - 'activeParameter': 1, - 'signatures': contains_exactly( - SignatureMatcher( 'øverløåd(a: number): string', - [ ParameterMatcher( 12, 21, '' ) ], - '' ), - SignatureMatcher( 'øverløåd(a: string, b: number): string', - [ ParameterMatcher( 12, 21, '' ), - ParameterMatcher( 23, 32, '' ) ], - '' ) - ), - } ), - } ) - } - } ) + @SharedYcmd + def test_Signature_Help_Trigger_AngleBracket( self, app ): + RunTest( app, { + 'description': 'Trigger after <', + 'request': { + 'filetype' : 'typescript', + 'filepath' : PathToTestFile( 'signatures.ts' ), + 'line_num' : 68, + 'column_num': 9, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( + 'generic(t: SomeClass): string', + [ ParameterMatcher( 8, 32, '' ) ], + '' ) + ), + } ), + } ) + } + } ) -@SharedYcmd -def Signature_Help_NoSignatures_test( app ): - RunTest( app, { - 'description': 'Test overloaded methods', - 'request': { - 'filetype' : 'typescript', - 'filepath' : PathToTestFile( 'signatures.ts' ), - 'line_num' : 68, - 'column_num': 22, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty(), - } ), - } ) - } - } ) + @SharedYcmd + def test_Signature_Help_Multiple_Signatures( self, app ): + RunTest( app, { + 'description': 'Test overloaded methods', + 'request': { + 'filetype' : 'typescript', + 'filepath' : PathToTestFile( 'signatures.ts' ), + 'line_num' : 89, + 'column_num': 18, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 1, + 'activeParameter': 1, + 'signatures': contains_exactly( + SignatureMatcher( 'øverløåd(a: number): string', + [ ParameterMatcher( 12, 21, '' ) ], + '' ), + SignatureMatcher( 'øverløåd(a: string, b: number): string', + [ ParameterMatcher( 12, 21, '' ), + ParameterMatcher( 23, 32, '' ) ], + '' ) + ), + } ), + } ) + } + } ) -@SharedYcmd -def Signature_Help_WithDoc_test( app ): - RunTest( app, { - 'description': 'Test parameter documentation', - 'request': { - 'filetype': 'typescript', - 'filepath': PathToTestFile( 'signatures.ts' ), - 'line_num': 101, - 'column_num': 26, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': contains_exactly( - SignatureMatcher( 'single_argument_with_doc(a: string): string', - [ ParameterMatcher( 25, 34, '- The argument' ) ], - 'A function with a single argument' ) ) - } ), - } ) - } - } ) + @SharedYcmd + def test_Signature_Help_NoSignatures( self, app ): + RunTest( app, { + 'description': 'Test overloaded methods', + 'request': { + 'filetype' : 'typescript', + 'filepath' : PathToTestFile( 'signatures.ts' ), + 'line_num' : 68, + 'column_num': 22, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty(), + } ), + } ) + } + } ) -@SharedYcmd -def Signature_Help_NoErrorWhenNoSignatureInfo_test( app ): - RunTest( app, { - 'description': 'Test dodgy (', - 'request': { - 'filetype' : 'typescript', - 'filepath' : PathToTestFile( 'signatures.ts' ), - 'line_num' : 103, - 'column_num': 5, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'errors': empty(), - 'signature_help': has_entries( { - 'activeSignature': 0, - 'activeParameter': 0, - 'signatures': empty(), - } ), - } ) - } - } ) + @SharedYcmd + def test_Signature_Help_WithDoc( self, app ): + RunTest( app, { + 'description': 'Test parameter documentation', + 'request': { + 'filetype': 'typescript', + 'filepath': PathToTestFile( 'signatures.ts' ), + 'line_num': 101, + 'column_num': 26, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': contains_exactly( + SignatureMatcher( + 'single_argument_with_doc(a: string): string', + [ ParameterMatcher( 25, 34, '- The argument' ) ], + 'A function with a single argument' ) ) + } ), + } ) + } + } ) -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @SharedYcmd + def test_Signature_Help_NoErrorWhenNoSignatureInfo( self, app ): + RunTest( app, { + 'description': 'Test dodgy (', + 'request': { + 'filetype' : 'typescript', + 'filepath' : PathToTestFile( 'signatures.ts' ), + 'line_num' : 103, + 'column_num': 5, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'errors': empty(), + 'signature_help': has_entries( { + 'activeSignature': 0, + 'activeParameter': 0, + 'signatures': empty(), + } ), + } ) + } + } ) diff --git a/ycmd/tests/typescript/subcommands_test.py b/ycmd/tests/typescript/subcommands_test.py index 5163be65e7..1be271a3c4 100644 --- a/ycmd/tests/typescript/subcommands_test.py +++ b/ycmd/tests/typescript/subcommands_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2015-2020 ycmd contributors +# Copyright (C) 2015-2021 ycmd contributors # # This file is part of ycmd. # @@ -24,11 +24,11 @@ has_items, matches_regexp ) from unittest.mock import patch +from unittest import TestCase import requests import pprint -import pytest -from ycmd.tests.typescript import IsolatedYcmd, PathToTestFile, SharedYcmd +from ycmd.tests.typescript import IsolatedYcmd, PathToTestFile, SharedYcmd, setUpModule, tearDownModule # noqa from ycmd.tests.test_utils import ( BuildRequest, ChunkMatcher, CombineRequest, @@ -82,1023 +82,1018 @@ def RunTest( app, test ): assert_that( response.json, test[ 'expect' ][ 'data' ] ) -@SharedYcmd -def Subcommands_DefinedSubcommands_test( app ): - subcommands_data = BuildRequest( completer_target = 'typescript' ) - - assert_that( - app.post_json( '/defined_subcommands', subcommands_data ).json, - contains_inanyorder( - 'Format', - 'GoTo', - 'GoToDeclaration', - 'GoToDefinition', - 'GoToImplementation', - 'GoToType', - 'GetDoc', - 'GetType', - 'GoToReferences', - 'GoToSymbol', - 'FixIt', - 'OrganizeImports', - 'RefactorRename', - 'RestartServer' - ) - ) - - -@SharedYcmd -def Subcommands_Format_WholeFile_Spaces_test( app ): - filepath = PathToTestFile( 'test.ts' ) +def Subcommands_GoTo_Basic( app, goto_command ): RunTest( app, { - 'description': 'Formatting is applied on the whole file ' - 'with tabs composed of 4 spaces', + 'description': goto_command + ' works within file', 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': True - } + 'command': goto_command, + 'line_num': 34, + 'column_num': 8, + 'filepath': PathToTestFile( 'test.ts' ), }, 'expect': { 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( ' ', - LocationMatcher( filepath, 3, 1 ), - LocationMatcher( filepath, 3, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 4, 1 ), - LocationMatcher( filepath, 4, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 4, 14 ), - LocationMatcher( filepath, 4, 14 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 5, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 5, 14 ), - LocationMatcher( filepath, 5, 14 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 6, 1 ), - LocationMatcher( filepath, 6, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 7, 1 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 7 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 9, 1 ), - LocationMatcher( filepath, 9, 7 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 10, 1 ), - LocationMatcher( filepath, 10, 5 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 11, 1 ), - LocationMatcher( filepath, 11, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 11, 6 ), - LocationMatcher( filepath, 11, 6 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 21, 1 ), - LocationMatcher( filepath, 21, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 22, 1 ), - LocationMatcher( filepath, 22, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 23, 1 ), - LocationMatcher( filepath, 23, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 24, 1 ), - LocationMatcher( filepath, 24, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 27, 1 ), - LocationMatcher( filepath, 27, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 28, 1 ), - LocationMatcher( filepath, 28, 4 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 29, 1 ), - LocationMatcher( filepath, 29, 4 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 30, 1 ), - LocationMatcher( filepath, 30, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 30, 17 ), - LocationMatcher( filepath, 30, 17 ) ), - ) - } ) ) - } ) + 'data': LocationMatcher( PathToTestFile( 'test.ts' ), 30, 3 ) } } ) -@SharedYcmd -def Subcommands_Format_WholeFile_Tabs_test( app ): - filepath = PathToTestFile( 'test.ts' ) +def Subcommands_GoTo_Unicode( app, goto_command ): RunTest( app, { - 'description': 'Formatting is applied on the whole file ' - 'with tabs composed of 2 spaces', + 'description': goto_command + ' works with Unicode characters', 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': False - } + 'command': goto_command, + 'line_num': 28, + 'column_num': 19, + 'filepath': PathToTestFile( 'unicode.ts' ), }, 'expect': { 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\t', - LocationMatcher( filepath, 3, 1 ), - LocationMatcher( filepath, 3, 3 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 4, 1 ), - LocationMatcher( filepath, 4, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 4, 14 ), - LocationMatcher( filepath, 4, 14 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 5, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 5, 14 ), - LocationMatcher( filepath, 5, 14 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 6, 1 ), - LocationMatcher( filepath, 6, 3 ) ), - ChunkMatcher( '\t\t', - LocationMatcher( filepath, 7, 1 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( '\t\t\t', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 7 ) ), - ChunkMatcher( '\t\t\t', - LocationMatcher( filepath, 9, 1 ), - LocationMatcher( filepath, 9, 7 ) ), - ChunkMatcher( '\t\t', - LocationMatcher( filepath, 10, 1 ), - LocationMatcher( filepath, 10, 5 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 11, 1 ), - LocationMatcher( filepath, 11, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 11, 6 ), - LocationMatcher( filepath, 11, 6 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 21, 1 ), - LocationMatcher( filepath, 21, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 22, 1 ), - LocationMatcher( filepath, 22, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 23, 1 ), - LocationMatcher( filepath, 23, 2 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 24, 1 ), - LocationMatcher( filepath, 24, 2 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 27, 1 ), - LocationMatcher( filepath, 27, 3 ) ), - ChunkMatcher( '\t ', - LocationMatcher( filepath, 28, 1 ), - LocationMatcher( filepath, 28, 4 ) ), - ChunkMatcher( '\t ', - LocationMatcher( filepath, 29, 1 ), - LocationMatcher( filepath, 29, 4 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 30, 1 ), - LocationMatcher( filepath, 30, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 30, 17 ), - LocationMatcher( filepath, 30, 17 ) ), - ) - } ) ) - } ) + 'data': LocationMatcher( PathToTestFile( 'unicode.ts' ), 15, 3 ) } } ) -@SharedYcmd -def Subcommands_Format_Range_Spaces_test( app ): - filepath = PathToTestFile( 'test.ts' ) +def Subcommands_GoTo_Fail( app, goto_command ): RunTest( app, { - 'description': 'Formatting is applied on some part of the file ' - 'with tabs composed of 4 spaces by default', + 'description': goto_command + ' fails on non-existing method', 'request': { - 'command': 'Format', - 'filepath': filepath, - 'range': { - 'start': { - 'line_num': 6, - 'column_num': 3, - }, - 'end': { - 'line_num': 11, - 'column_num': 6 - } - }, - 'options': { - 'tab_size': 4, - 'insert_spaces': True - } + 'command': goto_command, + 'line_num': 35, + 'column_num': 6, + 'filepath': PathToTestFile( 'test.ts' ), }, 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( ' ', - LocationMatcher( filepath, 6, 1 ), - LocationMatcher( filepath, 6, 3 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 7, 1 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 7 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 9, 1 ), - LocationMatcher( filepath, 9, 7 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 10, 1 ), - LocationMatcher( filepath, 10, 5 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 11, 1 ), - LocationMatcher( filepath, 11, 3 ) ), - ) - } ) ) - } ) + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, 'Could not find definition.' ) } } ) -@IsolatedYcmd() -def Subcommands_Format_Range_Tabs_test( app ): - WaitUntilCompleterServerReady( app, 'typescript' ) - filepath = PathToTestFile( 'test.ts' ) - RunTest( app, { - 'description': 'Formatting is applied on some part of the file ' - 'with tabs instead of spaces', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'range': { - 'start': { - 'line_num': 6, - 'column_num': 3, - }, - 'end': { - 'line_num': 11, - 'column_num': 6 +class SubcommandsTest( TestCase ): + @SharedYcmd + def test_Subcommands_DefinedSubcommands( self, app ): + subcommands_data = BuildRequest( completer_target = 'typescript' ) + + assert_that( + app.post_json( '/defined_subcommands', subcommands_data ).json, + contains_inanyorder( + 'Format', + 'GoTo', + 'GoToDeclaration', + 'GoToDefinition', + 'GoToImplementation', + 'GoToType', + 'GetDoc', + 'GetType', + 'GoToReferences', + 'GoToSymbol', + 'FixIt', + 'OrganizeImports', + 'RefactorRename', + 'RestartServer' + ) + ) + + + @SharedYcmd + def test_Subcommands_Format_WholeFile_Spaces( self, app ): + filepath = PathToTestFile( 'test.ts' ) + RunTest( app, { + 'description': 'Formatting is applied on the whole file ' + 'with tabs composed of 4 spaces', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': True } }, - 'options': { - 'tab_size': 4, - 'insert_spaces': False + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( ' ', + LocationMatcher( filepath, 3, 1 ), + LocationMatcher( filepath, 3, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 4, 1 ), + LocationMatcher( filepath, 4, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 4, 14 ), + LocationMatcher( filepath, 4, 14 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 5, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 5, 14 ), + LocationMatcher( filepath, 5, 14 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 6, 1 ), + LocationMatcher( filepath, 6, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 7, 1 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 7 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 9, 1 ), + LocationMatcher( filepath, 9, 7 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 10, 1 ), + LocationMatcher( filepath, 10, 5 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 11, 1 ), + LocationMatcher( filepath, 11, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 11, 6 ), + LocationMatcher( filepath, 11, 6 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 21, 1 ), + LocationMatcher( filepath, 21, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 22, 1 ), + LocationMatcher( filepath, 22, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 23, 1 ), + LocationMatcher( filepath, 23, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 24, 1 ), + LocationMatcher( filepath, 24, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 27, 1 ), + LocationMatcher( filepath, 27, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 28, 1 ), + LocationMatcher( filepath, 28, 4 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 29, 1 ), + LocationMatcher( filepath, 29, 4 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 30, 1 ), + LocationMatcher( filepath, 30, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 30, 17 ), + LocationMatcher( filepath, 30, 17 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( '\t', - LocationMatcher( filepath, 6, 1 ), - LocationMatcher( filepath, 6, 3 ) ), - ChunkMatcher( '\t\t', - LocationMatcher( filepath, 7, 1 ), - LocationMatcher( filepath, 7, 5 ) ), - ChunkMatcher( '\t\t\t', - LocationMatcher( filepath, 8, 1 ), - LocationMatcher( filepath, 8, 7 ) ), - ChunkMatcher( '\t\t\t', - LocationMatcher( filepath, 9, 1 ), - LocationMatcher( filepath, 9, 7 ) ), - ChunkMatcher( '\t\t', - LocationMatcher( filepath, 10, 1 ), - LocationMatcher( filepath, 10, 5 ) ), - ChunkMatcher( '\t', - LocationMatcher( filepath, 11, 1 ), - LocationMatcher( filepath, 11, 3 ) ), - ) - } ) ) - } ) - } - } ) + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'extra_confs', 'brace_on_same_line.py' ) } ) -def Subcommands_Format_ExtraConf_BraceOnSameLine_test( app ): - WaitUntilCompleterServerReady( app, 'typescript' ) - filepath = PathToTestFile( 'extra_confs', 'func.ts' ) - RunTest( app, { - 'description': 'Format with an extra conf, braces on new line', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': True + @SharedYcmd + def test_Subcommands_Format_WholeFile_Tabs( self, app ): + filepath = PathToTestFile( 'test.ts' ) + RunTest( app, { + 'description': 'Formatting is applied on the whole file ' + 'with tabs composed of 2 spaces', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': False + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\t', + LocationMatcher( filepath, 3, 1 ), + LocationMatcher( filepath, 3, 3 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 4, 1 ), + LocationMatcher( filepath, 4, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 4, 14 ), + LocationMatcher( filepath, 4, 14 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 5, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 5, 14 ), + LocationMatcher( filepath, 5, 14 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 6, 1 ), + LocationMatcher( filepath, 6, 3 ) ), + ChunkMatcher( '\t\t', + LocationMatcher( filepath, 7, 1 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( '\t\t\t', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 7 ) ), + ChunkMatcher( '\t\t\t', + LocationMatcher( filepath, 9, 1 ), + LocationMatcher( filepath, 9, 7 ) ), + ChunkMatcher( '\t\t', + LocationMatcher( filepath, 10, 1 ), + LocationMatcher( filepath, 10, 5 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 11, 1 ), + LocationMatcher( filepath, 11, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 11, 6 ), + LocationMatcher( filepath, 11, 6 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 21, 1 ), + LocationMatcher( filepath, 21, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 22, 1 ), + LocationMatcher( filepath, 22, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 23, 1 ), + LocationMatcher( filepath, 23, 2 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 24, 1 ), + LocationMatcher( filepath, 24, 2 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 27, 1 ), + LocationMatcher( filepath, 27, 3 ) ), + ChunkMatcher( '\t ', + LocationMatcher( filepath, 28, 1 ), + LocationMatcher( filepath, 28, 4 ) ), + ChunkMatcher( '\t ', + LocationMatcher( filepath, 29, 1 ), + LocationMatcher( filepath, 29, 4 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 30, 1 ), + LocationMatcher( filepath, 30, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 30, 17 ), + LocationMatcher( filepath, 30, 17 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( ' ', - LocationMatcher( filepath, 2, 1 ), - LocationMatcher( filepath, 2, 1 ) ), - ) - } ) ) - } ) - } - } ) + } ) -@IsolatedYcmd( { 'global_ycm_extra_conf': - PathToTestFile( 'extra_confs', 'brace_on_new_line.py' ) } ) -def Subcommands_Format_ExtraConf_BraceOnNewLine_test( app ): - WaitUntilCompleterServerReady( app, 'typescript' ) - filepath = PathToTestFile( 'extra_confs', 'func.ts' ) - RunTest( app, { - 'description': 'Format with an extra conf, braces on new line', - 'request': { - 'command': 'Format', - 'filepath': filepath, - 'options': { - 'tab_size': 4, - 'insert_spaces': True + @SharedYcmd + def test_Subcommands_Format_Range_Spaces( self, app ): + filepath = PathToTestFile( 'test.ts' ) + RunTest( app, { + 'description': 'Formatting is applied on some part of the file ' + 'with tabs composed of 4 spaces by default', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'range': { + 'start': { + 'line_num': 6, + 'column_num': 3, + }, + 'end': { + 'line_num': 11, + 'column_num': 6 + } + }, + 'options': { + 'tab_size': 4, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( ' ', + LocationMatcher( filepath, 6, 1 ), + LocationMatcher( filepath, 6, 3 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 7, 1 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 7 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 9, 1 ), + LocationMatcher( filepath, 9, 7 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 10, 1 ), + LocationMatcher( filepath, 10, 5 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 11, 1 ), + LocationMatcher( filepath, 11, 3 ) ), + ) + } ) ) + } ) } - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( matches_regexp( '\n?\n' ), - LocationMatcher( filepath, 1, 19 ), - LocationMatcher( filepath, 1, 20 ) ), - ChunkMatcher( ' ', - LocationMatcher( filepath, 2, 1 ), - LocationMatcher( filepath, 2, 1 ) ), - ) - } ) ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GetType_Basic_test( app ): - RunTest( app, { - 'description': 'GetType works on a variable', - 'request': { - 'command': 'GetType', - 'line_num': 17, - 'column_num': 1, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': MessageMatcher( 'var foo: Foo' ) - } - } ) - - -@SharedYcmd -def Subcommands_GetType_HasNoType_test( app ): - RunTest( app, { - 'description': 'GetType returns an error on a keyword', - 'request': { - 'command': 'GetType', - 'line_num': 32, - 'column_num': 1, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'No content available.' ) - } - } ) - - -@SharedYcmd -def Subcommands_GetDoc_Method_test( app ): - RunTest( app, { - 'description': 'GetDoc on a method returns its docstring', - 'request': { - 'command': 'GetDoc', - 'line_num': 34, - 'column_num': 9, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'detailed_info': '(method) Bar.testMethod(): void\n\n' - 'Method documentation' - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GetDoc_Class_test( app ): - RunTest( app, { - 'description': 'GetDoc on a class returns its docstring', - 'request': { - 'command': 'GetDoc', - 'line_num': 37, - 'column_num': 2, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'detailed_info': 'class Bar\n\n' - 'Class documentation\n\n' - 'Multi-line' - } ) - } - } ) - - -@SharedYcmd -def Subcommands_GetDoc_Class_Unicode_test( app ): - RunTest( app, { - 'description': 'GetDoc works with Unicode characters', - 'request': { - 'command': 'GetDoc', - 'line_num': 35, - 'column_num': 12, - 'filepath': PathToTestFile( 'unicode.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'detailed_info': 'class Båøz\n\n' - 'Test unicøde st††††', - } ) - } - } ) + } ) -@SharedYcmd -def Subcommands_GoToReferences_test( app ): - RunTest( app, { - 'description': 'GoToReferences works', - 'request': { - 'command': 'GoToReferences', - 'line_num': 33, - 'column_num': 6, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': contains_inanyorder( - has_entries( { 'description': 'var bar = new Bar();', - 'line_num' : 33, - 'column_num' : 5, - 'filepath' : PathToTestFile( 'test.ts' ) } ), - has_entries( { 'description': 'bar.testMethod();', - 'line_num' : 34, - 'column_num' : 1, - 'filepath' : PathToTestFile( 'test.ts' ) } ), - has_entries( { 'description': 'bar.nonExistingMethod();', - 'line_num' : 35, - 'column_num' : 1, - 'filepath' : PathToTestFile( 'test.ts' ) } ), - has_entries( { 'description': 'var bar = new Bar();', - 'line_num' : 1, - 'column_num' : 5, - 'filepath' : PathToTestFile( 'file3.ts' ) } ), - has_entries( { 'description': 'bar.testMethod();', - 'line_num' : 2, - 'column_num' : 1, - 'filepath' : PathToTestFile( 'file3.ts' ) } ) - ) - } - } ) + @IsolatedYcmd() + def test_Subcommands_Format_Range_Tabs( self, app ): + WaitUntilCompleterServerReady( app, 'typescript' ) + filepath = PathToTestFile( 'test.ts' ) + RunTest( app, { + 'description': 'Formatting is applied on some part of the file ' + 'with tabs instead of spaces', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'range': { + 'start': { + 'line_num': 6, + 'column_num': 3, + }, + 'end': { + 'line_num': 11, + 'column_num': 6 + } + }, + 'options': { + 'tab_size': 4, + 'insert_spaces': False + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( '\t', + LocationMatcher( filepath, 6, 1 ), + LocationMatcher( filepath, 6, 3 ) ), + ChunkMatcher( '\t\t', + LocationMatcher( filepath, 7, 1 ), + LocationMatcher( filepath, 7, 5 ) ), + ChunkMatcher( '\t\t\t', + LocationMatcher( filepath, 8, 1 ), + LocationMatcher( filepath, 8, 7 ) ), + ChunkMatcher( '\t\t\t', + LocationMatcher( filepath, 9, 1 ), + LocationMatcher( filepath, 9, 7 ) ), + ChunkMatcher( '\t\t', + LocationMatcher( filepath, 10, 1 ), + LocationMatcher( filepath, 10, 5 ) ), + ChunkMatcher( '\t', + LocationMatcher( filepath, 11, 1 ), + LocationMatcher( filepath, 11, 3 ) ), + ) + } ) ) + } ) + } + } ) -@SharedYcmd -def Subcommands_GoToImplementation_test( app ): - RunTest( app, { - 'description': 'GoToImplementation works', - 'request': { - 'command': 'GoToImplementation', - 'line_num': 6, - 'column_num': 11, - 'filepath': PathToTestFile( 'signatures.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': contains_inanyorder( - has_entries( { 'description': ' return {', - 'line_num' : 12, - 'column_num' : 10, - 'filepath' : PathToTestFile( 'signatures.ts' ) } ), - has_entries( { 'description': 'class SomeClass ' - 'implements ReturnValue {', - 'line_num' : 35, - 'column_num' : 7, - 'filepath' : PathToTestFile( 'signatures.ts' ) } ), - ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'extra_confs', 'brace_on_same_line.py' ) } ) + def test_Subcommands_Format_ExtraConf_BraceOnSameLine( self, app ): + WaitUntilCompleterServerReady( app, 'typescript' ) + filepath = PathToTestFile( 'extra_confs', 'func.ts' ) + RunTest( app, { + 'description': 'Format with an extra conf, braces on new line', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( ' ', + LocationMatcher( filepath, 2, 1 ), + LocationMatcher( filepath, 2, 1 ) ), + ) + } ) ) + } ) + } + } ) -@SharedYcmd -def Subcommands_GoToImplementation_InvalidLocation_test( app ): - RunTest( app, { - 'description': 'GoToImplementation on an invalid location raises exception', - 'request': { - 'command': 'GoToImplementation', - 'line_num': 1, - 'column_num': 1, - 'filepath': PathToTestFile( 'signatures.ts' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'No implementation found.' ) - } - } ) + @IsolatedYcmd( { 'global_ycm_extra_conf': + PathToTestFile( 'extra_confs', 'brace_on_new_line.py' ) } ) + def test_Subcommands_Format_ExtraConf_BraceOnNewLine( self, app ): + WaitUntilCompleterServerReady( app, 'typescript' ) + filepath = PathToTestFile( 'extra_confs', 'func.ts' ) + RunTest( app, { + 'description': 'Format with an extra conf, braces on new line', + 'request': { + 'command': 'Format', + 'filepath': filepath, + 'options': { + 'tab_size': 4, + 'insert_spaces': True + } + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_exactly( + ChunkMatcher( matches_regexp( '\n?\n' ), + LocationMatcher( filepath, 1, 19 ), + LocationMatcher( filepath, 1, 20 ) ), + ChunkMatcher( ' ', + LocationMatcher( filepath, 2, 1 ), + LocationMatcher( filepath, 2, 1 ) ), + ) + } ) ) + } ) + } + } ) + @SharedYcmd + def test_Subcommands_GetType_Basic( self, app ): + RunTest( app, { + 'description': 'GetType works on a variable', + 'request': { + 'command': 'GetType', + 'line_num': 17, + 'column_num': 1, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': MessageMatcher( 'var foo: Foo' ) + } + } ) -@SharedYcmd -def Subcommands_GoToReferences_Unicode_test( app ): - RunTest( app, { - 'description': 'GoToReferences works with Unicode characters', - 'request': { - 'command': 'GoToReferences', - 'line_num': 14, - 'column_num': 3, - 'filepath': PathToTestFile( 'unicode.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': contains_inanyorder( - has_entries( { 'description': ' å: number;', - 'line_num' : 14, - 'column_num' : 3, - 'filepath' : PathToTestFile( 'unicode.ts' ) } ), - has_entries( { 'description': 'var baz = new Bår(); baz.å;', - 'line_num' : 20, - 'column_num' : 27, - 'filepath' : PathToTestFile( 'unicode.ts' ) } ), - has_entries( { 'description': 'baz.å;', - 'line_num' : 23, - 'column_num' : 5, - 'filepath' : PathToTestFile( 'unicode.ts' ) } ), - has_entries( { 'description': 'føø_long_long.å;', - 'line_num' : 27, - 'column_num' : 17, - 'filepath' : PathToTestFile( 'unicode.ts' ) } ) - ) - } - } ) + @SharedYcmd + def test_Subcommands_GetType_HasNoType( self, app ): + RunTest( app, { + 'description': 'GetType returns an error on a keyword', + 'request': { + 'command': 'GetType', + 'line_num': 32, + 'column_num': 1, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, 'No content available.' ) + } + } ) -def Subcommands_GoTo_Basic( app, goto_command ): - RunTest( app, { - 'description': goto_command + ' works within file', - 'request': { - 'command': goto_command, - 'line_num': 34, - 'column_num': 8, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': LocationMatcher( PathToTestFile( 'test.ts' ), 30, 3 ) - } - } ) + @SharedYcmd + def test_Subcommands_GetDoc_Method( self, app ): + RunTest( app, { + 'description': 'GetDoc on a method returns its docstring', + 'request': { + 'command': 'GetDoc', + 'line_num': 34, + 'column_num': 9, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'detailed_info': '(method) Bar.testMethod(): void\n\n' + 'Method documentation' + } ) + } + } ) -@pytest.mark.parametrize( "req,rep", [ - ( ( 'signatures.ts', 1, 1, 'no_arguments_no_return' ), - ( 'signatures.ts', 3, 1, 'no_arguments_no_return' ) ), - ( ( 'signatures.ts', 1, 1, 'ReturnValue' ), - [ ( 'signatures.ts', 6, 1, 'ReturnValue' ) ] ), + @SharedYcmd + def test_Subcommands_GetDoc_Class( self, app ): + RunTest( app, { + 'description': 'GetDoc on a class returns its docstring', + 'request': { + 'command': 'GetDoc', + 'line_num': 37, + 'column_num': 2, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'detailed_info': 'class Bar\n\n' + 'Class documentation\n\n' + 'Multi-line' + } ) + } + } ) - ( ( 'signatures.ts', 1, 1, 'Foo' ), - [ ( 'test.ts', 14, 5, 'foo' ), - ( 'test.ts', 2, 1, 'Foo' ) ] ), - ( ( 'signatures.ts', 1, 1, 'nothinghere' ), 'Symbol not found' ) -] ) -@SharedYcmd -def Subcommands_GoToSymbol_test( app, req, rep ): - if isinstance( rep, tuple ): - expect = { - 'response': requests.codes.ok, - 'data': LocationMatcher( PathToTestFile( rep[ 0 ] ), *rep[ 1: ] ) - } - elif isinstance( rep, list ): - # NOTE: We use has_items here because tsserver will include results from - # node_modules and all sorts of other random places. - expect = { - 'response': requests.codes.ok, - 'data': has_items( *[ - LocationMatcher( PathToTestFile( r[ 0 ] ), *r[ 1: ] ) - for r in rep - ] ) - } - else: - expect = { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, rep ) - } + @SharedYcmd + def test_Subcommands_GetDoc_Class_Unicode( self, app ): + RunTest( app, { + 'description': 'GetDoc works with Unicode characters', + 'request': { + 'command': 'GetDoc', + 'line_num': 35, + 'column_num': 12, + 'filepath': PathToTestFile( 'unicode.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'detailed_info': 'class Båøz\n\n' + 'Test unicøde st††††', + } ) + } + } ) - RunTest( app, { - 'request': { - 'command': 'GoToSymbol', - 'arguments': [ req[ 3 ] ], - 'line_num': req[ 1 ], - 'column_num': req[ 2 ], - 'filepath': PathToTestFile( req[ 0 ] ), - }, - 'expect': expect - } ) + @SharedYcmd + def test_Subcommands_GoToReferences( self, app ): + RunTest( app, { + 'description': 'GoToReferences works', + 'request': { + 'command': 'GoToReferences', + 'line_num': 33, + 'column_num': 6, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_inanyorder( + has_entries( { 'description': 'var bar = new Bar();', + 'line_num' : 33, + 'column_num' : 5, + 'filepath' : PathToTestFile( 'test.ts' ) } ), + has_entries( { 'description': 'bar.testMethod();', + 'line_num' : 34, + 'column_num' : 1, + 'filepath' : PathToTestFile( 'test.ts' ) } ), + has_entries( { 'description': 'bar.nonExistingMethod();', + 'line_num' : 35, + 'column_num' : 1, + 'filepath' : PathToTestFile( 'test.ts' ) } ), + has_entries( { 'description': 'var bar = new Bar();', + 'line_num' : 1, + 'column_num' : 5, + 'filepath' : PathToTestFile( 'file3.ts' ) } ), + has_entries( { 'description': 'bar.testMethod();', + 'line_num' : 2, + 'column_num' : 1, + 'filepath' : PathToTestFile( 'file3.ts' ) } ) + ) + } + } ) -@pytest.mark.parametrize( 'command', [ 'GoTo', - 'GoToDefinition', - 'GoToDeclaration' ] ) -@SharedYcmd -def Subcommands_GoTo_Basic_test( app, command ): - Subcommands_GoTo_Basic( app, command ) + @SharedYcmd + def test_Subcommands_GoToImplementation( self, app ): + RunTest( app, { + 'description': 'GoToImplementation works', + 'request': { + 'command': 'GoToImplementation', + 'line_num': 6, + 'column_num': 11, + 'filepath': PathToTestFile( 'signatures.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_inanyorder( + has_entries( { 'description': ' return {', + 'line_num' : 12, + 'column_num' : 10, + 'filepath' : PathToTestFile( 'signatures.ts' ) } ), + has_entries( { 'description': 'class SomeClass ' + 'implements ReturnValue {', + 'line_num' : 35, + 'column_num' : 7, + 'filepath' : PathToTestFile( 'signatures.ts' ) } ), + ) + } + } ) -def Subcommands_GoTo_Unicode( app, goto_command ): - RunTest( app, { - 'description': goto_command + ' works with Unicode characters', - 'request': { - 'command': goto_command, - 'line_num': 28, - 'column_num': 19, - 'filepath': PathToTestFile( 'unicode.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': LocationMatcher( PathToTestFile( 'unicode.ts' ), 15, 3 ) - } - } ) + @SharedYcmd + def test_Subcommands_GoToImplementation_InvalidLocation( self, app ): + RunTest( app, { + 'description': 'GoToImplementation on an invalid ' + 'location raises exception', + 'request': { + 'command': 'GoToImplementation', + 'line_num': 1, + 'column_num': 1, + 'filepath': PathToTestFile( 'signatures.ts' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, 'No implementation found.' ) + } + } ) -@pytest.mark.parametrize( 'command', [ 'GoTo', - 'GoToDefinition', - 'GoToDeclaration' ] ) -@SharedYcmd -def Subcommands_GoTo_Unicode_test( app, command ): - Subcommands_GoTo_Unicode( app, command ) -def Subcommands_GoTo_Fail( app, goto_command ): - RunTest( app, { - 'description': goto_command + ' fails on non-existing method', - 'request': { - 'command': goto_command, - 'line_num': 35, - 'column_num': 6, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'Could not find definition.' ) - } - } ) + @SharedYcmd + def test_Subcommands_GoToReferences_Unicode( self, app ): + RunTest( app, { + 'description': 'GoToReferences works with Unicode characters', + 'request': { + 'command': 'GoToReferences', + 'line_num': 14, + 'column_num': 3, + 'filepath': PathToTestFile( 'unicode.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': contains_inanyorder( + has_entries( { 'description': ' å: number;', + 'line_num' : 14, + 'column_num' : 3, + 'filepath' : PathToTestFile( 'unicode.ts' ) } ), + has_entries( { 'description': 'var baz = new Bår(); baz.å;', + 'line_num' : 20, + 'column_num' : 27, + 'filepath' : PathToTestFile( 'unicode.ts' ) } ), + has_entries( { 'description': 'baz.å;', + 'line_num' : 23, + 'column_num' : 5, + 'filepath' : PathToTestFile( 'unicode.ts' ) } ), + has_entries( { 'description': 'føø_long_long.å;', + 'line_num' : 27, + 'column_num' : 17, + 'filepath' : PathToTestFile( 'unicode.ts' ) } ) + ) + } + } ) -@pytest.mark.parametrize( 'command', [ 'GoTo', - 'GoToDefinition', - 'GoToDeclaration' ] ) -@SharedYcmd -def Subcommands_GoTo_Fail_test( app, command ): - Subcommands_GoTo_Fail( app, command ) + @SharedYcmd + def test_Subcommands_GoToSymbol( self, app ): + for req, rep in [ + ( ( 'signatures.ts', 1, 1, 'no_arguments_no_return' ), + ( 'signatures.ts', 3, 1, 'no_arguments_no_return' ) ), + + ( ( 'signatures.ts', 1, 1, 'ReturnValue' ), + [ ( 'signatures.ts', 6, 1, 'ReturnValue' ) ] ), + + ( ( 'signatures.ts', 1, 1, 'Foo' ), + [ ( 'test.ts', 14, 5, 'foo' ), + ( 'test.ts', 2, 1, 'Foo' ) ] ), + + ( ( 'signatures.ts', 1, 1, 'nothinghere' ), 'Symbol not found' ) + ]: + with self.subTest( req = req, rep = rep ): + if isinstance( rep, tuple ): + expect = { + 'response': requests.codes.ok, + 'data': LocationMatcher( PathToTestFile( rep[ 0 ] ), *rep[ 1: ] ) + } + elif isinstance( rep, list ): + # NOTE: We use has_items here because tsserver will include results + # from node_modules and all sorts of other random places. + expect = { + 'response': requests.codes.ok, + 'data': has_items( *[ + LocationMatcher( PathToTestFile( r[ 0 ] ), *r[ 1: ] ) + for r in rep + ] ) + } + else: + expect = { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, rep ) + } + + RunTest( app, { + 'request': { + 'command': 'GoToSymbol', + 'arguments': [ req[ 3 ] ], + 'line_num': req[ 1 ], + 'column_num': req[ 2 ], + 'filepath': PathToTestFile( req[ 0 ] ), + }, + 'expect': expect + } ) + + + @SharedYcmd + def test_Subcommands_GoTo_Basic( self, app ): + for command in [ 'GoTo', 'GoToDefinition', 'GoToDeclaration' ]: + with self.subTest( command = command ): + Subcommands_GoTo_Basic( app, command ) + + + @SharedYcmd + def test_Subcommands_GoTo_Unicode( self, app ): + for command in [ 'GoTo', 'GoToDefinition', 'GoToDeclaration' ]: + with self.subTest( command = command ): + Subcommands_GoTo_Unicode( app, command ) + + + @SharedYcmd + def test_Subcommands_GoTo_Fail( self, app ): + for command in [ 'GoTo', 'GoToDefinition', 'GoToDeclaration' ]: + with self.subTest( command = command ): + Subcommands_GoTo_Fail( app, command ) + + + @SharedYcmd + def test_Subcommands_GoToType( self, app ): + RunTest( app, { + 'description': 'GoToType works', + 'request': { + 'command': 'GoToType', + 'line_num': 14, + 'column_num': 6, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': LocationMatcher( PathToTestFile( 'test.ts' ), 2, 7 ) + } + } ) -@SharedYcmd -def Subcommands_GoToType_test( app ): - RunTest( app, { - 'description': 'GoToType works', - 'request': { - 'command': 'GoToType', - 'line_num': 14, - 'column_num': 6, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': LocationMatcher( PathToTestFile( 'test.ts' ), 2, 7 ) - } - } ) + @SharedYcmd + def test_Subcommands_GoToType_Fail( self, app ): + RunTest( app, { + 'description': 'GoToType fails outside the buffer', + 'request': { + 'command': 'GoToType', + 'line_num': 39, + 'column_num': 8, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, 'Could not find type definition.' ) + } + } ) -@SharedYcmd -def Subcommands_GoToType_Fail_test( app ): - RunTest( app, { - 'description': 'GoToType fails outside the buffer', - 'request': { - 'command': 'GoToType', - 'line_num': 39, - 'column_num': 8, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, 'Could not find type definition.' ) - } - } ) + @SharedYcmd + def test_Subcommands_FixIt( self, app ): + RunTest( app, { + 'description': 'FixIt works on a non-existing method', + 'request': { + 'command': 'FixIt', + 'line_num': 35, + 'column_num': 12, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_inanyorder( + has_entries( { + 'text': "Declare method 'nonExistingMethod'", + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( + '^\r?\n' + ' nonExistingMethod\\(\\) {\r?\n' + ' throw new Error\\("Method not implemented."\\);\r?\n' + ' }$', + ), + LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ), + LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ) ) + ), + 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 35, 12 ) + } ), + has_entries( { + 'text': "Declare property 'nonExistingMethod'", + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( '^\r?\n' + ' nonExistingMethod: any;$' ), + LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ), + LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ) ) + ), + 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 35, 12 ) + } ), + has_entries( { + 'text': "Add index signature for property 'nonExistingMethod'", + 'chunks': contains_exactly( + ChunkMatcher( + matches_regexp( '^\r?\n' + ' \\[x: string\\]: any;$' ), + LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ), + LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ) ) + ), + 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 35, 12 ) + } ) + ) + } ) + } + } ) -@SharedYcmd -def Subcommands_FixIt_test( app ): - RunTest( app, { - 'description': 'FixIt works on a non-existing method', - 'request': { - 'command': 'FixIt', - 'line_num': 35, - 'column_num': 12, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_inanyorder( - has_entries( { - 'text': "Declare method 'nonExistingMethod'", + @SharedYcmd + def test_Subcommands_OrganizeImports( self, app ): + filepath = PathToTestFile( 'imports.ts' ) + RunTest( app, { + 'description': 'OrganizeImports removes unused imports, ' + 'coalesces imports from the same module, and sorts them', + 'request': { + 'command': 'OrganizeImports', + 'filepath': filepath, + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { 'chunks': contains_exactly( ChunkMatcher( matches_regexp( - '^\r?\n' - ' nonExistingMethod\\(\\) {\r?\n' - ' throw new Error\\("Method not implemented."\\);\r?\n' - ' }$', - ), - LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ), - LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ) ) - ), - 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 35, 12 ) - } ), - has_entries( { - 'text': "Declare property 'nonExistingMethod'", - 'chunks': contains_exactly( + 'import \\* as lib from "library";\r?\n' + 'import func, { func1, func2 } from "library";\r?\n' ), + LocationMatcher( filepath, 1, 1 ), + LocationMatcher( filepath, 2, 1 ) ), ChunkMatcher( - matches_regexp( '^\r?\n' - ' nonExistingMethod: any;$' ), - LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ), - LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ) ) - ), - 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 35, 12 ) - } ), - has_entries( { - 'text': "Add index signature for property 'nonExistingMethod'", - 'chunks': contains_exactly( + '', + LocationMatcher( filepath, 5, 1 ), + LocationMatcher( filepath, 6, 1 ) ), ChunkMatcher( - matches_regexp( '^\r?\n' - ' \\[x: string\\]: any;$' ), - LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ), - LocationMatcher( PathToTestFile( 'test.ts' ), 25, 12 ) ) - ), - 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 35, 12 ) - } ) - ) - } ) - } - } ) - - -@SharedYcmd -def Subcommands_OrganizeImports_test( app ): - filepath = PathToTestFile( 'imports.ts' ) - RunTest( app, { - 'description': 'OrganizeImports removes unused imports, ' - 'coalesces imports from the same module, and sorts them', - 'request': { - 'command': 'OrganizeImports', - 'filepath': filepath, - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_exactly( - ChunkMatcher( - matches_regexp( - 'import \\* as lib from "library";\r?\n' - 'import func, { func1, func2 } from "library";\r?\n' ), - LocationMatcher( filepath, 1, 1 ), - LocationMatcher( filepath, 2, 1 ) ), - ChunkMatcher( - '', - LocationMatcher( filepath, 5, 1 ), - LocationMatcher( filepath, 6, 1 ) ), - ChunkMatcher( - '', - LocationMatcher( filepath, 9, 1 ), - LocationMatcher( filepath, 10, 1 ) ), - ) - } ) ) - } ) - } - } ) + '', + LocationMatcher( filepath, 9, 1 ), + LocationMatcher( filepath, 10, 1 ) ), + ) + } ) ) + } ) + } + } ) -@SharedYcmd -def Subcommands_RefactorRename_Missing_test( app ): - RunTest( app, { - 'description': 'RefactorRename requires a parameter', - 'request': { - 'command': 'RefactorRename', - 'line_num': 30, - 'column_num': 6, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( ValueError, - 'Please specify a new name to rename it to.\n' - 'Usage: RefactorRename ' ) - } - } ) + @SharedYcmd + def test_Subcommands_RefactorRename_Missing( self, app ): + RunTest( app, { + 'description': 'RefactorRename requires a parameter', + 'request': { + 'command': 'RefactorRename', + 'line_num': 30, + 'column_num': 6, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( ValueError, + 'Please specify a new name to rename it to.\n' + 'Usage: RefactorRename ' ) + } + } ) -@SharedYcmd -def Subcommands_RefactorRename_NotPossible_test( app ): - RunTest( app, { - 'description': 'RefactorRename cannot rename a non-existing method', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'whatever' ], - 'line_num': 35, - 'column_num': 5, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.internal_server_error, - 'data': ErrorMatcher( RuntimeError, - 'Value cannot be renamed: ' - 'You cannot rename this element.' ) - } - } ) + @SharedYcmd + def test_Subcommands_RefactorRename_NotPossible( self, app ): + RunTest( app, { + 'description': 'RefactorRename cannot rename a non-existing method', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'whatever' ], + 'line_num': 35, + 'column_num': 5, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.internal_server_error, + 'data': ErrorMatcher( RuntimeError, + 'Value cannot be renamed: ' + 'You cannot rename this element.' ) + } + } ) -@SharedYcmd -def Subcommands_RefactorRename_Simple_test( app ): - RunTest( app, { - 'description': 'RefactorRename works on a class name', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'test' ], - 'line_num': 2, - 'column_num': 8, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_inanyorder( - ChunkMatcher( - 'test', - LocationMatcher( PathToTestFile( 'test.ts' ), 14, 15 ), - LocationMatcher( PathToTestFile( 'test.ts' ), 14, 18 ) ), - ChunkMatcher( - 'test', - LocationMatcher( PathToTestFile( 'test.ts' ), 2, 7 ), - LocationMatcher( PathToTestFile( 'test.ts' ), 2, 10 ) ), - ), - 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 2, 8 ) - } ) ) - } ) - } - } ) + @SharedYcmd + def test_Subcommands_RefactorRename_Simple( self, app ): + RunTest( app, { + 'description': 'RefactorRename works on a class name', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'test' ], + 'line_num': 2, + 'column_num': 8, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_inanyorder( + ChunkMatcher( + 'test', + LocationMatcher( PathToTestFile( 'test.ts' ), 14, 15 ), + LocationMatcher( PathToTestFile( 'test.ts' ), 14, 18 ) ), + ChunkMatcher( + 'test', + LocationMatcher( PathToTestFile( 'test.ts' ), 2, 7 ), + LocationMatcher( PathToTestFile( 'test.ts' ), 2, 10 ) ), + ), + 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 2, 8 ) + } ) ) + } ) + } + } ) -@SharedYcmd -def Subcommands_RefactorRename_MultipleFiles_test( app ): - RunTest( app, { - 'description': 'RefactorRename works across files', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'this-is-a-longer-string' ], - 'line_num': 25, - 'column_num': 9, - 'filepath': PathToTestFile( 'test.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_inanyorder( - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'test.ts' ), 25, 7 ), - LocationMatcher( PathToTestFile( 'test.ts' ), 25, 10 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'test.ts' ), 33, 15 ), - LocationMatcher( PathToTestFile( 'test.ts' ), 33, 18 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'test.ts' ), 37, 1 ), - LocationMatcher( PathToTestFile( 'test.ts' ), 37, 4 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'file2.ts' ), 1, 5 ), - LocationMatcher( PathToTestFile( 'file2.ts' ), 1, 8 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'file3.ts' ), 1, 15 ), - LocationMatcher( PathToTestFile( 'file3.ts' ), 1, 18 ) ), - ChunkMatcher( - 'this-is-a-longer-string', - LocationMatcher( PathToTestFile( 'test.tsx' ), 10, 8 ), - LocationMatcher( PathToTestFile( 'test.tsx' ), 10, 11 ) ), - ), - 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 25, 9 ) - } ) ) - } ) - } - } ) + @SharedYcmd + def test_Subcommands_RefactorRename_MultipleFiles( self, app ): + RunTest( app, { + 'description': 'RefactorRename works across files', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'this-is-a-longer-string' ], + 'line_num': 25, + 'column_num': 9, + 'filepath': PathToTestFile( 'test.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_inanyorder( + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'test.ts' ), 25, 7 ), + LocationMatcher( PathToTestFile( 'test.ts' ), 25, 10 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'test.ts' ), 33, 15 ), + LocationMatcher( PathToTestFile( 'test.ts' ), 33, 18 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'test.ts' ), 37, 1 ), + LocationMatcher( PathToTestFile( 'test.ts' ), 37, 4 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'file2.ts' ), 1, 5 ), + LocationMatcher( PathToTestFile( 'file2.ts' ), 1, 8 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'file3.ts' ), 1, 15 ), + LocationMatcher( PathToTestFile( 'file3.ts' ), 1, 18 ) ), + ChunkMatcher( + 'this-is-a-longer-string', + LocationMatcher( PathToTestFile( 'test.tsx' ), 10, 8 ), + LocationMatcher( PathToTestFile( 'test.tsx' ), 10, 11 ) ), + ), + 'location': LocationMatcher( PathToTestFile( 'test.ts' ), 25, 9 ) + } ) ) + } ) + } + } ) -@SharedYcmd -def Subcommands_RefactorRename_SimpleUnicode_test( app ): - RunTest( app, { - 'description': 'RefactorRename works with Unicode characters', - 'request': { - 'command': 'RefactorRename', - 'arguments': [ 'ø' ], - 'line_num': 14, - 'column_num': 3, - 'filepath': PathToTestFile( 'unicode.ts' ), - }, - 'expect': { - 'response': requests.codes.ok, - 'data': has_entries( { - 'fixits': contains_exactly( has_entries( { - 'chunks': contains_inanyorder( - ChunkMatcher( - 'ø', - LocationMatcher( PathToTestFile( 'unicode.ts' ), 14, 3 ), - LocationMatcher( PathToTestFile( 'unicode.ts' ), 14, 5 ) ), - ChunkMatcher( - 'ø', - LocationMatcher( PathToTestFile( 'unicode.ts' ), 20, 27 ), - LocationMatcher( PathToTestFile( 'unicode.ts' ), 20, 29 ) ), - ChunkMatcher( - 'ø', - LocationMatcher( PathToTestFile( 'unicode.ts' ), 23, 5 ), - LocationMatcher( PathToTestFile( 'unicode.ts' ), 23, 7 ) ), - ChunkMatcher( - 'ø', - LocationMatcher( PathToTestFile( 'unicode.ts' ), 27, 17 ), - LocationMatcher( PathToTestFile( 'unicode.ts' ), 27, 19 ) ), - ), - 'location': LocationMatcher( PathToTestFile( 'unicode.ts' ), 14, 3 ) - } ) ) - } ) - } - } ) + @SharedYcmd + def test_Subcommands_RefactorRename_SimpleUnicode( self, app ): + RunTest( app, { + 'description': 'RefactorRename works with Unicode characters', + 'request': { + 'command': 'RefactorRename', + 'arguments': [ 'ø' ], + 'line_num': 14, + 'column_num': 3, + 'filepath': PathToTestFile( 'unicode.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'fixits': contains_exactly( has_entries( { + 'chunks': contains_inanyorder( + ChunkMatcher( + 'ø', + LocationMatcher( PathToTestFile( 'unicode.ts' ), 14, 3 ), + LocationMatcher( PathToTestFile( 'unicode.ts' ), 14, 5 ) ), + ChunkMatcher( + 'ø', + LocationMatcher( PathToTestFile( 'unicode.ts' ), 20, 27 ), + LocationMatcher( PathToTestFile( 'unicode.ts' ), 20, 29 ) ), + ChunkMatcher( + 'ø', + LocationMatcher( PathToTestFile( 'unicode.ts' ), 23, 5 ), + LocationMatcher( PathToTestFile( 'unicode.ts' ), 23, 7 ) ), + ChunkMatcher( + 'ø', + LocationMatcher( PathToTestFile( 'unicode.ts' ), 27, 17 ), + LocationMatcher( PathToTestFile( 'unicode.ts' ), 27, 19 ) ), + ), + 'location': LocationMatcher( PathToTestFile( 'unicode.ts' ), 14, 3 ) + } ) ) + } ) + } + } ) -@IsolatedYcmd() -@patch( 'ycmd.utils.WaitUntilProcessIsTerminated', - MockProcessTerminationTimingOut ) -def Subcommands_StopServer_Timeout_test( app ): - WaitUntilCompleterServerReady( app, 'typescript' ) + @IsolatedYcmd() + @patch( 'ycmd.utils.WaitUntilProcessIsTerminated', + MockProcessTerminationTimingOut ) + def test_Subcommands_StopServer_Timeout( self, app ): + WaitUntilCompleterServerReady( app, 'typescript' ) - app.post_json( - '/run_completer_command', - BuildRequest( - filetype = 'typescript', - command_arguments = [ 'StopServer' ] + app.post_json( + '/run_completer_command', + BuildRequest( + filetype = 'typescript', + command_arguments = [ 'StopServer' ] + ) ) - ) - request_data = BuildRequest( filetype = 'typescript' ) - assert_that( app.post_json( '/debug_info', request_data ).json, - has_entry( - 'completer', - has_entry( 'servers', contains_exactly( - has_entry( 'is_running', False ) + request_data = BuildRequest( filetype = 'typescript' ) + assert_that( app.post_json( '/debug_info', request_data ).json, + has_entry( + 'completer', + has_entry( 'servers', contains_exactly( + has_entry( 'is_running', False ) + ) ) ) ) - ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True diff --git a/ycmd/tests/typescript/typescript_completer_test.py b/ycmd/tests/typescript/typescript_completer_test.py index 07b43f76b2..cb015027bc 100644 --- a/ycmd/tests/typescript/typescript_completer_test.py +++ b/ycmd/tests/typescript/typescript_completer_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2020 ycmd contributors +# Copyright (C) 2017-2021 ycmd contributors # # This file is part of ycmd. # @@ -16,32 +16,30 @@ # along with ycmd. If not, see . from unittest.mock import patch +from unittest import TestCase from hamcrest import assert_that, equal_to from ycmd import user_options_store from ycmd.completers.typescript.typescript_completer import ( ShouldEnableTypeScriptCompleter, FindTSServer ) +from ycmd.tests.typescript import setUpModule, tearDownModule # noqa -def ShouldEnableTypeScriptCompleter_NodeAndTsserverFound_test(): - user_options = user_options_store.GetAll() - assert_that( ShouldEnableTypeScriptCompleter( user_options ) ) +class TypescriptCompleterTest( TestCase ): + def test_ShouldEnableTypeScriptCompleter_NodeAndTsserverFound( self ): + user_options = user_options_store.GetAll() + assert_that( ShouldEnableTypeScriptCompleter( user_options ) ) -@patch( 'ycmd.utils.FindExecutable', return_value = None ) -def ShouldEnableTypeScriptCompleter_TsserverNotFound_test( *args ): - user_options = user_options_store.GetAll() - assert_that( not ShouldEnableTypeScriptCompleter( user_options ) ) + @patch( 'ycmd.utils.FindExecutable', return_value = None ) + def test_ShouldEnableTypeScriptCompleter_TsserverNotFound( self, *args ): + user_options = user_options_store.GetAll() + assert_that( not ShouldEnableTypeScriptCompleter( user_options ) ) -@patch( 'ycmd.utils.FindExecutableWithFallback', - wraps = lambda x, fb: x if x == 'tsserver' else None ) -@patch( 'os.path.isfile', return_value = True ) -def FindTSServer_CustomTsserverPath_test( *args ): - assert_that( 'tsserver', equal_to( FindTSServer( 'tsserver' ) ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + @patch( 'ycmd.utils.FindExecutableWithFallback', + wraps = lambda x, fb: x if x == 'tsserver' else None ) + @patch( 'os.path.isfile', return_value = True ) + def test_FindTSServer_CustomTsserverPath( self, *args ): + assert_that( 'tsserver', equal_to( FindTSServer( 'tsserver' ) ) ) diff --git a/ycmd/tests/utils_test.py b/ycmd/tests/utils_test.py index 0a63c6079b..2cc221d0de 100644 --- a/ycmd/tests/utils_test.py +++ b/ycmd/tests/utils_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016-2020 ycmd contributors. +# Copyright (C) 2016-2021 ycmd contributors. # # This file is part of ycmd. # @@ -16,7 +16,6 @@ # along with ycmd. If not, see . import os -import pytest import subprocess import sys import tempfile @@ -32,6 +31,7 @@ raises ) from unittest.mock import patch, call from types import ModuleType +from unittest import TestCase from ycmd import utils from ycmd.tests.test_utils import ( WindowsOnly, UnixOnly, CurrentWorkingDirectory, @@ -43,517 +43,528 @@ # changing things here, read the comments in utils.ToBytes. -def ToBytes_Bytes_test(): - value = utils.ToBytes( bytes( b'abc' ) ) - assert_that( value, equal_to( bytes( b'abc' ) ) ) - assert_that( type( value ), equal_to( bytes ) ) +@patch( 'ycmd.utils.LOGGER', autospec = True ) +def RunImportAndCheckCoreException( test, logger ): + with patch( 'ycmd.utils.ImportCore', + side_effect = ImportError( test[ 'exception_message' ] ) ): + assert_that( ImportAndCheckCore(), equal_to( test[ 'exit_status' ] ) ) + assert_that( logger.method_calls, has_length( 1 ) ) + logger.exception.assert_called_with( test[ 'logged_message' ] ) -def ToBytes_Str_test(): - value = utils.ToBytes( u'abc' ) - assert_that( value, equal_to( bytes( b'abc' ) ) ) - assert_that( type( value ), equal_to( bytes ) ) +class UtilsTest( TestCase ): + def test_ToBytes_Bytes( self ): + value = utils.ToBytes( bytes( b'abc' ) ) + assert_that( value, equal_to( bytes( b'abc' ) ) ) + assert_that( type( value ), equal_to( bytes ) ) -def ToBytes_Int_test(): - value = utils.ToBytes( 123 ) - assert_that( value, equal_to( bytes( b'123' ) ) ) - assert_that( type( value ), equal_to( bytes ) ) + def test_ToBytes_Str( self ): + value = utils.ToBytes( u'abc' ) + assert_that( value, equal_to( bytes( b'abc' ) ) ) + assert_that( type( value ), equal_to( bytes ) ) -def ToBytes_None_test(): - value = utils.ToBytes( None ) - assert_that( value, equal_to( bytes( b'' ) ) ) - assert_that( type( value ), equal_to( bytes ) ) + def test_ToBytes_Int( self ): + value = utils.ToBytes( 123 ) + assert_that( value, equal_to( bytes( b'123' ) ) ) + assert_that( type( value ), equal_to( bytes ) ) -def ToUnicode_Bytes_test(): - value = utils.ToUnicode( bytes( b'abc' ) ) - assert_that( value, equal_to( u'abc' ) ) - assert_that( isinstance( value, str ) ) + def test_ToBytes_None( self ): + value = utils.ToBytes( None ) + assert_that( value, equal_to( bytes( b'' ) ) ) + assert_that( type( value ), equal_to( bytes ) ) -def ToUnicode_Str_test(): - value = utils.ToUnicode( u'abc' ) - assert_that( value, equal_to( u'abc' ) ) - assert_that( isinstance( value, str ) ) + def test_ToUnicode_Bytes( self ): + value = utils.ToUnicode( bytes( b'abc' ) ) + assert_that( value, equal_to( u'abc' ) ) + assert_that( isinstance( value, str ) ) -def ToUnicode_Int_test(): - value = utils.ToUnicode( 123 ) - assert_that( value, equal_to( u'123' ) ) - assert_that( isinstance( value, str ) ) + def test_ToUnicode_Str( self ): + value = utils.ToUnicode( u'abc' ) + assert_that( value, equal_to( u'abc' ) ) + assert_that( isinstance( value, str ) ) -def ToUnicode_None_test(): - value = utils.ToUnicode( None ) - assert_that( value, equal_to( u'' ) ) - assert_that( isinstance( value, str ) ) + def test_ToUnicode_Int( self ): + value = utils.ToUnicode( 123 ) + assert_that( value, equal_to( u'123' ) ) + assert_that( isinstance( value, str ) ) -def JoinLinesAsUnicode_Bytes_test(): - value = utils.JoinLinesAsUnicode( [ bytes( b'abc' ), bytes( b'xyz' ) ] ) - assert_that( value, equal_to( u'abc\nxyz' ) ) - assert_that( isinstance( value, str ) ) + def test_ToUnicode_None( self ): + value = utils.ToUnicode( None ) + assert_that( value, equal_to( u'' ) ) + assert_that( isinstance( value, str ) ) -def JoinLinesAsUnicode_Str_test(): - value = utils.JoinLinesAsUnicode( [ u'abc', u'xyz' ] ) - assert_that( value, equal_to( u'abc\nxyz' ) ) - assert_that( isinstance( value, str ) ) + def test_JoinLinesAsUnicode_Bytes( self ): + value = utils.JoinLinesAsUnicode( [ bytes( b'abc' ), bytes( b'xyz' ) ] ) + assert_that( value, equal_to( u'abc\nxyz' ) ) + assert_that( isinstance( value, str ) ) -def JoinLinesAsUnicode_EmptyList_test(): - value = utils.JoinLinesAsUnicode( [] ) - assert_that( value, equal_to( u'' ) ) - assert_that( isinstance( value, str ) ) + def test_JoinLinesAsUnicode_Str( self ): + value = utils.JoinLinesAsUnicode( [ u'abc', u'xyz' ] ) + assert_that( value, equal_to( u'abc\nxyz' ) ) + assert_that( isinstance( value, str ) ) -def JoinLinesAsUnicode_BadInput_test(): - assert_that( - calling( utils.JoinLinesAsUnicode ).with_args( [ 42 ] ), - raises( ValueError, 'lines must contain either strings or bytes' ) - ) + def test_JoinLinesAsUnicode_EmptyList( self ): + value = utils.JoinLinesAsUnicode( [] ) + assert_that( value, equal_to( u'' ) ) + assert_that( isinstance( value, str ) ) -def RemoveIfExists_Exists_test(): - tempfile = PathToTestFile( 'remove-if-exists' ) - open( tempfile, 'a' ).close() - assert_that( os.path.exists( tempfile ) ) - utils.RemoveIfExists( tempfile ) - assert_that( not os.path.exists( tempfile ) ) + def test_JoinLinesAsUnicode_BadInput( self ): + assert_that( + calling( utils.JoinLinesAsUnicode ).with_args( [ 42 ] ), + raises( ValueError, 'lines must contain either strings or bytes' ) + ) -def RemoveIfExists_DoesntExist_test(): - tempfile = PathToTestFile( 'remove-if-exists' ) - assert_that( not os.path.exists( tempfile ) ) - utils.RemoveIfExists( tempfile ) - assert_that( not os.path.exists( tempfile ) ) + def test_RemoveIfExists_Exists( self ): + tempfile = PathToTestFile( 'remove-if-exists' ) + open( tempfile, 'a' ).close() + assert_that( os.path.exists( tempfile ) ) + utils.RemoveIfExists( tempfile ) + assert_that( not os.path.exists( tempfile ) ) -def PathToFirstExistingExecutable_Basic_test(): - if utils.OnWindows(): - assert_that( utils.PathToFirstExistingExecutable( [ 'notepad.exe' ] ) ) - else: - assert_that( utils.PathToFirstExistingExecutable( [ 'cat' ] ) ) + def test_RemoveIfExists_DoesntExist( self ): + tempfile = PathToTestFile( 'remove-if-exists' ) + assert_that( not os.path.exists( tempfile ) ) + utils.RemoveIfExists( tempfile ) + assert_that( not os.path.exists( tempfile ) ) -def PathToFirstExistingExecutable_Failure_test(): - assert_that( not utils.PathToFirstExistingExecutable( [ 'ycmd-foobar' ] ) ) + def test_PathToFirstExistingExecutable_Basic( self ): + if utils.OnWindows(): + assert_that( utils.PathToFirstExistingExecutable( [ 'notepad.exe' ] ) ) + else: + assert_that( utils.PathToFirstExistingExecutable( [ 'cat' ] ) ) -@UnixOnly -@patch( 'subprocess.Popen' ) -def SafePopen_RemoveStdinWindows_test( *args ): - utils.SafePopen( [ 'foo' ], stdin_windows = 'bar' ) - assert_that( subprocess.Popen.call_args, equal_to( call( [ 'foo' ] ) ) ) + def test_PathToFirstExistingExecutable_Failure( self ): + assert_that( not utils.PathToFirstExistingExecutable( [ 'ycmd-foobar' ] ) ) -@WindowsOnly -@patch( 'subprocess.Popen' ) -def SafePopen_ReplaceStdinWindowsPIPEOnWindows_test( *args ): - utils.SafePopen( [ 'foo' ], stdin_windows = subprocess.PIPE ) - assert_that( subprocess.Popen.call_args, - equal_to( call( [ 'foo' ], - stdin = subprocess.PIPE, - creationflags = utils.CREATE_NO_WINDOW ) ) ) + @UnixOnly + @patch( 'subprocess.Popen' ) + def test_SafePopen_RemoveStdinWindows( self, *args ): + utils.SafePopen( [ 'foo' ], stdin_windows = 'bar' ) + assert_that( subprocess.Popen.call_args, equal_to( call( [ 'foo' ] ) ) ) -@WindowsOnly -@patch( 'subprocess.Popen' ) -def SafePopen_WindowsPath_test( *args ): - tempfile = PathToTestFile( 'safe-popen-file' ) - open( tempfile, 'a' ).close() - try: - utils.SafePopen( [ 'foo', tempfile ], stdin_windows = subprocess.PIPE ) + @WindowsOnly + @patch( 'subprocess.Popen' ) + def test_SafePopen_ReplaceStdinWindowsPIPEOnWindows( self, *args ): + utils.SafePopen( [ 'foo' ], stdin_windows = subprocess.PIPE ) assert_that( subprocess.Popen.call_args, - equal_to( call( [ 'foo', tempfile ], + equal_to( call( [ 'foo' ], stdin = subprocess.PIPE, creationflags = utils.CREATE_NO_WINDOW ) ) ) - finally: - os.remove( tempfile ) -def PathsToAllParentFolders_Basic_test(): - assert_that( utils.PathsToAllParentFolders( '/home/user/projects/test.c' ), - contains_exactly( - os.path.normpath( '/home/user/projects' ), - os.path.normpath( '/home/user' ), - os.path.normpath( '/home' ), - os.path.normpath( '/' ), + @WindowsOnly + @patch( 'subprocess.Popen' ) + def test_SafePopen_WindowsPath( self, *args ): + tempfile = PathToTestFile( 'safe-popen-file' ) + open( tempfile, 'a' ).close() + + try: + utils.SafePopen( [ 'foo', tempfile ], stdin_windows = subprocess.PIPE ) + assert_that( subprocess.Popen.call_args, + equal_to( call( [ 'foo', tempfile ], + stdin = subprocess.PIPE, + creationflags = utils.CREATE_NO_WINDOW ) ) ) + finally: + os.remove( tempfile ) + + + def test_PathsToAllParentFolders_Basic( self ): + assert_that( utils.PathsToAllParentFolders( '/home/user/projects/test.c' ), + contains_exactly( + os.path.normpath( '/home/user/projects' ), + os.path.normpath( '/home/user' ), + os.path.normpath( '/home' ), + os.path.normpath( '/' ), + ) ) - ) -@patch( 'os.path.isdir', return_value = True ) -def PathsToAllParentFolders_IsDirectory_test( *args ): - assert_that( utils.PathsToAllParentFolders( '/home/user/projects' ), - contains_exactly( - os.path.normpath( '/home/user/projects' ), - os.path.normpath( '/home/user' ), - os.path.normpath( '/home' ), - os.path.normpath( '/' ) + @patch( 'os.path.isdir', return_value = True ) + def test_PathsToAllParentFolders_IsDirectory( self, *args ): + assert_that( utils.PathsToAllParentFolders( '/home/user/projects' ), + contains_exactly( + os.path.normpath( '/home/user/projects' ), + os.path.normpath( '/home/user' ), + os.path.normpath( '/home' ), + os.path.normpath( '/' ) + ) ) - ) -def PathsToAllParentFolders_FileAtRoot_test(): - assert_that( utils.PathsToAllParentFolders( '/test.c' ), - contains_exactly( os.path.normpath( '/' ) ) ) + def test_PathsToAllParentFolders_FileAtRoot( self ): + assert_that( utils.PathsToAllParentFolders( '/test.c' ), + contains_exactly( os.path.normpath( '/' ) ) ) -@WindowsOnly -def PathsToAllParentFolders_WindowsPath_test(): - assert_that( utils.PathsToAllParentFolders( r'C:\\foo\\goo\\zoo\\test.c' ), - contains_exactly( - os.path.normpath( r'C:\\foo\\goo\\zoo' ), - os.path.normpath( r'C:\\foo\\goo' ), - os.path.normpath( r'C:\\foo' ), - os.path.normpath( r'C:\\' ) + @WindowsOnly + def test_PathsToAllParentFolders_WindowsPath( self ): + assert_that( utils.PathsToAllParentFolders( r'C:\\foo\\goo\\zoo\\test.c' ), + contains_exactly( + os.path.normpath( r'C:\\foo\\goo\\zoo' ), + os.path.normpath( r'C:\\foo\\goo' ), + os.path.normpath( r'C:\\foo' ), + os.path.normpath( r'C:\\' ) + ) ) - ) - - -@pytest.mark.parametrize( 'path,expected', [ - ( '', ( '', '' ) ), - ( 'foo', ( 'foo', '' ) ), - ( 'foo/bar', ( 'foo', 'bar' ) ), - ( 'foo/bar/xyz', ( 'foo', 'bar/xyz' ) ), - ( 'foo/bar/xyz/', ( 'foo', 'bar/xyz' ) ), - ( '/', ( '/', '' ) ), - ( '/foo', ( '/', 'foo' ) ), - ( '/foo/bar', ( '/', 'foo/bar' ) ), - ( '/foo/bar/xyz', ( '/', 'foo/bar/xyz' ) ), - ( '/foo/bar/xyz/', ( '/', 'foo/bar/xyz' ) ) - ] ) -def PathLeftSplit_test( path, expected ): - assert_that( utils.PathLeftSplit( path ), equal_to( expected ) ) - - -@WindowsOnly -@pytest.mark.parametrize( 'path,expected', [ - ( 'foo\\bar', ( 'foo', 'bar' ) ), - ( 'foo\\bar\\xyz', ( 'foo', 'bar\\xyz' ) ), - ( 'foo\\bar\\xyz\\', ( 'foo', 'bar\\xyz' ) ), - ( 'C:\\', ( 'C:\\', '' ) ), - ( 'C:\\foo', ( 'C:\\', 'foo' ) ), - ( 'C:\\foo\\bar', ( 'C:\\', 'foo\\bar' ) ), - ( 'C:\\foo\\bar\\xyz', ( 'C:\\', 'foo\\bar\\xyz' ) ), - ( 'C:\\foo\\bar\\xyz\\', ( 'C:\\', 'foo\\bar\\xyz' ) ) - ] ) -def PathLeftSplit_Windows_test( path, expected ): - assert_that( utils.PathLeftSplit( path ), equal_to( expected ) ) - - -def OpenForStdHandle_PrintDoesntThrowException_test(): - try: - temp = PathToTestFile( 'open-for-std-handle' ) - with utils.OpenForStdHandle( temp ) as f: - print( 'foo', file = f ) - finally: - os.remove( temp ) - - -# Tuples of ( ( unicode_line_value, codepoint_offset ), expected_result ). -@pytest.mark.parametrize( 'test,expected', [ - # Simple ascii strings. - ( ( 'test', 1 ), 1 ), - ( ( 'test', 4 ), 4 ), - ( ( 'test', 5 ), 5 ), - - # Unicode char at beginning. - ( ( '†est', 1 ), 1 ), - ( ( '†est', 2 ), 4 ), - ( ( '†est', 4 ), 6 ), - ( ( '†est', 5 ), 7 ), - - # Unicode char at end. - ( ( 'tes†', 1 ), 1 ), - ( ( 'tes†', 2 ), 2 ), - ( ( 'tes†', 4 ), 4 ), - ( ( 'tes†', 5 ), 7 ), - - # Unicode char in middle. - ( ( 'tes†ing', 1 ), 1 ), - ( ( 'tes†ing', 2 ), 2 ), - ( ( 'tes†ing', 4 ), 4 ), - ( ( 'tes†ing', 5 ), 7 ), - ( ( 'tes†ing', 7 ), 9 ), - ( ( 'tes†ing', 8 ), 10 ), - - # Converts bytes to Unicode. - ( ( utils.ToBytes( '†est' ), 2 ), 4 ) - ] ) -def CodepointOffsetToByteOffset_test( test, expected ): - assert_that( utils.CodepointOffsetToByteOffset( *test ), - equal_to( expected ) ) - - -# Tuples of ( ( unicode_line_value, byte_offset ), expected_result ). -@pytest.mark.parametrize( 'test,expected', [ - # Simple ascii strings. - ( ( 'test', 1 ), 1 ), - ( ( 'test', 4 ), 4 ), - ( ( 'test', 5 ), 5 ), - - # Unicode char at beginning. - ( ( '†est', 1 ), 1 ), - ( ( '†est', 4 ), 2 ), - ( ( '†est', 6 ), 4 ), - ( ( '†est', 7 ), 5 ), - - # Unicode char at end. - ( ( 'tes†', 1 ), 1 ), - ( ( 'tes†', 2 ), 2 ), - ( ( 'tes†', 4 ), 4 ), - ( ( 'tes†', 7 ), 5 ), - - # Unicode char in middle. - ( ( 'tes†ing', 1 ), 1 ), - ( ( 'tes†ing', 2 ), 2 ), - ( ( 'tes†ing', 4 ), 4 ), - ( ( 'tes†ing', 7 ), 5 ), - ( ( 'tes†ing', 9 ), 7 ), - ( ( 'tes†ing', 10 ), 8 ), - ] ) -def ByteOffsetToCodepointOffset_test( test, expected ): - assert_that( utils.ByteOffsetToCodepointOffset( *test ), - equal_to( expected ) ) - - -@pytest.mark.parametrize( 'lines,expected', [ - ( '', [ '' ] ), - ( ' ', [ ' ' ] ), - ( '\n', [ '', '' ] ), - ( ' \n', [ ' ', '' ] ), - ( ' \n ', [ ' ', ' ' ] ), - ( 'test\n', [ 'test', '' ] ), - # Ignore \r on purpose. - ( '\r', [ '\r' ] ), - ( '\r ', [ '\r ' ] ), - ( 'test\r', [ 'test\r' ] ), - ( '\n\r', [ '', '\r' ] ), - ( '\r\n', [ '\r', '' ] ), - ( '\r\n\n', [ '\r', '', '' ] ), - ( 'test\ntesting', [ 'test', 'testing' ] ), - ( '\ntesting', [ '', 'testing' ] ), - # Do not split lines on \f and \v characters. - ( '\f\n\v', [ '\f', '\v' ] ) - ] ) -def SplitLines_test( lines, expected ): - assert_that( utils.SplitLines( lines ), expected ) - - -def FindExecutable_AbsolutePath_test(): - with TemporaryExecutable() as executable: - assert_that( executable, equal_to( utils.FindExecutable( executable ) ) ) - - -def FindExecutable_RelativePath_test(): - with TemporaryExecutable() as executable: - dirname, exename = os.path.split( executable ) - relative_executable = os.path.join( '.', exename ) - with CurrentWorkingDirectory( dirname ): - assert_that( relative_executable, - equal_to( utils.FindExecutable( relative_executable ) ) ) - - -@patch.dict( 'os.environ', { 'PATH': tempfile.gettempdir() } ) -def FindExecutable_ExecutableNameInPath_test(): - with TemporaryExecutable() as executable: - dirname, exename = os.path.split( executable ) - assert_that( executable, equal_to( utils.FindExecutable( exename ) ) ) - - -def FindExecutable_ReturnNoneIfFileIsNotExecutable_test(): - with tempfile.NamedTemporaryFile() as non_executable: - assert_that( None, equal_to( utils.FindExecutable( non_executable.name ) ) ) - - -@WindowsOnly -def FindExecutable_CurrentDirectory_test(): - with TemporaryExecutable() as executable: - dirname, exename = os.path.split( executable ) - with CurrentWorkingDirectory( dirname ): - assert_that( executable, equal_to( utils.FindExecutable( exename ) ) ) - - -@WindowsOnly -@patch.dict( 'os.environ', { 'PATHEXT': '.xyz' } ) -def FindExecutable_AdditionalPathExt_test(): - with TemporaryExecutable( extension = '.xyz' ) as executable: - assert_that( executable, equal_to( utils.FindExecutable( executable ) ) ) - - -def FindExecutableWithFallback_Empty_test(): - with TemporaryExecutable() as fallback: - assert_that( utils.FindExecutableWithFallback( '', fallback ), - equal_to( fallback ) ) - - -@patch( 'ycmd.utils.FindExecutable', return_value = None ) -def FindExecutableWithFallback_UserProvided_Invalid_test( find_executable ): - with TemporaryExecutable() as executable: - with TemporaryExecutable() as fallback: - assert_that( utils.FindExecutableWithFallback( executable, fallback ), - equal_to( None ) ) - - -def FindExecutableWithFallback_UserProvided_test(): - with TemporaryExecutable() as executable: - with TemporaryExecutable() as fallback: - assert_that( utils.FindExecutableWithFallback( executable, fallback ), - equal_to( executable ) ) -@patch( 'ycmd.utils.ProcessIsRunning', return_value = True ) -def WaitUntilProcessIsTerminated_TimedOut_test( *args ): - assert_that( - calling( utils.WaitUntilProcessIsTerminated ).with_args( None, - timeout = 0 ), - raises( RuntimeError, - 'Waited process to terminate for 0 seconds, aborting.' ) - ) + def test_PathLeftSplit( self ): + test_cases = [ + ( '', ( '', '' ) ), + ( 'foo', ( 'foo', '' ) ), + ( 'foo/bar', ( 'foo', 'bar' ) ), + ( 'foo/bar/xyz', ( 'foo', 'bar/xyz' ) ), + ( 'foo/bar/xyz/', ( 'foo', 'bar/xyz' ) ), + ( '/', ( '/', '' ) ), + ( '/foo', ( '/', 'foo' ) ), + ( '/foo/bar', ( '/', 'foo/bar' ) ), + ( '/foo/bar/xyz', ( '/', 'foo/bar/xyz' ) ), + ( '/foo/bar/xyz/', ( '/', 'foo/bar/xyz' ) ) + ] + for path, expected in test_cases: + with self.subTest( path = path, expected = expected ): + assert_that( utils.PathLeftSplit( path ), equal_to( expected ) ) + + + @WindowsOnly + def test_PathLeftSplit_Windows( self ): + test_cases = [ + ( 'foo\\bar', ( 'foo', 'bar' ) ), + ( 'foo\\bar\\xyz', ( 'foo', 'bar\\xyz' ) ), + ( 'foo\\bar\\xyz\\', ( 'foo', 'bar\\xyz' ) ), + ( 'C:\\', ( 'C:\\', '' ) ), + ( 'C:\\foo', ( 'C:\\', 'foo' ) ), + ( 'C:\\foo\\bar', ( 'C:\\', 'foo\\bar' ) ), + ( 'C:\\foo\\bar\\xyz', ( 'C:\\', 'foo\\bar\\xyz' ) ), + ( 'C:\\foo\\bar\\xyz\\', ( 'C:\\', 'foo\\bar\\xyz' ) ) + ] + for path, expected in test_cases: + with self.subTest( path = path, expected = expected ): + assert_that( utils.PathLeftSplit( path ), equal_to( expected ) ) + + + def test_OpenForStdHandle_PrintDoesntThrowException( self ): + try: + temp = PathToTestFile( 'open-for-std-handle' ) + with utils.OpenForStdHandle( temp ) as f: + print( 'foo', file = f ) + finally: + os.remove( temp ) + + + def test_CodepointOffsetToByteOffset( self ): + # Tuples of ( ( unicode_line_value, codepoint_offset ), expected_result ). + test_cases = [ + # Simple ascii strings. + ( ( 'test', 1 ), 1 ), + ( ( 'test', 4 ), 4 ), + ( ( 'test', 5 ), 5 ), + + # Unicode char at beginning. + ( ( '†est', 1 ), 1 ), + ( ( '†est', 2 ), 4 ), + ( ( '†est', 4 ), 6 ), + ( ( '†est', 5 ), 7 ), + + # Unicode char at end. + ( ( 'tes†', 1 ), 1 ), + ( ( 'tes†', 2 ), 2 ), + ( ( 'tes†', 4 ), 4 ), + ( ( 'tes†', 5 ), 7 ), + + # Unicode char in middle. + ( ( 'tes†ing', 1 ), 1 ), + ( ( 'tes†ing', 2 ), 2 ), + ( ( 'tes†ing', 4 ), 4 ), + ( ( 'tes†ing', 5 ), 7 ), + ( ( 'tes†ing', 7 ), 9 ), + ( ( 'tes†ing', 8 ), 10 ), + + # Converts bytes to Unicode. + ( ( utils.ToBytes( '†est' ), 2 ), 4 ) + ] + for test, expected in test_cases: + with self.subTest( test = test, expected = expected ): + assert_that( utils.CodepointOffsetToByteOffset( *test ), + equal_to( expected ) ) + + + def test_ByteOffsetToCodepointOffset( self ): + # Tuples of ( ( unicode_line_value, byte_offset ), expected_result ). + test_cases = [ + # Simple ascii strings. + ( ( 'test', 1 ), 1 ), + ( ( 'test', 4 ), 4 ), + ( ( 'test', 5 ), 5 ), + + # Unicode char at beginning. + ( ( '†est', 1 ), 1 ), + ( ( '†est', 4 ), 2 ), + ( ( '†est', 6 ), 4 ), + ( ( '†est', 7 ), 5 ), + + # Unicode char at end. + ( ( 'tes†', 1 ), 1 ), + ( ( 'tes†', 2 ), 2 ), + ( ( 'tes†', 4 ), 4 ), + ( ( 'tes†', 7 ), 5 ), + + # Unicode char in middle. + ( ( 'tes†ing', 1 ), 1 ), + ( ( 'tes†ing', 2 ), 2 ), + ( ( 'tes†ing', 4 ), 4 ), + ( ( 'tes†ing', 7 ), 5 ), + ( ( 'tes†ing', 9 ), 7 ), + ( ( 'tes†ing', 10 ), 8 ), + ] + for test, expected in test_cases: + with self.subTest( test = test, expected = expected ): + assert_that( utils.ByteOffsetToCodepointOffset( *test ), + equal_to( expected ) ) + + + def test_SplitLines( self ): + test_cases = [ + ( '', [ '' ] ), + ( ' ', [ ' ' ] ), + ( '\n', [ '', '' ] ), + ( ' \n', [ ' ', '' ] ), + ( ' \n ', [ ' ', ' ' ] ), + ( 'test\n', [ 'test', '' ] ), + # Ignore \r on purpose. + ( '\r', [ '\r' ] ), + ( '\r ', [ '\r ' ] ), + ( 'test\r', [ 'test\r' ] ), + ( '\n\r', [ '', '\r' ] ), + ( '\r\n', [ '\r', '' ] ), + ( '\r\n\n', [ '\r', '', '' ] ), + ( 'test\ntesting', [ 'test', 'testing' ] ), + ( '\ntesting', [ '', 'testing' ] ), + # Do not split lines on \f and \v characters. + ( '\f\n\v', [ '\f', '\v' ] ) + ] + for lines, expected in test_cases: + with self.subTest( lines = lines, expected = expected ): + assert_that( utils.SplitLines( lines ), expected ) + + + def test_FindExecutable_AbsolutePath( self ): + with TemporaryExecutable() as executable: + assert_that( executable, equal_to( utils.FindExecutable( executable ) ) ) + + + def test_FindExecutable_RelativePath( self ): + with TemporaryExecutable() as executable: + dirname, exename = os.path.split( executable ) + relative_executable = os.path.join( '.', exename ) + with CurrentWorkingDirectory( dirname ): + assert_that( relative_executable, + equal_to( utils.FindExecutable( relative_executable ) ) ) + + + @patch.dict( 'os.environ', { 'PATH': tempfile.gettempdir() } ) + def test_FindExecutable_ExecutableNameInPath( self ): + with TemporaryExecutable() as executable: + dirname, exename = os.path.split( executable ) + assert_that( executable, equal_to( utils.FindExecutable( exename ) ) ) -def LoadPythonSource_UnicodePath_test(): - filename = PathToTestFile( u'uni¢od€.py' ) - module = utils.LoadPythonSource( 'module_name', filename ) - assert_that( module, instance_of( ModuleType ) ) - assert_that( module.__file__, equal_to( filename ) ) - assert_that( module.__name__, equal_to( 'module_name' ) ) - assert_that( module, has_property( 'SomeMethod' ) ) - assert_that( module.SomeMethod(), equal_to( True ) ) - assert_that( sys.modules, has_entry( module.__name__, module ) ) + def test_FindExecutable_ReturnNoneIfFileIsNotExecutable( self ): + with tempfile.NamedTemporaryFile() as non_executable: + assert_that( None, + equal_to( utils.FindExecutable( non_executable.name ) ) ) -def GetCurrentDirectory_Py3NoCurrentDirectory_test(): - with patch( 'os.getcwd', side_effect = FileNotFoundError ): # noqa - assert_that( utils.GetCurrentDirectory(), - equal_to( tempfile.gettempdir() ) ) + @WindowsOnly + def test_FindExecutable_CurrentDirectory( self ): + with TemporaryExecutable() as executable: + dirname, exename = os.path.split( executable ) + with CurrentWorkingDirectory( dirname ): + assert_that( executable, equal_to( utils.FindExecutable( exename ) ) ) -def HashableDict_Equality_test(): - dict1 = { 'key': 'value' } - dict2 = { 'key': 'another_value' } - assert_that( utils.HashableDict( dict1 ) == utils.HashableDict( dict1 ) ) - assert_that( not utils.HashableDict( dict1 ) != utils.HashableDict( dict1 ) ) - assert_that( not utils.HashableDict( dict1 ) == dict1 ) - assert_that( utils.HashableDict( dict1 ) != dict1 ) - assert_that( not utils.HashableDict( dict1 ) == utils.HashableDict( dict2 ) ) - assert_that( utils.HashableDict( dict1 ) != utils.HashableDict( dict2 ) ) + @WindowsOnly + @patch.dict( 'os.environ', { 'PATHEXT': '.xyz' } ) + def test_FindExecutable_AdditionalPathExt( self ): + with TemporaryExecutable( extension = '.xyz' ) as executable: + assert_that( executable, equal_to( utils.FindExecutable( executable ) ) ) -@patch( 'ycmd.utils.LOGGER', autospec = True ) -def RunImportAndCheckCoreException( test, logger ): - with patch( 'ycmd.utils.ImportCore', - side_effect = ImportError( test[ 'exception_message' ] ) ): - assert_that( ImportAndCheckCore(), equal_to( test[ 'exit_status' ] ) ) - - assert_that( logger.method_calls, has_length( 1 ) ) - logger.exception.assert_called_with( test[ 'logged_message' ] ) + def test_FindExecutableWithFallback_Empty( self ): + with TemporaryExecutable() as fallback: + assert_that( utils.FindExecutableWithFallback( '', fallback ), + equal_to( fallback ) ) -@patch( 'ycmd.utils.LOGGER', autospec = True ) -def ImportAndCheckCore_Compatible_test( logger ): - assert_that( ImportAndCheckCore(), equal_to( 0 ) ) - assert_that( logger.method_calls, empty() ) + @patch( 'ycmd.utils.FindExecutable', return_value = None ) + def test_FindExecutableWithFallback_UserProvided_Invalid( self, *args ): + with TemporaryExecutable() as executable: + with TemporaryExecutable() as fallback: + assert_that( utils.FindExecutableWithFallback( executable, fallback ), + equal_to( None ) ) -@pytest.mark.valgrind_skip -def ImportAndCheckCore_Unexpected_test(): - RunImportAndCheckCoreException( { - 'exception_message': 'unexpected import exception', - 'exit_status': 3, - 'logged_message': 'unexpected import exception' - } ) + def test_FindExecutableWithFallback_UserProvided( self ): + with TemporaryExecutable() as executable: + with TemporaryExecutable() as fallback: + assert_that( utils.FindExecutableWithFallback( executable, fallback ), + equal_to( executable ) ) -@pytest.mark.valgrind_skip -def ImportAndCheckCore_Missing_test(): - RunImportAndCheckCoreException( { - 'exception_message': "No module named 'ycm_core'", - 'exit_status': 4, - 'logged_message': 'ycm_core library not detected; you need to compile it ' - 'by running the build.py script. See the documentation ' - 'for more details.' - } ) + @patch( 'ycmd.utils.ProcessIsRunning', return_value = True ) + def test_WaitUntilProcessIsTerminated_TimedOut( self, *args ): + assert_that( + calling( utils.WaitUntilProcessIsTerminated ).with_args( None, + timeout = 0 ), + raises( RuntimeError, + 'Waited process to terminate for 0 seconds, aborting.' ) + ) -@patch( 'ycm_core.YcmCoreVersion', side_effect = AttributeError() ) -@patch( 'ycmd.utils.LOGGER', autospec = True ) -def ImportAndCheckCore_Outdated_NoYcmCoreVersionMethod_test( logger, - *args ): - assert_that( ImportAndCheckCore(), equal_to( 7 ) ) - assert_that( logger.method_calls, has_length( 1 ) ) - logger.exception.assert_called_with( - 'ycm_core library too old; PLEASE RECOMPILE by running the build.py ' - 'script. See the documentation for more details.' ) + def test_LoadPythonSource_UnicodePath( self ): + filename = PathToTestFile( u'uni¢od€.py' ) + module = utils.LoadPythonSource( 'module_name', filename ) + assert_that( module, instance_of( ModuleType ) ) + assert_that( module.__file__, equal_to( filename ) ) + assert_that( module.__name__, equal_to( 'module_name' ) ) + assert_that( module, has_property( 'SomeMethod' ) ) + assert_that( module.SomeMethod(), equal_to( True ) ) + assert_that( sys.modules, has_entry( module.__name__, module ) ) + + + def test_GetCurrentDirectory_Py3NoCurrentDirectory( self ): + with patch( 'os.getcwd', side_effect = FileNotFoundError ): # noqa + assert_that( utils.GetCurrentDirectory(), + equal_to( tempfile.gettempdir() ) ) + + + def test_HashableDict_Equality( self ): + dict1 = { 'key': 'value' } + dict2 = { 'key': 'another_value' } + assert_that( utils.HashableDict( dict1 ) == utils.HashableDict( dict1 ) ) + assert_that( not utils.HashableDict( dict1 ) != + utils.HashableDict( dict1 ) ) + assert_that( not utils.HashableDict( dict1 ) == dict1 ) + assert_that( utils.HashableDict( dict1 ) != dict1 ) + assert_that( not utils.HashableDict( dict1 ) == + utils.HashableDict( dict2 ) ) + assert_that( utils.HashableDict( dict1 ) != utils.HashableDict( dict2 ) ) + + + @patch( 'ycmd.utils.LOGGER', autospec = True ) + def test_ImportAndCheckCore_Compatible( self, logger ): + assert_that( ImportAndCheckCore(), equal_to( 0 ) ) + assert_that( logger.method_calls, empty() ) + + + def test_ImportAndCheckCore_Unexpected( self ): + RunImportAndCheckCoreException( { + 'exception_message': 'unexpected import exception', + 'exit_status': 3, + 'logged_message': 'unexpected import exception' + } ) + + + def test_ImportAndCheckCore_Missing( self ): + RunImportAndCheckCoreException( { + 'exception_message': "No module named 'ycm_core'", + 'exit_status': 4, + 'logged_message': 'ycm_core library not detected; you need to compile ' + 'it by running the build.py script. ' + 'See the documentation for more details.' + } ) + + + @patch( 'ycm_core.YcmCoreVersion', side_effect = AttributeError() ) + @patch( 'ycmd.utils.LOGGER', autospec = True ) + def test_ImportAndCheckCore_Outdated_NoYcmCoreVersionMethod( self, + logger, + *args ): + assert_that( ImportAndCheckCore(), equal_to( 7 ) ) + assert_that( logger.method_calls, has_length( 1 ) ) + logger.exception.assert_called_with( + 'ycm_core library too old; PLEASE RECOMPILE by running the build.py ' + 'script. See the documentation for more details.' ) + + + @patch( 'ycm_core.YcmCoreVersion', return_value = 10 ) + @patch( 'ycmd.utils.ExpectedCoreVersion', return_value = 11 ) + @patch( 'ycmd.utils.LOGGER', autospec = True ) + def test_ImportAndCheckCore_Outdated_NoVersionMatch( self, logger, *args ): + assert_that( ImportAndCheckCore(), equal_to( 7 ) ) + assert_that( logger.method_calls, has_length( 1 ) ) + logger.error.assert_called_with( + 'ycm_core library too old; PLEASE RECOMPILE by running the build.py ' + 'script. See the documentation for more details.' ) + + + @patch( 'ycmd.utils.ListDirectory', return_value = [] ) + def test_GetClangResourceDir_NotFound( self, *args ): + assert_that( + calling( utils.GetClangResourceDir ), + raises( RuntimeError, 'Cannot find Clang resource directory' ) + ) -@patch( 'ycm_core.YcmCoreVersion', return_value = 10 ) -@patch( 'ycmd.utils.ExpectedCoreVersion', return_value = 11 ) -@patch( 'ycmd.utils.LOGGER', autospec = True ) -def ImportAndCheckCore_Outdated_NoVersionMatch_test( logger, *args ): - assert_that( ImportAndCheckCore(), equal_to( 7 ) ) - assert_that( logger.method_calls, has_length( 1 ) ) - logger.error.assert_called_with( - 'ycm_core library too old; PLEASE RECOMPILE by running the build.py ' - 'script. See the documentation for more details.' ) - - -@patch( 'ycmd.utils.ListDirectory', return_value = [] ) -def GetClangResourceDir_NotFound_test( *args ): - assert_that( - calling( utils.GetClangResourceDir ), - raises( RuntimeError, 'Cannot find Clang resource directory' ) - ) - - -@pytest.mark.parametrize( 'unsafe_name,safe_name', [ - ( 'this is a test 0123 -x', 'this_is_a_test_0123__x' ), - ( 'This Is A Test 0123 -x', 'this_is_a_test_0123__x' ), - ( 'T˙^ß ^ß å †´ß† 0123 -x', 't______________0123__x' ), - ( 'contains/slashes', 'contains_slashes' ), - ( 'contains/newline/\n', 'contains_newline__' ), - ( '', '' ), - ] ) -def MakeSafeFileNameString_test( unsafe_name, safe_name ): - assert_that( utils.MakeSafeFileNameString( unsafe_name ), - equal_to( safe_name ) ) - - -@pytest.mark.parametrize( 'target,override,expected', [ - ( {}, {}, {} ), - ( { 1: 1 }, {}, { 1: 1 } ), - ( {}, { 1: 1 }, { 1: 1 } ), - ( { 1: { 4: 4 } }, { 1: { 2: { 3: 3 } } }, { 1: { 2: { 3: 3 }, 4: 4 } } ), - ( { 1: {} }, { 1: 1 }, { 1: 1 } ), - ( - { - 'outer': { 'inner': { 'key': 'oldValue', 'existingKey': True } } - }, - { - 'outer': { 'inner': { 'key': 'newValue' } }, - 'newKey': { 'newDict': True }, - }, - { - 'outer': { - 'inner': { - 'key': 'newValue', - 'existingKey': True - } - }, - 'newKey': { 'newDict': True } - } ), -] ) -def UpdateDict_test( target, override, expected ): - assert_that( utils.UpdateDict( target, override ), - equal_to( expected ) ) - - -def Dummy_test(): - # Workaround for https://github.com/pytest-dev/pytest-rerunfailures/issues/51 - assert True + def test_MakeSafeFileNameString( self ): + for unsafe_name, safe_name in zip( + [ 'this is a test 0123 -x', 'This Is A Test 0123 -x', + 'T˙^ß ^ß å †´ß† 0123 -x', 'contains/slashes', + 'contains/newline/\n', '' ], + [ 'this_is_a_test_0123__x', 'this_is_a_test_0123__x', + 't______________0123__x', 'contains_slashes', + 'contains_newline__', '' ] ): + with self.subTest( unsafe_name = unsafe_name, safe_name = safe_name ): + assert_that( utils.MakeSafeFileNameString( unsafe_name ), + equal_to( safe_name ) ) + + + def test_UpdateDict( self ): + for target, override, expected in [ + ( {}, {}, {} ), + ( { 1: 1 }, {}, { 1: 1 } ), + ( {}, { 1: 1 }, { 1: 1 } ), + ( { 1: { 4: 4 } }, { 1: { 2: { 3: 3 } } }, { 1: { 2: { 3: 3 }, 4: 4 } } ), + ( { 1: {} }, { 1: 1 }, { 1: 1 } ), + ( + { + 'outer': { 'inner': { 'key': 'oldValue', 'existingKey': True } } + }, + { + 'outer': { 'inner': { 'key': 'newValue' } }, + 'newKey': { 'newDict': True }, + }, + { + 'outer': { + 'inner': { + 'key': 'newValue', + 'existingKey': True + } + }, + 'newKey': { 'newDict': True } + } ), + ]: + with self.subTest( target = target, + override = override, + expected = expected ): + assert_that( utils.UpdateDict( target, override ), + equal_to( expected ) )