forked from ocaml/ocaml
-
Notifications
You must be signed in to change notification settings - Fork 0
/
actions_helpers.ml
388 lines (365 loc) · 14 KB
/
actions_helpers.ml
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
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Sebastien Hinderer, projet Gallium, INRIA Paris *)
(* *)
(* Copyright 2016 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
(* Helper functions when writing actions *)
open Ocamltest_stdlib
let skip_with_reason reason =
let code _log env =
let result = Result.skip_with_reason reason in
(result, env)
in
Actions.make "skip" code
let pass_or_skip test pass_reason skip_reason _log env =
let open Result in
let result =
if test
then pass_with_reason pass_reason
else skip_with_reason skip_reason in
(result, env)
let mkreason what commandline exitcode =
Printf.sprintf "%s: command\n%s\nfailed with exit code %d"
what commandline exitcode
let testfile env =
match Environments.lookup Builtin_variables.test_file env with
| None -> assert false
| Some t -> t
let test_source_directory env =
Environments.safe_lookup Builtin_variables.test_source_directory env
let test_build_directory env =
Environments.safe_lookup Builtin_variables.test_build_directory env
let test_build_directory_prefix env =
Environments.safe_lookup Builtin_variables.test_build_directory_prefix env
let words_of_variable env variable =
String.words (Environments.safe_lookup variable env)
let exit_status_of_variable env variable =
try int_of_string
(Environments.safe_lookup variable env)
with _ -> 0
let readonly_files env = words_of_variable env Builtin_variables.readonly_files
let subdirectories env = words_of_variable env Builtin_variables.subdirectories
let setup_symlinks test_source_directory build_directory files =
let symlink filename =
(* Emulate ln -sfT *)
let src = Filename.concat test_source_directory filename in
let dst = Filename.concat build_directory filename in
let () =
if Sys.file_exists dst then
if Sys.win32 && Sys.is_directory dst then
(* Native symbolic links to directories don't disappear with unlink;
doing rmdir here is technically slightly more than ln -sfT would
do *)
Sys.rmdir dst
else
Sys.remove dst
in
Unix.symlink src dst in
let copy filename =
let src = Filename.concat test_source_directory filename in
let dst = Filename.concat build_directory filename in
Sys.copy_file src dst in
let f = if Unix.has_symlink () then symlink else copy in
Sys.make_directory build_directory;
List.iter f files
let setup_subdirectories source_directory build_directory subdirs =
let full_src_path name = Filename.concat source_directory name in
let full_dst_path name = Filename.concat build_directory name in
let cp_dir name =
Sys.copy_directory (full_src_path name) (full_dst_path name)
in
List.iter cp_dir subdirs
let setup_build_env add_testfile additional_files (_log : out_channel) env =
let source_dir = (test_source_directory env) in
let build_dir = (test_build_directory env) in
let some_files = additional_files @ (readonly_files env) in
let files =
if add_testfile
then (testfile env) :: some_files
else some_files in
setup_symlinks source_dir build_dir files;
let subdirs = subdirectories env in
setup_subdirectories source_dir build_dir subdirs;
Sys.chdir build_dir;
(Result.pass, env)
let setup_simple_build_env add_testfile additional_files log env =
let build_env = Environments.add
Builtin_variables.test_build_directory
(test_build_directory_prefix env) env in
setup_build_env add_testfile additional_files log build_env
let run_cmd
?(environment=[||])
?(stdin_variable=Builtin_variables.stdin)
?(stdout_variable=Builtin_variables.stdout)
?(stderr_variable=Builtin_variables.stderr)
?(append=false)
?timeout
log env original_cmd
=
let log_redirection std filename =
if filename<>"" then
begin
Printf.fprintf log " Redirecting %s to %s \n%!" std filename
end in
let cmd =
if (Environments.lookup_as_bool Strace.strace env) = Some true then
begin
let action_name = Environments.safe_lookup Actions.action_name env in
let test_build_directory = test_build_directory env in
let strace_logfile_name = Strace.get_logfile_name action_name in
let strace_logfile =
Filename.make_path [test_build_directory; strace_logfile_name]
in
let strace_flags = Environments.safe_lookup Strace.strace_flags env in
let strace_cmd =
["strace"; "-f"; "-o"; strace_logfile; strace_flags]
in
strace_cmd @ original_cmd
end else original_cmd
in
let lst = List.concat (List.map String.words cmd) in
let quoted_lst =
if Sys.win32
then List.map Filename.maybe_quote lst
else lst in
let cmd' = String.concat " " quoted_lst in
Printf.fprintf log "Commandline: %s\n" cmd';
let progname = List.hd quoted_lst in
let arguments = Array.of_list quoted_lst in
let stdin_filename = Environments.safe_lookup stdin_variable env in
let stdout_filename = Environments.safe_lookup stdout_variable env in
let stderr_filename = Environments.safe_lookup stderr_variable env in
log_redirection "stdin" stdin_filename;
log_redirection "stdout" stdout_filename;
log_redirection "stderr" stderr_filename;
let systemenv =
Environments.append_to_system_env
environment
env
in
let timeout =
match timeout with
| Some timeout -> timeout
| None ->
Option.value ~default:0
(Environments.lookup_as_int Builtin_variables.timeout env)
in
let n =
Run_command.run {
Run_command.progname = progname;
Run_command.argv = arguments;
Run_command.envp = systemenv;
Run_command.stdin_filename = stdin_filename;
Run_command.stdout_filename = stdout_filename;
Run_command.stderr_filename = stderr_filename;
Run_command.append = append;
Run_command.timeout = timeout;
Run_command.log = log
}
in
let dump_file s fn =
if not (Sys.file_is_empty fn) then begin
Printf.fprintf log "### begin %s ###\n" s;
Sys.dump_file log fn;
Printf.fprintf log "### end %s ###\n" s
end
in
dump_file "stdout" stdout_filename;
if stdout_filename <> stderr_filename then dump_file "stderr" stderr_filename;
n
let run
(log_message : string)
(redirect_output : bool)
(can_skip : bool)
(prog_variable : Variables.t)
(args_variable : Variables.t option)
(log : out_channel)
(env : Environments.t)
=
match Environments.lookup prog_variable env with
| None ->
let msg = Printf.sprintf "%s: variable %s is undefined"
log_message (Variables.name_of_variable prog_variable) in
(Result.fail_with_reason msg, env)
| Some program ->
let arguments = match args_variable with
| None -> ""
| Some variable -> Environments.safe_lookup variable env in
let commandline = [program; arguments] in
let what = log_message ^ " " ^ program ^ " " ^
begin if arguments="" then "without any argument"
else "with arguments " ^ arguments
end in
let env =
if redirect_output
then begin
let output = Environments.safe_lookup Builtin_variables.output env in
let env =
Environments.add_if_undefined Builtin_variables.stdout output env
in
Environments.add_if_undefined Builtin_variables.stderr output env
end else env
in
let expected_exit_status =
exit_status_of_variable env Builtin_variables.exit_status
in
let exit_status = run_cmd log env commandline in
if exit_status=expected_exit_status
then (Result.pass, env)
else begin
let reason = mkreason what (String.concat " " commandline) exit_status in
if exit_status = 125 && can_skip
then (Result.skip_with_reason reason, env)
else (Result.fail_with_reason reason, env)
end
let run_program =
run
"Running program"
true
false
Builtin_variables.program
(Some Builtin_variables.arguments)
let run_script log env =
let response_file = Filename.temp_file "ocamltest-" ".response" in
Printf.fprintf log "Script should write its response to %s\n%!"
response_file;
let scriptenv = Environments.add
Builtin_variables.ocamltest_response response_file env in
let (result, newenv) = run
"Running script"
true
true
Builtin_variables.script
None
log scriptenv in
let final_value =
if Result.is_pass result then begin
match Modifier_parser.modifiers_of_file response_file with
| modifiers ->
let modified_env = Environments.apply_modifiers newenv modifiers in
(result, modified_env)
| exception Failure reason ->
(Result.fail_with_reason reason, newenv)
| exception Variables.No_such_variable name ->
let reason =
Printf.sprintf "error in script response: unknown variable %s" name
in
(Result.fail_with_reason reason, newenv)
end else begin
let reason = String.trim (Sys.string_of_file response_file) in
let newresult = { result with Result.reason = Some reason } in
(newresult, newenv)
end
in
Sys.force_remove response_file;
final_value
let run_hook hook_name log input_env =
Printf.fprintf log "Entering run_hook for hook %s\n%!" hook_name;
let response_file = Filename.temp_file "ocamltest-" ".response" in
Printf.fprintf log "Hook should write its response to %s\n%!"
response_file;
let hookenv = Environments.add
Builtin_variables.ocamltest_response response_file input_env in
let systemenv =
Environments.to_system_env hookenv in
let timeout =
Option.value ~default:0
(Environments.lookup_as_int Builtin_variables.timeout input_env) in
let open Run_command in
let settings = {
progname = "sh";
argv = [|"sh"; Filename.maybe_quote hook_name|];
envp = systemenv;
stdin_filename = "";
stdout_filename = "";
stderr_filename = "";
append = false;
timeout = timeout;
log = log;
} in let exit_status = run settings in
let final_value = match exit_status with
| 0 ->
begin match Modifier_parser.modifiers_of_file response_file with
| modifiers ->
let modified_env = Environments.apply_modifiers hookenv modifiers in
(Result.pass, modified_env)
| exception Failure reason ->
(Result.fail_with_reason reason, hookenv)
| exception Variables.No_such_variable name ->
let reason =
Printf.sprintf "error in script response: unknown variable %s" name
in
(Result.fail_with_reason reason, hookenv)
end
| _ ->
Printf.fprintf log "Hook returned %d" exit_status;
let reason = String.trim (Sys.string_of_file response_file) in
if exit_status=125
then (Result.skip_with_reason reason, hookenv)
else (Result.fail_with_reason reason, hookenv)
in
Sys.force_remove response_file;
final_value
let check_output kind_of_output output_variable reference_variable log
env =
let to_int = function None -> 0 | Some s -> int_of_string s in
let skip_lines =
to_int (Environments.lookup Builtin_variables.skip_header_lines env) in
let skip_bytes =
to_int (Environments.lookup Builtin_variables.skip_header_bytes env) in
let reference_filename = Environments.safe_lookup reference_variable env in
let output_filename = Environments.safe_lookup output_variable env in
Printf.fprintf log "Comparing %s output %s to reference %s\n%!"
kind_of_output output_filename reference_filename;
let files =
{
Filecompare.filetype = Filecompare.Text;
Filecompare.reference_filename = reference_filename;
Filecompare.output_filename = output_filename
} in
let ignore_header_conf = {
Filecompare.lines = skip_lines;
Filecompare.bytes = skip_bytes;
} in
let tool =
Filecompare.make_cmp_tool ~ignore:ignore_header_conf in
match Filecompare.check_file ~tool files with
| Filecompare.Same -> (Result.pass, env)
| Filecompare.Different ->
let diff = Filecompare.diff files in
let diffstr = match diff with
| Ok difference -> difference
| Error diff_file -> ("See " ^ diff_file) in
let reason =
Printf.sprintf "%s output %s differs from reference %s: \n%s\n"
kind_of_output output_filename reference_filename diffstr in
if Environments.lookup_as_bool Builtin_variables.promote env = Some true
then begin
Printf.fprintf log "Promoting %s output %s to reference %s\n%!"
kind_of_output output_filename reference_filename;
Filecompare.promote files ignore_header_conf;
end;
(Result.fail_with_reason reason, env)
| Filecompare.Unexpected_output ->
let banner = String.make 40 '=' in
let unexpected_output = Sys.string_of_file output_filename in
let unexpected_output_with_banners = Printf.sprintf
"%s\n%s%s\n" banner unexpected_output banner in
let reason = Printf.sprintf
"The file %s was expected to be empty because there is no \
reference file %s but it is not:\n%s\n"
output_filename reference_filename unexpected_output_with_banners in
(Result.fail_with_reason reason, env)
| Filecompare.Error (commandline, exitcode) ->
let reason = Printf.sprintf "The command %s failed with status %d"
commandline exitcode in
(Result.fail_with_reason reason, env)