aboutsummaryrefslogtreecommitdiffstats
path: root/test/test_mininix.ml
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_mininix.ml')
-rw-r--r--test/test_mininix.ml319
1 files changed, 319 insertions, 0 deletions
diff --git a/test/test_mininix.ml b/test/test_mininix.ml
new file mode 100644
index 0000000..f628a8a
--- /dev/null
+++ b/test/test_mininix.ml
@@ -0,0 +1,319 @@
1open Core
2
3let with_dir path ~f =
4 let fd = Core_unix.opendir path in
5 f fd;
6 Core_unix.closedir fd
7
8let walk_dir path ~f =
9 with_dir path ~f:(fun fd ->
10 let rec go () =
11 match Core_unix.readdir_opt fd with
12 | Some entry ->
13 f (Filename.concat path entry);
14 go ()
15 | None -> ()
16 in
17 go ())
18
19type testcase = {
20 name : string;
21 dir : string;
22 input : string;
23 expected_output : [ `Okay of string | `Fail ];
24}
25
26let testdata_dir = "./testdata"
27and testcases = ref []
28and testcases_ignored = ref 0
29
30let add_testcase c = testcases := c :: !testcases
31
32let print_testcase_stats () =
33 let okay, fail =
34 List.fold !testcases ~init:(0, 0)
35 ~f:(fun (okay, fail) { expected_output; _ } ->
36 match expected_output with
37 | `Okay _ -> (okay + 1, fail)
38 | `Fail -> (okay, fail + 1))
39 in
40 printf
41 "Loaded %d test cases (ignored %d), expected results: okay %d, fail %d\n%!"
42 (okay + fail) !testcases_ignored okay fail
43
44let imports () =
45 Mininix.Import.materialize
46 [ { filename = "./testdata/lib.nix"; deps = [] } ]
47 ~relative_to:(Core_unix.getcwd ())
48
49type eval_err = [ `Timeout | `ParseError | `ProgramError | `ElaborateError ]
50[@@deriving sexp]
51
52type eval_result = (string, eval_err) Result.t [@@deriving sexp]
53
54let eval input ~name ~dir ~imports =
55 let dir = Filename.to_absolute_exn dir ~relative_to:(Core_unix.getcwd ()) in
56 try
57 input
58 |> Nix.parse ~filename:(name ^ ".nix")
59 |> Nix.elaborate ~dir:(Some dir)
60 |> Mininix.Nix2mininix.from_nix |> Mininix.apply_prelude
61 |> Mininix.interp_tl ~fuel:`Limited ~mode:`Deep ~imports
62 |> function
63 | Res (Some v) ->
64 Ok (v |> Mininix.Mininix2nix.from_val |> Nix.Printer.to_string)
65 | Res None -> Error `ProgramError
66 | NoFuel -> Error `Timeout
67 with
68 | Nix.ParseError _ -> Error `ParseError
69 | Nix.ElaborateError _ -> Error `ElaborateError
70 | Mininix.Nix2mininix.FromNixError _ -> Error `ElaborateError
71
72let eval_subproc input ~name ~dir ~imports =
73 let rxfd, txfd = Core_unix.pipe () in
74 match Core_unix.fork () with
75 | `In_the_child ->
76 let txc = Core_unix.out_channel_of_descr txfd in
77 eval input ~name ~dir ~imports
78 |> [%sexp_of: eval_result] |> Sexp.output txc;
79 exit 0
80 | `In_the_parent child_pid ->
81 let select_res =
82 Core_unix.select ~restart:true ~read:[ rxfd ] ~write:[] ~except:[]
83 ~timeout:(`After (Time_ns.Span.of_min 1.))
84 ()
85 in
86 if List.is_empty select_res.read then (
87 ignore (Signal_unix.send Signal.kill (`Pid child_pid));
88 ignore (Core_unix.waitpid child_pid);
89 Error `Timeout)
90 else
91 let rxc = Core_unix.in_channel_of_descr rxfd in
92 let res = Sexp.input_sexp rxc |> [%of_sexp: eval_result] in
93 ignore (Core_unix.waitpid child_pid);
94 Core_unix.close ~restart:true rxfd;
95 Core_unix.close ~restart:true txfd;
96 res
97
98type test_result =
99 [ `Timeout
100 | `ParseError
101 | `ProgramError
102 | `ElaborateError
103 | `WrongOutput
104 | `UnexpectedSuccess
105 | `Okay ]
106
107let run_testcase ~imports = function
108 | { name; dir; input; expected_output = `Okay expected_output } -> (
109 match eval_subproc input ~name ~dir ~imports with
110 | Ok got_output ->
111 if String.(strip got_output = strip expected_output) then `Okay
112 else `WrongOutput
113 | Error err -> (err :> test_result))
114 | { name; dir; input; expected_output = `Fail } -> (
115 match eval_subproc input ~name ~dir ~imports with
116 | Ok _ -> `UnexpectedSuccess
117 | Error _ -> `Okay)
118
119type test_stats = {
120 okay : int;
121 unexpected_success : int;
122 wrong_output : int;
123 parse_error : int;
124 elaborate_error : int;
125 program_error : int;
126 timeout : int;
127}
128
129let test_stats_empty =
130 {
131 okay = 0;
132 unexpected_success = 0;
133 wrong_output = 0;
134 parse_error = 0;
135 elaborate_error = 0;
136 program_error = 0;
137 timeout = 0;
138 }
139
140let run_testcases () =
141 Nix.Printer.set_width 1000000;
142 let mat_imports = imports () in
143 let stats =
144 List.foldi !testcases ~init:test_stats_empty ~f:(fun i stats c ->
145 printf "[%d/%d] %s %!" (i + 1) (List.length !testcases) c.name;
146 match run_testcase c ~imports:mat_imports with
147 | `Okay ->
148 printf "okay\n%!";
149 { stats with okay = stats.okay + 1 }
150 | `UnexpectedSuccess ->
151 printf "unexpectedly succeeded\n%!";
152 { stats with unexpected_success = stats.unexpected_success + 1 }
153 | `WrongOutput ->
154 printf "gave wrong output\n%!";
155 { stats with wrong_output = stats.wrong_output + 1 }
156 | `ParseError ->
157 printf "could not be parsed\n%!";
158 { stats with parse_error = stats.parse_error + 1 }
159 | `ElaborateError ->
160 printf "could not be elaborated\n%!";
161 { stats with elaborate_error = stats.elaborate_error + 1 }
162 | `ProgramError ->
163 printf "failed to execute\n%!";
164 { stats with program_error = stats.program_error + 1 }
165 | `Timeout ->
166 printf "timed out\n%!";
167 { stats with timeout = stats.timeout + 1 })
168 in
169 printf
170 "Results:\n\
171 \ %d gave the expected output\n\
172 \ %d unexpectedly succeeded\n\
173 \ %d gave wrong output\n\
174 \ %d could not be parsed\n\
175 \ %d could not be elaborated\n\
176 \ %d failed to execute\n\
177 \ %d timed out\n\
178 %!"
179 stats.okay stats.unexpected_success stats.wrong_output stats.parse_error
180 stats.elaborate_error stats.program_error stats.timeout
181
182let try_add_testcase without_ext =
183 try
184 let dir = Filename.dirname without_ext in
185 let input = In_channel.read_all (without_ext ^ ".nix") in
186 let name = Filename.basename without_ext in
187 if String.is_prefix ~prefix:"eval-fail" name then
188 add_testcase { name; dir; input; expected_output = `Fail }
189 else if String.is_prefix ~prefix:"eval-okay" name then
190 let expected_output = In_channel.read_all (without_ext ^ ".exp") in
191 add_testcase { name; dir; input; expected_output = `Okay expected_output }
192 with
193 (* There are certain test cases where the '.nix' file is available, but
194 there is no '.exp' file. (Instead, for example, there may be a
195 '.exp-disabled' file, which we don't check for.) So [add_testcase] fails
196 when trying to read the '.exp' file, which does not exist. We catch the
197 exception that is then raised in [add_testcase] here. *)
198 | Sys_error _ ->
199 ()
200
201let ignore_tests =
202 [
203 (* We do not implement '«repeated»' *)
204 "eval-okay-repeated-empty-attrs";
205 "eval-okay-repeated-empty-list";
206 (* # Very specific / hard-to-implement builtins: *)
207 (* We do not implement conversion from/to JSON/XML *)
208 "eval-okay-toxml";
209 "eval-okay-toxml2";
210 "eval-okay-tojson";
211 "eval-okay-fromTOML";
212 "eval-okay-fromTOML-timestamps";
213 "eval-okay-fromjson";
214 "eval-okay-fromjson-escapes";
215 "eval-fail-fromJSON-overflowing";
216 "eval-fail-fromTOML-timestamps";
217 "eval-fail-toJSON";
218 (* We do not implement hasing *)
219 "eval-okay-convertHash";
220 "eval-okay-hashstring";
221 "eval-okay-hashfile";
222 "eval-okay-groupBy";
223 "eval-okay-zipAttrsWith";
224 "eval-fail-hashfile-missing";
225 (* We do not support filesystem operations *)
226 "eval-okay-readDir";
227 "eval-okay-readfile";
228 "eval-okay-readFileType";
229 "eval-okay-symlink-resolution";
230 (* We do not support version operations *)
231 "eval-okay-splitversion";
232 "eval-okay-versions";
233 (* We do not support flake references *)
234 "eval-okay-parse-flake-ref";
235 "eval-okay-flake-ref-to-string";
236 "eval-fail-flake-ref-to-string-negative-integer";
237 (* We do not support regexes *)
238 "eval-okay-regex-match";
239 "eval-okay-regex-split";
240 (* # Features that the core interpreter lacks *)
241 (* We do not implement derivations and contexts *)
242 "eval-okay-derivation-legacy";
243 "eval-okay-eq-derivations";
244 "eval-fail-addDrvOutputDependencies-empty-context";
245 "eval-fail-addDrvOutputDependencies-multi-elem-context";
246 "eval-fail-addDrvOutputDependencies-wrong-element-kind";
247 "eval-fail-assert-equal-derivations";
248 "eval-fail-assert-equal-derivations-extra";
249 "eval-fail-derivation-name";
250 "eval-okay-context";
251 "eval-okay-context-introspection";
252 "eval-okay-substring-context";
253 "eval-fail-addErrorContext-example";
254 (* We do not support scopedImport *)
255 "eval-okay-import";
256 (* We do not support tryEval *)
257 "eval-okay-redefine-builtin";
258 "eval-okay-tryeval";
259 (* We do not support unsafeGetAttrPos nor __curPos *)
260 "eval-okay-curpos";
261 "eval-okay-getattrpos";
262 "eval-okay-getattrpos-functionargs";
263 "eval-okay-getattrpos-undefined";
264 "eval-okay-inherit-attr-pos";
265 (* We do not support environment variable lookup *)
266 "eval-okay-getenv";
267 (* We do not support '__override's. Rationale: this construct has expressly
268 been avoided in Nixpkgs since the 13.10 release, see
269 https://github.com/NixOS/nixpkgs/issues/2112 *)
270 "eval-okay-attrs6";
271 "eval-okay-overrides";
272 "eval-fail-set-override";
273 (* We do not implement the 'trace' builtin *)
274 "eval-okay-print";
275 "eval-okay-inherit-from";
276 (* ^ also uses __overrides, for which we lack support *)
277 (* We do not implement flags to set arguments / retrieve attributes
278 for the evaluator *)
279 (* We do not support setting variables outside of the program *)
280 "eval-okay-autoargs";
281 (* We do not support paths *)
282 "eval-okay-baseNameOf";
283 "eval-okay-path";
284 "eval-okay-path-string-interpolation";
285 "eval-okay-pathexists";
286 "eval-okay-search-path";
287 "eval-okay-string";
288 "eval-okay-types";
289 "eval-fail-assert-equal-paths";
290 "eval-fail-bad-string-interpolation-2";
291 "eval-fail-nonexist-path";
292 "eval-fail-path-slash";
293 "eval-fail-to-path";
294 (* We do not implement the 'currentSystem' and 'dirOf' builtins *)
295 "eval-okay-builtins";
296 (* We do not support fetch operations *)
297 "eval-fail-fetchTree-negative";
298 "eval-fail-fetchurl-baseName";
299 "eval-fail-fetchurl-baseName-attrs";
300 "eval-fail-fetchurl-baseName-attrs-name";
301 (* We do not support the pipe operator *)
302 "eval-fail-pipe-operators";
303 ]
304
305let () =
306 Printf.printf "Running in %s\n%!" (Core_unix.getcwd ());
307 walk_dir testdata_dir ~f:(fun entry ->
308 match Filename.split_extension entry with
309 | without_ext, Some "nix" ->
310 if
311 List.exists ignore_tests ~f:(fun name ->
312 String.(name = Filename.basename without_ext))
313 then testcases_ignored := !testcases_ignored + 1
314 else try_add_testcase without_ext
315 | _ -> ());
316 testcases :=
317 List.sort !testcases ~compare:(fun c1 c2 -> String.compare c1.name c2.name);
318 print_testcase_stats ();
319 run_testcases ()