@@ -14,6 +14,8 @@ module Node = {
1414 @scope (("process" , "stderr" ))
1515 external stderrWrite : string => unit = "write"
1616 @scope ("process" ) external cwd : unit => string = "cwd"
17+ @val @scope ("process" )
18+ external argv : array <string > = "argv"
1719 }
1820
1921 module Fs = {
@@ -42,9 +44,10 @@ module Node = {
4244 external spawnSync : (string , array <string >) => spawnSyncReturns = "spawnSync"
4345
4446 type readable
45- type spawnReturns = {stderr : readable }
47+ type spawnReturns = {stderr : readable , stdout : readable }
48+ type options = {cwd ?: string , env ?: Dict .t <string >, timeout ?: int }
4649 @module ("child_process" )
47- external spawn : (string , array <string >) => spawnReturns = "spawn"
50+ external spawn : (string , array <string >, ~ options : options = ? ) => spawnReturns = "spawn"
4851
4952 @send external on : (readable , string , Buffer .t => unit ) => unit = "on"
5053 @send
@@ -56,6 +59,19 @@ module Node = {
5659 @module ("os" )
5760 external tmpdir : unit => string = "tmpdir"
5861 }
62+
63+ module Util = {
64+ type arg = {@as ("type" ) type_ : string }
65+ type config = {
66+ args : array <string >,
67+ options : Dict .t <arg >,
68+ }
69+ type parsed = {
70+ values : Dict .t <string >,
71+ positionals : array <string >,
72+ }
73+ @module ("node:util" ) external parseArgs : config => parsed = "parseArgs"
74+ }
5975}
6076
6177open Node
@@ -64,6 +80,45 @@ module Docgen = RescriptTools.Docgen
6480
6581let bscBin = Path .join (["cli" , "bsc" ])
6682
83+ let options = Dict .fromArray ([("ignore-runtime-tests" , {Util .type_ : "string" })])
84+
85+ let {Util .values : values } = Util .parseArgs ({
86+ args : Process .argv -> Array .sliceToEnd (~start = 2 ),
87+ options ,
88+ })
89+
90+ let ignoreRuntimeTests = switch values -> Dict .get ("ignore-runtime-tests" ) {
91+ | Some (v ) =>
92+ v
93+ -> String .split ("," )
94+ -> Array .map (s => s -> String .trim )
95+ | None => []
96+ }
97+
98+ module SpawnAsync = {
99+ type t = {
100+ stdout : array <Buffer .t >,
101+ stderr : array <Buffer .t >,
102+ code : Null .t <float >,
103+ }
104+ let run = async (~command , ~args , ~options = ?) => {
105+ await Promise .make ((resolve , _reject ) => {
106+ let spawn = ChildProcess .spawn (command , args , ~options ?)
107+ let stdout = []
108+ let stderr = []
109+ spawn .stdout -> ChildProcess .on ("data" , data => {
110+ Array .push (stdout , data )
111+ })
112+ spawn .stderr -> ChildProcess .on ("data" , data => {
113+ Array .push (stderr , data )
114+ })
115+ spawn -> ChildProcess .once ("close" , (code , _signal ) => {
116+ resolve ({stdout , stderr , code })
117+ })
118+ })
119+ }
120+ }
121+
67122type example = {
68123 id : string ,
69124 kind : string ,
@@ -73,35 +128,75 @@ type example = {
73128
74129let createFileInTempDir = id => Path .join2 (OS .tmpdir (), id )
75130
76- let testCode = async (~id , ~code ) => {
131+ let compileTest = async (~id , ~code ) => {
77132 let id = id -> String .includes ("/" ) ? String .replace (id , "/" , "slash_op" ) : id
78133 let tempFileName = createFileInTempDir (id )
79134
80135 let () = await Fs .writeFile (tempFileName ++ ".res" , code )
81136
82137 let args = [tempFileName ++ ".res" , "-w" , "-3-109" ]
83138
84- let promise = await Promise .make ((resolve , _reject ) => {
85- let spawn = ChildProcess .spawn (bscBin , args )
86- let stderr = []
87- spawn .stderr -> ChildProcess .on ("data" , data => {
88- Array .push (stderr , data )
89- })
90- spawn -> ChildProcess .once ("close" , (_code , _signal ) => {
91- resolve (stderr )
92- })
93- })
139+ let {stderr , stdout } = await SpawnAsync .run (~command = bscBin , ~args )
94140
95- switch Array .length (promise ) > 0 {
141+ switch Array .length (stderr ) > 0 {
96142 | true =>
97- promise
143+ stderr
98144 -> Array .map (e => e -> Buffer .toString )
99145 -> Array .join ("" )
100146 -> Error
101- | false => Ok ()
147+ | false =>
148+ stdout
149+ -> Array .map (e => e -> Buffer .toString )
150+ -> Array .join ("" )
151+ -> Ok
102152 }
103153}
104154
155+ let runtimeTests = async code => {
156+ let {stdout , stderr , code : exitCode } = await SpawnAsync .run (
157+ ~command = "node" ,
158+ ~args = ["-e" , code ],
159+ ~options = {
160+ cwd : Process .cwd (),
161+ timeout : 2000 ,
162+ },
163+ )
164+
165+ // Some expressions, like, `console.error("error")` is printed to stderr but
166+ // exit code is 0
167+ let std = switch exitCode -> Null .toOption {
168+ | Some (exitCode ) if exitCode == 0.0 && Array .length (stderr ) > 0 => stderr -> Ok
169+ | Some (exitCode ) if exitCode == 0.0 => stdout -> Ok
170+ | None | Some (_ ) => Error (Array .length (stderr ) > 0 ? stderr : stdout )
171+ }
172+
173+ switch std {
174+ | Ok (buf ) =>
175+ buf
176+ -> Array .map (e => e -> Buffer .toString )
177+ -> Array .join ("" )
178+ -> Ok
179+ | Error (buf ) =>
180+ buf
181+ -> Array .map (e => e -> Buffer .toString )
182+ -> Array .join ("" )
183+ -> Error
184+ }
185+ }
186+
187+ let indentOutputCode = code => {
188+ let indent = String .repeat (" " , 2 )
189+
190+ code
191+ -> String .split ("\n " )
192+ -> Array .map (s => ` ${indent}${s}` )
193+ -> Array .join ("\n " )
194+ }
195+
196+ type error =
197+ | ReScript ({error : string })
198+ | Runtime ({rescript : string , js : string , error : string })
199+
105200let extractDocFromFile = file => {
106201 let toolsBin = Path .join ([Process .cwd (), "cli" , "rescript-tools" ])
107202 let spawn = ChildProcess .spawnSync (toolsBin , ["doc" , file ])
@@ -209,54 +304,90 @@ let main = async () => {
209304 await codes
210305 -> Array .mapWithIndex (async (code , int ) => {
211306 let id = ` ${id}_${Int.toString(int)}`
212- await testCode (~id , ~code )
307+ ( code , await compileTest (~id , ~code ) )
213308 })
214309 -> Promise .all
215310 (example , results )
216311 })
217312 -> Promise .all
218313
219- let errors = results -> Belt .Array .keepMap (((example , results )) => {
220- let errors = results -> Belt .Array .keepMap (result =>
314+ let examples = results -> Array .map (((example , results )) => {
315+ let (compiled , errors ) = results -> Array .reduce (([], []), (acc , (resCode , result )) => {
316+ let (oks , errors ) = acc
221317 switch result {
222- | Ok () => None
223- | Error (msg ) => Some ( msg )
318+ | Ok (jsCode ) => ([ ... oks , ( resCode , jsCode )], errors )
319+ | Error (output ) => ( oks , [ ... errors , ReScript ({ error : output })] )
224320 }
225- )
226-
227- if Array .length (errors ) > 0 {
228- Some ((example , errors ))
229- } else {
230- None
231- }
321+ })
322+ (example , (compiled , errors ))
232323 })
233324
325+ let exampleErrors =
326+ await examples
327+ -> Array .filter ((({id }, _ )) => ! Array .includes (ignoreRuntimeTests , id ))
328+ -> Array .map (async ((example , (compiled , errors ))) => {
329+ let nodeTests =
330+ await compiled
331+ -> Array .map (async ((res , js )) => (res , js , await runtimeTests (js )))
332+ -> Promise .all
333+
334+ let runtimeErrors = nodeTests -> Belt .Array .keepMap (((res , js , output )) =>
335+ switch output {
336+ | Ok (_ ) => None
337+ | Error (error ) => Some (Runtime ({rescript : res , js , error }))
338+ }
339+ )
340+
341+ (example , Array .concat (runtimeErrors , errors ))
342+ })
343+ -> Promise .all
344+
234345 // Print Errors
235- let () = errors -> Array .forEach (((test , errors )) => {
346+ let () = exampleErrors -> Array .forEach (((example , errors )) => {
236347 let red = s => ` \x1B [1;31m${s}\x1B [0m`
237348 let cyan = s => ` \x1b [36m${s}\x1b [0m`
238- let kind = switch test .kind {
349+ let kind = switch example .kind {
239350 | "moduleAlias" => "module alias"
240351 | other => other
241352 }
242353
243- let errorMessage =
244- errors
245- -> Array .map (e => {
246- // Drop line from path file
247- e
248- -> String .split ("\n " )
249- -> Array .filterWithIndex ((_ , i ) => i !== 2 )
250- -> Array .join ("\n " )
251- })
252- -> Array .join ("\n " )
354+ let errorMessage = errors -> Array .map (err =>
355+ switch err {
356+ | ReScript ({error }) =>
357+ let err =
358+ error
359+ -> String .split ("\n " )
360+ -> Array .filterWithIndex ((_ , i ) => i !== 2 )
361+ -> Array .join ("\n " )
362+
363+ ` ${"error" -> red}: failed to compile examples from ${kind} ${example.id-> cyan}
364+ ${err}`
365+ | Runtime ({rescript , js , error }) =>
366+ let indent = String .repeat (" " , 2 )
367+
368+ ` ${"runtime error" -> red}: failed to run examples from ${kind} ${example.id-> cyan}
369+
370+ ${indent}${"ReScript" -> cyan}
253371
254- let message = ` ${ "error" -> red}: failed to compile examples from ${kind} ${test.id -> cyan} \n ${errorMessage}`
372+ ${rescript -> indentOutputCode}
255373
256- Process .stderrWrite (message )
374+ ${indent}${"Compiled Js" -> cyan}
375+
376+ ${js-> indentOutputCode}
377+
378+ ${indent}${"stacktrace" -> red}
379+
380+ ${error-> indentOutputCode}
381+ `
382+ }
383+ )
384+
385+ errorMessage -> Array .forEach (e => Process .stderrWrite (e ))
257386 })
258387
259- errors -> Array .length == 0 ? 0 : 1
388+ let someError = exampleErrors -> Array .some (((_ , err )) => Array .length (err ) > 0 )
389+
390+ someError ? 1 : 0
260391}
261392
262393let exitCode = await main ()
0 commit comments