diff --git a/CMakeSettings.json b/CMakeSettings.json index ff22c14a8..e434dc046 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -5,8 +5,8 @@ "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x86" ], - "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", @@ -17,8 +17,8 @@ "generator": "Ninja", "configurationType": "RelWithDebInfo", "inheritEnvironments": [ "msvc_x86" ], - "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", @@ -29,8 +29,8 @@ "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", @@ -41,8 +41,8 @@ "generator": "Ninja", "configurationType": "RelWithDebInfo", "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", - "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "-v", "ctestCommandArgs": "", @@ -52,8 +52,8 @@ "name": "WSL-GNU-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", "cmakeExecutable": "/usr/bin/cmake", "cmakeCommandArgs": "-DCMAKE_CXX_COMPILER=\"g++-8\" -DCMAKE_C_COMPILER=\"gcc-8\"", "buildCommandArgs": "", @@ -67,8 +67,8 @@ "name": "WSL-Clang-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", "cmakeExecutable": "/usr/bin/cmake", "cmakeCommandArgs": "-DCMAKE_CXX_COMPILER=clang++-8 -DCMAKE_C_COMPILER=clang-8", "buildCommandArgs": "", @@ -82,8 +82,8 @@ "name": "WSL-Clang-Debug-ASAN", "generator": "Ninja", "configurationType": "Debug", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", "cmakeExecutable": "/usr/bin/cmake", "cmakeCommandArgs": "-DCMAKE_CXX_COMPILER=clang++-8 -DCMAKE_C_COMPILER=clang-8 -DCMAKE_CXX_FLAGS=\"-fsanitize=address,undefined\"", "buildCommandArgs": "", @@ -93,12 +93,27 @@ "addressSanitizerRuntimeFlags": "detect_leaks=0", "variables": [] }, + { + "name": "WSL-Clang-Release-ASAN", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", + "cmakeExecutable": "/usr/bin/cmake", + "cmakeCommandArgs": "-DCMAKE_CXX_COMPILER=clang++-8 -DCMAKE_C_COMPILER=clang-8 -DCMAKE_CXX_FLAGS=\"-fsanitize=address,undefined,fuzzer-no-link\"", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "linux_x64" ], + "wslPath": "${defaultWSLPath}", + "addressSanitizerRuntimeFlags": "detect_leaks=0", + "variables": [] + }, { "name": "WSL-Clang-Fuzzer", "generator": "Ninja", "configurationType": "Debug", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "buildRoot": "${projectDir}\\build\\${name}", + "installRoot": "${projectDir}\\install\\${name}", "cmakeExecutable": "/usr/bin/cmake", "cmakeCommandArgs": "-DCMAKE_CXX_COMPILER=clang++-8 -DCMAKE_C_COMPILER=clang-8 -DBUILD_FUZZER=On -DBUILD_VSIX=Off -DWITH_LIBCXX=Off -DCMAKE_CXX_FLAGS=\"-fsanitize=address,undefined\"", "buildCommandArgs": "-v", diff --git a/clients/vscode-hlasmplugin/package-lock.json b/clients/vscode-hlasmplugin/package-lock.json index d1c99ce4e..775d52ce3 100644 --- a/clients/vscode-hlasmplugin/package-lock.json +++ b/clients/vscode-hlasmplugin/package-lock.json @@ -1,6 +1,6 @@ { "name": "hlasm-language-support", - "version": "0.10.0", + "version": "0.11.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/language_server/src/dap/dap_feature.h b/language_server/src/dap/dap_feature.h index 3b832a068..ffc2d9987 100644 --- a/language_server/src/dap/dap_feature.h +++ b/language_server/src/dap/dap_feature.h @@ -45,7 +45,7 @@ class dap_feature : public feature : feature(ws_mngr, response_provider) {} - std::string convert_path(const std::string& path) + std::string convert_path(const std::string& path) const { if (path_format_ == path_format::URI) return uri_to_path(path); @@ -69,6 +69,7 @@ class dap_feature : public feature int column_1_based_; int line_1_based_; +private: path_format path_format_; }; diff --git a/language_server/src/dap/dap_server.cpp b/language_server/src/dap/dap_server.cpp index 652ef1587..f3be46766 100644 --- a/language_server/src/dap/dap_server.cpp +++ b/language_server/src/dap/dap_server.cpp @@ -27,6 +27,7 @@ server::server(parser_library::workspace_manager& ws_mngr) : language_server::server(ws_mngr) { features_.push_back(std::make_unique(ws_mngr_, *this)); + register_feature_methods(); register_methods(); } @@ -64,24 +65,23 @@ void server::message_received(const json& message) { try { - assert(message["type"] == "request"); - last_seq_ = message["seq"].get(); + assert(message.at("type") == "request"); + last_seq_ = message.at("seq").get(); auto arguments = message.find("arguments"); if (arguments == message.end()) - call_method(message["command"].get(), message["seq"], json()); + call_method(message.at("command").get(), message.at("seq"), json()); else - call_method(message["command"].get(), message["seq"], arguments.value()); + call_method(message.at("command").get(), message.at("seq"), arguments.value()); } catch (const nlohmann::json::exception& e) { + (void)e; LOG_WARNING(std::string("There was an error with received request:") + e.what()); } } void server::register_methods() { - language_server::server::register_methods(); - methods_.emplace( "initialize", std::bind(&server::on_initialize, this, std::placeholders::_1, std::placeholders::_2)); methods_.emplace( diff --git a/language_server/src/dap/dap_server.h b/language_server/src/dap/dap_server.h index d86f5139c..cec5da5b4 100644 --- a/language_server/src/dap/dap_server.h +++ b/language_server/src/dap/dap_server.h @@ -33,7 +33,7 @@ namespace hlasm_plugin::language_server::dap { class server : public hlasm_plugin::language_server::server { public: - server(parser_library::workspace_manager& ws_mngr); + explicit server(parser_library::workspace_manager& ws_mngr); // Inherited via server virtual void respond(const json& id, const std::string& requested_method, const json& args) override; @@ -48,7 +48,7 @@ class server : public hlasm_plugin::language_server::server virtual void message_received(const json& message) override; - virtual void register_methods() override; + private: @@ -56,6 +56,8 @@ class server : public hlasm_plugin::language_server::server void on_initialize(const json& requested_seq, const json& args); void on_disconnect(const json& request_seq, const json& args); + + void register_methods(); }; } // namespace hlasm_plugin::language_server::dap diff --git a/language_server/src/dap/feature_launch.cpp b/language_server/src/dap/feature_launch.cpp index e85bbd284..62ac791ca 100644 --- a/language_server/src/dap/feature_launch.cpp +++ b/language_server/src/dap/feature_launch.cpp @@ -76,8 +76,7 @@ void feature_launch::on_set_breakpoints(const json& request_seq, const json& arg { for (auto& bp_json : bpoints_found.value()) { - // parser_library::breakpoint bp(); - breakpoints.emplace_back((size_t)(bp_json["line"].get() - line_1_based_)); + breakpoints.emplace_back(bp_json["line"].get() - line_1_based_); breakpoints_verified.push_back(json { { "verified", true } }); } } @@ -192,7 +191,6 @@ void feature_launch::on_variables(const json& request_seq, const json& args) type = "B_TYPE"; break; case parser_library::set_type::C_TYPE: - type = "C_TYPE"; break; default: diff --git a/language_server/src/dap/feature_launch.h b/language_server/src/dap/feature_launch.h index 4bc14e3af..1c00b0923 100644 --- a/language_server/src/dap/feature_launch.h +++ b/language_server/src/dap/feature_launch.h @@ -19,7 +19,7 @@ namespace hlasm_plugin::language_server::dap { // Implements all the events and requests from DAP protocol. -class feature_launch : public dap_feature, parser_library::debug_event_consumer +class feature_launch : public dap_feature, public parser_library::debug_event_consumer { public: feature_launch(parser_library::workspace_manager& ws_mngr, response_provider& response_provider); diff --git a/language_server/src/dap/tcp_handler.cpp b/language_server/src/dap/tcp_handler.cpp index 562117d9b..cab9218fe 100644 --- a/language_server/src/dap/tcp_handler.cpp +++ b/language_server/src/dap/tcp_handler.cpp @@ -52,10 +52,9 @@ void tcp_handler::async_accept() newline_is_space::imbue_stream(*stream_); acceptor_.async_accept(*stream_->rdbuf(), std::bind(&tcp_handler::handle_accept, this, std::placeholders::_1)); } - catch (asio::system_error& e) + catch (const asio::system_error& e) { std::string message = "Warning: Macro tracer asio exception. " + std::string(e.what()) + "\n"; - std::cout << message; LOG_WARNING(message); return; } @@ -72,10 +71,9 @@ void tcp_handler::cancel() acceptor_.cancel(); acceptor_.close(); } - catch (asio::system_error& e) + catch (const asio::system_error& e) { std::string message = "Warning: Macro tracer asio exception. " + std::string(e.what()); - std::cout << message; LOG_WARNING(message); } } diff --git a/language_server/src/dispatcher.cpp b/language_server/src/dispatcher.cpp index fa4bd0971..a190e7b15 100644 --- a/language_server/src/dispatcher.cpp +++ b/language_server/src/dispatcher.cpp @@ -23,8 +23,7 @@ #include "logger.h" -namespace hlasm_plugin { -namespace language_server { +namespace hlasm_plugin::language_server { dispatcher::dispatcher(std::istream& in, std::ostream& out, server& server, request_manager& req_mngr) : server_(server) @@ -160,7 +159,6 @@ int dispatcher::run_server_loop() if (read_message(message)) { LOG_INFO(message); - LOG_INFO(message); json message_json = 0; try @@ -193,5 +191,4 @@ int dispatcher::run_server_loop() return ret; } -} // namespace language_server -} // namespace hlasm_plugin +} // namespace hlasm_plugin::language_server diff --git a/language_server/src/dispatcher.h b/language_server/src/dispatcher.h index 4b09bf8c4..ef5c66afb 100644 --- a/language_server/src/dispatcher.h +++ b/language_server/src/dispatcher.h @@ -41,9 +41,6 @@ class dispatcher : public send_message_provider // Reads messages from in_ in infinite loop, deserializes it and notifies the server. // Returns return value according to LSP: 0 if server was shut down apropriately int run_server_loop(); - bool read_message(std::string& out); - - void write_message(const std::string& in); // Serializes the json and sends it as message. void reply(const json& result) override; @@ -51,6 +48,10 @@ class dispatcher : public send_message_provider private: + bool read_message(std::string& out); + + void write_message(const std::string& in); + server& server_; std::istream& in_; std::ostream& out_; diff --git a/language_server/src/feature.cpp b/language_server/src/feature.cpp index f7ffb449c..5fbdb2977 100644 --- a/language_server/src/feature.cpp +++ b/language_server/src/feature.cpp @@ -105,12 +105,12 @@ parser_library::position feature::parse_position(const json& position_json) position_json["character"].get() }; } -json feature::range_to_json(parser_library::range range) +json feature::range_to_json(const parser_library::range & range) { return json { { "start", position_to_json(range.start) }, { "end", position_to_json(range.end) } }; } -json feature::position_to_json(parser_library::position position) +json feature::position_to_json(const parser_library::position & position) { return json { { "line", position.line }, { "character", position.column } }; } diff --git a/language_server/src/feature.h b/language_server/src/feature.h index d05062c2d..cb6b07f58 100644 --- a/language_server/src/feature.h +++ b/language_server/src/feature.h @@ -23,8 +23,7 @@ #include "common_types.h" #include "workspace_manager.h" -namespace hlasm_plugin { -namespace language_server { +namespace hlasm_plugin::language_server { // Provides methods to send notification, respond to request and respond with error respond class response_provider @@ -37,6 +36,7 @@ class response_provider int err_code, const std::string& err_message, const json& error) = 0; + virtual ~response_provider() = default; }; // Abstract class for group of methods that add functionality to server. @@ -45,14 +45,14 @@ class feature public: // Constructs the feature with workspace_manager. // All the requests and notification are passed to the workspace manager - feature(parser_library::workspace_manager& ws_mngr) + explicit feature(parser_library::workspace_manager& ws_mngr) : ws_mngr_(ws_mngr) - {} + { } // Constructs the feature with workspace_manager and response_provider through which the feature can send messages. feature(parser_library::workspace_manager& ws_mngr, response_provider& response_provider) : ws_mngr_(ws_mngr) , response_(&response_provider) - {} + { } // Implement to add methods to server. void virtual register_methods(std::map& methods) = 0; @@ -74,8 +74,8 @@ class feature // Converts LSP json representation of position into parse_library::position. static parser_library::position parse_position(const json& position_json); - static json range_to_json(parser_library::range range); - static json position_to_json(parser_library::position position); + static json range_to_json(const parser_library::range & range); + static json position_to_json(const parser_library::position & position); virtual ~feature() = default; @@ -85,7 +85,6 @@ class feature response_provider* response_ = nullptr; }; -} // namespace language_server -} // namespace hlasm_plugin +} // namespace hlasm_plugin::language_server #endif // !HLASMPLUGIN_LANGUAGESERVER_FEATURE_H diff --git a/language_server/src/lsp/feature_language_features.cpp b/language_server/src/lsp/feature_language_features.cpp index a760ceb13..e4e2a7042 100644 --- a/language_server/src/lsp/feature_language_features.cpp +++ b/language_server/src/lsp/feature_language_features.cpp @@ -48,7 +48,10 @@ json feature_language_features::register_capabilities() { { "resolveProvider", false }, { "triggerCharacters", { "&", ".", "_", "$", "#", "@", "*" } } } } }; } -void feature_language_features::initialize_feature(const json&) {} +void feature_language_features::initialize_feature(const json&) +{ + //No need for initialization in this feature. +} void feature_language_features::definition(const json& id, const json& params) { diff --git a/language_server/src/lsp/feature_text_synchronization.cpp b/language_server/src/lsp/feature_text_synchronization.cpp index 47592deeb..53dfddef4 100644 --- a/language_server/src/lsp/feature_text_synchronization.cpp +++ b/language_server/src/lsp/feature_text_synchronization.cpp @@ -51,7 +51,10 @@ json feature_text_synchronization::register_capabilities() } -void feature_text_synchronization::initialize_feature(const json&) {} +void feature_text_synchronization::initialize_feature(const json&) +{ + //No need for initialization in this feature. +} void feature_text_synchronization::on_did_open(const json&, const json& params) { @@ -164,6 +167,9 @@ void feature_text_synchronization::consume_highlighting_info(parser_library::all case parser_library::semantics::hl_scopes::data_def_extension: scope = "data_def_extension"; break; + default: + assert(false); + break; } tokens_array.push_back(json { { "lineStart", fi.token(j).token_range.start.line }, { "columnStart", fi.token(j).token_range.start.column }, diff --git a/language_server/src/lsp/feature_workspace_folders.cpp b/language_server/src/lsp/feature_workspace_folders.cpp index ff3d9989f..adafe2ed3 100644 --- a/language_server/src/lsp/feature_workspace_folders.cpp +++ b/language_server/src/lsp/feature_workspace_folders.cpp @@ -60,24 +60,18 @@ void feature_workspace_folders::initialize_feature(const json& initialize_params auto root_uri = initialize_params.find("rootUri"); - if (root_uri != initialize_params.end()) + if (root_uri != initialize_params.end() && !root_uri->is_null()) { - if (!root_uri->is_null()) - { - std::string uri = root_uri->get(); - add_workspace(uri, uri_to_path(uri)); - return; - } + std::string uri = root_uri->get(); + add_workspace(uri, uri_to_path(uri)); + return; } auto root_path = initialize_params.find("rootPath"); - if (root_path != initialize_params.end()) + if (root_path != initialize_params.end() && !root_path->is_null()) { - if (!root_path->is_null()) - { - std::filesystem::path path(root_path->get()); - add_workspace(path.lexically_normal().string(), path.lexically_normal().string()); - } + std::filesystem::path path(root_path->get()); + add_workspace(path.lexically_normal().string(), path.lexically_normal().string()); } } @@ -103,9 +97,9 @@ void feature_workspace_folders::add_workspaces(const json& added) } void feature_workspace_folders::remove_workspaces(const json& removed) { - for (auto it = removed.begin(); it != removed.end(); ++it) + for(auto ws : removed) { - std::string uri = (*it)["uri"].get(); + std::string uri = ws["uri"].get(); ws_mngr_.remove_workspace(uri_to_path(uri).c_str()); } @@ -122,7 +116,7 @@ void feature_workspace_folders::did_change_watched_files(const json&, const json for (auto& change : changes) paths.push_back(uri_to_path(change["uri"].get())); std::vector c_uris; - std::transform(paths.begin(), paths.end(), std::back_inserter(c_uris), [](std::string& s) { return s.c_str(); }); + std::transform(paths.begin(), paths.end(), std::back_inserter(c_uris), [](const std::string& s) { return s.c_str(); }); ws_mngr_.did_change_watched_files(c_uris.data(), c_uris.size()); } } // namespace hlasm_plugin::language_server::lsp diff --git a/language_server/src/lsp/feature_workspace_folders.h b/language_server/src/lsp/feature_workspace_folders.h index e49e9456f..34c10f887 100644 --- a/language_server/src/lsp/feature_workspace_folders.h +++ b/language_server/src/lsp/feature_workspace_folders.h @@ -28,7 +28,7 @@ namespace hlasm_plugin::language_server::lsp { class feature_workspace_folders : public feature { public: - feature_workspace_folders(parser_library::workspace_manager& ws_mngr); + explicit feature_workspace_folders(parser_library::workspace_manager& ws_mngr); // Adds workspace/didChangeWorkspaceFolders method to the map. void register_methods(std::map&) override; diff --git a/language_server/src/lsp/lsp_server.cpp b/language_server/src/lsp/lsp_server.cpp index 4f4ca3de7..9db843a06 100644 --- a/language_server/src/lsp/lsp_server.cpp +++ b/language_server/src/lsp/lsp_server.cpp @@ -31,6 +31,7 @@ server::server(parser_library::workspace_manager& ws_mngr) features_.push_back(std::make_unique(ws_mngr_)); features_.push_back(std::make_unique(ws_mngr_, *this)); features_.push_back(std::make_unique(ws_mngr_, *this)); + register_feature_methods(); register_methods(); ws_mngr_.register_diagnostics_consumer(this); @@ -85,8 +86,6 @@ void server::respond_error( void server::register_methods() { - language_server::server::register_methods(); - methods_.emplace( "initialize", std::bind(&server::on_initialize, this, std::placeholders::_1, std::placeholders::_2)); methods_.emplace("shutdown", std::bind(&server::on_shutdown, this, std::placeholders::_1, std::placeholders::_2)); @@ -170,7 +169,7 @@ void server::consume_diagnostics(parser_library::diagnostic_list diagnostics) // set of all files for which diagnostics came from the server. std::unordered_set new_files; // transform the diagnostics into json - for (auto& file_diags : diags) + for (const auto& file_diags : diags) { json diags_array = json::array(); for (auto d : file_diags.second) diff --git a/language_server/src/lsp/lsp_server.h b/language_server/src/lsp/lsp_server.h index 1b4e2dbdb..02cb35860 100644 --- a/language_server/src/lsp/lsp_server.h +++ b/language_server/src/lsp/lsp_server.h @@ -39,30 +39,26 @@ enum class message_type // Implements LSP server (session-controlling methods like initialize and shutdown). // Integrates 3 features: language features, text synchronization and workspace folders. // Consumes diagnostics that come from the parser library and sends them to LSP client. -class server : public hlasm_plugin::language_server::server, public parser_library::diagnostics_consumer +class server final : public hlasm_plugin::language_server::server, public parser_library::diagnostics_consumer { public: // Creates the server with workspace_manager as entry point to parser library. - server(parser_library::workspace_manager& ws_mngr); + explicit server(parser_library::workspace_manager& ws_mngr); // Parses LSP (JSON RPC) message and calls corresponding method. - virtual void message_received(const json& message) override; + void message_received(const json& message) override; protected: // Sends respond to request to LSP client using send_message_provider. - virtual void respond(const json& id, const std::string& requested_method, const json& args) override; + void respond(const json& id, const std::string& requested_method, const json& args) override; // Sends notification to LSP client using send_message_provider. - virtual void notify(const std::string& method, const json& args) override; + void notify(const std::string& method, const json& args) override; // Sends errorous respond to LSP client using send_message_provider. - virtual void respond_error(const json& id, + void respond_error(const json& id, const std::string& requested_method, int err_code, const std::string& err_message, const json& error) override; - - // Registers LSP methods implemented by this server (not by features). - virtual void register_methods() override; - private: // requests @@ -89,7 +85,10 @@ class server : public hlasm_plugin::language_server::server, public parser_libra std::unordered_set last_diagnostics_files_; // Implements parser_library::diagnostics_consumer: wraps the diagnostics in json and // sends them to client. - virtual void consume_diagnostics(parser_library::diagnostic_list diagnostics) override; + void consume_diagnostics(parser_library::diagnostic_list diagnostics) override; + + // Registers LSP methods implemented by this server (not by features). + void register_methods(); }; } // namespace hlasm_plugin::language_server::lsp diff --git a/language_server/src/main.cpp b/language_server/src/main.cpp index 37a3949a6..62e557141 100644 --- a/language_server/src/main.cpp +++ b/language_server/src/main.cpp @@ -114,7 +114,7 @@ int main(int argc, char** argv) return ret; } - catch (std::exception& ex) + catch (const std::exception& ex) { LOG_ERROR(ex.what()); return 1; diff --git a/language_server/src/request_manager.cpp b/language_server/src/request_manager.cpp index 2a7df6bc3..5399086a2 100644 --- a/language_server/src/request_manager.cpp +++ b/language_server/src/request_manager.cpp @@ -22,14 +22,20 @@ request::request(json message, server* executing_server) , executing_server(executing_server) {} -request_manager::request_manager(std::atomic* cancel) +request_manager::request_manager(std::atomic* cancel, async_policy async_pol) : end_worker_(false) , cancel_(cancel) , worker_(&request_manager::handle_request_, this, &end_worker_) + , async_policy_(async_pol) {} void request_manager::add_request(server* server, json message) { + if (async_policy_ == async_policy::SYNC) + { + server->message_received(message); + return; + } // add request to q { std::unique_lock lock(q_mtx_); @@ -62,14 +68,9 @@ void request_manager::end_worker() worker_.join(); } -bool hlasm_plugin::language_server::request_manager::is_running() +bool request_manager::is_running() const { - bool result = false; - { - std::unique_lock lock(q_mtx_); - result = !requests_.empty(); - } - return result; + return !requests_.empty(); } void request_manager::handle_request_(const std::atomic* end_loop) @@ -79,8 +80,7 @@ void request_manager::handle_request_(const std::atomic* end_loop) { std::unique_lock lock(q_mtx_); // wait for work to come - if (requests_.empty()) - cond_.wait(lock, [&] { return !requests_.empty() || *end_loop; }); + cond_.wait(lock, [&] { return !requests_.empty() || *end_loop; }); if (*end_loop) return; @@ -109,6 +109,9 @@ void request_manager::finish_server_requests(server* to_finish) { std::lock_guard guard(q_mtx_); + if (requests_.empty()) + return; + if (cancel_) *cancel_ = true; @@ -117,12 +120,12 @@ void request_manager::finish_server_requests(server* to_finish) std::this_thread::sleep_for(std::chrono::milliseconds(100)); // executes all remaining requests for a server - for (auto it = requests_.begin(); it != requests_.end(); ++it) + for(auto& req : requests_) { - if (it->executing_server != to_finish) + if (req.executing_server != to_finish) continue; - it->executing_server->message_received(it->message); + req.executing_server->message_received(req.message); } // remove the executed requests requests_.erase( @@ -132,7 +135,7 @@ void request_manager::finish_server_requests(server* to_finish) } -std::string request_manager::get_request_file_(json r, bool* is_parsing_required) +std::string request_manager::get_request_file_(json r, bool* is_parsing_required) const { constexpr const char* didOpen = "textDocument/didOpen"; constexpr const char* didChange = "textDocument/didChange"; diff --git a/language_server/src/request_manager.h b/language_server/src/request_manager.h index 88038d67f..52d2e606e 100644 --- a/language_server/src/request_manager.h +++ b/language_server/src/request_manager.h @@ -40,11 +40,18 @@ struct request class request_manager { public: - request_manager(std::atomic* cancel); + enum class async_policy + { + ASYNC, + SYNC + }; + + explicit request_manager(std::atomic* cancel, async_policy async_pol = async_policy::ASYNC); void add_request(server* server, json message); void finish_server_requests(server* server); void end_worker(); - bool is_running(); + bool is_running() const; + private: std::atomic end_worker_; @@ -59,7 +66,7 @@ class request_manager std::atomic currently_running_server_; void handle_request_(const std::atomic* end_loop); - std::string get_request_file_(json r, bool* is_parsing_required = nullptr); + std::string get_request_file_(json r, bool* is_parsing_required = nullptr) const; std::deque requests_; @@ -68,6 +75,8 @@ class request_manager std::atomic* cancel_; std::thread worker_; + + async_policy async_policy_; }; diff --git a/language_server/src/server.cpp b/language_server/src/server.cpp index 897ff831e..43503c78e 100644 --- a/language_server/src/server.cpp +++ b/language_server/src/server.cpp @@ -26,9 +26,9 @@ namespace hlasm_plugin::language_server { server::server(parser_library::workspace_manager& ws_mngr) : ws_mngr_(ws_mngr) -{} +{ } -void server::register_methods() +void server::register_feature_methods() { for (auto& f : features_) { @@ -52,6 +52,7 @@ void server::call_method(const std::string& method, const json& id, const json& } catch (const nlohmann::basic_json<>::exception& e) { + (void)e; LOG_WARNING("There is an error regarding the JSON or LSP:" + std::string(e.what())); } } @@ -63,10 +64,10 @@ void server::call_method(const std::string& method, const json& id, const json& } } -bool server::is_exit_notification_received() { return exit_notification_received_; } +bool server::is_exit_notification_received() const { return exit_notification_received_; } void server::set_send_message_provider(send_message_provider* provider) { send_message_ = provider; } -bool server::is_shutdown_request_received() { return shutdown_request_received_; } +bool server::is_shutdown_request_received() const { return shutdown_request_received_; } } // namespace hlasm_plugin::language_server diff --git a/language_server/src/server.h b/language_server/src/server.h index 56257049f..5b569a193 100644 --- a/language_server/src/server.h +++ b/language_server/src/server.h @@ -40,15 +40,15 @@ class server : public response_provider public: // Constructs the server with workspace_manager. // All the requests and notifications are passed to the workspace manager - server(parser_library::workspace_manager& ws_mngr); + explicit server(parser_library::workspace_manager& ws_mngr); // Tells the server that a massage was received. The server carries out the notification or request. virtual void message_received(const json& message) = 0; // Returns true, if LSP shutdown request has been received. - bool is_shutdown_request_received(); + bool is_shutdown_request_received() const; // Returns true, if LSP exit notification has been received. - bool is_exit_notification_received(); + bool is_exit_notification_received() const; void set_send_message_provider(send_message_provider* provider); @@ -64,7 +64,7 @@ class server : public response_provider parser_library::workspace_manager& ws_mngr_; - virtual void register_methods(); + void register_feature_methods(); // Calls a method that is registered in methods_ with the specified name with arguments and id of request. void call_method(const std::string& method, const json& id, const json& args); diff --git a/language_server/test/dap/dap_server_test.cpp b/language_server/test/dap/dap_server_test.cpp new file mode 100644 index 000000000..554397f52 --- /dev/null +++ b/language_server/test/dap/dap_server_test.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 Broadcom. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + */ + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" + +#include "dap/dap_server.h" +#include "workspace_manager.h" + +using namespace hlasm_plugin; +using namespace hlasm_plugin::language_server; + +struct send_message_provider_mock : public send_message_provider +{ + void reply(const json& result) override { replies.push_back(result); }; + + std::vector replies; +}; + + +TEST(dap_server, dap_server) +{ + std::string file_name = std::filesystem::absolute("to_trace").string(); + file_name[0] = (char)std::tolower((char)file_name[0]); + + std::string file_text = " LR 1,1"; + parser_library::workspace_manager ws_mngr; + ws_mngr.did_open_file(file_name.c_str(), 0, file_text.c_str(), file_text.size()); + + send_message_provider_mock smp; + dap::server serv(ws_mngr); + serv.set_send_message_provider(&smp); + + // actual message sent by VS Code + json initialize_message = + R"({"command":"initialize","arguments":{"clientID":"vscode","clientName":"Visual Studio Code","adapterID":"hlasm","pathFormat":"path","linesStartAt1":true,"columnsStartAt1":true,"supportsVariableType":true,"supportsVariablePaging":true,"supportsRunInTerminalRequest":true,"locale":"en-us","supportsProgressReporting":true},"type":"request","seq":1})"_json; + + serv.message_received(initialize_message); + + std::vector expected_response_init = { + R"({"body":{"supportsConfigurationDoneRequest":true},"command":"initialize","request_seq":1,"seq":2,"success":true,"type":"response"})"_json, + R"({"body":null,"event" : "initialized","seq" : 3,"type" : "event"})"_json + }; + + EXPECT_EQ(smp.replies, expected_response_init); + smp.replies.clear(); + + + json disconnect_message = + R"({"command":"disconnect","arguments":{"restart":false},"type":"request","seq":10})"_json; + + std::vector expected_response_disconnect = { + R"({"body":null,"command":"disconnect","request_seq":10,"seq":11,"success":true,"type":"response"})"_json + }; + + serv.message_received(disconnect_message); + EXPECT_EQ(smp.replies, expected_response_disconnect); + EXPECT_TRUE(serv.is_exit_notification_received()); + EXPECT_TRUE(serv.is_shutdown_request_received()); + +} + +TEST(dap_server, malformed_message) +{ + parser_library::workspace_manager ws_mngr; + json malf = R"({"commnd":"disconnect"})"_json; + dap::server serv(ws_mngr); + + + serv.message_received(malf); + // No assertions, the server must not crash. +} diff --git a/language_server/test/dap/feature_launch_test.cpp b/language_server/test/dap/feature_launch_test.cpp new file mode 100644 index 000000000..67534fe40 --- /dev/null +++ b/language_server/test/dap/feature_launch_test.cpp @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2019 Broadcom. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + */ + +#include +#include +#include +#include + +#include "gmock/gmock.h" + +#include "dap/feature_launch.h" +#include "workspace_manager.h" + + +using namespace hlasm_plugin; +using namespace hlasm_plugin::language_server; +using namespace hlasm_plugin::language_server::dap; +using namespace std::chrono_literals; + +struct response_mock +{ + json id; + std::string req_method; + json args; + + friend bool operator==(const response_mock& lhs, const response_mock& rhs) + { + return lhs.id == rhs.id && lhs.req_method == rhs.req_method && lhs.args == rhs.args; + } +}; + +struct notif_mock +{ + std::string req_method; + json args; + + friend bool operator==(const notif_mock& lhs, const notif_mock& rhs) + { + return lhs.req_method == rhs.req_method && lhs.args == rhs.args; + } +}; + + + +struct response_provider_mock : public response_provider +{ + void respond(const json& id, const std::string& requested_method, const json& args) override + { + responses.push_back({ id, requested_method, args }); + }; + void notify(const std::string& method, const json& args) override + { + notifs.push_back({ method, args }); + if (method == "stopped") + stopped = true; + if (method == "exited") + exited = true; + }; + void respond_error(const json&, const std::string&, int, const std::string&, const json&) override {}; + + std::vector responses; + std::vector notifs; + std::atomic stopped = false; + std::atomic exited = false; + + void wait_for_stopped() + { + size_t i = 0; + while (!stopped) + { + if (i > 50) + throw std::runtime_error("Wait for stopped timeout."); + ++i; + + std::this_thread::sleep_for(100ms); + } + stopped = false; + } + + void wait_for_exited() + { + size_t i = 0; + while (!exited) + { + if (i > 50) + throw std::runtime_error("Wait for exited timeout."); + ++i; + + std::this_thread::sleep_for(100ms); + } + exited = false; + } + + + void reset() + { + responses.clear(); + notifs.clear(); + } +}; + +struct feature_launch_test : public testing::Test +{ + feature_launch_test() + : feat(ws_mngr, resp_provider) + { + feat.register_methods(methods); + feat.initialize_feature(R"({"linesStartAt1":false, "columnsStartAt1":false, "pathFormat":"path"})"_json); + + file_name = std::filesystem::absolute("to_trace").string(); + file_name[0] = (char) std::tolower((char)file_name[0]); + } + + void check_simple_stack_trace(json id, size_t expected_line) + { + methods["stackTrace"]("1"_json, json()); + ASSERT_EQ(resp_provider.responses.size(), 1U); + const response_mock& r = resp_provider.responses[0]; + EXPECT_EQ(r.id, "1"_json); + EXPECT_EQ(r.req_method, "stackTrace"); + ASSERT_EQ(r.args["totalFrames"], 1U); + ASSERT_EQ(r.args["stackFrames"].size(), 1U); + const json& f = r.args["stackFrames"][0]; + EXPECT_EQ(f["name"], "OPENCODE"); + json expected_source { { "path", file_name } }; + EXPECT_EQ(f["source"], expected_source); + EXPECT_EQ(f["line"], expected_line); + EXPECT_EQ(f["endLine"], expected_line); + EXPECT_EQ(f["column"], 0); + EXPECT_EQ(f["endColumn"], 0); + resp_provider.reset(); + } + + void wait_for_stopped() + { + resp_provider.wait_for_stopped(); + EXPECT_EQ(resp_provider.notifs.size(), 1U); + } + + std::map methods; + response_provider_mock resp_provider; + parser_library::workspace_manager ws_mngr; + feature_launch feat; + std::string file_name; +}; + +std::string file_stop_on_entry = " LR 1,1"; + +TEST_F(feature_launch_test, stop_on_entry) +{ + ws_mngr.did_open_file(file_name.c_str(), 0, file_stop_on_entry.c_str(), file_stop_on_entry.size()); + + + methods["launch"]("0"_json, json { { "program", file_name }, { "stopOnEntry", true } }); + std::vector expected_resp = { { "0"_json, "launch", json() } }; + EXPECT_EQ(resp_provider.responses, expected_resp); + wait_for_stopped(); + resp_provider.reset(); + + check_simple_stack_trace("1"_json, 0); + + ws_mngr.disconnect(); +} + + +std::string file_step = R"( LR 1,1 + MACRO + MAC + LR 1,1 + MEND + + MAC + MAC + + LR 1,1 Second breakpoint on this line +)"; + +TEST_F(feature_launch_test, step) +{ + ws_mngr.did_open_file(file_name.c_str(), 0, file_step.c_str(), file_step.size()); + + methods["launch"]("0"_json, json { { "program", file_name }, { "stopOnEntry", true } }); + std::vector expected_resp = { { "0"_json, "launch", json() } }; + EXPECT_EQ(resp_provider.responses, expected_resp); + wait_for_stopped(); + resp_provider.reset(); + + + check_simple_stack_trace("2"_json, 0); + + methods["stepIn"]("3"_json, json()); + expected_resp = { { "3"_json, "stepIn", json() } }; + EXPECT_EQ(resp_provider.responses, expected_resp); + wait_for_stopped(); + resp_provider.reset(); + + check_simple_stack_trace("4"_json, 1); + + methods["next"]("5"_json, json()); + expected_resp = { { "5"_json, "next", json() } }; + EXPECT_EQ(resp_provider.responses, expected_resp); + wait_for_stopped(); + resp_provider.reset(); + + check_simple_stack_trace("6"_json, 6); + + methods["next"]("7"_json, json()); + expected_resp = { { "7"_json, "next", json() } }; + EXPECT_EQ(resp_provider.responses, expected_resp); + wait_for_stopped(); + resp_provider.reset(); + + check_simple_stack_trace("8"_json, 7); + + methods["stepIn"]("8"_json, json()); + expected_resp = { { "8"_json, "stepIn", json() } }; + EXPECT_EQ(resp_provider.responses, expected_resp); + wait_for_stopped(); + resp_provider.reset(); + + methods["stackTrace"]("9"_json, json()); + ASSERT_EQ(resp_provider.responses.size(), 1U); + const response_mock& r = resp_provider.responses[0]; + EXPECT_EQ(r.id, "9"_json); + EXPECT_EQ(r.req_method, "stackTrace"); + ASSERT_EQ(r.args["totalFrames"], 2U); + ASSERT_EQ(r.args["stackFrames"].size(), 2U); + const json& fs = r.args["stackFrames"]; + size_t expected_lines[2] = { 3, 7 }; + std::string expected_names[2] = { "MACRO", "OPENCODE" }; + for (size_t i = 0; i < 2; ++i) + { + const json& f = fs[i]; + EXPECT_EQ(f["name"], expected_names[i]); + json expected_source { { "path", file_name } }; + EXPECT_EQ(f["source"], expected_source); + EXPECT_EQ(f["line"], expected_lines[i]); + EXPECT_EQ(f["endLine"], expected_lines[i]); + EXPECT_EQ(f["column"], 0); + EXPECT_EQ(f["endColumn"], 0); + } + resp_provider.reset(); + + methods["continue"]("47"_json, json()); + resp_provider.wait_for_exited(); +} + +std::string file_breakpoint = R"( LR 1,1 + LR 1,1 First breakpoint comes on this line + + LR 1,1 Second breakpoint on this line +)"; + +TEST_F(feature_launch_test, breakpoint) +{ + ws_mngr.did_open_file(file_name.c_str(), 0, file_breakpoint.c_str(), file_breakpoint.size()); + + + json bp_args { { "source", { { "path", file_name } } }, { "breakpoints", R"([{"line":1}, {"line":3}])"_json } }; + methods["setBreakpoints"]("47"_json, bp_args); + std::vector expected_resp_bp = { { "47"_json, "setBreakpoints", R"( + { "breakpoints":[ + {"verified":true}, + {"verified":true} + ]})"_json } }; + EXPECT_EQ(resp_provider.responses, expected_resp_bp); + resp_provider.reset(); + + methods["launch"]("0"_json, json { { "program", file_name }, { "stopOnEntry", false } }); + std::vector expected_resp = { { "0"_json, "launch", json() } }; + EXPECT_EQ(resp_provider.responses, expected_resp); + wait_for_stopped(); + resp_provider.reset(); + + + check_simple_stack_trace("2"_json, 1); + + methods["continue"]("3"_json, json()); + std::vector expected_resp_cont = { + { "3"_json, "continue", json { { "allThreadsContinued", true } } } + }; + EXPECT_EQ(resp_provider.responses, expected_resp_cont); + wait_for_stopped(); + resp_provider.reset(); + + check_simple_stack_trace("4"_json, 3); + + ws_mngr.disconnect(); +} + +std::string file_variables = R"(&VARA SETA 4 +&VARB SETB 1 +&VARC SETC 'STH' + LR 1,1)"; + +TEST_F(feature_launch_test, variables) +{ + ws_mngr.did_open_file(file_name.c_str(), 0, file_variables.c_str(), file_variables.size()); + + methods["launch"]("0"_json, json { { "program", file_name }, { "stopOnEntry", true } }); + std::vector expected_resp = { { "0"_json, "launch", json() } }; + EXPECT_EQ(resp_provider.responses, expected_resp); + wait_for_stopped(); + resp_provider.reset(); + + + check_simple_stack_trace("2"_json, 0); + + methods["stepIn"]("3"_json, json()); + resp_provider.wait_for_stopped(); + methods["stepIn"]("3"_json, json()); + resp_provider.wait_for_stopped(); + methods["stepIn"]("3"_json, json()); + resp_provider.wait_for_stopped(); + resp_provider.reset(); + + methods["stackTrace"]("1"_json, json()); + ASSERT_EQ(resp_provider.responses.size(), 1U); + response_mock r = resp_provider.responses[0]; + EXPECT_EQ(r.id, "1"_json); + EXPECT_EQ(r.req_method, "stackTrace"); + ASSERT_EQ(r.args["totalFrames"], 1U); + ASSERT_EQ(r.args["stackFrames"].size(), 1U); + json f = r.args["stackFrames"][0]; + EXPECT_EQ(f["name"], "OPENCODE"); + json expected_source { { "path", file_name } }; + EXPECT_EQ(f["source"], expected_source); + EXPECT_EQ(f["line"], 3U); + EXPECT_EQ(f["endLine"], 3U); + EXPECT_EQ(f["column"], 0); + EXPECT_EQ(f["endColumn"], 0); + json frame_id = f["id"]; + resp_provider.reset(); + + methods["scopes"]("5"_json, json { { "frameId", frame_id } }); + ASSERT_EQ(resp_provider.responses.size(), 1U); + r = resp_provider.responses[0]; + EXPECT_EQ(r.id, "5"_json); + EXPECT_EQ(r.req_method, "scopes"); + json scopes = r.args["scopes"]; + json locals_ref; + for (const json & scope : scopes) + { + EXPECT_FALSE(scope.find("name") == scope.end()); + EXPECT_FALSE(scope.find("variablesReference") == scope.end()); + EXPECT_EQ(scope["expensive"], json(false)); + EXPECT_EQ(scope["source"], expected_source); + if (scope["name"] == "Locals") + locals_ref = scope["variablesReference"]; + } + resp_provider.reset(); + + methods["variables"]("8"_json, json { { "variablesReference", locals_ref } }); + ASSERT_EQ(resp_provider.responses.size(), 1U); + r = resp_provider.responses[0]; + EXPECT_EQ(r.id, "8"_json); + EXPECT_EQ(r.req_method, "variables"); + const json & variables = r.args["variables"]; + size_t var_count = 0; + for (const json & var : variables) + { + EXPECT_FALSE(var.find("name") == var.end()); + EXPECT_FALSE(var.find("value") == var.end()); + EXPECT_FALSE(var.find("variablesReference") == var.end()); + if (var["name"] == "VARA") + { + EXPECT_EQ(var["value"], json("4")); + EXPECT_EQ(var["type"], json("A_TYPE")); + ++var_count; + } + else if (var["name"] == "VARB") + { + EXPECT_EQ(var["value"], json("TRUE")); + EXPECT_EQ(var["type"], json("B_TYPE")); + ++var_count; + } + else if (var["name"] == "VARC") + { + EXPECT_EQ(var["value"], json("STH")); + EXPECT_EQ(var["type"], json("C_TYPE")); + ++var_count; + } + } + //test, that all variable symbols were reported + EXPECT_EQ(var_count, 3); + + ws_mngr.disconnect(); +} \ No newline at end of file diff --git a/language_server/test/dispatcher_test.cpp b/language_server/test/dispatcher_test.cpp new file mode 100644 index 000000000..6d03811be --- /dev/null +++ b/language_server/test/dispatcher_test.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2019 Broadcom. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + */ + +#include + +#include "gmock/gmock.h" + +#include "dispatcher.h" +#include "stream_helper.h" + + +using namespace hlasm_plugin::language_server; + +class server_mock : public server +{ + hlasm_plugin::parser_library::workspace_manager ws; + int counter = 0; + int messages_limit; + +public: + server_mock(int max_messages) + : server(ws) + , messages_limit(max_messages) + { } + + std::vector messages; + + virtual void respond(const json&, const std::string&, const json&) override { } + virtual void notify(const std::string&, const json&) override { } + virtual void respond_error(const json&, const std::string&, int, const std::string&, const json&) override { } + virtual void message_received(const json& message) override + { + ++counter; + if (counter == messages_limit) + { + shutdown_request_received_ = true; + exit_notification_received_ = true; + } + messages.push_back(message); + } +}; + +struct test_param +{ + int messages_limit; + int return_value; + std::vector headers; + std::vector messages; + bool write_messages; + std::string name; +}; + +struct stringer +{ + std::string operator()(::testing::TestParamInfo p) { return p.param.name; } +}; + +class dispatcher_fixture : public ::testing::TestWithParam +{ }; + +INSTANTIATE_TEST_SUITE_P(dispatcher, + dispatcher_fixture, + ::testing::Values( + test_param { 1, 1, { "Content-Length: 30\r\n\r\n\"A json message 1\"" }, {}, false, "unexpected_eof" }, + test_param { 1, + 0, + { "Content-Length: 18\r\nShould be ignored\r\n\r\n\"A json message 1\"" }, + { "\"A json message 1\""_json }, + false, + "unexpected_header_entry" }, + test_param{ 1, + 0, + { "Content-Length: 30\r\nContent-Length: 18\r\n\r\n" }, + { "\"A json message 1\""_json }, + true, + "two_content_length_headers" }, + test_param{ 1, + 0, + { "Content-Length: 0\r\nContent-Length: 18\r\n\r\n" }, + { "\"A json message 1\""_json }, + true, + "content_length_zero" }, + test_param { 1, + 1, + { "Content-Length: 3000000000000000000\r\n\r\n\"A json message 1\"" }, + {}, + false, + "unexpected_content_length" }, + test_param{ 1, + 1, + { "Content-Length: 27\r\n\r\n\"A malformed json message 1" }, + {}, + false, + "malformed_message" }, + test_param{ 2, + 0, + { "Content-Length: 18\r\n\r\n", "Content-Length: 20\r\n\r\n" }, + { "\"A json message 1\""_json, R"({"Second":"message"})"_json}, + true, + "two_messages" }), + stringer()); + +TEST_P(dispatcher_fixture, basic) +{ + std::stringstream ss_in; + std::stringstream ss_out; + for (size_t i = 0; i < GetParam().headers.size(); ++i) + { + ss_in << GetParam().headers[i]; + if (GetParam().write_messages) + ss_in << GetParam().messages[i].dump(); + } + + newline_is_space::imbue_stream(ss_in); + + std::atomic cancel = false; + request_manager rm(&cancel, request_manager::async_policy::SYNC); + + server_mock dummy_server(GetParam().messages_limit); + + + dispatcher disp(ss_in, ss_out, dummy_server, rm); + + int ret = disp.run_server_loop(); + + EXPECT_EQ(ret, GetParam().return_value); + EXPECT_EQ(dummy_server.messages, GetParam().messages); + rm.end_worker(); +} + +TEST(dispatcher, write_message) +{ + std::stringstream ss; + server_mock dummy_server(1); + std::atomic cancel = false; + request_manager rm(&cancel, request_manager::async_policy::SYNC); + dispatcher d(ss, ss, dummy_server, rm); + + json message = R"("A json message")"_json; + d.reply(message); + + EXPECT_EQ(ss.str(), "Content-Length: 16\r\n\r\n" + message.dump()); + + rm.end_worker(); +} diff --git a/language_server/test/lsp/feature_language_features_test.cpp b/language_server/test/lsp/feature_language_features_test.cpp index e74396074..fa07fbca2 100644 --- a/language_server/test/lsp/feature_language_features_test.cpp +++ b/language_server/test/lsp/feature_language_features_test.cpp @@ -28,6 +28,8 @@ constexpr const char* path = "c:\\test"; constexpr const char* path = "/home/test"; #endif +using namespace hlasm_plugin::language_server; + TEST(language_features, completion) { using namespace ::testing; diff --git a/language_server/test/lsp/feature_text_synchronization_test.cpp b/language_server/test/lsp/feature_text_synchronization_test.cpp index 7d42879f7..8c4176af0 100644 --- a/language_server/test/lsp/feature_text_synchronization_test.cpp +++ b/language_server/test/lsp/feature_text_synchronization_test.cpp @@ -29,6 +29,8 @@ const std::string txt_file_uri = R"(file:///home/user/somefile)"; const std::string txt_file_path = R"(/home/user/somefile)"; #endif +using namespace hlasm_plugin::language_server; + TEST(text_synchronization, did_open_file) { using namespace ::testing; diff --git a/language_server/test/server_test.cpp b/language_server/test/lsp/lsp_server_test.cpp similarity index 77% rename from language_server/test/server_test.cpp rename to language_server/test/lsp/lsp_server_test.cpp index 49f06b30b..ac449fe40 100644 --- a/language_server/test/server_test.cpp +++ b/language_server/test/lsp/lsp_server_test.cpp @@ -12,9 +12,6 @@ * Broadcom, Inc. - initial API and implementation */ -#ifndef HLASMPLUGIN_LANGUAGESERVER_TEST_SERVER_TEST_H -#define HLASMPLUGIN_LANGUAGESERVER_TEST_SERVER_TEST_H - #include #include "gmock/gmock.h" @@ -24,9 +21,9 @@ #include "lsp/feature_text_synchronization.h" #include "lsp/feature_workspace_folders.h" #include "lsp/lsp_server.h" -#include "send_message_provider_mock.h" +#include "../send_message_provider_mock.h" #include "workspace_manager.h" -#include "ws_mngr_mock.h" +#include "../ws_mngr_mock.h" namespace nlohmann { // needed in order to have mock methods with json arguments @@ -36,7 +33,7 @@ inline void PrintTo(json const& json, std::ostream* os) { *os << json.dump(); } using namespace hlasm_plugin; using namespace language_server; -TEST(lsp_server_test, initialize) +TEST(lsp_server, initialize) { // this is json params actually sent by vscode LSP client json j = @@ -64,6 +61,31 @@ TEST(lsp_server_test, initialize) EXPECT_EQ(server_capab["id"].get(), 47); ASSERT_NE(server_capab.find("result"), server_capab.end()); EXPECT_NE(server_capab["result"].find("capabilities"), server_capab["result"].end()); + + json shutdown_request = R"({"jsonrpc":"2.0","id":48,"method":"shutdown","params":null})"_json; + json shutdown_response = R"({"jsonrpc":"2.0","id":48,"result":null})"_json; + json exit_notification = R"({"jsonrpc":"2.0","method":"exit","params":null})"_json; + EXPECT_CALL(smpm, reply(shutdown_response)).Times(1); + EXPECT_FALSE(s.is_exit_notification_received()); + EXPECT_FALSE(s.is_shutdown_request_received()); + s.message_received(shutdown_request); + EXPECT_FALSE(s.is_exit_notification_received()); + EXPECT_TRUE(s.is_shutdown_request_received()); + s.message_received(exit_notification); + EXPECT_TRUE(s.is_exit_notification_received()); + EXPECT_TRUE(s.is_shutdown_request_received()); + +} + +TEST(lsp_server, not_implemented_method) +{ + json j = R"({"jsonrpc":"2.0","id":47,"method":"unknown_method","params":"A parameter"})"_json; + ws_mngr_mock ws_mngr; + send_message_provider_mock smpm; + lsp::server s(ws_mngr); + s.set_send_message_provider(&smpm); + + s.message_received(j); + //No result is tested, server should ignore unknown LSP method } -#endif // !HLASMPLUGIN_LANGUAGESERVER_TEST_SERVER_TEST_H diff --git a/language_server/test/regress_test.cpp b/language_server/test/regress_test.cpp index 6f78c052d..f84eef19f 100644 --- a/language_server/test/regress_test.cpp +++ b/language_server/test/regress_test.cpp @@ -12,9 +12,6 @@ * Broadcom, Inc. - initial API and implementation */ -#ifndef HLASMPLUGIN_LANGUAGESERVER_TEST_REGRESS_TEST_H -#define HLASMPLUGIN_LANGUAGESERVER_TEST_REGRESS_TEST_H - #include #include "gmock/gmock.h" @@ -670,9 +667,3 @@ TEST(regress_test, stability_sync) for (const auto& message : messages) s.message_received(message); } - - - - - -#endif // !HLASMPLUGIN_LANGUAGESERVER_TEST_REGRESS_TEST_H diff --git a/language_server/test/request_manager_test.cpp b/language_server/test/request_manager_test.cpp new file mode 100644 index 000000000..aa9c89622 --- /dev/null +++ b/language_server/test/request_manager_test.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Broadcom. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + */ + +#include +#include + +#include "gmock/gmock.h" + +#include "request_manager.h" +#include "server.h" + + +using namespace hlasm_plugin; +using namespace hlasm_plugin::language_server; +using namespace std::chrono_literals; + +class server_mock_rm : public server +{ +public: + server_mock_rm(std::atomic* cancel) + : server(ws_mngr_) + , cancel_(cancel) + { } + void message_received(const json&) override + { + ++messages_received; + for (size_t i = 0; i < 50; ++i) + { + if (*cancel_) + return; + std::this_thread::sleep_for(100ms); + } + } + + virtual void respond(const json&, const std::string&, const json&) override { } + virtual void notify(const std::string&, const json&) override { } + virtual void respond_error(const json&, const std::string&, int, const std::string&, const json&) override { } + + int messages_received = 0; + +private: + parser_library::workspace_manager ws_mngr_; + std::atomic* cancel_; +}; + +TEST(request_manager, finish_requests) +{ + std::atomic cancel = false; + request_manager rm(&cancel); + server_mock_rm s(&cancel); + server_mock_rm s2(&cancel); + + rm.add_request(&s, "0"_json); + rm.add_request(&s, "0"_json); + rm.add_request(&s, "0"_json); + + std::this_thread::sleep_for(100ms); + + rm.finish_server_requests(&s2); + EXPECT_TRUE(rm.is_running()); + + rm.finish_server_requests(&s); + + EXPECT_FALSE(rm.is_running()); + EXPECT_EQ(s.messages_received, 3); + + rm.finish_server_requests(&s); + + EXPECT_FALSE(rm.is_running()); + EXPECT_EQ(s.messages_received, 3); + + rm.end_worker(); +} diff --git a/language_server/test/response_provider_mock.h b/language_server/test/response_provider_mock.h index 46eecca88..dfe3833e0 100644 --- a/language_server/test/response_provider_mock.h +++ b/language_server/test/response_provider_mock.h @@ -16,7 +16,8 @@ #include "feature.h" -using namespace hlasm_plugin::language_server; +namespace hlasm_plugin::language_server +{ class response_provider_mock : public response_provider { @@ -29,3 +30,5 @@ class response_provider_mock : public response_provider const std::string& err_message, const json& error)); }; + +} diff --git a/language_server/test/stream_helper_test.cpp b/language_server/test/stream_helper_test.cpp new file mode 100644 index 000000000..eba60d5f6 --- /dev/null +++ b/language_server/test/stream_helper_test.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Broadcom. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Broadcom, Inc. - initial API and implementation + */ + +#include + +#include "gmock/gmock.h" + +#include "stream_helper.h" + +using namespace hlasm_plugin::language_server; + +TEST(stream_helper, stream_helper) +{ + std::stringstream ss("Spaces must not be treated\nas whitespaces and \n\r\n newlines must be \t treated as whitespaces."); + newline_is_space::imbue_stream(ss); + std::vector lines; + std::string line; + while (ss >> line) + { + lines.push_back(line); + } + std::vector expected = { + "Spaces must not be treated", + "as whitespaces and ", + "\r", + " newlines must be \t treated as whitespaces."}; + EXPECT_EQ(lines, expected); +}