1- //! Runs a Clar test and parses it's [TAP](https://testanything.org/) stream,
1+ //! Runs a Clar test and lightly parses it's [TAP](https://testanything.org/) stream,
22//! reporting progress/errors to the build system.
33// Based on Step.Run
44
@@ -77,27 +77,39 @@ fn make(step: *Step, options: std.Build.Step.MakeOptions) !void {
7777 const fifo = poller .fifo (.stdout );
7878 const r = fifo .reader ();
7979
80- var buf : [1024 ]u8 = undefined ;
81- var fbs = std .io .fixedBufferStream (& buf );
82- const w = fbs .writer ();
80+ var buf : std .BoundedArray (u8 , 1024 ) = .{};
81+ const w = buf .writer ();
82+
83+ var parser : TapParser = .default ;
84+ var node : ? std.Progress.Node = null ;
85+ defer if (node ) | n | n .end ();
8386
84- var parser : FailureParser = .default ;
8587 while (true ) {
8688 r .streamUntilDelimiter (w , '\n ' , null ) catch | err | switch (err ) {
8789 error .EndOfStream = > if (try poller .poll ()) continue else break ,
8890 else = > return err ,
8991 };
9092
91- const line = fbs .getWritten ();
92- defer fbs .reset ();
93-
94- options .progress_node .completeOne ();
95-
96- if (try parser .parseLine (arena , line )) | err | {
97- try step .addError ("{s}: {s}:{s}" , .{ err .desc , err .file , err .line });
98- try step .result_error_msgs .appendSlice (arena , b .dupeStrings (err .reasons .items ));
99- try step .result_error_msgs .append (arena , "\n " );
100- parser .reset (arena );
93+ const line = buf .constSlice ();
94+ defer buf .resize (0 ) catch unreachable ;
95+
96+ switch (try parser .parseLine (arena , line )) {
97+ .start_suite = > | suite | {
98+ if (node ) | n | n .end ();
99+ node = options .progress_node .start (suite , 0 );
100+ },
101+ .ok = > {
102+ if (node ) | n | n .completeOne ();
103+ },
104+ .failure = > | fail | {
105+ // @Cleanup print failures in a nicer way. Avoid redundant "error:" prefixes on newlines with minimal allocations.
106+ try step .result_error_msgs .append (arena , fail .description .items );
107+ try step .result_error_msgs .appendSlice (arena , fail .reasons .items );
108+ try step .result_error_msgs .append (arena , "\n " );
109+ if (node ) | n | n .completeOne ();
110+ parser .reset ();
111+ },
112+ .feed_line = > {},
101113 }
102114 }
103115
@@ -108,24 +120,34 @@ fn make(step: *Step, options: std.Build.Step.MakeOptions) !void {
108120 try step .writeManifestAndWatch (& man );
109121}
110122
111- const FailureParser = struct {
123+ const TapParser = struct {
112124 state : State ,
113- fail : Failure ,
114-
115- const Failure = struct {
116- desc : []const u8 ,
117- reasons : std .ArrayListUnmanaged ([]const u8 ),
118- file : []const u8 ,
119- line : []const u8 ,
125+ wip_failure : Result.Failure ,
126+
127+ const Result = union (enum ) {
128+ start_suite : []const u8 ,
129+ ok ,
130+ failure : Failure ,
131+ feed_line ,
132+
133+ const Failure = struct {
134+ description : std .ArrayListUnmanaged (u8 ),
135+ reasons : std .ArrayListUnmanaged ([]const u8 ),
136+ };
120137 };
121138
122- const not_ok = "not ok " ;
123- const spacer = " - " ;
124- const yaml_blk = " ---" ;
125- const pre_reason = "reason: |" ;
126- const at = "at:" ;
127- const file = "file: " ;
128- const _line = "line: " ;
139+ const keyword = struct {
140+ const suite_start = "# start of suite " ;
141+ const ok = "ok " ;
142+ const not_ok = "not ok " ;
143+ const spacer1 = " - " ;
144+ const spacer2 = ": " ;
145+ const yaml_blk = " ---" ;
146+ const pre_reason = "reason: |" ;
147+ const at = "at:" ;
148+ const file = "file: " ;
149+ const line = "line: " ;
150+ };
129151
130152 const State = enum {
131153 start ,
@@ -137,68 +159,75 @@ const FailureParser = struct {
137159 line ,
138160 };
139161
140- fn parseLine (p : * FailureParser , allocator : Allocator , line : []const u8 ) Allocator.Error ! ? Failure {
162+ fn parseLine (p : * TapParser , step_arena : Allocator , line : []const u8 ) Allocator.Error ! Result {
141163 loop : switch (p .state ) {
142164 .start = > {
143- if (std .mem .startsWith (u8 , line , not_ok )) {
144- @branchHint (.unlikely );
165+ if (std .mem .startsWith (u8 , line , keyword .suite_start )) {
166+ const suite_start = skip (line , keyword .spacer2 , keyword .suite_start .len ) orelse @panic ("expected suite number" );
167+ return .{ .start_suite = line [suite_start .. ] };
168+ } else if (std .mem .startsWith (u8 , line , keyword .ok )) {
169+ return .ok ;
170+ } else if (std .mem .startsWith (u8 , line , keyword .not_ok )) {
145171 p .state = .desc ;
146172 continue :loop p .state ;
147173 }
148174 },
175+
176+ // Failure parsing
149177 .desc = > {
150- const name_start = spacer .len + (std .mem .indexOfPos (u8 , line , not_ok .len , spacer ) orelse @panic ("expected spacer" ));
151- p .fail .desc = try allocator .dupe (u8 , line [name_start .. ]);
178+ const name_start = skip (line , keyword .spacer1 , keyword .not_ok .len ) orelse @panic ("expected spacer" );
179+ try p .wip_failure .description .appendSlice (step_arena , line [name_start .. ]);
180+ try p .wip_failure .description .appendSlice (step_arena , ": " );
152181 p .state = .yaml_start ;
153182 },
154183 .yaml_start = > {
155- _ = std .mem .indexOf (u8 , line , yaml_blk ) orelse @panic ("expected yaml_blk" );
184+ _ = std .mem .indexOf (u8 , line , keyword . yaml_blk ) orelse @panic ("expected yaml_blk" );
156185 p .state = .pre_reason ;
157186 },
158187 .pre_reason = > {
159- _ = std .mem .indexOf (u8 , line , pre_reason ) orelse @panic ("expected pre_reason" );
188+ _ = std .mem .indexOf (u8 , line , keyword . pre_reason ) orelse @panic ("expected pre_reason" );
160189 p .state = .reason ;
161190 },
162191 .reason = > {
163- if (std .mem .indexOf (u8 , line , at ) != null ) {
192+ if (std .mem .indexOf (u8 , line , keyword . at ) != null ) {
164193 p .state = .file ;
165194 } else {
166195 const ln = std .mem .trim (u8 , line , & std .ascii .whitespace );
167- try p .fail .reasons .append (allocator , try allocator .dupe (u8 , ln ));
196+ try p .wip_failure .reasons .append (step_arena , try step_arena .dupe (u8 , ln ));
168197 }
169198 },
170199 .file = > {
171- const file_start = file .len + (std .mem .indexOf (u8 , line , file ) orelse @panic ("expected file" ));
172- p .fail .file = try allocator .dupe (u8 , std .mem .trim (u8 , line [file_start .. ], &.{'\' ' }));
200+ const file_start = skip (line , keyword .file , 0 ) orelse @panic ("expected file" );
201+ const file = std .mem .trim (u8 , line [file_start .. ], &.{'\' ' });
202+ try p .wip_failure .description .appendSlice (step_arena , file );
203+ try p .wip_failure .description .append (step_arena , ':' );
173204 p .state = .line ;
174205 },
175206 .line = > {
176- const line_start = _line . len + ( std . mem . indexOf ( u8 , line , _line ) orelse @panic ("expected line" ) );
177- p . fail . line = try allocator . dupe ( u8 , line [line_start .. ]);
207+ const line_start = skip ( line , keyword . line , 0 ) orelse @panic ("expected line" );
208+ try p . wip_failure . description . appendSlice ( step_arena , line [line_start .. ]);
178209 p .state = .start ;
179- return p . fail ;
210+ return .{ . failure = p . wip_failure } ;
180211 },
181212 }
182213
183- return null ;
214+ return .feed_line ;
215+ }
216+
217+ fn skip (line : []const u8 , to_skip : []const u8 , start : usize ) ? usize {
218+ const index = std .mem .indexOfPos (u8 , line , start , to_skip ) orelse return null ;
219+ return to_skip .len + index ;
184220 }
185221
186- const default : FailureParser = .{
222+ const default : TapParser = .{
187223 .state = .start ,
188- .fail = .{
189- .desc = undefined ,
190- .reasons = .{},
191- .file = undefined ,
192- .line = undefined ,
224+ .wip_failure = .{
225+ .description = .empty ,
226+ .reasons = .empty ,
193227 },
194228 };
195229
196- fn reset (p : * FailureParser , allocator : Allocator ) void {
197- for (p .fail .reasons .items ) | reason | allocator .free (reason );
198- p .fail .reasons .deinit (allocator );
199- allocator .free (p .fail .desc );
200- allocator .free (p .fail .file );
201- allocator .free (p .fail .line );
230+ fn reset (p : * TapParser ) void {
202231 p .* = default ;
203232 }
204233};
0 commit comments