| author | |
| committer | |
| log | 0b8736f5ed6e7ae08e8ef84beb03351a95daabdd |
| tree | bf2b4f3bcfbfa39d1e56e8e47102274eb8421e7a |
| parent | e897637d8d25f5b6b118356d2355da7c9148d8cb |
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 | 463 | //test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release)); |
| 464 | 464 | //test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows)); |
| 465 | 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 | 467 | //test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes)); |
| 468 | 468 | test_step.dependOn(tests.addTranslateCTests(b, test_filter)); |
| 469 | 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 | 699 | return write_file_step; |
| 700 | 700 | } |
| 701 | 701 | |
| 702 | pub fn addWriteFiles(self: *Build) *WriteFileStep { | |
| 703 | const write_file_step = self.allocator.create(WriteFileStep) catch @panic("OOM"); | |
| 704 | write_file_step.* = WriteFileStep.init(self); | |
| 705 | return write_file_step; | |
| 702 | pub fn addWriteFiles(b: *Build) *WriteFileStep { | |
| 703 | return WriteFileStep.create(b); | |
| 706 | 704 | } |
| 707 | 705 | |
| 708 | 706 | pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep { |
| ... | ... | @@ -1239,6 +1237,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst |
| 1239 | 1237 | return install_step; |
| 1240 | 1238 | } |
| 1241 | 1239 | |
| 1240 | pub 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 | ||
| 1242 | 1248 | pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void { |
| 1243 | 1249 | const file = InstalledFile{ |
| 1244 | 1250 | .dir = dir, |
| ... | ... | @@ -1713,6 +1719,36 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 { |
| 1713 | 1719 | } |
| 1714 | 1720 | } |
| 1715 | 1721 | |
| 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. | |
| 1726 | pub 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. | |
| 1740 | fn 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 | ||
| 1716 | 1752 | test { |
| 1717 | 1753 | _ = CheckFileStep; |
| 1718 | 1754 | _ = CheckObjectStep; |
lib/std/Build/CheckFileStep.zig+16-4| ... | ... | @@ -12,13 +12,17 @@ expected_matches: []const []const u8, |
| 12 | 12 | source: std.Build.FileSource, |
| 13 | 13 | max_bytes: usize = 20 * 1024 * 1024, |
| 14 | 14 | |
| 15 | pub const Options = struct { | |
| 16 | expected_matches: []const []const u8, | |
| 17 | }; | |
| 18 | ||
| 15 | 19 | pub fn create( |
| 16 | 20 | owner: *std.Build, |
| 17 | 21 | source: std.Build.FileSource, |
| 18 | expected_matches: []const []const u8, | |
| 22 | options: Options, | |
| 19 | 23 | ) *CheckFileStep { |
| 20 | 24 | const self = owner.allocator.create(CheckFileStep) catch @panic("OOM"); |
| 21 | self.* = CheckFileStep{ | |
| 25 | self.* = .{ | |
| 22 | 26 | .step = Step.init(.{ |
| 23 | 27 | .id = .check_file, |
| 24 | 28 | .name = "CheckFile", |
| ... | ... | @@ -26,19 +30,27 @@ pub fn create( |
| 26 | 30 | .makeFn = make, |
| 27 | 31 | }), |
| 28 | 32 | .source = source.dupe(owner), |
| 29 | .expected_matches = owner.dupeStrings(expected_matches), | |
| 33 | .expected_matches = owner.dupeStrings(options.expected_matches), | |
| 30 | 34 | }; |
| 31 | 35 | self.source.addStepDependencies(&self.step); |
| 32 | 36 | return self; |
| 33 | 37 | } |
| 34 | 38 | |
| 39 | pub fn setName(self: *CheckFileStep, name: []const u8) void { | |
| 40 | self.step.name = name; | |
| 41 | } | |
| 42 | ||
| 35 | 43 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { |
| 36 | 44 | _ = prog_node; |
| 37 | 45 | const b = step.owner; |
| 38 | 46 | const self = @fieldParentPtr(CheckFileStep, "step", step); |
| 39 | 47 | |
| 40 | 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 | }; | |
| 42 | 54 | |
| 43 | 55 | for (self.expected_matches) |expected_match| { |
| 44 | 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 | 70 | captured_stdout: ?*Output = null, |
| 71 | 71 | captured_stderr: ?*Output = null, |
| 72 | 72 | |
| 73 | has_side_effects: bool = false, | |
| 74 | ||
| 73 | 75 | pub const StdIo = union(enum) { |
| 74 | 76 | /// Whether the RunStep has side-effects will be determined by whether or not one |
| 75 | 77 | /// of the args is an output file (added with `addOutputFileArg`). |
| ... | ... | @@ -103,12 +105,14 @@ pub const StdIo = union(enum) { |
| 103 | 105 | pub const Arg = union(enum) { |
| 104 | 106 | artifact: *CompileStep, |
| 105 | 107 | file_source: std.Build.FileSource, |
| 108 | directory_source: std.Build.FileSource, | |
| 106 | 109 | bytes: []u8, |
| 107 | 110 | output: *Output, |
| 108 | 111 | }; |
| 109 | 112 | |
| 110 | 113 | pub const Output = struct { |
| 111 | 114 | generated_file: std.Build.GeneratedFile, |
| 115 | prefix: []const u8, | |
| 112 | 116 | basename: []const u8, |
| 113 | 117 | }; |
| 114 | 118 | |
| ... | ... | @@ -142,10 +146,19 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { |
| 142 | 146 | /// run, and returns a FileSource which can be used as inputs to other APIs |
| 143 | 147 | /// throughout the build system. |
| 144 | 148 | pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { |
| 149 | return addPrefixedOutputFileArg(rs, "", basename); | |
| 150 | } | |
| 151 | ||
| 152 | pub fn addPrefixedOutputFileArg( | |
| 153 | rs: *RunStep, | |
| 154 | prefix: []const u8, | |
| 155 | basename: []const u8, | |
| 156 | ) std.Build.FileSource { | |
| 145 | 157 | const b = rs.step.owner; |
| 146 | 158 | |
| 147 | 159 | const output = b.allocator.create(Output) catch @panic("OOM"); |
| 148 | 160 | output.* = .{ |
| 161 | .prefix = prefix, | |
| 149 | 162 | .basename = basename, |
| 150 | 163 | .generated_file = .{ .step = &rs.step }, |
| 151 | 164 | }; |
| ... | ... | @@ -159,14 +172,21 @@ pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource |
| 159 | 172 | } |
| 160 | 173 | |
| 161 | 174 | pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { |
| 162 | self.argv.append(Arg{ | |
| 175 | self.argv.append(.{ | |
| 163 | 176 | .file_source = file_source.dupe(self.step.owner), |
| 164 | 177 | }) catch @panic("OOM"); |
| 165 | 178 | file_source.addStepDependencies(&self.step); |
| 166 | 179 | } |
| 167 | 180 | |
| 181 | pub 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 | ||
| 168 | 188 | pub 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 | } |
| 171 | 191 | |
| 172 | 192 | pub fn addArgs(self: *RunStep, args: []const []const u8) void { |
| ... | ... | @@ -274,6 +294,7 @@ pub fn captureStdErr(self: *RunStep) std.Build.FileSource { |
| 274 | 294 | |
| 275 | 295 | const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); |
| 276 | 296 | output.* = .{ |
| 297 | .prefix = "", | |
| 277 | 298 | .basename = "stderr", |
| 278 | 299 | .generated_file = .{ .step = &self.step }, |
| 279 | 300 | }; |
| ... | ... | @@ -288,6 +309,7 @@ pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile { |
| 288 | 309 | |
| 289 | 310 | const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); |
| 290 | 311 | output.* = .{ |
| 312 | .prefix = "", | |
| 291 | 313 | .basename = "stdout", |
| 292 | 314 | .generated_file = .{ .step = &self.step }, |
| 293 | 315 | }; |
| ... | ... | @@ -297,6 +319,7 @@ pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile { |
| 297 | 319 | |
| 298 | 320 | /// Returns whether the RunStep has side effects *other than* updating the output arguments. |
| 299 | 321 | fn hasSideEffects(self: RunStep) bool { |
| 322 | if (self.has_side_effects) return true; | |
| 300 | 323 | return switch (self.stdio) { |
| 301 | 324 | .infer_from_args => !self.hasAnyOutputArgs(), |
| 302 | 325 | .inherit => true, |
| ... | ... | @@ -373,6 +396,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { |
| 373 | 396 | try argv_list.append(file_path); |
| 374 | 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 | 404 | .artifact => |artifact| { |
| 377 | 405 | if (artifact.target.isWindows()) { |
| 378 | 406 | // 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 | 414 | _ = try man.addFile(file_path, null); |
| 387 | 415 | }, |
| 388 | 416 | .output => |output| { |
| 417 | man.hash.addBytes(output.prefix); | |
| 389 | 418 | man.hash.addBytes(output.basename); |
| 390 | 419 | // Add a placeholder into the argument list because we need the |
| 391 | 420 | // 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 | 485 | }; |
| 457 | 486 | const output_path = try b.cache_root.join(arena, &output_components); |
| 458 | 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 | } |
| 461 | 494 | |
| 462 | 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 | 72 | } |
| 73 | 73 | |
| 74 | 74 | pub 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 | } |
| 77 | 81 | |
| 78 | 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 | 14 | /// GeneratedFile field. |
| 15 | 15 | files: std.ArrayListUnmanaged(*File), |
| 16 | 16 | output_source_files: std.ArrayListUnmanaged(OutputSourceFile), |
| 17 | generated_directory: std.Build.GeneratedFile, | |
| 17 | 18 | |
| 18 | 19 | pub const base_id = .write_file; |
| 19 | 20 | |
| ... | ... | @@ -33,8 +34,9 @@ pub const Contents = union(enum) { |
| 33 | 34 | copy: std.Build.FileSource, |
| 34 | 35 | }; |
| 35 | 36 | |
| 36 | pub fn init(owner: *std.Build) WriteFileStep { | |
| 37 | return .{ | |
| 37 | pub fn create(owner: *std.Build) *WriteFileStep { | |
| 38 | const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM"); | |
| 39 | wf.* = .{ | |
| 38 | 40 | .step = Step.init(.{ |
| 39 | 41 | .id = .write_file, |
| 40 | 42 | .name = "WriteFile", |
| ... | ... | @@ -43,7 +45,9 @@ pub fn init(owner: *std.Build) WriteFileStep { |
| 43 | 45 | }), |
| 44 | 46 | .files = .{}, |
| 45 | 47 | .output_source_files = .{}, |
| 48 | .generated_directory = .{ .step = &wf.step }, | |
| 46 | 49 | }; |
| 50 | return wf; | |
| 47 | 51 | } |
| 48 | 52 | |
| 49 | 53 | pub 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 | 99 | }) catch @panic("OOM"); |
| 96 | 100 | } |
| 97 | 101 | |
| 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`. | |
| 108 | pub 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 | 116 | /// Gets a file source for the given sub_path. If the file does not exist, returns `null`. |
| 99 | 117 | pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource { |
| 100 | 118 | for (wf.files.items) |file| { |
| ... | ... | @@ -105,6 +123,12 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo |
| 105 | 123 | return null; |
| 106 | 124 | } |
| 107 | 125 | |
| 126 | /// Returns a `FileSource` representing the base directory that contains all the | |
| 127 | /// files from this `WriteFileStep`. | |
| 128 | pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource { | |
| 129 | return .{ .generated = &wf.generated_directory }; | |
| 130 | } | |
| 131 | ||
| 108 | 132 | fn maybeUpdateName(wf: *WriteFileStep) void { |
| 109 | 133 | if (wf.files.items.len == 1) { |
| 110 | 134 | // First time adding a file; update name. |
| ... | ... | @@ -193,12 +217,15 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { |
| 193 | 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 | 221 | return; |
| 197 | 222 | } |
| 198 | 223 | |
| 199 | 224 | const digest = man.final(); |
| 200 | 225 | const cache_path = "o" ++ fs.path.sep_str ++ digest; |
| 201 | 226 | |
| 227 | wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); | |
| 228 | ||
| 202 | 229 | var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { |
| 203 | 230 | return step.fail("unable to make path '{}{s}': {s}", .{ |
| 204 | 231 | b.cache_root, cache_path, @errorName(err), |
test/cli.zig deleted-195| ... | ... | @@ -1,195 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const testing = std.testing; | |
| 4 | const process = std.process; | |
| 5 | const fs = std.fs; | |
| 6 | const ChildProcess = std.ChildProcess; | |
| 7 | ||
| 8 | var a: std.mem.Allocator = undefined; | |
| 9 | ||
| 10 | pub 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 | ||
| 59 | fn 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 | ||
| 67 | fn 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 | ||
| 98 | fn 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 | ||
| 104 | fn 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 | ||
| 111 | fn 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 | ||
| 151 | fn 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 | ||
| 162 | fn 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 | 635 | _ = optimize_modes; |
| 636 | 636 | const step = b.step("test-cli", "Test the command line interface"); |
| 637 | 637 | |
| 638 | const exe = b.addExecutable(.{ | |
| 639 | .name = "test-cli", | |
| 640 | .root_source_file = .{ .path = "test/cli.zig" }, | |
| 641 | .target = .{}, | |
| 642 | .optimize = .Debug, | |
| 643 | }); | |
| 644 | const run_cmd = exe.run(); | |
| 645 | run_cmd.addArgs(&[_][]const u8{ | |
| 646 | fs.realpathAlloc(b.allocator, b.zig_exe) catch @panic("OOM"), | |
| 647 | b.pathFromRoot(b.cache_root.path orelse "."), | |
| 648 | }); | |
| 638 | { | |
| 639 | // Test `zig init-lib`. | |
| 640 | const tmp_path = b.makeTempPath(); | |
| 641 | const init_lib = b.addSystemCommand(&.{ b.zig_exe, "init-lib" }); | |
| 642 | init_lib.cwd = tmp_path; | |
| 643 | init_lib.setName("zig init-lib"); | |
| 644 | init_lib.expectStdOutEqual(""); | |
| 645 | init_lib.expectStdErrEqual( | |
| 646 | \\info: Created build.zig | |
| 647 | \\info: Created src/main.zig | |
| 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 | } | |
| 649 | 820 | |
| 650 | step.dependOn(&run_cmd.step); | |
| 651 | 821 | return step; |
| 652 | 822 | } |
| 653 | 823 |