-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdiscovery.zig
More file actions
162 lines (140 loc) · 5.41 KB
/
Copy pathdiscovery.zig
File metadata and controls
162 lines (140 loc) · 5.41 KB
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
const std = @import("std");
const compat = @import("compat.zig");
/// Test file discovery options
pub const DiscoveryOptions = struct {
/// Root directory to start searching from
root_path: []const u8 = ".",
/// Pattern to match test files (default: "*.test.zig")
pattern: []const u8 = "*.test.zig",
/// Whether to search recursively
recursive: bool = true,
/// Directories to exclude from search
exclude_dirs: []const []const u8 = &.{ "zig-cache", "zig-out", ".git", "node_modules" },
};
/// Discovered test file
pub const TestFile = struct {
/// Full path to the test file
path: []const u8,
/// Relative path from root
relative_path: []const u8,
/// File name only
name: []const u8,
pub fn deinit(self: *TestFile, allocator: std.mem.Allocator) void {
allocator.free(self.path);
allocator.free(self.relative_path);
allocator.free(self.name);
}
};
/// Test file discovery result
pub const DiscoveryResult = struct {
files: std.ArrayList(TestFile),
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) DiscoveryResult {
return DiscoveryResult{
.files = .empty,
.allocator = allocator,
};
}
pub fn deinit(self: *DiscoveryResult) void {
for (self.files.items) |*file| {
file.deinit(self.allocator);
}
self.files.deinit(self.allocator);
}
pub fn addFile(self: *DiscoveryResult, path: []const u8, relative_path: []const u8, name: []const u8) !void {
const file = TestFile{
.path = try self.allocator.dupe(u8, path),
.relative_path = try self.allocator.dupe(u8, relative_path),
.name = try self.allocator.dupe(u8, name),
};
try self.files.append(self.allocator, file);
}
};
/// Discover test files in a directory
pub fn discoverTests(allocator: std.mem.Allocator, options: DiscoveryOptions) !DiscoveryResult {
var result = DiscoveryResult.init(allocator);
errdefer result.deinit();
// Get absolute path of root
// TODO: realpathAlloc needs Io in Zig 0.16, using path as-is
const root_path = try allocator.dupe(u8, options.root_path);
defer allocator.free(root_path);
try scanDirectory(allocator, &result, root_path, root_path, options);
return result;
}
/// Recursively scan a directory for test files
fn scanDirectory(
allocator: std.mem.Allocator,
result: *DiscoveryResult,
root_path: []const u8,
current_path: []const u8,
options: DiscoveryOptions,
) !void {
var dir = compat.DirIterator.open(current_path) catch {
// Skip directories we can't open (permission issues, etc.)
std.debug.print("Warning: Cannot open directory {s}\n", .{current_path});
return;
};
defer dir.close();
while (try dir.next()) |entry| {
// Build full path
const full_path = try std.fs.path.join(allocator, &.{ current_path, entry.name });
defer allocator.free(full_path);
switch (entry.kind) {
.directory => {
if (!options.recursive) continue;
// Check if directory should be excluded
var should_exclude = false;
for (options.exclude_dirs) |exclude_dir| {
if (std.mem.eql(u8, entry.name, exclude_dir)) {
should_exclude = true;
break;
}
}
if (!should_exclude) {
try scanDirectory(allocator, result, root_path, full_path, options);
}
},
.file => {
// Check if file matches the pattern
if (matchesPattern(entry.name, options.pattern)) {
// Calculate relative path from root
const relative_path = if (std.mem.startsWith(u8, full_path, root_path))
full_path[root_path.len..]
else
full_path;
// Skip leading slash in relative path
const clean_relative = if (relative_path.len > 0 and relative_path[0] == '/')
relative_path[1..]
else
relative_path;
try result.addFile(full_path, clean_relative, entry.name);
}
},
else => {
// Skip other types (symlinks, etc.)
},
}
}
}
/// Check if a filename matches the pattern
fn matchesPattern(filename: []const u8, pattern: []const u8) bool {
// Simple pattern matching - check if filename ends with pattern
// For "*.test.zig", we check if filename ends with ".test.zig"
if (std.mem.startsWith(u8, pattern, "*")) {
const suffix = pattern[1..];
return std.mem.endsWith(u8, filename, suffix);
}
// Exact match
return std.mem.eql(u8, filename, pattern);
}
// Tests
test "matchesPattern with *.test.zig" {
try std.testing.expect(matchesPattern("foo.test.zig", "*.test.zig"));
try std.testing.expect(matchesPattern("bar.test.zig", "*.test.zig"));
try std.testing.expect(!matchesPattern("foo.zig", "*.test.zig"));
try std.testing.expect(!matchesPattern("test.zig", "*.test.zig"));
}
test "matchesPattern with exact match" {
try std.testing.expect(matchesPattern("test.zig", "test.zig"));
try std.testing.expect(!matchesPattern("other.zig", "test.zig"));
}