Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -1,15 +1,3 @@ -if (NOT DEFINED CLANGD_BUILD_XPC) - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(CLANGD_BUILD_XPC_DEFAULT ON) - else () - set(CLANGD_BUILD_XPC_DEFAULT OFF) - endif () - - set(CLANGD_BUILD_XPC ${CLANGD_BUILD_XPC_DEFAULT} CACHE BOOL "Build XPC Support For Clangd." FORCE) - - unset(CLANGD_BUILD_XPC_DEFAULT) -endif () - add_subdirectory(clang-apply-replacements) add_subdirectory(clang-reorder-fields) add_subdirectory(modularize) Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -1,6 +1,17 @@ # Configure the Features.inc file. -llvm_canonicalize_cmake_booleans( - CLANGD_BUILD_XPC) +if (NOT DEFINED CLANGD_BUILD_XPC) + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(CLANGD_BUILD_XPC_DEFAULT ON) + else () + set(CLANGD_BUILD_XPC_DEFAULT OFF) + endif () + + set(CLANGD_BUILD_XPC ${CLANGD_BUILD_XPC_DEFAULT} CACHE BOOL "Build XPC Support For Clangd." FORCE) + unset(CLANGD_BUILD_XPC_DEFAULT) +endif () + +llvm_canonicalize_cmake_booleans(CLANGD_BUILD_XPC) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/Features.inc.in ${CMAKE_CURRENT_BINARY_DIR}/Features.inc @@ -130,3 +141,8 @@ if ( CLANGD_BUILD_XPC ) add_subdirectory(xpc) endif () + +if(CLANG_INCLUDE_TESTS) +add_subdirectory(test) +add_subdirectory(unittests) +endif() Index: clangd/test/CMakeLists.txt =================================================================== --- /dev/null +++ clangd/test/CMakeLists.txt @@ -0,0 +1,28 @@ +set(CLANGD_TEST_DEPS + clangd + ClangdTests + # No tests for these, but we should still make sure they build. + clangd-indexer + dexp + ) + +if(CLANGD_BUILD_XPC) + list(APPEND CLANGD_TEST_DEPS clangd-xpc-test-client) +endif() + +foreach(dep FileCheck count not) + if(TARGET ${dep}) + list(APPEND CLANGD_TEST_DEPS ${dep}) + endif() +endforeach() + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.cfg) +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.cfg.in + ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.cfg) + +add_lit_testsuite(check-clangd "Running the Clangd regression tests" + ${CMAKE_CURRENT_BINARY_DIR}/Unit;${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CLANGD_TEST_DEPS}) Index: clangd/test/Unit/lit.cfg.in =================================================================== --- /dev/null +++ clangd/test/Unit/lit.cfg.in @@ -0,0 +1,23 @@ +@LIT_SITE_CFG_IN_HEADER@ +# This is a shim to run the gtest unittests in ../unittests using lit. + +import lit.formats +config.name = "Clangd Unit Tests" +config.test_format = lit.formats.GoogleTest('.', 'Tests') +config.test_source_root = "@CMAKE_CURRENT_BINARY_DIR@/../unittests" +config.test_exec_root = "@CMAKE_CURRENT_BINARY_DIR@/../unittests" + +# Point the dynamic loader at dynamic libraries in 'lib'. +# XXX: it seems every project has a copy of this logic. Move it somewhere. +import platform +if platform.system() == 'Darwin': + shlibpath_var = 'DYLD_LIBRARY_PATH' +elif platform.system() == 'Windows': + shlibpath_var = 'PATH' +else: + shlibpath_var = 'LD_LIBRARY_PATH' +config.environment[shlibpath_var] = os.path.pathsep.join(( + "@SHLIBDIR@", "@LLVM_LIBS_DIR@", + config.environment.get(shlibpath_var,''))) + + Index: clangd/test/lit.cfg.in =================================================================== --- /dev/null +++ clangd/test/lit.cfg.in @@ -0,0 +1,29 @@ +@LIT_SITE_CFG_IN_HEADER@ + +import lit.llvm +import lit.formats + +# Reuse clang configuration (PATH setup, etc). +config.clang_tools_dir = "@CLANG_TOOLS_DIR@" +config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" +config.clang_libs_dir = "@CLANG_LIBS_DIR@" +config.llvm_libs_dir = "@LLVM_LIBS_DIR@" +config.target_triple = "@TARGET_TRIPLE@" +config.host_triple = "@LLVM_HOST_TRIPLE@" +lit.llvm.initialize(lit_config, config) +lit.llvm.llvm_config.use_clang() + +config.name = 'Clangd' +config.suffixes = ['.test'] +config.excludes = ['Inputs'] +config.test_format = lit.formats.ShTest(not lit.llvm.llvm_config.use_lit_shell) +config.test_source_root = "@CMAKE_CURRENT_SOURCE_DIR@" +config.test_exec_root = "@CMAKE_CURRENT_BINARY_DIR@" + +# Clangd-specific lit environment. +config.substitutions.append(('%clangd-benchmark-dir', + "@CMAKE_CURRENT_BINARY_DIR@/../benchmarks")) + +if @CLANGD_BUILD_XPC@: + config.available_features.add('clangd-xpc-support') + Index: clangd/unittests/CMakeLists.txt =================================================================== --- clangd/unittests/CMakeLists.txt +++ clangd/unittests/CMakeLists.txt @@ -11,7 +11,17 @@ ${CLANGD_BINARY_DIR} ) -add_extra_unittest(ClangdTests +if(CLANG_BUILT_STANDALONE) + # LLVMTestingSupport library is needed for clangd tests. + if (EXISTS ${LLVM_MAIN_SRC_DIR}/lib/Testing/Support + AND NOT TARGET LLVMTestingSupport) + add_subdirectory(${LLVM_MAIN_SRC_DIR}/lib/Testing/Support + lib/Testing/Support) + endif() +endif() + +add_custom_target(ClangdUnitTests) +add_unittest(ClangdUnitTests ClangdTests Annotations.cpp BackgroundIndexTests.cpp CancellationTests.cpp Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -15,9 +15,7 @@ string(REPLACE ${CMAKE_CFG_INTDIR} ${LLVM_BUILD_MODE} CLANG_TOOLS_DIR ${LLVM_RUNTIME_OUTPUT_INTDIR}) -llvm_canonicalize_cmake_booleans( - CLANG_ENABLE_STATIC_ANALYZER - CLANGD_BUILD_XPC) +llvm_canonicalize_cmake_booleans(CLANG_ENABLE_STATIC_ANALYZER) configure_lit_site_cfg( ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in @@ -70,19 +68,6 @@ clang ) -if(CLANGD_BUILD_XPC) - list(APPEND CLANG_TOOLS_TEST_DEPS clangd-xpc-test-client) -endif() - -set(CLANGD_TEST_DEPS - clangd - ClangdTests - # clangd-related tools which don't have tests, add them to the test to make - # sure we don't introduce new changes that break their compilations. - clangd-indexer - dexp - ) - # Add lit test dependencies. set(LLVM_UTILS_DEPS FileCheck count not @@ -93,11 +78,6 @@ endif() endforeach() -foreach(clangd_dep ${CLANGD_TEST_DEPS}) - list(APPEND CLANG_TOOLS_TEST_DEPS - ${clangd_dep}) -endforeach() - add_lit_testsuite(check-clang-tools "Running the Clang extra tools' regression tests" ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CLANG_TOOLS_TEST_DEPS} @@ -105,15 +85,3 @@ ) set_target_properties(check-clang-tools PROPERTIES FOLDER "Clang extra tools' tests") - -# Setup an individual test for building and testing clangd-only stuff. -# Note: all clangd tests have been covered in check-clang-tools, this is a -# convenient target for clangd developers. -# Exclude check-clangd from check-all. -set(EXCLUDE_FROM_ALL ON) -add_lit_testsuite(check-clangd "Running the Clangd regression tests" - ${CMAKE_CURRENT_BINARY_DIR}/Unit/clangd;${CMAKE_CURRENT_BINARY_DIR}/clangd - DEPENDS ${CLANGD_TEST_DEPS} -) -set_target_properties(check-clangd PROPERTIES FOLDER "Clangd tests") -set(EXCLUDE_FROM_ALL OFF) Index: test/clangd/Inputs/BenchmarkHeader.h =================================================================== --- /dev/null +++ test/clangd/Inputs/BenchmarkHeader.h @@ -1,19 +0,0 @@ -namespace clang { -namespace clangd { -namespace dex { -class Dex; -} // namespace dex -} // namespace clangd -} // namespace clang - -namespace llvm { -namespace sys { - -int getHostNumPhysicalCores(); - -} // namespace sys -} // namespace llvm - -namespace { -int Variable; -} // namespace Index: test/clangd/Inputs/BenchmarkSource.cpp =================================================================== --- /dev/null +++ test/clangd/Inputs/BenchmarkSource.cpp @@ -1 +0,0 @@ -#include "BenchmarkHeader.h" Index: test/clangd/Inputs/background-index/compile_commands.json =================================================================== --- /dev/null +++ test/clangd/Inputs/background-index/compile_commands.json @@ -1,5 +0,0 @@ -[{ - "directory": "DIRECTORY", - "command": "clang foo.cpp", - "file": "DIRECTORY/foo.cpp" -}] Index: test/clangd/Inputs/background-index/definition.jsonrpc =================================================================== --- /dev/null +++ test/clangd/Inputs/background-index/definition.jsonrpc @@ -1,51 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 0, - "method": "initialize", - "params": { - "processId": 123, - "rootPath": "clangd", - "capabilities": {}, - "trace": "off" - } -} ---- -{ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": { - "textDocument": { - "uri": "file://DIRECTORY/bar.cpp", - "languageId": "cpp", - "version": 1, - "text": "#include \"foo.h\"\nint main(){\nreturn foo();\n}" - } - } -} ---- -{ - "jsonrpc": "2.0", - "id": 1, - "method": "sync", - "params": null -} ---- -{ - "jsonrpc": "2.0", - "id": 2, - "method": "textDocument/definition", - "params": { - "textDocument": { - "uri": "file://DIRECTORY/bar.cpp" - }, - "position": { - "line": 2, - "character": 8 - } - } -} -# CHECK: "uri": "file://{{.*}}/foo.cpp" ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/Inputs/background-index/foo.h =================================================================== --- /dev/null +++ test/clangd/Inputs/background-index/foo.h @@ -1,4 +0,0 @@ -#ifndef FOO_H -#define FOO_H -int foo(); -#endif Index: test/clangd/Inputs/background-index/foo.cpp =================================================================== --- /dev/null +++ test/clangd/Inputs/background-index/foo.cpp @@ -1,2 +0,0 @@ -#include "foo.h" -int foo() { return 42; } Index: test/clangd/Inputs/requests.json =================================================================== --- /dev/null +++ test/clangd/Inputs/requests.json @@ -1,7 +0,0 @@ -[{"Limit":100,"ProximityPaths":["/usr/home/user/clang-tools-extra/clangd/benchmarks/IndexBenchmark.cpp"],"Query":"OMP","RestrictForCodeCompletion":true,"Scopes":["clang::"], "AnyScope":false}, -{"Limit":100,"ProximityPaths":[],"Query":"s","RestrictForCodeCompletion":true,"Scopes":["llvm::", ""], "AnyScope":false}, -{"Limit":100,"ProximityPaths":[],"Query":"sy","RestrictForCodeCompletion":true,"Scopes":["llvm::", ""], "AnyScope":false}, -{"Limit":100,"ProximityPaths":[],"Query":"sys","RestrictForCodeCompletion":true,"Scopes":["llvm::", ""], "AnyScope":false}, -{"Limit":100,"ProximityPaths":[],"Query":"sys","RestrictForCodeCompletion":true,"Scopes":["llvm::", ""], "AnyScope":false}, -{"Limit":100,"ProximityPaths":[],"Query":"Dex","RestrictForCodeCompletion":true,"Scopes":["clang::clangd::", "clang::", "clang::clangd::dex::"],"AnyScope":false}, -{"Limit":100,"ProximityPaths":[],"Query":"Variable","RestrictForCodeCompletion":true,"Scopes":[""], "AnyScope":false}] Index: test/clangd/Inputs/symbols.test.yaml =================================================================== --- /dev/null +++ test/clangd/Inputs/symbols.test.yaml @@ -1,17 +0,0 @@ ---- -!Symbol -ID: 057557CEBF6E6B2D -Name: 'vector' -Scope: 'std::' -SymInfo: - Kind: Class - Lang: Cpp -CanonicalDeclaration: - FileURI: 'file:///vector.h' - Start: - Line: 215 - Column: 10 - End: - Line: 215 - Column: 16 -... Index: test/clangd/Unit/lit.site.cfg.py.in =================================================================== --- /dev/null +++ test/clangd/Unit/lit.site.cfg.py.in @@ -0,0 +1,9 @@ +@LIT_SITE_CFG_IN_HEADER@ + +config.extra_tools_obj_dir = "@CLANG_TOOLS_BINARY_DIR@/unittests" +config.extra_tools_src_dir = "@CLANG_TOOLS_SOURCE_DIR@/unittests" +config.llvm_libs_dir = "@LLVM_LIBS_DIR@" +config.shlibdir = "@SHLIBDIR@" +config.target_triple = "@TARGET_TRIPLE@" + +lit_config.load_config(config, "@CLANG_TOOLS_SOURCE_DIR@/test/Unit/lit.cfg.py") Index: test/clangd/background-index.test =================================================================== --- /dev/null +++ test/clangd/background-index.test @@ -1,20 +0,0 @@ -# We need to splice paths into file:// URIs for this test. -# UNSUPPORTED: win32 - -# Use a copy of inputs, as we'll mutate it (as will the background index). -# RUN: rm -rf %t -# RUN: cp -r %S/Inputs/background-index %t -# Need to embed the correct temp path in the actual JSON-RPC requests. -# RUN: sed -i -e "s|DIRECTORY|%t|" %t/* - -# We're editing bar.cpp, which includes foo.h. -# foo() is declared in foo.h and defined in foo.cpp. -# The background index should allow us to go-to-definition on foo(). -# RUN: clangd -background-index -background-index-rebuild-period=0 -lit-test < %t/definition.jsonrpc | FileCheck %t/definition.jsonrpc - -# Test that the index is writing files in the expected location. -# RUN: ls %t/.clangd/index/foo.cpp.*.idx - -# Test the index is read from disk: delete code and restart clangd. -# RUN: rm %t/foo.cpp -# RUN: clangd -background-index -lit-test < %t/definition.jsonrpc | FileCheck %t/definition.jsonrpc Index: test/clangd/compile-commands-path-in-initialize.test =================================================================== --- /dev/null +++ test/clangd/compile-commands-path-in-initialize.test @@ -1,29 +0,0 @@ -# Test that we can set choose a configuration/build directly in the initialize -# request. - -# RUN: rm -rf %t.dir/* && mkdir -p %t.dir -# RUN: mkdir %t.dir/build-1 -# RUN: echo '[{"directory": "%/t.dir", "command": "c++ the-file.cpp", "file": "the-file.cpp"}]' > %t.dir/compile_commands.json -# RUN: echo '[{"directory": "%/t.dir/build-1", "command": "c++ -DMACRO=1 the-file.cpp", "file": "../the-file.cpp"}]' > %t.dir/build-1/compile_commands.json - -# RUN: sed -e "s|INPUT_DIR|%/t.dir|g" %s > %t.test.1 - -# On Windows, we need the URI in didOpen to look like "uri":"file:///C:/..." -# (with the extra slash in the front), so we add it here. -# RUN: sed -e "s|file://\([A-Z]\):/|file:///\1:/|g" %t.test.1 > %t.test - -# RUN: clangd -lit-test < %t.test | FileCheck -strict-whitespace %t.test - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"initializationOptions":{"compilationDatabasePath":"INPUT_DIR/build-1"}}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file://INPUT_DIR/the-file.cpp","languageId":"cpp","version":1,"text":"#if !defined(MACRO)\n#pragma message (\"MACRO is not defined\")\n#elif MACRO == 1\n#pragma message (\"MACRO is one\")\n#else\n#pragma message (\"woops\")\n#endif\nint main() {}\n"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "-W#pragma-messages", -# CHECK-NEXT: "message": "MACRO is one", ---- -{"jsonrpc":"2.0","id":10000,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/completion-auto-trigger.test =================================================================== --- /dev/null +++ test/clangd/completion-auto-trigger.test @@ -1,106 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"namespace ns { int ns_member; } struct vector { int size; static int default_capacity; };\nvoid test(vector *a, vector *b) {\n if (a > b) {} \n a->size = 10;\n\n a ? a : b;\n ns::ns_member = 10;\n}"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":9},"context":{"triggerKind":2,"triggerCharacter":">"}}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32001, -# CHECK-NEXT: "message": "ignored auto-triggered completion, preceding char did not match" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 1, ---- -{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":3,"character":5},"context":{"triggerKind":2,"triggerCharacter":">"}}} -# CHECK: "id": 2, -# CHECK-NEXT: "jsonrpc": "2.0" -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "size", -# CHECK-NEXT: "insertText": "size", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": " size", -# CHECK-NEXT: "sortText": "3eacccccsize", -# CHECK-NEXT: "textEdit": { -# CHECK-NEXT: "newText": "size", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "default_capacity", -# CHECK-NEXT: "insertText": "default_capacity", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 10, -# CHECK-NEXT: "label": " default_capacity", -# CHECK-NEXT: "sortText": "3fd70a3ddefault_capacity", -# CHECK-NEXT: "textEdit": { -# CHECK-NEXT: "newText": "default_capacity", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":5,"character":9},"context":{"triggerKind":2,"triggerCharacter":":"}}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32001, -# CHECK-NEXT: "message": "ignored auto-triggered completion, preceding char did not match" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 3, ---- -{"jsonrpc":"2.0","id":4,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":6,"character":6},"context":{"triggerKind":2,"triggerCharacter":":"}}} ---- -# CHECK: "id": 4, -# CHECK-NEXT: "jsonrpc": "2.0" -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "ns_member", -# CHECK-NEXT: "insertText": "ns_member", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 6, -# CHECK-NEXT: "label": " ns_member", -# CHECK-NEXT: "sortText": "3f2cccccns_member", -# CHECK-NEXT: "textEdit": { -# CHECK-NEXT: "newText": "ns_member", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 6, -# CHECK-NEXT: "line": 6 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 6, -# CHECK-NEXT: "line": 6 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -{"jsonrpc":"2.0","id":5,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/completion-snippets.test =================================================================== --- /dev/null +++ test/clangd/completion-snippets.test @@ -1,56 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s -{ - "jsonrpc": "2.0", - "id": 0, - "method": "initialize", - "params": { - "processId": 123, - "rootPath": "clangd", - "capabilities": { - "textDocument": { - "completion": { - "completionItem": { - "snippetSupport": true - } - } - } - }, - "trace": "off" - } -} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"int func_with_args(int a, int b);\nint main() {\nfunc_with\n}"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":7}}} -# CHECK: "id": 1 -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": {{.*}} -# CHECK-NEXT: "items": [ -# CHECK: "filterText": "func_with_args", -# CHECK-NEXT: "insertText": "func_with_args(${1:int a}, ${2:int b})", -# CHECK-NEXT: "insertTextFormat": 2, -# CHECK-NEXT: "kind": 3, -# CHECK-NEXT: "label": " func_with_args(int a, int b)", -# CHECK-NEXT: "sortText": "{{.*}}func_with_args" -# CHECK-NEXT: "textEdit": { -# CHECK-NEXT: "newText": "func_with_args(${1:int a}, ${2:int b})", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 0, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":4,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/completion.test =================================================================== --- /dev/null +++ test/clangd/completion.test @@ -1,72 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct S { int a; };\nint main() {\nS().\n}"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":4}}} -# CHECK: "id": 1 -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "a", -# CHECK-NEXT: "insertText": "a", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": " a", -# CHECK-NEXT: "sortText": "{{.*}}a" -# CHECK-NEXT: "textEdit": { -# CHECK-NEXT: "newText": "a", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -# Update the source file and check for completions again. -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp","version":2},"contentChanges":[{"text":"struct S { int b; };\nint main() {\nS().\n}"}]}} ---- -{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":4}}} -# CHECK: "id": 3, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK-NEXT: { -# CHECK-NEXT: "detail": "int", -# CHECK-NEXT: "filterText": "b", -# CHECK-NEXT: "insertText": "b", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": " b", -# CHECK-NEXT: "sortText": "{{.*}}b" -# CHECK-NEXT: "textEdit": { -# CHECK-NEXT: "newText": "b", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":4,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/crash-non-added-files.test =================================================================== --- /dev/null +++ test/clangd/crash-non-added-files.test @@ -1,34 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32602 -# CHECK-NEXT: "message": "onCodeAction called for non-added file" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 2, ---- -{"jsonrpc":"2.0","id":3,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32602 -# CHECK-NEXT: "message": "onDocumentRangeFormatting called for non-added file" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 3, ---- -{"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"test:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32602 -# CHECK-NEXT: "message": "onDocumentFormatting called for non-added file" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 4, ---- -{"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"position":{"line":3,"character":1},"ch":"}","options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32602 -# CHECK-NEXT: "message": "onDocumentOnTypeFormatting called for non-added file" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 5, ---- -{"jsonrpc":"2.0","id":6,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/delimited-input-comment-at-the-end.test =================================================================== --- /dev/null +++ test/clangd/delimited-input-comment-at-the-end.test @@ -1,11 +0,0 @@ -# RUN: clangd -input-style=delimited -run-synchronously -input-mirror-file %t < %s -# RUN: grep '{"jsonrpc":"2.0","id":3,"method":"exit"}' %t -# -# RUN: clangd -lit-test -input-mirror-file %t < %s -# RUN: grep '{"jsonrpc":"2.0","id":3,"method":"exit"}' %t -# -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/diagnostic-category.test =================================================================== --- /dev/null +++ test/clangd/diagnostic-category.test @@ -1,45 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"categorySupport":true}}},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"struct Point {}; union Point p;"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "category": "Semantic Issue", -# CHECK-NEXT: "code": "use_with_wrong_tag", -# CHECK-NEXT: "message": "Use of 'Point' with tag type that does not match previous declaration (fix available)\n\nfoo.c:1:8: note: previous use is here", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 22, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 17, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 1, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "message": "Previous use is here\n\nfoo.c:1:18: error: use of 'Point' with tag type that does not match previous declaration", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 12, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 3 -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":4,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/diagnostics-notes.test =================================================================== --- /dev/null +++ test/clangd/diagnostics-notes.test @@ -1,48 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"relatedInformation":true}}},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cc","languageId":"cpp","version":1,"text":"int x;\nint x;"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "redefinition", -# CHECK-NEXT: "message": "Redefinition of 'x'", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "relatedInformation": [ -# CHECK-NEXT: { -# CHECK-NEXT: "location": { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "{{.*}}foo.cc" -# CHECK-NEXT: }, -# CHECK-NEXT: "message": "Previous definition is here" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "severity": 1, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.cc" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":5,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/diagnostics.test =================================================================== --- /dev/null +++ test/clangd/diagnostics.test @@ -1,55 +0,0 @@ -# RUN: clangd -lit-test -clang-tidy-checks=bugprone-sizeof-expression < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {\n(void)sizeof(42);\n}"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "-Wmain-return-type", -# CHECK-NEXT: "message": "Return type of 'main' is not 'int' (fix available)", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 0, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 2, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "code": "bugprone-sizeof-expression", -# CHECK-NEXT: "message": "Suspicious usage of 'sizeof(K)'; did you mean 'K'?", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 12, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 6, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 2, -# CHECK-NEXT: "source": "clang-tidy" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":2,"method":"sync","params":null} ---- -{"jsonrpc":"2.0","method":"textDocument/didClose","params":{"textDocument":{"uri":"test:///foo.c"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":5,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/did-change-configuration-params.test =================================================================== --- /dev/null +++ test/clangd/did-change-configuration-params.test @@ -1,56 +0,0 @@ -# RUN: clangd -compile_args_from=lsp -lit-test < %s 2> %t | FileCheck -strict-whitespace %s -# RUN: cat %t | FileCheck --check-prefix=ERR %s -# UNSUPPORTED: windows-gnu,windows-msvc -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test", "compilationCommand": ["clang", "-c", "foo.c"]}}}}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [], -# CHECK-NEXT: "uri": "file://{{.*}}/bar.c" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"compilationDatabaseChanges":{"/clangd-test/foo.c": {"workingDirectory":"/clangd-test2", "compilationCommand": ["clang", "-c", "foo.c", "-Wall", "-Werror"]}}}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "-Wuninitialized", -# CHECK-NEXT: "message": "Variable 'i' is uninitialized when used here (fix available)", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 28, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 27, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 1, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } -# -# ERR: Updating file {{.*}}foo.c with command -# ERR: [{{.*}}clangd-test2] -# ERR: clang -c foo.c -Wall -Werror -# Don't reparse the second file: -# ERR: Skipping rebuild of the AST for {{.*}}bar.c ---- -{"jsonrpc":"2.0","id":5,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} - - Index: test/clangd/execute-command.test =================================================================== --- /dev/null +++ test/clangd/execute-command.test @@ -1,68 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "-Wparentheses", -# CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 2, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } ---- -# No command name -{"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{}} ---- -# Invalid, non-scalar command name -{"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command": {}}} ---- -{"jsonrpc":"2.0","id":5,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","custom":"foo", "arguments":[{"changes":{"test:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}]}} ---- -# Arguments not a sequence. -{"jsonrpc":"2.0","id":6,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":"foo"}} ---- -# Unknown command. -{"jsonrpc":"2.0","id":7,"method":"workspace/executeCommand","params":{"command":"mycommand"}} ---- -# ApplyFix argument not a mapping node. -{"jsonrpc":"2.0","id":8,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","custom":"foo", "arguments":[""]}} ---- -# Custom field in WorkspaceEdit -{"jsonrpc":"2.0","id":9,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"custom":"foo", "changes":{"test:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}]}} ---- -# changes in WorkspaceEdit with no mapping node -{"jsonrpc":"2.0","id":10,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":"foo"}]}} ---- -# Custom field in WorkspaceEditChange -{"jsonrpc":"2.0","id":11,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"test:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}], "custom":"foo"}}]}} ---- -# No sequence node for TextEdits -{"jsonrpc":"2.0","id":12,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"test:///foo.c":"bar"}}]}} ---- -# No mapping node for TextEdit -{"jsonrpc":"2.0","id":13,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"test:///foo.c":[""]}}]}} ---- -# TextEdit not decoded -{"jsonrpc":"2.0","id":14,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"test:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":"","newText":")"}]}}]}} ---- -# Command name after arguments -{"jsonrpc":"2.0","id":9,"method":"workspace/executeCommand","params":{"arguments":[{"custom":"foo", "changes":{"test:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}],"command":"clangd.applyFix"}} ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/exit-with-shutdown.test =================================================================== --- /dev/null +++ test/clangd/exit-with-shutdown.test @@ -1,6 +0,0 @@ -# RUN: clangd -lit-test < %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/exit-without-shutdown.test =================================================================== --- /dev/null +++ test/clangd/exit-without-shutdown.test @@ -1,4 +0,0 @@ -# RUN: not clangd -lit-test < %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/filestatus.test =================================================================== --- /dev/null +++ test/clangd/filestatus.test @@ -1,13 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"initializationOptions":{"clangdFileStatus": true},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"int x; int y = x;"}}} -# CHECK: "method": "textDocument/clangd.fileStatus", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "state": "parsing includes", -# CHECK-NEXT: "uri": "{{.*}}/main.cpp" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/fixits-codeaction.test =================================================================== --- /dev/null +++ test/clangd/fixits-codeaction.test @@ -1,132 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"codeAction":{"codeActionLiteralSupport":{}}}},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "-Wparentheses", -# CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 2, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses (fixes available)", "code": "-Wparentheses", "source": "clang"}]}}} -# CHECK: "id": 2, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "-Wparentheses", -# CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 2, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "edit": { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "file://{{.*}}/foo.c": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "(", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": ")", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "kind": "quickfix", -# CHECK-NEXT: "title": "place parentheses around the assignment to silence this warning" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "-Wparentheses", -# CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 2, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "edit": { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "file://{{.*}}/foo.c": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "==", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 35, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 34, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "kind": "quickfix", -# CHECK-NEXT: "title": "use '==' to turn this assignment into an equality comparison" -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":4,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} - Index: test/clangd/fixits-command.test =================================================================== --- /dev/null +++ test/clangd/fixits-command.test @@ -1,212 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "-Wparentheses", -# CHECK-NEXT: "message": "Using the result of an assignment as a condition without parentheses (fixes available)", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 2, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses (fixes available)"}]}}} -# CHECK: "id": 2, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "arguments": [ -# CHECK-NEXT: { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "file://{{.*}}/foo.c": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "(", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": ")", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "command": "clangd.applyFix", -# CHECK-NEXT: "title": "Apply fix: place parentheses around the assignment to silence this warning" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "arguments": [ -# CHECK-NEXT: { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "file://{{.*}}/foo.c": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "==", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 35, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 34, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "command": "clangd.applyFix", -# CHECK-NEXT: "title": "Apply fix: use '==' to turn this assignment into an equality comparison" -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses (fixes available)"}]}}} -# Make sure unused "code" and "source" fields ignored gracefully -# CHECK: "id": 3, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "arguments": [ -# CHECK-NEXT: { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "file://{{.*}}/foo.c": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "(", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": ")", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "command": "clangd.applyFix", -# CHECK-NEXT: "title": "Apply fix: place parentheses around the assignment to silence this warning" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "arguments": [ -# CHECK-NEXT: { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "file://{{.*}}/foo.c": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "==", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 35, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 34, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "command": "clangd.applyFix", -# CHECK-NEXT: "title": "Apply fix: use '==' to turn this assignment into an equality comparison" -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"test:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}]}} -# CHECK: "id": 4, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": "Fix applied." -# -# CHECK: "id": 0, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "method": "workspace/applyEdit", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "edit": { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "{{.*}}/foo.c": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "(", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 32, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": ")", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 37, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":4,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/fixits-embed-in-diagnostic.test =================================================================== --- /dev/null +++ test/clangd/fixits-embed-in-diagnostic.test @@ -1,69 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":{"publishDiagnostics":{"codeActionsInline":true}}},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"struct Point {}; union Point p;"}}} -# CHECK: "method": "textDocument/publishDiagnostics", -# CHECK-NEXT: "params": { -# CHECK-NEXT: "diagnostics": [ -# CHECK-NEXT: { -# CHECK-NEXT: "code": "use_with_wrong_tag", -# CHECK-NEXT: "codeActions": [ -# CHECK-NEXT: { -# CHECK-NEXT: "edit": { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "file://{{.*}}/foo.c": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "struct", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 22, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 17, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "kind": "quickfix", -# CHECK-NEXT: "title": "change 'union' to 'struct'" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "message": "Use of 'Point' with tag type that does not match previous declaration (fix available)\n\nfoo.c:1:8: note: previous use is here", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 22, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 17, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 1, -# CHECK-NEXT: "source": "clang" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "message": "Previous use is here\n\nfoo.c:1:18: error: use of 'Point' with tag type that does not match previous declaration", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 12, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "severity": 3 -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "uri": "file://{{.*}}/foo.c" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":4,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/formatting.test =================================================================== --- /dev/null +++ test/clangd/formatting.test @@ -1,187 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"int foo ( int x ) {\n x = x+1;\n return x;\n }"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "\n ", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 19, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": " ", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 9, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 9, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": " ", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 10, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 10, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "\n ", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 12, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n x = x + 1;\n return x;\n }"}]}} -# -# ---- -{"jsonrpc":"2.0","id":2,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":1,"character":2},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: "id": 2, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [] ---- -{"jsonrpc":"2.0","id":3,"method":"textDocument/formatting","params":{"textDocument":{"uri":"test:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: "id": 3, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 8, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 10, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 9, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 16, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 15, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "\n", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 11, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":9},"contentChanges":[{"text":"int foo(int x) {\n x = x + 1;\n return x;\n}"}]}} ---- -{"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"test:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: "id": 4, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [] ---- -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n x = x + 1;\n return x;\n}"}]}} ---- -{"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"position":{"line":3,"character":1},"ch":"}","options":{"tabSize":4,"insertSpaces":true}}} -# CHECK: "id": 5, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 8, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 10, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 9, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 16, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 15, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":6,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/hover.test =================================================================== --- /dev/null +++ test/clangd/hover.test @@ -1,24 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":0,"character":27}}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "contents": { -# CHECK-NEXT: "kind": "plaintext", -# CHECK-NEXT: "value": "Declared in global namespace\n\nvoid foo()" -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT:} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/hover","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":0,"character":10}}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": null ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/index-tools.test =================================================================== --- /dev/null +++ test/clangd/index-tools.test @@ -1,6 +0,0 @@ -# RUN: clangd-indexer %p/Inputs/BenchmarkSource.cpp -- -I%p/Inputs > %t.index -# FIXME: By default, benchmarks are excluded from the list of default targets hence not built. Find a way to depend on benchmarks to run the next command. -# REQUIRES: shell -# RUN: if [ -f %clangd-benchmark-dir/IndexBenchmark ]; then %clangd-benchmark-dir/IndexBenchmark %t.index %p/Inputs/requests.json --benchmark_min_time=0.01 ; fi -# Pass invalid JSON file and check that IndexBenchmark fails to parse it. -# RUN: if [ -f %clangd-benchmark-dir/IndexBenchmark ]; then not %clangd-benchmark-dir/IndexBenchmark %t.index %t --benchmark_min_time=0.01 ; fi Index: test/clangd/initialize-params-invalid.test =================================================================== --- /dev/null +++ test/clangd/initialize-params-invalid.test @@ -1,12 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# Test with invalid initialize request parameters -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":"","rootUri":"test:///workspace","capabilities":{},"trace":"off"}} -# CHECK: "id": 0, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "capabilities": { -# ... ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/initialize-params.test =================================================================== --- /dev/null +++ test/clangd/initialize-params.test @@ -1,53 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# Test initialize request parameters with rootUri -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"test:///workspace","capabilities":{},"trace":"off"}} -# CHECK: "id": 0, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "capabilities": { -# CHECK-NEXT: "codeActionProvider": true, -# CHECK-NEXT: "completionProvider": { -# CHECK-NEXT: "resolveProvider": false, -# CHECK-NEXT: "triggerCharacters": [ -# CHECK-NEXT: ".", -# CHECK-NEXT: ">", -# CHECK-NEXT: ":" -# CHECK-NEXT: ] -# CHECK-NEXT: }, -# CHECK-NEXT: "declarationProvider": true, -# CHECK-NEXT: "definitionProvider": true, -# CHECK-NEXT: "documentFormattingProvider": true, -# CHECK-NEXT: "documentHighlightProvider": true, -# CHECK-NEXT: "documentOnTypeFormattingProvider": { -# CHECK-NEXT: "firstTriggerCharacter": "}", -# CHECK-NEXT: "moreTriggerCharacter": [] -# CHECK-NEXT: }, -# CHECK-NEXT: "documentRangeFormattingProvider": true, -# CHECK-NEXT: "documentSymbolProvider": true, -# CHECK-NEXT: "executeCommandProvider": { -# CHECK-NEXT: "commands": [ -# CHECK-NEXT: "clangd.applyFix", -# CHECK-NEXT: "clangd.applyTweak" -# CHECK-NEXT: ] -# CHECK-NEXT: }, -# CHECK-NEXT: "hoverProvider": true, -# CHECK-NEXT: "referencesProvider": true, -# CHECK-NEXT: "renameProvider": true, -# CHECK-NEXT: "signatureHelpProvider": { -# CHECK-NEXT: "triggerCharacters": [ -# CHECK-NEXT: "(", -# CHECK-NEXT: "," -# CHECK-NEXT: ] -# CHECK-NEXT: }, -# CHECK-NEXT: "textDocumentSync": 2, -# CHECK-NEXT: "typeHierarchyProvider": true -# CHECK-NEXT: "workspaceSymbolProvider": true -# CHECK-NEXT: } -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} -# CHECK: "id": 3, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": null ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/initialize-sequence.test =================================================================== --- /dev/null +++ test/clangd/initialize-sequence.test @@ -1,21 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"workspace/symbol","params":{"query":"std::basic_ostringstream"}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32002 -# CHECK-NEXT: "message": "server not initialized" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 0, ---- -{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","id":2,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32600 -# CHECK-NEXT: "message": "server already initialized" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 2, ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} - Index: test/clangd/input-mirror.test =================================================================== --- /dev/null +++ test/clangd/input-mirror.test @@ -1,17 +0,0 @@ -# RUN: clangd -pretty -run-synchronously -input-mirror-file %t < %s -# Note that we have to use '-b' as -input-mirror-file does not have a newline at the end of file. -# RUN: diff -b %t %s -# It is absolutely vital that this file has CRLF line endings. -# -Content-Length: 125 - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} -Content-Length: 172 - -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"int main() {\nint a;\na;\n}\n"}}} -Content-Length: 44 - -{"jsonrpc":"2.0","id":3,"method":"shutdown"} -Content-Length: 33 - -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/lit.local.cfg =================================================================== --- /dev/null +++ test/clangd/lit.local.cfg @@ -1,6 +0,0 @@ -import re -# We rely on the default -std being derived from the filetype. -# PS4 sets a different -std, and many tests break. -# FIXME: make our tests less brittle instead. -if re.match(r'.*-scei-ps4', config.target_triple): - config.unsupported = True Index: test/clangd/protocol.test =================================================================== --- /dev/null +++ test/clangd/protocol.test @@ -1,110 +0,0 @@ -# RUN: not clangd -pretty -run-synchronously -enable-test-uri-scheme < %s | FileCheck -strict-whitespace %s -# RUN: not clangd -pretty -run-synchronously -enable-test-uri-scheme < %s 2>&1 | FileCheck -check-prefix=STDERR %s -# vim: fileformat=dos -# It is absolutely vital that this file has CRLF line endings. -# -# Note that we invert the test because we intent to let clangd exit prematurely. -# -# Test protocol parsing -Content-Length: 125 -Content-Type: application/vscode-jsonrpc; charset-utf-8 - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} -# Test message with Content-Type after Content-Length -# -# CHECK: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK: } -Content-Length: 246 - -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct fake { int a, bb, ccc; int f(int i, const float f) const; };\nint main() {\n fake f;\n f.\n}\n"}}} - -Content-Length: 104 - -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp"}}} - -Content-Type: application/vscode-jsonrpc; charset-utf-8 -Content-Length: 146 - -{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:/main.cpp"},"position":{"line":3,"character":5}}} -# Test message with Content-Type before Content-Length -# -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK: "filterText": "a", -# CHECK-NEXT: "insertText": "a", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": " a", -# CHECK-NEXT: "sortText": "{{.*}}" -# CHECK: ] -# CHECK-NEXT: } - -X-Test: Testing -Content-Type: application/vscode-jsonrpc; charset-utf-8 -Content-Length: 146 -Content-Type: application/vscode-jsonrpc; charset-utf-8 -X-Testing: Test - -{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:/main.cpp"},"position":{"line":3,"character":5}}} - -Content-Type: application/vscode-jsonrpc; charset-utf-8 -Content-Length: 10 -Content-Length: 146 - -{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:/main.cpp"},"position":{"line":3,"character":5}}} -# Test message with duplicate Content-Length headers -# -# CHECK: "id": 3, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK: "filterText": "a", -# CHECK-NEXT: "insertText": "a", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": " a", -# CHECK-NEXT: "sortText": "{{.*}}" -# CHECK: ] -# CHECK-NEXT: } -# STDERR: Warning: Duplicate Content-Length header received. The previous value for this message (10) was ignored. - -Content-Type: application/vscode-jsonrpc; charset-utf-8 -Content-Length: 10 - -{"jsonrpc":"2.0","id":4,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:/main.cpp"},"position":{"line":3,"character":5}}} -# Test message with malformed Content-Length -# -# STDERR: JSON parse error -# Ensure we recover by sending another (valid) message - -Content-Length: 146 - -{"jsonrpc":"2.0","id":5,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:/main.cpp"},"position":{"line":3,"character":5}}} -# Test message with Content-Type before Content-Length -# -# CHECK: "id": 5, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "isIncomplete": false, -# CHECK-NEXT: "items": [ -# CHECK: "filterText": "a", -# CHECK-NEXT: "insertText": "a", -# CHECK-NEXT: "insertTextFormat": 1, -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "label": " a", -# CHECK-NEXT: "sortText": "{{.*}}" -# CHECK: ] -# CHECK-NEXT: } -Content-Length: 1024 - -{"jsonrpc":"2.0","id":5,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:/main.cpp"},"position":{"line":3,"character":5}}} -# Test message which reads beyond the end of the stream. -# -# Ensure this is the last test in the file! -# STDERR: Input was aborted. Read only {{[0-9]+}} bytes of expected {{[0-9]+}}. - Index: test/clangd/references.test =================================================================== --- /dev/null +++ test/clangd/references.test @@ -1,40 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"int x; int y = x;"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/references","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":0,"character":4}}} -# CHECK: "id": 1 -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "{{.*}}/main.cpp" -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 16, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 15, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "{{.*}}/main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/rename.test =================================================================== --- /dev/null +++ test/clangd/rename.test @@ -1,39 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.cpp","languageId":"cpp","version":1,"text":"int foo;"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/rename","params":{"textDocument":{"uri":"test:///foo.cpp"},"position":{"line":0,"character":5},"newName":"bar"}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "changes": { -# CHECK-NEXT: "file://{{.*}}/foo.cpp": [ -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "bar", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 7 -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4 -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":2,"method":"textDocument/rename","params":{"textDocument":{"uri":"test:///foo.cpp"},"position":{"line":0,"character":2},"newName":"bar"}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32001, -# CHECK-NEXT: "message": "there is no symbol at the given location" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 2, -# CHECK-NEXT: "jsonrpc": "2.0" ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/signature-help.test =================================================================== --- /dev/null +++ test/clangd/signature-help.test @@ -1,27 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# Start a session. -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"void x(int);\nint main(){\nx("}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":2}}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "activeParameter": 0, -# CHECK-NEXT: "activeSignature": 0, -# CHECK-NEXT: "signatures": [ -# CHECK-NEXT: { -# CHECK-NEXT: "label": "x(int) -> void", -# CHECK-NEXT: "parameters": [ -# CHECK-NEXT: { -# CHECK-NEXT: "label": "int" -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":100000,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/spaces-in-delimited-input.test =================================================================== --- /dev/null +++ test/clangd/spaces-in-delimited-input.test @@ -1,13 +0,0 @@ -# RUN: clangd -input-style=delimited -run-synchronously < %s 2>&1 | FileCheck %s -# RUN: clangd -lit-test -run-synchronously < %s 2>&1 | FileCheck %s -# -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} - ---- - -{"jsonrpc":"2.0","id":3,"method":"shutdown"} - ---- - -{"jsonrpc":"2.0","method":"exit"} -# CHECK-NOT: JSON parse error Index: test/clangd/symbol-info.test =================================================================== --- /dev/null +++ test/clangd/symbol-info.test @@ -1,14 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///simple.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///simple.cpp"},"position":{"line":0,"character":27}}} -# CHECK: "containerName": null, -# CHECK-NEXT: "id": "CA2EBE44A1D76D2A", -# CHECK-NEXT: "name": "foo", -# CHECK-NEXT: "usr": "c:@F@foo#" ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/symbols.test =================================================================== --- /dev/null +++ test/clangd/symbols.test @@ -1,84 +0,0 @@ -# RUN: clangd --index-file=%S/Inputs/symbols.test.yaml -lit-test < %s | FileCheck %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"workspace":{"symbol":{"symbolKind":{"valueSet": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}}}},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"workspace/symbol","params":{"query":"vector"}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "containerName": "std", -# CHECK-NEXT: "kind": 5, -# CHECK-NEXT: "location": { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": {{.*}}, -# CHECK-NEXT: "line": {{.*}} -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": {{.*}}, -# CHECK-NEXT: "line": {{.*}} -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file:///vector.h" -# CHECK-NEXT: }, -# CHECK-NEXT: "name": "vector" -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT:} ---- -{"jsonrpc":"2.0","id":2,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///main.cpp"}}} -# CHECK: "id": 2, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "containerName": "", -# CHECK-NEXT: "kind": 12, -# CHECK-NEXT: "location": { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": {{.*}}, -# CHECK-NEXT: "line": {{.*}} -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": {{.*}}, -# CHECK-NEXT: "line": {{.*}} -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/main.cpp" -# CHECK-NEXT: }, -# CHECK-NEXT: "name": "foo" -# CHECK-NEXT: } -# CHECK-NEXT: { -# CHECK-NEXT: "containerName": "", -# CHECK-NEXT: "kind": 12, -# CHECK-NEXT: "location": { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": {{.*}}, -# CHECK-NEXT: "line": {{.*}} -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": {{.*}}, -# CHECK-NEXT: "line": {{.*}} -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/main.cpp" -# CHECK-NEXT: }, -# CHECK-NEXT: "name": "main" -# CHECK-NEXT: } -# CHECK-NEXT: ] -# CHECK-NEXT:} ---- -{"jsonrpc":"2.0","id":3,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///foo.cpp"}}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32602, -# CHECK-NEXT: "message": "trying to get AST for non-added document" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 3, -# CHECK-NEXT: "jsonrpc": "2.0" ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/test-uri-posix.test =================================================================== --- /dev/null +++ test/clangd/test-uri-posix.test @@ -1,11 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# UNSUPPORTED: windows-gnu,windows-msvc -# Test authority-less URI -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {}"}}} -# CHECK: "uri": "file:///clangd-test/foo.c" ---- -{"jsonrpc":"2.0","id":5,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/test-uri-windows.test =================================================================== --- /dev/null +++ test/clangd/test-uri-windows.test @@ -1,11 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# REQUIRES: windows-gnu || windows-msvc -# Test authority-less URI -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {}"}}} -# CHECK: "uri": "file:///C:/clangd-test/foo.c" ---- -{"jsonrpc":"2.0","id":5,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/textdocument-didchange-fail.test =================================================================== --- /dev/null +++ test/clangd/textdocument-didchange-fail.test @@ -1,39 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"int main() {}\n"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":0,"character":6}}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 8, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///main.cpp"},"contentChanges":[{"range":{"start":{"line":100,"character":0},"end":{"line":100,"character":0}},"text": "foo"}]}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":0,"character":6}}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32602, -# CHECK-NEXT: "message": "trying to get AST for non-added document" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0" -# CHECK-NEXT:} ---- -{"jsonrpc":"2.0","id":4,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/too_large.test =================================================================== --- /dev/null +++ test/clangd/too_large.test @@ -1,7 +0,0 @@ -# RUN: not clangd -run-synchronously < %s 2>&1 | FileCheck -check-prefix=STDERR %s -# vim: fileformat=dos -# It is absolutely vital that this file has CRLF line endings. -# -Content-Length: 2147483648 - -# STDERR: Refusing to read message Index: test/clangd/trace.test =================================================================== --- /dev/null +++ test/clangd/trace.test @@ -1,28 +0,0 @@ -# RUN: env CLANGD_TRACE=%t clangd -lit-test < %s && FileCheck %s < %t -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c","version":1,"text":"void main() {}"}}} -# These assertions are a bit loose, to avoid brittleness. -# CHECK: { -# CHECK: "displayTimeUnit": "ns", -# CHECK: "traceEvents": [ -# CHECK: { -# CHECK: "ph": "X", -# CHECK: "name": "BuildPreamble", -# CHECK: "args": { -# CHECK: "File": "{{.*(/|\\)}}foo.c" -# CHECK: }, -# CHECK: } -# CHECK: { -# CHECK: "ph": "X", -# CHECK: "name": "BuildAST", -# CHECK: "args": { -# CHECK: "File": "{{.*(/|\\)}}foo.c" -# CHECK: }, -# CHECK: } -# CHECK: ] -# CHECK: } ---- -{"jsonrpc":"2.0","id":5,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/tweaks-format.test =================================================================== --- /dev/null +++ test/clangd/tweaks-format.test @@ -1,50 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cc","languageId":"cpp","version":1,"text":"int f() { if (true) { return 1; } else {} }"}}} ---- -{"jsonrpc":"2.0","id":5,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///main.cc"},"range":{"start":{"line":0,"character":11},"end":{"line":0,"character":11}},"context":{"diagnostics":[]}}} ---- -{"jsonrpc":"2.0","id":6,"method":"workspace/executeCommand","params":{"command":"clangd.applyTweak","arguments":[{"file":"test:///main.cc","selection":{"end":{"character":11,"line":0},"start":{"character":11,"line":0}},"tweakID":"SwapIfBranches"}]}} -# CHECK: "newText": "\n ", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 10, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 9, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "{\n }", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 33, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 20, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "newText": "{\n return 1;\n }\n", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 42, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 39, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":3,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/type-hierarchy.test =================================================================== --- /dev/null +++ test/clangd/type-hierarchy.test @@ -1,92 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct Parent {};\nstruct Child1 : Parent {};\nstruct Child2 : Child1 {};"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/typeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":1,"resolve":1}} -# CHECK: "id": 1 -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Child2", -# CHECK-NEXT: "parents": [ -# CHECK-NEXT: { -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Child1", -# CHECK-NEXT: "parents": [ -# CHECK-NEXT: { -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Parent", -# CHECK-NEXT: "parents": [], -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 15, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 0, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "selectionRange": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 24, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 0, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "selectionRange": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 24, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 0, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "selectionRange": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" -# CHECK-NEXT: } ---- -{"jsonrpc":"2.0","id":2,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/unsupported-method.test =================================================================== --- /dev/null +++ test/clangd/unsupported-method.test @@ -1,16 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":""}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/jumpInTheAirLikeYouJustDontCare","params":{}} -# CHECK: "error": { -# CHECK-NEXT: "code": -32601, -# CHECK-NEXT: "message": "method not found" -# CHECK-NEXT: }, -# CHECK-NEXT: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0" ---- -{"jsonrpc":"2.0","id":2,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/utf8.test =================================================================== --- /dev/null +++ test/clangd/utf8.test @@ -1,32 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -# This test verifies that we can negotiate UTF-8 offsets via protocol extension. -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"offsetEncoding":["utf-8","utf-16"]},"trace":"off"}} -# CHECK: "offsetEncoding": "utf-8" ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"/*ö*/int x;\nint y=x;"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":1,"character":6}}} -# /*ö*/int x; -# 01234567890 -# x is character (and utf-16) range [9,10) but byte range [10,11). -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 11, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 10, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":10000,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/xpc/initialize.test =================================================================== --- /dev/null +++ test/clangd/xpc/initialize.test @@ -1,10 +0,0 @@ -# RUN: clangd-xpc-test-client < %s | FileCheck %s -# REQUIRES: clangd-xpc-support - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"test:///workspace","capabilities":{},"trace":"off"}} -# CHECK: {"id":0,"jsonrpc":"2.0","result":{"capabilities" - -{"jsonrpc":"2.0","id":3,"method":"shutdown"} -# CHECK: {"id":3,"jsonrpc":"2.0","result":null} - -{"jsonrpc":"2.0","method":"exit"} Index: test/clangd/xrefs.test =================================================================== --- /dev/null +++ test/clangd/xrefs.test @@ -1,92 +0,0 @@ -# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---- -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"extern int x;\nint x = 0;\nint y = x;"}}} ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":8}}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/{{([A-Z]:/)?}}main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -# Toggle: we're on the definition, so jump to the declaration. -{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":1,"character":4}}} -# CHECK: "id": 1, -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 12, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 11, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/{{([A-Z]:/)?}}main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":8}}} -# CHECK: "id": 1 -# CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": [ -# CHECK-NEXT: { -# CHECK-NEXT: "kind": 1, -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 12, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 11, -# CHECK-NEXT: "line": 0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "kind": 1, -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 5, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 4, -# CHECK-NEXT: "line": 1 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: { -# CHECK-NEXT: "kind": 2, -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 9, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 8, -# CHECK-NEXT: "line": 2 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: ] ---- -{"jsonrpc":"2.0","id":10000,"method":"shutdown"} ---- -{"jsonrpc":"2.0","method":"exit"} Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -5,15 +5,6 @@ add_unittest(ExtraToolsUnitTests ${test_dirname} ${ARGN}) endfunction() -if(CLANG_BUILT_STANDALONE) - # LLVMTestingSupport library is needed for clangd tests. - if (EXISTS ${LLVM_MAIN_SRC_DIR}/lib/Testing/Support - AND NOT TARGET LLVMTestingSupport) - add_subdirectory(${LLVM_MAIN_SRC_DIR}/lib/Testing/Support - lib/Testing/Support) - endif() -endif() - add_subdirectory(clang-apply-replacements) add_subdirectory(clang-change-namespace) add_subdirectory(clang-doc) @@ -21,4 +12,3 @@ add_subdirectory(clang-move) add_subdirectory(clang-query) add_subdirectory(clang-tidy) -add_subdirectory(clangd) Index: unittests/clangd/Annotations.h =================================================================== --- /dev/null +++ unittests/clangd/Annotations.h @@ -1,39 +0,0 @@ -//===--- Annotations.h - Annotated source code for tests ---------*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// A clangd-specific version of llvm/Testing/Support/Annotations.h, replaces -// offsets and offset-based ranges with types from the LSP protocol. -//===---------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_ANNOTATIONS_H -#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_ANNOTATIONS_H - -#include "Protocol.h" -#include "llvm/Testing/Support/Annotations.h" - -namespace clang { -namespace clangd { - -/// Same as llvm::Annotations, but adjusts functions to LSP-specific types for -/// positions and ranges. -class Annotations : public llvm::Annotations { - using Base = llvm::Annotations; - -public: - using llvm::Annotations::Annotations; - - Position point(llvm::StringRef Name = "") const; - std::vector points(llvm::StringRef Name = "") const; - - clangd::Range range(llvm::StringRef Name = "") const; - std::vector ranges(llvm::StringRef Name = "") const; -}; - -} // namespace clangd -} // namespace clang - -#endif // LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_ANNOTATIONS_H Index: unittests/clangd/Annotations.cpp =================================================================== --- /dev/null +++ unittests/clangd/Annotations.cpp @@ -1,53 +0,0 @@ -//===--- Annotations.cpp - Annotated source code for unit tests --*- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Annotations.h" -#include "SourceCode.h" - -namespace clang { -namespace clangd { - -Position Annotations::point(llvm::StringRef Name) const { - return offsetToPosition(code(), Base::point(Name)); -} - -std::vector Annotations::points(llvm::StringRef Name) const { - auto Offsets = Base::points(Name); - - std::vector Ps; - Ps.reserve(Offsets.size()); - for (size_t O : Offsets) - Ps.push_back(offsetToPosition(code(), O)); - - return Ps; -} - -static clangd::Range toLSPRange(llvm::StringRef Code, Annotations::Range R) { - clangd::Range LSPRange; - LSPRange.start = offsetToPosition(Code, R.Begin); - LSPRange.end = offsetToPosition(Code, R.End); - return LSPRange; -} - -clangd::Range Annotations::range(llvm::StringRef Name) const { - return toLSPRange(code(), Base::range(Name)); -} - -std::vector Annotations::ranges(llvm::StringRef Name) const { - auto OffsetRanges = Base::ranges(Name); - - std::vector Rs; - Rs.reserve(OffsetRanges.size()); - for (Annotations::Range R : OffsetRanges) - Rs.push_back(toLSPRange(code(), R)); - - return Rs; -} - -} // namespace clangd -} // namespace clang Index: unittests/clangd/BackgroundIndexTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/BackgroundIndexTests.cpp @@ -1,465 +0,0 @@ -#include "SyncAPI.h" -#include "TestFS.h" -#include "index/Background.h" -#include "llvm/Support/ScopedPrinter.h" -#include "llvm/Support/Threading.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include - -using testing::_; -using testing::AllOf; -using testing::Contains; -using testing::ElementsAre; -using testing::Not; -using testing::UnorderedElementsAre; - -namespace clang { -namespace clangd { - -MATCHER_P(Named, N, "") { return arg.Name == N; } -MATCHER(Declared, "") { - return !StringRef(arg.CanonicalDeclaration.FileURI).empty(); -} -MATCHER(Defined, "") { return !StringRef(arg.Definition.FileURI).empty(); } -MATCHER_P(FileURI, F, "") { return StringRef(arg.Location.FileURI) == F; } -testing::Matcher -RefsAre(std::vector> Matchers) { - return ElementsAre(testing::Pair(_, UnorderedElementsAreArray(Matchers))); -} -// URI cannot be empty since it references keys in the IncludeGraph. -MATCHER(EmptyIncludeNode, "") { - return !arg.IsTU && !arg.URI.empty() && arg.Digest == FileDigest{{0}} && - arg.DirectIncludes.empty(); -} - -class MemoryShardStorage : public BackgroundIndexStorage { - mutable std::mutex StorageMu; - llvm::StringMap &Storage; - size_t &CacheHits; - -public: - MemoryShardStorage(llvm::StringMap &Storage, size_t &CacheHits) - : Storage(Storage), CacheHits(CacheHits) {} - llvm::Error storeShard(llvm::StringRef ShardIdentifier, - IndexFileOut Shard) const override { - std::lock_guard Lock(StorageMu); - AccessedPaths.insert(ShardIdentifier); - Storage[ShardIdentifier] = llvm::to_string(Shard); - return llvm::Error::success(); - } - std::unique_ptr - loadShard(llvm::StringRef ShardIdentifier) const override { - std::lock_guard Lock(StorageMu); - AccessedPaths.insert(ShardIdentifier); - if (Storage.find(ShardIdentifier) == Storage.end()) { - return nullptr; - } - auto IndexFile = readIndexFile(Storage[ShardIdentifier]); - if (!IndexFile) { - ADD_FAILURE() << "Error while reading " << ShardIdentifier << ':' - << IndexFile.takeError(); - return nullptr; - } - CacheHits++; - return llvm::make_unique(std::move(*IndexFile)); - } - - mutable llvm::StringSet<> AccessedPaths; -}; - -class BackgroundIndexTest : public ::testing::Test { -protected: - BackgroundIndexTest() { BackgroundIndex::preventThreadStarvationInTests(); } -}; - -TEST_F(BackgroundIndexTest, NoCrashOnErrorFile) { - MockFSProvider FS; - FS.Files[testPath("root/A.cc")] = "error file"; - llvm::StringMap Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); -} - -TEST_F(BackgroundIndexTest, IndexTwoFiles) { - MockFSProvider FS; - // a.h yields different symbols when included by A.cc vs B.cc. - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - #if A - class A_CC {}; - #else - class B_CC{}; - #endif - )cpp"; - FS.Files[testPath("root/A.cc")] = - "#include \"A.h\"\nvoid g() { (void)common; }"; - FS.Files[testPath("root/B.cc")] = - R"cpp( - #define A 0 - #include "A.h" - void f_b() { - (void)common; - })cpp"; - llvm::StringMap Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - EXPECT_THAT( - runFuzzyFind(Idx, ""), - UnorderedElementsAre(Named("common"), Named("A_CC"), Named("g"), - AllOf(Named("f_b"), Declared(), Not(Defined())))); - - Cmd.Filename = testPath("root/B.cc"); - Cmd.CommandLine = {"clang++", Cmd.Filename}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - // B_CC is dropped as we don't collect symbols from A.h in this compilation. - EXPECT_THAT(runFuzzyFind(Idx, ""), - UnorderedElementsAre(Named("common"), Named("A_CC"), Named("g"), - AllOf(Named("f_b"), Declared(), Defined()))); - - auto Syms = runFuzzyFind(Idx, "common"); - EXPECT_THAT(Syms, UnorderedElementsAre(Named("common"))); - auto Common = *Syms.begin(); - EXPECT_THAT(getRefs(Idx, Common.ID), - RefsAre({FileURI("unittest:///root/A.h"), - FileURI("unittest:///root/A.cc"), - FileURI("unittest:///root/B.cc")})); -} - -TEST_F(BackgroundIndexTest, ShardStorageTest) { - MockFSProvider FS; - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - class A_CC {}; - )cpp"; - std::string A_CC = "#include \"A.h\"\nvoid g() { (void)common; }"; - FS.Files[testPath("root/A.cc")] = A_CC; - - llvm::StringMap Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; - // Check nothing is loaded from Storage, but A.cc and A.h has been stored. - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 0U); - EXPECT_EQ(Storage.size(), 2U); - - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. - EXPECT_EQ(Storage.size(), 2U); - - auto ShardHeader = MSS.loadShard(testPath("root/A.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_THAT( - *ShardHeader->Symbols, - UnorderedElementsAre(Named("common"), Named("A_CC"), - AllOf(Named("f_b"), Declared(), Not(Defined())))); - for (const auto &Ref : *ShardHeader->Refs) - EXPECT_THAT(Ref.second, - UnorderedElementsAre(FileURI("unittest:///root/A.h"))); - - auto ShardSource = MSS.loadShard(testPath("root/A.cc")); - EXPECT_NE(ShardSource, nullptr); - EXPECT_THAT(*ShardSource->Symbols, UnorderedElementsAre(Named("g"))); - EXPECT_THAT(*ShardSource->Refs, RefsAre({FileURI("unittest:///root/A.cc")})); -} - -TEST_F(BackgroundIndexTest, DirectIncludesTest) { - MockFSProvider FS; - FS.Files[testPath("root/B.h")] = ""; - FS.Files[testPath("root/A.h")] = R"cpp( - #include "B.h" - void common(); - void f_b(); - class A_CC {}; - )cpp"; - std::string A_CC = "#include \"A.h\"\nvoid g() { (void)common; }"; - FS.Files[testPath("root/A.cc")] = A_CC; - - llvm::StringMap Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - - auto ShardSource = MSS.loadShard(testPath("root/A.cc")); - EXPECT_TRUE(ShardSource->Sources); - EXPECT_EQ(ShardSource->Sources->size(), 2U); // A.cc, A.h - EXPECT_THAT( - ShardSource->Sources->lookup("unittest:///root/A.cc").DirectIncludes, - UnorderedElementsAre("unittest:///root/A.h")); - EXPECT_NE(ShardSource->Sources->lookup("unittest:///root/A.cc").Digest, - FileDigest{{0}}); - EXPECT_THAT(ShardSource->Sources->lookup("unittest:///root/A.h"), - EmptyIncludeNode()); - - auto ShardHeader = MSS.loadShard(testPath("root/A.h")); - EXPECT_TRUE(ShardHeader->Sources); - EXPECT_EQ(ShardHeader->Sources->size(), 2U); // A.h, B.h - EXPECT_THAT( - ShardHeader->Sources->lookup("unittest:///root/A.h").DirectIncludes, - UnorderedElementsAre("unittest:///root/B.h")); - EXPECT_NE(ShardHeader->Sources->lookup("unittest:///root/A.h").Digest, - FileDigest{{0}}); - EXPECT_THAT(ShardHeader->Sources->lookup("unittest:///root/B.h"), - EmptyIncludeNode()); -} - -// FIXME: figure out the right timeouts or rewrite to not use the timeouts and -// re-enable. -TEST_F(BackgroundIndexTest, DISABLED_PeriodicalIndex) { - MockFSProvider FS; - llvm::StringMap Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx( - Context::empty(), FS, CDB, [&](llvm::StringRef) { return &MSS; }, - /*BuildIndexPeriodMs=*/500); - - FS.Files[testPath("root/A.cc")] = "#include \"A.h\""; - - tooling::CompileCommand Cmd; - FS.Files[testPath("root/A.h")] = "class X {};"; - Cmd.Filename = testPath("root/A.cc"); - Cmd.CommandLine = {"clang++", Cmd.Filename}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre()); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("X"))); - - FS.Files[testPath("root/A.h")] = "class Y {};"; - FS.Files[testPath("root/A.cc")] += " "; // Force reindex the file. - Cmd.CommandLine = {"clang++", "-DA=1", testPath("root/A.cc")}; - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("X"))); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - EXPECT_THAT(runFuzzyFind(Idx, ""), ElementsAre(Named("Y"))); -} - -TEST_F(BackgroundIndexTest, ShardStorageLoad) { - MockFSProvider FS; - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - class A_CC {}; - )cpp"; - FS.Files[testPath("root/A.cc")] = - "#include \"A.h\"\nvoid g() { (void)common; }"; - - llvm::StringMap Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; - // Check nothing is loaded from Storage, but A.cc and A.h has been stored. - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - - // Change header. - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - class A_CC {}; - class A_CCnew {}; - )cpp"; - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. - - // Check if the new symbol has arrived. - auto ShardHeader = MSS.loadShard(testPath("root/A.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_THAT(*ShardHeader->Symbols, Contains(Named("A_CCnew"))); - - // Change source. - FS.Files[testPath("root/A.cc")] = - "#include \"A.h\"\nvoid g() { (void)common; }\nvoid f_b() {}"; - { - CacheHits = 0; - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 2U); // Check both A.cc and A.h loaded from cache. - - // Check if the new symbol has arrived. - ShardHeader = MSS.loadShard(testPath("root/A.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_THAT(*ShardHeader->Symbols, Contains(Named("A_CCnew"))); - auto ShardSource = MSS.loadShard(testPath("root/A.cc")); - EXPECT_NE(ShardSource, nullptr); - EXPECT_THAT(*ShardSource->Symbols, - Contains(AllOf(Named("f_b"), Declared(), Defined()))); -} - -TEST_F(BackgroundIndexTest, ShardStorageEmptyFile) { - MockFSProvider FS; - FS.Files[testPath("root/A.h")] = R"cpp( - void common(); - void f_b(); - class A_CC {}; - )cpp"; - FS.Files[testPath("root/B.h")] = R"cpp( - #include "A.h" - )cpp"; - FS.Files[testPath("root/A.cc")] = - "#include \"B.h\"\nvoid g() { (void)common; }"; - - llvm::StringMap Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - - tooling::CompileCommand Cmd; - Cmd.Filename = testPath("root/A.cc"); - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", testPath("root/A.cc")}; - // Check that A.cc, A.h and B.h has been stored. - { - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_THAT(Storage.keys(), - UnorderedElementsAre(testPath("root/A.cc"), testPath("root/A.h"), - testPath("root/B.h"))); - auto ShardHeader = MSS.loadShard(testPath("root/B.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_TRUE(ShardHeader->Symbols->empty()); - - // Check that A.cc, A.h and B.h has been loaded. - { - CacheHits = 0; - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 3U); - - // Update B.h to contain some symbols. - FS.Files[testPath("root/B.h")] = R"cpp( - #include "A.h" - void new_func(); - )cpp"; - // Check that B.h has been stored with new contents. - { - CacheHits = 0; - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - CDB.setCompileCommand(testPath("root/A.cc"), Cmd); - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - } - EXPECT_EQ(CacheHits, 3U); - ShardHeader = MSS.loadShard(testPath("root/B.h")); - EXPECT_NE(ShardHeader, nullptr); - EXPECT_THAT(*ShardHeader->Symbols, - Contains(AllOf(Named("new_func"), Declared(), Not(Defined())))); -} - -TEST_F(BackgroundIndexTest, NoDotsInAbsPath) { - MockFSProvider FS; - llvm::StringMap Storage; - size_t CacheHits = 0; - MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr); - BackgroundIndex Idx(Context::empty(), FS, CDB, - [&](llvm::StringRef) { return &MSS; }); - - tooling::CompileCommand Cmd; - FS.Files[testPath("root/A.cc")] = ""; - Cmd.Filename = "../A.cc"; - Cmd.Directory = testPath("root/build"); - Cmd.CommandLine = {"clang++", "../A.cc"}; - CDB.setCompileCommand(testPath("root/build/../A.cc"), Cmd); - - FS.Files[testPath("root/B.cc")] = ""; - Cmd.Filename = "./B.cc"; - Cmd.Directory = testPath("root"); - Cmd.CommandLine = {"clang++", "./B.cc"}; - CDB.setCompileCommand(testPath("root/./B.cc"), Cmd); - - ASSERT_TRUE(Idx.blockUntilIdleForTest()); - for (llvm::StringRef AbsPath : MSS.AccessedPaths.keys()) { - EXPECT_FALSE(AbsPath.contains("./")) << AbsPath; - EXPECT_FALSE(AbsPath.contains("../")) << AbsPath; - } -} - -} // namespace clangd -} // namespace clang Index: unittests/clangd/CancellationTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/CancellationTests.cpp @@ -1,65 +0,0 @@ -#include "Cancellation.h" -#include "Context.h" -#include "Threading.h" -#include "llvm/Support/Error.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include -#include -#include - -namespace clang { -namespace clangd { -namespace { - -TEST(CancellationTest, CancellationTest) { - auto Task = cancelableTask(); - WithContext ContextWithCancellation(std::move(Task.first)); - EXPECT_FALSE(isCancelled()); - Task.second(); - EXPECT_TRUE(isCancelled()); -} - -TEST(CancellationTest, CancelerDiesContextLives) { - llvm::Optional ContextWithCancellation; - { - auto Task = cancelableTask(); - ContextWithCancellation.emplace(std::move(Task.first)); - EXPECT_FALSE(isCancelled()); - Task.second(); - EXPECT_TRUE(isCancelled()); - } - EXPECT_TRUE(isCancelled()); -} - -TEST(CancellationTest, TaskContextDiesHandleLives) { - auto Task = cancelableTask(); - { - WithContext ContextWithCancellation(std::move(Task.first)); - EXPECT_FALSE(isCancelled()); - Task.second(); - EXPECT_TRUE(isCancelled()); - } - // Still should be able to cancel without any problems. - Task.second(); -} - -TEST(CancellationTest, AsynCancellationTest) { - std::atomic HasCancelled(false); - Notification Cancelled; - auto TaskToBeCancelled = [&](Context Ctx) { - WithContext ContextGuard(std::move(Ctx)); - Cancelled.wait(); - HasCancelled = isCancelled(); - }; - auto Task = cancelableTask(); - std::thread AsyncTask(TaskToBeCancelled, std::move(Task.first)); - Task.second(); - Cancelled.notify(); - AsyncTask.join(); - - EXPECT_TRUE(HasCancelled); -} -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/ClangdTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/ClangdTests.cpp @@ -1,1162 +0,0 @@ -//===-- ClangdTests.cpp - Clangd unit tests ---------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Annotations.h" -#include "ClangdLSPServer.h" -#include "ClangdServer.h" -#include "GlobalCompilationDatabase.h" -#include "Matchers.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "Threading.h" -#include "URI.h" -#include "clang/Config/config.h" -#include "clang/Sema/CodeCompleteConsumer.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/Regex.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include -#include - -namespace clang { -namespace clangd { - -namespace { - -using ::testing::ElementsAre; -using ::testing::Field; -using ::testing::Gt; -using ::testing::IsEmpty; -using ::testing::Pair; -using ::testing::UnorderedElementsAre; - -MATCHER_P2(DeclAt, File, Range, "") { - return arg.PreferredDeclaration == - Location{URIForFile::canonicalize(File, testRoot()), Range}; -} - -bool diagsContainErrors(const std::vector &Diagnostics) { - for (auto D : Diagnostics) { - if (D.Severity == DiagnosticsEngine::Error || - D.Severity == DiagnosticsEngine::Fatal) - return true; - } - return false; -} - -class ErrorCheckingDiagConsumer : public DiagnosticsConsumer { -public: - void onDiagnosticsReady(PathRef File, - std::vector Diagnostics) override { - bool HadError = diagsContainErrors(Diagnostics); - std::lock_guard Lock(Mutex); - HadErrorInLastDiags = HadError; - } - - bool hadErrorInLastDiags() { - std::lock_guard Lock(Mutex); - return HadErrorInLastDiags; - } - -private: - std::mutex Mutex; - bool HadErrorInLastDiags = false; -}; - -/// For each file, record whether the last published diagnostics contained at -/// least one error. -class MultipleErrorCheckingDiagConsumer : public DiagnosticsConsumer { -public: - void onDiagnosticsReady(PathRef File, - std::vector Diagnostics) override { - bool HadError = diagsContainErrors(Diagnostics); - - std::lock_guard Lock(Mutex); - LastDiagsHadError[File] = HadError; - } - - /// Exposes all files consumed by onDiagnosticsReady in an unspecified order. - /// For each file, a bool value indicates whether the last diagnostics - /// contained an error. - std::vector> filesWithDiags() const { - std::vector> Result; - std::lock_guard Lock(Mutex); - - for (const auto &it : LastDiagsHadError) { - Result.emplace_back(it.first(), it.second); - } - - return Result; - } - - void clear() { - std::lock_guard Lock(Mutex); - LastDiagsHadError.clear(); - } - -private: - mutable std::mutex Mutex; - llvm::StringMap LastDiagsHadError; -}; - -/// Replaces all patterns of the form 0x123abc with spaces -std::string replacePtrsInDump(std::string const &Dump) { - llvm::Regex RE("0x[0-9a-fA-F]+"); - llvm::SmallVector Matches; - llvm::StringRef Pending = Dump; - - std::string Result; - while (RE.match(Pending, &Matches)) { - assert(Matches.size() == 1 && "Exactly one match expected"); - auto MatchPos = Matches[0].data() - Pending.data(); - - Result += Pending.take_front(MatchPos); - Pending = Pending.drop_front(MatchPos + Matches[0].size()); - } - Result += Pending; - - return Result; -} - -std::string dumpASTWithoutMemoryLocs(ClangdServer &Server, PathRef File) { - auto DumpWithMemLocs = runDumpAST(Server, File); - return replacePtrsInDump(DumpWithMemLocs); -} - -class ClangdVFSTest : public ::testing::Test { -protected: - std::string parseSourceAndDumpAST( - PathRef SourceFileRelPath, llvm::StringRef SourceContents, - std::vector> ExtraFiles = {}, - bool ExpectErrors = false) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - for (const auto &FileWithContents : ExtraFiles) - FS.Files[testPath(FileWithContents.first)] = FileWithContents.second; - - auto SourceFilename = testPath(SourceFileRelPath); - Server.addDocument(SourceFilename, SourceContents); - auto Result = dumpASTWithoutMemoryLocs(Server, SourceFilename); - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_EQ(ExpectErrors, DiagConsumer.hadErrorInLastDiags()); - return Result; - } -}; - -TEST_F(ClangdVFSTest, Parse) { - // FIXME: figure out a stable format for AST dumps, so that we can check the - // output of the dump itself is equal to the expected one, not just that it's - // different. - auto Empty = parseSourceAndDumpAST("foo.cpp", "", {}); - auto OneDecl = parseSourceAndDumpAST("foo.cpp", "int a;", {}); - auto SomeDecls = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;", {}); - EXPECT_NE(Empty, OneDecl); - EXPECT_NE(Empty, SomeDecls); - EXPECT_NE(SomeDecls, OneDecl); - - auto Empty2 = parseSourceAndDumpAST("foo.cpp", ""); - auto OneDecl2 = parseSourceAndDumpAST("foo.cpp", "int a;"); - auto SomeDecls2 = parseSourceAndDumpAST("foo.cpp", "int a; int b; int c;"); - EXPECT_EQ(Empty, Empty2); - EXPECT_EQ(OneDecl, OneDecl2); - EXPECT_EQ(SomeDecls, SomeDecls2); -} - -TEST_F(ClangdVFSTest, ParseWithHeader) { - parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {}, - /*ExpectErrors=*/true); - parseSourceAndDumpAST("foo.cpp", "#include \"foo.h\"", {{"foo.h", ""}}, - /*ExpectErrors=*/false); - - const auto SourceContents = R"cpp( -#include "foo.h" -int b = a; -)cpp"; - parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", ""}}, - /*ExpectErrors=*/true); - parseSourceAndDumpAST("foo.cpp", SourceContents, {{"foo.h", "int a;"}}, - /*ExpectErrors=*/false); -} - -TEST_F(ClangdVFSTest, Reparse) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - const auto SourceContents = R"cpp( -#include "foo.h" -int b = a; -)cpp"; - - auto FooCpp = testPath("foo.cpp"); - - FS.Files[testPath("foo.h")] = "int a;"; - FS.Files[FooCpp] = SourceContents; - - Server.addDocument(FooCpp, SourceContents); - auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - Server.addDocument(FooCpp, ""); - auto DumpParseEmpty = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - Server.addDocument(FooCpp, SourceContents); - auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - EXPECT_EQ(DumpParse1, DumpParse2); - EXPECT_NE(DumpParse1, DumpParseEmpty); -} - -TEST_F(ClangdVFSTest, ReparseOnHeaderChange) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - const auto SourceContents = R"cpp( -#include "foo.h" -int b = a; -)cpp"; - - auto FooCpp = testPath("foo.cpp"); - auto FooH = testPath("foo.h"); - - FS.Files[FooH] = "int a;"; - FS.Files[FooCpp] = SourceContents; - - Server.addDocument(FooCpp, SourceContents); - auto DumpParse1 = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - FS.Files[FooH] = ""; - Server.addDocument(FooCpp, SourceContents); - auto DumpParseDifferent = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); - - FS.Files[FooH] = "int a;"; - Server.addDocument(FooCpp, SourceContents); - auto DumpParse2 = dumpASTWithoutMemoryLocs(Server, FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - EXPECT_EQ(DumpParse1, DumpParse2); - EXPECT_NE(DumpParse1, DumpParseDifferent); -} - -TEST_F(ClangdVFSTest, PropagatesContexts) { - static Key Secret; - struct FSProvider : public FileSystemProvider { - IntrusiveRefCntPtr getFileSystem() const override { - Got = Context::current().getExisting(Secret); - return buildTestFS({}); - } - mutable int Got; - } FS; - struct DiagConsumer : public DiagnosticsConsumer { - void onDiagnosticsReady(PathRef File, - std::vector Diagnostics) override { - Got = Context::current().getExisting(Secret); - } - int Got; - } DiagConsumer; - MockCompilationDatabase CDB; - - // Verify that the context is plumbed to the FS provider and diagnostics. - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - { - WithContextValue Entrypoint(Secret, 42); - Server.addDocument(testPath("foo.cpp"), "void main(){}"); - } - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_EQ(FS.Got, 42); - EXPECT_EQ(DiagConsumer.Got, 42); -} - -// Only enable this test on Unix -#ifdef LLVM_ON_UNIX -TEST_F(ClangdVFSTest, SearchLibDir) { - // Checks that searches for GCC installation is done through vfs. - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - CDB.ExtraClangFlags.insert(CDB.ExtraClangFlags.end(), - {"-xc++", "-target", "x86_64-linux-unknown", - "-m64", "--gcc-toolchain=/randomusr", - "-stdlib=libstdc++"}); - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - // Just a random gcc version string - SmallString<8> Version("4.9.3"); - - // A lib dir for gcc installation - SmallString<64> LibDir("/randomusr/lib/gcc/x86_64-linux-gnu"); - llvm::sys::path::append(LibDir, Version); - - // Put crtbegin.o into LibDir/64 to trick clang into thinking there's a gcc - // installation there. - SmallString<64> DummyLibFile; - llvm::sys::path::append(DummyLibFile, LibDir, "64", "crtbegin.o"); - FS.Files[DummyLibFile] = ""; - - SmallString<64> IncludeDir("/randomusr/include/c++"); - llvm::sys::path::append(IncludeDir, Version); - - SmallString<64> StringPath; - llvm::sys::path::append(StringPath, IncludeDir, "string"); - FS.Files[StringPath] = "class mock_string {};"; - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents = R"cpp( -#include -mock_string x; -)cpp"; - FS.Files[FooCpp] = SourceContents; - - runAddDocument(Server, FooCpp, SourceContents); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - - const auto SourceContentsWithError = R"cpp( -#include -std::string x; -)cpp"; - runAddDocument(Server, FooCpp, SourceContentsWithError); - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); -} -#endif // LLVM_ON_UNIX - -TEST_F(ClangdVFSTest, ForceReparseCompileCommand) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents1 = R"cpp( -template -struct foo { T x; }; -)cpp"; - const auto SourceContents2 = R"cpp( -template -struct bar { T x; }; -)cpp"; - - FS.Files[FooCpp] = ""; - - // First parse files in C mode and check they produce errors. - CDB.ExtraClangFlags = {"-xc"}; - runAddDocument(Server, FooCpp, SourceContents1); - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); - runAddDocument(Server, FooCpp, SourceContents2); - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); - - // Now switch to C++ mode. - CDB.ExtraClangFlags = {"-xc++"}; - runAddDocument(Server, FooCpp, SourceContents2, WantDiagnostics::Auto); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - // Subsequent addDocument calls should finish without errors too. - runAddDocument(Server, FooCpp, SourceContents1); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - runAddDocument(Server, FooCpp, SourceContents2); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); -} - -TEST_F(ClangdVFSTest, ForceReparseCompileCommandDefines) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents = R"cpp( -#ifdef WITH_ERROR -this -#endif - -int main() { return 0; } -)cpp"; - FS.Files[FooCpp] = ""; - - // Parse with define, we expect to see the errors. - CDB.ExtraClangFlags = {"-DWITH_ERROR"}; - runAddDocument(Server, FooCpp, SourceContents); - EXPECT_TRUE(DiagConsumer.hadErrorInLastDiags()); - - // Parse without the define, no errors should be produced. - CDB.ExtraClangFlags = {}; - runAddDocument(Server, FooCpp, SourceContents, WantDiagnostics::Auto); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); - // Subsequent addDocument call should finish without errors too. - runAddDocument(Server, FooCpp, SourceContents); - EXPECT_FALSE(DiagConsumer.hadErrorInLastDiags()); -} - -// Test ClangdServer.reparseOpenedFiles. -TEST_F(ClangdVFSTest, ReparseOpenedFiles) { - Annotations FooSource(R"cpp( -#ifdef MACRO -static void $one[[bob]]() {} -#else -static void $two[[bob]]() {} -#endif - -int main () { bo^b (); return 0; } -)cpp"); - - Annotations BarSource(R"cpp( -#ifdef MACRO -this is an error -#endif -)cpp"); - - Annotations BazSource(R"cpp( -int hello; -)cpp"); - - MockFSProvider FS; - MockCompilationDatabase CDB; - MultipleErrorCheckingDiagConsumer DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - auto BarCpp = testPath("bar.cpp"); - auto BazCpp = testPath("baz.cpp"); - - FS.Files[FooCpp] = ""; - FS.Files[BarCpp] = ""; - FS.Files[BazCpp] = ""; - - CDB.ExtraClangFlags = {"-DMACRO=1"}; - Server.addDocument(FooCpp, FooSource.code()); - Server.addDocument(BarCpp, BarSource.code()); - Server.addDocument(BazCpp, BazSource.code()); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - EXPECT_THAT(DiagConsumer.filesWithDiags(), - UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, true), - Pair(BazCpp, false))); - - auto Locations = runLocateSymbolAt(Server, FooCpp, FooSource.point()); - EXPECT_TRUE(bool(Locations)); - EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("one")))); - - // Undefine MACRO, close baz.cpp. - CDB.ExtraClangFlags.clear(); - DiagConsumer.clear(); - Server.removeDocument(BazCpp); - Server.addDocument(FooCpp, FooSource.code(), WantDiagnostics::Auto); - Server.addDocument(BarCpp, BarSource.code(), WantDiagnostics::Auto); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - EXPECT_THAT(DiagConsumer.filesWithDiags(), - UnorderedElementsAre(Pair(FooCpp, false), Pair(BarCpp, false))); - - Locations = runLocateSymbolAt(Server, FooCpp, FooSource.point()); - EXPECT_TRUE(bool(Locations)); - EXPECT_THAT(*Locations, ElementsAre(DeclAt(FooCpp, FooSource.range("two")))); -} - -TEST_F(ClangdVFSTest, MemoryUsage) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - Path FooCpp = testPath("foo.cpp"); - const auto SourceContents = R"cpp( -struct Something { - int method(); -}; -)cpp"; - Path BarCpp = testPath("bar.cpp"); - - FS.Files[FooCpp] = ""; - FS.Files[BarCpp] = ""; - - EXPECT_THAT(Server.getUsedBytesPerFile(), IsEmpty()); - - Server.addDocument(FooCpp, SourceContents); - Server.addDocument(BarCpp, SourceContents); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - EXPECT_THAT(Server.getUsedBytesPerFile(), - UnorderedElementsAre(Pair(FooCpp, Gt(0u)), Pair(BarCpp, Gt(0u)))); - - Server.removeDocument(FooCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(Server.getUsedBytesPerFile(), ElementsAre(Pair(BarCpp, Gt(0u)))); - - Server.removeDocument(BarCpp); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(Server.getUsedBytesPerFile(), IsEmpty()); -} - -TEST_F(ClangdVFSTest, InvalidCompileCommand) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - // clang cannot create CompilerInvocation if we pass two files in the - // CompileCommand. We pass the file in ExtraFlags once and CDB adds another - // one in getCompileCommand(). - CDB.ExtraClangFlags.push_back(FooCpp); - - // Clang can't parse command args in that case, but we shouldn't crash. - runAddDocument(Server, FooCpp, "int main() {}"); - - EXPECT_EQ(runDumpAST(Server, FooCpp), ""); - EXPECT_ERROR(runLocateSymbolAt(Server, FooCpp, Position())); - EXPECT_ERROR(runFindDocumentHighlights(Server, FooCpp, Position())); - EXPECT_ERROR(runRename(Server, FooCpp, Position(), "new_name")); - // Identifier-based fallback completion. - EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Position(), - clangd::CodeCompleteOptions())) - .Completions, - ElementsAre(Field(&CodeCompletion::Name, "int"), - Field(&CodeCompletion::Name, "main"))); - auto SigHelp = runSignatureHelp(Server, FooCpp, Position()); - ASSERT_TRUE(bool(SigHelp)) << "signatureHelp returned an error"; - EXPECT_THAT(SigHelp->signatures, IsEmpty()); -} - -class ClangdThreadingTest : public ClangdVFSTest {}; - -TEST_F(ClangdThreadingTest, StressTest) { - // Without 'static' clang gives an error for a usage inside TestDiagConsumer. - static const unsigned FilesCount = 5; - const unsigned RequestsCount = 500; - // Blocking requests wait for the parsing to complete, they slow down the test - // dramatically, so they are issued rarely. Each - // BlockingRequestInterval-request will be a blocking one. - const unsigned BlockingRequestInterval = 40; - - const auto SourceContentsWithoutErrors = R"cpp( -int a; -int b; -int c; -int d; -)cpp"; - - const auto SourceContentsWithErrors = R"cpp( -int a = x; -int b; -int c; -int d; -)cpp"; - - // Giving invalid line and column number should not crash ClangdServer, but - // just to make sure we're sometimes hitting the bounds inside the file we - // limit the intervals of line and column number that are generated. - unsigned MaxLineForFileRequests = 7; - unsigned MaxColumnForFileRequests = 10; - - std::vector FilePaths; - MockFSProvider FS; - for (unsigned I = 0; I < FilesCount; ++I) { - std::string Name = std::string("Foo") + std::to_string(I) + ".cpp"; - FS.Files[Name] = ""; - FilePaths.push_back(testPath(Name)); - } - - struct FileStat { - unsigned HitsWithoutErrors = 0; - unsigned HitsWithErrors = 0; - bool HadErrorsInLastDiags = false; - }; - - class TestDiagConsumer : public DiagnosticsConsumer { - public: - TestDiagConsumer() : Stats(FilesCount, FileStat()) {} - - void onDiagnosticsReady(PathRef File, - std::vector Diagnostics) override { - StringRef FileIndexStr = llvm::sys::path::stem(File); - ASSERT_TRUE(FileIndexStr.consume_front("Foo")); - - unsigned long FileIndex = std::stoul(FileIndexStr.str()); - - bool HadError = diagsContainErrors(Diagnostics); - - std::lock_guard Lock(Mutex); - if (HadError) - Stats[FileIndex].HitsWithErrors++; - else - Stats[FileIndex].HitsWithoutErrors++; - Stats[FileIndex].HadErrorsInLastDiags = HadError; - } - - std::vector takeFileStats() { - std::lock_guard Lock(Mutex); - return std::move(Stats); - } - - private: - std::mutex Mutex; - std::vector Stats; - }; - - struct RequestStats { - unsigned RequestsWithoutErrors = 0; - unsigned RequestsWithErrors = 0; - bool LastContentsHadErrors = false; - bool FileIsRemoved = true; - }; - - std::vector ReqStats; - ReqStats.reserve(FilesCount); - for (unsigned FileIndex = 0; FileIndex < FilesCount; ++FileIndex) - ReqStats.emplace_back(); - - TestDiagConsumer DiagConsumer; - { - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - // Prepare some random distributions for the test. - std::random_device RandGen; - - std::uniform_int_distribution FileIndexDist(0, FilesCount - 1); - // Pass a text that contains compiler errors to addDocument in about 20% of - // all requests. - std::bernoulli_distribution ShouldHaveErrorsDist(0.2); - // Line and Column numbers for requests that need them. - std::uniform_int_distribution LineDist(0, MaxLineForFileRequests); - std::uniform_int_distribution ColumnDist(0, MaxColumnForFileRequests); - - // Some helpers. - auto UpdateStatsOnAddDocument = [&](unsigned FileIndex, bool HadErrors) { - auto &Stats = ReqStats[FileIndex]; - - if (HadErrors) - ++Stats.RequestsWithErrors; - else - ++Stats.RequestsWithoutErrors; - Stats.LastContentsHadErrors = HadErrors; - Stats.FileIsRemoved = false; - }; - - auto UpdateStatsOnRemoveDocument = [&](unsigned FileIndex) { - auto &Stats = ReqStats[FileIndex]; - - Stats.FileIsRemoved = true; - }; - - auto AddDocument = [&](unsigned FileIndex, bool SkipCache) { - bool ShouldHaveErrors = ShouldHaveErrorsDist(RandGen); - Server.addDocument(FilePaths[FileIndex], - ShouldHaveErrors ? SourceContentsWithErrors - : SourceContentsWithoutErrors, - WantDiagnostics::Auto); - UpdateStatsOnAddDocument(FileIndex, ShouldHaveErrors); - }; - - // Various requests that we would randomly run. - auto AddDocumentRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - AddDocument(FileIndex, /*SkipCache=*/false); - }; - - auto ForceReparseRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - AddDocument(FileIndex, /*SkipCache=*/true); - }; - - auto RemoveDocumentRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - // Make sure we don't violate the ClangdServer's contract. - if (ReqStats[FileIndex].FileIsRemoved) - AddDocument(FileIndex, /*SkipCache=*/false); - - Server.removeDocument(FilePaths[FileIndex]); - UpdateStatsOnRemoveDocument(FileIndex); - }; - - auto CodeCompletionRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - // Make sure we don't violate the ClangdServer's contract. - if (ReqStats[FileIndex].FileIsRemoved) - AddDocument(FileIndex, /*SkipCache=*/false); - - Position Pos; - Pos.line = LineDist(RandGen); - Pos.character = ColumnDist(RandGen); - // FIXME(ibiryukov): Also test async completion requests. - // Simply putting CodeCompletion into async requests now would make - // tests slow, since there's no way to cancel previous completion - // requests as opposed to AddDocument/RemoveDocument, which are implicitly - // cancelled by any subsequent AddDocument/RemoveDocument request to the - // same file. - cantFail(runCodeComplete(Server, FilePaths[FileIndex], Pos, - clangd::CodeCompleteOptions())); - }; - - auto LocateSymbolRequest = [&]() { - unsigned FileIndex = FileIndexDist(RandGen); - // Make sure we don't violate the ClangdServer's contract. - if (ReqStats[FileIndex].FileIsRemoved) - AddDocument(FileIndex, /*SkipCache=*/false); - - Position Pos; - Pos.line = LineDist(RandGen); - Pos.character = ColumnDist(RandGen); - - ASSERT_TRUE(!!runLocateSymbolAt(Server, FilePaths[FileIndex], Pos)); - }; - - std::vector> AsyncRequests = { - AddDocumentRequest, ForceReparseRequest, RemoveDocumentRequest}; - std::vector> BlockingRequests = { - CodeCompletionRequest, LocateSymbolRequest}; - - // Bash requests to ClangdServer in a loop. - std::uniform_int_distribution AsyncRequestIndexDist( - 0, AsyncRequests.size() - 1); - std::uniform_int_distribution BlockingRequestIndexDist( - 0, BlockingRequests.size() - 1); - for (unsigned I = 1; I <= RequestsCount; ++I) { - if (I % BlockingRequestInterval != 0) { - // Issue an async request most of the time. It should be fast. - unsigned RequestIndex = AsyncRequestIndexDist(RandGen); - AsyncRequests[RequestIndex](); - } else { - // Issue a blocking request once in a while. - auto RequestIndex = BlockingRequestIndexDist(RandGen); - BlockingRequests[RequestIndex](); - } - } - ASSERT_TRUE(Server.blockUntilIdleForTest()); - } - - // Check some invariants about the state of the program. - std::vector Stats = DiagConsumer.takeFileStats(); - for (unsigned I = 0; I < FilesCount; ++I) { - if (!ReqStats[I].FileIsRemoved) { - ASSERT_EQ(Stats[I].HadErrorsInLastDiags, - ReqStats[I].LastContentsHadErrors); - } - - ASSERT_LE(Stats[I].HitsWithErrors, ReqStats[I].RequestsWithErrors); - ASSERT_LE(Stats[I].HitsWithoutErrors, ReqStats[I].RequestsWithoutErrors); - } -} - -TEST_F(ClangdVFSTest, CheckSourceHeaderSwitch) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto SourceContents = R"cpp( - #include "foo.h" - int b = a; - )cpp"; - - auto FooCpp = testPath("foo.cpp"); - auto FooH = testPath("foo.h"); - auto Invalid = testPath("main.cpp"); - - FS.Files[FooCpp] = SourceContents; - FS.Files[FooH] = "int a;"; - FS.Files[Invalid] = "int main() { \n return 0; \n }"; - - Optional PathResult = Server.switchSourceHeader(FooCpp); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), FooH); - - PathResult = Server.switchSourceHeader(FooH); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), FooCpp); - - SourceContents = R"c( - #include "foo.HH" - int b = a; - )c"; - - // Test with header file in capital letters and different extension, source - // file with different extension - auto FooC = testPath("bar.c"); - auto FooHH = testPath("bar.HH"); - - FS.Files[FooC] = SourceContents; - FS.Files[FooHH] = "int a;"; - - PathResult = Server.switchSourceHeader(FooC); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), FooHH); - - // Test with both capital letters - auto Foo2C = testPath("foo2.C"); - auto Foo2HH = testPath("foo2.HH"); - FS.Files[Foo2C] = SourceContents; - FS.Files[Foo2HH] = "int a;"; - - PathResult = Server.switchSourceHeader(Foo2C); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), Foo2HH); - - // Test with source file as capital letter and .hxx header file - auto Foo3C = testPath("foo3.C"); - auto Foo3HXX = testPath("foo3.hxx"); - - SourceContents = R"c( - #include "foo3.hxx" - int b = a; - )c"; - - FS.Files[Foo3C] = SourceContents; - FS.Files[Foo3HXX] = "int a;"; - - PathResult = Server.switchSourceHeader(Foo3C); - EXPECT_TRUE(PathResult.hasValue()); - ASSERT_EQ(PathResult.getValue(), Foo3HXX); - - // Test if asking for a corresponding file that doesn't exist returns an empty - // string. - PathResult = Server.switchSourceHeader(Invalid); - EXPECT_FALSE(PathResult.hasValue()); -} - -TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) { - class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer { - public: - std::atomic Count = {0}; - - NoConcurrentAccessDiagConsumer(std::promise StartSecondReparse) - : StartSecondReparse(std::move(StartSecondReparse)) {} - - void onDiagnosticsReady(PathRef, std::vector) override { - ++Count; - std::unique_lock Lock(Mutex, std::try_to_lock_t()); - ASSERT_TRUE(Lock.owns_lock()) - << "Detected concurrent onDiagnosticsReady calls for the same file."; - - // If we started the second parse immediately, it might cancel the first. - // So we don't allow it to start until the first has delivered diags... - if (FirstRequest) { - FirstRequest = false; - StartSecondReparse.set_value(); - // ... but then we wait long enough that the callbacks would overlap. - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } - } - - private: - std::mutex Mutex; - bool FirstRequest = true; - std::promise StartSecondReparse; - }; - - const auto SourceContentsWithoutErrors = R"cpp( -int a; -int b; -int c; -int d; -)cpp"; - - const auto SourceContentsWithErrors = R"cpp( -int a = x; -int b; -int c; -int d; -)cpp"; - - auto FooCpp = testPath("foo.cpp"); - MockFSProvider FS; - FS.Files[FooCpp] = ""; - - std::promise StartSecondPromise; - std::future StartSecond = StartSecondPromise.get_future(); - - NoConcurrentAccessDiagConsumer DiagConsumer(std::move(StartSecondPromise)); - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - Server.addDocument(FooCpp, SourceContentsWithErrors); - StartSecond.wait(); - Server.addDocument(FooCpp, SourceContentsWithoutErrors); - ASSERT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - ASSERT_EQ(DiagConsumer.Count, 2); // Sanity check - we actually ran both? -} - -TEST_F(ClangdVFSTest, FormatCode) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto Path = testPath("foo.cpp"); - std::string Code = R"cpp( -#include "x.h" -#include "y.h" - -void f( ) {} -)cpp"; - std::string Expected = R"cpp( -#include "x.h" -#include "y.h" - -void f() {} -)cpp"; - FS.Files[Path] = Code; - runAddDocument(Server, Path, Code); - - auto Replaces = Server.formatFile(Code, Path); - EXPECT_TRUE(static_cast(Replaces)); - auto Changed = tooling::applyAllReplacements(Code, *Replaces); - EXPECT_TRUE(static_cast(Changed)); - EXPECT_EQ(Expected, *Changed); -} - -TEST_F(ClangdVFSTest, ChangedHeaderFromISystem) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto SourcePath = testPath("source/foo.cpp"); - auto HeaderPath = testPath("headers/foo.h"); - FS.Files[HeaderPath] = "struct X { int bar; };"; - Annotations Code(R"cpp( - #include "foo.h" - - int main() { - X().ba^ - })cpp"); - CDB.ExtraClangFlags.push_back("-xc++"); - CDB.ExtraClangFlags.push_back("-isystem" + testPath("headers")); - - runAddDocument(Server, SourcePath, Code.code()); - auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(), - clangd::CodeCompleteOptions())) - .Completions; - EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar"))); - // Update the header and rerun addDocument to make sure we get the updated - // files. - FS.Files[HeaderPath] = "struct X { int bar; int baz; };"; - runAddDocument(Server, SourcePath, Code.code()); - Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(), - clangd::CodeCompleteOptions())) - .Completions; - // We want to make sure we see the updated version. - EXPECT_THAT(Completions, ElementsAre(Field(&CodeCompletion::Name, "bar"), - Field(&CodeCompletion::Name, "baz"))); -} - -// FIXME(ioeric): make this work for windows again. -#ifndef _WIN32 -// Check that running code completion doesn't stat() a bunch of files from the -// preamble again. (They should be using the preamble's stat-cache) -TEST(ClangdTests, PreambleVFSStatCache) { - class ListenStatsFSProvider : public FileSystemProvider { - public: - ListenStatsFSProvider(llvm::StringMap &CountStats) - : CountStats(CountStats) {} - - IntrusiveRefCntPtr getFileSystem() const override { - class ListenStatVFS : public llvm::vfs::ProxyFileSystem { - public: - ListenStatVFS(IntrusiveRefCntPtr FS, - llvm::StringMap &CountStats) - : ProxyFileSystem(std::move(FS)), CountStats(CountStats) {} - - llvm::ErrorOr> - openFileForRead(const Twine &Path) override { - ++CountStats[llvm::sys::path::filename(Path.str())]; - return ProxyFileSystem::openFileForRead(Path); - } - llvm::ErrorOr status(const Twine &Path) override { - ++CountStats[llvm::sys::path::filename(Path.str())]; - return ProxyFileSystem::status(Path); - } - - private: - llvm::StringMap &CountStats; - }; - - return IntrusiveRefCntPtr( - new ListenStatVFS(buildTestFS(Files), CountStats)); - } - - // If relative paths are used, they are resolved with testPath(). - llvm::StringMap Files; - llvm::StringMap &CountStats; - }; - - llvm::StringMap CountStats; - ListenStatsFSProvider FS(CountStats); - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto SourcePath = testPath("foo.cpp"); - auto HeaderPath = testPath("foo.h"); - FS.Files[HeaderPath] = "struct TestSym {};"; - Annotations Code(R"cpp( - #include "foo.h" - - int main() { - TestSy^ - })cpp"); - - runAddDocument(Server, SourcePath, Code.code()); - - unsigned Before = CountStats["foo.h"]; - EXPECT_GT(Before, 0u); - auto Completions = cantFail(runCodeComplete(Server, SourcePath, Code.point(), - clangd::CodeCompleteOptions())) - .Completions; - EXPECT_EQ(CountStats["foo.h"], Before); - EXPECT_THAT(Completions, - ElementsAre(Field(&CodeCompletion::Name, "TestSym"))); -} -#endif - -TEST_F(ClangdVFSTest, FlagsWithPlugins) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - CDB.ExtraClangFlags = { - "-Xclang", - "-add-plugin", - "-Xclang", - "random-plugin", - }; - OverlayCDB OCDB(&CDB); - ClangdServer Server(OCDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents = "int main() { return 0; }"; - FS.Files[FooCpp] = FooCpp; - Server.addDocument(FooCpp, SourceContents); - auto Result = dumpASTWithoutMemoryLocs(Server, FooCpp); - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_NE(Result, ""); -} - -TEST_F(ClangdVFSTest, FallbackWhenPreambleIsNotReady) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - Annotations Code(R"cpp( - namespace ns { int xyz; } - using namespace ns; - int main() { - xy^ - })cpp"); - FS.Files[FooCpp] = FooCpp; - - auto Opts = clangd::CodeCompleteOptions(); - Opts.AllowFallback = true; - - // This will make compile command broken and preamble absent. - CDB.ExtraClangFlags = {"yolo.cc"}; - Server.addDocument(FooCpp, Code.code()); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)); - EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery); - // Identifier-based fallback completion doesn't know about "symbol" scope. - EXPECT_THAT(Res.Completions, - ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), - Field(&CodeCompletion::Scope, "")))); - - // Make the compile command work again. - CDB.ExtraClangFlags = {"-std=c++11"}; - Server.addDocument(FooCpp, Code.code()); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(), - clangd::CodeCompleteOptions())) - .Completions, - ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), - Field(&CodeCompletion::Scope, "ns::")))); -} - -TEST_F(ClangdVFSTest, FallbackWhenWaitingForCompileCommand) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - // Returns compile command only when notified. - class DelayedCompilationDatabase : public GlobalCompilationDatabase { - public: - DelayedCompilationDatabase(Notification &CanReturnCommand) - : CanReturnCommand(CanReturnCommand) {} - - llvm::Optional - getCompileCommand(PathRef File, ProjectInfo * = nullptr) const override { - // FIXME: make this timeout and fail instead of waiting forever in case - // something goes wrong. - CanReturnCommand.wait(); - auto FileName = llvm::sys::path::filename(File); - std::vector CommandLine = {"clangd", "-ffreestanding", File}; - return {tooling::CompileCommand(llvm::sys::path::parent_path(File), - FileName, std::move(CommandLine), "")}; - } - - std::vector ExtraClangFlags; - - private: - Notification &CanReturnCommand; - }; - - Notification CanReturnCommand; - DelayedCompilationDatabase CDB(CanReturnCommand); - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - Annotations Code(R"cpp( - namespace ns { int xyz; } - using namespace ns; - int main() { - xy^ - })cpp"); - FS.Files[FooCpp] = FooCpp; - Server.addDocument(FooCpp, Code.code()); - - // Sleep for some time to make sure code completion is not run because update - // hasn't been scheduled. - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - auto Opts = clangd::CodeCompleteOptions(); - Opts.AllowFallback = true; - - auto Res = cantFail(runCodeComplete(Server, FooCpp, Code.point(), Opts)); - EXPECT_EQ(Res.Context, CodeCompletionContext::CCC_Recovery); - - CanReturnCommand.notify(); - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(cantFail(runCodeComplete(Server, FooCpp, Code.point(), - clangd::CodeCompleteOptions())) - .Completions, - ElementsAre(AllOf(Field(&CodeCompletion::Name, "xyz"), - Field(&CodeCompletion::Scope, "ns::")))); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/ClangdUnitTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/ClangdUnitTests.cpp @@ -1,86 +0,0 @@ -//===-- ClangdUnitTests.cpp - ClangdUnit tests ------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Annotations.h" -#include "ClangdUnit.h" -#include "SourceCode.h" -#include "TestTU.h" -#include "llvm/Support/ScopedPrinter.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using testing::ElementsAre; - -TEST(ClangdUnitTest, GetBeginningOfIdentifier) { - std::string Preamble = R"cpp( -struct Bar { int func(); }; -#define MACRO(X) void f() { X; } -Bar* bar; - )cpp"; - // First ^ is the expected beginning, last is the search position. - for (std::string Text : std::vector{ - "int ^f^oo();", // inside identifier - "int ^foo();", // beginning of identifier - "int ^foo^();", // end of identifier - "int foo(^);", // non-identifier - "^int foo();", // beginning of file (can't back up) - "int ^f0^0();", // after a digit (lexing at N-1 is wrong) - "int ^λλ^λ();", // UTF-8 handled properly when backing up - - // identifier in macro arg - "MACRO(bar->^func())", // beginning of identifier - "MACRO(bar->^fun^c())", // inside identifier - "MACRO(bar->^func^())", // end of identifier - "MACRO(^bar->func())", // begin identifier - "MACRO(^bar^->func())", // end identifier - "^MACRO(bar->func())", // beginning of macro name - "^MAC^RO(bar->func())", // inside macro name - "^MACRO^(bar->func())", // end of macro name - }) { - std::string WithPreamble = Preamble + Text; - Annotations TestCase(WithPreamble); - auto AST = TestTU::withCode(TestCase.code()).build(); - const auto &SourceMgr = AST.getASTContext().getSourceManager(); - SourceLocation Actual = getBeginningOfIdentifier( - AST, TestCase.points().back(), SourceMgr.getMainFileID()); - Position ActualPos = offsetToPosition( - TestCase.code(), - SourceMgr.getFileOffset(SourceMgr.getSpellingLoc(Actual))); - EXPECT_EQ(TestCase.points().front(), ActualPos) << Text; - } -} - -MATCHER_P(DeclNamed, Name, "") { - if (NamedDecl *ND = dyn_cast(arg)) - if (ND->getName() == Name) - return true; - if (auto *Stream = result_listener->stream()) { - llvm::raw_os_ostream OS(*Stream); - arg->dump(OS); - } - return false; -} - -TEST(ClangdUnitTest, TopLevelDecls) { - TestTU TU; - TU.HeaderCode = R"( - int header1(); - int header2; - )"; - TU.Code = "int main();"; - auto AST = TU.build(); - EXPECT_THAT(AST.getLocalTopLevelDecls(), ElementsAre(DeclNamed("main"))); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/CodeCompleteTests.cpp @@ -1,2523 +0,0 @@ -//===-- CodeCompleteTests.cpp -----------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Annotations.h" -#include "ClangdServer.h" -#include "CodeComplete.h" -#include "Compiler.h" -#include "Matchers.h" -#include "Protocol.h" -#include "Quality.h" -#include "SourceCode.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "TestIndex.h" -#include "TestTU.h" -#include "index/Index.h" -#include "index/MemIndex.h" -#include "clang/Sema/CodeCompleteConsumer.h" -#include "clang/Tooling/CompilationDatabase.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/Path.h" -#include "llvm/Testing/Support/Error.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { - -namespace { -using ::llvm::Failed; -using ::testing::AllOf; -using ::testing::Contains; -using ::testing::ElementsAre; -using ::testing::Field; -using ::testing::HasSubstr; -using ::testing::IsEmpty; -using ::testing::Not; -using ::testing::UnorderedElementsAre; - -class IgnoreDiagnostics : public DiagnosticsConsumer { - void onDiagnosticsReady(PathRef File, - std::vector Diagnostics) override {} -}; - -// GMock helpers for matching completion items. -MATCHER_P(Named, Name, "") { return arg.Name == Name; } -MATCHER_P(Scope, S, "") { return arg.Scope == S; } -MATCHER_P(Qualifier, Q, "") { return arg.RequiredQualifier == Q; } -MATCHER_P(Labeled, Label, "") { - return arg.RequiredQualifier + arg.Name + arg.Signature == Label; -} -MATCHER_P(SigHelpLabeled, Label, "") { return arg.label == Label; } -MATCHER_P(Kind, K, "") { return arg.Kind == K; } -MATCHER_P(Doc, D, "") { return arg.Documentation == D; } -MATCHER_P(ReturnType, D, "") { return arg.ReturnType == D; } -MATCHER_P(HasInclude, IncludeHeader, "") { - return !arg.Includes.empty() && arg.Includes[0].Header == IncludeHeader; -} -MATCHER_P(InsertInclude, IncludeHeader, "") { - return !arg.Includes.empty() && arg.Includes[0].Header == IncludeHeader && - bool(arg.Includes[0].Insertion); -} -MATCHER(InsertInclude, "") { - return !arg.Includes.empty() && bool(arg.Includes[0].Insertion); -} -MATCHER_P(SnippetSuffix, Text, "") { return arg.SnippetSuffix == Text; } -MATCHER_P(Origin, OriginSet, "") { return arg.Origin == OriginSet; } -MATCHER_P(Signature, S, "") { return arg.Signature == S; } - -// Shorthand for Contains(Named(Name)). -Matcher &> Has(std::string Name) { - return Contains(Named(std::move(Name))); -} -Matcher &> Has(std::string Name, - CompletionItemKind K) { - return Contains(AllOf(Named(std::move(Name)), Kind(K))); -} -MATCHER(IsDocumented, "") { return !arg.Documentation.empty(); } -MATCHER(Deprecated, "") { return arg.Deprecated; } - -std::unique_ptr memIndex(std::vector Symbols) { - SymbolSlab::Builder Slab; - for (const auto &Sym : Symbols) - Slab.insert(Sym); - return MemIndex::build(std::move(Slab).build(), RefSlab()); -} - -CodeCompleteResult completions(ClangdServer &Server, llvm::StringRef TestCode, - Position point, - std::vector IndexSymbols = {}, - clangd::CodeCompleteOptions Opts = {}) { - std::unique_ptr OverrideIndex; - if (!IndexSymbols.empty()) { - assert(!Opts.Index && "both Index and IndexSymbols given!"); - OverrideIndex = memIndex(std::move(IndexSymbols)); - Opts.Index = OverrideIndex.get(); - } - - auto File = testPath("foo.cpp"); - runAddDocument(Server, File, TestCode); - auto CompletionList = - llvm::cantFail(runCodeComplete(Server, File, point, Opts)); - return CompletionList; -} - -CodeCompleteResult completions(ClangdServer &Server, llvm::StringRef Text, - std::vector IndexSymbols = {}, - clangd::CodeCompleteOptions Opts = {}, - PathRef FilePath = "foo.cpp") { - std::unique_ptr OverrideIndex; - if (!IndexSymbols.empty()) { - assert(!Opts.Index && "both Index and IndexSymbols given!"); - OverrideIndex = memIndex(std::move(IndexSymbols)); - Opts.Index = OverrideIndex.get(); - } - - auto File = testPath(FilePath); - Annotations Test(Text); - runAddDocument(Server, File, Test.code()); - auto CompletionList = - llvm::cantFail(runCodeComplete(Server, File, Test.point(), Opts)); - return CompletionList; -} - -// Builds a server and runs code completion. -// If IndexSymbols is non-empty, an index will be built and passed to opts. -CodeCompleteResult completions(llvm::StringRef Text, - std::vector IndexSymbols = {}, - clangd::CodeCompleteOptions Opts = {}, - PathRef FilePath = "foo.cpp") { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - return completions(Server, Text, std::move(IndexSymbols), std::move(Opts), - FilePath); -} - -// Builds a server and runs code completion. -// If IndexSymbols is non-empty, an index will be built and passed to opts. -CodeCompleteResult completionsNoCompile(llvm::StringRef Text, - std::vector IndexSymbols = {}, - clangd::CodeCompleteOptions Opts = {}, - PathRef FilePath = "foo.cpp") { - std::unique_ptr OverrideIndex; - if (!IndexSymbols.empty()) { - assert(!Opts.Index && "both Index and IndexSymbols given!"); - OverrideIndex = memIndex(std::move(IndexSymbols)); - Opts.Index = OverrideIndex.get(); - } - - MockFSProvider FS; - Annotations Test(Text); - return codeComplete(FilePath, tooling::CompileCommand(), /*Preamble=*/nullptr, - Test.code(), Test.point(), FS.getFileSystem(), Opts); -} - -Symbol withReferences(int N, Symbol S) { - S.References = N; - return S; -} - -TEST(CompletionTest, Limit) { - clangd::CodeCompleteOptions Opts; - Opts.Limit = 2; - auto Results = completions(R"cpp( -struct ClassWithMembers { - int AAA(); - int BBB(); - int CCC(); -}; -int main() { ClassWithMembers().^ } - )cpp", - /*IndexSymbols=*/{}, Opts); - - EXPECT_TRUE(Results.HasMore); - EXPECT_THAT(Results.Completions, ElementsAre(Named("AAA"), Named("BBB"))); -} - -TEST(CompletionTest, Filter) { - std::string Body = R"cpp( - #define MotorCar - int Car; - struct S { - int FooBar; - int FooBaz; - int Qux; - }; - )cpp"; - - // Only items matching the fuzzy query are returned. - EXPECT_THAT(completions(Body + "int main() { S().Foba^ }").Completions, - AllOf(Has("FooBar"), Has("FooBaz"), Not(Has("Qux")))); - - // Macros require prefix match. - EXPECT_THAT(completions(Body + "int main() { C^ }").Completions, - AllOf(Has("Car"), Not(Has("MotorCar")))); -} - -void TestAfterDotCompletion(clangd::CodeCompleteOptions Opts) { - auto Results = completions( - R"cpp( - int global_var; - - int global_func(); - - // Make sure this is not in preamble. - #define MACRO X - - struct GlobalClass {}; - - struct ClassWithMembers { - /// Doc for method. - int method(); - - int field; - private: - int private_field; - }; - - int test() { - struct LocalClass {}; - - /// Doc for local_var. - int local_var; - - ClassWithMembers().^ - } - )cpp", - {cls("IndexClass"), var("index_var"), func("index_func")}, Opts); - - // Class members. The only items that must be present in after-dot - // completion. - EXPECT_THAT(Results.Completions, - AllOf(Has("method"), Has("field"), Not(Has("ClassWithMembers")), - Not(Has("operator=")), Not(Has("~ClassWithMembers")))); - EXPECT_IFF(Opts.IncludeIneligibleResults, Results.Completions, - Has("private_field")); - // Global items. - EXPECT_THAT( - Results.Completions, - Not(AnyOf(Has("global_var"), Has("index_var"), Has("global_func"), - Has("global_func()"), Has("index_func"), Has("GlobalClass"), - Has("IndexClass"), Has("MACRO"), Has("LocalClass")))); - // There should be no code patterns (aka snippets) in after-dot - // completion. At least there aren't any we're aware of. - EXPECT_THAT(Results.Completions, - Not(Contains(Kind(CompletionItemKind::Snippet)))); - // Check documentation. - EXPECT_IFF(Opts.IncludeComments, Results.Completions, - Contains(IsDocumented())); -} - -void TestGlobalScopeCompletion(clangd::CodeCompleteOptions Opts) { - auto Results = completions( - R"cpp( - int global_var; - int global_func(); - - // Make sure this is not in preamble. - #define MACRO X - - struct GlobalClass {}; - - struct ClassWithMembers { - /// Doc for method. - int method(); - }; - - int test() { - struct LocalClass {}; - - /// Doc for local_var. - int local_var; - - ^ - } - )cpp", - {cls("IndexClass"), var("index_var"), func("index_func")}, Opts); - - // Class members. Should never be present in global completions. - EXPECT_THAT(Results.Completions, - Not(AnyOf(Has("method"), Has("method()"), Has("field")))); - // Global items. - EXPECT_THAT(Results.Completions, - AllOf(Has("global_var"), Has("index_var"), Has("global_func"), - Has("index_func" /* our fake symbol doesn't include () */), - Has("GlobalClass"), Has("IndexClass"))); - // A macro. - EXPECT_IFF(Opts.IncludeMacros, Results.Completions, Has("MACRO")); - // Local items. Must be present always. - EXPECT_THAT(Results.Completions, - AllOf(Has("local_var"), Has("LocalClass"), - Contains(Kind(CompletionItemKind::Snippet)))); - // Check documentation. - EXPECT_IFF(Opts.IncludeComments, Results.Completions, - Contains(IsDocumented())); -} - -TEST(CompletionTest, CompletionOptions) { - auto Test = [&](const clangd::CodeCompleteOptions &Opts) { - TestAfterDotCompletion(Opts); - TestGlobalScopeCompletion(Opts); - }; - // We used to test every combination of options, but that got too slow (2^N). - auto Flags = { - &clangd::CodeCompleteOptions::IncludeMacros, - &clangd::CodeCompleteOptions::IncludeComments, - &clangd::CodeCompleteOptions::IncludeCodePatterns, - &clangd::CodeCompleteOptions::IncludeIneligibleResults, - }; - // Test default options. - Test({}); - // Test with one flag flipped. - for (auto &F : Flags) { - clangd::CodeCompleteOptions O; - O.*F ^= true; - Test(O); - } -} - -TEST(CompletionTest, Priorities) { - auto Internal = completions(R"cpp( - class Foo { - public: void pub(); - protected: void prot(); - private: void priv(); - }; - void Foo::pub() { this->^ } - )cpp"); - EXPECT_THAT(Internal.Completions, - HasSubsequence(Named("priv"), Named("prot"), Named("pub"))); - - auto External = completions(R"cpp( - class Foo { - public: void pub(); - protected: void prot(); - private: void priv(); - }; - void test() { - Foo F; - F.^ - } - )cpp"); - EXPECT_THAT(External.Completions, - AllOf(Has("pub"), Not(Has("prot")), Not(Has("priv")))); -} - -TEST(CompletionTest, Qualifiers) { - auto Results = completions(R"cpp( - class Foo { - public: int foo() const; - int bar() const; - }; - class Bar : public Foo { - int foo() const; - }; - void test() { Bar().^ } - )cpp"); - EXPECT_THAT(Results.Completions, - Contains(AllOf(Qualifier(""), Named("bar")))); - // Hidden members are not shown. - EXPECT_THAT(Results.Completions, - Not(Contains(AllOf(Qualifier("Foo::"), Named("foo"))))); - // Private members are not shown. - EXPECT_THAT(Results.Completions, - Not(Contains(AllOf(Qualifier(""), Named("foo"))))); -} - -TEST(CompletionTest, InjectedTypename) { - // These are suppressed when accessed as a member... - EXPECT_THAT(completions("struct X{}; void foo(){ X().^ }").Completions, - Not(Has("X"))); - EXPECT_THAT(completions("struct X{ void foo(){ this->^ } };").Completions, - Not(Has("X"))); - // ...but accessible in other, more useful cases. - EXPECT_THAT(completions("struct X{ void foo(){ ^ } };").Completions, - Has("X")); - EXPECT_THAT( - completions("struct Y{}; struct X:Y{ void foo(){ ^ } };").Completions, - Has("Y")); - EXPECT_THAT( - completions( - "template struct Y{}; struct X:Y{ void foo(){ ^ } };") - .Completions, - Has("Y")); - // This case is marginal (`using X::X` is useful), we allow it for now. - EXPECT_THAT(completions("struct X{}; void foo(){ X::^ }").Completions, - Has("X")); -} - -TEST(CompletionTest, SkipInjectedWhenUnqualified) { - EXPECT_THAT(completions("struct X { void f() { X^ }};").Completions, - ElementsAre(Named("X"), Named("~X"))); -} - -TEST(CompletionTest, Snippets) { - clangd::CodeCompleteOptions Opts; - auto Results = completions( - R"cpp( - struct fake { - int a; - int f(int i, const float f) const; - }; - int main() { - fake f; - f.^ - } - )cpp", - /*IndexSymbols=*/{}, Opts); - EXPECT_THAT( - Results.Completions, - HasSubsequence(Named("a"), - SnippetSuffix("(${1:int i}, ${2:const float f})"))); -} - -TEST(CompletionTest, Kinds) { - auto Results = completions( - R"cpp( - int variable; - struct Struct {}; - int function(); - // make sure MACRO is not included in preamble. - #define MACRO 10 - int X = ^ - )cpp", - {func("indexFunction"), var("indexVariable"), cls("indexClass")}); - EXPECT_THAT(Results.Completions, - AllOf(Has("function", CompletionItemKind::Function), - Has("variable", CompletionItemKind::Variable), - Has("int", CompletionItemKind::Keyword), - Has("Struct", CompletionItemKind::Class), - Has("MACRO", CompletionItemKind::Text), - Has("indexFunction", CompletionItemKind::Function), - Has("indexVariable", CompletionItemKind::Variable), - Has("indexClass", CompletionItemKind::Class))); - - Results = completions("nam^"); - EXPECT_THAT(Results.Completions, - Has("namespace", CompletionItemKind::Snippet)); -} - -TEST(CompletionTest, NoDuplicates) { - auto Results = completions( - R"cpp( - class Adapter { - }; - - void f() { - Adapter^ - } - )cpp", - {cls("Adapter")}); - - // Make sure there are no duplicate entries of 'Adapter'. - EXPECT_THAT(Results.Completions, ElementsAre(Named("Adapter"))); -} - -TEST(CompletionTest, ScopedNoIndex) { - auto Results = completions( - R"cpp( - namespace fake { int BigBang, Babble, Box; }; - int main() { fake::ba^ } - ")cpp"); - // Babble is a better match than BigBang. Box doesn't match at all. - EXPECT_THAT(Results.Completions, - ElementsAre(Named("Babble"), Named("BigBang"))); -} - -TEST(CompletionTest, Scoped) { - auto Results = completions( - R"cpp( - namespace fake { int Babble, Box; }; - int main() { fake::ba^ } - ")cpp", - {var("fake::BigBang")}); - EXPECT_THAT(Results.Completions, - ElementsAre(Named("Babble"), Named("BigBang"))); -} - -TEST(CompletionTest, ScopedWithFilter) { - auto Results = completions( - R"cpp( - void f() { ns::x^ } - )cpp", - {cls("ns::XYZ"), func("ns::foo")}); - EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("XYZ"))); -} - -TEST(CompletionTest, ReferencesAffectRanking) { - auto Results = completions("int main() { abs^ }", {ns("absl"), func("absb")}); - EXPECT_THAT(Results.Completions, - HasSubsequence(Named("absb"), Named("absl"))); - Results = completions("int main() { abs^ }", - {withReferences(10000, ns("absl")), func("absb")}); - EXPECT_THAT(Results.Completions, - HasSubsequence(Named("absl"), Named("absb"))); -} - -TEST(CompletionTest, GlobalQualified) { - auto Results = completions( - R"cpp( - void f() { ::^ } - )cpp", - {cls("XYZ")}); - EXPECT_THAT(Results.Completions, - AllOf(Has("XYZ", CompletionItemKind::Class), - Has("f", CompletionItemKind::Function))); -} - -TEST(CompletionTest, FullyQualified) { - auto Results = completions( - R"cpp( - namespace ns { void bar(); } - void f() { ::ns::^ } - )cpp", - {cls("ns::XYZ")}); - EXPECT_THAT(Results.Completions, - AllOf(Has("XYZ", CompletionItemKind::Class), - Has("bar", CompletionItemKind::Function))); -} - -TEST(CompletionTest, SemaIndexMerge) { - auto Results = completions( - R"cpp( - namespace ns { int local; void both(); } - void f() { ::ns::^ } - )cpp", - {func("ns::both"), cls("ns::Index")}); - // We get results from both index and sema, with no duplicates. - EXPECT_THAT(Results.Completions, - UnorderedElementsAre( - AllOf(Named("local"), Origin(SymbolOrigin::AST)), - AllOf(Named("Index"), Origin(SymbolOrigin::Static)), - AllOf(Named("both"), - Origin(SymbolOrigin::AST | SymbolOrigin::Static)))); -} - -TEST(CompletionTest, SemaIndexMergeWithLimit) { - clangd::CodeCompleteOptions Opts; - Opts.Limit = 1; - auto Results = completions( - R"cpp( - namespace ns { int local; void both(); } - void f() { ::ns::^ } - )cpp", - {func("ns::both"), cls("ns::Index")}, Opts); - EXPECT_EQ(Results.Completions.size(), Opts.Limit); - EXPECT_TRUE(Results.HasMore); -} - -TEST(CompletionTest, IncludeInsertionPreprocessorIntegrationTests) { - MockFSProvider FS; - MockCompilationDatabase CDB; - std::string Subdir = testPath("sub"); - std::string SearchDirArg = (Twine("-I") + Subdir).str(); - CDB.ExtraClangFlags = {SearchDirArg.c_str()}; - std::string BarHeader = testPath("sub/bar.h"); - FS.Files[BarHeader] = ""; - - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - auto BarURI = URI::create(BarHeader).toString(); - Symbol Sym = cls("ns::X"); - Sym.CanonicalDeclaration.FileURI = BarURI.c_str(); - Sym.IncludeHeaders.emplace_back(BarURI, 1); - // Shoten include path based on search dirctory and insert. - auto Results = completions(Server, - R"cpp( - int main() { ns::^ } - )cpp", - {Sym}); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Named("X"), InsertInclude("\"bar.h\"")))); - // Can be disabled via option. - CodeCompleteOptions NoInsertion; - NoInsertion.InsertIncludes = CodeCompleteOptions::NeverInsert; - Results = completions(Server, - R"cpp( - int main() { ns::^ } - )cpp", - {Sym}, NoInsertion); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Named("X"), Not(InsertInclude())))); - // Duplicate based on inclusions in preamble. - Results = completions(Server, - R"cpp( - #include "sub/bar.h" // not shortest, so should only match resolved. - int main() { ns::^ } - )cpp", - {Sym}); - EXPECT_THAT(Results.Completions, ElementsAre(AllOf(Named("X"), Labeled("X"), - Not(InsertInclude())))); -} - -TEST(CompletionTest, NoIncludeInsertionWhenDeclFoundInFile) { - MockFSProvider FS; - MockCompilationDatabase CDB; - - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - Symbol SymX = cls("ns::X"); - Symbol SymY = cls("ns::Y"); - std::string BarHeader = testPath("bar.h"); - auto BarURI = URI::create(BarHeader).toString(); - SymX.CanonicalDeclaration.FileURI = BarURI.c_str(); - SymY.CanonicalDeclaration.FileURI = BarURI.c_str(); - SymX.IncludeHeaders.emplace_back("", 1); - SymY.IncludeHeaders.emplace_back("", 1); - // Shoten include path based on search dirctory and insert. - auto Results = completions(Server, - R"cpp( - namespace ns { - class X; - class Y {}; - } - int main() { ns::^ } - )cpp", - {SymX, SymY}); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Named("X"), Not(InsertInclude())), - AllOf(Named("Y"), Not(InsertInclude())))); -} - -TEST(CompletionTest, IndexSuppressesPreambleCompletions) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - FS.Files[testPath("bar.h")] = - R"cpp(namespace ns { struct preamble { int member; }; })cpp"; - auto File = testPath("foo.cpp"); - Annotations Test(R"cpp( - #include "bar.h" - namespace ns { int local; } - void f() { ns::^; } - void f2() { ns::preamble().$2^; } - )cpp"); - runAddDocument(Server, File, Test.code()); - clangd::CodeCompleteOptions Opts = {}; - - auto I = memIndex({var("ns::index")}); - Opts.Index = I.get(); - auto WithIndex = cantFail(runCodeComplete(Server, File, Test.point(), Opts)); - EXPECT_THAT(WithIndex.Completions, - UnorderedElementsAre(Named("local"), Named("index"))); - auto ClassFromPreamble = - cantFail(runCodeComplete(Server, File, Test.point("2"), Opts)); - EXPECT_THAT(ClassFromPreamble.Completions, Contains(Named("member"))); - - Opts.Index = nullptr; - auto WithoutIndex = - cantFail(runCodeComplete(Server, File, Test.point(), Opts)); - EXPECT_THAT(WithoutIndex.Completions, - UnorderedElementsAre(Named("local"), Named("preamble"))); -} - -// This verifies that we get normal preprocessor completions in the preamble. -// This is a regression test for an old bug: if we override the preamble and -// try to complete inside it, clang kicks our completion point just outside the -// preamble, resulting in always getting top-level completions. -TEST(CompletionTest, CompletionInPreamble) { - auto Results = completions(R"cpp( - #ifnd^ef FOO_H_ - #define BAR_H_ - #include - int foo() {} - #endif - )cpp") - .Completions; - EXPECT_THAT(Results, ElementsAre(Named("ifndef"))); -} - -TEST(CompletionTest, DynamicIndexIncludeInsertion) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer::Options Opts = ClangdServer::optsForTest(); - Opts.BuildDynamicSymbolIndex = true; - ClangdServer Server(CDB, FS, DiagConsumer, Opts); - - FS.Files[testPath("foo_header.h")] = R"cpp( - #pragma once - struct Foo { - // Member doc - int foo(); - }; - )cpp"; - const std::string FileContent(R"cpp( - #include "foo_header.h" - int Foo::foo() { - return 42; - } - )cpp"); - Server.addDocument(testPath("foo_impl.cpp"), FileContent); - // Wait for the dynamic index being built. - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT(completions(Server, "Foo^ foo;").Completions, - ElementsAre(AllOf(Named("Foo"), - HasInclude('"' + - llvm::sys::path::convert_to_slash( - testPath("foo_header.h")) + - '"'), - InsertInclude()))); -} - -TEST(CompletionTest, DynamicIndexMultiFile) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - auto Opts = ClangdServer::optsForTest(); - Opts.BuildDynamicSymbolIndex = true; - ClangdServer Server(CDB, FS, DiagConsumer, Opts); - - FS.Files[testPath("foo.h")] = R"cpp( - namespace ns { class XYZ {}; void foo(int x) {} } - )cpp"; - runAddDocument(Server, testPath("foo.cpp"), R"cpp( - #include "foo.h" - )cpp"); - - auto File = testPath("bar.cpp"); - Annotations Test(R"cpp( - namespace ns { - class XXX {}; - /// Doooc - void fooooo() {} - } - void f() { ns::^ } - )cpp"); - runAddDocument(Server, File, Test.code()); - - auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); - // "XYZ" and "foo" are not included in the file being completed but are still - // visible through the index. - EXPECT_THAT(Results.Completions, Has("XYZ", CompletionItemKind::Class)); - EXPECT_THAT(Results.Completions, Has("foo", CompletionItemKind::Function)); - EXPECT_THAT(Results.Completions, Has("XXX", CompletionItemKind::Class)); - EXPECT_THAT(Results.Completions, - Contains((Named("fooooo"), Kind(CompletionItemKind::Function), - Doc("Doooc"), ReturnType("void")))); -} - -TEST(CompletionTest, Documentation) { - auto Results = completions( - R"cpp( - // Non-doxygen comment. - int foo(); - /// Doxygen comment. - /// \param int a - int bar(int a); - /* Multi-line - block comment - */ - int baz(); - - int x = ^ - )cpp"); - EXPECT_THAT(Results.Completions, - Contains(AllOf(Named("foo"), Doc("Non-doxygen comment.")))); - EXPECT_THAT( - Results.Completions, - Contains(AllOf(Named("bar"), Doc("Doxygen comment.\n\\param int a")))); - EXPECT_THAT(Results.Completions, - Contains(AllOf(Named("baz"), Doc("Multi-line\nblock comment")))); -} - -TEST(CompletionTest, GlobalCompletionFiltering) { - - Symbol Class = cls("XYZ"); - Class.Flags = static_cast( - Class.Flags & ~(Symbol::IndexedForCodeCompletion)); - Symbol Func = func("XYZ::foooo"); - Func.Flags = static_cast( - Func.Flags & ~(Symbol::IndexedForCodeCompletion)); - - auto Results = completions(R"(// void f() { - XYZ::foooo^ - })", - {Class, Func}); - EXPECT_THAT(Results.Completions, IsEmpty()); -} - -TEST(CodeCompleteTest, DisableTypoCorrection) { - auto Results = completions(R"cpp( - namespace clang { int v; } - void f() { clangd::^ - )cpp"); - EXPECT_TRUE(Results.Completions.empty()); -} - -TEST(CodeCompleteTest, NoColonColonAtTheEnd) { - auto Results = completions(R"cpp( - namespace clang { } - void f() { - clan^ - } - )cpp"); - - EXPECT_THAT(Results.Completions, Contains(Labeled("clang"))); - EXPECT_THAT(Results.Completions, Not(Contains(Labeled("clang::")))); -} - -TEST(CompletionTest, BacktrackCrashes) { - // Sema calls code completion callbacks twice in these cases. - auto Results = completions(R"cpp( - namespace ns { - struct FooBarBaz {}; - } // namespace ns - - int foo(ns::FooBar^ - )cpp"); - - EXPECT_THAT(Results.Completions, ElementsAre(Labeled("FooBarBaz"))); - - // Check we don't crash in that case too. - completions(R"cpp( - struct FooBarBaz {}; - void test() { - if (FooBarBaz * x^) {} - } -)cpp"); -} - -TEST(CompletionTest, CompleteInMacroWithStringification) { - auto Results = completions(R"cpp( -void f(const char *, int x); -#define F(x) f(#x, x) - -namespace ns { -int X; -int Y; -} // namespace ns - -int f(int input_num) { - F(ns::^) -} -)cpp"); - - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(Named("X"), Named("Y"))); -} - -TEST(CompletionTest, CompleteInMacroAndNamespaceWithStringification) { - auto Results = completions(R"cpp( -void f(const char *, int x); -#define F(x) f(#x, x) - -namespace ns { -int X; - -int f(int input_num) { - F(^) -} -} // namespace ns -)cpp"); - - EXPECT_THAT(Results.Completions, Contains(Named("X"))); -} - -TEST(CompletionTest, IgnoreCompleteInExcludedPPBranchWithRecoveryContext) { - auto Results = completions(R"cpp( - int bar(int param_in_bar) { - } - - int foo(int param_in_foo) { -#if 0 - // In recorvery mode, "param_in_foo" will also be suggested among many other - // unrelated symbols; however, this is really a special case where this works. - // If the #if block is outside of the function, "param_in_foo" is still - // suggested, but "bar" and "foo" are missing. So the recovery mode doesn't - // really provide useful results in excluded branches. - par^ -#endif - } -)cpp"); - - EXPECT_TRUE(Results.Completions.empty()); -} -SignatureHelp signatures(llvm::StringRef Text, Position Point, - std::vector IndexSymbols = {}) { - std::unique_ptr Index; - if (!IndexSymbols.empty()) - Index = memIndex(IndexSymbols); - - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer::Options Opts = ClangdServer::optsForTest(); - Opts.StaticIndex = Index.get(); - - ClangdServer Server(CDB, FS, DiagConsumer, Opts); - auto File = testPath("foo.cpp"); - runAddDocument(Server, File, Text); - return llvm::cantFail(runSignatureHelp(Server, File, Point)); -} - -SignatureHelp signatures(llvm::StringRef Text, - std::vector IndexSymbols = {}) { - Annotations Test(Text); - return signatures(Test.code(), Test.point(), std::move(IndexSymbols)); -} - -MATCHER_P(ParamsAre, P, "") { - if (P.size() != arg.parameters.size()) - return false; - for (unsigned I = 0; I < P.size(); ++I) - if (P[I] != arg.parameters[I].label) - return false; - return true; -} -MATCHER_P(SigDoc, Doc, "") { return arg.documentation == Doc; } - -Matcher Sig(std::string Label, - std::vector Params) { - return AllOf(SigHelpLabeled(Label), ParamsAre(Params)); -} - -TEST(SignatureHelpTest, Overloads) { - auto Results = signatures(R"cpp( - void foo(int x, int y); - void foo(int x, float y); - void foo(float x, int y); - void foo(float x, float y); - void bar(int x, int y = 0); - int main() { foo(^); } - )cpp"); - EXPECT_THAT(Results.signatures, - UnorderedElementsAre( - Sig("foo(float x, float y) -> void", {"float x", "float y"}), - Sig("foo(float x, int y) -> void", {"float x", "int y"}), - Sig("foo(int x, float y) -> void", {"int x", "float y"}), - Sig("foo(int x, int y) -> void", {"int x", "int y"}))); - // We always prefer the first signature. - EXPECT_EQ(0, Results.activeSignature); - EXPECT_EQ(0, Results.activeParameter); -} - -TEST(SignatureHelpTest, DefaultArgs) { - auto Results = signatures(R"cpp( - void bar(int x, int y = 0); - void bar(float x = 0, int y = 42); - int main() { bar(^ - )cpp"); - EXPECT_THAT(Results.signatures, - UnorderedElementsAre( - Sig("bar(int x, int y = 0) -> void", {"int x", "int y = 0"}), - Sig("bar(float x = 0, int y = 42) -> void", - {"float x = 0", "int y = 42"}))); - EXPECT_EQ(0, Results.activeSignature); - EXPECT_EQ(0, Results.activeParameter); -} - -TEST(SignatureHelpTest, ActiveArg) { - auto Results = signatures(R"cpp( - int baz(int a, int b, int c); - int main() { baz(baz(1,2,3), ^); } - )cpp"); - EXPECT_THAT(Results.signatures, - ElementsAre(Sig("baz(int a, int b, int c) -> int", - {"int a", "int b", "int c"}))); - EXPECT_EQ(0, Results.activeSignature); - EXPECT_EQ(1, Results.activeParameter); -} - -TEST(SignatureHelpTest, OpeningParen) { - llvm::StringLiteral Tests[] = {// Recursive function call. - R"cpp( - int foo(int a, int b, int c); - int main() { - foo(foo $p^( foo(10, 10, 10), ^ ))); - })cpp", - // Functional type cast. - R"cpp( - struct Foo { - Foo(int a, int b, int c); - }; - int main() { - Foo $p^( 10, ^ ); - })cpp", - // New expression. - R"cpp( - struct Foo { - Foo(int a, int b, int c); - }; - int main() { - new Foo $p^( 10, ^ ); - })cpp", - // Macro expansion. - R"cpp( - int foo(int a, int b, int c); - #define FOO foo( - - int main() { - // Macro expansions. - $p^FOO 10, ^ ); - })cpp", - // Macro arguments. - R"cpp( - int foo(int a, int b, int c); - int main() { - #define ID(X) X - ID(foo $p^( foo(10), ^ )) - })cpp"}; - - for (auto Test : Tests) { - Annotations Code(Test); - EXPECT_EQ(signatures(Code.code(), Code.point()).argListStart, - Code.point("p")) - << "Test source:" << Test; - } -} - -class IndexRequestCollector : public SymbolIndex { -public: - bool - fuzzyFind(const FuzzyFindRequest &Req, - llvm::function_ref Callback) const override { - std::lock_guard Lock(Mut); - Requests.push_back(Req); - return true; - } - - void lookup(const LookupRequest &, - llvm::function_ref) const override {} - - void refs(const RefsRequest &, - llvm::function_ref) const override {} - - // This is incorrect, but IndexRequestCollector is not an actual index and it - // isn't used in production code. - size_t estimateMemoryUsage() const override { return 0; } - - const std::vector consumeRequests() const { - std::lock_guard Lock(Mut); - auto Reqs = std::move(Requests); - Requests = {}; - return Reqs; - } - -private: - // We need a mutex to handle async fuzzy find requests. - mutable std::mutex Mut; - mutable std::vector Requests; -}; - -std::vector captureIndexRequests(llvm::StringRef Code) { - clangd::CodeCompleteOptions Opts; - IndexRequestCollector Requests; - Opts.Index = &Requests; - completions(Code, {}, Opts); - return Requests.consumeRequests(); -} - -TEST(CompletionTest, UnqualifiedIdQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace std {} - using namespace std; - namespace ns { - void f() { - vec^ - } - } - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("", "ns::", "std::")))); -} - -TEST(CompletionTest, EnclosingScopeComesFirst) { - auto Requests = captureIndexRequests(R"cpp( - namespace std {} - using namespace std; - namespace nx { - namespace ns { - namespace { - void f() { - vec^ - } - } - } - } - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field( - &FuzzyFindRequest::Scopes, - UnorderedElementsAre("", "std::", "nx::ns::", "nx::")))); - EXPECT_EQ(Requests[0].Scopes[0], "nx::ns::"); -} - -TEST(CompletionTest, ResolvedQualifiedIdQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace ns1 {} - namespace ns2 {} // ignore - namespace ns3 { namespace nns3 {} } - namespace foo { - using namespace ns1; - using namespace ns3::nns3; - } - namespace ns { - void f() { - foo::^ - } - } - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field( - &FuzzyFindRequest::Scopes, - UnorderedElementsAre("foo::", "ns1::", "ns3::nns3::")))); -} - -TEST(CompletionTest, UnresolvedQualifierIdQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace a {} - using namespace a; - namespace ns { - void f() { - bar::^ - } - } // namespace ns - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field( - &FuzzyFindRequest::Scopes, - UnorderedElementsAre("a::bar::", "ns::bar::", "bar::")))); -} - -TEST(CompletionTest, UnresolvedNestedQualifierIdQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace a {} - using namespace a; - namespace ns { - void f() { - ::a::bar::^ - } - } // namespace ns - )cpp"); - - EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("a::bar::")))); -} - -TEST(CompletionTest, EmptyQualifiedQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace ns { - void f() { - ^ - } - } // namespace ns - )cpp"); - - EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("", "ns::")))); -} - -TEST(CompletionTest, GlobalQualifiedQuery) { - auto Requests = captureIndexRequests(R"cpp( - namespace ns { - void f() { - ::^ - } - } // namespace ns - )cpp"); - - EXPECT_THAT(Requests, ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("")))); -} - -TEST(CompletionTest, NoDuplicatedQueryScopes) { - auto Requests = captureIndexRequests(R"cpp( - namespace {} - - namespace na { - namespace {} - namespace nb { - ^ - } // namespace nb - } // namespace na - )cpp"); - - EXPECT_THAT(Requests, - ElementsAre(Field(&FuzzyFindRequest::Scopes, - UnorderedElementsAre("na::", "na::nb::", "")))); -} - -TEST(CompletionTest, NoIndexCompletionsInsideClasses) { - auto Completions = completions( - R"cpp( - struct Foo { - int SomeNameOfField; - typedef int SomeNameOfTypedefField; - }; - - Foo::^)cpp", - {func("::SomeNameInTheIndex"), func("::Foo::SomeNameInTheIndex")}); - - EXPECT_THAT(Completions.Completions, - AllOf(Contains(Labeled("SomeNameOfField")), - Contains(Labeled("SomeNameOfTypedefField")), - Not(Contains(Labeled("SomeNameInTheIndex"))))); -} - -TEST(CompletionTest, NoIndexCompletionsInsideDependentCode) { - { - auto Completions = completions( - R"cpp( - template - void foo() { - T::^ - } - )cpp", - {func("::SomeNameInTheIndex")}); - - EXPECT_THAT(Completions.Completions, - Not(Contains(Labeled("SomeNameInTheIndex")))); - } - - { - auto Completions = completions( - R"cpp( - template - void foo() { - T::template Y::^ - } - )cpp", - {func("::SomeNameInTheIndex")}); - - EXPECT_THAT(Completions.Completions, - Not(Contains(Labeled("SomeNameInTheIndex")))); - } - - { - auto Completions = completions( - R"cpp( - template - void foo() { - T::foo::^ - } - )cpp", - {func("::SomeNameInTheIndex")}); - - EXPECT_THAT(Completions.Completions, - Not(Contains(Labeled("SomeNameInTheIndex")))); - } -} - -TEST(CompletionTest, OverloadBundling) { - clangd::CodeCompleteOptions Opts; - Opts.BundleOverloads = true; - - std::string Context = R"cpp( - struct X { - // Overload with int - int a(int); - // Overload with bool - int a(bool); - int b(float); - }; - int GFuncC(int); - int GFuncD(int); - )cpp"; - - // Member completions are bundled. - EXPECT_THAT(completions(Context + "int y = X().^", {}, Opts).Completions, - UnorderedElementsAre(Labeled("a(…)"), Labeled("b(float)"))); - - // Non-member completions are bundled, including index+sema. - Symbol NoArgsGFunc = func("GFuncC"); - EXPECT_THAT( - completions(Context + "int y = GFunc^", {NoArgsGFunc}, Opts).Completions, - UnorderedElementsAre(Labeled("GFuncC(…)"), Labeled("GFuncD(int)"))); - - // Differences in header-to-insert suppress bundling. - std::string DeclFile = URI::create(testPath("foo")).toString(); - NoArgsGFunc.CanonicalDeclaration.FileURI = DeclFile.c_str(); - NoArgsGFunc.IncludeHeaders.emplace_back("", 1); - EXPECT_THAT( - completions(Context + "int y = GFunc^", {NoArgsGFunc}, Opts).Completions, - UnorderedElementsAre(AllOf(Named("GFuncC"), InsertInclude("")), - Labeled("GFuncC(int)"), Labeled("GFuncD(int)"))); - - // Examine a bundled completion in detail. - auto A = - completions(Context + "int y = X().a^", {}, Opts).Completions.front(); - EXPECT_EQ(A.Name, "a"); - EXPECT_EQ(A.Signature, "(…)"); - EXPECT_EQ(A.BundleSize, 2u); - EXPECT_EQ(A.Kind, CompletionItemKind::Method); - EXPECT_EQ(A.ReturnType, "int"); // All overloads return int. - // For now we just return one of the doc strings arbitrarily. - EXPECT_THAT(A.Documentation, AnyOf(HasSubstr("Overload with int"), - HasSubstr("Overload with bool"))); - EXPECT_EQ(A.SnippetSuffix, "($0)"); -} - -TEST(CompletionTest, DocumentationFromChangedFileCrash) { - MockFSProvider FS; - auto FooH = testPath("foo.h"); - auto FooCpp = testPath("foo.cpp"); - FS.Files[FooH] = R"cpp( - // this is my documentation comment. - int func(); - )cpp"; - FS.Files[FooCpp] = ""; - - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - Annotations Source(R"cpp( - #include "foo.h" - int func() { - // This makes sure we have func from header in the AST. - } - int a = fun^ - )cpp"); - Server.addDocument(FooCpp, Source.code(), WantDiagnostics::Yes); - // We need to wait for preamble to build. - ASSERT_TRUE(Server.blockUntilIdleForTest()); - - // Change the header file. Completion will reuse the old preamble! - FS.Files[FooH] = R"cpp( - int func(); - )cpp"; - - clangd::CodeCompleteOptions Opts; - Opts.IncludeComments = true; - CodeCompleteResult Completions = - cantFail(runCodeComplete(Server, FooCpp, Source.point(), Opts)); - // We shouldn't crash. Unfortunately, current workaround is to not produce - // comments for symbols from headers. - EXPECT_THAT(Completions.Completions, - Contains(AllOf(Not(IsDocumented()), Named("func")))); -} - -TEST(CompletionTest, NonDocComments) { - MockFSProvider FS; - auto FooCpp = testPath("foo.cpp"); - FS.Files[FooCpp] = ""; - - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - Annotations Source(R"cpp( - // We ignore namespace comments, for rationale see CodeCompletionStrings.h. - namespace comments_ns { - } - - // ------------------ - int comments_foo(); - - // A comment and a decl are separated by newlines. - // Therefore, the comment shouldn't show up as doc comment. - - int comments_bar(); - - // this comment should be in the results. - int comments_baz(); - - - template - struct Struct { - int comments_qux(); - int comments_quux(); - }; - - - // This comment should not be there. - - template - int Struct::comments_qux() { - } - - // This comment **should** be in results. - template - int Struct::comments_quux() { - int a = comments^; - } - )cpp"); - // FIXME: Auto-completion in a template requires disabling delayed template - // parsing. - CDB.ExtraClangFlags.push_back("-fno-delayed-template-parsing"); - runAddDocument(Server, FooCpp, Source.code(), WantDiagnostics::Yes); - CodeCompleteResult Completions = cantFail(runCodeComplete( - Server, FooCpp, Source.point(), clangd::CodeCompleteOptions())); - - // We should not get any of those comments in completion. - EXPECT_THAT( - Completions.Completions, - UnorderedElementsAre(AllOf(Not(IsDocumented()), Named("comments_foo")), - AllOf(IsDocumented(), Named("comments_baz")), - AllOf(IsDocumented(), Named("comments_quux")), - AllOf(Not(IsDocumented()), Named("comments_ns")), - // FIXME(ibiryukov): the following items should have - // empty documentation, since they are separated from - // a comment with an empty line. Unfortunately, I - // couldn't make Sema tests pass if we ignore those. - AllOf(IsDocumented(), Named("comments_bar")), - AllOf(IsDocumented(), Named("comments_qux")))); -} - -TEST(CompletionTest, CompleteOnInvalidLine) { - auto FooCpp = testPath("foo.cpp"); - - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - MockFSProvider FS; - FS.Files[FooCpp] = "// empty file"; - - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - // Run completion outside the file range. - Position Pos; - Pos.line = 100; - Pos.character = 0; - EXPECT_THAT_EXPECTED( - runCodeComplete(Server, FooCpp, Pos, clangd::CodeCompleteOptions()), - Failed()); -} - -TEST(CompletionTest, QualifiedNames) { - auto Results = completions( - R"cpp( - namespace ns { int local; void both(); } - void f() { ::ns::^ } - )cpp", - {func("ns::both"), cls("ns::Index")}); - // We get results from both index and sema, with no duplicates. - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(Scope("ns::"), Scope("ns::"), Scope("ns::"))); -} - -TEST(CompletionTest, Render) { - CodeCompletion C; - C.Name = "x"; - C.Signature = "(bool) const"; - C.SnippetSuffix = "(${0:bool})"; - C.ReturnType = "int"; - C.RequiredQualifier = "Foo::"; - C.Scope = "ns::Foo::"; - C.Documentation = "This is x()."; - C.Includes.emplace_back(); - auto &Include = C.Includes.back(); - Include.Header = "\"foo.h\""; - C.Kind = CompletionItemKind::Method; - C.Score.Total = 1.0; - C.Origin = SymbolOrigin::AST | SymbolOrigin::Static; - - CodeCompleteOptions Opts; - Opts.IncludeIndicator.Insert = "^"; - Opts.IncludeIndicator.NoInsert = ""; - Opts.EnableSnippets = false; - - auto R = C.render(Opts); - EXPECT_EQ(R.label, "Foo::x(bool) const"); - EXPECT_EQ(R.insertText, "Foo::x"); - EXPECT_EQ(R.insertTextFormat, InsertTextFormat::PlainText); - EXPECT_EQ(R.filterText, "x"); - EXPECT_EQ(R.detail, "int\n\"foo.h\""); - EXPECT_EQ(R.documentation, "This is x()."); - EXPECT_THAT(R.additionalTextEdits, IsEmpty()); - EXPECT_EQ(R.sortText, sortText(1.0, "x")); - EXPECT_FALSE(R.deprecated); - - Opts.EnableSnippets = true; - R = C.render(Opts); - EXPECT_EQ(R.insertText, "Foo::x(${0:bool})"); - EXPECT_EQ(R.insertTextFormat, InsertTextFormat::Snippet); - - Include.Insertion.emplace(); - R = C.render(Opts); - EXPECT_EQ(R.label, "^Foo::x(bool) const"); - EXPECT_THAT(R.additionalTextEdits, Not(IsEmpty())); - - Opts.ShowOrigins = true; - R = C.render(Opts); - EXPECT_EQ(R.label, "^[AS]Foo::x(bool) const"); - - C.BundleSize = 2; - R = C.render(Opts); - EXPECT_EQ(R.detail, "[2 overloads]\n\"foo.h\""); - - C.Deprecated = true; - R = C.render(Opts); - EXPECT_TRUE(R.deprecated); -} - -TEST(CompletionTest, IgnoreRecoveryResults) { - auto Results = completions( - R"cpp( - namespace ns { int NotRecovered() { return 0; } } - void f() { - // Sema enters recovery mode first and then normal mode. - if (auto x = ns::NotRecover^) - } - )cpp"); - EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("NotRecovered"))); -} - -TEST(CompletionTest, ScopeOfClassFieldInConstructorInitializer) { - auto Results = completions( - R"cpp( - namespace ns { - class X { public: X(); int x_; }; - X::X() : x_^(0) {} - } - )cpp"); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Scope("ns::X::"), Named("x_")))); -} - -TEST(CompletionTest, CodeCompletionContext) { - auto Results = completions( - R"cpp( - namespace ns { - class X { public: X(); int x_; }; - void f() { - X x; - x.^; - } - } - )cpp"); - - EXPECT_THAT(Results.Context, CodeCompletionContext::CCC_DotMemberAccess); -} - -TEST(CompletionTest, FixItForArrowToDot) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - Annotations TestCode( - R"cpp( - class Auxilary { - public: - void AuxFunction(); - }; - class ClassWithPtr { - public: - void MemberFunction(); - Auxilary* operator->() const; - Auxilary* Aux; - }; - void f() { - ClassWithPtr x; - x[[->]]^; - } - )cpp"); - auto Results = - completions(Server, TestCode.code(), TestCode.point(), {}, Opts); - EXPECT_EQ(Results.Completions.size(), 3u); - - TextEdit ReplacementEdit; - ReplacementEdit.range = TestCode.range(); - ReplacementEdit.newText = "."; - for (const auto &C : Results.Completions) { - EXPECT_TRUE(C.FixIts.size() == 1u || C.Name == "AuxFunction"); - if (!C.FixIts.empty()) { - EXPECT_THAT(C.FixIts, ElementsAre(ReplacementEdit)); - } - } -} - -TEST(CompletionTest, FixItForDotToArrow) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - Annotations TestCode( - R"cpp( - class Auxilary { - public: - void AuxFunction(); - }; - class ClassWithPtr { - public: - void MemberFunction(); - Auxilary* operator->() const; - Auxilary* Aux; - }; - void f() { - ClassWithPtr x; - x[[.]]^; - } - )cpp"); - auto Results = - completions(Server, TestCode.code(), TestCode.point(), {}, Opts); - EXPECT_EQ(Results.Completions.size(), 3u); - - TextEdit ReplacementEdit; - ReplacementEdit.range = TestCode.range(); - ReplacementEdit.newText = "->"; - for (const auto &C : Results.Completions) { - EXPECT_TRUE(C.FixIts.empty() || C.Name == "AuxFunction"); - if (!C.FixIts.empty()) { - EXPECT_THAT(C.FixIts, ElementsAre(ReplacementEdit)); - } - } -} - -TEST(CompletionTest, RenderWithFixItMerged) { - TextEdit FixIt; - FixIt.range.end.character = 5; - FixIt.newText = "->"; - - CodeCompletion C; - C.Name = "x"; - C.RequiredQualifier = "Foo::"; - C.FixIts = {FixIt}; - C.CompletionTokenRange.start.character = 5; - - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - - auto R = C.render(Opts); - EXPECT_TRUE(R.textEdit); - EXPECT_EQ(R.textEdit->newText, "->Foo::x"); - EXPECT_TRUE(R.additionalTextEdits.empty()); -} - -TEST(CompletionTest, RenderWithFixItNonMerged) { - TextEdit FixIt; - FixIt.range.end.character = 4; - FixIt.newText = "->"; - - CodeCompletion C; - C.Name = "x"; - C.RequiredQualifier = "Foo::"; - C.FixIts = {FixIt}; - C.CompletionTokenRange.start.character = 5; - - CodeCompleteOptions Opts; - Opts.IncludeFixIts = true; - - auto R = C.render(Opts); - EXPECT_TRUE(R.textEdit); - EXPECT_EQ(R.textEdit->newText, "Foo::x"); - EXPECT_THAT(R.additionalTextEdits, UnorderedElementsAre(FixIt)); -} - -TEST(CompletionTest, CompletionTokenRange) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - constexpr const char *TestCodes[] = { - R"cpp( - class Auxilary { - public: - void AuxFunction(); - }; - void f() { - Auxilary x; - x.[[Aux]]^; - } - )cpp", - R"cpp( - class Auxilary { - public: - void AuxFunction(); - }; - void f() { - Auxilary x; - x.[[]]^; - } - )cpp"}; - for (const auto &Text : TestCodes) { - Annotations TestCode(Text); - auto Results = completions(Server, TestCode.code(), TestCode.point()); - - EXPECT_EQ(Results.Completions.size(), 1u); - EXPECT_THAT(Results.Completions.front().CompletionTokenRange, - TestCode.range()); - } -} - -TEST(SignatureHelpTest, OverloadsOrdering) { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, float y); - void foo(float x, int y); - void foo(float x, float y); - void foo(int x, int y = 0); - int main() { foo(^); } - )cpp"); - EXPECT_THAT( - Results.signatures, - ElementsAre( - Sig("foo(int x) -> void", {"int x"}), - Sig("foo(int x, int y = 0) -> void", {"int x", "int y = 0"}), - Sig("foo(float x, int y) -> void", {"float x", "int y"}), - Sig("foo(int x, float y) -> void", {"int x", "float y"}), - Sig("foo(float x, float y) -> void", {"float x", "float y"}))); - // We always prefer the first signature. - EXPECT_EQ(0, Results.activeSignature); - EXPECT_EQ(0, Results.activeParameter); -} - -TEST(SignatureHelpTest, InstantiatedSignatures) { - StringRef Sig0 = R"cpp( - template - void foo(T, T, T); - - int main() { - foo(^); - } - )cpp"; - - EXPECT_THAT(signatures(Sig0).signatures, - ElementsAre(Sig("foo(T, T, T) -> void", {"T", "T", "T"}))); - - StringRef Sig1 = R"cpp( - template - void foo(T, T, T); - - int main() { - foo(10, ^); - })cpp"; - - EXPECT_THAT(signatures(Sig1).signatures, - ElementsAre(Sig("foo(T, T, T) -> void", {"T", "T", "T"}))); - - StringRef Sig2 = R"cpp( - template - void foo(T...); - - int main() { - foo(^); - } - )cpp"; - - EXPECT_THAT(signatures(Sig2).signatures, - ElementsAre(Sig("foo(T...) -> void", {"T..."}))); - - // It is debatable whether we should substitute the outer template parameter - // ('T') in that case. Currently we don't substitute it in signature help, but - // do substitute in code complete. - // FIXME: make code complete and signature help consistent, figure out which - // way is better. - StringRef Sig3 = R"cpp( - template - struct X { - template - void foo(T, U); - }; - - int main() { - X().foo(^) - } - )cpp"; - - EXPECT_THAT(signatures(Sig3).signatures, - ElementsAre(Sig("foo(T, U) -> void", {"T", "U"}))); -} - -TEST(SignatureHelpTest, IndexDocumentation) { - Symbol Foo0 = sym("foo", index::SymbolKind::Function, "@F@\\0#"); - Foo0.Documentation = "Doc from the index"; - Symbol Foo1 = sym("foo", index::SymbolKind::Function, "@F@\\0#I#"); - Foo1.Documentation = "Doc from the index"; - Symbol Foo2 = sym("foo", index::SymbolKind::Function, "@F@\\0#I#I#"); - - StringRef Sig0 = R"cpp( - int foo(); - int foo(double); - - void test() { - foo(^); - } - )cpp"; - - EXPECT_THAT( - signatures(Sig0, {Foo0}).signatures, - ElementsAre(AllOf(Sig("foo() -> int", {}), SigDoc("Doc from the index")), - AllOf(Sig("foo(double) -> int", {"double"}), SigDoc("")))); - - StringRef Sig1 = R"cpp( - int foo(); - // Overriden doc from sema - int foo(int); - // Doc from sema - int foo(int, int); - - void test() { - foo(^); - } - )cpp"; - - EXPECT_THAT( - signatures(Sig1, {Foo0, Foo1, Foo2}).signatures, - ElementsAre(AllOf(Sig("foo() -> int", {}), SigDoc("Doc from the index")), - AllOf(Sig("foo(int) -> int", {"int"}), - SigDoc("Overriden doc from sema")), - AllOf(Sig("foo(int, int) -> int", {"int", "int"}), - SigDoc("Doc from sema")))); -} - -TEST(SignatureHelpTest, DynamicIndexDocumentation) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer::Options Opts = ClangdServer::optsForTest(); - Opts.BuildDynamicSymbolIndex = true; - ClangdServer Server(CDB, FS, DiagConsumer, Opts); - - FS.Files[testPath("foo.h")] = R"cpp( - struct Foo { - // Member doc - int foo(); - }; - )cpp"; - Annotations FileContent(R"cpp( - #include "foo.h" - void test() { - Foo f; - f.foo(^); - } - )cpp"); - auto File = testPath("test.cpp"); - Server.addDocument(File, FileContent.code()); - // Wait for the dynamic index being built. - ASSERT_TRUE(Server.blockUntilIdleForTest()); - EXPECT_THAT( - llvm::cantFail(runSignatureHelp(Server, File, FileContent.point())) - .signatures, - ElementsAre(AllOf(Sig("foo() -> int", {}), SigDoc("Member doc")))); -} - -TEST(CompletionTest, CompletionFunctionArgsDisabled) { - CodeCompleteOptions Opts; - Opts.EnableSnippets = true; - Opts.EnableFunctionArgSnippets = false; - - { - auto Results = completions( - R"cpp( - void xfoo(); - void xfoo(int x, int y); - void f() { xfo^ })cpp", - {}, Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Named("xfoo"), SnippetSuffix("()")), - AllOf(Named("xfoo"), SnippetSuffix("($0)")))); - } - { - auto Results = completions( - R"cpp( - void xbar(); - void f() { xba^ })cpp", - {}, Opts); - EXPECT_THAT(Results.Completions, UnorderedElementsAre(AllOf( - Named("xbar"), SnippetSuffix("()")))); - } - { - Opts.BundleOverloads = true; - auto Results = completions( - R"cpp( - void xfoo(); - void xfoo(int x, int y); - void f() { xfo^ })cpp", - {}, Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Named("xfoo"), SnippetSuffix("($0)")))); - } - { - auto Results = completions( - R"cpp( - template - void xfoo(int a, U b); - void f() { xfo^ })cpp", - {}, Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Named("xfoo"), SnippetSuffix("<$1>($0)")))); - } - { - auto Results = completions( - R"cpp( - template - class foo_class{}; - template - using foo_alias = T**; - void f() { foo_^ })cpp", - {}, Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Named("foo_class"), SnippetSuffix("<$0>")), - AllOf(Named("foo_alias"), SnippetSuffix("<$0>")))); - } -} - -TEST(CompletionTest, SuggestOverrides) { - constexpr const char *const Text(R"cpp( - class A { - public: - virtual void vfunc(bool param); - virtual void vfunc(bool param, int p); - void func(bool param); - }; - class B : public A { - virtual void ttt(bool param) const; - void vfunc(bool param, int p) override; - }; - class C : public B { - public: - void vfunc(bool param) override; - ^ - }; - )cpp"); - const auto Results = completions(Text); - EXPECT_THAT(Results.Completions, - AllOf(Contains(Labeled("void vfunc(bool param, int p) override")), - Contains(Labeled("void ttt(bool param) const override")), - Not(Contains(Labeled("void vfunc(bool param) override"))))); -} - -TEST(CompletionTest, OverridesNonIdentName) { - // Check the completions call does not crash. - completions(R"cpp( - struct Base { - virtual ~Base() = 0; - virtual operator int() = 0; - virtual Base& operator+(Base&) = 0; - }; - - struct Derived : Base { - ^ - }; - )cpp"); -} - -TEST(GuessCompletionPrefix, Filters) { - for (llvm::StringRef Case : { - "[[scope::]][[ident]]^", - "[[]][[]]^", - "\n[[]][[]]^", - "[[]][[ab]]^", - "x.[[]][[ab]]^", - "x.[[]][[]]^", - "[[x::]][[ab]]^", - "[[x::]][[]]^", - "[[::x::]][[ab]]^", - "some text [[scope::more::]][[identif]]^ier", - "some text [[scope::]][[mor]]^e::identifier", - "weird case foo::[[::bar::]][[baz]]^", - }) { - Annotations F(Case); - auto Offset = cantFail(positionToOffset(F.code(), F.point())); - auto ToStringRef = [&](Range R) { - return F.code().slice(cantFail(positionToOffset(F.code(), R.start)), - cantFail(positionToOffset(F.code(), R.end))); - }; - auto WantQualifier = ToStringRef(F.ranges()[0]), - WantName = ToStringRef(F.ranges()[1]); - - auto Prefix = guessCompletionPrefix(F.code(), Offset); - // Even when components are empty, check their offsets are correct. - EXPECT_EQ(WantQualifier, Prefix.Qualifier) << Case; - EXPECT_EQ(WantQualifier.begin(), Prefix.Qualifier.begin()) << Case; - EXPECT_EQ(WantName, Prefix.Name) << Case; - EXPECT_EQ(WantName.begin(), Prefix.Name.begin()) << Case; - } -} - -TEST(CompletionTest, EnableSpeculativeIndexRequest) { - MockFSProvider FS; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto File = testPath("foo.cpp"); - Annotations Test(R"cpp( - namespace ns1 { int abc; } - namespace ns2 { int abc; } - void f() { ns1::ab$1^; ns1::ab$2^; } - void f2() { ns2::ab$3^; } - )cpp"); - runAddDocument(Server, File, Test.code()); - clangd::CodeCompleteOptions Opts = {}; - - IndexRequestCollector Requests; - Opts.Index = &Requests; - Opts.SpeculativeIndexRequest = true; - - auto CompleteAtPoint = [&](StringRef P) { - cantFail(runCodeComplete(Server, File, Test.point(P), Opts)); - // Sleep for a while to make sure asynchronous call (if applicable) is also - // triggered before callback is invoked. - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - }; - - CompleteAtPoint("1"); - auto Reqs1 = Requests.consumeRequests(); - ASSERT_EQ(Reqs1.size(), 1u); - EXPECT_THAT(Reqs1[0].Scopes, UnorderedElementsAre("ns1::")); - - CompleteAtPoint("2"); - auto Reqs2 = Requests.consumeRequests(); - // Speculation succeeded. Used speculative index result. - ASSERT_EQ(Reqs2.size(), 1u); - EXPECT_EQ(Reqs2[0], Reqs1[0]); - - CompleteAtPoint("3"); - // Speculation failed. Sent speculative index request and the new index - // request after sema. - auto Reqs3 = Requests.consumeRequests(); - ASSERT_EQ(Reqs3.size(), 2u); -} - -TEST(CompletionTest, InsertTheMostPopularHeader) { - std::string DeclFile = URI::create(testPath("foo")).toString(); - Symbol sym = func("Func"); - sym.CanonicalDeclaration.FileURI = DeclFile.c_str(); - sym.IncludeHeaders.emplace_back("\"foo.h\"", 2); - sym.IncludeHeaders.emplace_back("\"bar.h\"", 1000); - - auto Results = completions("Fun^", {sym}).Completions; - assert(!Results.empty()); - EXPECT_THAT(Results[0], AllOf(Named("Func"), InsertInclude("\"bar.h\""))); - EXPECT_EQ(Results[0].Includes.size(), 2u); -} - -TEST(CompletionTest, NoInsertIncludeIfOnePresent) { - MockFSProvider FS; - MockCompilationDatabase CDB; - - std::string FooHeader = testPath("foo.h"); - FS.Files[FooHeader] = ""; - - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - std::string DeclFile = URI::create(testPath("foo")).toString(); - Symbol sym = func("Func"); - sym.CanonicalDeclaration.FileURI = DeclFile.c_str(); - sym.IncludeHeaders.emplace_back("\"foo.h\"", 2); - sym.IncludeHeaders.emplace_back("\"bar.h\"", 1000); - - EXPECT_THAT( - completions(Server, "#include \"foo.h\"\nFun^", {sym}).Completions, - UnorderedElementsAre( - AllOf(Named("Func"), HasInclude("\"foo.h\""), Not(InsertInclude())))); -} - -TEST(CompletionTest, MergeMacrosFromIndexAndSema) { - Symbol Sym; - Sym.Name = "Clangd_Macro_Test"; - Sym.ID = SymbolID("c:foo.cpp@8@macro@Clangd_Macro_Test"); - Sym.SymInfo.Kind = index::SymbolKind::Macro; - Sym.Flags |= Symbol::IndexedForCodeCompletion; - EXPECT_THAT(completions("#define Clangd_Macro_Test\nClangd_Macro_T^", {Sym}) - .Completions, - UnorderedElementsAre(Named("Clangd_Macro_Test"))); -} - -TEST(CompletionTest, NoMacroFromPreambleIfIndexIsSet) { - auto Results = completions( - R"cpp(#define CLANGD_PREAMBLE x - - int x = 0; - #define CLANGD_MAIN x - void f() { CLANGD_^ } - )cpp", - {func("CLANGD_INDEX")}); - // Index is overriden in code completion options, so the preamble symbol is - // not seen. - EXPECT_THAT(Results.Completions, UnorderedElementsAre(Named("CLANGD_MAIN"), - Named("CLANGD_INDEX"))); -} - -TEST(CompletionTest, DeprecatedResults) { - std::string Body = R"cpp( - void TestClangd(); - void TestClangc() __attribute__((deprecated("", ""))); - )cpp"; - - EXPECT_THAT( - completions(Body + "int main() { TestClang^ }").Completions, - UnorderedElementsAre(AllOf(Named("TestClangd"), Not(Deprecated())), - AllOf(Named("TestClangc"), Deprecated()))); -} - -TEST(SignatureHelpTest, InsideArgument) { - { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, int y); - int main() { foo(1+^); } - )cpp"); - EXPECT_THAT( - Results.signatures, - ElementsAre(Sig("foo(int x) -> void", {"int x"}), - Sig("foo(int x, int y) -> void", {"int x", "int y"}))); - EXPECT_EQ(0, Results.activeParameter); - } - { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, int y); - int main() { foo(1^); } - )cpp"); - EXPECT_THAT( - Results.signatures, - ElementsAre(Sig("foo(int x) -> void", {"int x"}), - Sig("foo(int x, int y) -> void", {"int x", "int y"}))); - EXPECT_EQ(0, Results.activeParameter); - } - { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, int y); - int main() { foo(1^0); } - )cpp"); - EXPECT_THAT( - Results.signatures, - ElementsAre(Sig("foo(int x) -> void", {"int x"}), - Sig("foo(int x, int y) -> void", {"int x", "int y"}))); - EXPECT_EQ(0, Results.activeParameter); - } - { - const auto Results = signatures(R"cpp( - void foo(int x); - void foo(int x, int y); - int bar(int x, int y); - int main() { bar(foo(2, 3^)); } - )cpp"); - EXPECT_THAT(Results.signatures, ElementsAre(Sig("foo(int x, int y) -> void", - {"int x", "int y"}))); - EXPECT_EQ(1, Results.activeParameter); - } -} - -TEST(SignatureHelpTest, ConstructorInitializeFields) { - { - const auto Results = signatures(R"cpp( - struct A { - A(int); - }; - struct B { - B() : a_elem(^) {} - A a_elem; - }; - )cpp"); - EXPECT_THAT(Results.signatures, - UnorderedElementsAre(Sig("A(int)", {"int"}), - Sig("A(A &&)", {"A &&"}), - Sig("A(const A &)", {"const A &"}))); - } - { - const auto Results = signatures(R"cpp( - struct A { - A(int); - }; - struct C { - C(int); - C(A); - }; - struct B { - B() : c_elem(A(1^)) {} - C c_elem; - }; - )cpp"); - EXPECT_THAT(Results.signatures, - UnorderedElementsAre(Sig("A(int)", {"int"}), - Sig("A(A &&)", {"A &&"}), - Sig("A(const A &)", {"const A &"}))); - } -} - -TEST(CompletionTest, IncludedCompletionKinds) { - MockFSProvider FS; - MockCompilationDatabase CDB; - std::string Subdir = testPath("sub"); - std::string SearchDirArg = (Twine("-I") + Subdir).str(); - CDB.ExtraClangFlags = {SearchDirArg.c_str()}; - std::string BarHeader = testPath("sub/bar.h"); - FS.Files[BarHeader] = ""; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest()); - auto Results = completions(Server, - R"cpp( - #include "^" - )cpp"); - EXPECT_THAT(Results.Completions, - AllOf(Has("sub/", CompletionItemKind::Folder), - Has("bar.h\"", CompletionItemKind::File))); -} - -TEST(CompletionTest, NoCrashAtNonAlphaIncludeHeader) { - auto Results = completions( - R"cpp( - #include "./^" - )cpp"); - EXPECT_TRUE(Results.Completions.empty()); -} - -TEST(CompletionTest, NoAllScopesCompletionWhenQualified) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions( - R"cpp( - void f() { na::Clangd^ } - )cpp", - {cls("na::ClangdA"), cls("nx::ClangdX"), cls("Clangd3")}, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre( - AllOf(Qualifier(""), Scope("na::"), Named("ClangdA")))); -} - -TEST(CompletionTest, AllScopesCompletion) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions( - R"cpp( - namespace na { - void f() { Clangd^ } - } - )cpp", - {cls("nx::Clangd1"), cls("ny::Clangd2"), cls("Clangd3"), - cls("na::nb::Clangd4")}, - Opts); - EXPECT_THAT( - Results.Completions, - UnorderedElementsAre(AllOf(Qualifier("nx::"), Named("Clangd1")), - AllOf(Qualifier("ny::"), Named("Clangd2")), - AllOf(Qualifier(""), Scope(""), Named("Clangd3")), - AllOf(Qualifier("nb::"), Named("Clangd4")))); -} - -TEST(CompletionTest, NoQualifierIfShadowed) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions(R"cpp( - namespace nx { class Clangd1 {}; } - using nx::Clangd1; - void f() { Clangd^ } - )cpp", - {cls("nx::Clangd1"), cls("nx::Clangd2")}, Opts); - // Although Clangd1 is from another namespace, Sema tells us it's in-scope and - // needs no qualifier. - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Named("Clangd1")), - AllOf(Qualifier("nx::"), Named("Clangd2")))); -} - -TEST(CompletionTest, NoCompletionsForNewNames) { - clangd::CodeCompleteOptions Opts; - Opts.AllScopes = true; - auto Results = completions(R"cpp( - void f() { int n^ } - )cpp", - {cls("naber"), cls("nx::naber")}, Opts); - EXPECT_THAT(Results.Completions, UnorderedElementsAre()); -} - -TEST(CompletionTest, ObjectiveCMethodNoArguments) { - auto Results = completions(R"objc( - @interface Foo - @property(nonatomic, setter=setXToIgnoreComplete:) int value; - @end - Foo *foo = [Foo new]; int y = [foo v^] - )objc", - /*IndexSymbols=*/{}, - /*Opts=*/{}, "Foo.m"); - - auto C = Results.Completions; - EXPECT_THAT(C, ElementsAre(Named("value"))); - EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); - EXPECT_THAT(C, ElementsAre(ReturnType("int"))); - EXPECT_THAT(C, ElementsAre(Signature(""))); - EXPECT_THAT(C, ElementsAre(SnippetSuffix(""))); -} - -TEST(CompletionTest, ObjectiveCMethodOneArgument) { - auto Results = completions(R"objc( - @interface Foo - - (int)valueForCharacter:(char)c; - @end - Foo *foo = [Foo new]; int y = [foo v^] - )objc", - /*IndexSymbols=*/{}, - /*Opts=*/{}, "Foo.m"); - - auto C = Results.Completions; - EXPECT_THAT(C, ElementsAre(Named("valueForCharacter:"))); - EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); - EXPECT_THAT(C, ElementsAre(ReturnType("int"))); - EXPECT_THAT(C, ElementsAre(Signature("(char)"))); - EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(char)}"))); -} - -TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromBeginning) { - auto Results = completions(R"objc( - @interface Foo - + (id)fooWithValue:(int)value fooey:(unsigned int)fooey; - @end - id val = [Foo foo^] - )objc", - /*IndexSymbols=*/{}, - /*Opts=*/{}, "Foo.m"); - - auto C = Results.Completions; - EXPECT_THAT(C, ElementsAre(Named("fooWithValue:"))); - EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); - EXPECT_THAT(C, ElementsAre(ReturnType("id"))); - EXPECT_THAT(C, ElementsAre(Signature("(int) fooey:(unsigned int)"))); - EXPECT_THAT( - C, ElementsAre(SnippetSuffix("${1:(int)} fooey:${2:(unsigned int)}"))); -} - -TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromMiddle) { - auto Results = completions(R"objc( - @interface Foo - + (id)fooWithValue:(int)value fooey:(unsigned int)fooey; - @end - id val = [Foo fooWithValue:10 f^] - )objc", - /*IndexSymbols=*/{}, - /*Opts=*/{}, "Foo.m"); - - auto C = Results.Completions; - EXPECT_THAT(C, ElementsAre(Named("fooey:"))); - EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); - EXPECT_THAT(C, ElementsAre(ReturnType("id"))); - EXPECT_THAT(C, ElementsAre(Signature("(unsigned int)"))); - EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(unsigned int)}"))); -} - -TEST(CompletionTest, WorksWithNullType) { - auto R = completions(R"cpp( - int main() { - for (auto [loopVar] : y ) { // y has to be unresolved. - int z = loopV^; - } - } - )cpp"); - EXPECT_THAT(R.Completions, ElementsAre(Named("loopVar"))); -} - -TEST(CompletionTest, UsingDecl) { - const char *Header(R"cpp( - void foo(int); - namespace std { - using ::foo; - })cpp"); - const char *Source(R"cpp( - void bar() { - std::^; - })cpp"); - auto Index = TestTU::withHeaderCode(Header).index(); - clangd::CodeCompleteOptions Opts; - Opts.Index = Index.get(); - Opts.AllScopes = true; - auto R = completions(Source, {}, Opts); - EXPECT_THAT(R.Completions, - ElementsAre(AllOf(Scope("std::"), Named("foo"), - Kind(CompletionItemKind::Reference)))); -} - -TEST(CompletionTest, ScopeIsUnresolved) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions(R"cpp( - namespace a { - void f() { b::X^ } - } - )cpp", - {cls("a::b::XYZ")}, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Named("XYZ")))); -} - -TEST(CompletionTest, NestedScopeIsUnresolved) { - clangd::CodeCompleteOptions Opts = {}; - Opts.AllScopes = true; - - auto Results = completions(R"cpp( - namespace a { - namespace b {} - void f() { b::c::X^ } - } - )cpp", - {cls("a::b::c::XYZ")}, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Named("XYZ")))); -} - -// Clang parser gets confused here and doesn't report the ns:: prefix. -// Naive behavior is to insert it again. We examine the source and recover. -TEST(CompletionTest, NamespaceDoubleInsertion) { - clangd::CodeCompleteOptions Opts = {}; - - auto Results = completions(R"cpp( - namespace foo { - namespace ns {} - #define M(X) < X - M(ns::ABC^ - } - )cpp", - {cls("foo::ns::ABCDE")}, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Named("ABCDE")))); -} - -TEST(NoCompileCompletionTest, Basic) { - auto Results = completionsNoCompile(R"cpp( - void func() { - int xyz; - int abc; - ^ - } - )cpp"); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(Named("void"), Named("func"), Named("int"), - Named("xyz"), Named("abc"))); -} - -TEST(NoCompileCompletionTest, WithFilter) { - auto Results = completionsNoCompile(R"cpp( - void func() { - int sym1; - int sym2; - int xyz1; - int xyz2; - sy^ - } - )cpp"); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(Named("sym1"), Named("sym2"))); -} - -TEST(NoCompileCompletionTest, WithIndex) { - std::vector Syms = {func("xxx"), func("a::xxx"), func("ns::b::xxx"), - func("c::xxx"), func("ns::d::xxx")}; - auto Results = completionsNoCompile( - R"cpp( - // Current-scopes, unqualified completion. - using namespace a; - namespace ns { - using namespace b; - void foo() { - xx^ - } - )cpp", - Syms); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Scope("")), - AllOf(Qualifier(""), Scope("a::")), - AllOf(Qualifier(""), Scope("ns::b::")))); - CodeCompleteOptions Opts; - Opts.AllScopes = true; - Results = completionsNoCompile( - R"cpp( - // All-scopes unqualified completion. - using namespace a; - namespace ns { - using namespace b; - void foo() { - xx^ - } - )cpp", - Syms, Opts); - EXPECT_THAT(Results.Completions, - UnorderedElementsAre(AllOf(Qualifier(""), Scope("")), - AllOf(Qualifier(""), Scope("a::")), - AllOf(Qualifier(""), Scope("ns::b::")), - AllOf(Qualifier("c::"), Scope("c::")), - AllOf(Qualifier("d::"), Scope("ns::d::")))); - Results = completionsNoCompile( - R"cpp( - // Qualified completion. - using namespace a; - namespace ns { - using namespace b; - void foo() { - b::xx^ - } - )cpp", - Syms, Opts); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Qualifier(""), Scope("ns::b::")))); - Results = completionsNoCompile( - R"cpp( - // Absolutely qualified completion. - using namespace a; - namespace ns { - using namespace b; - void foo() { - ::a::xx^ - } - )cpp", - Syms, Opts); - EXPECT_THAT(Results.Completions, - ElementsAre(AllOf(Qualifier(""), Scope("a::")))); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/CodeCompletionStringsTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/CodeCompletionStringsTests.cpp @@ -1,160 +0,0 @@ -//===-- CodeCompletionStringsTests.cpp --------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "CodeCompletionStrings.h" -#include "clang/Sema/CodeCompleteConsumer.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -class CompletionStringTest : public ::testing::Test { -public: - CompletionStringTest() - : Allocator(std::make_shared()), - CCTUInfo(Allocator), Builder(*Allocator, CCTUInfo) {} - -protected: - void computeSignature(const CodeCompletionString &CCS) { - Signature.clear(); - Snippet.clear(); - getSignature(CCS, &Signature, &Snippet); - } - - std::shared_ptr Allocator; - CodeCompletionTUInfo CCTUInfo; - CodeCompletionBuilder Builder; - std::string Signature; - std::string Snippet; -}; - -TEST_F(CompletionStringTest, ReturnType) { - Builder.AddResultTypeChunk("result"); - Builder.AddResultTypeChunk("redundant result no no"); - EXPECT_EQ(getReturnType(*Builder.TakeString()), "result"); -} - -TEST_F(CompletionStringTest, Documentation) { - Builder.addBriefComment("This is ignored"); - EXPECT_EQ(formatDocumentation(*Builder.TakeString(), "Is this brief?"), - "Is this brief?"); -} - -TEST_F(CompletionStringTest, DocumentationWithAnnotation) { - Builder.addBriefComment("This is ignored"); - Builder.AddAnnotation("Ano"); - EXPECT_EQ(formatDocumentation(*Builder.TakeString(), "Is this brief?"), - "Annotation: Ano\n\nIs this brief?"); -} - -TEST_F(CompletionStringTest, MultipleAnnotations) { - Builder.AddAnnotation("Ano1"); - Builder.AddAnnotation("Ano2"); - Builder.AddAnnotation("Ano3"); - - EXPECT_EQ(formatDocumentation(*Builder.TakeString(), ""), - "Annotations: Ano1 Ano2 Ano3\n"); -} - -TEST_F(CompletionStringTest, EmptySignature) { - Builder.AddTypedTextChunk("X"); - Builder.AddResultTypeChunk("result no no"); - computeSignature(*Builder.TakeString()); - EXPECT_EQ(Signature, ""); - EXPECT_EQ(Snippet, ""); -} - -TEST_F(CompletionStringTest, Function) { - Builder.AddResultTypeChunk("result no no"); - Builder.addBriefComment("This comment is ignored"); - Builder.AddTypedTextChunk("Foo"); - Builder.AddChunk(CodeCompletionString::CK_LeftParen); - Builder.AddPlaceholderChunk("p1"); - Builder.AddChunk(CodeCompletionString::CK_Comma); - Builder.AddPlaceholderChunk("p2"); - Builder.AddChunk(CodeCompletionString::CK_RightParen); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, "(p1, p2)"); - EXPECT_EQ(Snippet, "(${1:p1}, ${2:p2})"); - EXPECT_EQ(formatDocumentation(*CCS, "Foo's comment"), "Foo's comment"); -} - -TEST_F(CompletionStringTest, EscapeSnippet) { - Builder.AddTypedTextChunk("Foo"); - Builder.AddChunk(CodeCompletionString::CK_LeftParen); - Builder.AddPlaceholderChunk("$p}1\\"); - Builder.AddChunk(CodeCompletionString::CK_RightParen); - - computeSignature(*Builder.TakeString()); - EXPECT_EQ(Signature, "($p}1\\)"); - EXPECT_EQ(Snippet, "(${1:\\$p\\}1\\\\})"); -} - -TEST_F(CompletionStringTest, IgnoreInformativeQualifier) { - Builder.AddTypedTextChunk("X"); - Builder.AddInformativeChunk("info ok"); - Builder.AddInformativeChunk("info no no::"); - computeSignature(*Builder.TakeString()); - EXPECT_EQ(Signature, "info ok"); - EXPECT_EQ(Snippet, ""); -} - -TEST_F(CompletionStringTest, ObjectiveCMethodNoArguments) { - Builder.AddResultTypeChunk("void"); - Builder.AddTypedTextChunk("methodName"); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, ""); - EXPECT_EQ(Snippet, ""); -} - -TEST_F(CompletionStringTest, ObjectiveCMethodOneArgument) { - Builder.AddResultTypeChunk("void"); - Builder.AddTypedTextChunk("methodWithArg:"); - Builder.AddPlaceholderChunk("(type)"); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, "(type)"); - EXPECT_EQ(Snippet, "${1:(type)}"); -} - -TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromBeginning) { - Builder.AddResultTypeChunk("int"); - Builder.AddTypedTextChunk("withFoo:"); - Builder.AddPlaceholderChunk("(type)"); - Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); - Builder.AddTypedTextChunk("bar:"); - Builder.AddPlaceholderChunk("(type2)"); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, "(type) bar:(type2)"); - EXPECT_EQ(Snippet, "${1:(type)} bar:${2:(type2)}"); -} - -TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromMiddle) { - Builder.AddResultTypeChunk("int"); - Builder.AddInformativeChunk("withFoo:"); - Builder.AddTypedTextChunk("bar:"); - Builder.AddPlaceholderChunk("(type2)"); - - auto *CCS = Builder.TakeString(); - computeSignature(*CCS); - EXPECT_EQ(Signature, "(type2)"); - EXPECT_EQ(Snippet, "${1:(type2)}"); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/ContextTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/ContextTests.cpp @@ -1,56 +0,0 @@ -//===-- ContextTests.cpp - Context tests ------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Context.h" - -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { - -TEST(ContextTests, Simple) { - Key IntParam; - Key ExtraIntParam; - - Context Ctx = Context::empty().derive(IntParam, 10).derive(ExtraIntParam, 20); - - EXPECT_EQ(*Ctx.get(IntParam), 10); - EXPECT_EQ(*Ctx.get(ExtraIntParam), 20); -} - -TEST(ContextTests, MoveOps) { - Key> Param; - - Context Ctx = Context::empty().derive(Param, llvm::make_unique(10)); - EXPECT_EQ(**Ctx.get(Param), 10); - - Context NewCtx = std::move(Ctx); - EXPECT_EQ(**NewCtx.get(Param), 10); -} - -TEST(ContextTests, Builders) { - Key ParentParam; - Key ParentAndChildParam; - Key ChildParam; - - Context ParentCtx = - Context::empty().derive(ParentParam, 10).derive(ParentAndChildParam, 20); - Context ChildCtx = - ParentCtx.derive(ParentAndChildParam, 30).derive(ChildParam, 40); - - EXPECT_EQ(*ParentCtx.get(ParentParam), 10); - EXPECT_EQ(*ParentCtx.get(ParentAndChildParam), 20); - EXPECT_EQ(ParentCtx.get(ChildParam), nullptr); - - EXPECT_EQ(*ChildCtx.get(ParentParam), 10); - EXPECT_EQ(*ChildCtx.get(ParentAndChildParam), 30); - EXPECT_EQ(*ChildCtx.get(ChildParam), 40); -} - -} // namespace clangd -} // namespace clang Index: unittests/clangd/DexTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/DexTests.cpp @@ -1,753 +0,0 @@ -//===-- DexTests.cpp ---------------------------------*- C++ -*-----------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "FuzzyMatch.h" -#include "TestFS.h" -#include "TestIndex.h" -#include "index/Index.h" -#include "index/Merge.h" -#include "index/SymbolID.h" -#include "index/dex/Dex.h" -#include "index/dex/Iterator.h" -#include "index/dex/Token.h" -#include "index/dex/Trigram.h" -#include "llvm/Support/ScopedPrinter.h" -#include "llvm/Support/raw_ostream.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include -#include - -using ::testing::AnyOf; -using ::testing::ElementsAre; -using ::testing::IsEmpty; -using ::testing::UnorderedElementsAre; - -namespace clang { -namespace clangd { -namespace dex { -namespace { - -//===----------------------------------------------------------------------===// -// Query iterator tests. -//===----------------------------------------------------------------------===// - -std::vector consumeIDs(Iterator &It) { - auto IDAndScore = consume(It); - std::vector IDs(IDAndScore.size()); - for (size_t I = 0; I < IDAndScore.size(); ++I) - IDs[I] = IDAndScore[I].first; - return IDs; -} - -TEST(DexIterators, DocumentIterator) { - const PostingList L({4, 7, 8, 20, 42, 100}); - auto DocIterator = L.iterator(); - - EXPECT_EQ(DocIterator->peek(), 4U); - EXPECT_FALSE(DocIterator->reachedEnd()); - - DocIterator->advance(); - EXPECT_EQ(DocIterator->peek(), 7U); - EXPECT_FALSE(DocIterator->reachedEnd()); - - DocIterator->advanceTo(20); - EXPECT_EQ(DocIterator->peek(), 20U); - EXPECT_FALSE(DocIterator->reachedEnd()); - - DocIterator->advanceTo(65); - EXPECT_EQ(DocIterator->peek(), 100U); - EXPECT_FALSE(DocIterator->reachedEnd()); - - DocIterator->advanceTo(420); - EXPECT_TRUE(DocIterator->reachedEnd()); -} - -TEST(DexIterators, AndTwoLists) { - Corpus C{10000}; - const PostingList L0({0, 5, 7, 10, 42, 320, 9000}); - const PostingList L1({0, 4, 7, 10, 30, 60, 320, 9000}); - - auto And = C.intersect(L1.iterator(), L0.iterator()); - - EXPECT_FALSE(And->reachedEnd()); - EXPECT_THAT(consumeIDs(*And), ElementsAre(0U, 7U, 10U, 320U, 9000U)); - - And = C.intersect(L0.iterator(), L1.iterator()); - - And->advanceTo(0); - EXPECT_EQ(And->peek(), 0U); - And->advanceTo(5); - EXPECT_EQ(And->peek(), 7U); - And->advanceTo(10); - EXPECT_EQ(And->peek(), 10U); - And->advanceTo(42); - EXPECT_EQ(And->peek(), 320U); - And->advanceTo(8999); - EXPECT_EQ(And->peek(), 9000U); - And->advanceTo(9001); -} - -TEST(DexIterators, AndThreeLists) { - Corpus C{10000}; - const PostingList L0({0, 5, 7, 10, 42, 320, 9000}); - const PostingList L1({0, 4, 7, 10, 30, 60, 320, 9000}); - const PostingList L2({1, 4, 7, 11, 30, 60, 320, 9000}); - - auto And = C.intersect(L0.iterator(), L1.iterator(), L2.iterator()); - EXPECT_EQ(And->peek(), 7U); - And->advanceTo(300); - EXPECT_EQ(And->peek(), 320U); - And->advanceTo(100000); - - EXPECT_TRUE(And->reachedEnd()); -} - -TEST(DexIterators, AndEmpty) { - Corpus C{10000}; - const PostingList L1{1}; - const PostingList L2{2}; - // These iterators are empty, but the optimizer can't tell. - auto Empty1 = C.intersect(L1.iterator(), L2.iterator()); - auto Empty2 = C.intersect(L1.iterator(), L2.iterator()); - // And syncs iterators on construction, and used to fail on empty children. - auto And = C.intersect(std::move(Empty1), std::move(Empty2)); - EXPECT_TRUE(And->reachedEnd()); -} - -TEST(DexIterators, OrTwoLists) { - Corpus C{10000}; - const PostingList L0({0, 5, 7, 10, 42, 320, 9000}); - const PostingList L1({0, 4, 7, 10, 30, 60, 320, 9000}); - - auto Or = C.unionOf(L0.iterator(), L1.iterator()); - - EXPECT_FALSE(Or->reachedEnd()); - EXPECT_EQ(Or->peek(), 0U); - Or->advance(); - EXPECT_EQ(Or->peek(), 4U); - Or->advance(); - EXPECT_EQ(Or->peek(), 5U); - Or->advance(); - EXPECT_EQ(Or->peek(), 7U); - Or->advance(); - EXPECT_EQ(Or->peek(), 10U); - Or->advance(); - EXPECT_EQ(Or->peek(), 30U); - Or->advanceTo(42); - EXPECT_EQ(Or->peek(), 42U); - Or->advanceTo(300); - EXPECT_EQ(Or->peek(), 320U); - Or->advanceTo(9000); - EXPECT_EQ(Or->peek(), 9000U); - Or->advanceTo(9001); - EXPECT_TRUE(Or->reachedEnd()); - - Or = C.unionOf(L0.iterator(), L1.iterator()); - - EXPECT_THAT(consumeIDs(*Or), - ElementsAre(0U, 4U, 5U, 7U, 10U, 30U, 42U, 60U, 320U, 9000U)); -} - -TEST(DexIterators, OrThreeLists) { - Corpus C{10000}; - const PostingList L0({0, 5, 7, 10, 42, 320, 9000}); - const PostingList L1({0, 4, 7, 10, 30, 60, 320, 9000}); - const PostingList L2({1, 4, 7, 11, 30, 60, 320, 9000}); - - auto Or = C.unionOf(L0.iterator(), L1.iterator(), L2.iterator()); - - EXPECT_FALSE(Or->reachedEnd()); - EXPECT_EQ(Or->peek(), 0U); - - Or->advance(); - EXPECT_EQ(Or->peek(), 1U); - - Or->advance(); - EXPECT_EQ(Or->peek(), 4U); - - Or->advanceTo(7); - - Or->advanceTo(59); - EXPECT_EQ(Or->peek(), 60U); - - Or->advanceTo(9001); - EXPECT_TRUE(Or->reachedEnd()); -} - -// FIXME(kbobyrev): The testcase below is similar to what is expected in real -// queries. It should be updated once new iterators (such as boosting, limiting, -// etc iterators) appear. However, it is not exhaustive and it would be -// beneficial to implement automatic generation (e.g. fuzzing) of query trees -// for more comprehensive testing. -TEST(DexIterators, QueryTree) { - // - // +-----------------+ - // |And Iterator:1, 5| - // +--------+--------+ - // | - // | - // +-------------+----------------------+ - // | | - // | | - // +----------v----------+ +----------v------------+ - // |And Iterator: 1, 5, 9| |Or Iterator: 0, 1, 3, 5| - // +----------+----------+ +----------+------------+ - // | | - // +------+-----+ ------------+ - // | | | | - // +-------v-----+ +----+---+ +---v----+ +----v---+ - // |1, 3, 5, 8, 9| |Boost: 2| |Boost: 3| |Boost: 4| - // +-------------+ +----+---+ +---+----+ +----+---+ - // | | | - // +----v-----+ +-v--+ +---v---+ - // |1, 5, 7, 9| |1, 5| |0, 3, 5| - // +----------+ +----+ +-------+ - // - Corpus C{10}; - const PostingList L0({1, 3, 5, 8, 9}); - const PostingList L1({1, 5, 7, 9}); - const PostingList L2({1, 5}); - const PostingList L3({0, 3, 5}); - - // Root of the query tree: [1, 5] - auto Root = C.intersect( - // Lower And Iterator: [1, 5, 9] - C.intersect(L0.iterator(), C.boost(L1.iterator(), 2U)), - // Lower Or Iterator: [0, 1, 5] - C.unionOf(C.boost(L2.iterator(), 3U), C.boost(L3.iterator(), 4U))); - - EXPECT_FALSE(Root->reachedEnd()); - EXPECT_EQ(Root->peek(), 1U); - Root->advanceTo(0); - // Advance multiple times. Shouldn't do anything. - Root->advanceTo(1); - Root->advanceTo(0); - EXPECT_EQ(Root->peek(), 1U); - auto ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 6); - Root->advance(); - EXPECT_EQ(Root->peek(), 5U); - Root->advanceTo(5); - EXPECT_EQ(Root->peek(), 5U); - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 8); - Root->advanceTo(9000); - EXPECT_TRUE(Root->reachedEnd()); -} - -TEST(DexIterators, StringRepresentation) { - Corpus C{10}; - const PostingList L1({1, 3, 5}); - const PostingList L2({1, 7, 9}); - - // No token given, prints full posting list. - auto I1 = L1.iterator(); - EXPECT_EQ(llvm::to_string(*I1), "[1 3 5]"); - - // Token given, uses token's string representation. - Token Tok(Token::Kind::Trigram, "L2"); - auto I2 = L1.iterator(&Tok); - EXPECT_EQ(llvm::to_string(*I2), "T=L2"); - - auto Tree = C.limit(C.intersect(move(I1), move(I2)), 10); - // AND reorders its children, we don't care which order it prints. - EXPECT_THAT(llvm::to_string(*Tree), AnyOf("(LIMIT 10 (& [1 3 5] T=L2))", - "(LIMIT 10 (& T=L2 [1 3 5]))")); -} - -TEST(DexIterators, Limit) { - Corpus C{10000}; - const PostingList L0({3, 6, 7, 20, 42, 100}); - const PostingList L1({1, 3, 5, 6, 7, 30, 100}); - const PostingList L2({0, 3, 5, 7, 8, 100}); - - auto DocIterator = C.limit(L0.iterator(), 42); - EXPECT_THAT(consumeIDs(*DocIterator), ElementsAre(3, 6, 7, 20, 42, 100)); - - DocIterator = C.limit(L0.iterator(), 3); - EXPECT_THAT(consumeIDs(*DocIterator), ElementsAre(3, 6, 7)); - - DocIterator = C.limit(L0.iterator(), 0); - EXPECT_THAT(consumeIDs(*DocIterator), ElementsAre()); - - auto AndIterator = - C.intersect(C.limit(C.all(), 343), C.limit(L0.iterator(), 2), - C.limit(L1.iterator(), 3), C.limit(L2.iterator(), 42)); - EXPECT_THAT(consumeIDs(*AndIterator), ElementsAre(3, 7)); -} - -TEST(DexIterators, True) { - EXPECT_TRUE(Corpus{0}.all()->reachedEnd()); - EXPECT_THAT(consumeIDs(*Corpus{4}.all()), ElementsAre(0, 1, 2, 3)); -} - -TEST(DexIterators, Boost) { - Corpus C{5}; - auto BoostIterator = C.boost(C.all(), 42U); - EXPECT_FALSE(BoostIterator->reachedEnd()); - auto ElementBoost = BoostIterator->consume(); - EXPECT_THAT(ElementBoost, 42U); - - const PostingList L0({2, 4}); - const PostingList L1({1, 4}); - auto Root = C.unionOf(C.all(), C.boost(L0.iterator(), 2U), - C.boost(L1.iterator(), 3U)); - - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 1); - Root->advance(); - EXPECT_THAT(Root->peek(), 1U); - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 3); - - Root->advance(); - EXPECT_THAT(Root->peek(), 2U); - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 2); - - Root->advanceTo(4); - ElementBoost = Root->consume(); - EXPECT_THAT(ElementBoost, 3); -} - -TEST(DexIterators, Optimizations) { - Corpus C{5}; - const PostingList L1{1}; - const PostingList L2{2}; - const PostingList L3{3}; - - // empty and/or yield true/false - EXPECT_EQ(llvm::to_string(*C.intersect()), "true"); - EXPECT_EQ(llvm::to_string(*C.unionOf()), "false"); - - // true/false inside and/or short-circuit - EXPECT_EQ(llvm::to_string(*C.intersect(L1.iterator(), C.all())), "[1]"); - EXPECT_EQ(llvm::to_string(*C.intersect(L1.iterator(), C.none())), "false"); - // Not optimized to avoid breaking boosts. - EXPECT_EQ(llvm::to_string(*C.unionOf(L1.iterator(), C.all())), - "(| [1] true)"); - EXPECT_EQ(llvm::to_string(*C.unionOf(L1.iterator(), C.none())), "[1]"); - - // and/or nested inside and/or are flattened - EXPECT_EQ(llvm::to_string(*C.intersect( - L1.iterator(), C.intersect(L1.iterator(), L1.iterator()))), - "(& [1] [1] [1])"); - EXPECT_EQ(llvm::to_string(*C.unionOf( - L1.iterator(), C.unionOf(L2.iterator(), L3.iterator()))), - "(| [1] [2] [3])"); - - // optimizations combine over multiple levels - EXPECT_EQ(llvm::to_string(*C.intersect( - C.intersect(L1.iterator(), C.intersect()), C.unionOf(C.all()))), - "[1]"); -} - -//===----------------------------------------------------------------------===// -// Search token tests. -//===----------------------------------------------------------------------===// - -testing::Matcher> -tokensAre(std::initializer_list Strings, Token::Kind Kind) { - std::vector Tokens; - for (const auto &TokenData : Strings) { - Tokens.push_back(Token(Kind, TokenData)); - } - return testing::UnorderedElementsAreArray(Tokens); -} - -testing::Matcher> -trigramsAre(std::initializer_list Trigrams) { - return tokensAre(Trigrams, Token::Kind::Trigram); -} - -TEST(DexTrigrams, IdentifierTrigrams) { - EXPECT_THAT(generateIdentifierTrigrams("X86"), - trigramsAre({"x86", "x", "x8"})); - - EXPECT_THAT(generateIdentifierTrigrams("nl"), trigramsAre({"nl", "n"})); - - EXPECT_THAT(generateIdentifierTrigrams("n"), trigramsAre({"n"})); - - EXPECT_THAT(generateIdentifierTrigrams("clangd"), - trigramsAre({"c", "cl", "cla", "lan", "ang", "ngd"})); - - EXPECT_THAT(generateIdentifierTrigrams("abc_def"), - trigramsAre({"a", "ab", "ad", "abc", "abd", "ade", "bcd", "bde", - "cde", "def"})); - - EXPECT_THAT(generateIdentifierTrigrams("a_b_c_d_e_"), - trigramsAre({"a", "a_", "ab", "abc", "bcd", "cde"})); - - EXPECT_THAT(generateIdentifierTrigrams("unique_ptr"), - trigramsAre({"u", "un", "up", "uni", "unp", "upt", "niq", "nip", - "npt", "iqu", "iqp", "ipt", "que", "qup", "qpt", - "uep", "ept", "ptr"})); - - EXPECT_THAT( - generateIdentifierTrigrams("TUDecl"), - trigramsAre({"t", "tu", "td", "tud", "tde", "ude", "dec", "ecl"})); - - EXPECT_THAT(generateIdentifierTrigrams("IsOK"), - trigramsAre({"i", "is", "io", "iso", "iok", "sok"})); - - EXPECT_THAT( - generateIdentifierTrigrams("abc_defGhij__klm"), - trigramsAre({"a", "ab", "ad", "abc", "abd", "ade", "adg", "bcd", - "bde", "bdg", "cde", "cdg", "def", "deg", "dgh", "dgk", - "efg", "egh", "egk", "fgh", "fgk", "ghi", "ghk", "gkl", - "hij", "hik", "hkl", "ijk", "ikl", "jkl", "klm"})); -} - -TEST(DexTrigrams, QueryTrigrams) { - EXPECT_THAT(generateQueryTrigrams("c"), trigramsAre({"c"})); - EXPECT_THAT(generateQueryTrigrams("cl"), trigramsAre({"cl"})); - EXPECT_THAT(generateQueryTrigrams("cla"), trigramsAre({"cla"})); - - EXPECT_THAT(generateQueryTrigrams(""), trigramsAre({})); - EXPECT_THAT(generateQueryTrigrams("_"), trigramsAre({"_"})); - EXPECT_THAT(generateQueryTrigrams("__"), trigramsAre({"__"})); - EXPECT_THAT(generateQueryTrigrams("___"), trigramsAre({})); - - EXPECT_THAT(generateQueryTrigrams("X86"), trigramsAre({"x86"})); - - EXPECT_THAT(generateQueryTrigrams("clangd"), - trigramsAre({"cla", "lan", "ang", "ngd"})); - - EXPECT_THAT(generateQueryTrigrams("abc_def"), - trigramsAre({"abc", "bcd", "cde", "def"})); - - EXPECT_THAT(generateQueryTrigrams("a_b_c_d_e_"), - trigramsAre({"abc", "bcd", "cde"})); - - EXPECT_THAT(generateQueryTrigrams("unique_ptr"), - trigramsAre({"uni", "niq", "iqu", "que", "uep", "ept", "ptr"})); - - EXPECT_THAT(generateQueryTrigrams("TUDecl"), - trigramsAre({"tud", "ude", "dec", "ecl"})); - - EXPECT_THAT(generateQueryTrigrams("IsOK"), trigramsAre({"iso", "sok"})); - - EXPECT_THAT(generateQueryTrigrams("abc_defGhij__klm"), - trigramsAre({"abc", "bcd", "cde", "def", "efg", "fgh", "ghi", - "hij", "ijk", "jkl", "klm"})); -} - -TEST(DexSearchTokens, SymbolPath) { - EXPECT_THAT(generateProximityURIs( - "unittest:///clang-tools-extra/clangd/index/Token.h"), - ElementsAre("unittest:///clang-tools-extra/clangd/index/Token.h", - "unittest:///clang-tools-extra/clangd/index", - "unittest:///clang-tools-extra/clangd", - "unittest:///clang-tools-extra", "unittest:///")); - - EXPECT_THAT(generateProximityURIs("unittest:///a/b/c.h"), - ElementsAre("unittest:///a/b/c.h", "unittest:///a/b", - "unittest:///a", "unittest:///")); -} - -//===----------------------------------------------------------------------===// -// Index tests. -//===----------------------------------------------------------------------===// - -TEST(Dex, Lookup) { - auto I = Dex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab()); - EXPECT_THAT(lookup(*I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::abc", "ns::xyz")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::xyz")); - EXPECT_THAT(lookup(*I, SymbolID("ns::nonono")), UnorderedElementsAre()); -} - -TEST(Dex, FuzzyFind) { - auto Index = - Dex::build(generateSymbols({"ns::ABC", "ns::BCD", "::ABC", - "ns::nested::ABC", "other::ABC", "other::A"}), - RefSlab()); - FuzzyFindRequest Req; - Req.Query = "ABC"; - Req.Scopes = {"ns::"}; - EXPECT_THAT(match(*Index, Req), UnorderedElementsAre("ns::ABC")); - Req.Scopes = {"ns::", "ns::nested::"}; - EXPECT_THAT(match(*Index, Req), - UnorderedElementsAre("ns::ABC", "ns::nested::ABC")); - Req.Query = "A"; - Req.Scopes = {"other::"}; - EXPECT_THAT(match(*Index, Req), - UnorderedElementsAre("other::A", "other::ABC")); - Req.Query = ""; - Req.Scopes = {}; - Req.AnyScope = true; - EXPECT_THAT(match(*Index, Req), - UnorderedElementsAre("ns::ABC", "ns::BCD", "::ABC", - "ns::nested::ABC", "other::ABC", - "other::A")); -} - -TEST(DexTest, DexLimitedNumMatches) { - auto I = Dex::build(generateNumSymbols(0, 100), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "5"; - Req.AnyScope = true; - Req.Limit = 3; - bool Incomplete; - auto Matches = match(*I, Req, &Incomplete); - EXPECT_TRUE(Req.Limit); - EXPECT_EQ(Matches.size(), *Req.Limit); - EXPECT_TRUE(Incomplete); -} - -TEST(DexTest, FuzzyMatch) { - auto I = Dex::build( - generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}), - RefSlab()); - FuzzyFindRequest Req; - Req.Query = "lol"; - Req.AnyScope = true; - Req.Limit = 2; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("LaughingOutLoud", "LittleOldLady")); -} - -TEST(DexTest, ShortQuery) { - auto I = Dex::build(generateSymbols({"OneTwoThreeFour"}), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - bool Incomplete; - - EXPECT_THAT(match(*I, Req, &Incomplete), ElementsAre("OneTwoThreeFour")); - EXPECT_FALSE(Incomplete) << "Empty string is not a short query"; - - Req.Query = "t"; - EXPECT_THAT(match(*I, Req, &Incomplete), ElementsAre()); - EXPECT_TRUE(Incomplete) << "Short queries have different semantics"; - - Req.Query = "tt"; - EXPECT_THAT(match(*I, Req, &Incomplete), ElementsAre()); - EXPECT_TRUE(Incomplete) << "Short queries have different semantics"; - - Req.Query = "ttf"; - EXPECT_THAT(match(*I, Req, &Incomplete), ElementsAre("OneTwoThreeFour")); - EXPECT_FALSE(Incomplete) << "3-char string is not a short query"; -} - -TEST(DexTest, MatchQualifiedNamesWithoutSpecificScope) { - auto I = Dex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.Query = "y"; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3")); -} - -TEST(DexTest, MatchQualifiedNamesWithGlobalScope) { - auto I = Dex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {""}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("y3")); -} - -TEST(DexTest, MatchQualifiedNamesWithOneScope) { - auto I = Dex::build( - generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2")); -} - -TEST(DexTest, MatchQualifiedNamesWithMultipleScopes) { - auto I = Dex::build( - generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::", "b::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3")); -} - -TEST(DexTest, NoMatchNestedScopes) { - auto I = Dex::build(generateSymbols({"a::y1", "a::b::y2"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1")); -} - -TEST(DexTest, WildcardScope) { - auto I = - Dex::build(generateSymbols({"a::y1", "a::b::y2", "c::y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("a::y1", "a::b::y2", "c::y3")); -} - -TEST(DexTest, IgnoreCases) { - auto I = Dex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "AB"; - Req.Scopes = {"ns::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("ns::ABC", "ns::abc")); -} - -TEST(DexTest, UnknownPostingList) { - // Regression test: we used to ignore unknown scopes and accept any symbol. - auto I = Dex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab()); - FuzzyFindRequest Req; - Req.Scopes = {"ns2::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre()); -} - -TEST(DexTest, Lookup) { - auto I = Dex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab()); - EXPECT_THAT(lookup(*I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::abc", "ns::xyz")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::xyz")); - EXPECT_THAT(lookup(*I, SymbolID("ns::nonono")), UnorderedElementsAre()); -} - -TEST(DexTest, SymbolIndexOptionsFilter) { - auto CodeCompletionSymbol = symbol("Completion"); - auto NonCodeCompletionSymbol = symbol("NoCompletion"); - CodeCompletionSymbol.Flags = Symbol::SymbolFlag::IndexedForCodeCompletion; - NonCodeCompletionSymbol.Flags = Symbol::SymbolFlag::None; - std::vector Symbols{CodeCompletionSymbol, NonCodeCompletionSymbol}; - Dex I(Symbols, RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.RestrictForCodeCompletion = false; - EXPECT_THAT(match(I, Req), ElementsAre("Completion", "NoCompletion")); - Req.RestrictForCodeCompletion = true; - EXPECT_THAT(match(I, Req), ElementsAre("Completion")); -} - -TEST(DexTest, ProximityPathsBoosting) { - auto RootSymbol = symbol("root::abc"); - RootSymbol.CanonicalDeclaration.FileURI = "unittest:///file.h"; - auto CloseSymbol = symbol("close::abc"); - CloseSymbol.CanonicalDeclaration.FileURI = "unittest:///a/b/c/d/e/f/file.h"; - - std::vector Symbols{CloseSymbol, RootSymbol}; - Dex I(Symbols, RefSlab()); - - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.Query = "abc"; - // The best candidate can change depending on the proximity paths. - Req.Limit = 1; - - // FuzzyFind request comes from the file which is far from the root: expect - // CloseSymbol to come out. - Req.ProximityPaths = {testPath("a/b/c/d/e/f/file.h")}; - EXPECT_THAT(match(I, Req), ElementsAre("close::abc")); - - // FuzzyFind request comes from the file which is close to the root: expect - // RootSymbol to come out. - Req.ProximityPaths = {testPath("file.h")}; - EXPECT_THAT(match(I, Req), ElementsAre("root::abc")); -} - -TEST(DexTests, Refs) { - llvm::DenseMap> Refs; - auto AddRef = [&](const Symbol &Sym, const char *Filename, RefKind Kind) { - auto &SymbolRefs = Refs[Sym.ID]; - SymbolRefs.emplace_back(); - SymbolRefs.back().Kind = Kind; - SymbolRefs.back().Location.FileURI = Filename; - }; - auto Foo = symbol("foo"); - auto Bar = symbol("bar"); - AddRef(Foo, "foo.h", RefKind::Declaration); - AddRef(Foo, "foo.cc", RefKind::Definition); - AddRef(Foo, "reffoo.h", RefKind::Reference); - AddRef(Bar, "bar.h", RefKind::Declaration); - - RefsRequest Req; - Req.IDs.insert(Foo.ID); - Req.Filter = RefKind::Declaration | RefKind::Definition; - - std::vector Files; - Dex(std::vector{Foo, Bar}, Refs).refs(Req, [&](const Ref &R) { - Files.push_back(R.Location.FileURI); - }); - EXPECT_THAT(Files, UnorderedElementsAre("foo.h", "foo.cc")); - - Req.Limit = 1; - Files.clear(); - Dex(std::vector{Foo, Bar}, Refs).refs(Req, [&](const Ref &R) { - Files.push_back(R.Location.FileURI); - }); - EXPECT_THAT(Files, ElementsAre(AnyOf("foo.h", "foo.cc"))); -} - -TEST(DexTest, PreferredTypesBoosting) { - auto Sym1 = symbol("t1"); - Sym1.Type = "T1"; - auto Sym2 = symbol("t2"); - Sym2.Type = "T2"; - - std::vector Symbols{Sym1, Sym2}; - Dex I(Symbols, RefSlab()); - - FuzzyFindRequest Req; - Req.AnyScope = true; - Req.Query = "t"; - // The best candidate can change depending on the preferred type. - Req.Limit = 1; - - Req.PreferredTypes = {Sym1.Type}; - EXPECT_THAT(match(I, Req), ElementsAre("t1")); - - Req.PreferredTypes = {Sym2.Type}; - EXPECT_THAT(match(I, Req), ElementsAre("t2")); -} - -TEST(DexTest, TemplateSpecialization) { - SymbolSlab::Builder B; - - Symbol S = symbol("TempSpec"); - S.ID = SymbolID("0"); - B.insert(S); - - S = symbol("TempSpec"); - S.ID = SymbolID("1"); - S.TemplateSpecializationArgs = ""; - S.SymInfo.Properties = static_cast( - index::SymbolProperty::TemplateSpecialization); - B.insert(S); - - S = symbol("TempSpec"); - S.ID = SymbolID("2"); - S.TemplateSpecializationArgs = ""; - S.SymInfo.Properties = static_cast( - index::SymbolProperty::TemplatePartialSpecialization); - B.insert(S); - - auto I = dex::Dex::build(std::move(B).build(), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - - Req.Query = "TempSpec"; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("TempSpec", "TempSpec", - "TempSpec")); - - // FIXME: Add filtering for template argument list. - Req.Query = "TempSpec WithFix(testing::Matcher FixMatcher) { - return Field(&Diag::Fixes, ElementsAre(FixMatcher)); -} - -testing::Matcher WithFix(testing::Matcher FixMatcher1, - testing::Matcher FixMatcher2) { - return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2)); -} - -testing::Matcher WithNote(testing::Matcher NoteMatcher) { - return Field(&Diag::Notes, ElementsAre(NoteMatcher)); -} - -MATCHER_P2(Diag, Range, Message, - "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") { - return arg.Range == Range && arg.Message == Message; -} - -MATCHER_P3(Fix, Range, Replacement, Message, - "Fix " + llvm::to_string(Range) + " => " + - testing::PrintToString(Replacement) + " = [" + Message + "]") { - return arg.Message == Message && arg.Edits.size() == 1 && - arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement; -} - -MATCHER_P(EqualToLSPDiag, LSPDiag, - "LSP diagnostic " + llvm::to_string(LSPDiag)) { - if (toJSON(arg) != toJSON(LSPDiag)) { - *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}", - toJSON(LSPDiag), toJSON(arg)).str(); - return false; - } - return true; -} - -MATCHER_P(DiagSource, S, "") { return arg.Source == S; } -MATCHER_P(DiagName, N, "") { return arg.Name == N; } - -MATCHER_P(EqualToFix, Fix, "LSP fix " + llvm::to_string(Fix)) { - if (arg.Message != Fix.Message) - return false; - if (arg.Edits.size() != Fix.Edits.size()) - return false; - for (std::size_t I = 0; I < arg.Edits.size(); ++I) { - if (arg.Edits[I].range != Fix.Edits[I].range || - arg.Edits[I].newText != Fix.Edits[I].newText) - return false; - } - return true; -} - - -// Helper function to make tests shorter. -Position pos(int line, int character) { - Position Res; - Res.line = line; - Res.character = character; - return Res; -} - -TEST(DiagnosticsTest, DiagnosticRanges) { - // Check we report correct ranges, including various edge-cases. - Annotations Test(R"cpp( - namespace test{}; - void $decl[[foo]](); - int main() { - $typo[[go\ -o]](); - foo()$semicolon[[]]//with comments - $unk[[unknown]](); - double $type[[bar]] = "foo"; - struct Foo { int x; }; Foo a; - a.$nomember[[y]]; - test::$nomembernamespace[[test]]; - } - )cpp"); - EXPECT_THAT( - TestTU::withCode(Test.code()).build().getDiagnostics(), - ElementsAre( - // This range spans lines. - AllOf(Diag(Test.range("typo"), - "use of undeclared identifier 'goo'; did you mean 'foo'?"), - DiagSource(Diag::Clang), - DiagName("undeclared_var_use_suggest"), - WithFix( - Fix(Test.range("typo"), "foo", "change 'go\\ o' to 'foo'")), - // This is a pretty normal range. - WithNote(Diag(Test.range("decl"), "'foo' declared here"))), - // This range is zero-width and insertion. Therefore make sure we are - // not expanding it into other tokens. Since we are not going to - // replace those. - AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"), - WithFix(Fix(Test.range("semicolon"), ";", "insert ';'"))), - // This range isn't provided by clang, we expand to the token. - Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"), - Diag(Test.range("type"), - "cannot initialize a variable of type 'double' with an lvalue " - "of type 'const char [4]'"), - Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"), - Diag(Test.range("nomembernamespace"), - "no member named 'test' in namespace 'test'"))); -} - -TEST(DiagnosticsTest, FlagsMatter) { - Annotations Test("[[void]] main() {}"); - auto TU = TestTU::withCode(Test.code()); - EXPECT_THAT(TU.build().getDiagnostics(), - ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"), - WithFix(Fix(Test.range(), "int", - "change 'void' to 'int'"))))); - // Same code built as C gets different diagnostics. - TU.Filename = "Plain.c"; - EXPECT_THAT( - TU.build().getDiagnostics(), - ElementsAre(AllOf( - Diag(Test.range(), "return type of 'main' is not 'int'"), - WithFix(Fix(Test.range(), "int", "change return type to 'int'"))))); -} - -TEST(DiagnosticsTest, DiagnosticPreamble) { - Annotations Test(R"cpp( - #include $[["not-found.h"]] - )cpp"); - - auto TU = TestTU::withCode(Test.code()); - EXPECT_THAT(TU.build().getDiagnostics(), - ElementsAre(testing::AllOf( - Diag(Test.range(), "'not-found.h' file not found"), - DiagSource(Diag::Clang), DiagName("pp_file_not_found")))); -} - -TEST(DiagnosticsTest, ClangTidy) { - Annotations Test(R"cpp( - #include $deprecated[["assert.h"]] - - #define $macrodef[[SQUARE]](X) (X)*(X) - int main() { - return $doubled[[sizeof]](sizeof(int)); - int y = 4; - return SQUARE($macroarg[[++]]y); - } - )cpp"); - auto TU = TestTU::withCode(Test.code()); - TU.HeaderFilename = "assert.h"; // Suppress "not found" error. - TU.ClangTidyChecks = - "-*, bugprone-sizeof-expression, bugprone-macro-repeated-side-effects, " - "modernize-deprecated-headers"; - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre( - AllOf(Diag(Test.range("deprecated"), - "inclusion of deprecated C++ header 'assert.h'; consider " - "using 'cassert' instead"), - DiagSource(Diag::ClangTidy), - DiagName("modernize-deprecated-headers"), - WithFix(Fix(Test.range("deprecated"), "", - "change '\"assert.h\"' to ''"))), - Diag(Test.range("doubled"), - "suspicious usage of 'sizeof(sizeof(...))'"), - AllOf( - Diag(Test.range("macroarg"), - "side effects in the 1st macro argument 'X' are repeated in " - "macro expansion"), - DiagSource(Diag::ClangTidy), - DiagName("bugprone-macro-repeated-side-effects"), - WithNote( - Diag(Test.range("macrodef"), "macro 'SQUARE' defined here"))), - Diag(Test.range("macroarg"), - "multiple unsequenced modifications to 'y'"))); -} - -TEST(DiagnosticsTest, Preprocessor) { - // This looks like a preamble, but there's an #else in the middle! - // Check that: - // - the #else doesn't generate diagnostics (we had this bug) - // - we get diagnostics from the taken branch - // - we get no diagnostics from the not taken branch - Annotations Test(R"cpp( - #ifndef FOO - #define FOO - int a = [[b]]; - #else - int x = y; - #endif - )cpp"); - EXPECT_THAT( - TestTU::withCode(Test.code()).build().getDiagnostics(), - ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'"))); -} - -TEST(DiagnosticsTest, InsideMacros) { - Annotations Test(R"cpp( - #define TEN 10 - #define RET(x) return x + 10 - - int* foo() { - RET($foo[[0]]); - } - int* bar() { - return $bar[[TEN]]; - } - )cpp"); - EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(), - ElementsAre(Diag(Test.range("foo"), - "cannot initialize return object of type " - "'int *' with an rvalue of type 'int'"), - Diag(Test.range("bar"), - "cannot initialize return object of type " - "'int *' with an rvalue of type 'int'"))); -} - -TEST(DiagnosticsTest, NoFixItInMacro) { - Annotations Test(R"cpp( - #define Define(name) void name() {} - - [[Define]](main) - )cpp"); - auto TU = TestTU::withCode(Test.code()); - EXPECT_THAT(TU.build().getDiagnostics(), - ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"), - Not(WithFix(_))))); -} - -TEST(DiagnosticsTest, ToLSP) { - URIForFile MainFile = - URIForFile::canonicalize(testPath("foo/bar/main.cpp"), ""); - URIForFile HeaderFile = - URIForFile::canonicalize(testPath("foo/bar/header.h"), ""); - - clangd::Diag D; - D.ID = clang::diag::err_enum_class_reference; - D.Name = "enum_class_reference"; - D.Source = clangd::Diag::Clang; - D.Message = "something terrible happened"; - D.Range = {pos(1, 2), pos(3, 4)}; - D.InsideMainFile = true; - D.Severity = DiagnosticsEngine::Error; - D.File = "foo/bar/main.cpp"; - D.AbsFile = MainFile.file(); - - clangd::Note NoteInMain; - NoteInMain.Message = "declared somewhere in the main file"; - NoteInMain.Range = {pos(5, 6), pos(7, 8)}; - NoteInMain.Severity = DiagnosticsEngine::Remark; - NoteInMain.File = "../foo/bar/main.cpp"; - NoteInMain.InsideMainFile = true; - NoteInMain.AbsFile = MainFile.file(); - - D.Notes.push_back(NoteInMain); - - clangd::Note NoteInHeader; - NoteInHeader.Message = "declared somewhere in the header file"; - NoteInHeader.Range = {pos(9, 10), pos(11, 12)}; - NoteInHeader.Severity = DiagnosticsEngine::Note; - NoteInHeader.File = "../foo/baz/header.h"; - NoteInHeader.InsideMainFile = false; - NoteInHeader.AbsFile = HeaderFile.file(); - D.Notes.push_back(NoteInHeader); - - clangd::Fix F; - F.Message = "do something"; - D.Fixes.push_back(F); - - // Diagnostics should turn into these: - clangd::Diagnostic MainLSP; - MainLSP.range = D.Range; - MainLSP.severity = getSeverity(DiagnosticsEngine::Error); - MainLSP.code = "enum_class_reference"; - MainLSP.source = "clang"; - MainLSP.message = R"(Something terrible happened (fix available) - -main.cpp:6:7: remark: declared somewhere in the main file - -../foo/baz/header.h:10:11: -note: declared somewhere in the header file)"; - - clangd::Diagnostic NoteInMainLSP; - NoteInMainLSP.range = NoteInMain.Range; - NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark); - NoteInMainLSP.message = R"(Declared somewhere in the main file - -main.cpp:2:3: error: something terrible happened)"; - - ClangdDiagnosticOptions Opts; - // Transform diagnostics and check the results. - std::vector>> LSPDiags; - toLSPDiags(D, MainFile, Opts, - [&](clangd::Diagnostic LSPDiag, ArrayRef Fixes) { - LSPDiags.push_back( - {std::move(LSPDiag), - std::vector(Fixes.begin(), Fixes.end())}); - }); - - EXPECT_THAT( - LSPDiags, - ElementsAre(Pair(EqualToLSPDiag(MainLSP), ElementsAre(EqualToFix(F))), - Pair(EqualToLSPDiag(NoteInMainLSP), IsEmpty()))); - EXPECT_EQ(LSPDiags[0].first.code, "enum_class_reference"); - EXPECT_EQ(LSPDiags[0].first.source, "clang"); - EXPECT_EQ(LSPDiags[1].first.code, ""); - EXPECT_EQ(LSPDiags[1].first.source, ""); - - // Same thing, but don't flatten notes into the main list. - LSPDiags.clear(); - Opts.EmitRelatedLocations = true; - toLSPDiags(D, MainFile, Opts, - [&](clangd::Diagnostic LSPDiag, ArrayRef Fixes) { - LSPDiags.push_back( - {std::move(LSPDiag), - std::vector(Fixes.begin(), Fixes.end())}); - }); - MainLSP.message = "Something terrible happened (fix available)"; - DiagnosticRelatedInformation NoteInMainDRI; - NoteInMainDRI.message = "Declared somewhere in the main file"; - NoteInMainDRI.location.range = NoteInMain.Range; - NoteInMainDRI.location.uri = MainFile; - MainLSP.relatedInformation = {NoteInMainDRI}; - DiagnosticRelatedInformation NoteInHeaderDRI; - NoteInHeaderDRI.message = "Declared somewhere in the header file"; - NoteInHeaderDRI.location.range = NoteInHeader.Range; - NoteInHeaderDRI.location.uri = HeaderFile; - MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI}; - EXPECT_THAT( - LSPDiags, - ElementsAre(Pair(EqualToLSPDiag(MainLSP), ElementsAre(EqualToFix(F))))); -} - -struct SymbolWithHeader { - std::string QName; - std::string DeclaringFile; - std::string IncludeHeader; -}; - -std::unique_ptr -buildIndexWithSymbol(llvm::ArrayRef Syms) { - SymbolSlab::Builder Slab; - for (const auto &S : Syms) { - Symbol Sym = cls(S.QName); - Sym.Flags |= Symbol::IndexedForCodeCompletion; - Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str(); - Sym.Definition.FileURI = S.DeclaringFile.c_str(); - Sym.IncludeHeaders.emplace_back(S.IncludeHeader, 1); - Slab.insert(Sym); - } - return MemIndex::build(std::move(Slab).build(), RefSlab()); -} - -TEST(IncludeFixerTest, IncompleteType) { - Annotations Test(R"cpp( -$insert[[]]namespace ns { - class X; - $nested[[X::]]Nested n; -} -class Y : $base[[public ns::X]] {}; -int main() { - ns::X *x; - x$access[[->]]f(); -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre( - AllOf(Diag(Test.range("nested"), - "incomplete type 'ns::X' named in nested name specifier"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - AllOf(Diag(Test.range("base"), "base class has incomplete type"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - AllOf(Diag(Test.range("access"), - "member access into incomplete type 'ns::X'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))))); -} - -TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) { - Annotations Test(R"cpp( -$insert[[]]namespace ns { - class X; -} -class Y : $base[[public ns::X]] {}; -int main() { - ns::X *x; - x$access[[->]]f(); -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - Symbol Sym = cls("ns::X"); - Sym.Flags |= Symbol::IndexedForCodeCompletion; - Sym.CanonicalDeclaration.FileURI = "unittest:///x.h"; - Sym.Definition.FileURI = "unittest:///x.cc"; - Sym.IncludeHeaders.emplace_back("\"x.h\"", 1); - - SymbolSlab::Builder Slab; - Slab.insert(Sym); - auto Index = MemIndex::build(std::move(Slab).build(), RefSlab()); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT(TU.build().getDiagnostics(), - UnorderedElementsAre( - Diag(Test.range("base"), "base class has incomplete type"), - Diag(Test.range("access"), - "member access into incomplete type 'ns::X'"))); -} - -TEST(IncludeFixerTest, Typo) { - Annotations Test(R"cpp( -$insert[[]]namespace ns { -void foo() { - $unqualified1[[X]] x; - // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be - // considered the unresolved type. - $unqualified2[[X]]::Nested n; -} -} -void bar() { - ns::$qualified1[[X]] x; // ns:: is valid. - ns::$qualified2[[X]](); // Error: no member in namespace - - ::$global[[Global]] glob; -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}, - SymbolWithHeader{"Global", "unittest:///global.h", "\"global.h\""}}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre( - AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"), - AllOf(Diag(Test.range("qualified1"), - "no type named 'X' in namespace 'ns'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - AllOf(Diag(Test.range("qualified2"), - "no member named 'X' in namespace 'ns'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::X"))), - AllOf(Diag(Test.range("global"), - "no type named 'Global' in the global namespace"), - WithFix(Fix(Test.range("insert"), "#include \"global.h\"\n", - "Add include \"global.h\" for symbol Global"))))); -} - -TEST(IncludeFixerTest, MultipleMatchedSymbols) { - Annotations Test(R"cpp( -$insert[[]]namespace na { -namespace nb { -void foo() { - $unqualified[[X]] x; -} -} -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - {SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""}, - SymbolWithHeader{"na::nb::X", "unittest:///b.h", "\"b.h\""}}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT(TU.build().getDiagnostics(), - UnorderedElementsAre(AllOf( - Diag(Test.range("unqualified"), "unknown type name 'X'"), - WithFix(Fix(Test.range("insert"), "#include \"a.h\"\n", - "Add include \"a.h\" for symbol na::X"), - Fix(Test.range("insert"), "#include \"b.h\"\n", - "Add include \"b.h\" for symbol na::nb::X"))))); -} - -TEST(IncludeFixerTest, NoCrashMemebrAccess) { - Annotations Test(R"cpp( - struct X { int xyz; }; - void g() { X x; x.$[[xy]] } - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'"))); -} - -TEST(IncludeFixerTest, UseCachedIndexResults) { - // As index results for the identical request are cached, more than 5 fixes - // are generated. - Annotations Test(R"cpp( -$insert[[]]void foo() { - $x1[[X]] x; - $x2[[X]] x; - $x3[[X]] x; - $x4[[X]] x; - $x5[[X]] x; - $x6[[X]] x; - $x7[[X]] x; -} - -class X; -void bar(X *x) { - x$a1[[->]]f(); - x$a2[[->]]f(); - x$a3[[->]]f(); - x$a4[[->]]f(); - x$a5[[->]]f(); - x$a6[[->]]f(); - x$a7[[->]]f(); -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = - buildIndexWithSymbol(SymbolWithHeader{"X", "unittest:///a.h", "\"a.h\""}); - TU.ExternalIndex = Index.get(); - - auto Parsed = TU.build(); - for (const auto &D : Parsed.getDiagnostics()) { - EXPECT_EQ(D.Fixes.size(), 1u); - EXPECT_EQ(D.Fixes[0].Message, - std::string("Add include \"a.h\" for symbol X")); - } -} - -TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) { - Annotations Test(R"cpp( -$insert[[]]namespace ns { -} -void g() { ns::$[[scope]]::X_Y(); } - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre(AllOf( - Diag(Test.range(), "no member named 'scope' in namespace 'ns'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol ns::scope::X_Y"))))); -} - -TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) { - Annotations Test(R"cpp( -$insert[[]]namespace clang { -void f() { - // "clangd::" will be corrected to "clang::" by Sema. - $q1[[clangd]]::$x[[X]] x; - $q2[[clangd]]::$ns[[ns]]::Y y; -} -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""}, - SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT( - TU.build().getDiagnostics(), - UnorderedElementsAre( - AllOf( - Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; " - "did you mean 'clang'?"), - WithFix(_, // change clangd to clang - Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol clang::clangd::X"))), - AllOf( - Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol clang::clangd::X"))), - AllOf( - Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; " - "did you mean 'clang'?"), - WithFix( - _, // change clangd to clangd - Fix(Test.range("insert"), "#include \"y.h\"\n", - "Add include \"y.h\" for symbol clang::clangd::ns::Y"))), - AllOf(Diag(Test.range("ns"), - "no member named 'ns' in namespace 'clang'"), - WithFix(Fix( - Test.range("insert"), "#include \"y.h\"\n", - "Add include \"y.h\" for symbol clang::clangd::ns::Y"))))); -} - -TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) { - Annotations Test(R"cpp( -$insert[[]]namespace a {} -namespace b = a; -namespace c { - b::$[[X]] x; -} - )cpp"); - auto TU = TestTU::withCode(Test.code()); - auto Index = buildIndexWithSymbol( - SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""}); - TU.ExternalIndex = Index.get(); - - EXPECT_THAT(TU.build().getDiagnostics(), - UnorderedElementsAre(AllOf( - Diag(Test.range(), "no type named 'X' in namespace 'a'"), - WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n", - "Add include \"x.h\" for symbol a::X"))))); -} - -} // namespace -} // namespace clangd -} // namespace clang - Index: unittests/clangd/DraftStoreTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/DraftStoreTests.cpp @@ -1,347 +0,0 @@ -//===-- DraftStoreTests.cpp -------------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Annotations.h" -#include "DraftStore.h" -#include "SourceCode.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -struct IncrementalTestStep { - llvm::StringRef Src; - llvm::StringRef Contents; -}; - -int rangeLength(llvm::StringRef Code, const Range &Rng) { - llvm::Expected Start = positionToOffset(Code, Rng.start); - llvm::Expected End = positionToOffset(Code, Rng.end); - assert(Start); - assert(End); - return *End - *Start; -} - -/// Send the changes one by one to updateDraft, verify the intermediate results. -void stepByStep(llvm::ArrayRef Steps) { - DraftStore DS; - Annotations InitialSrc(Steps.front().Src); - constexpr llvm::StringLiteral Path("/hello.cpp"); - - // Set the initial content. - DS.addDraft(Path, InitialSrc.code()); - - for (size_t i = 1; i < Steps.size(); i++) { - Annotations SrcBefore(Steps[i - 1].Src); - Annotations SrcAfter(Steps[i].Src); - llvm::StringRef Contents = Steps[i - 1].Contents; - TextDocumentContentChangeEvent Event{ - SrcBefore.range(), - rangeLength(SrcBefore.code(), SrcBefore.range()), - Contents.str(), - }; - - llvm::Expected Result = DS.updateDraft(Path, {Event}); - ASSERT_TRUE(!!Result); - EXPECT_EQ(*Result, SrcAfter.code()); - EXPECT_EQ(*DS.getDraft(Path), SrcAfter.code()); - } -} - -/// Send all the changes at once to updateDraft, check only the final result. -void allAtOnce(llvm::ArrayRef Steps) { - DraftStore DS; - Annotations InitialSrc(Steps.front().Src); - Annotations FinalSrc(Steps.back().Src); - constexpr llvm::StringLiteral Path("/hello.cpp"); - std::vector Changes; - - for (size_t i = 0; i < Steps.size() - 1; i++) { - Annotations Src(Steps[i].Src); - llvm::StringRef Contents = Steps[i].Contents; - - Changes.push_back({ - Src.range(), - rangeLength(Src.code(), Src.range()), - Contents.str(), - }); - } - - // Set the initial content. - DS.addDraft(Path, InitialSrc.code()); - - llvm::Expected Result = DS.updateDraft(Path, Changes); - - ASSERT_TRUE(!!Result) << llvm::toString(Result.takeError()); - EXPECT_EQ(*Result, FinalSrc.code()); - EXPECT_EQ(*DS.getDraft(Path), FinalSrc.code()); -} - -TEST(DraftStoreIncrementalUpdateTest, Simple) { - // clang-format off - IncrementalTestStep Steps[] = - { - // Replace a range - { -R"cpp(static int -hello[[World]]() -{})cpp", - "Universe" - }, - // Delete a range - { -R"cpp(static int -hello[[Universe]]() -{})cpp", - "" - }, - // Add a range - { -R"cpp(static int -hello[[]]() -{})cpp", - "Monde" - }, - { -R"cpp(static int -helloMonde() -{})cpp", - "" - } - }; - // clang-format on - - stepByStep(Steps); - allAtOnce(Steps); -} - -TEST(DraftStoreIncrementalUpdateTest, MultiLine) { - // clang-format off - IncrementalTestStep Steps[] = - { - // Replace a range - { -R"cpp(static [[int -helloWorld]]() -{})cpp", -R"cpp(char -welcome)cpp" - }, - // Delete a range - { -R"cpp(static char[[ -welcome]]() -{})cpp", - "" - }, - // Add a range - { -R"cpp(static char[[]]() -{})cpp", - R"cpp( -cookies)cpp" - }, - // Replace the whole file - { -R"cpp([[static char -cookies() -{}]])cpp", - R"cpp(#include -)cpp" - }, - // Delete the whole file - { - R"cpp([[#include -]])cpp", - "", - }, - // Add something to an empty file - { - "[[]]", - R"cpp(int main() { -)cpp", - }, - { - R"cpp(int main() { -)cpp", - "" - } - }; - // clang-format on - - stepByStep(Steps); - allAtOnce(Steps); -} - -TEST(DraftStoreIncrementalUpdateTest, WrongRangeLength) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 0; - Change.range->end.character = 2; - Change.rangeLength = 10; - - Expected Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ( - toString(Result.takeError()), - "Change's rangeLength (10) doesn't match the computed range length (2)."); -} - -TEST(DraftStoreIncrementalUpdateTest, EndBeforeStart) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 5; - Change.range->end.line = 0; - Change.range->end.character = 3; - - Expected Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "Range's end position (0:3) is before start position (0:5)"); -} - -TEST(DraftStoreIncrementalUpdateTest, StartCharOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 100; - Change.range->end.line = 0; - Change.range->end.character = 100; - Change.text = "foo"; - - Expected Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); -} - -TEST(DraftStoreIncrementalUpdateTest, EndCharOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 0; - Change.range->end.character = 100; - Change.text = "foo"; - - Expected Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); -} - -TEST(DraftStoreIncrementalUpdateTest, StartLineOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 100; - Change.range->start.character = 0; - Change.range->end.line = 100; - Change.range->end.character = 0; - Change.text = "foo"; - - Expected Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), "Line value is out of range (100)"); -} - -TEST(DraftStoreIncrementalUpdateTest, EndLineOutOfRange) { - DraftStore DS; - Path File = "foo.cpp"; - - DS.addDraft(File, "int main() {}\n"); - - TextDocumentContentChangeEvent Change; - Change.range.emplace(); - Change.range->start.line = 0; - Change.range->start.character = 0; - Change.range->end.line = 100; - Change.range->end.character = 0; - Change.text = "foo"; - - Expected Result = DS.updateDraft(File, {Change}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), "Line value is out of range (100)"); -} - -/// Check that if a valid change is followed by an invalid change, the original -/// version of the document (prior to all changes) is kept. -TEST(DraftStoreIncrementalUpdateTest, InvalidRangeInASequence) { - DraftStore DS; - Path File = "foo.cpp"; - - StringRef OriginalContents = "int main() {}\n"; - DS.addDraft(File, OriginalContents); - - // The valid change - TextDocumentContentChangeEvent Change1; - Change1.range.emplace(); - Change1.range->start.line = 0; - Change1.range->start.character = 0; - Change1.range->end.line = 0; - Change1.range->end.character = 0; - Change1.text = "Hello "; - - // The invalid change - TextDocumentContentChangeEvent Change2; - Change2.range.emplace(); - Change2.range->start.line = 0; - Change2.range->start.character = 5; - Change2.range->end.line = 0; - Change2.range->end.character = 100; - Change2.text = "something"; - - Expected Result = DS.updateDraft(File, {Change1, Change2}); - - EXPECT_TRUE(!Result); - EXPECT_EQ(toString(Result.takeError()), - "utf-16 offset 100 is invalid for line 0"); - - Optional Contents = DS.getDraft(File); - EXPECT_TRUE(Contents); - EXPECT_EQ(*Contents, OriginalContents); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/ExpectedTypeTest.cpp =================================================================== --- /dev/null +++ unittests/clangd/ExpectedTypeTest.cpp @@ -1,153 +0,0 @@ -//===-- ExpectedTypeTest.cpp -----------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "ClangdUnit.h" -#include "ExpectedTypes.h" -#include "TestTU.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/Decl.h" -#include "llvm/ADT/StringRef.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using ::testing::Field; -using ::testing::Matcher; -using ::testing::SizeIs; -using ::testing::UnorderedElementsAreArray; - -class ExpectedTypeConversionTest : public ::testing::Test { -protected: - void build(llvm::StringRef Code) { - assert(!AST && "AST built twice"); - AST = TestTU::withCode(Code).build(); - } - - const ValueDecl *decl(llvm::StringRef Name) { - return &cast(findDecl(*AST, Name)); - } - - QualType typeOf(llvm::StringRef Name) { - return decl(Name)->getType().getCanonicalType(); - } - - /// An overload for convenience. - llvm::Optional fromCompletionResult(const ValueDecl *D) { - return OpaqueType::fromCompletionResult( - ASTCtx(), CodeCompletionResult(D, CCP_Declaration)); - } - - /// A set of DeclNames whose type match each other computed by - /// OpaqueType::fromCompletionResult. - using EquivClass = std::set; - - Matcher> - ClassesAre(llvm::ArrayRef Classes) { - using MapEntry = std::map::value_type; - - std::vector> Elements; - Elements.reserve(Classes.size()); - for (auto &Cls : Classes) - Elements.push_back(Field(&MapEntry::second, Cls)); - return UnorderedElementsAreArray(Elements); - } - - // Groups \p Decls into equivalence classes based on the result of - // 'OpaqueType::fromCompletionResult'. - std::map - buildEquivClasses(llvm::ArrayRef DeclNames) { - std::map Classes; - for (llvm::StringRef Name : DeclNames) { - auto Type = OpaqueType::fromType(ASTCtx(), typeOf(Name)); - Classes[Type->raw()].insert(Name); - } - return Classes; - } - - ASTContext &ASTCtx() { return AST->getASTContext(); } - -private: - // Set after calling build(). - llvm::Optional AST; -}; - -TEST_F(ExpectedTypeConversionTest, BasicTypes) { - build(R"cpp( - // ints. - bool b; - int i; - unsigned int ui; - long long ll; - - // floats. - float f; - double d; - - // pointers - int* iptr; - bool* bptr; - - // user-defined types. - struct X {}; - X user_type; - )cpp"); - - EXPECT_THAT(buildEquivClasses({"b", "i", "ui", "ll", "f", "d", "iptr", "bptr", - "user_type"}), - ClassesAre({{"b"}, - {"i", "ui", "ll"}, - {"f", "d"}, - {"iptr"}, - {"bptr"}, - {"user_type"}})); -} - -TEST_F(ExpectedTypeConversionTest, ReferencesDontMatter) { - build(R"cpp( - int noref; - int & ref = noref; - const int & const_ref = noref; - int && rv_ref = 10; - )cpp"); - - EXPECT_THAT(buildEquivClasses({"noref", "ref", "const_ref", "rv_ref"}), - SizeIs(1)); -} - -TEST_F(ExpectedTypeConversionTest, ArraysDecay) { - build(R"cpp( - int arr[2]; - int (&arr_ref)[2] = arr; - int *ptr; - )cpp"); - - EXPECT_THAT(buildEquivClasses({"arr", "arr_ref", "ptr"}), SizeIs(1)); -} - -TEST_F(ExpectedTypeConversionTest, FunctionReturns) { - build(R"cpp( - int returns_int(); - int* returns_ptr(); - - int int_; - int* int_ptr; - )cpp"); - - OpaqueType IntTy = *OpaqueType::fromType(ASTCtx(), typeOf("int_")); - EXPECT_EQ(fromCompletionResult(decl("returns_int")), IntTy); - - OpaqueType IntPtrTy = *OpaqueType::fromType(ASTCtx(), typeOf("int_ptr")); - EXPECT_EQ(fromCompletionResult(decl("returns_ptr")), IntPtrTy); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/FSTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/FSTests.cpp @@ -1,50 +0,0 @@ -//===-- FSTests.cpp - File system related tests -----------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "FS.h" -#include "TestFS.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -TEST(FSTests, PreambleStatusCache) { - llvm::StringMap Files; - Files["x"] = ""; - Files["y"] = ""; - Files["main"] = ""; - auto FS = buildTestFS(Files); - FS->setCurrentWorkingDirectory(testRoot()); - - PreambleFileStatusCache StatCache(testPath("main")); - auto ProduceFS = StatCache.getProducingFS(FS); - EXPECT_TRUE(ProduceFS->openFileForRead("x")); - EXPECT_TRUE(ProduceFS->status("y")); - EXPECT_TRUE(ProduceFS->status("main")); - - EXPECT_TRUE(StatCache.lookup(testPath("x")).hasValue()); - EXPECT_TRUE(StatCache.lookup(testPath("y")).hasValue()); - // Main file is not cached. - EXPECT_FALSE(StatCache.lookup(testPath("main")).hasValue()); - - llvm::vfs::Status S("fake", llvm::sys::fs::UniqueID(0, 0), - std::chrono::system_clock::now(), 0, 0, 1024, - llvm::sys::fs::file_type::regular_file, - llvm::sys::fs::all_all); - StatCache.update(*FS, S); - auto ConsumeFS = StatCache.getConsumingFS(FS); - auto Cached = ConsumeFS->status(testPath("fake")); - EXPECT_TRUE(Cached); - EXPECT_EQ(Cached->getName(), S.getName()); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/FileDistanceTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/FileDistanceTests.cpp @@ -1,123 +0,0 @@ -//===-- FileDistanceTests.cpp ------------------------*- C++ -*-----------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "FileDistance.h" -#include "TestFS.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -TEST(FileDistanceTests, Distance) { - FileDistanceOptions Opts; - Opts.UpCost = 5; - Opts.DownCost = 3; - SourceParams CostTwo; - CostTwo.Cost = 2; - FileDistance D( - {{"tools/clang/lib/Format/FormatToken.cpp", SourceParams()}, - {"tools/clang/include/clang/Format/FormatToken.h", SourceParams()}, - {"include/llvm/ADT/StringRef.h", CostTwo}}, - Opts); - - // Source - EXPECT_EQ(D.distance("tools/clang/lib/Format/FormatToken.cpp"), 0u); - EXPECT_EQ(D.distance("include/llvm/ADT/StringRef.h"), 2u); - // Parent - EXPECT_EQ(D.distance("tools/clang/lib/Format/"), 5u); - // Child - EXPECT_EQ(D.distance("tools/clang/lib/Format/FormatToken.cpp/Oops"), 3u); - // Ancestor (up+up+up+up) - EXPECT_EQ(D.distance("/"), 22u); - // Sibling (up+down) - EXPECT_EQ(D.distance("tools/clang/lib/Format/AnotherFile.cpp"), 8u); - // Cousin (up+up+down+down) - EXPECT_EQ(D.distance("include/llvm/Support/Allocator.h"), 18u); - // First cousin, once removed (up+up+up+down+down) - EXPECT_EQ(D.distance("include/llvm-c/Core.h"), 23u); -} - -TEST(FileDistanceTests, BadSource) { - // We mustn't assume that paths above sources are best reached via them. - FileDistanceOptions Opts; - Opts.UpCost = 5; - Opts.DownCost = 3; - SourceParams CostLots; - CostLots.Cost = 100; - FileDistance D({{"a", SourceParams()}, {"b/b/b", CostLots}}, Opts); - EXPECT_EQ(D.distance("b"), 8u); // a+up+down, not b+up+up - EXPECT_EQ(D.distance("b/b/b"), 14u); // a+up+down+down+down, not b - EXPECT_EQ(D.distance("b/b/b/c"), 17u); // a+up+down+down+down+down, not b+down -} - -// Force the unittest URI scheme to be linked, -static int LLVM_ATTRIBUTE_UNUSED UseUnittestScheme = UnittestSchemeAnchorSource; - -TEST(FileDistanceTests, URI) { - FileDistanceOptions Opts; - Opts.UpCost = 5; - Opts.DownCost = 3; - SourceParams CostLots; - CostLots.Cost = 1000; - - URIDistance D({{testPath("foo"), CostLots}, - {"/not/a/testpath", SourceParams()}, - {"C:\\not\\a\\testpath", SourceParams()}}, - Opts); -#ifdef _WIN32 - EXPECT_EQ(D.distance("file:///C%3a/not/a/testpath/either"), 3u); -#else - EXPECT_EQ(D.distance("file:///not/a/testpath/either"), 3u); -#endif - EXPECT_EQ(D.distance("unittest:///foo"), 1000u); - EXPECT_EQ(D.distance("unittest:///bar"), 1008u); -} - -TEST(FileDistance, LimitUpTraversals) { - FileDistanceOptions Opts; - Opts.UpCost = Opts.DownCost = 1; - SourceParams CheapButLimited, CostLots; - CheapButLimited.MaxUpTraversals = 1; - CostLots.Cost = 100; - - FileDistance D({{"/", CostLots}, {"/a/b/c", CheapButLimited}}, Opts); - EXPECT_EQ(D.distance("/a"), 101u); - EXPECT_EQ(D.distance("/a/z"), 102u); - EXPECT_EQ(D.distance("/a/b"), 1u); - EXPECT_EQ(D.distance("/a/b/z"), 2u); -} - -TEST(FileDistance, DisallowDownTraversalsFromRoot) { - FileDistanceOptions Opts; - Opts.UpCost = Opts.DownCost = 1; - Opts.AllowDownTraversalFromRoot = false; - SourceParams CostLots; - CostLots.Cost = 100; - - FileDistance D({{"/", SourceParams()}, {"/a/b/c", CostLots}}, Opts); - EXPECT_EQ(D.distance("/"), 0u); - EXPECT_EQ(D.distance("/a"), 102u); - EXPECT_EQ(D.distance("/a/b"), 101u); - EXPECT_EQ(D.distance("/x"), FileDistance::Unreachable); -} - -TEST(ScopeDistance, Smoke) { - ScopeDistance D({"x::y::z", "x::", "", "a::"}); - EXPECT_EQ(D.distance("x::y::z::"), 0u); - EXPECT_GT(D.distance("x::y::"), D.distance("x::y::z::")); - EXPECT_GT(D.distance("x::"), D.distance("x::y::")); - EXPECT_GT(D.distance("x::y::z::down::"), D.distance("x::y::")); - EXPECT_GT(D.distance(""), D.distance("a::")); - EXPECT_GT(D.distance("x::"), D.distance("a::")); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/FileIndexTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/FileIndexTests.cpp @@ -1,371 +0,0 @@ -//===-- FileIndexTests.cpp ---------------------------*- C++ -*-----------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "AST.h" -#include "Annotations.h" -#include "ClangdUnit.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "TestTU.h" -#include "index/CanonicalIncludes.h" -#include "index/FileIndex.h" -#include "index/Index.h" -#include "clang/Frontend/CompilerInvocation.h" -#include "clang/Frontend/Utils.h" -#include "clang/Index/IndexSymbol.h" -#include "clang/Lex/Preprocessor.h" -#include "clang/Tooling/CompilationDatabase.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::_; -using testing::AllOf; -using testing::Contains; -using testing::ElementsAre; -using testing::IsEmpty; -using testing::Pair; -using testing::UnorderedElementsAre; - -MATCHER_P(RefRange, Range, "") { - return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), - arg.Location.End.line(), arg.Location.End.column()) == - std::make_tuple(Range.start.line, Range.start.character, - Range.end.line, Range.end.character); -} -MATCHER_P(FileURI, F, "") { return llvm::StringRef(arg.Location.FileURI) == F; } -MATCHER_P(DeclURI, U, "") { - return llvm::StringRef(arg.CanonicalDeclaration.FileURI) == U; -} -MATCHER_P(DefURI, U, "") { - return llvm::StringRef(arg.Definition.FileURI) == U; -} -MATCHER_P(QName, N, "") { return (arg.Scope + arg.Name).str() == N; } - -namespace clang { -namespace clangd { -namespace { -testing::Matcher -RefsAre(std::vector> Matchers) { - return ElementsAre(testing::Pair(_, UnorderedElementsAreArray(Matchers))); -} - -Symbol symbol(llvm::StringRef ID) { - Symbol Sym; - Sym.ID = SymbolID(ID); - Sym.Name = ID; - return Sym; -} - -std::unique_ptr numSlab(int Begin, int End) { - SymbolSlab::Builder Slab; - for (int i = Begin; i <= End; i++) - Slab.insert(symbol(std::to_string(i))); - return llvm::make_unique(std::move(Slab).build()); -} - -std::unique_ptr refSlab(const SymbolID &ID, const char *Path) { - RefSlab::Builder Slab; - Ref R; - R.Location.FileURI = Path; - R.Kind = RefKind::Reference; - Slab.insert(ID, R); - return llvm::make_unique(std::move(Slab).build()); -} - -TEST(FileSymbolsTest, UpdateAndGet) { - FileSymbols FS; - EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), IsEmpty()); - - FS.update("f1", numSlab(1, 3), refSlab(SymbolID("1"), "f1.cc")); - EXPECT_THAT(runFuzzyFind(*FS.buildIndex(IndexType::Light), ""), - UnorderedElementsAre(QName("1"), QName("2"), QName("3"))); - EXPECT_THAT(getRefs(*FS.buildIndex(IndexType::Light), SymbolID("1")), - RefsAre({FileURI("f1.cc")})); -} - -TEST(FileSymbolsTest, Overlap) { - FileSymbols FS; - FS.update("f1", numSlab(1, 3), nullptr); - FS.update("f2", numSlab(3, 5), nullptr); - for (auto Type : {IndexType::Light, IndexType::Heavy}) - EXPECT_THAT(runFuzzyFind(*FS.buildIndex(Type), ""), - UnorderedElementsAre(QName("1"), QName("2"), QName("3"), - QName("4"), QName("5"))); -} - -TEST(FileSymbolsTest, MergeOverlap) { - FileSymbols FS; - auto OneSymboSlab = [](Symbol Sym) { - SymbolSlab::Builder S; - S.insert(Sym); - return llvm::make_unique(std::move(S).build()); - }; - auto X1 = symbol("x"); - X1.CanonicalDeclaration.FileURI = "file:///x1"; - auto X2 = symbol("x"); - X2.Definition.FileURI = "file:///x2"; - - FS.update("f1", OneSymboSlab(X1), nullptr); - FS.update("f2", OneSymboSlab(X2), nullptr); - for (auto Type : {IndexType::Light, IndexType::Heavy}) - EXPECT_THAT( - runFuzzyFind(*FS.buildIndex(Type, DuplicateHandling::Merge), "x"), - UnorderedElementsAre( - AllOf(QName("x"), DeclURI("file:///x1"), DefURI("file:///x2")))); -} - -TEST(FileSymbolsTest, SnapshotAliveAfterRemove) { - FileSymbols FS; - - SymbolID ID("1"); - FS.update("f1", numSlab(1, 3), refSlab(ID, "f1.cc")); - - auto Symbols = FS.buildIndex(IndexType::Light); - EXPECT_THAT(runFuzzyFind(*Symbols, ""), - UnorderedElementsAre(QName("1"), QName("2"), QName("3"))); - EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")})); - - FS.update("f1", nullptr, nullptr); - auto Empty = FS.buildIndex(IndexType::Light); - EXPECT_THAT(runFuzzyFind(*Empty, ""), IsEmpty()); - EXPECT_THAT(getRefs(*Empty, ID), ElementsAre()); - - EXPECT_THAT(runFuzzyFind(*Symbols, ""), - UnorderedElementsAre(QName("1"), QName("2"), QName("3"))); - EXPECT_THAT(getRefs(*Symbols, ID), RefsAre({FileURI("f1.cc")})); -} - -// Adds Basename.cpp, which includes Basename.h, which contains Code. -void update(FileIndex &M, llvm::StringRef Basename, llvm::StringRef Code) { - TestTU File; - File.Filename = (Basename + ".cpp").str(); - File.HeaderFilename = (Basename + ".h").str(); - File.HeaderCode = Code; - auto AST = File.build(); - M.updatePreamble(File.Filename, AST.getASTContext(), AST.getPreprocessorPtr(), - AST.getCanonicalIncludes()); -} - -TEST(FileIndexTest, CustomizedURIScheme) { - FileIndex M; - update(M, "f", "class string {};"); - - EXPECT_THAT(runFuzzyFind(M, ""), ElementsAre(DeclURI("unittest:///f.h"))); -} - -TEST(FileIndexTest, IndexAST) { - FileIndex M; - update(M, "f1", "namespace ns { void f() {} class X {}; }"); - - FuzzyFindRequest Req; - Req.Query = ""; - Req.Scopes = {"ns::"}; - EXPECT_THAT(runFuzzyFind(M, Req), - UnorderedElementsAre(QName("ns::f"), QName("ns::X"))); -} - -TEST(FileIndexTest, NoLocal) { - FileIndex M; - update(M, "f1", "namespace ns { void f() { int local = 0; } class X {}; }"); - - EXPECT_THAT( - runFuzzyFind(M, ""), - UnorderedElementsAre(QName("ns"), QName("ns::f"), QName("ns::X"))); -} - -TEST(FileIndexTest, IndexMultiASTAndDeduplicate) { - FileIndex M; - update(M, "f1", "namespace ns { void f() {} class X {}; }"); - update(M, "f2", "namespace ns { void ff() {} class X {}; }"); - - FuzzyFindRequest Req; - Req.Scopes = {"ns::"}; - EXPECT_THAT( - runFuzzyFind(M, Req), - UnorderedElementsAre(QName("ns::f"), QName("ns::X"), QName("ns::ff"))); -} - -TEST(FileIndexTest, ClassMembers) { - FileIndex M; - update(M, "f1", "class X { static int m1; int m2; static void f(); };"); - - EXPECT_THAT(runFuzzyFind(M, ""), - UnorderedElementsAre(QName("X"), QName("X::m1"), QName("X::m2"), - QName("X::f"))); -} - -TEST(FileIndexTest, IncludeCollected) { - FileIndex M; - update( - M, "f", - "// IWYU pragma: private, include \nclass string {};"); - - auto Symbols = runFuzzyFind(M, ""); - EXPECT_THAT(Symbols, ElementsAre(_)); - EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, - ""); -} - -TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) { - TestTU TU; - TU.HeaderCode = "class Foo{};"; - TU.HeaderFilename = "algorithm"; - - auto Symbols = runFuzzyFind(*TU.index(), ""); - EXPECT_THAT(Symbols, ElementsAre(_)); - EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader, - ""); -} - -TEST(FileIndexTest, TemplateParamsInLabel) { - auto Source = R"cpp( -template -class vector { -}; - -template -vector make_vector(Arg A) {} -)cpp"; - - FileIndex M; - update(M, "f", Source); - - auto Symbols = runFuzzyFind(M, ""); - EXPECT_THAT(Symbols, - UnorderedElementsAre(QName("vector"), QName("make_vector"))); - auto It = Symbols.begin(); - Symbol Vector = *It++; - Symbol MakeVector = *It++; - if (MakeVector.Name == "vector") - std::swap(MakeVector, Vector); - - EXPECT_EQ(Vector.Signature, ""); - EXPECT_EQ(Vector.CompletionSnippetSuffix, "<${1:class Ty}>"); - - EXPECT_EQ(MakeVector.Signature, "(Arg A)"); - EXPECT_EQ(MakeVector.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})"); -} - -TEST(FileIndexTest, RebuildWithPreamble) { - auto FooCpp = testPath("foo.cpp"); - auto FooH = testPath("foo.h"); - // Preparse ParseInputs. - ParseInputs PI; - PI.CompileCommand.Directory = testRoot(); - PI.CompileCommand.Filename = FooCpp; - PI.CompileCommand.CommandLine = {"clang", "-xc++", FooCpp}; - - llvm::StringMap Files; - Files[FooCpp] = ""; - Files[FooH] = R"cpp( - namespace ns_in_header { - int func_in_header(); - } - )cpp"; - PI.FS = buildTestFS(std::move(Files)); - - PI.Contents = R"cpp( - #include "foo.h" - namespace ns_in_source { - int func_in_source(); - } - )cpp"; - - // Rebuild the file. - auto CI = buildCompilerInvocation(PI); - - FileIndex Index; - bool IndexUpdated = false; - buildPreamble( - FooCpp, *CI, /*OldPreamble=*/nullptr, tooling::CompileCommand(), PI, - /*StoreInMemory=*/true, - [&](ASTContext &Ctx, std::shared_ptr PP, - const CanonicalIncludes &CanonIncludes) { - EXPECT_FALSE(IndexUpdated) << "Expected only a single index update"; - IndexUpdated = true; - Index.updatePreamble(FooCpp, Ctx, std::move(PP), CanonIncludes); - }); - ASSERT_TRUE(IndexUpdated); - - // Check the index contains symbols from the preamble, but not from the main - // file. - FuzzyFindRequest Req; - Req.Query = ""; - Req.Scopes = {"", "ns_in_header::"}; - - EXPECT_THAT(runFuzzyFind(Index, Req), - UnorderedElementsAre(QName("ns_in_header"), - QName("ns_in_header::func_in_header"))); -} - -TEST(FileIndexTest, Refs) { - const char *HeaderCode = "class Foo {};"; - Annotations MainCode(R"cpp( - void f() { - $foo[[Foo]] foo; - } - )cpp"); - - auto Foo = - findSymbol(TestTU::withHeaderCode(HeaderCode).headerSymbols(), "Foo"); - - RefsRequest Request; - Request.IDs = {Foo.ID}; - - FileIndex Index; - // Add test.cc - TestTU Test; - Test.HeaderCode = HeaderCode; - Test.Code = MainCode.code(); - Test.Filename = "test.cc"; - auto AST = Test.build(); - Index.updateMain(Test.Filename, AST); - // Add test2.cc - TestTU Test2; - Test2.HeaderCode = HeaderCode; - Test2.Code = MainCode.code(); - Test2.Filename = "test2.cc"; - AST = Test2.build(); - Index.updateMain(Test2.Filename, AST); - - EXPECT_THAT(getRefs(Index, Foo.ID), - RefsAre({AllOf(RefRange(MainCode.range("foo")), - FileURI("unittest:///test.cc")), - AllOf(RefRange(MainCode.range("foo")), - FileURI("unittest:///test2.cc"))})); -} - -TEST(FileIndexTest, CollectMacros) { - FileIndex M; - update(M, "f", "#define CLANGD 1"); - EXPECT_THAT(runFuzzyFind(M, ""), Contains(QName("CLANGD"))); -} - -TEST(FileIndexTest, ReferencesInMainFileWithPreamble) { - TestTU TU; - TU.HeaderCode = "class Foo{};"; - Annotations Main(R"cpp( - #include "foo.h" - void f() { - [[Foo]] foo; - } - )cpp"); - TU.Code = Main.code(); - auto AST = TU.build(); - FileIndex Index; - Index.updateMain(testPath(TU.Filename), AST); - - // Expect to see references in main file, references in headers are excluded - // because we only index main AST. - EXPECT_THAT(getRefs(Index, findSymbol(TU.headerSymbols(), "Foo").ID), - RefsAre({RefRange(Main.range())})); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/FindSymbolsTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/FindSymbolsTests.cpp @@ -1,688 +0,0 @@ -//===-- FindSymbolsTests.cpp -------------------------*- C++ -*------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -#include "Annotations.h" -#include "ClangdServer.h" -#include "FindSymbols.h" -#include "SyncAPI.h" -#include "TestFS.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { - -namespace { - -using ::testing::AllOf; -using ::testing::AnyOf; -using ::testing::ElementsAre; -using ::testing::ElementsAreArray; -using ::testing::Field; -using ::testing::IsEmpty; -using ::testing::UnorderedElementsAre; - -class IgnoreDiagnostics : public DiagnosticsConsumer { - void onDiagnosticsReady(PathRef File, - std::vector Diagnostics) override {} -}; - -// GMock helpers for matching SymbolInfos items. -MATCHER_P(QName, Name, "") { - if (arg.containerName.empty()) - return arg.name == Name; - return (arg.containerName + "::" + arg.name) == Name; -} -MATCHER_P(WithName, N, "") { return arg.name == N; } -MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; } -MATCHER_P(SymRange, Range, "") { return arg.location.range == Range; } - -// GMock helpers for matching DocumentSymbol. -MATCHER_P(SymNameRange, Range, "") { return arg.selectionRange == Range; } -template -testing::Matcher Children(ChildMatchers... ChildrenM) { - return Field(&DocumentSymbol::children, ElementsAre(ChildrenM...)); -} - -ClangdServer::Options optsForTests() { - auto ServerOpts = ClangdServer::optsForTest(); - ServerOpts.WorkspaceRoot = testRoot(); - ServerOpts.BuildDynamicSymbolIndex = true; - return ServerOpts; -} - -class WorkspaceSymbolsTest : public ::testing::Test { -public: - WorkspaceSymbolsTest() - : Server(CDB, FSProvider, DiagConsumer, optsForTests()) { - // Make sure the test root directory is created. - FSProvider.Files[testPath("unused")] = ""; - CDB.ExtraClangFlags = {"-xc++"}; - } - -protected: - MockFSProvider FSProvider; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server; - int Limit = 0; - - std::vector getSymbols(llvm::StringRef Query) { - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; - auto SymbolInfos = runWorkspaceSymbols(Server, Query, Limit); - EXPECT_TRUE(bool(SymbolInfos)) << "workspaceSymbols returned an error"; - return *SymbolInfos; - } - - void addFile(llvm::StringRef FileName, llvm::StringRef Contents) { - auto Path = testPath(FileName); - FSProvider.Files[Path] = Contents; - Server.addDocument(Path, Contents); - } -}; - -} // namespace - -TEST_F(WorkspaceSymbolsTest, Macros) { - addFile("foo.cpp", R"cpp( - #define MACRO X - )cpp"); - - // LSP's SymbolKind doesn't have a "Macro" kind, and - // indexSymbolKindToSymbolKind() currently maps macros - // to SymbolKind::String. - EXPECT_THAT(getSymbols("macro"), - ElementsAre(AllOf(QName("MACRO"), WithKind(SymbolKind::String)))); -} - -TEST_F(WorkspaceSymbolsTest, NoLocals) { - addFile("foo.cpp", R"cpp( - void test(int FirstParam, int SecondParam) { - struct LocalClass {}; - int local_var; - })cpp"); - EXPECT_THAT(getSymbols("l"), IsEmpty()); - EXPECT_THAT(getSymbols("p"), IsEmpty()); -} - -TEST_F(WorkspaceSymbolsTest, Globals) { - addFile("foo.h", R"cpp( - int global_var; - - int global_func(); - - struct GlobalStruct {};)cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("global"), - UnorderedElementsAre( - AllOf(QName("GlobalStruct"), WithKind(SymbolKind::Struct)), - AllOf(QName("global_func"), WithKind(SymbolKind::Function)), - AllOf(QName("global_var"), WithKind(SymbolKind::Variable)))); -} - -TEST_F(WorkspaceSymbolsTest, Unnamed) { - addFile("foo.h", R"cpp( - struct { - int InUnnamed; - } UnnamedStruct;)cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("UnnamedStruct"), - ElementsAre(AllOf(QName("UnnamedStruct"), - WithKind(SymbolKind::Variable)))); - EXPECT_THAT(getSymbols("InUnnamed"), - ElementsAre(AllOf(QName("(anonymous struct)::InUnnamed"), - WithKind(SymbolKind::Field)))); -} - -TEST_F(WorkspaceSymbolsTest, InMainFile) { - addFile("foo.cpp", R"cpp( - int test() {} - static test2() {} - )cpp"); - EXPECT_THAT(getSymbols("test"), ElementsAre(QName("test"), QName("test2"))); -} - -TEST_F(WorkspaceSymbolsTest, Namespaces) { - addFile("foo.h", R"cpp( - namespace ans1 { - int ai1; - namespace ans2 { - int ai2; - } - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("a"), - UnorderedElementsAre(QName("ans1"), QName("ans1::ai1"), - QName("ans1::ans2"), - QName("ans1::ans2::ai2"))); - EXPECT_THAT(getSymbols("::"), ElementsAre(QName("ans1"))); - EXPECT_THAT(getSymbols("::a"), ElementsAre(QName("ans1"))); - EXPECT_THAT(getSymbols("ans1::"), - UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"))); - EXPECT_THAT(getSymbols("::ans1"), ElementsAre(QName("ans1"))); - EXPECT_THAT(getSymbols("::ans1::"), - UnorderedElementsAre(QName("ans1::ai1"), QName("ans1::ans2"))); - EXPECT_THAT(getSymbols("::ans1::ans2"), ElementsAre(QName("ans1::ans2"))); - EXPECT_THAT(getSymbols("::ans1::ans2::"), - ElementsAre(QName("ans1::ans2::ai2"))); -} - -TEST_F(WorkspaceSymbolsTest, AnonymousNamespace) { - addFile("foo.h", R"cpp( - namespace { - void test() {} - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("test"), ElementsAre(QName("test"))); -} - -TEST_F(WorkspaceSymbolsTest, MultiFile) { - addFile("foo.h", R"cpp( - int foo() { - } - )cpp"); - addFile("foo2.h", R"cpp( - int foo2() { - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - #include "foo2.h" - )cpp"); - EXPECT_THAT(getSymbols("foo"), - UnorderedElementsAre(QName("foo"), QName("foo2"))); -} - -TEST_F(WorkspaceSymbolsTest, GlobalNamespaceQueries) { - addFile("foo.h", R"cpp( - int foo() { - } - class Foo { - int a; - }; - namespace ns { - int foo2() { - } - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("::"), - UnorderedElementsAre( - AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("foo"), WithKind(SymbolKind::Function)), - AllOf(QName("ns"), WithKind(SymbolKind::Namespace)))); - EXPECT_THAT(getSymbols(":"), IsEmpty()); - EXPECT_THAT(getSymbols(""), IsEmpty()); -} - -TEST_F(WorkspaceSymbolsTest, Enums) { - addFile("foo.h", R"cpp( - enum { - Red - }; - enum Color { - Green - }; - enum class Color2 { - Yellow - }; - namespace ns { - enum { - Black - }; - enum Color3 { - Blue - }; - enum class Color4 { - White - }; - } - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("Red"), ElementsAre(QName("Red"))); - EXPECT_THAT(getSymbols("::Red"), ElementsAre(QName("Red"))); - EXPECT_THAT(getSymbols("Green"), ElementsAre(QName("Green"))); - EXPECT_THAT(getSymbols("Green"), ElementsAre(QName("Green"))); - EXPECT_THAT(getSymbols("Color2::Yellow"), - ElementsAre(QName("Color2::Yellow"))); - EXPECT_THAT(getSymbols("Yellow"), ElementsAre(QName("Color2::Yellow"))); - - EXPECT_THAT(getSymbols("ns::Black"), ElementsAre(QName("ns::Black"))); - EXPECT_THAT(getSymbols("ns::Blue"), ElementsAre(QName("ns::Blue"))); - EXPECT_THAT(getSymbols("ns::Color4::White"), - ElementsAre(QName("ns::Color4::White"))); -} - -TEST_F(WorkspaceSymbolsTest, Ranking) { - addFile("foo.h", R"cpp( - namespace ns{} - void func(); - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols("::"), ElementsAre(QName("func"), QName("ns"))); -} - -TEST_F(WorkspaceSymbolsTest, WithLimit) { - addFile("foo.h", R"cpp( - int foo; - int foo2; - )cpp"); - addFile("foo.cpp", R"cpp( - #include "foo.h" - )cpp"); - // Foo is higher ranked because of exact name match. - EXPECT_THAT(getSymbols("foo"), - UnorderedElementsAre( - AllOf(QName("foo"), WithKind(SymbolKind::Variable)), - AllOf(QName("foo2"), WithKind(SymbolKind::Variable)))); - - Limit = 1; - EXPECT_THAT(getSymbols("foo"), ElementsAre(QName("foo"))); -} - -TEST_F(WorkspaceSymbolsTest, TempSpecs) { - addFile("foo.h", R"cpp( - template class Foo {}; - template class Foo {}; - template <> class Foo {}; - template <> class Foo {}; - )cpp"); - // Foo is higher ranked because of exact name match. - EXPECT_THAT( - getSymbols("Foo"), - UnorderedElementsAre( - AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo"), WithKind(SymbolKind::Class)), - AllOf(QName("Foo"), WithKind(SymbolKind::Class)))); -} - -namespace { -class DocumentSymbolsTest : public ::testing::Test { -public: - DocumentSymbolsTest() - : Server(CDB, FSProvider, DiagConsumer, optsForTests()) {} - -protected: - MockFSProvider FSProvider; - MockCompilationDatabase CDB; - IgnoreDiagnostics DiagConsumer; - ClangdServer Server; - - std::vector getSymbols(PathRef File) { - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; - auto SymbolInfos = runDocumentSymbols(Server, File); - EXPECT_TRUE(bool(SymbolInfos)) << "documentSymbols returned an error"; - return *SymbolInfos; - } - - void addFile(llvm::StringRef FilePath, llvm::StringRef Contents) { - FSProvider.Files[FilePath] = Contents; - Server.addDocument(FilePath, Contents); - } -}; -} // namespace - -TEST_F(DocumentSymbolsTest, BasicSymbols) { - std::string FilePath = testPath("foo.cpp"); - Annotations Main(R"( - class Foo; - class Foo { - Foo() {} - Foo(int a) {} - void $decl[[f]](); - friend void f1(); - friend class Friend; - Foo& operator=(const Foo&); - ~Foo(); - class Nested { - void f(); - }; - }; - class Friend { - }; - - void f1(); - inline void f2() {} - static const int KInt = 2; - const char* kStr = "123"; - - void f1() {} - - namespace foo { - // Type alias - typedef int int32; - using int32_t = int32; - - // Variable - int v1; - - // Namespace - namespace bar { - int v2; - } - // Namespace alias - namespace baz = bar; - - using bar::v2; - } // namespace foo - )"); - - addFile(FilePath, Main.code()); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAreArray( - {AllOf(WithName("Foo"), WithKind(SymbolKind::Class), Children()), - AllOf(WithName("Foo"), WithKind(SymbolKind::Class), - Children(AllOf(WithName("Foo"), WithKind(SymbolKind::Method), - Children()), - AllOf(WithName("Foo"), WithKind(SymbolKind::Method), - Children()), - AllOf(WithName("f"), WithKind(SymbolKind::Method), - Children()), - AllOf(WithName("operator="), - WithKind(SymbolKind::Method), Children()), - AllOf(WithName("~Foo"), WithKind(SymbolKind::Method), - Children()), - AllOf(WithName("Nested"), WithKind(SymbolKind::Class), - Children(AllOf(WithName("f"), - WithKind(SymbolKind::Method), - Children()))))), - AllOf(WithName("Friend"), WithKind(SymbolKind::Class), Children()), - AllOf(WithName("f1"), WithKind(SymbolKind::Function), Children()), - AllOf(WithName("f2"), WithKind(SymbolKind::Function), Children()), - AllOf(WithName("KInt"), WithKind(SymbolKind::Variable), Children()), - AllOf(WithName("kStr"), WithKind(SymbolKind::Variable), Children()), - AllOf(WithName("f1"), WithKind(SymbolKind::Function), Children()), - AllOf(WithName("foo"), WithKind(SymbolKind::Namespace), - Children( - AllOf(WithName("int32"), WithKind(SymbolKind::Class), - Children()), - AllOf(WithName("int32_t"), WithKind(SymbolKind::Class), - Children()), - AllOf(WithName("v1"), WithKind(SymbolKind::Variable), - Children()), - AllOf(WithName("bar"), WithKind(SymbolKind::Namespace), - Children(AllOf(WithName("v2"), - WithKind(SymbolKind::Variable), - Children()))), - AllOf(WithName("baz"), WithKind(SymbolKind::Namespace), - Children()), - AllOf(WithName("v2"), WithKind(SymbolKind::Namespace))))})); -} - -TEST_F(DocumentSymbolsTest, DeclarationDefinition) { - std::string FilePath = testPath("foo.cpp"); - Annotations Main(R"( - class Foo { - void $decl[[f]](); - }; - void Foo::$def[[f]]() { - } - )"); - - addFile(FilePath, Main.code()); - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(AllOf(WithName("Foo"), WithKind(SymbolKind::Class), - Children(AllOf( - WithName("f"), WithKind(SymbolKind::Method), - SymNameRange(Main.range("decl"))))), - AllOf(WithName("f"), WithKind(SymbolKind::Method), - SymNameRange(Main.range("def"))))); -} - -TEST_F(DocumentSymbolsTest, ExternSymbol) { - std::string FilePath = testPath("foo.cpp"); - addFile(testPath("foo.h"), R"cpp( - extern int var; - )cpp"); - addFile(FilePath, R"cpp( - #include "foo.h" - )cpp"); - - EXPECT_THAT(getSymbols(FilePath), IsEmpty()); -} - -TEST_F(DocumentSymbolsTest, NoLocals) { - std::string FilePath = testPath("foo.cpp"); - addFile(FilePath, - R"cpp( - void test(int FirstParam, int SecondParam) { - struct LocalClass {}; - int local_var; - })cpp"); - EXPECT_THAT(getSymbols(FilePath), ElementsAre(WithName("test"))); -} - -TEST_F(DocumentSymbolsTest, Unnamed) { - std::string FilePath = testPath("foo.h"); - addFile(FilePath, - R"cpp( - struct { - int InUnnamed; - } UnnamedStruct; - )cpp"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAre( - AllOf(WithName("(anonymous struct)"), WithKind(SymbolKind::Struct), - Children(AllOf(WithName("InUnnamed"), - WithKind(SymbolKind::Field), Children()))), - AllOf(WithName("UnnamedStruct"), WithKind(SymbolKind::Variable), - Children()))); -} - -TEST_F(DocumentSymbolsTest, InHeaderFile) { - addFile(testPath("bar.h"), R"cpp( - int foo() { - } - )cpp"); - std::string FilePath = testPath("foo.h"); - addFile(FilePath, R"cpp( - #include "bar.h" - int test() { - } - )cpp"); - addFile(testPath("foo.cpp"), R"cpp( - #include "foo.h" - )cpp"); - EXPECT_THAT(getSymbols(FilePath), ElementsAre(WithName("test"))); -} - -TEST_F(DocumentSymbolsTest, Template) { - std::string FilePath = testPath("foo.cpp"); - addFile(FilePath, R"( - template struct Tmpl {T x = 0;}; - template <> struct Tmpl { - int y = 0; - }; - extern template struct Tmpl; - template struct Tmpl; - - template - int funcTmpl(U a); - template <> - int funcTmpl(double a); - - template - int varTmpl = T(); - template <> - double varTmpl = 10.0; - )"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAre( - AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), - Children(AllOf(WithName("x"), WithKind(SymbolKind::Field)))), - AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), - Children(WithName("y"))), - AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), - Children()), - AllOf(WithName("Tmpl"), WithKind(SymbolKind::Struct), - Children()), - AllOf(WithName("funcTmpl"), Children()), - AllOf(WithName("funcTmpl"), Children()), - AllOf(WithName("varTmpl"), Children()), - AllOf(WithName("varTmpl"), Children()))); -} - -TEST_F(DocumentSymbolsTest, Namespaces) { - std::string FilePath = testPath("foo.cpp"); - addFile(FilePath, R"cpp( - namespace ans1 { - int ai1; - namespace ans2 { - int ai2; - } - } - namespace { - void test() {} - } - - namespace na { - inline namespace nb { - class Foo {}; - } - } - namespace na { - // This is still inlined. - namespace nb { - class Bar {}; - } - } - )cpp"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAreArray>( - {AllOf(WithName("ans1"), - Children(AllOf(WithName("ai1"), Children()), - AllOf(WithName("ans2"), Children(WithName("ai2"))))), - AllOf(WithName("(anonymous namespace)"), Children(WithName("test"))), - AllOf(WithName("na"), - Children(AllOf(WithName("nb"), Children(WithName("Foo"))))), - AllOf(WithName("na"), - Children(AllOf(WithName("nb"), Children(WithName("Bar")))))})); -} - -TEST_F(DocumentSymbolsTest, Enums) { - std::string FilePath = testPath("foo.cpp"); - addFile(FilePath, R"( - enum { - Red - }; - enum Color { - Green - }; - enum class Color2 { - Yellow - }; - namespace ns { - enum { - Black - }; - } - )"); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAre( - AllOf(WithName("(anonymous enum)"), Children(WithName("Red"))), - AllOf(WithName("Color"), Children(WithName("Green"))), - AllOf(WithName("Color2"), Children(WithName("Yellow"))), - AllOf(WithName("ns"), Children(AllOf(WithName("(anonymous enum)"), - Children(WithName("Black"))))))); -} - -TEST_F(DocumentSymbolsTest, FromMacro) { - std::string FilePath = testPath("foo.cpp"); - Annotations Main(R"( - #define FF(name) \ - class name##_Test {}; - - $expansion[[FF]](abc); - - #define FF2() \ - class $spelling[[Test]] {}; - - FF2(); - )"); - addFile(FilePath, Main.code()); - EXPECT_THAT( - getSymbols(FilePath), - ElementsAre( - AllOf(WithName("abc_Test"), SymNameRange(Main.range("expansion"))), - AllOf(WithName("Test"), SymNameRange(Main.range("spelling"))))); -} - -TEST_F(DocumentSymbolsTest, FuncTemplates) { - std::string FilePath = testPath("foo.cpp"); - Annotations Source(R"cpp( - template - T foo() {} - - auto x = foo(); - auto y = foo() - )cpp"); - addFile(FilePath, Source.code()); - // Make sure we only see the template declaration, not instantiations. - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(WithName("foo"), WithName("x"), WithName("y"))); -} - -TEST_F(DocumentSymbolsTest, UsingDirectives) { - std::string FilePath = testPath("foo.cpp"); - Annotations Source(R"cpp( - namespace ns { - int foo; - } - - namespace ns_alias = ns; - - using namespace ::ns; // check we don't loose qualifiers. - using namespace ns_alias; // and namespace aliases. - )cpp"); - addFile(FilePath, Source.code()); - EXPECT_THAT(getSymbols(FilePath), - ElementsAre(WithName("ns"), WithName("ns_alias"), - WithName("using namespace ::ns"), - WithName("using namespace ns_alias"))); -} - -TEST_F(DocumentSymbolsTest, TempSpecs) { - addFile("foo.cpp", R"cpp( - template class Foo {}; - template class Foo {}; - template <> class Foo {}; - template <> class Foo {}; - )cpp"); - // Foo is higher ranked because of exact name match. - EXPECT_THAT( - getSymbols("foo.cpp"), - UnorderedElementsAre( - AllOf(WithName("Foo"), WithKind(SymbolKind::Class)), - AllOf(WithName("Foo"), WithKind(SymbolKind::Class)), - AllOf(WithName("Foo"), WithKind(SymbolKind::Class)), - AllOf(WithName("Foo"), WithKind(SymbolKind::Class)))); -} - -} // namespace clangd -} // namespace clang Index: unittests/clangd/FunctionTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/FunctionTests.cpp @@ -1,51 +0,0 @@ -//===-- FunctionTests.cpp -------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Function.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -TEST(EventTest, Subscriptions) { - Event E; - int N = 0; - { - Event::Subscription SubA; - // No subscriptions are active. - E.broadcast(42); - EXPECT_EQ(0, N); - - Event::Subscription SubB = E.observe([&](int) { ++N; }); - // Now one is active. - E.broadcast(42); - EXPECT_EQ(1, N); - - SubA = E.observe([&](int) { ++N; }); - // Both are active. - EXPECT_EQ(1, N); - E.broadcast(42); - EXPECT_EQ(3, N); - - SubA = std::move(SubB); - // One is active. - EXPECT_EQ(3, N); - E.broadcast(42); - EXPECT_EQ(4, N); - } - // None are active. - EXPECT_EQ(4, N); - E.broadcast(42); - EXPECT_EQ(4, N); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/FuzzyMatchTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/FuzzyMatchTests.cpp @@ -1,312 +0,0 @@ -//===-- FuzzyMatchTests.cpp - String fuzzy matcher tests --------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "FuzzyMatch.h" - -#include "llvm/ADT/StringExtras.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { -using testing::Not; - -struct ExpectedMatch { - // Annotations are optional, and will not be asserted if absent. - ExpectedMatch(llvm::StringRef Match) : Word(Match), Annotated(Match) { - for (char C : "[]") - Word.erase(std::remove(Word.begin(), Word.end(), C), Word.end()); - if (Word.size() == Annotated->size()) - Annotated = llvm::None; - } - bool accepts(llvm::StringRef ActualAnnotated) const { - return !Annotated || ActualAnnotated == *Annotated; - } - - friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const ExpectedMatch &M) { - OS << "'" << M.Word; - if (M.Annotated) - OS << "' as " << *M.Annotated; - return OS; - } - - std::string Word; - -private: - llvm::Optional Annotated; -}; - -struct MatchesMatcher : public testing::MatcherInterface { - ExpectedMatch Candidate; - llvm::Optional Score; - MatchesMatcher(ExpectedMatch Candidate, llvm::Optional Score) - : Candidate(std::move(Candidate)), Score(Score) {} - - void DescribeTo(::std::ostream *OS) const override { - llvm::raw_os_ostream(*OS) << "Matches " << Candidate; - if (Score) - *OS << " with score " << *Score; - } - - bool MatchAndExplain(llvm::StringRef Pattern, - testing::MatchResultListener *L) const override { - std::unique_ptr OS( - L->stream() - ? (llvm::raw_ostream *)(new llvm::raw_os_ostream(*L->stream())) - : new llvm::raw_null_ostream()); - FuzzyMatcher Matcher(Pattern); - auto Result = Matcher.match(Candidate.Word); - auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n"); - return Result && Candidate.accepts(AnnotatedMatch) && - (!Score || testing::Value(*Result, testing::FloatEq(*Score))); - } -}; - -// Accepts patterns that match a given word, optionally requiring a score. -// Dumps the debug tables on match failure. -testing::Matcher matches(llvm::StringRef M, - llvm::Optional Score = {}) { - return testing::MakeMatcher(new MatchesMatcher(M, Score)); -} - -TEST(FuzzyMatch, Matches) { - EXPECT_THAT("", matches("unique_ptr")); - EXPECT_THAT("u_p", matches("[u]nique[_p]tr")); - EXPECT_THAT("up", matches("[u]nique_[p]tr")); - EXPECT_THAT("uq", Not(matches("unique_ptr"))); - EXPECT_THAT("qp", Not(matches("unique_ptr"))); - EXPECT_THAT("log", Not(matches("SVGFEMorphologyElement"))); - - EXPECT_THAT("tit", matches("win.[tit]")); - EXPECT_THAT("title", matches("win.[title]")); - EXPECT_THAT("WordCla", matches("[Word]Character[Cla]ssifier")); - EXPECT_THAT("WordCCla", matches("[WordC]haracter[Cla]ssifier")); - - EXPECT_THAT("dete", Not(matches("editor.quickSuggestionsDelay"))); - - EXPECT_THAT("highlight", matches("editorHover[Highlight]")); - EXPECT_THAT("hhighlight", matches("editor[H]over[Highlight]")); - EXPECT_THAT("dhhighlight", Not(matches("editorHoverHighlight"))); - - EXPECT_THAT("-moz", matches("[-moz]-foo")); - EXPECT_THAT("moz", matches("-[moz]-foo")); - EXPECT_THAT("moza", matches("-[moz]-[a]nimation")); - - EXPECT_THAT("ab", matches("[ab]A")); - EXPECT_THAT("ccm", Not(matches("cacmelCase"))); - EXPECT_THAT("bti", Not(matches("the_black_knight"))); - EXPECT_THAT("ccm", Not(matches("camelCase"))); - EXPECT_THAT("cmcm", Not(matches("camelCase"))); - EXPECT_THAT("BK", matches("the_[b]lack_[k]night")); - EXPECT_THAT("KeyboardLayout=", Not(matches("KeyboardLayout"))); - EXPECT_THAT("LLL", matches("SVisual[L]ogger[L]ogs[L]ist")); - EXPECT_THAT("LLLL", Not(matches("SVilLoLosLi"))); - EXPECT_THAT("LLLL", Not(matches("SVisualLoggerLogsList"))); - EXPECT_THAT("TEdit", matches("[T]ext[Edit]")); - EXPECT_THAT("TEdit", matches("[T]ext[Edit]or")); - EXPECT_THAT("TEdit", Not(matches("[T]ext[edit]"))); - EXPECT_THAT("TEdit", matches("[t]ext_[edit]")); - EXPECT_THAT("TEditDt", matches("[T]ext[Edit]or[D]ecoration[T]ype")); - EXPECT_THAT("TEdit", matches("[T]ext[Edit]orDecorationType")); - EXPECT_THAT("Tedit", matches("[T]ext[Edit]")); - EXPECT_THAT("ba", Not(matches("?AB?"))); - EXPECT_THAT("bkn", matches("the_[b]lack_[kn]ight")); - EXPECT_THAT("bt", Not(matches("the_[b]lack_knigh[t]"))); - EXPECT_THAT("ccm", Not(matches("[c]amelCase[cm]"))); - EXPECT_THAT("fdm", Not(matches("[f]in[dM]odel"))); - EXPECT_THAT("fob", Not(matches("[fo]o[b]ar"))); - EXPECT_THAT("fobz", Not(matches("foobar"))); - EXPECT_THAT("foobar", matches("[foobar]")); - EXPECT_THAT("form", matches("editor.[form]atOnSave")); - EXPECT_THAT("g p", matches("[G]it:[ P]ull")); - EXPECT_THAT("g p", matches("[G]it:[ P]ull")); - EXPECT_THAT("gip", matches("[Gi]t: [P]ull")); - EXPECT_THAT("gip", matches("[Gi]t: [P]ull")); - EXPECT_THAT("gp", matches("[G]it: [P]ull")); - EXPECT_THAT("gp", matches("[G]it_Git_[P]ull")); - EXPECT_THAT("is", matches("[I]mport[S]tatement")); - EXPECT_THAT("is", matches("[is]Valid")); - EXPECT_THAT("lowrd", Not(matches("[low]Wo[rd]"))); - EXPECT_THAT("myvable", Not(matches("[myva]ria[ble]"))); - EXPECT_THAT("no", Not(matches(""))); - EXPECT_THAT("no", Not(matches("match"))); - EXPECT_THAT("ob", Not(matches("foobar"))); - EXPECT_THAT("sl", matches("[S]Visual[L]oggerLogsList")); - EXPECT_THAT("sllll", matches("[S]Visua[L]ogger[Ll]ama[L]ist")); - EXPECT_THAT("THRE", matches("H[T]ML[HRE]lement")); - EXPECT_THAT("b", Not(matches("NDEBUG"))); - EXPECT_THAT("Three", matches("[Three]")); - EXPECT_THAT("fo", Not(matches("barfoo"))); - EXPECT_THAT("fo", matches("bar_[fo]o")); - EXPECT_THAT("fo", matches("bar_[Fo]o")); - EXPECT_THAT("fo", matches("bar [fo]o")); - EXPECT_THAT("fo", matches("bar.[fo]o")); - EXPECT_THAT("fo", matches("bar/[fo]o")); - EXPECT_THAT("fo", matches("bar\\[fo]o")); - - EXPECT_THAT( - "aaaaaa", - matches("[aaaaaa]aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); - EXPECT_THAT("baba", Not(matches("ababababab"))); - EXPECT_THAT("fsfsfs", Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsa"))); - EXPECT_THAT("fsfsfsfsfsfsfsf", - Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsafdsafdsafdsafdsfd" - "safdsfdfdfasdnfdsajfndsjnafjndsajlknfdsa"))); - - EXPECT_THAT(" g", matches("[ g]roup")); - EXPECT_THAT("g", matches(" [g]roup")); - EXPECT_THAT("g g", Not(matches(" groupGroup"))); - EXPECT_THAT("g g", matches(" [g]roup[ G]roup")); - EXPECT_THAT(" g g", matches("[ ] [g]roup[ G]roup")); - EXPECT_THAT("zz", matches("[zz]Group")); - EXPECT_THAT("zzg", matches("[zzG]roup")); - EXPECT_THAT("g", matches("zz[G]roup")); - - EXPECT_THAT("aaaa", matches("_a_[aaaa]")); // Prefer consecutive. - // These would ideally match, but would need special segmentation rules. - EXPECT_THAT("printf", Not(matches("s[printf]"))); - EXPECT_THAT("str", Not(matches("o[str]eam"))); - EXPECT_THAT("strcpy", Not(matches("strncpy"))); - EXPECT_THAT("std", Not(matches("PTHREAD_MUTEX_STALLED"))); - EXPECT_THAT("std", Not(matches("pthread_condattr_setpshared"))); -} - -struct RankMatcher : public testing::MatcherInterface { - std::vector RankedStrings; - RankMatcher(std::initializer_list RankedStrings) - : RankedStrings(RankedStrings) {} - - void DescribeTo(::std::ostream *OS) const override { - llvm::raw_os_ostream O(*OS); - O << "Ranks strings in order: ["; - for (const auto &Str : RankedStrings) - O << "\n\t" << Str; - O << "\n]"; - } - - bool MatchAndExplain(llvm::StringRef Pattern, - testing::MatchResultListener *L) const override { - std::unique_ptr OS( - L->stream() - ? (llvm::raw_ostream *)(new llvm::raw_os_ostream(*L->stream())) - : new llvm::raw_null_ostream()); - FuzzyMatcher Matcher(Pattern); - const ExpectedMatch *LastMatch; - llvm::Optional LastScore; - bool Ok = true; - for (const auto &Str : RankedStrings) { - auto Score = Matcher.match(Str.Word); - if (!Score) { - *OS << "\nDoesn't match '" << Str.Word << "'"; - Matcher.dumpLast(*OS << "\n"); - Ok = false; - } else { - std::string Buf; - llvm::raw_string_ostream Info(Buf); - auto AnnotatedMatch = Matcher.dumpLast(Info); - - if (!Str.accepts(AnnotatedMatch)) { - *OS << "\nDoesn't match " << Str << ", but " << AnnotatedMatch << "\n" - << Info.str(); - Ok = false; - } else if (LastScore && *LastScore < *Score) { - *OS << "\nRanks '" << Str.Word << "'=" << *Score << " above '" - << LastMatch->Word << "'=" << *LastScore << "\n" - << Info.str(); - Matcher.match(LastMatch->Word); - Matcher.dumpLast(*OS << "\n"); - Ok = false; - } - } - LastMatch = &Str; - LastScore = Score; - } - return Ok; - } -}; - -// Accepts patterns that match all the strings and rank them in the given order. -// Dumps the debug tables on match failure. -template -testing::Matcher ranks(T... RankedStrings) { - return testing::MakeMatcher( - new RankMatcher{ExpectedMatch(RankedStrings)...}); -} - -TEST(FuzzyMatch, Ranking) { - EXPECT_THAT("cons", - ranks("[cons]ole", "[Cons]ole", "ArrayBuffer[Cons]tructor")); - EXPECT_THAT("foo", ranks("[foo]", "[Foo]")); - EXPECT_THAT("onMes", - ranks("[onMes]sage", "[onmes]sage", "[on]This[M]ega[Es]capes")); - EXPECT_THAT("onmes", - ranks("[onmes]sage", "[onMes]sage", "[on]This[M]ega[Es]capes")); - EXPECT_THAT("CC", ranks("[C]amel[C]ase", "[c]amel[C]ase")); - EXPECT_THAT("cC", ranks("[c]amel[C]ase", "[C]amel[C]ase")); - EXPECT_THAT("p", ranks("[p]", "[p]arse", "[p]osix", "[p]afdsa", "[p]ath")); - EXPECT_THAT("pa", ranks("[pa]rse", "[pa]th", "[pa]fdsa")); - EXPECT_THAT("log", ranks("[log]", "Scroll[Log]icalPosition")); - EXPECT_THAT("e", ranks("[e]lse", "Abstract[E]lement")); - EXPECT_THAT("workbench.sideb", - ranks("[workbench.sideB]ar.location", - "[workbench.]editor.default[SideB]ySideLayout")); - EXPECT_THAT("editor.r", ranks("[editor.r]enderControlCharacter", - "[editor.]overview[R]ulerlanes", - "diff[Editor.r]enderSideBySide")); - EXPECT_THAT("-mo", ranks("[-mo]z-columns", "[-]ms-ime-[mo]de")); - EXPECT_THAT("convertModelPosition", - ranks("[convertModelPosition]ToViewPosition", - "[convert]ViewTo[ModelPosition]")); - EXPECT_THAT("is", ranks("[is]ValidViewletId", "[i]mport [s]tatement")); - EXPECT_THAT("strcpy", ranks("[strcpy]", "[strcpy]_s")); -} - -// Verify some bounds so we know scores fall in the right range. -// Testing exact scores is fragile, so we prefer Ranking tests. -TEST(FuzzyMatch, Scoring) { - EXPECT_THAT("abs", matches("[a]w[B]xYz[S]", 7.f / 12.f)); - EXPECT_THAT("abs", matches("[abs]l", 1.f)); - EXPECT_THAT("abs", matches("[abs]", 2.f)); - EXPECT_THAT("Abs", matches("[abs]", 2.f)); -} - -TEST(FuzzyMatch, InitialismAndPrefix) { - // We want these scores to be roughly the same. - EXPECT_THAT("up", matches("[u]nique_[p]tr", 3.f / 4.f)); - EXPECT_THAT("up", matches("[up]per_bound", 1.f)); -} - -// Returns pretty-printed segmentation of Text. -// e.g. std::basic_string --> +-- +---- +----- -std::string segment(llvm::StringRef Text) { - std::vector Roles(Text.size()); - calculateRoles(Text, Roles); - std::string Printed; - for (unsigned I = 0; I < Text.size(); ++I) - Printed.push_back("?-+ "[static_cast(Roles[I])]); - return Printed; -} - -// this is a no-op hack so clang-format will vertically align our testcases. -llvm::StringRef returns(llvm::StringRef Text) { return Text; } - -TEST(FuzzyMatch, Segmentation) { - EXPECT_THAT(segment("std::basic_string"), // - returns("+-- +---- +-----")); - EXPECT_THAT(segment("XMLHttpRequest"), // - returns("+--+---+------")); - EXPECT_THAT(segment("t3h PeNgU1N oF d00m!!!!!!!!"), // - returns("+-- +-+-+-+ ++ +--- ")); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/GlobalCompilationDatabaseTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/GlobalCompilationDatabaseTests.cpp @@ -1,151 +0,0 @@ -//===-- GlobalCompilationDatabaseTests.cpp ----------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "GlobalCompilationDatabase.h" - -#include "TestFS.h" -#include "llvm/ADT/StringExtras.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { -using ::testing::AllOf; -using ::testing::Contains; -using ::testing::ElementsAre; -using ::testing::EndsWith; -using ::testing::Not; - -TEST(GlobalCompilationDatabaseTest, FallbackCommand) { - DirectoryBasedGlobalCompilationDatabase DB(None); - auto Cmd = DB.getFallbackCommand(testPath("foo/bar.cc")); - EXPECT_EQ(Cmd.Directory, testPath("foo")); - EXPECT_THAT(Cmd.CommandLine, - ElementsAre(EndsWith("clang"), testPath("foo/bar.cc"))); - EXPECT_EQ(Cmd.Output, ""); - - // .h files have unknown language, so they are parsed liberally as obj-c++. - Cmd = DB.getFallbackCommand(testPath("foo/bar.h")); - EXPECT_THAT(Cmd.CommandLine, - ElementsAre(EndsWith("clang"), "-xobjective-c++-header", - testPath("foo/bar.h"))); -} - -static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) { - return tooling::CompileCommand(testRoot(), File, {"clang", Arg, File}, ""); -} - -class OverlayCDBTest : public ::testing::Test { - class BaseCDB : public GlobalCompilationDatabase { - public: - llvm::Optional - getCompileCommand(llvm::StringRef File, - ProjectInfo *Project) const override { - if (File == testPath("foo.cc")) { - if (Project) - Project->SourceRoot = testRoot(); - return cmd(File, "-DA=1"); - } - return None; - } - - tooling::CompileCommand - getFallbackCommand(llvm::StringRef File) const override { - return cmd(File, "-DA=2"); - } - }; - -protected: - OverlayCDBTest() : Base(llvm::make_unique()) {} - std::unique_ptr Base; -}; - -TEST_F(OverlayCDBTest, GetCompileCommand) { - OverlayCDB CDB(Base.get(), {}, std::string("")); - EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, - AllOf(Contains(testPath("foo.cc")), Contains("-DA=1"))); - EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None); - - auto Override = cmd(testPath("foo.cc"), "-DA=3"); - CDB.setCompileCommand(testPath("foo.cc"), Override); - EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, - Contains("-DA=3")); - EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None); - CDB.setCompileCommand(testPath("missing.cc"), Override); - EXPECT_THAT(CDB.getCompileCommand(testPath("missing.cc"))->CommandLine, - Contains("-DA=3")); -} - -TEST_F(OverlayCDBTest, GetFallbackCommand) { - OverlayCDB CDB(Base.get(), {"-DA=4"}); - EXPECT_THAT(CDB.getFallbackCommand(testPath("bar.cc")).CommandLine, - ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4")); -} - -TEST_F(OverlayCDBTest, NoBase) { - OverlayCDB CDB(nullptr, {"-DA=6"}, std::string("")); - EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), None); - auto Override = cmd(testPath("bar.cc"), "-DA=5"); - CDB.setCompileCommand(testPath("bar.cc"), Override); - EXPECT_THAT(CDB.getCompileCommand(testPath("bar.cc"))->CommandLine, - Contains("-DA=5")); - - EXPECT_THAT(CDB.getFallbackCommand(testPath("foo.cc")).CommandLine, - ElementsAre(EndsWith("clang"), testPath("foo.cc"), "-DA=6")); -} - -TEST_F(OverlayCDBTest, Watch) { - OverlayCDB Inner(nullptr); - OverlayCDB Outer(&Inner); - - std::vector> Changes; - auto Sub = Outer.watch([&](const std::vector &ChangedFiles) { - Changes.push_back(ChangedFiles); - }); - - Inner.setCompileCommand("A.cpp", tooling::CompileCommand()); - Outer.setCompileCommand("B.cpp", tooling::CompileCommand()); - Inner.setCompileCommand("A.cpp", llvm::None); - Outer.setCompileCommand("C.cpp", llvm::None); - EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"), - ElementsAre("A.cpp"), ElementsAre("C.cpp"))); -} - -TEST_F(OverlayCDBTest, Adjustments) { - OverlayCDB CDB(Base.get(), {}, std::string("")); - auto Cmd = CDB.getCompileCommand(testPath("foo.cc")).getValue(); - // Delete the file name. - Cmd.CommandLine.pop_back(); - - // Check dependency file commands are dropped. - Cmd.CommandLine.push_back("-MF"); - Cmd.CommandLine.push_back("random-dependency"); - - // Check plugin-related commands are dropped. - Cmd.CommandLine.push_back("-Xclang"); - Cmd.CommandLine.push_back("-load"); - Cmd.CommandLine.push_back("-Xclang"); - Cmd.CommandLine.push_back("random-plugin"); - - Cmd.CommandLine.push_back("-DA=5"); - Cmd.CommandLine.push_back(Cmd.Filename); - - CDB.setCompileCommand(testPath("foo.cc"), Cmd); - - EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, - AllOf(Contains("-fsyntax-only"), Contains("-DA=5"), - Contains(testPath("foo.cc")), Not(Contains("-MF")), - Not(Contains("random-dependency")), - Not(Contains("-Xclang")), Not(Contains("-load")), - Not(Contains("random-plugin")))); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/HeadersTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/HeadersTests.cpp @@ -1,279 +0,0 @@ -//===-- HeadersTests.cpp - Include headers unit tests -----------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Headers.h" - -#include "Compiler.h" -#include "TestFS.h" -#include "TestTU.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Frontend/CompilerInvocation.h" -#include "clang/Frontend/FrontendActions.h" -#include "clang/Lex/PreprocessorOptions.h" -#include "llvm/Support/Path.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using ::testing::AllOf; -using ::testing::ElementsAre; -using ::testing::UnorderedElementsAre; - -class HeadersTest : public ::testing::Test { -public: - HeadersTest() { - CDB.ExtraClangFlags = {SearchDirArg.c_str()}; - FS.Files[MainFile] = ""; - // Make sure directory sub/ exists. - FS.Files[testPath("sub/EMPTY")] = ""; - } - -private: - std::unique_ptr setupClang() { - auto Cmd = CDB.getCompileCommand(MainFile); - assert(static_cast(Cmd)); - auto VFS = FS.getFileSystem(); - VFS->setCurrentWorkingDirectory(Cmd->Directory); - - ParseInputs PI; - PI.CompileCommand = *Cmd; - PI.FS = VFS; - auto CI = buildCompilerInvocation(PI); - EXPECT_TRUE(static_cast(CI)); - // The diagnostic options must be set before creating a CompilerInstance. - CI->getDiagnosticOpts().IgnoreWarnings = true; - auto Clang = prepareCompilerInstance( - std::move(CI), /*Preamble=*/nullptr, - llvm::MemoryBuffer::getMemBuffer(FS.Files[MainFile], MainFile), VFS, - IgnoreDiags); - - EXPECT_FALSE(Clang->getFrontendOpts().Inputs.empty()); - return Clang; - } - -protected: - IncludeStructure collectIncludes() { - auto Clang = setupClang(); - PreprocessOnlyAction Action; - EXPECT_TRUE( - Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])); - IncludeStructure Includes; - Clang->getPreprocessor().addPPCallbacks( - collectIncludeStructureCallback(Clang->getSourceManager(), &Includes)); - EXPECT_TRUE(Action.Execute()); - Action.EndSourceFile(); - return Includes; - } - - // Calculates the include path, or returns "" on error or header should not be - // inserted. - std::string calculate(PathRef Original, PathRef Preferred = "", - const std::vector &Inclusions = {}) { - auto Clang = setupClang(); - PreprocessOnlyAction Action; - EXPECT_TRUE( - Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])); - - if (Preferred.empty()) - Preferred = Original; - auto ToHeaderFile = [](llvm::StringRef Header) { - return HeaderFile{Header, - /*Verbatim=*/!llvm::sys::path::is_absolute(Header)}; - }; - - IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(), - CDB.getCompileCommand(MainFile)->Directory, - &Clang->getPreprocessor().getHeaderSearchInfo()); - for (const auto &Inc : Inclusions) - Inserter.addExisting(Inc); - auto Inserted = ToHeaderFile(Preferred); - if (!Inserter.shouldInsertInclude(Original, Inserted)) - return ""; - std::string Path = Inserter.calculateIncludePath(Inserted); - Action.EndSourceFile(); - return Path; - } - - llvm::Optional insert(llvm::StringRef VerbatimHeader) { - auto Clang = setupClang(); - PreprocessOnlyAction Action; - EXPECT_TRUE( - Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])); - - IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(), - CDB.getCompileCommand(MainFile)->Directory, - &Clang->getPreprocessor().getHeaderSearchInfo()); - auto Edit = Inserter.insert(VerbatimHeader); - Action.EndSourceFile(); - return Edit; - } - - MockFSProvider FS; - MockCompilationDatabase CDB; - std::string MainFile = testPath("main.cpp"); - std::string Subdir = testPath("sub"); - std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str(); - IgnoringDiagConsumer IgnoreDiags; -}; - -MATCHER_P(Written, Name, "") { return arg.Written == Name; } -MATCHER_P(Resolved, Name, "") { return arg.Resolved == Name; } -MATCHER_P(IncludeLine, N, "") { return arg.R.start.line == N; } - -MATCHER_P2(Distance, File, D, "") { - if (arg.getKey() != File) - *result_listener << "file =" << arg.getKey().str(); - if (arg.getValue() != D) - *result_listener << "distance =" << arg.getValue(); - return arg.getKey() == File && arg.getValue() == D; -} - -TEST_F(HeadersTest, CollectRewrittenAndResolved) { - FS.Files[MainFile] = R"cpp( -#include "sub/bar.h" // not shortest -)cpp"; - std::string BarHeader = testPath("sub/bar.h"); - FS.Files[BarHeader] = ""; - - EXPECT_THAT(collectIncludes().MainFileIncludes, - UnorderedElementsAre( - AllOf(Written("\"sub/bar.h\""), Resolved(BarHeader)))); - EXPECT_THAT(collectIncludes().includeDepth(MainFile), - UnorderedElementsAre(Distance(MainFile, 0u), - Distance(testPath("sub/bar.h"), 1u))); -} - -TEST_F(HeadersTest, OnlyCollectInclusionsInMain) { - std::string BazHeader = testPath("sub/baz.h"); - FS.Files[BazHeader] = ""; - std::string BarHeader = testPath("sub/bar.h"); - FS.Files[BarHeader] = R"cpp( -#include "baz.h" -)cpp"; - FS.Files[MainFile] = R"cpp( -#include "bar.h" -)cpp"; - EXPECT_THAT( - collectIncludes().MainFileIncludes, - UnorderedElementsAre(AllOf(Written("\"bar.h\""), Resolved(BarHeader)))); - EXPECT_THAT(collectIncludes().includeDepth(MainFile), - UnorderedElementsAre(Distance(MainFile, 0u), - Distance(testPath("sub/bar.h"), 1u), - Distance(testPath("sub/baz.h"), 2u))); - // includeDepth() also works for non-main files. - EXPECT_THAT(collectIncludes().includeDepth(testPath("sub/bar.h")), - UnorderedElementsAre(Distance(testPath("sub/bar.h"), 0u), - Distance(testPath("sub/baz.h"), 1u))); -} - -TEST_F(HeadersTest, PreambleIncludesPresentOnce) { - // We use TestTU here, to ensure we use the preamble replay logic. - // We're testing that the logic doesn't crash, and doesn't result in duplicate - // includes. (We'd test more directly, but it's pretty well encapsulated!) - auto TU = TestTU::withCode(R"cpp( - #include "a.h" - #include "a.h" - void foo(); - #include "a.h" - )cpp"); - TU.HeaderFilename = "a.h"; // suppress "not found". - EXPECT_THAT(TU.build().getIncludeStructure().MainFileIncludes, - ElementsAre(IncludeLine(1), IncludeLine(2), IncludeLine(4))); -} - -TEST_F(HeadersTest, UnResolvedInclusion) { - FS.Files[MainFile] = R"cpp( -#include "foo.h" -)cpp"; - - EXPECT_THAT(collectIncludes().MainFileIncludes, - UnorderedElementsAre(AllOf(Written("\"foo.h\""), Resolved("")))); - EXPECT_THAT(collectIncludes().includeDepth(MainFile), - UnorderedElementsAre(Distance(MainFile, 0u))); -} - -TEST_F(HeadersTest, InsertInclude) { - std::string Path = testPath("sub/bar.h"); - FS.Files[Path] = ""; - EXPECT_EQ(calculate(Path), "\"bar.h\""); -} - -TEST_F(HeadersTest, DoNotInsertIfInSameFile) { - MainFile = testPath("main.h"); - EXPECT_EQ(calculate(MainFile), ""); -} - -TEST_F(HeadersTest, ShortenedInclude) { - std::string BarHeader = testPath("sub/bar.h"); - EXPECT_EQ(calculate(BarHeader), "\"bar.h\""); - - SearchDirArg = (llvm::Twine("-I") + Subdir + "/..").str(); - CDB.ExtraClangFlags = {SearchDirArg.c_str()}; - BarHeader = testPath("sub/bar.h"); - EXPECT_EQ(calculate(BarHeader), "\"sub/bar.h\""); -} - -TEST_F(HeadersTest, NotShortenedInclude) { - std::string BarHeader = - llvm::sys::path::convert_to_slash(testPath("sub-2/bar.h")); - EXPECT_EQ(calculate(BarHeader, ""), "\"" + BarHeader + "\""); -} - -TEST_F(HeadersTest, PreferredHeader) { - std::string BarHeader = testPath("sub/bar.h"); - EXPECT_EQ(calculate(BarHeader, ""), ""); - - std::string BazHeader = testPath("sub/baz.h"); - EXPECT_EQ(calculate(BarHeader, BazHeader), "\"baz.h\""); -} - -TEST_F(HeadersTest, DontInsertDuplicatePreferred) { - Inclusion Inc; - Inc.Written = "\"bar.h\""; - Inc.Resolved = ""; - EXPECT_EQ(calculate(testPath("sub/bar.h"), "\"bar.h\"", {Inc}), ""); - EXPECT_EQ(calculate("\"x.h\"", "\"bar.h\"", {Inc}), ""); -} - -TEST_F(HeadersTest, DontInsertDuplicateResolved) { - Inclusion Inc; - Inc.Written = "fake-bar.h"; - Inc.Resolved = testPath("sub/bar.h"); - EXPECT_EQ(calculate(Inc.Resolved, "", {Inc}), ""); - // Do not insert preferred. - EXPECT_EQ(calculate(Inc.Resolved, "\"BAR.h\"", {Inc}), ""); -} - -TEST_F(HeadersTest, PreferInserted) { - auto Edit = insert(""); - EXPECT_TRUE(Edit.hasValue()); - EXPECT_TRUE(StringRef(Edit->newText).contains("")); -} - -TEST(Headers, NoHeaderSearchInfo) { - std::string MainFile = testPath("main.cpp"); - IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(), - /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr); - - auto HeaderPath = testPath("sub/bar.h"); - auto Inserting = HeaderFile{HeaderPath, /*Verbatim=*/false}; - auto Verbatim = HeaderFile{"", /*Verbatim=*/true}; - - EXPECT_EQ(Inserter.calculateIncludePath(Inserting), "\"" + HeaderPath + "\""); - EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Inserting), false); - - EXPECT_EQ(Inserter.calculateIncludePath(Verbatim), ""); - EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Verbatim), true); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/IndexActionTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/IndexActionTests.cpp @@ -1,253 +0,0 @@ -//===------ IndexActionTests.cpp -------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "TestFS.h" -#include "index/IndexAction.h" -#include "clang/Tooling/Tooling.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace clang { -namespace clangd { -namespace { - -using ::testing::AllOf; -using ::testing::ElementsAre; -using ::testing::Not; -using ::testing::Pair; -using ::testing::UnorderedElementsAre; -using ::testing::UnorderedPointwise; - -std::string toUri(llvm::StringRef Path) { return URI::create(Path).toString(); } - -MATCHER(IsTU, "") { return arg.IsTU; } - -MATCHER_P(HasDigest, Digest, "") { return arg.Digest == Digest; } - -MATCHER_P(HasName, Name, "") { return arg.Name == Name; } - -MATCHER(HasSameURI, "") { - llvm::StringRef URI = testing::get<0>(arg); - const std::string &Path = testing::get<1>(arg); - return toUri(Path) == URI; -} - -testing::Matcher -IncludesAre(const std::vector &Includes) { - return ::testing::Field(&IncludeGraphNode::DirectIncludes, - UnorderedPointwise(HasSameURI(), Includes)); -} - -void checkNodesAreInitialized(const IndexFileIn &IndexFile, - const std::vector &Paths) { - ASSERT_TRUE(IndexFile.Sources); - EXPECT_THAT(Paths.size(), IndexFile.Sources->size()); - for (llvm::StringRef Path : Paths) { - auto URI = toUri(Path); - const auto &Node = IndexFile.Sources->lookup(URI); - // Uninitialized nodes will have an empty URI. - EXPECT_EQ(Node.URI.data(), IndexFile.Sources->find(URI)->getKeyData()); - } -} - -std::map toMap(const IncludeGraph &IG) { - std::map Nodes; - for (auto &I : IG) - Nodes.emplace(I.getKey(), I.getValue()); - return Nodes; -} - -class IndexActionTest : public ::testing::Test { -public: - IndexActionTest() : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem) {} - - IndexFileIn - runIndexingAction(llvm::StringRef MainFilePath, - const std::vector &ExtraArgs = {}) { - IndexFileIn IndexFile; - llvm::IntrusiveRefCntPtr Files( - new FileManager(FileSystemOptions(), InMemoryFileSystem)); - - auto Action = createStaticIndexingAction( - SymbolCollector::Options(), - [&](SymbolSlab S) { IndexFile.Symbols = std::move(S); }, - [&](RefSlab R) { IndexFile.Refs = std::move(R); }, - [&](IncludeGraph IG) { IndexFile.Sources = std::move(IG); }); - - std::vector Args = {"index_action", "-fsyntax-only", - "-xc++", "-std=c++11", - "-iquote", testRoot()}; - Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); - Args.push_back(MainFilePath); - - tooling::ToolInvocation Invocation( - Args, Action.release(), Files.get(), - std::make_shared()); - - Invocation.run(); - - checkNodesAreInitialized(IndexFile, FilePaths); - return IndexFile; - } - - void addFile(llvm::StringRef Path, llvm::StringRef Content) { - InMemoryFileSystem->addFile(Path, 0, - llvm::MemoryBuffer::getMemBuffer(Content)); - FilePaths.push_back(Path); - } - -protected: - std::vector FilePaths; - llvm::IntrusiveRefCntPtr InMemoryFileSystem; -}; - -TEST_F(IndexActionTest, CollectIncludeGraph) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = "#include \"level1.h\""; - std::string Level1HeaderPath = testPath("level1.h"); - std::string Level1HeaderCode = "#include \"level2.h\""; - std::string Level2HeaderPath = testPath("level2.h"); - std::string Level2HeaderCode = ""; - - addFile(MainFilePath, MainCode); - addFile(Level1HeaderPath, Level1HeaderCode); - addFile(Level2HeaderPath, Level2HeaderCode); - - IndexFileIn IndexFile = runIndexingAction(MainFilePath); - auto Nodes = toMap(*IndexFile.Sources); - - EXPECT_THAT(Nodes, - UnorderedElementsAre( - Pair(toUri(MainFilePath), - AllOf(IsTU(), IncludesAre({Level1HeaderPath}), - HasDigest(digest(MainCode)))), - Pair(toUri(Level1HeaderPath), - AllOf(Not(IsTU()), IncludesAre({Level2HeaderPath}), - HasDigest(digest(Level1HeaderCode)))), - Pair(toUri(Level2HeaderPath), - AllOf(Not(IsTU()), IncludesAre({}), - HasDigest(digest(Level2HeaderCode)))))); -} - -TEST_F(IndexActionTest, IncludeGraphSelfInclude) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = "#include \"header.h\""; - std::string HeaderPath = testPath("header.h"); - std::string HeaderCode = R"cpp( - #ifndef _GUARD_ - #define _GUARD_ - #include "header.h" - #endif)cpp"; - - addFile(MainFilePath, MainCode); - addFile(HeaderPath, HeaderCode); - - IndexFileIn IndexFile = runIndexingAction(MainFilePath); - auto Nodes = toMap(*IndexFile.Sources); - - EXPECT_THAT( - Nodes, - UnorderedElementsAre( - Pair(toUri(MainFilePath), AllOf(IsTU(), IncludesAre({HeaderPath}), - HasDigest(digest(MainCode)))), - Pair(toUri(HeaderPath), AllOf(Not(IsTU()), IncludesAre({HeaderPath}), - HasDigest(digest(HeaderCode)))))); -} - -TEST_F(IndexActionTest, IncludeGraphSkippedFile) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = R"cpp( - #include "common.h" - #include "header.h" - )cpp"; - - std::string CommonHeaderPath = testPath("common.h"); - std::string CommonHeaderCode = R"cpp( - #ifndef _GUARD_ - #define _GUARD_ - void f(); - #endif)cpp"; - - std::string HeaderPath = testPath("header.h"); - std::string HeaderCode = R"cpp( - #include "common.h" - void g();)cpp"; - - addFile(MainFilePath, MainCode); - addFile(HeaderPath, HeaderCode); - addFile(CommonHeaderPath, CommonHeaderCode); - - IndexFileIn IndexFile = runIndexingAction(MainFilePath); - auto Nodes = toMap(*IndexFile.Sources); - - EXPECT_THAT( - Nodes, UnorderedElementsAre( - Pair(toUri(MainFilePath), - AllOf(IsTU(), IncludesAre({HeaderPath, CommonHeaderPath}), - HasDigest(digest(MainCode)))), - Pair(toUri(HeaderPath), - AllOf(Not(IsTU()), IncludesAre({CommonHeaderPath}), - HasDigest(digest(HeaderCode)))), - Pair(toUri(CommonHeaderPath), - AllOf(Not(IsTU()), IncludesAre({}), - HasDigest(digest(CommonHeaderCode)))))); -} - -TEST_F(IndexActionTest, IncludeGraphDynamicInclude) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = R"cpp( - #ifndef FOO - #define FOO "main.cpp" - #else - #define FOO "header.h" - #endif - - #include FOO)cpp"; - std::string HeaderPath = testPath("header.h"); - std::string HeaderCode = ""; - - addFile(MainFilePath, MainCode); - addFile(HeaderPath, HeaderCode); - - IndexFileIn IndexFile = runIndexingAction(MainFilePath); - auto Nodes = toMap(*IndexFile.Sources); - - EXPECT_THAT( - Nodes, - UnorderedElementsAre( - Pair(toUri(MainFilePath), - AllOf(IsTU(), IncludesAre({MainFilePath, HeaderPath}), - HasDigest(digest(MainCode)))), - Pair(toUri(HeaderPath), AllOf(Not(IsTU()), IncludesAre({}), - HasDigest(digest(HeaderCode)))))); -} - -TEST_F(IndexActionTest, NoWarnings) { - std::string MainFilePath = testPath("main.cpp"); - std::string MainCode = R"cpp( - void foo(int x) { - if (x = 1) // -Wparentheses - return; - if (x = 1) // -Wparentheses - return; - } - void bar() {} - )cpp"; - addFile(MainFilePath, MainCode); - // We set -ferror-limit so the warning-promoted-to-error would be fatal. - // This would cause indexing to stop (if warnings weren't disabled). - IndexFileIn IndexFile = runIndexingAction( - MainFilePath, {"-ferror-limit=1", "-Wparentheses", "-Werror"}); - ASSERT_TRUE(IndexFile.Sources); - ASSERT_NE(0u, IndexFile.Sources->size()); - EXPECT_THAT(*IndexFile.Symbols, ElementsAre(HasName("foo"), HasName("bar"))); -} - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/IndexTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/IndexTests.cpp @@ -1,408 +0,0 @@ -//===-- IndexTests.cpp -------------------------------*- C++ -*-----------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "Annotations.h" -#include "TestIndex.h" -#include "TestTU.h" -#include "index/FileIndex.h" -#include "index/Index.h" -#include "index/MemIndex.h" -#include "index/Merge.h" -#include "index/Symbol.h" -#include "clang/Index/IndexSymbol.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::_; -using testing::AllOf; -using testing::AnyOf; -using testing::ElementsAre; -using testing::IsEmpty; -using testing::Pair; -using testing::Pointee; -using testing::UnorderedElementsAre; - -namespace clang { -namespace clangd { -namespace { - -MATCHER_P(Named, N, "") { return arg.Name == N; } -MATCHER_P(RefRange, Range, "") { - return std::make_tuple(arg.Location.Start.line(), arg.Location.Start.column(), - arg.Location.End.line(), arg.Location.End.column()) == - std::make_tuple(Range.start.line, Range.start.character, - Range.end.line, Range.end.character); -} -MATCHER_P(FileURI, F, "") { return StringRef(arg.Location.FileURI) == F; } - -TEST(SymbolLocation, Position) { - using Position = SymbolLocation::Position; - Position Pos; - - Pos.setLine(1); - EXPECT_EQ(1u, Pos.line()); - Pos.setColumn(2); - EXPECT_EQ(2u, Pos.column()); - EXPECT_FALSE(Pos.hasOverflow()); - - Pos.setLine(Position::MaxLine + 1); // overflow - EXPECT_TRUE(Pos.hasOverflow()); - EXPECT_EQ(Pos.line(), Position::MaxLine); - Pos.setLine(1); // reset the overflowed line. - - Pos.setColumn(Position::MaxColumn + 1); // overflow - EXPECT_TRUE(Pos.hasOverflow()); - EXPECT_EQ(Pos.column(), Position::MaxColumn); -} - -TEST(SymbolSlab, FindAndIterate) { - SymbolSlab::Builder B; - B.insert(symbol("Z")); - B.insert(symbol("Y")); - B.insert(symbol("X")); - EXPECT_EQ(nullptr, B.find(SymbolID("W"))); - for (const char *Sym : {"X", "Y", "Z"}) - EXPECT_THAT(B.find(SymbolID(Sym)), Pointee(Named(Sym))); - - SymbolSlab S = std::move(B).build(); - EXPECT_THAT(S, UnorderedElementsAre(Named("X"), Named("Y"), Named("Z"))); - EXPECT_EQ(S.end(), S.find(SymbolID("W"))); - for (const char *Sym : {"X", "Y", "Z"}) - EXPECT_THAT(*S.find(SymbolID(Sym)), Named(Sym)); -} - -TEST(SwapIndexTest, OldIndexRecycled) { - auto Token = std::make_shared(); - std::weak_ptr WeakToken = Token; - - SwapIndex S(llvm::make_unique( - SymbolSlab(), RefSlab(), std::move(Token), /*BackingDataSize=*/0)); - EXPECT_FALSE(WeakToken.expired()); // Current MemIndex keeps it alive. - S.reset(llvm::make_unique()); // Now the MemIndex is destroyed. - EXPECT_TRUE(WeakToken.expired()); // So the token is too. -} - -TEST(MemIndexTest, MemIndexDeduplicate) { - std::vector Symbols = {symbol("1"), symbol("2"), symbol("3"), - symbol("2") /* duplicate */}; - FuzzyFindRequest Req; - Req.Query = "2"; - Req.AnyScope = true; - MemIndex I(Symbols, RefSlab()); - EXPECT_THAT(match(I, Req), ElementsAre("2")); -} - -TEST(MemIndexTest, MemIndexLimitedNumMatches) { - auto I = MemIndex::build(generateNumSymbols(0, 100), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "5"; - Req.AnyScope = true; - Req.Limit = 3; - bool Incomplete; - auto Matches = match(*I, Req, &Incomplete); - EXPECT_TRUE(Req.Limit); - EXPECT_EQ(Matches.size(), *Req.Limit); - EXPECT_TRUE(Incomplete); -} - -TEST(MemIndexTest, FuzzyMatch) { - auto I = MemIndex::build( - generateSymbols({"LaughingOutLoud", "LionPopulation", "LittleOldLady"}), - RefSlab()); - FuzzyFindRequest Req; - Req.Query = "lol"; - Req.AnyScope = true; - Req.Limit = 2; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("LaughingOutLoud", "LittleOldLady")); -} - -TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) { - auto I = - MemIndex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.AnyScope = true; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "b::y2", "y3")); -} - -TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) { - auto I = - MemIndex::build(generateSymbols({"a::y1", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {""}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("y3")); -} - -TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) { - auto I = MemIndex::build( - generateSymbols({"a::y1", "a::y2", "a::x", "b::y2", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2")); -} - -TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) { - auto I = MemIndex::build( - generateSymbols({"a::y1", "a::y2", "a::x", "b::y3", "y3"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::", "b::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1", "a::y2", "b::y3")); -} - -TEST(MemIndexTest, NoMatchNestedScopes) { - auto I = MemIndex::build(generateSymbols({"a::y1", "a::b::y2"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "y"; - Req.Scopes = {"a::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1")); -} - -TEST(MemIndexTest, IgnoreCases) { - auto I = MemIndex::build(generateSymbols({"ns::ABC", "ns::abc"}), RefSlab()); - FuzzyFindRequest Req; - Req.Query = "AB"; - Req.Scopes = {"ns::"}; - EXPECT_THAT(match(*I, Req), UnorderedElementsAre("ns::ABC", "ns::abc")); -} - -TEST(MemIndexTest, Lookup) { - auto I = MemIndex::build(generateSymbols({"ns::abc", "ns::xyz"}), RefSlab()); - EXPECT_THAT(lookup(*I, SymbolID("ns::abc")), UnorderedElementsAre("ns::abc")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::abc"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::abc", "ns::xyz")); - EXPECT_THAT(lookup(*I, {SymbolID("ns::nonono"), SymbolID("ns::xyz")}), - UnorderedElementsAre("ns::xyz")); - EXPECT_THAT(lookup(*I, SymbolID("ns::nonono")), UnorderedElementsAre()); -} - -TEST(MemIndexTest, TemplateSpecialization) { - SymbolSlab::Builder B; - - Symbol S = symbol("TempSpec"); - S.ID = SymbolID("1"); - B.insert(S); - - S = symbol("TempSpec"); - S.ID = SymbolID("2"); - S.TemplateSpecializationArgs = ""; - S.SymInfo.Properties = static_cast( - index::SymbolProperty::TemplateSpecialization); - B.insert(S); - - S = symbol("TempSpec"); - S.ID = SymbolID("3"); - S.TemplateSpecializationArgs = ""; - S.SymInfo.Properties = static_cast( - index::SymbolProperty::TemplatePartialSpecialization); - B.insert(S); - - auto I = MemIndex::build(std::move(B).build(), RefSlab()); - FuzzyFindRequest Req; - Req.AnyScope = true; - - Req.Query = "TempSpec"; - EXPECT_THAT(match(*I, Req), - UnorderedElementsAre("TempSpec", "TempSpec", - "TempSpec")); - - // FIXME: Add filtering for template argument list. - Req.Query = "TempSpec - -namespace clang { -namespace clangd { -namespace { - -// No fmemopen on windows or on versions of MacOS X earlier than 10.13, so we -// can't easily run this test. -#if !(defined(_WIN32) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ - __MAC_OS_X_VERSION_MIN_REQUIRED < 101300)) - -// Fixture takes care of managing the input/output buffers for the transport. -class JSONTransportTest : public ::testing::Test { - std::string InBuf, OutBuf, MirrorBuf; - llvm::raw_string_ostream Out, Mirror; - std::unique_ptr In; - -protected: - JSONTransportTest() : Out(OutBuf), Mirror(MirrorBuf), In(nullptr, nullptr) {} - - template - std::unique_ptr transport(std::string InData, bool Pretty, - JSONStreamStyle Style) { - InBuf = std::move(InData); - In = {fmemopen(&InBuf[0], InBuf.size(), "r"), &fclose}; - return newJSONTransport(In.get(), Out, &Mirror, Pretty, Style); - } - - std::string input() const { return InBuf; } - std::string output() { return Out.str(); } - std::string input_mirror() { return Mirror.str(); } -}; - -// Echo is a simple server running on a transport: -// - logs each message it gets. -// - when it gets a call, replies to it -// - when it gets a notification for method "call", makes a call on Target -// Hangs up when it gets an exit notification. -class Echo : public Transport::MessageHandler { - Transport &Target; - std::string LogBuf; - llvm::raw_string_ostream Log; - -public: - Echo(Transport &Target) : Target(Target), Log(LogBuf) {} - - std::string log() { return Log.str(); } - - bool onNotify(llvm::StringRef Method, llvm::json::Value Params) override { - Log << "Notification " << Method << ": " << Params << "\n"; - if (Method == "call") - Target.call("echo call", std::move(Params), 42); - return Method != "exit"; - } - - bool onCall(llvm::StringRef Method, llvm::json::Value Params, - llvm::json::Value ID) override { - Log << "Call " << Method << "(" << ID << "): " << Params << "\n"; - if (Method == "err") - Target.reply( - ID, llvm::make_error("trouble at mill", ErrorCode(88))); - else - Target.reply(ID, std::move(Params)); - return true; - } - - bool onReply(llvm::json::Value ID, - llvm::Expected Params) override { - if (Params) - Log << "Reply(" << ID << "): " << *Params << "\n"; - else - Log << "Reply(" << ID - << "): error = " << llvm::toString(Params.takeError()) << "\n"; - return true; - } -}; - -std::string trim(llvm::StringRef S) { return S.trim().str(); } - -// Runs an Echo session using the standard JSON-RPC format we use in production. -TEST_F(JSONTransportTest, StandardDense) { - auto T = transport( - "Content-Length: 52\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "call", "params": 1234})" - "Content-Length: 46\r\n\r\n" - R"({"jsonrpc": "2.0", "id": 1234, "result": 5678})" - "Content-Length: 67\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"})" - "Content-Length: 73\r\n\r\n" - R"({"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}})" - "Content-Length: 68\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"})" - "Content-Length: 36\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "exit"})", - /*Pretty=*/false, JSONStreamStyle::Standard); - Echo E(*T); - auto Err = T->loop(E); - EXPECT_FALSE(bool(Err)) << toString(std::move(Err)); - - const char *WantLog = R"( -Notification call: 1234 -Reply(1234): 5678 -Call foo("abcd"): "efgh" -Reply("xyz"): error = 99: bad! -Call err("wxyz"): "boom!" -Notification exit: null - )"; - EXPECT_EQ(trim(E.log()), trim(WantLog)); - const char *WantOutput = - "Content-Length: 60\r\n\r\n" - R"({"id":42,"jsonrpc":"2.0","method":"echo call","params":1234})" - "Content-Length: 45\r\n\r\n" - R"({"id":"abcd","jsonrpc":"2.0","result":"efgh"})" - "Content-Length: 77\r\n\r\n" - R"({"error":{"code":88,"message":"trouble at mill"},"id":"wxyz","jsonrpc":"2.0"})"; - EXPECT_EQ(output(), WantOutput); - EXPECT_EQ(trim(input_mirror()), trim(input())); -} - -// Runs an Echo session using the "delimited" input and pretty-printed output -// that we use in lit tests. -TEST_F(JSONTransportTest, DelimitedPretty) { - auto T = transport(R"jsonrpc( -{"jsonrpc": "2.0", "method": "call", "params": 1234} ---- -{"jsonrpc": "2.0", "id": 1234, "result": 5678} ---- -{"jsonrpc": "2.0", "method": "foo", "id": "abcd", "params": "efgh"} ---- -{"jsonrpc": "2.0", "id": "xyz", "error": {"code": 99, "message": "bad!"}} ---- -{"jsonrpc": "2.0", "method": "err", "id": "wxyz", "params": "boom!"} ---- -{"jsonrpc": "2.0", "method": "exit"} - )jsonrpc", - /*Pretty=*/true, JSONStreamStyle::Delimited); - Echo E(*T); - auto Err = T->loop(E); - EXPECT_FALSE(bool(Err)) << toString(std::move(Err)); - - const char *WantLog = R"( -Notification call: 1234 -Reply(1234): 5678 -Call foo("abcd"): "efgh" -Reply("xyz"): error = 99: bad! -Call err("wxyz"): "boom!" -Notification exit: null - )"; - EXPECT_EQ(trim(E.log()), trim(WantLog)); - const char *WantOutput = "Content-Length: 77\r\n\r\n" - R"({ - "id": 42, - "jsonrpc": "2.0", - "method": "echo call", - "params": 1234 -})" - "Content-Length: 58\r\n\r\n" - R"({ - "id": "abcd", - "jsonrpc": "2.0", - "result": "efgh" -})" - "Content-Length: 105\r\n\r\n" - R"({ - "error": { - "code": 88, - "message": "trouble at mill" - }, - "id": "wxyz", - "jsonrpc": "2.0" -})"; - EXPECT_EQ(output(), WantOutput); - EXPECT_EQ(trim(input_mirror()), trim(input())); -} - -// IO errors such as EOF ane reported. -// The only successful return from loop() is if a handler returned false. -TEST_F(JSONTransportTest, EndOfFile) { - auto T = transport("Content-Length: 52\r\n\r\n" - R"({"jsonrpc": "2.0", "method": "call", "params": 1234})", - /*Pretty=*/false, JSONStreamStyle::Standard); - Echo E(*T); - auto Err = T->loop(E); - EXPECT_EQ(trim(E.log()), "Notification call: 1234"); - EXPECT_TRUE(bool(Err)); // Ran into EOF with no handler signalling done. - consumeError(std::move(Err)); - EXPECT_EQ(trim(input_mirror()), trim(input())); -} - -#endif - -} // namespace -} // namespace clangd -} // namespace clang Index: unittests/clangd/Matchers.h =================================================================== --- /dev/null +++ unittests/clangd/Matchers.h @@ -1,199 +0,0 @@ -//===-- Matchers.h ----------------------------------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// GMock matchers that aren't specific to particular tests. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H -#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H -#include "Protocol.h" -#include "gmock/gmock.h" - -namespace clang { -namespace clangd { -using ::testing::Matcher; - -// EXPECT_IFF expects matcher if condition is true, and Not(matcher) if false. -// This is hard to write as a function, because matchers may be polymorphic. -#define EXPECT_IFF(condition, value, matcher) \ - do { \ - if (condition) \ - EXPECT_THAT(value, matcher); \ - else \ - EXPECT_THAT(value, ::testing::Not(matcher)); \ - } while (0) - -// HasSubsequence(m1, m2, ...) matches a vector containing elements that match -// m1, m2 ... in that order. -// -// SubsequenceMatcher implements this once the type of vector is known. -template -class SubsequenceMatcher - : public ::testing::MatcherInterface &> { - std::vector> Matchers; - -public: - SubsequenceMatcher(std::vector> M) : Matchers(M) {} - - void DescribeTo(std::ostream *OS) const override { - *OS << "Contains the subsequence ["; - const char *Sep = ""; - for (const auto &M : Matchers) { - *OS << Sep; - M.DescribeTo(OS); - Sep = ", "; - } - *OS << "]"; - } - - bool MatchAndExplain(const std::vector &V, - ::testing::MatchResultListener *L) const override { - std::vector Matches(Matchers.size()); - size_t I = 0; - for (size_t J = 0; I < Matchers.size() && J < V.size(); ++J) - if (Matchers[I].Matches(V[J])) - Matches[I++] = J; - if (I == Matchers.size()) // We exhausted all matchers. - return true; - if (L->IsInterested()) { - *L << "\n Matched:"; - for (size_t K = 0; K < I; ++K) { - *L << "\n\t"; - Matchers[K].DescribeTo(L->stream()); - *L << " ==> " << ::testing::PrintToString(V[Matches[K]]); - } - *L << "\n\t"; - Matchers[I].DescribeTo(L->stream()); - *L << " ==> no subsequent match"; - } - return false; - } -}; - -// PolySubsequenceMatcher implements a "polymorphic" SubsequenceMatcher. -// It captures the types of the element matchers, and can be converted to -// Matcher> if each matcher can be converted to Matcher. -// This allows HasSubsequence() to accept polymorphic matchers like Not(). -template class PolySubsequenceMatcher { - std::tuple Matchers; - -public: - PolySubsequenceMatcher(M &&... Args) - : Matchers(std::make_tuple(std::forward(Args)...)) {} - - template operator Matcher &>() const { - return ::testing::MakeMatcher(new SubsequenceMatcher( - TypedMatchers(llvm::index_sequence_for{}))); - } - -private: - template - std::vector> TypedMatchers(llvm::index_sequence) const { - return {std::get(Matchers)...}; - } -}; - -// HasSubsequence(m1, m2, ...) matches a vector containing elements that match -// m1, m2 ... in that order. -// The real implementation is in SubsequenceMatcher. -template -PolySubsequenceMatcher HasSubsequence(Args &&... M) { - return PolySubsequenceMatcher(std::forward(M)...); -} - -// EXPECT_ERROR seems like a pretty generic name, make sure it's not defined -// already. -#ifdef EXPECT_ERROR -#error "Refusing to redefine EXPECT_ERROR" -#endif - -// Consumes llvm::Expected, checks it contains an error and marks it as -// handled. -#define EXPECT_ERROR(expectedValue) \ - do { \ - auto &&ComputedValue = (expectedValue); \ - if (ComputedValue) { \ - ADD_FAILURE() << "expected an error from " << #expectedValue \ - << " but got " \ - << ::testing::PrintToString(*ComputedValue); \ - break; \ - } \ - llvm::consumeError(ComputedValue.takeError()); \ - } while (false) - -// Implements the HasValue(m) matcher for matching an Optional whose -// value matches matcher m. -template class OptionalMatcher { -public: - explicit OptionalMatcher(const InnerMatcher &matcher) : matcher_(matcher) {} - - // This type conversion operator template allows Optional(m) to be - // used as a matcher for any Optional type whose value type is - // compatible with the inner matcher. - // - // The reason we do this instead of relying on - // MakePolymorphicMatcher() is that the latter is not flexible - // enough for implementing the DescribeTo() method of Optional(). - template operator Matcher() const { - return MakeMatcher(new Impl(matcher_)); - } - -private: - // The monomorphic implementation that works for a particular optional type. - template - class Impl : public ::testing::MatcherInterface { - public: - using Value = typename std::remove_const< - typename std::remove_reference::type>::type::value_type; - - explicit Impl(const InnerMatcher &matcher) - : matcher_(::testing::MatcherCast(matcher)) {} - - virtual void DescribeTo(::std::ostream *os) const { - *os << "has a value that "; - matcher_.DescribeTo(os); - } - - virtual void DescribeNegationTo(::std::ostream *os) const { - *os << "does not have a value that "; - matcher_.DescribeTo(os); - } - - virtual bool - MatchAndExplain(Optional optional, - ::testing::MatchResultListener *listener) const { - if (!optional.hasValue()) - return false; - - *listener << "which has a value "; - return MatchPrintAndExplain(*optional, matcher_, listener); - } - - private: - const Matcher matcher_; - - GTEST_DISALLOW_ASSIGN_(Impl); - }; - - const InnerMatcher matcher_; - - GTEST_DISALLOW_ASSIGN_(OptionalMatcher); -}; - -// Creates a matcher that matches an Optional that has a value -// that matches inner_matcher. -template -inline OptionalMatcher -HasValue(const InnerMatcher &inner_matcher) { - return OptionalMatcher(inner_matcher); -} - -} // namespace clangd -} // namespace clang -#endif Index: unittests/clangd/PrintASTTests.cpp =================================================================== --- /dev/null +++ unittests/clangd/PrintASTTests.cpp @@ -1,102 +0,0 @@ -//===--- PrintASTTests.cpp ----------------------------------------- C++-*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "AST.h" -#include "Annotations.h" -#include "Protocol.h" -#include "SourceCode.h" -#include "TestTU.h" -#include "clang/AST/RecursiveASTVisitor.h" -#include "gmock/gmock.h" -#include "gtest/gtest-param-test.h" -#include "gtest/gtest.h" -#include "gtest/internal/gtest-param-util-generated.h" - -namespace clang { -namespace clangd { -namespace { - -using testing::ElementsAreArray; - -struct Case { - const char *AnnotatedCode; - std::vector Expected; -}; -class ASTUtils : public testing::Test, - public ::testing::WithParamInterface {}; - -TEST_P(ASTUtils, PrintTemplateArgs) { - auto Pair = GetParam(); - Annotations Test(Pair.AnnotatedCode); - auto AST = TestTU::withCode(Test.code()).build(); - struct Visitor : RecursiveASTVisitor { - Visitor(std::vector Points) : Points(std::move(Points)) {} - bool VisitNamedDecl(const NamedDecl *ND) { - if (TemplateArgsAtPoints.size() == Points.size()) - return true; - auto Pos = sourceLocToPosition(ND->getASTContext().getSourceManager(), - ND->getLocation()); - if (Pos != Points[TemplateArgsAtPoints.size()]) - return true; - TemplateArgsAtPoints.push_back(printTemplateSpecializationArgs(*ND)); - return true; - } - std::vector TemplateArgsAtPoints; - const std::vector Points; - }; - Visitor V(Test.points()); - V.TraverseDecl(AST.getASTContext().getTranslationUnitDecl()); - EXPECT_THAT(V.TemplateArgsAtPoints, ElementsAreArray(Pair.Expected)); -} - -INSTANTIATE_TEST_CASE_P(ASTUtilsTests, ASTUtils, - testing::ValuesIn(std::vector({ - { - R"cpp( - template class Bar {}; - template <> class ^Bar {};)cpp", - {""}}, - { - R"cpp( - template class Bar {}; - template class Z, int Q> - struct Foo {}; - template struct ^Foo; - template - struct ^Foo {};)cpp", - {"", ""}}, - { - R"cpp( - template void Foz() {}; - template <> void ^Foz<3, 5, 8>() {};)cpp", - {"<3, 5, 8>"}}, - { - R"cpp( - template class Bar {}; - template