1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
|
(***********************************************************************)
(* *)
(* ocamlbuild *)
(* *)
(* Wojciech Meyer *)
(* *)
(* Copyright 2012 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the Q Public License version 1.0. *)
(* *)
(***********************************************************************)
open Format
external (|>) : 'a -> ('a -> 'b) -> 'b = "%revapply"
let print_list ~sep f ppf = function
| [] -> ()
| x :: [] -> f ppf x
| x :: xs -> f ppf x; List.iter (fun x -> sep ppf (); f ppf x) xs
let print_list_com f = print_list ~sep:(fun ppf () -> pp_print_string ppf ",") f
let print_list_blank f = print_list ~sep:(fun ppf () -> pp_print_string ppf " ") f
let print_string_list = print_list_com pp_print_string
let print_string_list_com = print_list_com pp_print_string
let print_string_list_blank = print_list_blank pp_print_string
let exists filename =
try ignore(Unix.stat filename); true
with Unix.Unix_error ((Unix.ENOENT),_,_) -> false
let execute cmd =
let ic = Unix.open_process_in cmd and lst = ref [] in
try while true do lst := input_line ic :: !lst done; assert false
with End_of_file ->
let ret_code = Unix.close_process_in ic
in ret_code, List.rev !lst
let rm f =
if exists f then
ignore(Sys.command (Printf.sprintf "rm -r ./%s" f))
module Match = struct
type atts = unit
(* File consists of file attribute and name *)
type file = atts * string
(* Result is an outcome of execution, if consists of returned exit code,
and stream from stdout *)
type result = int * string
type t =
(* Represents file in the tree *)
| F of file
(* Directory, consists of name and sub entries *)
| D of file * t list
(* Like file, but will be executed, and the result will compared *)
| X of file * result
(* Symlink *)
| L of file * file
(* We request that everything below should match exactly *)
| Exact of t
(* Here we want just the tree contained entities but we allow some
other stuff to be there too *)
| Contains of t
(* Any means that we match anything *)
| Any
(* Empty a tree leaf that don't match at all *)
| Empty
(* Type of error, we either expect something or something is un-expected *)
type error =
Expected of string
| Unexpected of string
| Structure of string * string list
| Output of string * string
(* This will print the tree *)
let print ppf tree =
let rec lines ppf lst =
List.iter (fun line -> pp_print_space ppf (); item ppf line) lst
and item ppf = function
| F (_, name) -> fprintf ppf "@[<h>%s@]" name
| D ((_, name), children) -> fprintf ppf "@[<v 1>@[<h>%s/@]%a@]" name lines children
| X ((_,name), _) -> fprintf ppf "@[<h>%s@]" name
| L ((_,src), (_,dst)) -> fprintf ppf "@[<h>%s->%s@]@" src dst
| Exact content -> fprintf ppf "{%a}" item content
| Contains content -> fprintf ppf "<%a>" item content
| Any -> pp_print_char ppf '*'
| Empty -> pp_print_char ppf '#'
in
pp_open_vbox ppf 0;
item ppf tree;
pp_close_box ppf ()
let f ?(atts=()) name = F (atts, name)
let d ?(atts=()) name children = D ((atts, name), children)
let lf ?(atts=()) lst = List.map (fun nm -> F (atts,nm)) lst
let x ?(atts=()) name ~output = X ((atts,name), (0,output))
let match_with_fs ~root m =
let errors = ref [] in
let rec visit ~exact path m =
let file name =
"./" ^ (List.rev (name :: path) |> String.concat "/")
in
let exists_assert filename =
if not (exists (file filename)) then
errors := Expected filename :: !errors;
in
let take_name = function
| F (_, name)
| D ((_, name),_) -> [name]
| _ -> []
in
match m with
| F ((),name) ->
exists_assert name
| D (((),name), sub) ->
exists_assert name;
let lst = List.flatten (List.map take_name sub) in
let lst' = Sys.readdir name |> Array.to_list in
let lst' = List.filter (fun x -> not (List.mem x lst)) lst' in
(if exact && lst' <> [] then
errors := Structure ((file name), lst') :: !errors);
List.iter (visit ~exact (name :: path)) sub
| X (((), name), (retcode, output)) ->
let _,output' = execute (file name) in
let output' = String.concat "\n" output' in
if output <> output' then
errors := Output (output, output') :: !errors
| Exact sub -> visit ~exact:true path sub
| Contains sub -> visit ~exact:false path sub
| _ -> assert false
in
let dir = Sys.getcwd () in
Unix.chdir root;
visit ~exact:false [] m;
Unix.chdir dir;
List.rev !errors
let string_of_error = function
| Expected s -> Printf.sprintf "expected '%s' on a file system" s
| Unexpected s -> Printf.sprintf "un-expected '%s' on a file system" s
| Structure (s,l) -> Printf.sprintf "directory structure '%s' has un-expected files %s" s (String.concat ", " l)
| Output (e, p) -> Printf.sprintf "not matching output '%s' expected but got %s" e p
end
module Option = struct
type flag = string
type path = string
type level = int
type package = string
type file = string
type command = string
type _module = string
type tag = string
type t =
[ `version
| `vnum
| `quiet
| `verbose of level
| `documentation
| `log of file
| `no_log
| `clean
| `r
| `I of path
| `Is of path list
| `X of path
| `Xs of path list
| `lib of flag
| `libs of flag list
| `_mod of _module
| `mods of _module list
| `pkg of package
| `pkgs of package list
| `package of package
| `lflag of flag
| `lflags of flag list
| `cflag of flag
| `cflags of flag list
| `docflag of flag
| `docflags of flag list
| `yaccflag of flag
| `yaccflags of flag list
| `lexflag of flag
| `lexflags of flag list
| `ppflag of flag
| `pp of flag list
| `tag of tag
| `tags of tag list
| `tag_line of tag
| `show_tags of path
| `ignore of _module list
| `no_links
| `no_skip
| `no_hygiene
| `no_plugin
| `no_stdlib
| `dont_catch_errors
| `just_plugin
| `byte_plugin
| `plugin_option
| `sanitization_script
| `no_sanitize
| `nothing_should_be_rebuilt
| `classic_display
| `use_menhir
| `use_jocaml
| `use_ocamlfind
| `j of level
| `build_dir of path
| `install_lib_dir of path
| `install_bin_dir of path
| `where
| `ocamlc of command
| `ocamlopt of command
| `ocamldep of command
| `ocamldoc of command
| `ocamlyacc of command
| `menhir of command
| `ocamllex of command
| `ocamlmktop of command
| `ocamlrun of command
| `help ]
type arg = string * string list
let print_level = pp_print_int
let print_flag = pp_print_string
let print_package = pp_print_string
let print_tag = pp_print_string
let print_tags = print_string_list_com
let print_path = pp_print_string
let print_paths = print_string_list_com
let print_flags = print_string_list_com
let print_module = pp_print_string
let print_modules = print_string_list_com
let print_packages = print_string_list_com
let print_command = pp_print_string
let print_opt ppf o =
fprintf ppf "-";
match o with
| `version -> fprintf ppf "version"
| `vnum -> fprintf ppf "vnum"
| `quiet -> fprintf ppf "quiet"
| `verbose level -> fprintf ppf "verbose %a" print_level level
| `documentation -> fprintf ppf "documentation"
| `log file -> fprintf ppf "log"
| `no_log -> fprintf ppf "no-log"
| `clean -> fprintf ppf "clean"
| `r -> fprintf ppf "r"
| `I path -> fprintf ppf "I %a" print_path path
| `Is paths -> fprintf ppf "Is %a" print_paths paths
| `X path -> fprintf ppf "X %a" print_path path
| `Xs paths -> fprintf ppf "Xs %a" print_paths paths
| `lib flag -> fprintf ppf "lib %a" print_flag flag
| `libs flags -> fprintf ppf "libs %a" print_flags flags
| `_mod _module -> fprintf ppf "mod %a" print_module _module
| `mods _modules -> fprintf ppf "mods %a" print_modules _modules
| `pkg package -> fprintf ppf "pkg %a" print_package package
| `pkgs packages -> fprintf ppf "pkgs %a" print_packages packages
| `package package -> fprintf ppf "package %a" print_package package
| `lflag flag -> fprintf ppf "lflag %a" print_flag flag
| `lflags flags -> fprintf ppf "lflags %a" print_flags flags
| `cflag flag -> fprintf ppf "cflag %a" print_flag flag
| `cflags flags -> fprintf ppf "cflags %a" print_flags flags
| `docflag flag -> fprintf ppf "docflag %a" print_flag flag
| `docflags flags -> fprintf ppf "docflags %a" print_flags flags
| `yaccflag flag -> fprintf ppf "yaccflag %a" print_flag flag
| `yaccflags flags -> fprintf ppf "yaccflags %a" print_flags flags
| `lexflag flag -> fprintf ppf "lexflag %a" print_flag flag
| `lexflags flags -> fprintf ppf "lexflags %a" print_flags flags
| `ppflag flag -> fprintf ppf "ppflag %a" print_flag flag
| `pp flags -> fprintf ppf "pp %a" print_flags flags
| `tag tag -> fprintf ppf "tag %a" print_tag tag
| `tags tags -> fprintf ppf "tags %a" print_tags tags
| `tag_line tag -> fprintf ppf "tag-line %a" print_tag tag
| `show_tags path -> fprintf ppf "show-tags %a" print_path path
| `ignore _modules -> fprintf ppf "ignore %a" print_modules _modules
| `no_links -> fprintf ppf "no-links"
| `no_skip -> fprintf ppf "no-skip"
| `no_hygiene -> fprintf ppf "no-hygiene"
| `no_plugin -> fprintf ppf "no-pluging"
| `no_stdlib -> fprintf ppf "no-stdlib"
| `dont_catch_errors -> fprintf ppf "dont"
| `just_plugin -> fprintf ppf "just-plugin"
| `byte_plugin -> fprintf ppf "byte-plugin"
| `plugin_option -> fprintf ppf "plugin-option"
| `sanitization_script -> fprintf ppf "sanitization-script"
| `no_sanitize -> fprintf ppf "no-sanitze"
| `nothing_should_be_rebuilt -> fprintf ppf "nothing_should_be_rebuilt"
| `classic_display -> fprintf ppf "classic-display"
| `use_menhir -> fprintf ppf "use-menhir"
| `use_jocaml -> fprintf ppf "use-jocaml"
| `use_ocamlfind -> fprintf ppf "use-ocamlfind"
| `j level -> fprintf ppf "j %a" print_level level
| `build_dir path -> fprintf ppf "build-dir %a" print_path path
| `install_lib_dir path -> fprintf ppf "install %a" print_path path
| `install_bin_dir path -> fprintf ppf "install %a" print_path path
| `where -> fprintf ppf "where"
| `ocamlc command -> fprintf ppf "ocamlc %a" print_command command
| `ocamlopt command -> fprintf ppf "ocamlopt %a" print_command command
| `ocamldep command -> fprintf ppf "ocamldep %a" print_command command
| `ocamldoc command -> fprintf ppf "ocamldoc %a" print_command command
| `ocamlyacc command -> fprintf ppf "ocamlyacc %a" print_command command
| `menhir command -> fprintf ppf "menhir %a" print_command command
| `ocamllex command -> fprintf ppf "ocamllex %a" print_command command
| `ocamlmktop command -> fprintf ppf "ocamlmktop %a" print_command command
| `ocamlrun command -> fprintf ppf "ocamlrun %a" print_command command
| `help -> fprintf ppf "help"
end
module Tree = struct
type name = string
type content = string
type t =
F of name * content
| D of name * t list
| E
let f ?(content="") name = F (name, content)
let d name children = D (name, children)
let create_on_fs ~root f =
let rec visit path f =
let file name =
List.rev (name :: path)
|> String.concat "/"
in
match f with
| F (name, content) ->
let ch = file name |> open_out in
output_string ch content;
close_out ch
| D (name, sub) ->
(* print_endline ("mking " ^ (file name)); *)
Unix.mkdir (file name) 0o750;
List.iter (visit (name :: path)) sub
| E -> ()
in
let dir = Sys.getcwd () in
Unix.chdir root;
visit [] f;
Unix.chdir dir
end
type content = string
type filename = string
type run = filename * content
type test = { name : string
; description : string
; tree : Tree.t list
; matching : Match.t list
; options : Option.t list
; targets : string * string list
; pre_cmd : string option
; run : run list }
let tests = ref []
let test ?(options=[]) ?(run=[]) ?pre_cmd
name
~description
~tree
~matching
~targets =
tests := !tests @ [{ name; description; tree; matching; options; targets; pre_cmd; run }]
let run ~root =
let command opts args =
let b = Buffer.create 127 in
let f = Format.formatter_of_buffer b in
fprintf f "%s %a %a" ocamlbuild (print_list_blank Option.print_opt) opts (print_list_blank pp_print_string) args;
Format.pp_print_flush f ();
Buffer.contents b
in
let one_test
{ name
; description
; tree
; matching
; options
; targets
; pre_cmd
; run } =
let dir = Sys.getcwd () in
Unix.chdir root;
rm name;
Unix.mkdir name 0o750;
List.iter (Tree.create_on_fs ~root:name) tree;
Unix.chdir name;
(match pre_cmd with
| None -> ()
| Some str -> ignore(Sys.command str));
let log_name = name ^ ".log" in
let cmd = command options (fst targets :: snd targets) in
Unix.(match execute cmd with
| WEXITED n,lines
| WSIGNALED n,lines
| WSTOPPED n,lines when n <> 0 ->
let ch = open_out log_name in
List.iter (fun l -> output_string ch l; output_string ch "\n") lines;
close_out ch;
Printf.printf "\x1b[0;31m\x1b[1m[FAILED]\x1b[0m \x1b[1m%-20s\x1b[0;33m%s.\n" name
(Printf.sprintf "Command '%s' with error code %n output written to %s" cmd n log_name);
Unix.chdir dir;
| _ ->
Unix.chdir dir;
Unix.chdir root;
let errors = List.concat (List.map (Match.match_with_fs ~root:name) matching) in
begin if errors == [] then
Printf.printf "\x1b[0;32m\x1b[1m[PASSED]\x1b[0m \x1b[1m%-20s\x1b[0;36m%s.\n" name description
else begin
let ch = open_out log_name in
output_string ch ("Run '" ^ cmd ^ "'\n");
List.iter (fun e -> output_string ch (Match.string_of_error e); output_string ch ".\n") errors;
close_out ch;
Printf.printf "\x1b[0;31m\x1b[1m[FAILED]\x1b[0m \x1b[1m%-20s\x1b[0;33m%s.\n" name
(Printf.sprintf "Some system checks failed, output written to %s" log_name)
end
end;
Unix.chdir dir)
in List.iter one_test !tests
|