authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-06 22:44:36-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-15 10:48:13-07:00
log0b8736f5ed6e7ae08e8ef84beb03351a95daabdd
treebf2b4f3bcfbfa39d1e56e8e47102274eb8421e7a
parente897637d8d25f5b6b118356d2355da7c9148d8cb

re-enable CLI tests

CLI tests are now ported over to the new std.Build API and thus work properly with concurrency. * add `std.Build.addCheckFile` for creating a `std.Build.CheckFileStep`. * add `std.Build.makeTempPath`. This function is intended to be called in the `configure` phase only. It returns an absolute directory path, which is potentially going to be a source of API breakage in the future, so keep that in mind when using this function. * add `std.Build.CheckFileStep.setName`. * `std.Build.CheckFileStep`: better error message when reading the input file fails. * `std.Build.RunStep`: add a `has_side_effects` flag for when you need to override the autodetection. * `std.Build.RunStep`: add the ability to obtain a FileSource for the directory that contains the written files. * `std.Build.WriteFileStep`: add a way to write bytes to an arbitrary path - absolute or relative to the package root. Be careful with this because it updates source files. This should not be used as part of the normal build process, but as a utility occasionally run by a developer with intent to modify source files and then commit those changes to version control. A file added this way is not available with `getFileSource`.

8 files changed, 309 insertions(+), 222 deletions(-)

build.zig+1-1
...@@ -463,7 +463,7 @@ pub fn build(b: *std.Build) !void {...@@ -463,7 +463,7 @@ pub fn build(b: *std.Build) !void {
463 //test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release));463 //test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release));
464 //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows));464 //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows));
465 test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes));465 test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes));
466 //test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes));466 test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes));
467 //test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes));467 //test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes));
468 test_step.dependOn(tests.addTranslateCTests(b, test_filter));468 test_step.dependOn(tests.addTranslateCTests(b, test_filter));
469 if (!skip_run_translated_c) {469 if (!skip_run_translated_c) {
lib/std/Build.zig+40-4
...@@ -699,10 +699,8 @@ pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *Writ...@@ -699,10 +699,8 @@ pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *Writ
699 return write_file_step;699 return write_file_step;
700}700}
701701
702pub fn addWriteFiles(self: *Build) *WriteFileStep {702pub fn addWriteFiles(b: *Build) *WriteFileStep {
703 const write_file_step = self.allocator.create(WriteFileStep) catch @panic("OOM");703 return WriteFileStep.create(b);
704 write_file_step.* = WriteFileStep.init(self);
705 return write_file_step;
706}704}
707705
708pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {706pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {
...@@ -1239,6 +1237,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst...@@ -1239,6 +1237,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst
1239 return install_step;1237 return install_step;
1240}1238}
12411239
1240pub fn addCheckFile(
1241 b: *Build,
1242 file_source: FileSource,
1243 options: CheckFileStep.Options,
1244) *CheckFileStep {
1245 return CheckFileStep.create(b, file_source, options);
1246}
1247
1242pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {1248pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {
1243 const file = InstalledFile{1249 const file = InstalledFile{
1244 .dir = dir,1250 .dir = dir,
...@@ -1713,6 +1719,36 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {...@@ -1713,6 +1719,36 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {
1713 }1719 }
1714}1720}
17151721
1722/// This function is intended to be called in the `configure` phase only.
1723/// It returns an absolute directory path, which is potentially going to be a
1724/// source of API breakage in the future, so keep that in mind when using this
1725/// function.
1726pub fn makeTempPath(b: *Build) []const u8 {
1727 const rand_int = std.crypto.random.int(u64);
1728 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ hex64(rand_int);
1729 const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
1730 fs.cwd().makePath(result_path) catch |err| {
1731 std.debug.print("unable to make tmp path '{s}': {s}\n", .{
1732 result_path, @errorName(err),
1733 });
1734 };
1735 return result_path;
1736}
1737
1738/// There are a few copies of this function in miscellaneous places. Would be nice to find
1739/// a home for them.
1740fn hex64(x: u64) [16]u8 {
1741 const hex_charset = "0123456789abcdef";
1742 var result: [16]u8 = undefined;
1743 var i: usize = 0;
1744 while (i < 8) : (i += 1) {
1745 const byte = @truncate(u8, x >> @intCast(u6, 8 * i));
1746 result[i * 2 + 0] = hex_charset[byte >> 4];
1747 result[i * 2 + 1] = hex_charset[byte & 15];
1748 }
1749 return result;
1750}
1751
1716test {1752test {
1717 _ = CheckFileStep;1753 _ = CheckFileStep;
1718 _ = CheckObjectStep;1754 _ = CheckObjectStep;
lib/std/Build/CheckFileStep.zig+16-4
...@@ -12,13 +12,17 @@ expected_matches: []const []const u8,...@@ -12,13 +12,17 @@ expected_matches: []const []const u8,
12source: std.Build.FileSource,12source: std.Build.FileSource,
13max_bytes: usize = 20 * 1024 * 1024,13max_bytes: usize = 20 * 1024 * 1024,
1414
15pub const Options = struct {
16 expected_matches: []const []const u8,
17};
18
15pub fn create(19pub fn create(
16 owner: *std.Build,20 owner: *std.Build,
17 source: std.Build.FileSource,21 source: std.Build.FileSource,
18 expected_matches: []const []const u8,22 options: Options,
19) *CheckFileStep {23) *CheckFileStep {
20 const self = owner.allocator.create(CheckFileStep) catch @panic("OOM");24 const self = owner.allocator.create(CheckFileStep) catch @panic("OOM");
21 self.* = CheckFileStep{25 self.* = .{
22 .step = Step.init(.{26 .step = Step.init(.{
23 .id = .check_file,27 .id = .check_file,
24 .name = "CheckFile",28 .name = "CheckFile",
...@@ -26,19 +30,27 @@ pub fn create(...@@ -26,19 +30,27 @@ pub fn create(
26 .makeFn = make,30 .makeFn = make,
27 }),31 }),
28 .source = source.dupe(owner),32 .source = source.dupe(owner),
29 .expected_matches = owner.dupeStrings(expected_matches),33 .expected_matches = owner.dupeStrings(options.expected_matches),
30 };34 };
31 self.source.addStepDependencies(&self.step);35 self.source.addStepDependencies(&self.step);
32 return self;36 return self;
33}37}
3438
39pub fn setName(self: *CheckFileStep, name: []const u8) void {
40 self.step.name = name;
41}
42
35fn make(step: *Step, prog_node: *std.Progress.Node) !void {43fn make(step: *Step, prog_node: *std.Progress.Node) !void {
36 _ = prog_node;44 _ = prog_node;
37 const b = step.owner;45 const b = step.owner;
38 const self = @fieldParentPtr(CheckFileStep, "step", step);46 const self = @fieldParentPtr(CheckFileStep, "step", step);
3947
40 const src_path = self.source.getPath(b);48 const src_path = self.source.getPath(b);
41 const contents = try fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes);49 const contents = fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes) catch |err| {
50 return step.fail("unable to read '{s}': {s}", .{
51 src_path, @errorName(err),
52 });
53 };
4254
43 for (self.expected_matches) |expected_match| {55 for (self.expected_matches) |expected_match| {
44 if (mem.indexOf(u8, contents, expected_match) == null) {56 if (mem.indexOf(u8, contents, expected_match) == null) {
lib/std/Build/RunStep.zig+36-3
...@@ -70,6 +70,8 @@ max_stdio_size: usize = 10 * 1024 * 1024,...@@ -70,6 +70,8 @@ max_stdio_size: usize = 10 * 1024 * 1024,
70captured_stdout: ?*Output = null,70captured_stdout: ?*Output = null,
71captured_stderr: ?*Output = null,71captured_stderr: ?*Output = null,
7272
73has_side_effects: bool = false,
74
73pub const StdIo = union(enum) {75pub const StdIo = union(enum) {
74 /// Whether the RunStep has side-effects will be determined by whether or not one76 /// Whether the RunStep has side-effects will be determined by whether or not one
75 /// of the args is an output file (added with `addOutputFileArg`).77 /// of the args is an output file (added with `addOutputFileArg`).
...@@ -103,12 +105,14 @@ pub const StdIo = union(enum) {...@@ -103,12 +105,14 @@ pub const StdIo = union(enum) {
103pub const Arg = union(enum) {105pub const Arg = union(enum) {
104 artifact: *CompileStep,106 artifact: *CompileStep,
105 file_source: std.Build.FileSource,107 file_source: std.Build.FileSource,
108 directory_source: std.Build.FileSource,
106 bytes: []u8,109 bytes: []u8,
107 output: *Output,110 output: *Output,
108};111};
109112
110pub const Output = struct {113pub const Output = struct {
111 generated_file: std.Build.GeneratedFile,114 generated_file: std.Build.GeneratedFile,
115 prefix: []const u8,
112 basename: []const u8,116 basename: []const u8,
113};117};
114118
...@@ -142,10 +146,19 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {...@@ -142,10 +146,19 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {
142/// run, and returns a FileSource which can be used as inputs to other APIs146/// run, and returns a FileSource which can be used as inputs to other APIs
143/// throughout the build system.147/// throughout the build system.
144pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {148pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {
149 return addPrefixedOutputFileArg(rs, "", basename);
150}
151
152pub fn addPrefixedOutputFileArg(
153 rs: *RunStep,
154 prefix: []const u8,
155 basename: []const u8,
156) std.Build.FileSource {
145 const b = rs.step.owner;157 const b = rs.step.owner;
146158
147 const output = b.allocator.create(Output) catch @panic("OOM");159 const output = b.allocator.create(Output) catch @panic("OOM");
148 output.* = .{160 output.* = .{
161 .prefix = prefix,
149 .basename = basename,162 .basename = basename,
150 .generated_file = .{ .step = &rs.step },163 .generated_file = .{ .step = &rs.step },
151 };164 };
...@@ -159,14 +172,21 @@ pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource...@@ -159,14 +172,21 @@ pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource
159}172}
160173
161pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void {174pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void {
162 self.argv.append(Arg{175 self.argv.append(.{
163 .file_source = file_source.dupe(self.step.owner),176 .file_source = file_source.dupe(self.step.owner),
164 }) catch @panic("OOM");177 }) catch @panic("OOM");
165 file_source.addStepDependencies(&self.step);178 file_source.addStepDependencies(&self.step);
166}179}
167180
181pub fn addDirectorySourceArg(self: *RunStep, directory_source: std.Build.FileSource) void {
182 self.argv.append(.{
183 .directory_source = directory_source.dupe(self.step.owner),
184 }) catch @panic("OOM");
185 directory_source.addStepDependencies(&self.step);
186}
187
168pub fn addArg(self: *RunStep, arg: []const u8) void {188pub fn addArg(self: *RunStep, arg: []const u8) void {
169 self.argv.append(Arg{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM");189 self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM");
170}190}
171191
172pub fn addArgs(self: *RunStep, args: []const []const u8) void {192pub fn addArgs(self: *RunStep, args: []const []const u8) void {
...@@ -274,6 +294,7 @@ pub fn captureStdErr(self: *RunStep) std.Build.FileSource {...@@ -274,6 +294,7 @@ pub fn captureStdErr(self: *RunStep) std.Build.FileSource {
274294
275 const output = self.step.owner.allocator.create(Output) catch @panic("OOM");295 const output = self.step.owner.allocator.create(Output) catch @panic("OOM");
276 output.* = .{296 output.* = .{
297 .prefix = "",
277 .basename = "stderr",298 .basename = "stderr",
278 .generated_file = .{ .step = &self.step },299 .generated_file = .{ .step = &self.step },
279 };300 };
...@@ -288,6 +309,7 @@ pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile {...@@ -288,6 +309,7 @@ pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile {
288309
289 const output = self.step.owner.allocator.create(Output) catch @panic("OOM");310 const output = self.step.owner.allocator.create(Output) catch @panic("OOM");
290 output.* = .{311 output.* = .{
312 .prefix = "",
291 .basename = "stdout",313 .basename = "stdout",
292 .generated_file = .{ .step = &self.step },314 .generated_file = .{ .step = &self.step },
293 };315 };
...@@ -297,6 +319,7 @@ pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile {...@@ -297,6 +319,7 @@ pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile {
297319
298/// Returns whether the RunStep has side effects *other than* updating the output arguments.320/// Returns whether the RunStep has side effects *other than* updating the output arguments.
299fn hasSideEffects(self: RunStep) bool {321fn hasSideEffects(self: RunStep) bool {
322 if (self.has_side_effects) return true;
300 return switch (self.stdio) {323 return switch (self.stdio) {
301 .infer_from_args => !self.hasAnyOutputArgs(),324 .infer_from_args => !self.hasAnyOutputArgs(),
302 .inherit => true,325 .inherit => true,
...@@ -373,6 +396,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -373,6 +396,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
373 try argv_list.append(file_path);396 try argv_list.append(file_path);
374 _ = try man.addFile(file_path, null);397 _ = try man.addFile(file_path, null);
375 },398 },
399 .directory_source => |file| {
400 const file_path = file.getPath(b);
401 try argv_list.append(file_path);
402 man.hash.addBytes(file_path);
403 },
376 .artifact => |artifact| {404 .artifact => |artifact| {
377 if (artifact.target.isWindows()) {405 if (artifact.target.isWindows()) {
378 // On Windows we don't have rpaths so we have to add .dll search paths to PATH406 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
...@@ -386,6 +414,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -386,6 +414,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
386 _ = try man.addFile(file_path, null);414 _ = try man.addFile(file_path, null);
387 },415 },
388 .output => |output| {416 .output => |output| {
417 man.hash.addBytes(output.prefix);
389 man.hash.addBytes(output.basename);418 man.hash.addBytes(output.basename);
390 // Add a placeholder into the argument list because we need the419 // Add a placeholder into the argument list because we need the
391 // manifest hash to be updated with all arguments before the420 // manifest hash to be updated with all arguments before the
...@@ -456,7 +485,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -456,7 +485,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
456 };485 };
457 const output_path = try b.cache_root.join(arena, &output_components);486 const output_path = try b.cache_root.join(arena, &output_components);
458 placeholder.output.generated_file.path = output_path;487 placeholder.output.generated_file.path = output_path;
459 argv_list.items[placeholder.index] = output_path;488 const cli_arg = if (placeholder.output.prefix.len == 0)
489 output_path
490 else
491 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
492 argv_list.items[placeholder.index] = cli_arg;
460 }493 }
461494
462 try runCommand(self, argv_list.items, has_side_effects, &digest);495 try runCommand(self, argv_list.items, has_side_effects, &digest);
lib/std/Build/TranslateCStep.zig+5-1
...@@ -72,7 +72,11 @@ pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void {...@@ -72,7 +72,11 @@ pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void {
72}72}
7373
74pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep {74pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep {
75 return CheckFileStep.create(self.step.owner, .{ .generated = &self.output_file }, self.step.owner.dupeStrings(expected_matches));75 return CheckFileStep.create(
76 self.step.owner,
77 .{ .generated = &self.output_file },
78 .{ .expected_matches = expected_matches },
79 );
76}80}
7781
78/// If the value is omitted, it is set to 1.82/// If the value is omitted, it is set to 1.
lib/std/Build/WriteFileStep.zig+29-2
...@@ -14,6 +14,7 @@ step: Step,...@@ -14,6 +14,7 @@ step: Step,
14/// GeneratedFile field.14/// GeneratedFile field.
15files: std.ArrayListUnmanaged(*File),15files: std.ArrayListUnmanaged(*File),
16output_source_files: std.ArrayListUnmanaged(OutputSourceFile),16output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
17generated_directory: std.Build.GeneratedFile,
1718
18pub const base_id = .write_file;19pub const base_id = .write_file;
1920
...@@ -33,8 +34,9 @@ pub const Contents = union(enum) {...@@ -33,8 +34,9 @@ pub const Contents = union(enum) {
33 copy: std.Build.FileSource,34 copy: std.Build.FileSource,
34};35};
3536
36pub fn init(owner: *std.Build) WriteFileStep {37pub fn create(owner: *std.Build) *WriteFileStep {
37 return .{38 const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM");
39 wf.* = .{
38 .step = Step.init(.{40 .step = Step.init(.{
39 .id = .write_file,41 .id = .write_file,
40 .name = "WriteFile",42 .name = "WriteFile",
...@@ -43,7 +45,9 @@ pub fn init(owner: *std.Build) WriteFileStep {...@@ -43,7 +45,9 @@ pub fn init(owner: *std.Build) WriteFileStep {
43 }),45 }),
44 .files = .{},46 .files = .{},
45 .output_source_files = .{},47 .output_source_files = .{},
48 .generated_directory = .{ .step = &wf.step },
46 };49 };
50 return wf;
47}51}
4852
49pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {53pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {
...@@ -95,6 +99,20 @@ pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub...@@ -95,6 +99,20 @@ pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub
95 }) catch @panic("OOM");99 }) catch @panic("OOM");
96}100}
97101
102/// A path relative to the package root.
103/// Be careful with this because it updates source files. This should not be
104/// used as part of the normal build process, but as a utility occasionally
105/// run by a developer with intent to modify source files and then commit
106/// those changes to version control.
107/// A file added this way is not available with `getFileSource`.
108pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void {
109 const b = wf.step.owner;
110 wf.output_source_files.append(b.allocator, .{
111 .contents = .{ .bytes = bytes },
112 .sub_path = sub_path,
113 }) catch @panic("OOM");
114}
115
98/// Gets a file source for the given sub_path. If the file does not exist, returns `null`.116/// Gets a file source for the given sub_path. If the file does not exist, returns `null`.
99pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource {117pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource {
100 for (wf.files.items) |file| {118 for (wf.files.items) |file| {
...@@ -105,6 +123,12 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo...@@ -105,6 +123,12 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo
105 return null;123 return null;
106}124}
107125
126/// Returns a `FileSource` representing the base directory that contains all the
127/// files from this `WriteFileStep`.
128pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource {
129 return .{ .generated = &wf.generated_directory };
130}
131
108fn maybeUpdateName(wf: *WriteFileStep) void {132fn maybeUpdateName(wf: *WriteFileStep) void {
109 if (wf.files.items.len == 1) {133 if (wf.files.items.len == 1) {
110 // First time adding a file; update name.134 // First time adding a file; update name.
...@@ -193,12 +217,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -193,12 +217,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
193 "o", &digest, file.sub_path,217 "o", &digest, file.sub_path,
194 });218 });
195 }219 }
220 wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
196 return;221 return;
197 }222 }
198223
199 const digest = man.final();224 const digest = man.final();
200 const cache_path = "o" ++ fs.path.sep_str ++ digest;225 const cache_path = "o" ++ fs.path.sep_str ++ digest;
201226
227 wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
228
202 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {229 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
203 return step.fail("unable to make path '{}{s}': {s}", .{230 return step.fail("unable to make path '{}{s}': {s}", .{
204 b.cache_root, cache_path, @errorName(err),231 b.cache_root, cache_path, @errorName(err),
test/cli.zig deleted-195
...@@ -1,195 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const testing = std.testing;
4const process = std.process;
5const fs = std.fs;
6const ChildProcess = std.ChildProcess;
7
8var a: std.mem.Allocator = undefined;
9
10pub fn main() !void {
11 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
12 defer _ = gpa.deinit();
13 var arena = std.heap.ArenaAllocator.init(gpa.allocator());
14 defer arena.deinit();
15
16 a = arena.allocator();
17 var arg_it = try process.argsWithAllocator(a);
18
19 // skip my own exe name
20 _ = arg_it.skip();
21
22 const zig_exe_rel = arg_it.next() orelse {
23 std.debug.print("Expected first argument to be path to zig compiler\n", .{});
24 return error.InvalidArgs;
25 };
26 const cache_root = arg_it.next() orelse {
27 std.debug.print("Expected second argument to be cache root directory path\n", .{});
28 return error.InvalidArgs;
29 };
30 const zig_exe = try fs.path.resolve(a, &[_][]const u8{zig_exe_rel});
31
32 const dir_path = try fs.path.join(a, &[_][]const u8{ cache_root, "clitest" });
33 defer fs.cwd().deleteTree(dir_path) catch {};
34
35 const TestFn = fn ([]const u8, []const u8) anyerror!void;
36 const Test = struct {
37 func: TestFn,
38 name: []const u8,
39 };
40 const tests = [_]Test{
41 .{ .func = testZigInitLib, .name = "zig init-lib" },
42 .{ .func = testZigInitExe, .name = "zig init-exe" },
43 .{ .func = testGodboltApi, .name = "godbolt API" },
44 .{ .func = testMissingOutputPath, .name = "missing output path" },
45 .{ .func = testZigFmt, .name = "zig fmt" },
46 };
47 inline for (tests) |t| {
48 try fs.cwd().deleteTree(dir_path);
49 try fs.cwd().makeDir(dir_path);
50 t.func(zig_exe, dir_path) catch |err| {
51 std.debug.print("test '{s}' failed: {s}\n", .{
52 t.name, @errorName(err),
53 });
54 return err;
55 };
56 }
57}
58
59fn printCmd(cwd: []const u8, argv: []const []const u8) void {
60 std.debug.print("cd {s} && ", .{cwd});
61 for (argv) |arg| {
62 std.debug.print("{s} ", .{arg});
63 }
64 std.debug.print("\n", .{});
65}
66
67fn exec(cwd: []const u8, expect_0: bool, argv: []const []const u8) !ChildProcess.ExecResult {
68 const max_output_size = 100 * 1024;
69 const result = ChildProcess.exec(.{
70 .allocator = a,
71 .argv = argv,
72 .cwd = cwd,
73 .max_output_bytes = max_output_size,
74 }) catch |err| {
75 std.debug.print("The following command failed:\n", .{});
76 printCmd(cwd, argv);
77 return err;
78 };
79 switch (result.term) {
80 .Exited => |code| {
81 if ((code != 0) == expect_0) {
82 std.debug.print("The following command exited with error code {}:\n", .{code});
83 printCmd(cwd, argv);
84 std.debug.print("stderr:\n{s}\n", .{result.stderr});
85 return error.CommandFailed;
86 }
87 },
88 else => {
89 std.debug.print("The following command terminated unexpectedly:\n", .{});
90 printCmd(cwd, argv);
91 std.debug.print("stderr:\n{s}\n", .{result.stderr});
92 return error.CommandFailed;
93 },
94 }
95 return result;
96}
97
98fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void {
99 _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-lib" });
100 const test_result = try exec(dir_path, true, &[_][]const u8{ zig_exe, "build", "test" });
101 try testing.expectStringEndsWith(test_result.stderr, "All 1 tests passed.\n");
102}
103
104fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void {
105 _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
106 const run_result = try exec(dir_path, true, &[_][]const u8{ zig_exe, "build", "run" });
107 try testing.expectEqualStrings("All your codebase are belong to us.\n", run_result.stderr);
108 try testing.expectEqualStrings("Run `zig build test` to run the tests.\n", run_result.stdout);
109}
110
111fn testGodboltApi(zig_exe: []const u8, dir_path: []const u8) anyerror!void {
112 if (builtin.os.tag != .linux or builtin.cpu.arch != .x86_64) return;
113
114 const example_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "example.zig" });
115 const example_s_path = try fs.path.join(a, &[_][]const u8{ dir_path, "example.s" });
116
117 try fs.cwd().writeFile(example_zig_path,
118 \\// Type your code here, or load an example.
119 \\export fn square(num: i32) i32 {
120 \\ return num * num;
121 \\}
122 \\extern fn zig_panic() noreturn;
123 \\pub fn panic(msg: []const u8, error_return_trace: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn {
124 \\ _ = msg;
125 \\ _ = error_return_trace;
126 \\ zig_panic();
127 \\}
128 );
129
130 var args = std.ArrayList([]const u8).init(a);
131 try args.appendSlice(&[_][]const u8{
132 zig_exe, "build-obj",
133 "--cache-dir", dir_path,
134 "--name", "example",
135 "-fno-emit-bin", "-fno-emit-h",
136 "-fstrip", "-OReleaseFast",
137 example_zig_path,
138 });
139
140 const emit_asm_arg = try std.fmt.allocPrint(a, "-femit-asm={s}", .{example_s_path});
141 try args.append(emit_asm_arg);
142
143 _ = try exec(dir_path, true, args.items);
144
145 const out_asm = try std.fs.cwd().readFileAlloc(a, example_s_path, std.math.maxInt(usize));
146 try testing.expect(std.mem.indexOf(u8, out_asm, "square:") != null);
147 try testing.expect(std.mem.indexOf(u8, out_asm, "mov\teax, edi") != null);
148 try testing.expect(std.mem.indexOf(u8, out_asm, "imul\teax, edi") != null);
149}
150
151fn testMissingOutputPath(zig_exe: []const u8, dir_path: []const u8) !void {
152 _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
153 const output_path = try fs.path.join(a, &[_][]const u8{ "does", "not", "exist", "foo.exe" });
154 const output_arg = try std.fmt.allocPrint(a, "-femit-bin={s}", .{output_path});
155 const source_path = try fs.path.join(a, &[_][]const u8{ "src", "main.zig" });
156 const result = try exec(dir_path, false, &[_][]const u8{ zig_exe, "build-exe", source_path, output_arg });
157 const s = std.fs.path.sep_str;
158 const expected: []const u8 = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n";
159 try testing.expectEqualStrings(expected, result.stderr);
160}
161
162fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void {
163 _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
164
165 const unformatted_code = " // no reason for indent";
166
167 const fmt1_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt1.zig" });
168 try fs.cwd().writeFile(fmt1_zig_path, unformatted_code);
169
170 const run_result1 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", fmt1_zig_path });
171 // stderr should be file path + \n
172 try testing.expect(std.mem.startsWith(u8, run_result1.stdout, fmt1_zig_path));
173 try testing.expect(run_result1.stdout.len == fmt1_zig_path.len + 1 and run_result1.stdout[run_result1.stdout.len - 1] == '\n');
174
175 const fmt2_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt2.zig" });
176 try fs.cwd().writeFile(fmt2_zig_path, unformatted_code);
177
178 const run_result2 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path });
179 // running it on the dir, only the new file should be changed
180 try testing.expect(std.mem.startsWith(u8, run_result2.stdout, fmt2_zig_path));
181 try testing.expect(run_result2.stdout.len == fmt2_zig_path.len + 1 and run_result2.stdout[run_result2.stdout.len - 1] == '\n');
182
183 const run_result3 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path });
184 // both files have been formatted, nothing should change now
185 try testing.expect(run_result3.stdout.len == 0);
186
187 // Check UTF-16 decoding
188 const fmt4_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt4.zig" });
189 var unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
190 try fs.cwd().writeFile(fmt4_zig_path, unformatted_code_utf16);
191
192 const run_result4 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path });
193 try testing.expect(std.mem.startsWith(u8, run_result4.stdout, fmt4_zig_path));
194 try testing.expect(run_result4.stdout.len == fmt4_zig_path.len + 1 and run_result4.stdout[run_result4.stdout.len - 1] == '\n');
195}
test/tests.zig+182-12
...@@ -635,19 +635,189 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co...@@ -635,19 +635,189 @@ pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []co
635 _ = optimize_modes;635 _ = optimize_modes;
636 const step = b.step("test-cli", "Test the command line interface");636 const step = b.step("test-cli", "Test the command line interface");
637637
638 const exe = b.addExecutable(.{638 {
639 .name = "test-cli",639 // Test `zig init-lib`.
640 .root_source_file = .{ .path = "test/cli.zig" },640 const tmp_path = b.makeTempPath();
641 .target = .{},641 const init_lib = b.addSystemCommand(&.{ b.zig_exe, "init-lib" });
642 .optimize = .Debug,642 init_lib.cwd = tmp_path;
643 });643 init_lib.setName("zig init-lib");
644 const run_cmd = exe.run();644 init_lib.expectStdOutEqual("");
645 run_cmd.addArgs(&[_][]const u8{645 init_lib.expectStdErrEqual(
646 fs.realpathAlloc(b.allocator, b.zig_exe) catch @panic("OOM"),646 \\info: Created build.zig
647 b.pathFromRoot(b.cache_root.path orelse "."),647 \\info: Created src/main.zig
648 });648 \\info: Next, try `zig build --help` or `zig build test`
649 \\
650 );
651
652 const run_test = b.addSystemCommand(&.{ b.zig_exe, "build", "test" });
653 run_test.cwd = tmp_path;
654 run_test.setName("zig build test");
655 run_test.expectStdOutEqual("");
656 run_test.step.dependOn(&init_lib.step);
657
658 const cleanup = b.addRemoveDirTree(tmp_path);
659 cleanup.step.dependOn(&run_test.step);
660
661 step.dependOn(&cleanup.step);
662 }
663
664 {
665 // Test `zig init-exe`.
666 const tmp_path = b.makeTempPath();
667 const init_exe = b.addSystemCommand(&.{ b.zig_exe, "init-exe" });
668 init_exe.cwd = tmp_path;
669 init_exe.setName("zig init-exe");
670 init_exe.expectStdOutEqual("");
671 init_exe.expectStdErrEqual(
672 \\info: Created build.zig
673 \\info: Created src/main.zig
674 \\info: Next, try `zig build --help` or `zig build run`
675 \\
676 );
677
678 // Test missing output path.
679 const s = std.fs.path.sep_str;
680 const bad_out_arg = "-femit-bin=does" ++ s ++ "not" ++ s ++ "exist" ++ s ++ "foo.exe";
681 const ok_src_arg = "src" ++ s ++ "main.zig";
682 const expected = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n";
683 const run_bad = b.addSystemCommand(&.{ b.zig_exe, "build-exe", ok_src_arg, bad_out_arg });
684 run_bad.setName("zig build-exe error message for bad -femit-bin arg");
685 run_bad.expectExitCode(1);
686 run_bad.expectStdErrEqual(expected);
687 run_bad.expectStdOutEqual("");
688 run_bad.step.dependOn(&init_exe.step);
689
690 const run_test = b.addSystemCommand(&.{ b.zig_exe, "build", "test" });
691 run_test.cwd = tmp_path;
692 run_test.setName("zig build test");
693 run_test.expectStdOutEqual("");
694 run_test.step.dependOn(&init_exe.step);
695
696 const run_run = b.addSystemCommand(&.{ b.zig_exe, "build", "run" });
697 run_run.cwd = tmp_path;
698 run_run.setName("zig build run");
699 run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n");
700 run_run.expectStdErrEqual("All your codebase are belong to us.\n");
701 run_run.step.dependOn(&init_exe.step);
702
703 const cleanup = b.addRemoveDirTree(tmp_path);
704 cleanup.step.dependOn(&run_test.step);
705 cleanup.step.dependOn(&run_run.step);
706 cleanup.step.dependOn(&run_bad.step);
707
708 step.dependOn(&cleanup.step);
709 }
710
711 // Test Godbolt API
712 if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
713 const tmp_path = b.makeTempPath();
714
715 const writefile = b.addWriteFile("example.zig",
716 \\// Type your code here, or load an example.
717 \\export fn square(num: i32) i32 {
718 \\ return num * num;
719 \\}
720 \\extern fn zig_panic() noreturn;
721 \\pub fn panic(msg: []const u8, error_return_trace: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn {
722 \\ _ = msg;
723 \\ _ = error_return_trace;
724 \\ zig_panic();
725 \\}
726 );
727
728 // This is intended to be the exact CLI usage used by godbolt.org.
729 const run = b.addSystemCommand(&.{
730 b.zig_exe, "build-obj",
731 "--cache-dir", tmp_path,
732 "--name", "example",
733 "-fno-emit-bin", "-fno-emit-h",
734 "-fstrip", "-OReleaseFast",
735 });
736 run.addFileSourceArg(writefile.getFileSource("example.zig").?);
737 const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s");
738
739 const checkfile = b.addCheckFile(example_s, .{
740 .expected_matches = &.{
741 "square:",
742 "mov\teax, edi",
743 "imul\teax, edi",
744 },
745 });
746 checkfile.setName("check godbolt.org CLI usage generating valid asm");
747
748 const cleanup = b.addRemoveDirTree(tmp_path);
749 cleanup.step.dependOn(&checkfile.step);
750
751 step.dependOn(&cleanup.step);
752 }
753
754 {
755 // Test `zig fmt`.
756 // This test must use a temporary directory rather than a cache
757 // directory because this test will be mutating the files. The cache
758 // system relies on cache directories being mutated only by their
759 // owners.
760 const tmp_path = b.makeTempPath();
761 const unformatted_code = " // no reason for indent";
762 const s = std.fs.path.sep_str;
763
764 var dir = fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled");
765 defer dir.close();
766 dir.writeFile("fmt1.zig", unformatted_code) catch @panic("unhandled");
767 dir.writeFile("fmt2.zig", unformatted_code) catch @panic("unhandled");
768
769 // Test zig fmt affecting only the appropriate files.
770 const run1 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "fmt1.zig" });
771 run1.setName("run zig fmt one file");
772 run1.cwd = tmp_path;
773 run1.has_side_effects = true;
774 // stdout should be file path + \n
775 run1.expectStdOutEqual("fmt1.zig\n");
776
777 // running it on the dir, only the new file should be changed
778 const run2 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
779 run2.setName("run zig fmt the directory");
780 run2.cwd = tmp_path;
781 run2.has_side_effects = true;
782 run2.expectStdOutEqual("." ++ s ++ "fmt2.zig\n");
783 run2.step.dependOn(&run1.step);
784
785 // both files have been formatted, nothing should change now
786 const run3 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
787 run3.setName("run zig fmt with nothing to do");
788 run3.cwd = tmp_path;
789 run3.has_side_effects = true;
790 run3.expectStdOutEqual("");
791 run3.step.dependOn(&run2.step);
792
793 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
794 const fmt4_path = fs.path.join(b.allocator, &.{ tmp_path, "fmt4.zig" }) catch @panic("OOM");
795 const write4 = b.addWriteFiles();
796 write4.addBytesToSource(unformatted_code_utf16, fmt4_path);
797 write4.step.dependOn(&run3.step);
798
799 // Test `zig fmt` handling UTF-16 decoding.
800 const run4 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
801 run4.setName("run zig fmt convert UTF-16 to UTF-8");
802 run4.cwd = tmp_path;
803 run4.has_side_effects = true;
804 run4.expectStdOutEqual("." ++ s ++ "fmt4.zig\n");
805 run4.step.dependOn(&write4.step);
806
807 // TODO change this to an exact match
808 const check4 = b.addCheckFile(.{ .path = fmt4_path }, .{
809 .expected_matches = &.{
810 "// no reason",
811 },
812 });
813 check4.step.dependOn(&run4.step);
814
815 const cleanup = b.addRemoveDirTree(tmp_path);
816 cleanup.step.dependOn(&check4.step);
817
818 step.dependOn(&cleanup.step);
819 }
649820
650 step.dependOn(&run_cmd.step);
651 return step;821 return step;
652}822}
653823