From 9f1572184e165920635b93b01d5214f2a730479b Mon Sep 17 00:00:00 2001 From: metif12 Date: Sun, 4 Oct 2026 09:58:37 +0330 Subject: [PATCH] cgen: keep side effects out of `assert` conditions in stdio tests `-prod` removes assert statements whole, as documented in doc/docs.md and implemented in `vlib/v/gen/c/stmt.v`: ```v .assert_stmt { if g.is_prod { return } } ``` Two tests here used a side-effecting call *as* the assertion's condition: ```v assert os.fd_dup2(transport.read_fd, 0) >= 0 ``` That dup2 is the operation the test depends on, not a check on it. Under `-prod` the statement disappears, fd 0 keeps pointing at the real stdin, and the read that follows blocks on a descriptor nobody is going to write to. So: - `v -prod test lsp_test.v` never terminated (>40 min for a file that takes 25s) - `v -prod test integration_test.v` likewise Perform the call outside the assert, matching how the neighbouring code already does it (`os.fd_close(transport.write_fd)` a few lines above has no check either). V's behaviour here is correct and is not changed by this commit; the defect was in the tests. Verified on Windows with V 0137eb5: - `v -prod test lsp_test.v` -> OK, 53s (was hanging) - `v -prod test integration_test.v` -> OK, 62s (was hanging) - `v -prod test .` -> completes in 76s, 4/6; the two failures are the unrelated `index_max_file_bytes` compile error that #526 fixes. Applying that fix on top gives `6 passed, 6 total` under `-prod`. - Normal builds unchanged: `v test lsp_test.v` OK, and `integration_test.v` fails only on the pre-existing Sublime Text handshake bug that #527 fixes. Reported upstream as vlang/v#29426, where the `-prod` codegen evidence is included. That issue is closed as not-a-bug: the behaviour is documented and matches C's `assert` under `NDEBUG`. --- integration_test.v | 264 +++++++++++++++++++++++---------------------- lsp_test.v | 223 +++++++++++++++++++------------------- 2 files changed, 247 insertions(+), 240 deletions(-) diff --git a/integration_test.v b/integration_test.v index d3410bdf..9535ca54 100644 --- a/integration_test.v +++ b/integration_test.v @@ -59,9 +59,9 @@ fn create_integration_test_env() (&App, string) { integration_test_must_mkdir_all(project_dir) app := &App{ - text: '' + text: '' open_files: map[string]string{} - temp_dir: temp_dir + temp_dir: temp_dir } return app, project_dir } @@ -115,7 +115,9 @@ fn test_integration_stdio_initialize_completion_and_hover() { } os.fd_close(transport.write_fd) transport.write_fd = -1 - assert os.fd_dup2(transport.read_fd, 0) >= 0 + // Keep this outside `assert`: `-prod` removes assert statements whole, so a dup2 + // hidden in one never runs and the read below waits on the real stdin forever. + os.fd_dup2(transport.read_fd, 0) mut capture := os.stdio_capture() or { assert false, 'failed to capture stdio test output: ${err}' @@ -165,20 +167,20 @@ fn test_integration_stdio_initialize_completion_and_hover() { fn test_integration_initialize_capabilities() { // Simulate what the server returns for initialize response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } } @@ -199,7 +201,7 @@ fn test_integration_initialize_capabilities() { fn test_integration_initialize_response_structure() { // Verify response has proper JSON-RPC structure response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ definition_provider: true @@ -217,7 +219,7 @@ fn test_integration_initialize_response_structure() { fn test_integration_initialize_does_not_advertise_client_snippet_support() { response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ completion_provider: CompletionProvider{ @@ -233,14 +235,14 @@ fn test_integration_initialize_does_not_advertise_client_snippet_support() { fn test_integration_initialize_workspace_capabilities() { response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ execute_command_provider: ExecuteCommandOptions{ commands: ['vls.runFile', 'vls.runTests'] } - workspace: WorkspaceCapability{ - file_operations: WorkspaceFileOperations{ + workspace: WorkspaceCapability{ + file_operations: WorkspaceFileOperations{ will_create: FileOperationRegistrationOptions{ filters: [ FileOperationFilter{ @@ -270,7 +272,7 @@ fn test_integration_initialize_workspace_capabilities() { } } workspace_folders: WorkspaceFoldersServerCapability{ - supported: true + supported: true change_notifications: true } } @@ -301,10 +303,10 @@ fn test_integration_document_lifecycle() { // 1. Open document open_request := Request{ - id: 1 - method: 'textDocument/didOpen' + id: 1 + method: 'textDocument/didOpen' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } @@ -320,11 +322,11 @@ fn test_integration_document_lifecycle() { // 2. Change document new_content := "module main\n\nfn main() {\n\tprintln('world')\n}\n" change_request := Request{ - id: 2 - method: 'textDocument/didChange' + id: 2 + method: 'textDocument/didChange' jsonrpc: '2.0' - params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + params: json2.encode(Params{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -539,7 +541,7 @@ fn test_integration_diagnostics_syntax_error() { // Trigger change to get diagnostics change_request := Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -585,7 +587,7 @@ fn test_integration_diagnostics_valid_code() { change_request := Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -639,17 +641,17 @@ fn test_integration_diagnostics_deduplication() { errors := [ JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'first error' }, JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'duplicate error' }, // Same position JsonError{ line_nr: 6 - col: 1 + col: 1 message: 'different position' }, ] @@ -691,7 +693,7 @@ fn test_integration_diagnostics_empty_file() { // Empty content should be processed and return diagnostics for the empty file result := app.on_did_change(Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -736,14 +738,14 @@ fn test_integration_completion_request() { // Request completion at the position after "os." request := Request{ - id: 1 - method: 'textDocument/completion' + id: 1 + method: 'textDocument/completion' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 4 } // After "os." @@ -774,12 +776,12 @@ fn test_integration_completion_request_id_preserved() { // Test with various IDs for id in [1, 42, 100, 999] { request := Request{ - id: id + id: id params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 2 char: 0 } @@ -807,12 +809,12 @@ fn test_integration_completion_at_function_call() { app.open_files[uri] = content request := Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 9 } @@ -851,14 +853,14 @@ fn test_integration_definition_request() { // Request definition at the call site of helper() request := Request{ - id: 2 - method: 'textDocument/definition' + id: 2 + method: 'textDocument/definition' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } // At "helper()" @@ -920,12 +922,12 @@ fn test_integration_definition_multifile() { // Request definition from main file request := Request{ - id: 3 + id: 3 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 2 } @@ -973,13 +975,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { app.text = open_content definition := app.operation_at_pos(.definition, Request{ - id: 31 + id: 31 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 20 } @@ -993,13 +995,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert definition_location.range.start.line == 2 hover := app.operation_at_pos(.hover, Request{ - id: 32 + id: 32 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 20 } @@ -1011,13 +1013,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert (hover.result as Hover).contents.value.contains('answer') signature := app.operation_at_pos(.signature_help, Request{ - id: 33 + id: 33 method: 'textDocument/signatureHelp' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 25 } @@ -1030,13 +1032,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert signature_help.signatures.any(it.label.contains('answer')) completion := app.operation_at_pos(.completion, Request{ - id: 34 + id: 34 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 18 } @@ -1070,17 +1072,17 @@ fn (a VlangVCallAnchor) decl_path(root string) string { // vlib/v, `builder.compile` before that. const vlang_v_module_call_anchors = [ VlangVCallAnchor{ - call: 'driver.run(' + call: 'driver.run(' qualifier: 'driver.' - name: 'run' - decl_rel: 'vlib/v/driver/driver.v' + name: 'run' + decl_rel: 'vlib/v/driver/driver.v' decl_text: 'fn run(' }, VlangVCallAnchor{ - call: "builder.compile('build'" + call: "builder.compile('build'" qualifier: 'builder.' - name: 'compile' - decl_rel: 'vlib/v/builder/compile.v' + name: 'compile' + decl_rel: 'vlib/v/builder/compile.v' decl_text: 'fn compile(' }, ] @@ -1088,17 +1090,17 @@ const vlang_v_module_call_anchors = [ // Method calls on a local variable, for receiver-type inference. const vlang_v_method_call_anchors = [ VlangVCallAnchor{ - call: 'process.set_args(' + call: 'process.set_args(' qualifier: 'process.' - name: 'set_args' - decl_rel: 'vlib/os/process.v' + name: 'set_args' + decl_rel: 'vlib/os/process.v' decl_text: 'fn (mut p Process) set_args(' }, VlangVCallAnchor{ - call: "timers.show('v start'" + call: "timers.show('v start'" qualifier: 'timers.' - name: 'show' - decl_rel: 'vlib/v/util/timers.v' + name: 'show' + decl_rel: 'vlib/v/util/timers.v' decl_text: 'fn (mut t Timers) show(' }, ] @@ -1171,12 +1173,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { for i, method in [Method.definition, .declaration, .type_definition, .implementation] { response := app.operation_at_pos(method, Request{ - id: 40 + i + id: 40 + i params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: compile_col + 2 } @@ -1191,12 +1193,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { } hover := app.operation_at_pos(.hover, Request{ - id: 44 + id: 44 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: compile_col + 2 } @@ -1211,12 +1213,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { open_paren_col := lines[call_line].index(call_text) or { -1 } assert open_paren_col >= 0 signature := app.operation_at_pos(.signature_help, Request{ - id: 45 + id: 45 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: open_paren_col + call_text.len } @@ -1235,12 +1237,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { app.open_files[main_uri] = completion_content app.text = completion_content completion := app.operation_at_pos(.completion, Request{ - id: 46 + id: 46 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: dot_col + anchor.qualifier.len } @@ -1288,13 +1290,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( app.workspace_roots = [root] module_completion := app.operation_at_pos(.completion, Request{ - id: 47 + id: 47 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: compile_line char: compile_dot_col } @@ -1306,13 +1308,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( assert (module_completion.result as CompletionList).items.any(it.label == module_anchor.name) receiver_completion := app.operation_at_pos(.completion, Request{ - id: 48 + id: 48 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: timer_line char: timer_dot_col } @@ -1324,13 +1326,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( assert (receiver_completion.result as CompletionList).items.any(it.label == method_anchor.name) definition := app.operation_at_pos(.definition, Request{ - id: 49 + id: 49 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: timer_line char: show_col + 2 } @@ -1374,14 +1376,14 @@ fn test_integration_signature_help_request() { // Request signature help after opening paren request := Request{ - id: 3 - method: 'textDocument/signatureHelp' + id: 3 + method: 'textDocument/signatureHelp' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 7 } // After "greet(" @@ -1415,12 +1417,12 @@ fn test_integration_signature_help_with_params() { // At second parameter position request := Request{ - id: 4 + id: 4 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 7 } @@ -1584,7 +1586,7 @@ fn test_integration_json_error_with_special_chars() { fn test_integration_response_encoding() { response := Response{ - id: 42 + id: 42 result: 'null' } @@ -1600,20 +1602,20 @@ fn test_integration_notification_encoding() { notification := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 5 } } - message: 'test error' + message: 'test error' severity: 1 }, ] @@ -1663,19 +1665,19 @@ fn test_integration_begin_progress_with_client_support_emits_create_and_begin() fn test_integration_completion_response_encoding() { details := [ Detail{ - kind: 6 - label: 'println' + kind: 6 + label: 'println' detail: 'fn println(s string)' }, Detail{ - kind: 6 - label: 'print' + kind: 6 + label: 'print' detail: 'fn print(s string)' }, ] response := Response{ - id: 1 + id: 1 result: details } @@ -1686,15 +1688,15 @@ fn test_integration_completion_response_encoding() { fn test_integration_location_response_encoding() { response := Response{ - id: 1 + id: 1 result: Location{ - uri: 'file:///test/main.v' + uri: 'file:///test/main.v' range: LSPRange{ start: Position{ line: 10 char: 5 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -1709,11 +1711,11 @@ fn test_integration_location_response_encoding() { fn test_integration_signature_help_response_encoding() { response := Response{ - id: 1 + id: 1 result: SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: 'fn test(a int, b string)' + label: 'fn test(a int, b string)' parameters: [ ParameterInformation{ label: 'a int' @@ -1752,13 +1754,13 @@ fn test_integration_request_id_preserved() { // Test with different request IDs for id in [1, 42, 999, 0] { request := Request{ - id: id + id: id method: 'textDocument/completion' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 2 char: 0 } @@ -1871,17 +1873,17 @@ fn test_integration_full_lifecycle() { // 2. Simulate initialize (verify capabilities) caps := Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } assert caps.capabilities.definition_provider == true @@ -1902,7 +1904,7 @@ fn test_integration_full_lifecycle() { modified_content := 'module main\n\nfn helper() {}\n\nfn main() {\n\thelper()\n}\n' app.on_did_change(Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -1916,12 +1918,12 @@ fn test_integration_full_lifecycle() { // 5. Request completion comp_response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -1933,12 +1935,12 @@ fn test_integration_full_lifecycle() { // 6. Request definition def_response := app.operation_at_pos(.definition, Request{ - id: 2 + id: 2 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -1955,7 +1957,7 @@ fn test_integration_full_lifecycle() { fn test_integration_shutdown_response() { // Verify shutdown response structure shutdown_resp := Response{ - id: 1 + id: 1 result: 'null' } @@ -2429,12 +2431,12 @@ fn test_integration_completion_includes_sibling_pub_fn() { // Request completion at `helper` on line 3, col 1 (not after '.') response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 1 } @@ -2475,12 +2477,12 @@ fn test_integration_completion_includes_private_sibling_fn() { app.text = main_content response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 2 } @@ -2514,12 +2516,12 @@ fn test_integration_completion_includes_current_file_fns() { app.text = content response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 // inside fn main, after `he` char: 2 } @@ -2614,13 +2616,13 @@ fn test_integration_prepare_rename_returns_symbol_range() { app.open_files[uri] = content response := app.handle_prepare_rename(Request{ - id: 301 + id: 301 method: 'textDocument/prepareRename' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 4 char: 12 } @@ -2649,7 +2651,7 @@ fn test_integration_workspace_symbol_query_matches() { app.open_files[uri] = content response := app.handle_workspace_symbol(Request{ - id: 302 + id: 302 method: 'workspace/symbol' params: json2.encode(WorkspaceSymbolParams{ query: 'name' @@ -2682,7 +2684,7 @@ fn test_integration_workspace_symbol_indexes_loose_module_sibling() { assert find_project_root(project_dir) == '' response := app.handle_workspace_symbol(Request{ - id: 303 + id: 303 method: 'workspace/symbol' params: json2.encode(WorkspaceSymbolParams{ query: 'unopened_loose' @@ -2714,13 +2716,13 @@ fn test_integration_alias_navigation_methods_preserve_id() { mut request_id := 410 for m in methods { resp := app.operation_at_pos(m, Request{ - id: request_id + id: request_id method: m.str() params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -2735,17 +2737,17 @@ fn test_integration_alias_navigation_methods_preserve_id() { fn test_integration_capability_flags_for_new_features() { caps := Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 2 - save: SaveOptions{ + change: 2 + save: SaveOptions{ include_text: true } } - declaration_provider: true - type_definition_provider: true - implementation_provider: true - rename_provider: RenameOptions{ + declaration_provider: true + type_definition_provider: true + implementation_provider: true + rename_provider: RenameOptions{ prepare_provider: true } workspace_symbol_provider: true @@ -2795,8 +2797,10 @@ fn test_integration_sublime_text_lsp_handshake() { root_uri := path_to_uri(project_dir) initialize := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":42,"clientInfo":{"name":"Sublime Text LSP","version":"2.13.0"},"locale":"en","rootUri":"${root_uri}","rootPath":"${project_dir}","workspaceFolders":[{"uri":"${root_uri}","name":"test_project"}],"capabilities":{"general":{"positionEncodings":["utf-16"]},"workspace":{"workspaceFolders":true,"configuration":true,"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":true}},"textDocument":{"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"completionItem":{"snippetSupport":true,"documentationFormat":["markdown","plaintext"]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"publishDiagnostics":{"versionSupport":true}},"window":{"workDoneProgress":true}},"initializationOptions":{}}}' initialized := '{"jsonrpc":"2.0","method":"initialized","params":{}}' - output := integration_run_frames(mut app, project_dir, 'sublime_handshake', [initialize, - initialized]) + output := integration_run_frames(mut app, project_dir, 'sublime_handshake', [ + initialize, + initialized, + ]) assert app.received_initialize assert app.workspace_roots == [project_dir] diff --git a/lsp_test.v b/lsp_test.v index 0949ec75..abd19455 100644 --- a/lsp_test.v +++ b/lsp_test.v @@ -19,7 +19,11 @@ fn test_stdio_reader_processes_frame_before_eof() { transport.close() } - assert os.fd_dup2(transport.read_fd, 0) >= 0 + // This dup2 is the operation the test depends on, not a check on it, so it + // must not live inside `assert`: `-prod` removes assert statements whole, + // which left fd 0 pointing at the real stdin and made the read below block + // forever instead of failing. + os.fd_dup2(transport.read_fd, 0) payload := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' frame := 'Content-Length: ${payload.len}\r\n\r\n${payload}' transport.write(frame.bytes()) or { @@ -242,9 +246,8 @@ fn test_method_roundtrip_all_values() { .workspace_did_change_configuration, .workspace_did_change_workspace_folders, .document_highlight, .selection_range, .semantic_tokens_range, .range_formatting, .will_save, .will_save_wait_until, .did_change_watched_files, .code_lens, .code_lens_resolve, - .execute_command, .inline_value, .linked_editing_range, .will_create_files, - .will_rename_files, .will_delete_files, .on_type_formatting, .set_trace, .cancel_request, - .shutdown, .exit] + .execute_command, .inline_value, .linked_editing_range, .will_create_files, .will_rename_files, + .will_delete_files, .on_type_formatting, .set_trace, .cancel_request, .shutdown, .exit] for m in methods { assert Method.from_string(m.str()) == m } @@ -472,7 +475,7 @@ fn test_lsp_range_same_line() { line: 5 char: 10 } - end: Position{ + end: Position{ line: 5 char: 20 } @@ -487,7 +490,7 @@ fn test_lsp_range_multi_line() { line: 5 char: 0 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -501,7 +504,7 @@ fn test_lsp_range_json_encoding() { line: 1 char: 2 } - end: Position{ + end: Position{ line: 3 char: 4 } @@ -593,8 +596,8 @@ fn test_request_default_values() { fn test_request_with_values() { req := Request{ - id: 1 - method: 'textDocument/completion' + id: 1 + method: 'textDocument/completion' jsonrpc: '2.0' } assert req.id == 1 @@ -644,7 +647,7 @@ fn test_request_json_decoding_initialize() { fn test_response_default_jsonrpc() { resp := Response{ - id: 1 + id: 1 result: 'null' } assert resp.jsonrpc == '2.0' @@ -652,7 +655,7 @@ fn test_response_default_jsonrpc() { fn test_response_json_encoding() { resp := Response{ - id: 42 + id: 42 result: 'null' } encoded := json2.encode(resp, escape_unicode: true) @@ -662,7 +665,7 @@ fn test_response_json_encoding() { fn test_encode_response_payload_uses_json_null_for_null_result() { resp := Response{ - id: 2 + id: 2 result: 'null' } encoded := encode_response_payload(resp) @@ -672,7 +675,7 @@ fn test_encode_response_payload_uses_json_null_for_null_result() { fn test_encode_response_payload_preserves_non_null_results() { resp := Response{ - id: 3 + id: 3 result: []TextEdit{} } encoded := encode_response_payload(resp) @@ -681,7 +684,7 @@ fn test_encode_response_payload_preserves_non_null_results() { fn test_encode_response_payload_strips_sum_type_tag_from_capabilities() { resp := Response{ - id: 4 + id: 4 result: Capabilities{ capabilities: Capability{ definition_provider: true @@ -695,14 +698,14 @@ fn test_encode_response_payload_strips_sum_type_tag_from_capabilities() { fn test_encode_response_payload_strips_sum_type_tag_from_prepare_rename() { resp := Response{ - id: 5 + id: 5 result: PrepareRenameResult{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 1 char: 2 } - end: Position{ + end: Position{ line: 1 char: 7 } @@ -717,15 +720,15 @@ fn test_encode_response_payload_strips_sum_type_tag_from_prepare_rename() { fn test_workspace_edit_closed_document_has_explicit_null_version() { resp := Response{ - id: 6 + id: 6 result: WorkspaceEdit{ document_changes: [TextDocumentEdit{ text_document: OptionalVersionedTextDocumentIdentifier{ - uri: 'file:///tmp/closed.v' + uri: 'file:///tmp/closed.v' version: json2.null } - edits: [TextEdit{ - range: LSPRange{} + edits: [TextEdit{ + range: LSPRange{} new_text: 'renamed' }] }] @@ -924,7 +927,7 @@ fn test_notification_json_encoding() { notif := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [] } } @@ -935,8 +938,8 @@ fn test_notification_json_encoding() { fn test_lsp_diagnostic_error_severity() { diag := LSPDiagnostic{ - range: LSPRange{} - message: 'error message' + range: LSPRange{} + message: 'error message' severity: 1 } assert diag.severity == 1 // Error @@ -945,8 +948,8 @@ fn test_lsp_diagnostic_error_severity() { fn test_lsp_diagnostic_warning_severity() { diag := LSPDiagnostic{ - range: LSPRange{} - message: 'warning message' + range: LSPRange{} + message: 'warning message' severity: 2 } assert diag.severity == 2 // Warning @@ -954,17 +957,17 @@ fn test_lsp_diagnostic_warning_severity() { fn test_lsp_diagnostic_json_encoding() { diag := LSPDiagnostic{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 5 char: 0 } - end: Position{ + end: Position{ line: 5 char: 10 } } - message: 'undefined identifier' + message: 'undefined identifier' severity: 1 } encoded := json2.encode(diag, escape_unicode: true) @@ -974,9 +977,9 @@ fn test_lsp_diagnostic_json_encoding() { fn test_detail_function_kind() { detail := Detail{ - kind: 6 // Function - label: 'my_function' - detail: 'fn my_function() string' + kind: 6 // Function + label: 'my_function' + detail: 'fn my_function() string' documentation: 'A helper function' } assert detail.kind == 6 @@ -985,9 +988,9 @@ fn test_detail_function_kind() { fn test_detail_variable_kind() { detail := Detail{ - kind: 6 - label: 'my_var' - detail: 'int' + kind: 6 + label: 'my_var' + detail: 'int' documentation: 'A variable' } assert detail.label == 'my_var' @@ -995,10 +998,10 @@ fn test_detail_variable_kind() { fn test_detail_with_snippet() { detail := Detail{ - kind: 6 - label: 'println' - detail: 'fn println(s string)' - insert_text: 'println(\${1:s})' + kind: 6 + label: 'println' + detail: 'fn println(s string)' + insert_text: 'println(\${1:s})' insert_text_format: 2 // Snippet } assert detail.insert_text? == 'println(\${1:s})' @@ -1007,7 +1010,7 @@ fn test_detail_with_snippet() { fn test_detail_json_encoding() { detail := Detail{ - kind: 6 + kind: 6 label: 'test_fn' } encoded := json2.encode(detail, escape_unicode: true) @@ -1017,13 +1020,13 @@ fn test_detail_json_encoding() { fn test_location_basic() { loc := Location{ - uri: 'file:///test/file.v' + uri: 'file:///test/file.v' range: LSPRange{ start: Position{ line: 10 char: 5 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -1035,13 +1038,13 @@ fn test_location_basic() { fn test_location_json_encoding() { loc := Location{ - uri: 'file:///path/to/file.v' + uri: 'file:///path/to/file.v' range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 5 } @@ -1061,9 +1064,9 @@ fn test_signature_help_empty() { fn test_signature_help_with_signature() { sig := SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: 'fn test(a int, b string)' + label: 'fn test(a int, b string)' parameters: [ ParameterInformation{ label: 'a int' @@ -1084,7 +1087,7 @@ fn test_signature_help_with_signature() { fn test_signature_help_json_encoding() { sig := SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ label: 'fn example()' }, @@ -1100,17 +1103,17 @@ fn test_signature_help_json_encoding() { fn test_capabilities_full() { caps := Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } assert caps.capabilities.definition_provider == true @@ -1147,7 +1150,7 @@ fn test_completion_item_capability_snippet_support() { fn test_text_document_sync_full() { sync := TextDocumentSyncOptions{ open_close: true - change: 1 // Full + change: 1 // Full } assert sync.open_close == true assert sync.change == 1 @@ -1156,7 +1159,7 @@ fn test_text_document_sync_full() { fn test_text_document_sync_incremental() { sync := TextDocumentSyncOptions{ open_close: true - change: 2 // Incremental + change: 2 // Incremental } assert sync.change == 2 } @@ -1181,7 +1184,7 @@ fn test_response_result_string() { fn test_response_result_details() { details := [ Detail{ - kind: 6 + kind: 6 label: 'test' }, ] @@ -1234,7 +1237,7 @@ fn test_response_result_location() { fn test_publish_diagnostics_params_empty() { params := PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [] } assert params.uri == 'file:///test.v' @@ -1243,16 +1246,16 @@ fn test_publish_diagnostics_params_empty() { fn test_publish_diagnostics_params_with_diagnostics() { params := PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{} - message: 'error 1' + range: LSPRange{} + message: 'error 1' severity: 1 }, LSPDiagnostic{ - range: LSPRange{} - message: 'error 2' + range: LSPRange{} + message: 'error 2' severity: 1 }, ] @@ -1262,11 +1265,11 @@ fn test_publish_diagnostics_params_with_diagnostics() { fn test_json_error_struct() { err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'undefined identifier' line_nr: 10 - col: 5 - len: 3 + col: 5 + len: 3 } assert err.path == '/test/file.v' assert err.message == 'undefined identifier' @@ -1305,11 +1308,11 @@ fn test_json_var_ac_with_details() { ac := JsonVarAC{ details: [ Detail{ - kind: 6 + kind: 6 label: 'fn1' }, Detail{ - kind: 6 + kind: 6 label: 'fn2' }, ] @@ -1340,14 +1343,14 @@ fn test_document_symbol_default_values() { fn test_document_symbol_with_values() { sym := DocumentSymbol{ - name: 'greet' - kind: sym_kind_function - range: LSPRange{ + name: 'greet' + kind: sym_kind_function + range: LSPRange{ start: Position{ line: 2 char: 0 } - end: Position{ + end: Position{ line: 2 char: 20 } @@ -1357,12 +1360,12 @@ fn test_document_symbol_with_values() { line: 2 char: 3 } - end: Position{ + end: Position{ line: 2 char: 8 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} } assert sym.name == 'greet' assert sym.kind == sym_kind_function @@ -1372,14 +1375,14 @@ fn test_document_symbol_with_values() { fn test_document_symbol_json_encoding() { sym := DocumentSymbol{ - name: 'Person' - kind: sym_kind_struct - range: LSPRange{ + name: 'Person' + kind: sym_kind_struct + range: LSPRange{ start: Position{ line: 5 char: 0 } - end: Position{ + end: Position{ line: 5 char: 14 } @@ -1389,12 +1392,12 @@ fn test_document_symbol_json_encoding() { line: 5 char: 7 } - end: Position{ + end: Position{ line: 5 char: 13 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} } encoded := json2.encode(sym, escape_unicode: true) assert encoded.contains('"name":"Person"') @@ -1417,17 +1420,17 @@ fn test_document_symbol_json_decoding() { fn test_document_symbol_with_children() { sym := DocumentSymbol{ - name: 'App' - kind: sym_kind_struct - range: LSPRange{} + name: 'App' + kind: sym_kind_struct + range: LSPRange{} selection_range: LSPRange{} - children: [ + children: [ DocumentSymbol{ - name: 'run' - kind: sym_kind_method - range: LSPRange{} + name: 'run' + kind: sym_kind_method + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] } @@ -1508,10 +1511,10 @@ fn test_method_roundtrip_new_methods() { fn test_response_result_workspace_symbols() { result := ResponseResult([ WorkspaceSymbol{ - name: 'main' - kind: sym_kind_function + name: 'main' + kind: sym_kind_function location: Location{ - uri: 'file:///tmp/main.v' + uri: 'file:///tmp/main.v' range: LSPRange{} } }, @@ -1526,12 +1529,12 @@ fn test_response_result_workspace_symbols() { fn test_response_result_prepare_rename_result() { result := ResponseResult(PrepareRenameResult{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 1 char: 2 } - end: Position{ + end: Position{ line: 1 char: 5 } @@ -1559,18 +1562,18 @@ fn test_response_result_document_symbols_empty() { fn test_response_result_document_symbols_with_data() { syms := [ DocumentSymbol{ - name: 'main' - kind: sym_kind_function - range: LSPRange{} + name: 'main' + kind: sym_kind_function + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, DocumentSymbol{ - name: 'App' - kind: sym_kind_struct - range: LSPRange{} + name: 'App' + kind: sym_kind_struct + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] result := ResponseResult(syms) @@ -1588,14 +1591,14 @@ fn test_response_result_document_symbols_with_data() { fn test_response_with_document_symbols_json_encoding() { syms := [ DocumentSymbol{ - name: 'greet' - kind: sym_kind_function - range: LSPRange{ + name: 'greet' + kind: sym_kind_function + range: LSPRange{ start: Position{ line: 2 char: 0 } - end: Position{ + end: Position{ line: 2 char: 25 } @@ -1605,16 +1608,16 @@ fn test_response_with_document_symbols_json_encoding() { line: 2 char: 3 } - end: Position{ + end: Position{ line: 2 char: 8 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] resp := Response{ - id: 7 + id: 7 result: syms } encoded := json2.encode(resp, escape_unicode: true) @@ -1640,7 +1643,7 @@ fn test_capability_document_symbol_provider_json_encoding() { caps := Capabilities{ capabilities: Capability{ document_symbol_provider: true - definition_provider: true + definition_provider: true } } encoded := json2.encode(caps, escape_unicode: true) @@ -1802,13 +1805,13 @@ fn test_encode_response_payload_strips_type_from_array_variant() { // Array-of-struct result variants (e.g. []WorkspaceSymbol) must also have // their per-element `_type` discriminators stripped (P1-10). resp := Response{ - id: 9 + id: 9 result: [ WorkspaceSymbol{ - name: 'helper_fn' - kind: sym_kind_function + name: 'helper_fn' + kind: sym_kind_function location: Location{ - uri: 'file:///tmp/lib.v' + uri: 'file:///tmp/lib.v' range: LSPRange{} } },