Skip to content

Commit

Permalink
add analysis tests
Browse files Browse the repository at this point in the history
These new tests make easier to directly test the analysis backend
without going through one of the LSP requests. Some of the existing
tests should also be ported to this new system.

They can be executed with `zig build test-analysis` which will also
compile faster than `zig build test`.
  • Loading branch information
Techatrix committed Feb 15, 2025
1 parent 414f1b0 commit c5f2544
Show file tree
Hide file tree
Showing 10 changed files with 805 additions and 4 deletions.
24 changes: 21 additions & 3 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -243,13 +243,20 @@ pub fn build(b: *Build) !void {
.use_lld = use_llvm,
});

{ // zig build test
const test_step = b.step("test", "Run all the tests");
var coverage_test_analysis_steps: std.ArrayListUnmanaged(*std.Build.Step.Run) = .empty;

{ // zig build test, zig build test-build-runner, zig build test-analysis
const test_build_runner_step = b.step("test-build-runner", "Run all the build runner tests");
@import("tests/add_build_runner_cases.zig").addCases(b, test_build_runner_step, test_filters);

const test_analysis_step = b.step("test-analysis", "Run all the analysis tests");
var test_analysis_steps: std.ArrayListUnmanaged(*std.Build.Step.Run) = .empty;
@import("tests/add_analysis_cases.zig").addCases(b, test_filters, &test_analysis_steps, &coverage_test_analysis_steps);
for (test_analysis_steps.items) |run| test_analysis_step.dependOn(&run.step);

const test_step = b.step("test", "Run all the tests");
test_step.dependOn(test_build_runner_step);
test_step.dependOn(test_analysis_step);
test_step.dependOn(&b.addRunArtifact(tests).step);
test_step.dependOn(&b.addRunArtifact(src_tests).step);
}
Expand All @@ -263,14 +270,25 @@ pub fn build(b: *Build) !void {
const merged_coverage_output = merge_step.addOutputFileArg(".");

for ([_]*std.Build.Step.Compile{ tests, src_tests }) |test_exe| {
const kcov_collect = std.Build.Step.Run.create(b, "collect coverage");
const kcov_collect = std.Build.Step.Run.create(b, b.fmt("run {s} (collect coverage)", .{test_exe.name}));
kcov_collect.addArgs(&.{ kcov_bin, "--collect-only" });
kcov_collect.addPrefixedDirectoryArg("--include-pattern=", b.path("src"));
merge_step.addDirectoryArg(kcov_collect.addOutputFileArg(test_exe.name));
kcov_collect.addArtifactArg(test_exe);
kcov_collect.enableTestRunnerMode();
}

for (coverage_test_analysis_steps.items) |run_step| {
run_step.setName(b.fmt("{s} (collect coverage)", .{run_step.step.name}));

// prepend the kcov exec args
const argv = run_step.argv.toOwnedSlice(b.allocator) catch @panic("OOM");
run_step.addArgs(&.{ kcov_bin, "--collect-only" });
run_step.addPrefixedDirectoryArg("--include-pattern=", b.path("src"));
merge_step.addDirectoryArg(run_step.addOutputFileArg(run_step.producer.?.name));
run_step.argv.appendSlice(b.allocator, argv) catch @panic("OOM");
}

const install_coverage = b.addInstallDirectory(.{
.source_dir = merged_coverage_output,
.install_dir = .{ .custom = "coverage" },
Expand Down
2 changes: 1 addition & 1 deletion src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2879,7 +2879,7 @@ pub const Type = struct {
}

pub fn fmtTypeVal(ty: Type, analyser: *Analyser, options: FormatOptions) std.fmt.Formatter(format) {
std.debug.assert(ty.is_type_val);
std.debug.assert(ty.data == .ip_index or ty.is_type_val);
return .{ .data = .{ .ty = ty, .analyser = analyser, .options = options } };
}

Expand Down
1 change: 1 addition & 0 deletions src/zls.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub const analyser = @import("analyser/analyser.zig");
pub const configuration = @import("configuration.zig");
pub const DocumentScope = @import("DocumentScope.zig");
pub const BuildRunnerVersion = @import("build_runner/BuildRunnerVersion.zig");
pub const DiagnosticsCollection = @import("DiagnosticsCollection.zig");

pub const signature_help = @import("features/signature_help.zig");
pub const references = @import("features/references.zig");
Expand Down
51 changes: 51 additions & 0 deletions tests/add_analysis_cases.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const std = @import("std");

pub fn addCases(
b: *std.Build,
test_filters: []const []const u8,
steps1: *std.ArrayListUnmanaged(*std.Build.Step.Run),
steps2: *std.ArrayListUnmanaged(*std.Build.Step.Run),
) void {
const cases_dir = b.path("tests/analysis");
const cases_path_from_root = b.pathFromRoot("tests/analysis");

const check_exe = b.addExecutable(.{
.name = "analysis_check",
.root_source_file = b.path("tests/analysis_check.zig"),
.target = b.graph.host,
});
check_exe.root_module.addImport("zls", b.modules.get("zls").?);

// https://github.com/ziglang/zig/issues/20605
var dir = std.fs.openDirAbsolute(b.pathFromRoot(cases_path_from_root), .{ .iterate = true }) catch |err|
std.debug.panic("failed to open '{s}': {}", .{ cases_path_from_root, err });
defer dir.close();

var it = dir.iterate();

while (true) {
const entry = it.next() catch |err|
std.debug.panic("failed to walk directory '{s}': {}", .{ cases_path_from_root, err }) orelse break;

if (entry.kind != .file) continue;
if (!std.mem.eql(u8, std.fs.path.extension(entry.name), ".zig")) continue;

for (test_filters) |test_filter| {
if (std.mem.indexOf(u8, entry.name, test_filter) != null) break;
} else if (test_filters.len > 0) continue;

// We create two runs steps, one for `zig build test` and another for `zig build coverage`
for ([_]*std.ArrayListUnmanaged(*std.Build.Step.Run){ steps1, steps2 }) |steps| {
const run_check = std.Build.Step.Run.create(b, b.fmt("run analysis on {s}", .{entry.name}));
run_check.producer = check_exe;
run_check.addArtifactArg(check_exe);
run_check.addArg("--zig-exe-path");
run_check.addFileArg(.{ .cwd_relative = b.graph.zig_exe });
run_check.addArg("--zig-lib-path");
run_check.addDirectoryArg(.{ .cwd_relative = b.fmt("{}", .{b.graph.zig_lib_directory}) });
run_check.addFileArg(cases_dir.path(b, entry.name));

steps.append(b.allocator, run_check) catch @panic("OOM");
}
}
}
81 changes: 81 additions & 0 deletions tests/analysis/array.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const ArrayType = [3]u8;
// ^^^^^^^^^ (type)([3]u8)
const ArrayTypeWithSentinel = [3:0]u8;
// ^^^^^^^^^^^^^^^^^^^^^ (type)([3:0]u8)

const empty_array: [0]u8 = undefined;
// ^^^^^^^^^^^ ([0]u8)()
const empty_array_len = empty_array.len;
// ^^^^^^^^^^^^^^^ (usize)()

const length = 3;
const unknown_length: usize = undefined;
var runtime_index: usize = 5;

const some_array: [length]u8 = undefined;
// ^^^^^^^^^^ ([3]u8)()

const some_unsized_array: [unknown_length]u8 = undefined;
// ^^^^^^^^^^^^^^^^^^ ([?]u8)()

const some_array_len = some_array.len;
// ^^^^^^^^^^^^^^ (usize)()

const some_unsized_array_len = some_unsized_array.len;
// ^^^^^^^^^^^^^^^^^^^^^^ (usize)()

const array_indexing = some_array[0];
// ^^^^^^^^^^^^^^ (u8)()

// TODO this should be `*const [2]u8`
const array_slice_open_1 = some_array[1..];
// ^^^^^^^^^^^^^^^^^^ ([]u8)()

// TODO this should be `*const [0]u8`
const array_slice_open_3 = some_array[3..];
// ^^^^^^^^^^^^^^^^^^ ([]u8)()

// TODO this should be `*const [?]u8`
const array_slice_open_4 = some_array[4..];
// ^^^^^^^^^^^^^^^^^^ ([]u8)()

const array_slice_open_runtime = some_array[runtime_index..];
// ^^^^^^^^^^^^^^^^^^^^^^^^ ([]u8)()

// TODO this should be `*const [2]u8`
const array_slice_0_2 = some_array[0..2];
// ^^^^^^^^^^^^^^^ ([]u8)()

// TODO this should be `*const [2 :0]u8`
const array_slice_0_2_sentinel = some_array[0..2 :0];
// TODO ^^^^^^^^^^^^^^^ ([:0]u8)()

// TODO this should be `*const [?]u8`
const array_slice_0_5 = some_array[0..5];
// ^^^^^^^^^^^^^^^ ([]u8)()

// TODO this should be `*const [?]u8`
const array_slice_3_2 = some_array[3..2];
// ^^^^^^^^^^^^^^^ ([]u8)()

const array_slice_0_runtime = some_array[0..runtime_index];
// ^^^^^^^^^^^^^^^^^^^^^ ([]u8)()

const array_slice_with_sentinel = some_array[0..runtime_index :0];
// TODO ^^^^^^^^^^^^^^^^^^^^^^^^^ ([:0]u8)()

//
// Array init
//

const array_init = [length]u8{};
// ^^^^^^^^^^ ([3]u8)()
const array_init_inferred_len_0 = [_]u8{};
// TODO ^^^^^^^^^^^^^^^^^^^^^^^^^ ([0]u8)()
const array_init_inferred_len_3 = [_]u8{ 1, 2, 3 };
// TODO ^^^^^^^^^^^^^^^^^^^^^^^^^ ([0]u8)()

comptime {
// Use @compileLog to verify the expected type with the compiler:
// @compileLog(some_array);
}
48 changes: 48 additions & 0 deletions tests/analysis/basic.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const alpha: bool = true;
// ^^^^^ (bool)()
const beta: bool = false;
// ^^^^ (bool)()
const gamma: type = bool;
// ^^^^^ (type)(bool)
const delta: comptime_int = 4;
// ^^^^^ (comptime_int)()
const epsilon = null;
// ^^^^^^^ (@TypeOf(null))(null)
const zeta: type = u32;
// ^^^^ (type)(u32)
const eta: type = isize;
// ^^^ (type)(isize)
const theta = true;
// ^^^^^ (bool)(true)
const iota = false;
// ^^^^ (bool)(false)
const kappa = bool;
// ^^^^^ (type)(bool)
const lambda = 4;
// ^^^^^^ (comptime_int)()
const mu = undefined;
// ^^ (@TypeOf(undefined))(undefined)
const nu: type = i1;
// ^^ (type)(i1)
const xi: type = usize;
// ^^ (type)(usize)
const omicron = 0;
// ^^^^^^^ ()()
const pi = 3.14159;
// ^^ (comptime_float)()
const rho: type = anyopaque;
// ^^^ (type)(anyopaque)
const sigma = noreturn;
// ^^^^^ (type)(noreturn)
const tau = anyerror;
// ^^^ (type)(anyerror)
const upsilon = 0;
// ^^^^^^^ ()()
const phi = 0;
// ^^^ ()()
const chi = 0;
// ^^^ ()()
const psi = 0;
// ^^^ ()()
const omega = 0;
// ^^^^^ ()()
20 changes: 20 additions & 0 deletions tests/analysis/optional.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const alpha: ?u32 = undefined;
// ^^^^^ (?u32)()

const beta = alpha.?;
// ^^^^ (u32)()

const gamma = if (alpha) |value| value else null;
// ^^^^^ (u32)()

const delta = alpha orelse unreachable;
// ^^^^^ (u32)()

const epsilon = alpha.?;
// ^^^^^^^ (u32)()

const zeta = alpha orelse null;
// TODO ^^^^ (?u32)()

const eta = alpha orelse 5;
// ^^^ (u32)()
128 changes: 128 additions & 0 deletions tests/analysis/pointer.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//
// single item pointer *T
//

const one_u32: *const u32 = &@as(u32, 5);
// ^^^^^^^ (*const u32)()

const one_u32_deref = one_u32.*;
// ^^^^^^^^^^^^^ (u32)()

const one_u32_indexing = one_u32[0];
// ^^^^^^^^^^^^^^^^ (unknown)()

const one_u32_slice_len_0_5 = one_u32[0..5];
// ^^^^^^^^^^^^^^^^^^^^^ (unknown)()

const one_u32_slice_len_0_0 = one_u32[0..0];
// TODO ^^^^^^^^^^^^^^^^^^^^^ (*const [0]u32)()

const one_u32_slice_len_0_1 = one_u32[0..1];
// TODO ^^^^^^^^^^^^^^^^^^^^^ (*const [1]u32)()

const one_u32_slice_len_1_1 = one_u32[1..1];
// TODO ^^^^^^^^^^^^^^^^^^^^^ (*const [0]u32)()

const one_u32_slice_open = one_u32[1..];
// ^^^^^^^^^^^^^^^^^^ (unknown)()

const one_u32_orelse = one_u32 orelse unreachable;
// ^^^^^^^^^^^^^^ (unknown)()

const one_u32_unwrap = one_u32.?;
// ^^^^^^^^^^^^^^ (unknown)()

//
// many item pointer [*]T
//

const many_u32: [*]const u32 = &[_]u32{ 1, 2 };
// ^^^^^^^^ ([*]const u32)()

const many_u32_deref = many_u32.*;
// ^^^^^^^^^^^^^^ (unknown)()

const many_u32_indexing = many_u32[0];
// ^^^^^^^^^^^^^^^^^ (u32)()

// TODO this should be `*const [2]u32`
const many_u32_slice_len_comptime = many_u32[0..2];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()

const many_u32_slice_len_runtime = many_u32[0..runtime_index];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()

const many_u32_slice_open = many_u32[1..];
// ^^^^^^^^^^^^^^^^^^^ ([*]const u32)()

const many_u32_orelse = many_u32 orelse unreachable;
// ^^^^^^^^^^^^^^^ (unknown)()

const many_u32_unwrap = many_u32.?;
// ^^^^^^^^^^^^^^^ (unknown)()

//
// slice []T
//

const slice_u32: []const u32 = &.{ 1, 2 };
// ^^^^^^^^^ ([]const u32)()

const slice_u32_deref = slice_u32.*;
// ^^^^^^^^^^^^^^^ (unknown)()

const slice_u32_indexing = slice_u32[0];
// ^^^^^^^^^^^^^^^^^^ (u32)()

// TODO this should be `*const [2]u32`
const slice_u32_slice_len_comptime = slice_u32[0..2];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()

const slice_u32_slice_len_runtime = slice_u32[0..runtime_index];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()

// TODO this should be `*const [1]u32`
const slice_u32_slice_open = slice_u32[1..];
// ^^^^^^^^^^^^^^^^^^^^ ([]const u32)()

const slice_u32_orelse = slice_u32 orelse unreachable;
// ^^^^^^^^^^^^^^^^ (unknown)()

const slice_u32_unwrap = slice_u32.?;
// ^^^^^^^^^^^^^^^^ (unknown)()

//
// C pointer [*c]T
//

const c_u32: [*c]const u32 = &[_]u32{ 1, 2 };
// ^^^^^ ([*c]const u32)()

const c_u32_deref = c_u32.*;
// ^^^^^^^^^^^ (u32)()

const c_u32_indexing = c_u32[0];
// ^^^^^^^^^^^^^^ (u32)()

// TODO this should be `*const [2]u32`
const c_u32_slice_len_comptime = c_u32[0..2];
// ^^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()

const c_u32_slice_len_runtime = c_u32[0..runtime_index];
// ^^^^^^^^^^^^^^^^^^^^^^^ ([]const u32)()

const c_u32_slice_open = c_u32[1..];
// ^^^^^^^^^^^^^^^^ ([*c]const u32)()

const c_u32_orelse = c_u32 orelse unreachable;
// ^^^^^^^^^^^^ ([*c]const u32)()

const c_u32_unwrap = c_u32.?;
// ^^^^^^^^^^^^ ([*c]const u32)()

var runtime_index: usize = 5;

comptime {
// Use @compileLog to verify the expected type with the compiler:
// @compileLog(many_u32_slice_len_comptime);
}
Loading

0 comments on commit c5f2544

Please sign in to comment.