Merge pull request #570 from Techatrix/session-tests

Revive Session tests
This commit is contained in:
Auguste Rame 2022-08-04 23:27:18 +02:00 committed by GitHub
commit 6ed5ba833b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 708 additions and 470 deletions

View File

@ -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);

View File

@ -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,

File diff suppressed because it is too large Load Diff

142
src/main.zig Normal file
View 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
View 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);
}
};

View File

@ -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":[]}
// );
// }