| ... | @@ -1669,39 +1669,44 @@ fn evalZigTest( | ... | @@ -1669,39 +1669,44 @@ fn evalZigTest( |
| 1669 | | 1669 | |
| 1670 | while (true) { | 1670 | while (true) { |
| 1671 | var child = try process.spawn(io, spawn_options); | 1671 | var child = try process.spawn(io, spawn_options); |
| 1672 | var poller = std.Io.poll(gpa, StdioPollEnum, .{ | 1672 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; |
| 1673 | .stdout = child.stdout.?, | 1673 | var multi_reader: Io.File.MultiReader = undefined; |
| 1674 | .stderr = child.stderr.?, | 1674 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); |
| 1675 | }); | | |
| 1676 | var child_killed = false; | 1675 | var child_killed = false; |
| 1677 | defer if (!child_killed) { | 1676 | defer if (!child_killed) { |
| 1678 | child.kill(io); | 1677 | child.kill(io); |
| 1679 | poller.deinit(); | 1678 | multi_reader.deinit(); |
| 1680 | run.step.result_peak_rss = @max( | 1679 | run.step.result_peak_rss = @max( |
| 1681 | run.step.result_peak_rss, | 1680 | run.step.result_peak_rss, |
| 1682 | child.resource_usage_statistics.getMaxRss() orelse 0, | 1681 | child.resource_usage_statistics.getMaxRss() orelse 0, |
| 1683 | ); | 1682 | ); |
| 1684 | }; | 1683 | }; |
| 1685 | | 1684 | |
| 1686 | switch (try pollZigTest( | 1685 | switch (try waitZigTest( |
| 1687 | run, | 1686 | run, |
| 1688 | &child, | 1687 | &child, |
| 1689 | options, | 1688 | options, |
| 1690 | fuzz_context, | 1689 | fuzz_context, |
| 1691 | &poller, | 1690 | &multi_reader, |
| 1692 | &test_metadata, | 1691 | &test_metadata, |
| 1693 | &test_results, | 1692 | &test_results, |
| 1694 | )) { | 1693 | )) { |
| 1695 | .write_failed => |err| { | 1694 | .write_failed => |err| { |
| 1696 | // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured | 1695 | // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured |
| 1697 | // all available stderr to make our error output as useful as possible. | 1696 | // all available stderr to make our error output as useful as possible. |
| 1698 | while (try poller.poll()) {} | 1697 | const stderr_fr = multi_reader.fileReader(1); |
| 1699 | run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered()); | 1698 | while (true) { |
| | 1699 | stderr_fr.interface.fillMore() catch |e| switch (e) { |
| | 1700 | error.ReadFailed => return stderr_fr.err.?, |
| | 1701 | error.EndOfStream => break, |
| | 1702 | }; |
| | 1703 | } |
| | 1704 | run.step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered()); |
| 1700 | | 1705 | |
| 1701 | // Clean up everything and wait for the child to exit. | 1706 | // Clean up everything and wait for the child to exit. |
| 1702 | child.stdin.?.close(io); | 1707 | child.stdin.?.close(io); |
| 1703 | child.stdin = null; | 1708 | child.stdin = null; |
| 1704 | poller.deinit(); | 1709 | multi_reader.deinit(); |
| 1705 | child_killed = true; | 1710 | child_killed = true; |
| 1706 | const term = try child.wait(io); | 1711 | const term = try child.wait(io); |
| 1707 | run.step.result_peak_rss = @max( | 1712 | run.step.result_peak_rss = @max( |
| ... | @@ -1716,13 +1721,14 @@ fn evalZigTest( | ... | @@ -1716,13 +1721,14 @@ fn evalZigTest( |
| 1716 | .no_poll => |no_poll| { | 1721 | .no_poll => |no_poll| { |
| 1717 | // This might be a success (we requested exit and the child dutifully closed stdout) or | 1722 | // This might be a success (we requested exit and the child dutifully closed stdout) or |
| 1718 | // a crash of some kind. Either way, the child will terminate by itself -- wait for it. | 1723 | // a crash of some kind. Either way, the child will terminate by itself -- wait for it. |
| 1719 | const stderr_owned = try arena.dupe(u8, poller.reader(.stderr).buffered()); | 1724 | const stderr_reader = multi_reader.reader(1); |
| 1720 | poller.reader(.stderr).tossBuffered(); | 1725 | const stderr_owned = try arena.dupe(u8, stderr_reader.buffered()); |
| | 1726 | stderr_reader.tossBuffered(); |
| 1721 | | 1727 | |
| 1722 | // Clean up everything and wait for the child to exit. | 1728 | // Clean up everything and wait for the child to exit. |
| 1723 | child.stdin.?.close(io); | 1729 | child.stdin.?.close(io); |
| 1724 | child.stdin = null; | 1730 | child.stdin = null; |
| 1725 | poller.deinit(); | 1731 | multi_reader.deinit(); |
| 1726 | child_killed = true; | 1732 | child_killed = true; |
| 1727 | const term = try child.wait(io); | 1733 | const term = try child.wait(io); |
| 1728 | run.step.result_peak_rss = @max( | 1734 | run.step.result_peak_rss = @max( |
| ... | @@ -1770,8 +1776,9 @@ fn evalZigTest( | ... | @@ -1770,8 +1776,9 @@ fn evalZigTest( |
| 1770 | return; | 1776 | return; |
| 1771 | }, | 1777 | }, |
| 1772 | .timeout => |timeout| { | 1778 | .timeout => |timeout| { |
| 1773 | const stderr = poller.reader(.stderr).buffered(); | 1779 | const stderr_reader = multi_reader.reader(1); |
| 1774 | poller.reader(.stderr).tossBuffered(); | 1780 | const stderr = stderr_reader.buffered(); |
| | 1781 | stderr_reader.tossBuffered(); |
| 1775 | if (timeout.active_test_index) |test_index| { | 1782 | if (timeout.active_test_index) |test_index| { |
| 1776 | // A test was running. Report the timeout against that test, and continue on to | 1783 | // A test was running. Report the timeout against that test, and continue on to |
| 1777 | // the next test. | 1784 | // the next test. |
| ... | @@ -1796,16 +1803,16 @@ fn evalZigTest( | ... | @@ -1796,16 +1803,16 @@ fn evalZigTest( |
| 1796 | } | 1803 | } |
| 1797 | } | 1804 | } |
| 1798 | | 1805 | |
| 1799 | /// Polls stdout of a Zig test process until a termination condition is reached: | 1806 | /// Reads stdout of a Zig test process until a termination condition is reached: |
| 1800 | /// * A write fails, indicating the child unexpectedly closed stdin | 1807 | /// * A write fails, indicating the child unexpectedly closed stdin |
| 1801 | /// * A test (or a response from the test runner) times out | 1808 | /// * A test (or a response from the test runner) times out |
| 1802 | /// * `poll` fails, indicating the child closed stdout and stderr | 1809 | /// * The wait fails, indicating the child closed stdout and stderr |
| 1803 | fn pollZigTest( | 1810 | fn waitZigTest( |
| 1804 | run: *Run, | 1811 | run: *Run, |
| 1805 | child: *process.Child, | 1812 | child: *process.Child, |
| 1806 | options: Step.MakeOptions, | 1813 | options: Step.MakeOptions, |
| 1807 | fuzz_context: ?FuzzContext, | 1814 | fuzz_context: ?FuzzContext, |
| 1808 | poller: *std.Io.Poller(StdioPollEnum), | 1815 | multi_reader: *Io.File.MultiReader, |
| 1809 | opt_metadata: *?TestMetadata, | 1816 | opt_metadata: *?TestMetadata, |
| 1810 | results: *Step.TestResults, | 1817 | results: *Step.TestResults, |
| 1811 | ) !union(enum) { | 1818 | ) !union(enum) { |
| ... | @@ -1874,12 +1881,11 @@ fn pollZigTest( | ... | @@ -1874,12 +1881,11 @@ fn pollZigTest( |
| 1874 | break :ns @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); | 1881 | break :ns @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); |
| 1875 | }; | 1882 | }; |
| 1876 | | 1883 | |
| 1877 | const stdout = poller.reader(.stdout); | 1884 | const stdout = multi_reader.reader(0); |
| 1878 | const stderr = poller.reader(.stderr); | 1885 | const stderr = multi_reader.reader(1); |
| | 1886 | const Header = std.zig.Server.Message.Header; |
| 1879 | | 1887 | |
| 1880 | while (true) { | 1888 | while (true) { |
| 1881 | const Header = std.zig.Server.Message.Header; | | |
| 1882 | | | |
| 1883 | // This block is exited when `stdout` contains enough bytes for a `Header`. | 1889 | // This block is exited when `stdout` contains enough bytes for a `Header`. |
| 1884 | header_ready: { | 1890 | header_ready: { |
| 1885 | if (stdout.buffered().len >= @sizeOf(Header)) { | 1891 | if (stdout.buffered().len >= @sizeOf(Header)) { |
| ... | @@ -1894,18 +1900,22 @@ fn pollZigTest( | ... | @@ -1894,18 +1900,22 @@ fn pollZigTest( |
| 1894 | break :ns options.unit_test_timeout_ns; | 1900 | break :ns options.unit_test_timeout_ns; |
| 1895 | }; | 1901 | }; |
| 1896 | | 1902 | |
| 1897 | if (opt_timeout_ns) |timeout_ns| { | 1903 | const timeout: Io.Timeout = if (opt_timeout_ns) |timeout_ns| .{ .duration = .{ |
| 1898 | const remaining_ns = timeout_ns -| timer.?.read(); | 1904 | .raw = .fromNanoseconds(timeout_ns -| timer.?.read()), |
| 1899 | if (!try poller.pollTimeout(remaining_ns)) return .{ .no_poll = .{ | 1905 | .clock = .awake, |
| 1900 | .active_test_index = active_test_index, | 1906 | } } else .none; |
| 1901 | .ns_elapsed = if (timer) |*t| t.read() else 0, | 1907 | |
| 1902 | } }; | 1908 | multi_reader.fill(timeout) catch |err| switch (err) { |
| 1903 | } else { | 1909 | error.Timeout, error.EndOfStream => return .{ .no_poll = .{ |
| 1904 | if (!try poller.poll()) return .{ .no_poll = .{ | | |
| 1905 | .active_test_index = active_test_index, | 1910 | .active_test_index = active_test_index, |
| 1906 | .ns_elapsed = if (timer) |*t| t.read() else 0, | 1911 | .ns_elapsed = if (timer) |*t| t.read() else 0, |
| 1907 | } }; | 1912 | } }, |
| 1908 | } | 1913 | error.UnsupportedClock => { |
| | 1914 | timer = null; |
| | 1915 | continue; |
| | 1916 | }, |
| | 1917 | else => |e| return e, |
| | 1918 | }; |
| 1909 | | 1919 | |
| 1910 | if (stdout.buffered().len >= @sizeOf(Header)) { | 1920 | if (stdout.buffered().len >= @sizeOf(Header)) { |
| 1911 | // There wasn't a header before, but there is one after the `poll`. | 1921 | // There wasn't a header before, but there is one after the `poll`. |
| ... | @@ -1923,11 +1933,8 @@ fn pollZigTest( | ... | @@ -1923,11 +1933,8 @@ fn pollZigTest( |
| 1923 | } | 1933 | } |
| 1924 | // There is definitely a header available now -- read it. | 1934 | // There is definitely a header available now -- read it. |
| 1925 | const header = stdout.takeStruct(Header, .little) catch unreachable; | 1935 | const header = stdout.takeStruct(Header, .little) catch unreachable; |
| | 1936 | try stdout.fill(header.bytes_len); |
| 1926 | | 1937 | |
| 1927 | while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) return .{ .no_poll = .{ | | |
| 1928 | .active_test_index = active_test_index, | | |
| 1929 | .ns_elapsed = if (timer) |*t| t.read() else 0, | | |
| 1930 | } }; | | |
| 1931 | const body = stdout.take(header.bytes_len) catch unreachable; | 1938 | const body = stdout.take(header.bytes_len) catch unreachable; |
| 1932 | var body_r: std.Io.Reader = .fixed(body); | 1939 | var body_r: std.Io.Reader = .fixed(body); |
| 1933 | switch (header.tag) { | 1940 | switch (header.tag) { |
| ... | @@ -2164,6 +2171,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul | ... | @@ -2164,6 +2171,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul |
| 2164 | const b = run.step.owner; | 2171 | const b = run.step.owner; |
| 2165 | const io = b.graph.io; | 2172 | const io = b.graph.io; |
| 2166 | const arena = b.allocator; | 2173 | const arena = b.allocator; |
| | 2174 | const gpa = b.allocator; |
| 2167 | | 2175 | |
| 2168 | var child = try process.spawn(io, spawn_options); | 2176 | var child = try process.spawn(io, spawn_options); |
| 2169 | defer child.kill(io); | 2177 | defer child.kill(io); |
| ... | @@ -2211,23 +2219,31 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul | ... | @@ -2211,23 +2219,31 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul |
| 2211 | | 2219 | |
| 2212 | if (child.stdout) |stdout| { | 2220 | if (child.stdout) |stdout| { |
| 2213 | if (child.stderr) |stderr| { | 2221 | if (child.stderr) |stderr| { |
| 2214 | var poller = std.Io.poll(arena, enum { stdout, stderr }, .{ | 2222 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; |
| 2215 | .stdout = stdout, | 2223 | var multi_reader: Io.File.MultiReader = undefined; |
| 2216 | .stderr = stderr, | 2224 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ stdout, stderr }); |
| 2217 | }); | 2225 | defer multi_reader.deinit(); |
| 2218 | defer poller.deinit(); | | |
| 2219 | | 2226 | |
| 2220 | while (try poller.poll()) { | 2227 | const stdout_reader = multi_reader.reader(0); |
| | 2228 | const stderr_reader = multi_reader.reader(1); |
| | 2229 | |
| | 2230 | while (multi_reader.fill(.none)) |_| { |
| 2221 | if (run.stdio_limit.toInt()) |limit| { | 2231 | if (run.stdio_limit.toInt()) |limit| { |
| 2222 | if (poller.reader(.stderr).buffered().len > limit) | 2232 | if (stdout_reader.buffered().len > limit) |
| 2223 | return error.StdoutStreamTooLong; | 2233 | return error.StdoutStreamTooLong; |
| 2224 | if (poller.reader(.stderr).buffered().len > limit) | 2234 | if (stderr_reader.buffered().len > limit) |
| 2225 | return error.StderrStreamTooLong; | 2235 | return error.StderrStreamTooLong; |
| 2226 | } | 2236 | } |
| | 2237 | } else |err| switch (err) { |
| | 2238 | error.UnsupportedClock, error.Timeout => unreachable, |
| | 2239 | error.EndOfStream => {}, |
| | 2240 | else => |e| return e, |
| 2227 | } | 2241 | } |
| 2228 | | 2242 | |
| 2229 | stdout_bytes = try poller.toOwnedSlice(.stdout); | 2243 | try multi_reader.checkAnyError(); |
| 2230 | stderr_bytes = try poller.toOwnedSlice(.stderr); | 2244 | |
| | 2245 | stdout_bytes = try multi_reader.toOwnedSlice(0); |
| | 2246 | stderr_bytes = try multi_reader.toOwnedSlice(1); |
| 2231 | } else { | 2247 | } else { |
| 2232 | var stdout_reader = stdout.readerStreaming(io, &.{}); | 2248 | var stdout_reader = stdout.readerStreaming(io, &.{}); |
| 2233 | stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) { | 2249 | stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) { |