1const builtin = @import("builtin");
2
3const std = @import("std");
4const Io = std.Io;
5const Allocator = std.mem.Allocator;
6
7pub fn main(init: std.process.Init) !void {
8 const gpa = init.gpa;
9 const io = init.io;
10
11 var it = try init.minimal.args.iterateAllocator(gpa);
12 defer it.deinit();
13 _ = it.next() orelse unreachable; // skip binary name
14
15 const iterations: u64 = iterations: {
16 const arg = it.next() orelse "0";
17 break :iterations try std.fmt.parseUnsigned(u64, arg, 10);
18 };
19
20 var rand_seed = false;
21 const seed: u64 = seed: {
22 const seed_arg = it.next() orelse {
23 rand_seed = true;
24 var buf: [8]u8 = undefined;
25 io.random(&buf);
26 break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
27 };
28 break :seed try std.fmt.parseUnsigned(u64, seed_arg, 10);
29 };
30 var random = std.Random.DefaultPrng.init(seed);
31 const rand = random.random();
32
33 // If the seed was not given via the CLI, then output the
34 // randomly chosen seed so that this run can be reproduced
35 if (rand_seed) {
36 std.debug.print("rand seed: {}\n", .{seed});
37 }
38
39 var i: u64 = 0;
40 while (iterations == 0 or i < iterations) {
41 const rand_arg = try randomArg(gpa, rand);
42 defer gpa.free(rand_arg);
43
44 try testExec(gpa, io, &.{rand_arg}, null);
45
46 i += 1;
47 }
48}
49
50fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.Environ.Map) !void {
51 try testExecBat(gpa, io, "args1.bat", args, env);
52 try testExecBat(gpa, io, "args2.bat", args, env);
53 try testExecBat(gpa, io, "args3.bat", args, env);
54}
55
56fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.Environ.Map) !void {
57 const argv = try gpa.alloc([]const u8, 1 + args.len);
58 defer gpa.free(argv);
59 argv[0] = bat;
60 @memcpy(argv[1..], args);
61
62 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
63
64 const result = try std.process.run(gpa, io, .{
65 .environ_map = env,
66 .argv = argv,
67 });
68 defer gpa.free(result.stdout);
69 defer gpa.free(result.stderr);
70
71 try std.testing.expectEqualStrings("", result.stderr);
72 var it = std.mem.splitScalar(u8, result.stdout, '\x00');
73 var i: usize = 0;
74 while (it.next()) |actual_arg| {
75 if (i >= args.len and can_have_trailing_empty_args) {
76 try std.testing.expectEqualStrings("", actual_arg);
77 continue;
78 }
79 const expected_arg = args[i];
80 try std.testing.expectEqualSlices(u8, expected_arg, actual_arg);
81 i += 1;
82 }
83}
84
85fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 {
86 const Choice = enum {
87 backslash,
88 quote,
89 space,
90 control,
91 printable,
92 surrogate_half,
93 non_ascii,
94 };
95
96 const choices = rand.uintAtMostBiased(u16, 256);
97 var buf: std.ArrayList(u8) = try .initCapacity(gpa, choices);
98 errdefer buf.deinit(gpa);
99
100 var last_codepoint: u21 = 0;
101 for (0..choices) |_| {
102 const choice = rand.enumValue(Choice);
103 const codepoint: u21 = switch (choice) {
104 .backslash => '\\',
105 .quote => '"',
106 .space => ' ',
107 .control => switch (rand.uintAtMostBiased(u8, 0x21)) {
108 // NUL/CR/LF can't roundtrip
109 '\x00', '\r', '\n' => ' ',
110 0x21 => '\x7F',
111 else => |b| b,
112 },
113 .printable => '!' + rand.uintAtMostBiased(u8, '~' - '!'),
114 .surrogate_half => rand.intRangeAtMostBiased(u16, 0xD800, 0xDFFF),
115 .non_ascii => rand.intRangeAtMostBiased(u21, 0x80, 0x10FFFF),
116 };
117 // Ensure that we always return well-formed WTF-8.
118 // Instead of concatenating to ensure well-formed WTF-8,
119 // we just skip encoding the low surrogate.
120 if (std.unicode.isSurrogateCodepoint(last_codepoint) and std.unicode.isSurrogateCodepoint(codepoint)) {
121 if (std.unicode.utf16IsHighSurrogate(@intCast(last_codepoint)) and std.unicode.utf16IsLowSurrogate(@intCast(codepoint))) {
122 continue;
123 }
124 }
125 try buf.ensureUnusedCapacity(gpa, 4);
126 const unused_slice = buf.unusedCapacitySlice();
127 const len = std.unicode.wtf8Encode(codepoint, unused_slice) catch unreachable;
128 buf.items.len += len;
129 last_codepoint = codepoint;
130 }
131
132 return buf.toOwnedSlice(gpa);
133}