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,
2
2
//! reporting progress/errors to the build system.
3
3
// Based on Step.Run
4
4
@@ -77,27 +77,39 @@ fn make(step: *Step, options: std.Build.Step.MakeOptions) !void {
77
77
const fifo = poller .fifo (.stdout );
78
78
const r = fifo .reader ();
79
79
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 ();
83
86
84
- var parser : FailureParser = .default ;
85
87
while (true ) {
86
88
r .streamUntilDelimiter (w , '\n ' , null ) catch | err | switch (err ) {
87
89
error .EndOfStream = > if (try poller .poll ()) continue else break ,
88
90
else = > return err ,
89
91
};
90
92
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 = > {},
101
113
}
102
114
}
103
115
@@ -108,24 +120,34 @@ fn make(step: *Step, options: std.Build.Step.MakeOptions) !void {
108
120
try step .writeManifestAndWatch (& man );
109
121
}
110
122
111
- const FailureParser = struct {
123
+ const TapParser = struct {
112
124
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
+ };
120
137
};
121
138
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
+ };
129
151
130
152
const State = enum {
131
153
start ,
@@ -137,68 +159,75 @@ const FailureParser = struct {
137
159
line ,
138
160
};
139
161
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 {
141
163
loop : switch (p .state ) {
142
164
.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 )) {
145
171
p .state = .desc ;
146
172
continue :loop p .state ;
147
173
}
148
174
},
175
+
176
+ // Failure parsing
149
177
.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 , ": " );
152
181
p .state = .yaml_start ;
153
182
},
154
183
.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" );
156
185
p .state = .pre_reason ;
157
186
},
158
187
.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" );
160
189
p .state = .reason ;
161
190
},
162
191
.reason = > {
163
- if (std .mem .indexOf (u8 , line , at ) != null ) {
192
+ if (std .mem .indexOf (u8 , line , keyword . at ) != null ) {
164
193
p .state = .file ;
165
194
} else {
166
195
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 ));
168
197
}
169
198
},
170
199
.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 , ':' );
173
204
p .state = .line ;
174
205
},
175
206
.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 .. ]);
178
209
p .state = .start ;
179
- return p . fail ;
210
+ return .{ . failure = p . wip_failure } ;
180
211
},
181
212
}
182
213
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 ;
184
220
}
185
221
186
- const default : FailureParser = .{
222
+ const default : TapParser = .{
187
223
.state = .start ,
188
- .fail = .{
189
- .desc = undefined ,
190
- .reasons = .{},
191
- .file = undefined ,
192
- .line = undefined ,
224
+ .wip_failure = .{
225
+ .description = .empty ,
226
+ .reasons = .empty ,
193
227
},
194
228
};
195
229
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 {
202
231
p .* = default ;
203
232
}
204
233
};
0 commit comments