@@ -24,21 +24,82 @@ struct Config {
24
24
string dmdBinPath = " dmd" ;
25
25
bool printLineNumbers; // whether line numbers should be shown on errors
26
26
}
27
- Config config;
27
+ shared Config config;
28
+
29
+ // a range until the next ')', nested () are ignored
30
+ auto untilClosingParentheses (R)(R rs)
31
+ {
32
+ struct State
33
+ {
34
+ uint rightParensCount = 1 ;
35
+ bool inCodeBlock;
36
+ uint dashCount;
37
+ }
38
+ return rs.cumulativeFold! ((state, r){
39
+ if (r == ' -' )
40
+ {
41
+ state.dashCount++ ;
42
+ }
43
+ else
44
+ {
45
+ if (state.dashCount >= 3 )
46
+ state.inCodeBlock = ! state.inCodeBlock;
47
+ state.dashCount = 0 ;
48
+ }
49
+ switch (r)
50
+ {
51
+ case ' -' :
52
+ break ;
53
+ case ' (' :
54
+ if (! state.inCodeBlock)
55
+ state.rightParensCount++ ;
56
+ break ;
57
+ case ' )' :
58
+ if (! state.inCodeBlock)
59
+ state.rightParensCount-- ;
60
+ break ;
61
+ default :
62
+ }
63
+ return state;
64
+ })(State()).zip(rs).until! (e => e[0 ].rightParensCount == 0 ).map! (e => e[1 ]);
65
+ }
66
+
67
+ unittest
68
+ {
69
+ import std.algorithm.comparison : equal;
70
+ assert (" aa $(foo $(bar)foobar)" .untilClosingParentheses.equal(" aa $(foo $(bar)foobar)" ));
71
+ }
72
+
73
+ auto findDdocMacro (R)(R text, string ddocKey)
74
+ {
75
+ return text.splitter(ddocKey).map! untilClosingParentheses.dropOne;
76
+ }
77
+
78
+ auto ddocMacroToCode (R)(R text)
79
+ {
80
+ import std.ascii : newline;
81
+ import std.conv : to;
82
+ return text.find(" ---" )
83
+ .findSplitAfter(newline)[1 ]
84
+ .findSplitBefore(" ---" )[0 ]
85
+ .to! string ;
86
+ }
28
87
29
88
int main (string [] args)
30
89
{
31
- import std.conv , std. file , std.getopt , std.path ;
90
+ import std.file , std.getopt , std.path ;
32
91
import std.parallelism : parallel;
33
92
import std.process : environment;
93
+ import std.typecons : Tuple ;
34
94
35
95
auto specDir = __FILE_FULL_PATH__.dirName.buildPath(" spec" );
36
- config.dmdBinPath = environment.get (" DMD" , " dmd" );
37
96
bool hasFailed;
38
97
98
+ config.dmdBinPath = environment.get (" DMD" , " dmd" );
39
99
auto helpInformation = getopt(
40
100
args,
41
- " l|lines" , " Show the line numbers on errors" , &config.printLineNumbers,
101
+ " l|lines" , " Show the line numbers on errors" , cast (bool * ) &config.printLineNumbers,
102
+ " compiler" , " D compiler to use" , cast (string * ) &config.dmdBinPath,
42
103
);
43
104
44
105
if (helpInformation.helpWanted)
@@ -50,23 +111,22 @@ int main(string[] args)
50
111
}
51
112
52
113
// Find all examples in the specification
53
- auto r = regex(` SPEC_RUNNABLE_EXAMPLE\n\s*---+\n[^-]*---+\n\s*\)` , " s" );
114
+ alias findExamples = (file, ddocKey) => file
115
+ .readText
116
+ .findDdocMacro(ddocKey)
117
+ .map! ddocMacroToCode;
118
+
119
+ alias SpecType = Tuple ! (string , " key" , CompileConfig.TestMode, " mode" );
120
+ auto specTypes = [
121
+ SpecType(" $(SPEC_RUNNABLE_EXAMPLE_COMPILE" , CompileConfig.TestMode.compile),
122
+ SpecType(" $(SPEC_RUNNABLE_EXAMPLE_RUN" , CompileConfig.TestMode.run),
123
+ SpecType(" $(SPEC_RUNNABLE_EXAMPLE_FAIL" , CompileConfig.TestMode.fail),
124
+ ];
54
125
foreach (file; specDir.dirEntries(" *.dd" , SpanMode.depth).parallel(1 ))
55
126
{
56
- import std.ascii : newline;
57
- import std.uni : isWhite;
58
- auto allTests =
59
- file
60
- .readText
61
- .matchAll(r)
62
- .map! (a => a[0 ])
63
- .map! (a => a
64
- .find(" ---" )
65
- .findSplitAfter(newline)[1 ]
66
- .findSplitBefore(" ---" )[0 ]
67
- .to! string )
68
- .map! compileAndCheck;
69
-
127
+ auto allTests = specTypes.map! (c => findExamples(file, c.key)
128
+ .map! (e => compileAndCheck(e, CompileConfig(c.mode))))
129
+ .joiner;
70
130
if (! allTests.empty)
71
131
{
72
132
writefln(" %s: %d examples found" , file.baseName, allTests.walkLength);
@@ -77,6 +137,15 @@ int main(string[] args)
77
137
return hasFailed;
78
138
}
79
139
140
+ struct CompileConfig
141
+ {
142
+ enum TestMode { run, compile, fail }
143
+ TestMode mode;
144
+ string [] args;
145
+ string expectedStdout;
146
+ string expectedStderr;
147
+ }
148
+
80
149
/**
81
150
Executes source code with a D compiler (compile-only)
82
151
@@ -85,13 +154,29 @@ Params:
85
154
86
155
Returns: the exit code of the compiler invocation.
87
156
*/
88
- auto compileAndCheck (R)(R buffer)
157
+ auto compileAndCheck (R)(R buffer, CompileConfig config )
89
158
{
90
159
import std.process ;
91
160
import std.uni : isWhite;
92
161
93
- auto pipes = pipeProcess([config.dmdBinPath, " -c" , " -o-" , " -" ],
94
- Redirect.stdin | Redirect.stdout | Redirect.stderr);
162
+ string [] args = [.config.dmdBinPath];
163
+ args ~= config.args;
164
+ with (CompileConfig.TestMode)
165
+ final switch (config.mode)
166
+ {
167
+ case run:
168
+ args ~= [" -run" ];
169
+ break ;
170
+ case compile:
171
+ args ~= [" -c" , " -o-" ];
172
+ break ;
173
+ case fail:
174
+ args ~= [" -c" , " -o-" ];
175
+ break ;
176
+ }
177
+ args ~= " -" ;
178
+
179
+ auto pipes = pipeProcess(args, Redirect.all);
95
180
96
181
static mainRegex = regex(` (void|int)\s+main` );
97
182
const hasMain = ! buffer.matchFirst(mainRegex).empty;
@@ -107,15 +192,27 @@ auto compileAndCheck(R)(R buffer)
107
192
pipes.stdin.write(buffer);
108
193
pipes.stdin.close;
109
194
auto ret = wait(pipes.pid);
110
- if (ret != 0 )
195
+ if (config.mode == CompileConfig.TestMode.fail)
196
+ {
197
+ if (ret == 0 )
198
+ {
199
+ stderr.writefln(" Compilation should have failed for:\n %s" , buffer);
200
+ ret = 1 ;
201
+ }
202
+ else
203
+ {
204
+ ret = 0 ;
205
+ }
206
+ }
207
+ else if (ret != 0 )
111
208
{
112
- stderr.writeln(" --- " );
209
+ stderr.writeln(" ---" );
113
210
int lineNumber = 1 ;
114
211
buffer
115
212
.splitter(" \n " )
116
213
.each! ((a) {
117
214
const indent = hasMain ? " " : " " ;
118
- if (config.printLineNumbers)
215
+ if (. config.printLineNumbers)
119
216
stderr.writefln(" %3d: %s%s" , lineNumber++ , indent, a);
120
217
else
121
218
stderr.writefln(" %s%s" , indent, a);
@@ -124,5 +221,23 @@ auto compileAndCheck(R)(R buffer)
124
221
stderr.writeln(" ---" );
125
222
pipes.stderr.byLine.each! (e => stderr.writeln(e));
126
223
}
224
+ // check stdout or stderr
225
+ static foreach (stream; [" stdout" , " stderr" ])
226
+ {{
227
+ import std.ascii : toUpper;
228
+ import std.conv : to;
229
+ mixin (" auto expected = config.expected" ~ stream.front.toUpper.to! string ~ stream.dropOne~ " ;" );
230
+ if (expected)
231
+ {
232
+ mixin (" auto stream = pipes." ~ stream ~ " ;" );
233
+ auto obs = appender! string ;
234
+ stream.byChunk(4096 ).each! (c => obs.put(c));
235
+ scope (failure) {
236
+ stderr.writefln(" Expected: %s" , expected);
237
+ stderr.writefln(" Observed: %s" , obs.data);
238
+ }
239
+ assert (obs.data == expected);
240
+ }
241
+ }}
127
242
return ret;
128
243
}
0 commit comments