projects/guix·blob master · refs

505 lines · 14.5 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2023 Lars-Dominik Braun <[email protected]>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (test-toml)
20 #:use-module (guix build toml)
21 #:use-module (guix tests)
22 #:use-module (srfi srfi-19) ; For datetime.
23 #:use-module (srfi srfi-64)
24 #:use-module (ice-9 match))
25
26;; Work around <https://codeberg.org/guix/guix/issues/5339>.
27(module-set! (resolve-module '(guix build toml))
28 'raise (@ (srfi srfi-34) raise))
29
30(test-begin "toml")
31
32;; Tests taken from https://toml.io/en/v1.0.0
33
34(test-error "parse-toml: Unspecified key"
35 &file-not-consumed
36 (parse-toml "key = # INVALID"))
37
38(test-error "parse-toml: Missing EOL"
39 &file-not-consumed
40 (parse-toml "first = \"Tom\" last = \"Preston-Werner\" # INVALID"))
41
42(test-equal "parse-toml: Bare keys"
43 '(("key" . "value") ("bare_key" . "value") ("bare-key" . "value") ("1234" . "value"))
44 (parse-toml "key = \"value\"
45bare_key = \"value\"
46bare-key = \"value\"
471234 = \"value\""))
48
49(test-equal "parse-toml: Quoted keys"
50 '(("127.0.0.1" . "value")
51 ("character encoding" . "value")
52 ("ʎǝʞ" . "value")
53 ("key2" . "value")
54 ("quoted \"value\"" . "value"))
55 (parse-toml "\"127.0.0.1\" = \"value\"
56\"character encoding\" = \"value\"
57\"ʎǝʞ\" = \"value\"
58'key2' = \"value\"
59'quoted \"value\"' = \"value\""))
60
61(test-equal "parse-toml: Quoted keys with escapes"
62 '(("key \\ with \n escapes" . "value")
63 ("key" ("with \t escapes" ("and \n dots" . "value"))))
64 (parse-toml "\"key \\\\ with \\n escapes\" = \"value\"
65key.\"with \\t escapes\".\"and \\n dots\" = \"value\""))
66
67(test-error "parse-toml: No key"
68 &file-not-consumed
69 (parse-toml "= \"no key name\""))
70
71(test-equal "parse-toml: Empty key"
72 '(("" . "blank"))
73 (parse-toml "\"\" = \"blank\""))
74
75(test-equal "parse-toml: Dotted keys"
76 '(("name" . "Orange")
77 ("physical" ("color" . "orange")
78 ("shape" . "round"))
79 ("site" ("google.com" . #t)))
80 (parse-toml "name = \"Orange\"
81physical.color = \"orange\"
82physical.shape = \"round\"
83site.\"google.com\" = true"))
84
85(test-equal "parse-toml: Dotted keys with whitespace"
86 '(("fruit" ("name" . "banana") ("color" . "yellow") ("flavor" . "banana")))
87 (parse-toml "fruit.name = \"banana\" # this is best practice
88fruit. color = \"yellow\" # same as fruit.color
89fruit . flavor = \"banana\" # same as fruit.flavor"))
90
91(test-error "parse-toml: Multiple keys"
92 &already-defined
93 (parse-toml "name = \"Tom\"
94name = \"Pradyun\""))
95
96(test-equal "parse-toml: Implicit tables"
97 '(("fruit" ("apple" ("smooth" . #t)) ("orange" . 2)))
98 (parse-toml "fruit.apple.smooth = true
99fruit.orange = 2"))
100
101(test-error "parse-toml: Write to value"
102 &already-defined
103 (parse-toml "fruit.apple = 1
104fruit.apple.smooth = true"))
105
106(test-equal "parse-toml: String"
107 '(("str" . "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."))
108 (parse-toml "str = \"I'm a string. \\\"You can quote me\\\". Name\\tJos\\xE9\\nLocation\\tSF.\""))
109
110(test-equal "parse-toml: Empty string"
111 '(("str1" . "")
112 ("str2" . "")
113 ("str3" . "")
114 ("str4" . ""))
115 (parse-toml "str1 = \"\"
116str2 = ''
117str3 = \"\"\"\"\"\"
118str4 = ''''''"))
119
120(test-equal "parse-toml: Multi-line basic strings"
121 '(("str1" . "Roses are red\nViolets are blue")
122 ("str2" . "The quick brown fox jumps over the lazy dog.")
123 ("str3" . "The quick brown fox jumps over the lazy dog.")
124 ("str4" . "Here are two quotation marks: \"\". Simple enough.")
125 ("str5" . "Here are three quotation marks: \"\"\".")
126 ("str6" . "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\".")
127 ("str7" . "\"This,\" she said, \"is just a pointless statement.\""))
128 (parse-toml "str1 = \"\"\"
129Roses are red
130Violets are blue\"\"\"
131
132str2 = \"\"\"
133The quick brown \\
134
135
136 fox jumps over \\
137 the lazy dog.\"\"\"
138
139str3 = \"\"\"\\
140 The quick brown \\
141 fox jumps over \\
142 the lazy dog.\\
143 \"\"\"
144
145str4 = \"\"\"Here are two quotation marks: \"\". Simple enough.\"\"\"
146# str5 = \"\"\"Here are three quotation marks: \"\"\".\"\"\" # INVALID
147str5 = \"\"\"Here are three quotation marks: \"\"\\\".\"\"\"
148str6 = \"\"\"Here are fifteen quotation marks: \"\"\\\"\"\"\\\"\"\"\\\"\"\"\\\"\"\"\\\".\"\"\"
149
150# \"This,\" she said, \"is just a pointless statement.\"
151str7 = \"\"\"\"This,\" she said, \"is just a pointless statement.\"\"\"\""))
152
153(test-equal "parse-toml: Literal string"
154 '(("winpath" . "C:\\Users\\nodejs\\templates")
155 ("winpath2" . "\\\\ServerX\\admin$\\system32\\")
156 ("quoted" . "Tom \"Dubs\" Preston-Werner")
157 ("regex" . "<\\i\\c*\\s*>"))
158 (parse-toml "winpath = 'C:\\Users\\nodejs\\templates'
159winpath2 = '\\\\ServerX\\admin$\\system32\\'
160quoted = 'Tom \"Dubs\" Preston-Werner'
161regex = '<\\i\\c*\\s*>'"))
162
163(test-equal "parse-toml: Multi-line literal strings"
164 '(("regex2" . "I [dw]on't need \\d{2} apples")
165 ("lines" . "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n")
166 ("quot15" . "Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"")
167 ("apos15" . "Here are fifteen apostrophes: '''''''''''''''")
168 ("str" . "'That,' she said, 'is still pointless.'"))
169 (parse-toml "regex2 = '''I [dw]on't need \\d{2} apples'''
170lines = '''
171The first newline is
172trimmed in raw strings.
173 All other whitespace
174 is preserved.
175'''
176quot15 = '''Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"'''
177
178# apos15 = '''Here are fifteen apostrophes: '''''''''''''''''' # INVALID
179apos15 = \"Here are fifteen apostrophes: '''''''''''''''\"
180
181# 'That,' she said, 'is still pointless.'
182str = ''''That,' she said, 'is still pointless.''''"))
183
184(test-equal "parse-toml: Decimal integer"
185 '(("int1" . 99) ("int2" . 42) ("int3" . 0) ("int4" . -17))
186 (parse-toml "int1 = +99
187int2 = 42
188int3 = 0
189int4 = -17"))
190
191(test-equal "parse-toml: Decimal integer underscores"
192 '(("int5" . 1000) ("int6" . 5349221) ("int7" . 5349221) ("int8" . 12345))
193 (parse-toml "int5 = 1_000
194int6 = 5_349_221
195int7 = 53_49_221 # Indian number system grouping
196int8 = 1_2_3_4_5 # VALID but discouraged"))
197
198(test-equal "parse-toml: Hexadecimal"
199 `(("hex1" . ,#xdeadbeef) ("hex2" . ,#xdeadbeef) ("hex3" . ,#xdeadbeef))
200 (parse-toml "hex1 = 0xDEADBEEF
201hex2 = 0xdeadbeef
202hex3 = 0xdead_beef"))
203
204(test-equal "parse-toml: Octal"
205 `(("oct1" . ,#o01234567) ("oct2" . #o755))
206 (parse-toml "oct1 = 0o01234567
207oct2 = 0o755"))
208
209(test-equal "parse-toml: Binary"
210 `(("bin1" . ,#b11010110))
211 (parse-toml "bin1 = 0b11010110"))
212
213(test-equal "parse-toml: Float"
214 '(("flt1" . 1.0)
215 ("flt2" . 3.1415)
216 ("flt3" . -0.01)
217 ("flt4" . 5e+22)
218 ("flt5" . 1e06)
219 ("flt6" . -2e-2)
220 ("flt7" . 6.626e-34)
221 ("flt8" . 224617.445991228))
222 (parse-toml "# fractional
223flt1 = +1.0
224flt2 = 3.1415
225flt3 = -0.01
226
227# exponent
228flt4 = 5e+22
229flt5 = 1e06
230flt6 = -2E-2
231
232# both
233flt7 = 6.626e-34
234
235flt8 = 224_617.445_991_228"))
236
237(test-equal "parse-toml: Float"
238 '(("sf1" . +inf.0)
239 ("sf2" . +inf.0)
240 ("sf3" . -inf.0)
241 ("sf4" . +nan.0)
242 ("sf5" . +nan.0)
243 ("sf6" . -nan.0))
244 (parse-toml "# infinity
245sf1 = inf # positive infinity
246sf2 = +inf # positive infinity
247sf3 = -inf # negative infinity
248
249# not a number
250sf4 = nan # actual sNaN/qNaN encoding is implementation-specific
251sf5 = +nan # same as `nan`
252sf6 = -nan # valid, actual encoding is implementation-specific"))
253
254(test-equal "parse-toml: Boolean"
255 '(("bool1" . #t)
256 ("bool2" . #f))
257 (parse-toml "bool1 = true
258bool2 = false"))
259
260(test-equal "parse-toml: Offset date-time"
261 `(("odt1" . ,(make-date #f 0 32 7 27 5 1979 0))
262 ("odt2" . ,(make-date #f 0 32 0 27 5 1979 (* -7 60 60)))
263 ("odt3" . ,(make-date 999999 0 32 0 27 5 1979 (* 7 60 60)))
264 ("odt4" . ,(make-date #f 0 32 7 27 5 1979 0))
265 ("odt5" . ,(make-date #f 0 32 7 27 5 1979 0)))
266 (parse-toml "odt1 = 1979-05-27T07:32:00Z
267odt2 = 1979-05-27T00:32:00-07:00
268odt3 = 1979-05-27T00:32:00.999999+07:00
269odt4 = 1979-05-27 07:32:00Z
270odt5 = 1979-05-27 07:32:00z"))
271
272(test-equal "parse-toml: Local date-time"
273 `(("ldt1" . ,(make-date #f 0 32 7 27 5 1979 #f))
274 ("ldt2" . ,(make-date 999999 0 32 0 27 5 1979 #f)))
275 (parse-toml "ldt1 = 1979-05-27T07:32:00
276ldt2 = 1979-05-27T00:32:00.999999"))
277
278(test-equal "parse-toml: Local date"
279 `(("ld1" . ,(make-date #f #f #f #f 27 5 1979 #f)))
280 (parse-toml "ld1 = 1979-05-27"))
281
282(test-equal "parse-toml: Local time"
283 `(("lt1" . ,(make-date #f 0 32 7 #f #f #f #f))
284 ("lt2" . ,(make-date 999999 0 32 0 #f #f #f #f)))
285 (parse-toml "lt1 = 07:32:00
286lt2 = 00:32:00.999999"))
287
288(test-equal "parse-toml: Arrays"
289 '(("integers" 1 2 3)
290 ("colors" "red" "yellow" "green")
291 ("nested_arrays_of_ints" (1 2) (3 4 5))
292 ("nested_mixed_array" (1 2) ("a" "b" "c"))
293 ("string_array" "all" "strings")
294 ("numbers" 0.1 0.2 0.5 1 2 5)
295 ("contributors" "Foo Bar <[email protected]>" (("name" . "Baz Qux") ("email" . "[email protected]") ("url" . "https://example.com/bazqux")))
296 ("integers2" 1 2 3)
297 ("integers3" 1 2))
298 (parse-toml "integers = [ 1, 2, 3 ]
299colors = [ \"red\", \"yellow\", \"green\" ]
300nested_arrays_of_ints = [ [ 1, 2 ], [3, 4, 5] ]
301nested_mixed_array = [ [ 1, 2 ], [\"a\", \"b\", \"c\"] ]
302string_array = [ \"all\", 'strings' ]
303
304# Mixed-type arrays are allowed
305numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]
306contributors = [
307 \"Foo Bar <[email protected]>\",
308 { name = \"Baz Qux\", email = \"[email protected]\", url = \"https://example.com/bazqux\" }
309]
310
311integers2 = [
312 1, 2, 3
313]
314
315integers3 = [
316 1,
317 2, # this is ok
318]"))
319
320(test-equal "parse-toml: Arrays of empty strings"
321 '(("empty1" "")
322 ("empty2" "" "")
323 ("empty3" "" "" "")
324 ("emptyraw1" "")
325 ("emptyraw2" "" "")
326 ("emptyraw3" "" "" "")
327 ("emptyml1" "")
328 ("emptyml2" "" "")
329 ("emptyml3" "" "" "")
330 ("emptyrawml1" "")
331 ("emptyrawml2" "" "")
332 ("emptyrawml3" "" "" ""))
333 (parse-toml "empty1 = [ \"\" ]
334empty2 = [ \"\", \"\" ]
335empty3 = [ \"\", \"\", \"\" ]
336emptyraw1 = [ '' ]
337emptyraw2 = [ '', '' ]
338emptyraw3 = [ '', '', '' ]
339emptyml1 = [ \"\"\"\"\"\" ]
340emptyml2 = [ \"\"\"\"\"\", \"\"\"\"\"\" ]
341emptyml3 = [ \"\"\"\"\"\", \"\"\"\"\"\", \"\"\"\"\"\" ]
342emptyrawml1 = [ '''''' ]
343emptyrawml2 = [ '''''', '''''' ]
344emptyrawml3 = [ '''''', '''''', '''''' ]
345"))
346
347(test-equal "parse-toml: Tables"
348 '(("table-1" ("key1" . "some string")
349 ("key2" . 123))
350 ("table-2" ("key1" . "another string")
351 ("key2" . 456)))
352 (parse-toml "[table-1]
353key1 = \"some string\"
354key2 = 123
355
356[table-2]
357key1 = \"another string\"
358key2 = 456"))
359
360
361(test-equal "parse-toml: Dotted table"
362 '(("dog" ("tater.man" ("type" ("name" . "pug")))))
363 (parse-toml "[dog.\"tater.man\"]
364type.name = \"pug\""))
365
366
367(test-equal "parse-toml: Dotted table with whitespace"
368 '(("a" ("b" ("c" ("x" . 1))))
369 ("d" ("e" ("f" ("x" . 1))))
370 ("g" ("h" ("i" ("x" . 1))))
371 ("j" ("ʞ" ("l" ("x" . 1)))))
372 (parse-toml "[a.b.c] # this is best practice
373x=1
374[ d.e.f ] # same as [d.e.f]
375x=1
376[ g . h . i ] # same as [g.h.i]
377x=1
378[ j . \"ʞ\" . 'l' ] # same as [j.\"ʞ\".'l']
379x=1"))
380
381;; XXX: technically this is not allowed, but we permit it.
382(test-equal "parse-toml: Multiple tables"
383 '(("fruit" ("apple" . "red") ("orange" . "orange")))
384 (parse-toml "[fruit]
385apple = \"red\"
386
387[fruit]
388orange = \"orange\""))
389
390(test-error "parse-toml: Assignment to non-table"
391 #t
392 (parse-toml "[fruit]
393apple = \"red\"
394
395[fruit.apple]
396texture = \"smooth\""))
397
398(test-equal "parse-toml: Dotted keys create tables"
399 '(("fruit" ("apple" ("color" . "red") ("taste" ("sweet" . #t)))))
400 (parse-toml "fruit.apple.color = \"red\"
401fruit.apple.taste.sweet = true"))
402
403(test-equal "parse-toml: Inline tables"
404 '(("name" ("first" . "Tom") ("last" . "Preston-Werner"))
405 ("point" ("x" . 1) ("y" . 2))
406 ("animal" ("type" ("name" . "pug")))
407 ("contact" . (("personal" . (("name" . "Donald Duck")
408 ("email" . "[email protected]")))
409 ("work" . (("name" . "Coin cleaner")
410 ("email" . "[email protected]"))))))
411 (parse-toml "name = { first = \"Tom\", last = \"Preston-Werner\" }
412point = { x = 1, y = 2 }
413animal = { type.name = \"pug\" }
414contact = {
415 personal = {
416 name = \"Donald Duck\",
417 email = \"[email protected]\",
418 },
419 work = {
420 name = \"Coin cleaner\",
421 email = \"[email protected]\",
422 },
423}"))
424
425(test-equal "parse-toml: Empty inline table"
426 '(("name")
427 ("point")
428 ("animal"))
429 (parse-toml "name = {}
430point = { }
431animal = { }"))
432
433(test-error "parse-toml: Invalid assignment to inline table"
434 #t
435 (parse-toml "[product]
436type = { name = \"Nail\" }
437type.edible = false # INVALID"))
438
439;; We do not catch this semantic error yet.
440(test-expect-fail 1)
441(test-error "parse-toml: Invalid assignment to implicit table"
442 #t
443 (parse-toml "[product]
444type.name = \"Nail\"
445type = { edible = false } # INVALID"))
446
447;; Not implemented.
448(test-expect-fail 1)
449(test-equal "parse-toml: Array of tables"
450 '(("products" (("name" . "Hammer") ("sku" . 738594937))
451 ()
452 (("name" . "Nail") ("sku" . 284758393) ("color" . "gray"))))
453 (parse-toml "[[products]]
454name = \"Hammer\"
455sku = 738594937
456
457[[products]] # empty table within the array
458
459[[products]]
460name = \"Nail\"
461sku = 284758393
462
463color = \"gray\""))
464
465;; Not implemented.
466(test-expect-fail 1)
467(test-equal "parse-toml: Array of tables"
468 '(("fruits" . ((("name" . "apple")
469 ("physical" . (("color" . "red")
470 ("shape" . "round")))
471 ("varieties" . ((("name" . "red delicious"))
472 (("name" . "granny smith")))))
473 (("name" . "banana")
474 ("varieties" . ((("name" . "plantain"))))))))
475 (parse-toml "[[fruits]]
476name = \"apple\"
477
478[fruits.physical] # subtable
479color = \"red\"
480shape = \"round\"
481
482[[fruits.varieties]] # nested array of tables
483name = \"red delicious\"
484
485[[fruits.varieties]]
486name = \"granny smith\"
487
488
489[[fruits]]
490name = \"banana\"
491
492[[fruits.varieties]]
493name = \"plantain\""))
494
495;; Not implemented.
496(test-expect-fail 1)
497(test-error "parse-toml: Assignment to statically defined array"
498 #t
499 (parse-toml "fruits = []
500
501[[fruits]]
502x=1"))
503
504(test-end "toml")
505