File: regtest.ml

package info (click to toggle)
why 2.13-2
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 12,608 kB
  • ctags: 16,817
  • sloc: ml: 102,672; java: 7,173; ansic: 4,439; makefile: 1,409; sh: 585
file content (777 lines) | stat: -rw-r--r-- 23,288 bytes parent folder | download
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
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
(** the options to launch the toplevel with if the test file is not
     annotated with test options *)
let default_options = [ "" ]

(** the list of tests suites to consider *)
let default_suites =
[ "java" ]

let () =
  Unix.putenv "FRAMAC_SHARE" (Filename.concat Filename.current_dir_name "share");
  Unix.putenv "OCAMLRUNPARAM" ""

(** the name of the directory-wide configuration file*)
let dir_config_file = "test_config"

(** the files in [suites] whose name matches
    the pattern [test_file_regexp] will be considered as test files *)
let test_file_regexp = ".*\\.\\(java\\)$"

(** the pattern that ends the parsing of options in a test file *)
let end_comment = Str.regexp ".*\\*/"

let opt_to_byte =
  let opt = Str.regexp "[.]opt$" in
  function toplevel ->
    Str.global_replace opt ".byte" toplevel

let base_path = Filename.current_dir_name
(*    (Filename.concat
        (Filename.dirname Sys.executable_name)
	Filename.parent_dir_name)
*)

let test_path = "tests"

(** Command-line flags *)

type behavior = Examine | Update | Run | Show
let behavior = ref Run
let verbosity = ref 0
let use_byte = ref false
let do_diffs = ref "diff -u"
let n = ref 4    (* the level of parallelism *)
let suites = ref []
(** options given to toplevel for all tests *)
let additional_options = ref ""
(** special configuration, with associated oracles *)
let special_config = ref ""

let m = Mutex.create ()

let lock_fprintf f =
  Mutex.lock m;
  Format.kfprintf (fun _ -> Mutex.unlock m) f

let lock_printf s = lock_fprintf Format.std_formatter s
let lock_eprintf s = lock_fprintf Format.err_formatter s

let make_test_suite s =
  suites := s :: !suites

let () = Arg.parse
  [
    "", Arg.Unit (fun () -> ()) , "" ;
    "-examine", Arg.Unit (fun () -> behavior := Examine) ,
    " Examine the logs that are different from oracles.";
    "-update", Arg.Unit (fun () -> behavior := Update) ,
    " Take the current logs as oracles.";
    "-show", Arg.Unit (fun () -> behavior := Show; use_byte := true) ,
    " Show the results of the tests. Sets -byte.";
    "-run", Arg.Unit (fun () -> behavior := Run) ,
    "(default)  Delete the logs, run the tests, then examine the logs that are different from the oracles.";
    "", Arg.Unit (fun () -> ()) , "" ;
    "-v", Arg.Unit (fun () -> incr verbosity), " Increase verbosity (up to twice)" ;
    "-diff", Arg.String (fun s -> do_diffs := s), "<command>  Use command for diffs" ;
    "-j", Arg.Int (fun i -> if i>=0 then n := i else ( lock_printf "Option -j requires nonnegative argument@."; exit (-1))), "<n>  Use nonnegative integer n for level of parallelism" ;
    "-byte", Arg.Set use_byte, " Use bytecode toplevel";
    "-opt", Arg.Clear use_byte, " Use native toplevel (default)";
    "-config", Arg.Set_string special_config, " Use special configuration \
      and oracles";
    "-add-options", Arg.Set_string additional_options,
    "add additional options to be passed to the toplevels \
     that will be launched";
    "", Arg.Unit (fun () -> ()) ,"\nA test suite can be the name of a directory in ./tests or the path to a file.\n\nExamples:\nptests\nptests -diff \"echo diff\" -examine     # see again the list of tests that failed\nptests misc                           # for a single test suite\nptests tests/misc/alias.c             # for a single test\nptests -examine tests/misc/alias.c    # to see the differences again\nptests -v -j 1                        # to check the time taken by each test\n"
  ]
  make_test_suite
  "usage: ptests [options] [names of test suites]"

(* redefine config file if special configuration expected *)
let dir_config_file = 
  if !special_config = "" then dir_config_file else
    dir_config_file ^ "_" ^ !special_config

let make_toplevel_path exec = exec
(*  if Filename.is_relative exec then
    Filename.concat (Filename.concat base_path "bin") exec
  else exec
*)

(* redefine oracle directory if special configuration expected *)
let oracle_dirname = 
  if !special_config = "" then "oracle" else
    "oracle_" ^ !special_config
 
(* redefine result directory if special configuration expected *)
let result_dirname = 
  if !special_config = "" then "result" else
    "result_" ^ !special_config

let gen_make_file s dir file = Filename.concat (Filename.concat dir s) file
let make_result_file = gen_make_file result_dirname
let make_oracle_file = gen_make_file oracle_dirname

let toplevel_path =
  make_toplevel_path (gen_make_file "bin" base_path "krakatoa.opt")

type execnow =
    {
      ex_cmd: string;      (** command to launch *)
      ex_log: string list; (** log files *)
      ex_bin: string list; (** bin files *)
      ex_dir: string;      (** directory of test suite *)
    }

(** configuration of a directory/test. *)
type config =
    {
      dc_test_regexp: string; (** regexp of test files. *)
      dc_execnow    : execnow option; (** command to be launched before
                                         the toplevel(s)
                                     *)
      dc_toplevel   : string; (** full path of the toplevel used *)
      dc_filter     : string option; (** optional filter to apply to
			      standard output *)
      dc_options    : string list; (** options to launch the toplevel on *)
      dc_dont_run   : bool;
    }

let default_config =
  { dc_test_regexp = test_file_regexp ;
    dc_execnow = None;
    dc_toplevel = toplevel_path ;
    dc_filter = None ;
    dc_options = default_options ;
    dc_dont_run = false;
  }

let launch command_string =
  let result = Unix.system command_string in
  match result with
  | Unix.WEXITED 127 ->
      lock_printf "%% Couldn't execute command. Retrying once.@.";
      Thread.delay 0.1;
      ( match Unix.system command_string with
	Unix.WEXITED r when r <> 127 -> r
      | _ -> lock_printf "%% Retry failed, exiting.@."; exit 1 )
  | Unix.WEXITED r -> r
  | Unix.WSIGNALED s ->
      lock_printf "%% SIGNAL %d received while executing command:@\n%s@\nStopping@."
	s command_string ;
      exit 1
  | Unix.WSTOPPED s ->
      lock_printf "%% STOP %d received while executing command:@\n%s@\nStopping@."
	s command_string;
      exit 1

let scan_execnow dir (s:string) =
  let rec aux (s:execnow) =
    try
      Scanf.sscanf s.ex_cmd "%[ ]LOG %[A-Za-z0-9_',+=:.] %s@\n"
	(fun _ name cmd ->
	   aux { s with ex_cmd = cmd; ex_log = name :: s.ex_log })
    with Scanf.Scan_failure _ ->
      try
	Scanf.sscanf s.ex_cmd "%[ ]BIN %[A-Za-z0-9.] %s@\n"
	  (fun _ name cmd ->
	     aux { s with ex_cmd = cmd; ex_bin = name :: s.ex_bin })
      with Scanf.Scan_failure _ ->
	s
  in
  aux { ex_cmd = s; ex_log = []; ex_bin = []; ex_dir = dir }

(* how to process options *)
let config_options =
  [ "CMD",
    (fun _ s (current,rev_opts) ->
       { current with dc_toplevel = make_toplevel_path s}, rev_opts);

    "OPT",
    (fun _ s (current,rev_opts) -> current, (s::rev_opts) );

    "FILEREG",
    (fun _ s (current,rev_opts) ->
       { current with dc_test_regexp = s }, rev_opts );

    "FILTER",
    (fun _ s (current,rev_opts) ->
       { current with dc_filter = Some s }, rev_opts );

    "GCC",
    (fun _ _ acc -> acc);

    "COMMENT",
    (fun _ _ acc -> acc);

    "DONTRUN",
    (fun _ s (current,rev_opts) ->
       { current with dc_dont_run = true }, rev_opts );

    "EXECNOW",
    (fun dir s (current,rev_opts)->
       let execnow = scan_execnow dir s in
       { current with dc_execnow = Some execnow }, rev_opts);
  ]

let scan_options dir scan_buffer default =
  let r = ref (default, [])  in
  let treat_line s =
    try
      Scanf.sscanf s "%[ *]%[A-Za-z0-9]:%s@\n"
        (fun _ name opt ->
          try
            r := (List.assoc name config_options) dir opt !r
          with Not_found ->
            lock_eprintf "@[unknown configuration option: %s@\n%!@]" name)
    with Scanf.Scan_failure _ ->
      if Str.string_match end_comment s 0
      then raise End_of_file
      else ()
  in
  try
    while true do
      Scanf.bscanf scan_buffer "%s@\n" treat_line
    done;
    assert false
  with
    End_of_file ->
      let rev_options = snd !r in
      let options =
	if rev_options = []
	then default.dc_options
	else List.rev rev_options
      in
      { (fst !r) with dc_options = options }

let scan_test_file default dir f =
  let f = Filename.concat dir f in
  let exists_as_file =
    try
      (Unix.lstat f).Unix.st_kind = Unix.S_REG
    with Unix.Unix_error _ | Sys_error _ -> false
  in
    if exists_as_file then begin
        let scan_buffer = Scanf.Scanning.from_file f in
          try
            Scanf.bscanf scan_buffer "/* run.config%s@\n" (fun _ -> ());
            scan_options dir scan_buffer default
          with
            | End_of_file
            | Scanf.Scan_failure _ ->
                default
      end else
      (* if the file has disappeared, don't try to run it... *)
      { default with dc_dont_run = true }

type toplevel_command =
    { file : string ;
      options : string ;
      toplevel: string ;
      filter : string option ;
      directory : string ;
      n : int }

type command =
  | Toplevel of toplevel_command
  | Target of execnow * command Queue.t

type log = Err | Res

type diff =
  | Command_error of toplevel_command * log
  | Target_error of execnow
  | Log_error of string (** directory *) * string (** file *)

type cmps =
  | Cmp_Toplevel of toplevel_command
  | Cmp_Log of string (** directory *) * string (** file *)

type shared =
    { lock : Mutex.t ;
      lock_target : Mutex.t ;
      commands_empty : Condition.t ;
      work_available : Condition.t ;
      diff_available : Condition.t ;
      mutable commands : command Queue.t ; (* file, options, number *)
      cmps : cmps Queue.t ;
      (* command that has finished its execution *)
      diffs : diff Queue.t ;
      (* cmp that showed some difference *)
      mutable commands_finished : bool ;
      mutable cmp_finished : bool ;
      mutable summary_run : int ;
      mutable summary_ok : int ;
      mutable summary_log : int;
    }

let shared =
  { lock = Mutex.create () ;
    lock_target = Mutex.create () ;
    commands_empty = Condition.create () ;
    work_available = Condition.create () ;
    diff_available = Condition.create () ;
    commands = Queue.create () ;
    cmps = Queue.create () ;
    diffs = Queue.create () ;
    commands_finished = false ;
    cmp_finished = false ;
    summary_run = 0 ;
    summary_ok = 0 ;
    summary_log = 0 }

let unlock () = Mutex.unlock shared.lock

let lock () = Mutex.lock shared.lock

let catenate_number prefix n =
  if n > 0
  then prefix ^ "." ^ (string_of_int n)
  else prefix

let name_without_extension command =
  try
    (Filename.chop_extension command.file)
  with
    Invalid_argument _ ->
      failwith ("Ce nom de fichier de test ne comporte pas d'extension: " ^
		   command.file)

let gen_prefix s cmd =
  let prefix = gen_make_file s cmd.directory (name_without_extension cmd) in
  catenate_number prefix cmd.n

let log_prefix = gen_prefix result_dirname
let oracle_prefix = gen_prefix oracle_dirname

let basic_command_string command =
  command.toplevel ^ " " ^
    command.options ^ " " ^
    !additional_options ^ " " ^
    (Filename.concat command.directory command.file)

let command_string command =
  let log_prefix = log_prefix command in
  let command_string = basic_command_string command in
  let command_string =
    command_string ^ " 2>" ^ log_prefix ^ ".err.log"
  in
  let command_string =
    match command.filter with
      None -> command_string
    | Some filter ->
	command_string ^ " | " ^ filter
  in
  let command_string =
    command_string ^ " >" ^ log_prefix ^ ".res.log"
  in
  command_string

let update_toplevel_command command =
  let log_prefix = log_prefix command in
  let oracle_prefix = oracle_prefix command in
  let command_string =
    "mv " ^
      log_prefix ^ ".res.log " ^
      oracle_prefix ^ ".res.oracle"
  in
  ignore (launch command_string);
 let command_string =
    "mv " ^
      log_prefix ^ ".err.log " ^
      oracle_prefix ^ ".err.oracle"
  in
  ignore (launch command_string)

let update_command = function
    Toplevel cmd -> update_toplevel_command cmd
  | Target _ -> assert false

let update_log_files dir file =
  let command_string =
    "mv " ^ make_result_file dir file ^ " " ^ make_oracle_file dir file
  in
  ignore (launch command_string)

let do_command command =
  match command with
    Toplevel command ->
      (* Update : copy the logs. Do not enqueue any cmp
         Run | Show: launch the command, then enqueue the cmp
         Examine : just enqueue the cmp *)
      if !behavior = Update
      then update_toplevel_command command
      else begin
          (* Run, Show or Examine *)
          if !behavior <> Examine
          then begin
	      let command_string = command_string command in
	      if !verbosity >= 1
	      then lock_printf "%s@." command_string ;
	      ignore (launch command_string)
	    end;
	  lock ();
	  shared.summary_run <- succ shared.summary_run ;
	  shared.summary_log <- shared.summary_log + 2 ;
	  Queue.push (Cmp_Toplevel command) shared.cmps;
	  unlock ()
	end
  | Target (execnow, cmds) ->
      if !behavior = Update then begin
	  List.iter (update_log_files execnow.ex_dir) execnow.ex_log;
          Queue.iter update_command cmds
	end else
        begin
          let res =
            if !behavior <> Examine then begin
		let filenames =
		  List.fold_left
		    (fun s f -> s ^ " " ^ make_result_file execnow.ex_dir f)
		    ""
		    (execnow.ex_bin @ execnow.ex_log)
		in
		(* TODO this should be done with Unix.unlink *)
		ignore (launch ("rm" ^ filenames ^ " 2> /dev/null"));
		Mutex.lock shared.lock_target;
		let r = launch execnow.ex_cmd in
		Mutex.unlock shared.lock_target;
		r
              end else
	      0
          in
          lock();
	  shared.summary_log <- succ shared.summary_log;
          if res = 0
	  then begin
	      shared.summary_ok <- succ shared.summary_ok;
              Queue.transfer shared.commands cmds;
	      shared.commands <- cmds;
              Condition.signal shared.work_available;
	      if !behavior = Examine || !behavior = Run
	      then begin
		  List.iter
		    (fun f -> Queue.push (Cmp_Log(execnow.ex_dir, f)) shared.cmps)
		    execnow.ex_log
		end
            end
	  else begin
	      let treat_cmd = function
		  Toplevel cmd ->
		    shared.summary_run <- shared.summary_run + 1;
		    let log_prefix = log_prefix cmd in
		    begin try
			Unix.unlink (log_prefix ^ ".res.log ")
		      with Unix.Unix_error _ -> ()
		    end;
		| Target _ -> assert false
	      in
	      Queue.iter treat_cmd cmds;
              Queue.push (Target_error execnow) shared.diffs;
              Condition.signal shared.diff_available
            end;
          unlock()
        end

let log_ext = function Res -> ".res" | Err -> ".err"

let compare_one_file cmp log_prefix oracle_prefix log_kind =
  if !behavior = Show
  then begin
    lock();
    Queue.push (Command_error(cmp,log_kind)) shared.diffs;
    Condition.signal shared.diff_available;
    unlock()
  end else
    let ext = log_ext log_kind in
    let log_file = log_prefix ^ ext ^ ".log " in
    let oracle_file = oracle_prefix ^ ext ^ ".oracle" in
    let cmp_string = "cmp -s " ^ log_file ^ oracle_file in
    if !verbosity >= 2 then lock_printf "%% cmp%s (%d) :%s@."
      ext
      cmp.n
      cmp_string;
    match launch cmp_string with
      0 ->
	lock();
	shared.summary_ok <- shared.summary_ok + 1;
	unlock()
    | 1 ->
	lock();
	Queue.push (Command_error (cmp,log_kind)) shared.diffs;
	Condition.signal shared.diff_available;
	unlock()
    | 2 ->
	lock_printf
	  "%% System error while comparing. Maybe one of the files is missing...@\n%s or %s@."
	  log_file oracle_file;
    | _ -> assert false

let compare_one_log_file dir file =
  if !behavior = Show
  then begin
    lock();
    Queue.push (Log_error(dir,file)) shared.diffs;
    Condition.signal shared.diff_available;
    unlock()
  end else
    let log_file = make_result_file dir file in
    let oracle_file = make_oracle_file dir file in
    let cmp_string = "cmp -s " ^ log_file ^ " " ^ oracle_file in
    if !verbosity >= 2 then lock_printf "%% cmplog: %s / %s@." dir file;
    shared.summary_log <- succ shared.summary_log;
    match launch cmp_string with
      0 ->
	lock();
	shared.summary_ok <- shared.summary_ok + 1;
	unlock()
    | 1 ->
	lock();
	Queue.push (Log_error (dir,file)) shared.diffs;
	Condition.signal shared.diff_available;
	unlock()
    | 2 ->
	lock_printf
	  "%% System error while comparing. Maybe one of the files is missing...@\n%s or %s@."
	  log_file oracle_file;
    | _ -> assert false

let do_cmp = function
  | Cmp_Toplevel cmp ->
      let log_prefix = log_prefix cmp in
      let oracle_prefix = oracle_prefix cmp in
      compare_one_file cmp log_prefix oracle_prefix Res;
      compare_one_file cmp log_prefix oracle_prefix Err
  | Cmp_Log(dir, f) ->
      compare_one_log_file dir f

let worker_thread () =
  while true do
    lock () ;
    if (Queue.length shared.commands) + (Queue.length shared.cmps) < !n
    then Condition.signal shared.commands_empty;
    try
      let cmp = Queue.pop shared.cmps in
      unlock () ;
      do_cmp cmp
    with Queue.Empty ->
      try
	let command = Queue.pop shared.commands in
	unlock () ;
	do_command command
      with Queue.Empty ->
	if shared.commands_finished then (unlock () ; Thread.exit ());

	Condition.signal shared.commands_empty;
	(* we still have the lock at this point *)

	Condition.wait shared.work_available shared.lock;
	  (* this atomically releases the lock and suspends
	     the thread on the condition work_available *)

	unlock ();
  done

let do_diff = function
  | Command_error (diff, kind) ->
      let log_prefix = log_prefix diff in
      let log_ext = log_ext kind in
      let command_string = command_string diff in
      lock_printf "Command:@\n%s@." command_string;
      if !behavior = Show
      then ignore (launch ("cat " ^ log_prefix ^ log_ext ^ ".log"))
      else
        let oracle_prefix = oracle_prefix diff in
        let diff_string =
          !do_diffs ^ " " ^
	    oracle_prefix ^ log_ext ^ ".oracle " ^
	    log_prefix ^ log_ext ^ ".log"
        in
        ignore (launch diff_string)
  | Target_error execnow ->
      lock_printf "Custom command failed: %s@\n" execnow.ex_cmd
  | Log_error(dir, file) ->
      let result_file = make_result_file dir file in
      lock_printf "Log of %s:@." result_file;
      if !behavior = Show
      then ignore (launch ("cat " ^ result_file))
      else
        let diff_string =
	  !do_diffs ^ " " ^ make_oracle_file dir file ^ " " ^ result_file
	in
        ignore (launch diff_string)


let diff_thread () =
  lock () ;
  while true do
    try
      let diff = Queue.pop shared.diffs in
      unlock ();
      do_diff diff;
      lock ()
    with Queue.Empty ->
      if shared.cmp_finished then (unlock () ; Thread.exit ());

      Condition.wait shared.diff_available shared.lock
      (* this atomically releases the lock and suspends
	 the thread on the condition cmp_available *)
  done

let test_pattern config =
  let regexp = Str.regexp config.dc_test_regexp in
  fun file ->
    Str.string_match regexp file 0

let files = Queue.create ()

(* test for a possible toplevel configuration. *)
let default_config =
  let general_config_file = Filename.concat test_path dir_config_file in
    if Sys.file_exists general_config_file
    then begin
      let scan_buffer = Scanf.Scanning.from_file general_config_file in
      scan_options Filename.current_dir_name scan_buffer default_config
    end
    else default_config

let () =
  (* enqueue the test files *)
  let suites =
    match !suites with
      [] -> default_suites
    | l -> List.rev l
  in
  List.iter
    (fun suite ->
       if !verbosity >= 2 then lock_printf "%% Now treating test %s\n%!" suite;
      (* the "suite" may be a directory in [test_path] or a single file *)
      let interpret_as_file =
	try
	  ignore (Filename.chop_extension suite);
	  true
	with Invalid_argument _ -> false
      in
      let directory =
	if interpret_as_file
	then
	  Filename.dirname suite
	else
	  Filename.concat test_path suite
      in
      let config = Filename.concat directory dir_config_file in
      let dir_config =
        if Sys.file_exists config
	then begin
	    let scan_buffer = Scanf.Scanning.from_file config in
            scan_options directory scan_buffer default_config
        end
	else default_config
      in
      if interpret_as_file
      then Queue.push (Filename.basename suite, directory, dir_config) files
      else begin
	  let dir_files = Sys.readdir directory in
	  for i = 0 to pred (Array.length dir_files) do
	    let file = dir_files.(i) in
	    assert (Filename.is_relative file);
	    if test_pattern dir_config file
	    then Queue.push (file, directory, dir_config) files;
	  done
	end)
    suites

let dispatcher () =
  try
    while true
    do
      lock ();
      while (Queue.length shared.commands) + (Queue.length shared.cmps) >= !n
      do
	Condition.wait shared.commands_empty shared.lock;
      done;
      (* we have the lock *)
      let file, directory, config = Queue.pop files in
      let config =
        scan_test_file config directory file in
      let toplevel =
	if not !use_byte
	then config.dc_toplevel
	else opt_to_byte config.dc_toplevel
      in
      let i = ref 0 in
      let make_toplevel_cmd option =
        {file=file; options = option; toplevel = toplevel;
         n = !i; directory = directory;
	 filter = config.dc_filter}
      in
      let treat_option q option =
	Queue.push
	  (Toplevel (make_toplevel_cmd option))
	  q;
	incr i
      in
      if not config.dc_dont_run
      then begin
      (match config.dc_execnow with
         | Some s ->
	     let subworkqueue = Queue.create () in
	     List.iter (treat_option subworkqueue) config.dc_options;
             Queue.push
               (Target (s, subworkqueue))
               shared.commands
         | None ->
             List.iter
	       (treat_option shared.commands)
	       config.dc_options);
      Condition.broadcast shared.work_available;
      end;
      unlock () ;
      done
  with Queue.Empty ->
    shared.commands_finished <- true;
    unlock ()

let () =
  let worker_ids = Array.init !n
    (fun _ -> Thread.create worker_thread ())
  in
  let diff_id = Thread.create diff_thread () in

  dispatcher ();
  if !behavior = Run
  then
    lock_printf "%% Dispatch finished, waiting for workers to complete@.";
  ignore (Thread.create
    (fun () ->
      while true do
	Condition.broadcast shared.work_available;
	Thread.delay 0.5;
      done)
    ());
  Array.iter Thread.join worker_ids;

  if !behavior = Run
  then
    lock_printf "%% Comparisons finished, waiting for diffs to complete@.";
  lock();
  shared.cmp_finished <- true;
  unlock();
  ignore (Thread.create
    (fun () ->
      while true do
	Condition.broadcast shared.diff_available;
	Thread.delay 0.5;
      done)
    ());
  Thread.join diff_id;
  if !behavior = Run
  then
    lock_printf "%% Diffs finished. Summary:@\nRun = %d@\nOk  = %d of %d@."
      shared.summary_run shared.summary_ok shared.summary_log;
  exit 0;


(*
Local Variables: 
compile-command: "make -j -C .. bin/ptests.byte"
End: 
*)