Merge pull request #570 from Techatrix/session-tests
Revive Session tests
This commit is contained in:
commit
6ed5ba833b
@ -6,7 +6,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
const mode = b.standardReleaseOptions();
|
||||
const exe = b.addExecutable("zls", "src/Server.zig");
|
||||
const exe = b.addExecutable("zls", "src/main.zig");
|
||||
const exe_options = b.addOptions();
|
||||
exe.addOptions("build_options", exe_options);
|
||||
|
||||
@ -82,6 +82,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
|
||||
var session_tests = b.addTest("tests/sessions.zig");
|
||||
session_tests.addPackage(.{ .name = "header", .source = .{ .path = "src/header.zig" } });
|
||||
session_tests.addPackage(.{ .name = "server", .source = .{ .path = "src/Server.zig" }, .dependencies = exe.packages.items });
|
||||
session_tests.setBuildMode(.Debug);
|
||||
session_tests.setTarget(target);
|
||||
test_step.dependOn(&session_tests.step);
|
||||
|
@ -54,7 +54,7 @@ allocator: std.mem.Allocator,
|
||||
handles: UriToHandleMap = .{},
|
||||
build_files: BuildFileList = .{},
|
||||
|
||||
config: *const Config,
|
||||
config: Config,
|
||||
std_uri: ?[]const u8,
|
||||
// TODO make this configurable
|
||||
// We can't figure it out ourselves since we don't know what arguments
|
||||
@ -66,7 +66,7 @@ zig_global_cache_root: []const u8 = "ZLS_DONT_CARE",
|
||||
|
||||
pub fn init(
|
||||
allocator: std.mem.Allocator,
|
||||
config: *const Config,
|
||||
config: Config,
|
||||
) !DocumentStore {
|
||||
return DocumentStore{
|
||||
.allocator = allocator,
|
||||
|
821
src/Server.zig
821
src/Server.zig
File diff suppressed because it is too large
Load Diff
142
src/main.zig
Normal file
142
src/main.zig
Normal file
@ -0,0 +1,142 @@
|
||||
const std = @import("std");
|
||||
const zig_builtin = @import("builtin");
|
||||
const build_options = @import("build_options");
|
||||
const tracy = @import("tracy.zig");
|
||||
const known_folders = @import("known-folders");
|
||||
const Config = @import("Config.zig");
|
||||
const Server = @import("Server.zig");
|
||||
const setup = @import("setup.zig");
|
||||
const readRequestHeader = @import("header.zig").readRequestHeader;
|
||||
|
||||
const logger = std.log.scoped(.main);
|
||||
|
||||
// Always set this to debug to make std.log call into our handler, then control the runtime
|
||||
// value in the definition below.
|
||||
pub const log_level = .debug;
|
||||
|
||||
var actual_log_level: std.log.Level = switch (zig_builtin.mode) {
|
||||
.Debug => .debug,
|
||||
else => @intToEnum(std.log.Level, @enumToInt(build_options.log_level)), // temporary fix to build failing on release-safe due to a Zig bug
|
||||
};
|
||||
|
||||
fn loop(server: *Server) !void {
|
||||
var reader = std.io.getStdIn().reader();
|
||||
|
||||
while (server.keep_running) {
|
||||
const headers = readRequestHeader(server.allocator, reader) catch |err| {
|
||||
logger.err("{s}; exiting!", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
const buffer = try server.allocator.alloc(u8, headers.content_length);
|
||||
try reader.readNoEof(buffer);
|
||||
|
||||
var writer = std.io.getStdOut().writer();
|
||||
|
||||
try server.processJsonRpc(writer, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
const ConfigWithPath = struct {
|
||||
config: Config,
|
||||
config_path: ?[]const u8,
|
||||
};
|
||||
|
||||
fn getConfig(allocator: std.mem.Allocator, config_path: ?[]const u8) !?ConfigWithPath {
|
||||
if (config_path) |path| {
|
||||
if (Config.loadFromFile(allocator, path)) |conf| {
|
||||
return ConfigWithPath{
|
||||
.config = conf,
|
||||
.config_path = path,
|
||||
};
|
||||
}
|
||||
std.debug.print(
|
||||
\\Could not open configuration file '{s}'
|
||||
\\Falling back to a lookup in the local and global configuration folders
|
||||
\\
|
||||
, .{path});
|
||||
}
|
||||
|
||||
if (try known_folders.getPath(allocator, .local_configuration)) |path| {
|
||||
if (Config.loadFromFolder(allocator, path)) |conf| {
|
||||
return ConfigWithPath{
|
||||
.config = conf,
|
||||
.config_path = path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (try known_folders.getPath(allocator, .global_configuration)) |path| {
|
||||
if (Config.loadFromFolder(allocator, path)) |conf| {
|
||||
return ConfigWithPath{
|
||||
.config = conf,
|
||||
.config_path = path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const stack_frames = switch (zig_builtin.mode) {
|
||||
.Debug => 10,
|
||||
else => 0,
|
||||
};
|
||||
|
||||
pub fn main() anyerror!void {
|
||||
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){};
|
||||
defer _ = gpa_state.deinit();
|
||||
|
||||
var allocator = gpa_state.allocator();
|
||||
if (tracy.enable_allocation) {
|
||||
allocator = tracy.tracyAllocator(allocator).allocator();
|
||||
}
|
||||
|
||||
var config_path: ?[]const u8 = null;
|
||||
defer if (config_path) |path| allocator.free(path);
|
||||
|
||||
// Check arguments.
|
||||
var args_it = try std.process.ArgIterator.initWithAllocator(allocator);
|
||||
defer args_it.deinit();
|
||||
if (!args_it.skip()) @panic("Could not find self argument");
|
||||
|
||||
while (args_it.next()) |arg| {
|
||||
// TODO add --help --version
|
||||
if (std.mem.eql(u8, arg, "--debug-log")) {
|
||||
actual_log_level = .debug;
|
||||
std.debug.print("Enabled debug logging\n", .{});
|
||||
} else if (std.mem.eql(u8, arg, "--config-path")) {
|
||||
var path = args_it.next() orelse {
|
||||
std.debug.print("Expected configuration file path after --config-path argument\n", .{});
|
||||
std.os.exit(1);
|
||||
};
|
||||
config_path = try allocator.dupe(u8, path);
|
||||
} else if (std.mem.eql(u8, arg, "config") or std.mem.eql(u8, arg, "configure")) {
|
||||
try setup.wizard(allocator);
|
||||
return;
|
||||
} else {
|
||||
std.debug.print("Unrecognized argument {s}\n", .{arg});
|
||||
std.os.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
var new_config = blk: {
|
||||
if (try getConfig(allocator, config_path)) |config| {
|
||||
break :blk config;
|
||||
}
|
||||
logger.info("No config file zls.json found.", .{});
|
||||
break :blk ConfigWithPath{
|
||||
.config = Config{},
|
||||
.config_path = null,
|
||||
};
|
||||
};
|
||||
|
||||
var server = try Server.init(
|
||||
allocator,
|
||||
new_config.config,
|
||||
new_config.config_path,
|
||||
actual_log_level,
|
||||
);
|
||||
defer server.deinit();
|
||||
|
||||
try loop(&server);
|
||||
}
|
87
tests/context.zig
Normal file
87
tests/context.zig
Normal file
@ -0,0 +1,87 @@
|
||||
const std = @import("std");
|
||||
const headerPkg = @import("header");
|
||||
const Server = @import("server");
|
||||
|
||||
const initialize_msg =
|
||||
\\{"processId":6896,"clientInfo":{"name":"vscode","version":"1.46.1"},"rootPath":null,"rootUri":null,"capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional"},"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]}},"executeCommand":{"dynamicRegistration":true},"configuration":true,"workspaceFolders":true},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"complexDiagnosticCodeSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true},"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true}},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]}},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}}},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["comment","keyword","number","regexp","operator","namespace","type","struct","class","interface","enum","typeParameter","function","member","macro","variable","parameter","property","label"],"tokenModifiers":["declaration","documentation","static","abstract","deprecated","readonly"]}},"window":{"workDoneProgress":true}},"trace":"off","workspaceFolders":[{"uri":"file://./tests", "name":"root"}]}
|
||||
;
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
pub const Context = struct {
|
||||
server: Server,
|
||||
request_id: u32 = 1,
|
||||
|
||||
pub fn init() !Context {
|
||||
var context = Context{
|
||||
.server = try Server.init(
|
||||
allocator,
|
||||
.{},
|
||||
null,
|
||||
.debug,
|
||||
),
|
||||
};
|
||||
|
||||
try context.request("initialize", initialize_msg, null);
|
||||
try context.request("initialized", "{}", null);
|
||||
return context;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Context) void {
|
||||
self.request("shutdown", "{}", null) catch {};
|
||||
self.server.deinit();
|
||||
}
|
||||
|
||||
pub fn request(
|
||||
self: *Context,
|
||||
method: []const u8,
|
||||
params: []const u8,
|
||||
expect: ?[]const u8,
|
||||
) !void {
|
||||
var output = std.ArrayList(u8).init(allocator);
|
||||
defer output.deinit();
|
||||
|
||||
// create the request
|
||||
self.request_id += 1;
|
||||
const req = try std.fmt.allocPrint(allocator,
|
||||
\\{{"jsonrpc":"2.0","id":{},"method":"{s}","params":{s}}}
|
||||
, .{ self.request_id, method, params });
|
||||
defer allocator.free(req);
|
||||
|
||||
// send the request to the server
|
||||
try self.server.processJsonRpc(output.writer(), req);
|
||||
|
||||
// if we don't expect a response ignore it
|
||||
const expected = expect orelse return;
|
||||
|
||||
// get the output from the server
|
||||
var buffer_stream = std.io.fixedBufferStream(output.items);
|
||||
const header = try headerPkg.readRequestHeader(allocator, buffer_stream.reader());
|
||||
defer header.deinit(allocator);
|
||||
|
||||
var response_bytes = try allocator.alloc(u8, header.content_length);
|
||||
defer allocator.free(response_bytes);
|
||||
|
||||
const content_length = try buffer_stream.reader().readAll(response_bytes);
|
||||
try std.testing.expectEqual(content_length, header.content_length);
|
||||
|
||||
// parse the response
|
||||
var parser = std.json.Parser.init(allocator, false);
|
||||
defer parser.deinit();
|
||||
|
||||
var tree = try parser.parse(response_bytes);
|
||||
defer tree.deinit();
|
||||
|
||||
const response = tree.root.Object;
|
||||
|
||||
// assertions
|
||||
try std.testing.expectEqualStrings("2.0", response.get("jsonrpc").?.String);
|
||||
try std.testing.expectEqual(self.request_id, @intCast(u32, response.get("id").?.Integer));
|
||||
try std.testing.expect(!response.contains("error"));
|
||||
|
||||
const result_json = try std.json.stringifyAlloc(allocator, response.get("result").?, .{});
|
||||
defer allocator.free(result_json);
|
||||
|
||||
try std.testing.expectEqualStrings(expected, result_json);
|
||||
}
|
||||
};
|
@ -1 +1,120 @@
|
||||
// TODO Rewrite tests; the old session tests were extremely broken and unhelpful
|
||||
const std = @import("std");
|
||||
const Context = @import("context.zig").Context;
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
test "Open file, ask for semantic tokens" {
|
||||
var ctx = try Context.init();
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.request("textDocument/didOpen",
|
||||
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");"}}
|
||||
, null);
|
||||
|
||||
try ctx.request("textDocument/semanticTokens/full",
|
||||
\\{"textDocument":{"uri":"file:///test.zig"}}
|
||||
,
|
||||
\\{"data":[0,0,5,7,0,0,6,3,2,32,0,4,1,11,0,0,2,7,12,0,0,8,5,9,0]}
|
||||
);
|
||||
}
|
||||
|
||||
test "Request completion in an empty file" {
|
||||
var ctx = try Context.init();
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.request("textDocument/didOpen",
|
||||
\\{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":""}}}
|
||||
, null);
|
||||
try ctx.request("textDocument/completion",
|
||||
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":0,"character":0}}
|
||||
, null);
|
||||
}
|
||||
|
||||
test "Request completion with no trailing whitespace" {
|
||||
var ctx = try Context.init();
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.request("textDocument/didOpen",
|
||||
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const std = @import(\"std\");\nc"}}
|
||||
, null);
|
||||
|
||||
try ctx.request("textDocument/completion",
|
||||
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
|
||||
,
|
||||
\\{"isIncomplete":false,"items":[{"label":"std","labelDetails":{"detail":"","description":"@import(\"std\")","sortText":null},"kind":21,"detail":"std","sortText":"1_std","filterText":null,"insertText":"std","insertTextFormat":1,"documentation":null}]}
|
||||
);
|
||||
}
|
||||
|
||||
test "Encoded space in file name and usingnamespace on non-existing symbol" {
|
||||
var ctx = try Context.init();
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.request("textDocument/didOpen",
|
||||
\\{"textDocument":{"uri":"file:///%20test.zig","languageId":"zig","version":420,"text":"usingnamespace a.b;\nb."}}
|
||||
, null);
|
||||
try ctx.request("textDocument/completion",
|
||||
\\{"textDocument":{"uri":"file:///%20test.zig"}, "position":{"line":1,"character":2}}
|
||||
,
|
||||
\\{"isIncomplete":false,"items":[]}
|
||||
);
|
||||
}
|
||||
|
||||
test "Self-referential definition" {
|
||||
var ctx = try Context.init();
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.request("textDocument/didOpen",
|
||||
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const h = h(0);\nc"}}
|
||||
, null);
|
||||
try ctx.request("textDocument/completion",
|
||||
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
|
||||
,
|
||||
\\{"isIncomplete":false,"items":[{"label":"h","labelDetails":{"detail":"","description":"h(0)","sortText":null},"kind":21,"detail":"h","sortText":"1_h","filterText":null,"insertText":"h","insertTextFormat":1,"documentation":null}]}
|
||||
);
|
||||
}
|
||||
|
||||
// This test as written depends on the configuration in the *host* machines zls.json, if `enable_snippets` is true then
|
||||
// the insert text is "w()" if it is false it is "w"
|
||||
//
|
||||
// test "Missing return type" {
|
||||
// var server = try Server.start(initialize_msg, null);
|
||||
// defer server.shutdown();
|
||||
|
||||
// try server.request("textDocument/didOpen",
|
||||
// \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"fn w() {}\nc"}}
|
||||
// , null);
|
||||
// try server.request("textDocument/completion",
|
||||
// \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
|
||||
// ,
|
||||
// \\{"isIncomplete":false,"items":[{"label":"w","kind":3,"textEdit":null,"filterText":null,"insertText":"w","insertTextFormat":1,"detail":"fn","documentation":null}]}
|
||||
// );
|
||||
// }
|
||||
|
||||
test "Pointer and optional deref" {
|
||||
var ctx = try Context.init();
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.request("textDocument/didOpen",
|
||||
\\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"var value: ?struct { data: i32 = 5 } = null;const ptr = &value;\nconst a = ptr.*.?."}}
|
||||
, null);
|
||||
try ctx.request("textDocument/completion",
|
||||
\\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":18}}
|
||||
,
|
||||
\\{"isIncomplete":false,"items":[{"label":"data","labelDetails":{"detail":"","description":"i32 ","sortText":null},"kind":5,"detail":"data","sortText":"3_data","filterText":null,"insertText":"data","insertTextFormat":1,"documentation":null}]}
|
||||
);
|
||||
}
|
||||
|
||||
// not fixed yet!
|
||||
// test "Self-referential import" {
|
||||
// var ctx = try Context.init();
|
||||
// defer ctx.deinit();
|
||||
//
|
||||
// try ctx.request("textDocument/didOpen",
|
||||
// \\{"textDocument":{"uri":"file:///test.zig","languageId":"zig","version":420,"text":"const a = @import(\"test.zig\").a;\nc"}}
|
||||
// , null);
|
||||
// try ctx.request("textDocument/completion",
|
||||
// \\{"textDocument":{"uri":"file:///test.zig"}, "position":{"line":1,"character":1}}
|
||||
// ,
|
||||
// \\{"isIncomplete":false,"items":[]}
|
||||
// );
|
||||
// }
|
||||
|
Loading…
Reference in New Issue
Block a user