@@ -24,21 +24,82 @@ struct Config {
2424 string dmdBinPath = " dmd" ;
2525 bool printLineNumbers; // whether line numbers should be shown on errors
2626}
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+ }
2887
2988int main (string [] args)
3089{
31- import std.conv , std. file , std.getopt , std.path ;
90+ import std.file , std.getopt , std.path ;
3291 import std.parallelism : parallel;
3392 import std.process : environment;
93+ import std.typecons : Tuple ;
3494
3595 auto specDir = __FILE_FULL_PATH__.dirName.buildPath(" spec" );
36- config.dmdBinPath = environment.get (" DMD" , " dmd" );
3796 bool hasFailed;
3897
98+ config.dmdBinPath = environment.get (" DMD" , " dmd" );
3999 auto helpInformation = getopt(
40100 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,
42103 );
43104
44105 if (helpInformation.helpWanted)
@@ -50,23 +111,22 @@ int main(string[] args)
50111 }
51112
52113 // 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+ ];
54125 foreach (file; specDir.dirEntries(" *.dd" , SpanMode.depth).parallel(1 ))
55126 {
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;
70130 if (! allTests.empty)
71131 {
72132 writefln(" %s: %d examples found" , file.baseName, allTests.walkLength);
@@ -77,6 +137,15 @@ int main(string[] args)
77137 return hasFailed;
78138}
79139
140+ struct CompileConfig
141+ {
142+ enum TestMode { run, compile, fail }
143+ TestMode mode;
144+ string [] args;
145+ string expectedStdout;
146+ string expectedStderr;
147+ }
148+
80149/**
81150Executes source code with a D compiler (compile-only)
82151
@@ -85,13 +154,29 @@ Params:
85154
86155Returns: the exit code of the compiler invocation.
87156*/
88- auto compileAndCheck (R)(R buffer)
157+ auto compileAndCheck (R)(R buffer, CompileConfig config )
89158{
90159 import std.process ;
91160 import std.uni : isWhite;
92161
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);
95180
96181 static mainRegex = regex(` (void|int)\s+main` );
97182 const hasMain = ! buffer.matchFirst(mainRegex).empty;
@@ -107,15 +192,27 @@ auto compileAndCheck(R)(R buffer)
107192 pipes.stdin.write(buffer);
108193 pipes.stdin.close;
109194 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 )
111208 {
112- stderr.writeln(" --- " );
209+ stderr.writeln(" ---" );
113210 int lineNumber = 1 ;
114211 buffer
115212 .splitter(" \n " )
116213 .each! ((a) {
117214 const indent = hasMain ? " " : " " ;
118- if (config.printLineNumbers)
215+ if (. config.printLineNumbers)
119216 stderr.writefln(" %3d: %s%s" , lineNumber++ , indent, a);
120217 else
121218 stderr.writefln(" %s%s" , indent, a);
@@ -124,5 +221,23 @@ auto compileAndCheck(R)(R buffer)
124221 stderr.writeln(" ---" );
125222 pipes.stderr.byLine.each! (e => stderr.writeln(e));
126223 }
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+ }}
127242 return ret;
128243}
0 commit comments