1 use std::fmt;
2 use std::iter::FromIterator;
3
4 use snapbox::assert_eq;
5 use toml_edit::{array, table, value, Document, Item, Key, Table, Value};
6
7 macro_rules! parse_key {
8 ($s:expr) => {{
9 let key = $s.parse::<Key>();
10 assert!(key.is_ok());
11 key.unwrap()
12 }};
13 }
14
15 macro_rules! as_table {
16 ($e:ident) => {{
17 assert!($e.is_table());
18 $e.as_table_mut().unwrap()
19 }};
20 }
21
22 // Copied from https://github.com/colin-kiegel/rust-pretty-assertions/issues/24
23 /// Wrapper around string slice that makes debug output `{:?}` to print string same way as `{}`.
24 /// Used in different `assert*!` macros in combination with `pretty_assertions` crate to make
25 /// test failures to show nice diffs.
26 #[derive(PartialEq, Eq)]
27 struct PrettyString<'a>(pub &'a str);
28 /// Make diff to display string as multi-line string
29 impl<'a> fmt::Debug for PrettyString<'a> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.write_str(self.0)
32 }
33 }
34
35 struct Test {
36 doc: Document,
37 }
38
given(input: &str) -> Test39 fn given(input: &str) -> Test {
40 let doc = input.parse::<Document>();
41 assert!(doc.is_ok());
42 Test { doc: doc.unwrap() }
43 }
44
45 impl Test {
running<F>(&mut self, func: F) -> &mut Self where F: Fn(&mut Table),46 fn running<F>(&mut self, func: F) -> &mut Self
47 where
48 F: Fn(&mut Table),
49 {
50 {
51 let root = self.doc.as_table_mut();
52 func(root);
53 }
54 self
55 }
56
57 #[track_caller]
produces_display(&self, expected: &str) -> &Self58 fn produces_display(&self, expected: &str) -> &Self {
59 assert_eq(expected, self.doc.to_string());
60 self
61 }
62 }
63
64 // insertion
65
66 #[test]
test_insert_leaf_table()67 fn test_insert_leaf_table() {
68 given(
69 r#"[servers]
70
71 [servers.alpha]
72 ip = "10.0.0.1"
73 dc = "eqdc10"
74
75 [other.table]"#,
76 )
77 .running(|root| {
78 root["servers"]["beta"] = table();
79 root["servers"]["beta"]["ip"] = value("10.0.0.2");
80 root["servers"]["beta"]["dc"] = value("eqdc10");
81 })
82 .produces_display(
83 r#"[servers]
84
85 [servers.alpha]
86 ip = "10.0.0.1"
87 dc = "eqdc10"
88
89 [servers.beta]
90 ip = "10.0.0.2"
91 dc = "eqdc10"
92
93 [other.table]
94 "#,
95 );
96 }
97
98 #[test]
test_inserted_leaf_table_goes_after_last_sibling()99 fn test_inserted_leaf_table_goes_after_last_sibling() {
100 given(
101 r#"
102 [package]
103 [dependencies]
104 [[example]]
105 [dependencies.opencl]
106 [dev-dependencies]"#,
107 )
108 .running(|root| {
109 root["dependencies"]["newthing"] = table();
110 })
111 .produces_display(
112 r#"
113 [package]
114 [dependencies]
115 [[example]]
116 [dependencies.opencl]
117
118 [dependencies.newthing]
119 [dev-dependencies]
120 "#,
121 );
122 }
123
124 #[test]
test_inserting_tables_from_different_parsed_docs()125 fn test_inserting_tables_from_different_parsed_docs() {
126 given("[a]")
127 .running(|root| {
128 let other = "[b]".parse::<Document>().unwrap();
129 root["b"] = other["b"].clone();
130 })
131 .produces_display("[a]\n[b]\n");
132 }
133 #[test]
test_insert_nonleaf_table()134 fn test_insert_nonleaf_table() {
135 given(
136 r#"
137 [other.table]"#,
138 )
139 .running(|root| {
140 root["servers"] = table();
141 root["servers"]["alpha"] = table();
142 root["servers"]["alpha"]["ip"] = value("10.0.0.1");
143 root["servers"]["alpha"]["dc"] = value("eqdc10");
144 })
145 .produces_display(
146 r#"
147 [other.table]
148
149 [servers]
150
151 [servers.alpha]
152 ip = "10.0.0.1"
153 dc = "eqdc10"
154 "#,
155 );
156 }
157
158 #[test]
test_insert_array()159 fn test_insert_array() {
160 given(
161 r#"
162 [package]
163 title = "withoutarray""#,
164 )
165 .running(|root| {
166 root["bin"] = array();
167 assert!(root["bin"].is_array_of_tables());
168 let array = root["bin"].as_array_of_tables_mut().unwrap();
169 {
170 let mut table = Table::new();
171 table["hello"] = value("world");
172 array.push(table);
173 }
174 array.push(Table::new());
175 })
176 .produces_display(
177 r#"
178 [package]
179 title = "withoutarray"
180
181 [[bin]]
182 hello = "world"
183
184 [[bin]]
185 "#,
186 );
187 }
188
189 #[test]
test_insert_values()190 fn test_insert_values() {
191 given(
192 r#"
193 [tbl.son]"#,
194 )
195 .running(|root| {
196 root["tbl"]["key1"] = value("value1");
197 root["tbl"]["key2"] = value(42);
198 root["tbl"]["key3"] = value(8.1415926);
199 })
200 .produces_display(
201 r#"[tbl]
202 key1 = "value1"
203 key2 = 42
204 key3 = 8.1415926
205
206 [tbl.son]
207 "#,
208 );
209 }
210
211 // removal
212
213 #[test]
test_remove_leaf_table()214 fn test_remove_leaf_table() {
215 given(
216 r#"
217 [servers]
218
219 # Indentation (tabs and/or spaces) is allowed but not required
220 [servers.alpha]
221 ip = "10.0.0.1"
222 dc = "eqdc10"
223
224 [servers.beta]
225 ip = "10.0.0.2"
226 dc = "eqdc10""#,
227 )
228 .running(|root| {
229 let servers = root.get_mut("servers").unwrap();
230 let servers = as_table!(servers);
231 assert!(servers.remove("alpha").is_some());
232 })
233 .produces_display(
234 r#"
235 [servers]
236
237 [servers.beta]
238 ip = "10.0.0.2"
239 dc = "eqdc10"
240 "#,
241 );
242 }
243
244 #[test]
test_remove_nonleaf_table()245 fn test_remove_nonleaf_table() {
246 given(
247 r#"
248 title = "not relevant"
249
250 # comment 1
251 [a.b.c] # comment 1.1
252 key1 = 1 # comment 1.2
253 # comment 2
254 [b] # comment 2.1
255 key2 = 2 # comment 2.2
256
257 # comment 3
258 [a] # comment 3.1
259 key3 = 3 # comment 3.2
260 [[a.'array']]
261 b = 1
262
263 [[a.b.c.trololololololo]] # ohohohohoho
264 c = 2
265 key3 = 42
266
267 # comment on some other table
268 [some.other.table]
269
270
271
272
273 # comment 4
274 [a.b] # comment 4.1
275 key4 = 4 # comment 4.2
276 key41 = 41 # comment 4.3
277
278
279 "#,
280 )
281 .running(|root| {
282 assert!(root.remove("a").is_some());
283 })
284 .produces_display(
285 r#"
286 title = "not relevant"
287 # comment 2
288 [b] # comment 2.1
289 key2 = 2 # comment 2.2
290
291 # comment on some other table
292 [some.other.table]
293
294
295 "#,
296 );
297 }
298
299 #[test]
test_remove_array_entry()300 fn test_remove_array_entry() {
301 given(
302 r#"
303 [package]
304 name = "hello"
305 version = "1.0.0"
306
307 [[bin]]
308 name = "world"
309 path = "src/bin/world/main.rs"
310
311 [dependencies]
312 nom = "4.0" # future is here
313
314 [[bin]]
315 name = "delete me please"
316 path = "src/bin/dmp/main.rs""#,
317 )
318 .running(|root| {
319 let dmp = root.get_mut("bin").unwrap();
320 assert!(dmp.is_array_of_tables());
321 let dmp = dmp.as_array_of_tables_mut().unwrap();
322 assert_eq!(dmp.len(), 2);
323 dmp.remove(1);
324 assert_eq!(dmp.len(), 1);
325 })
326 .produces_display(
327 r#"
328 [package]
329 name = "hello"
330 version = "1.0.0"
331
332 [[bin]]
333 name = "world"
334 path = "src/bin/world/main.rs"
335
336 [dependencies]
337 nom = "4.0" # future is here
338 "#,
339 );
340 }
341
342 #[test]
test_remove_array()343 fn test_remove_array() {
344 given(
345 r#"
346 [package]
347 name = "hello"
348 version = "1.0.0"
349
350 [[bin]]
351 name = "world"
352 path = "src/bin/world/main.rs"
353
354 [dependencies]
355 nom = "4.0" # future is here
356
357 [[bin]]
358 name = "delete me please"
359 path = "src/bin/dmp/main.rs""#,
360 )
361 .running(|root| {
362 assert!(root.remove("bin").is_some());
363 })
364 .produces_display(
365 r#"
366 [package]
367 name = "hello"
368 version = "1.0.0"
369
370 [dependencies]
371 nom = "4.0" # future is here
372 "#,
373 );
374 }
375
376 #[test]
test_remove_value()377 fn test_remove_value() {
378 given(
379 r#"
380 name = "hello"
381 # delete this
382 version = "1.0.0" # please
383 documentation = "https://docs.rs/hello""#,
384 )
385 .running(|root| {
386 let value = root.remove("version");
387 assert!(value.is_some());
388 let value = value.unwrap();
389 assert!(value.is_value());
390 let value = value.as_value().unwrap();
391 assert!(value.is_str());
392 let value = value.as_str().unwrap();
393 assert_eq(value, "1.0.0");
394 })
395 .produces_display(
396 r#"
397 name = "hello"
398 documentation = "https://docs.rs/hello"
399 "#,
400 );
401 }
402
403 #[test]
test_remove_last_value_from_implicit()404 fn test_remove_last_value_from_implicit() {
405 given(
406 r#"
407 [a]
408 b = 1"#,
409 )
410 .running(|root| {
411 let a = root.get_mut("a").unwrap();
412 assert!(a.is_table());
413 let a = as_table!(a);
414 a.set_implicit(true);
415 let value = a.remove("b");
416 assert!(value.is_some());
417 let value = value.unwrap();
418 assert!(value.is_value());
419 let value = value.as_value().unwrap();
420 assert_eq!(value.as_integer(), Some(1));
421 })
422 .produces_display(r#""#);
423 }
424
425 // values
426
427 #[test]
test_sort_values()428 fn test_sort_values() {
429 given(
430 r#"
431 [a.z]
432
433 [a]
434 # this comment is attached to b
435 b = 2 # as well as this
436 a = 1
437 c = 3
438
439 [a.y]"#,
440 )
441 .running(|root| {
442 let a = root.get_mut("a").unwrap();
443 let a = as_table!(a);
444 a.sort_values();
445 })
446 .produces_display(
447 r#"
448 [a.z]
449
450 [a]
451 a = 1
452 # this comment is attached to b
453 b = 2 # as well as this
454 c = 3
455
456 [a.y]
457 "#,
458 );
459 }
460
461 #[test]
test_sort_values_by()462 fn test_sort_values_by() {
463 given(
464 r#"
465 [a.z]
466
467 [a]
468 # this comment is attached to b
469 b = 2 # as well as this
470 a = 1
471 "c" = 3
472
473 [a.y]"#,
474 )
475 .running(|root| {
476 let a = root.get_mut("a").unwrap();
477 let a = as_table!(a);
478 // Sort by the representation, not the value. So "\"c\"" sorts before "a" because '"' sorts
479 // before 'a'.
480 a.sort_values_by(|k1, _, k2, _| k1.display_repr().cmp(&k2.display_repr()));
481 })
482 .produces_display(
483 r#"
484 [a.z]
485
486 [a]
487 "c" = 3
488 a = 1
489 # this comment is attached to b
490 b = 2 # as well as this
491
492 [a.y]
493 "#,
494 );
495 }
496
497 #[test]
test_set_position()498 fn test_set_position() {
499 given(
500 r#"
501 [package]
502 [dependencies]
503 [dependencies.opencl]
504 [dev-dependencies]"#,
505 )
506 .running(|root| {
507 for (header, table) in root.iter_mut() {
508 if header == "dependencies" {
509 let tab = as_table!(table);
510 tab.set_position(0);
511 let (_, segmented) = tab.iter_mut().next().unwrap();
512 as_table!(segmented).set_position(5)
513 }
514 }
515 })
516 .produces_display(
517 r#" [dependencies]
518
519 [package]
520 [dev-dependencies]
521 [dependencies.opencl]
522 "#,
523 );
524 }
525
526 #[test]
test_multiple_zero_positions()527 fn test_multiple_zero_positions() {
528 given(
529 r#"
530 [package]
531 [dependencies]
532 [dependencies.opencl]
533 a=""
534 [dev-dependencies]"#,
535 )
536 .running(|root| {
537 for (_, table) in root.iter_mut() {
538 as_table!(table).set_position(0)
539 }
540 })
541 .produces_display(
542 r#"
543 [package]
544 [dependencies]
545 [dev-dependencies]
546 [dependencies.opencl]
547 a=""
548 "#,
549 );
550 }
551
552 #[test]
test_multiple_max_usize_positions()553 fn test_multiple_max_usize_positions() {
554 given(
555 r#"
556 [package]
557 [dependencies]
558 [dependencies.opencl]
559 a=""
560 [dev-dependencies]"#,
561 )
562 .running(|root| {
563 for (_, table) in root.iter_mut() {
564 as_table!(table).set_position(usize::MAX)
565 }
566 })
567 .produces_display(
568 r#" [dependencies.opencl]
569 a=""
570
571 [package]
572 [dependencies]
573 [dev-dependencies]
574 "#,
575 );
576 }
577
578 macro_rules! as_array {
579 ($entry:ident) => {{
580 assert!($entry.is_value());
581 let a = $entry.as_value_mut().unwrap();
582 assert!(a.is_array());
583 a.as_array_mut().unwrap()
584 }};
585 }
586
587 #[test]
test_insert_replace_into_array()588 fn test_insert_replace_into_array() {
589 given(
590 r#"
591 a = [1,2,3]
592 b = []"#,
593 )
594 .running(|root| {
595 {
596 let a = root.get_mut("a").unwrap();
597 let a = as_array!(a);
598 assert_eq!(a.len(), 3);
599 assert!(a.get(2).is_some());
600 a.push(4);
601 assert_eq!(a.len(), 4);
602 a.fmt();
603 }
604 let b = root.get_mut("b").unwrap();
605 let b = as_array!(b);
606 assert!(b.is_empty());
607 b.push("hello");
608 assert_eq!(b.len(), 1);
609
610 b.push_formatted(Value::from("world").decorated("\n", "\n"));
611 b.push_formatted(Value::from("test").decorated("", ""));
612
613 b.insert(1, "beep");
614 b.insert_formatted(2, Value::from("boop").decorated(" ", " "));
615
616 // This should preserve formatting.
617 assert_eq!(b.replace(2, "zoink").as_str(), Some("boop"));
618 // This should replace formatting.
619 assert_eq!(
620 b.replace_formatted(4, Value::from("yikes").decorated(" ", ""))
621 .as_str(),
622 Some("test")
623 );
624 dbg!(root);
625 })
626 .produces_display(
627 r#"
628 a = [1, 2, 3, 4]
629 b = ["hello", "beep", "zoink" ,
630 "world"
631 , "yikes"]
632 "#,
633 );
634 }
635
636 #[test]
test_remove_from_array()637 fn test_remove_from_array() {
638 given(
639 r#"
640 a = [1, 2, 3, 4]
641 b = ["hello"]"#,
642 )
643 .running(|root| {
644 {
645 let a = root.get_mut("a").unwrap();
646 let a = as_array!(a);
647 assert_eq!(a.len(), 4);
648 assert!(a.remove(3).is_integer());
649 assert_eq!(a.len(), 3);
650 }
651 let b = root.get_mut("b").unwrap();
652 let b = as_array!(b);
653 assert_eq!(b.len(), 1);
654 assert!(b.remove(0).is_str());
655 assert!(b.is_empty());
656 })
657 .produces_display(
658 r#"
659 a = [1, 2, 3]
660 b = []
661 "#,
662 );
663 }
664
665 #[test]
test_format_array()666 fn test_format_array() {
667 given(
668 r#"
669 a = [
670 1,
671 "2",
672 3.0,
673 ]
674 "#,
675 )
676 .running(|root| {
677 for (_, v) in root.iter_mut() {
678 if let Item::Value(Value::Array(array)) = v {
679 array.fmt();
680 }
681 }
682 })
683 .produces_display(
684 r#"
685 a = [1, "2", 3.0]
686 "#,
687 );
688 }
689
690 macro_rules! as_inline_table {
691 ($entry:ident) => {{
692 assert!($entry.is_value());
693 let a = $entry.as_value_mut().unwrap();
694 assert!(a.is_inline_table());
695 a.as_inline_table_mut().unwrap()
696 }};
697 }
698
699 #[test]
test_insert_into_inline_table()700 fn test_insert_into_inline_table() {
701 given(
702 r#"
703 a = {a=2, c = 3}
704 b = {}"#,
705 )
706 .running(|root| {
707 {
708 let a = root.get_mut("a").unwrap();
709 let a = as_inline_table!(a);
710 assert_eq!(a.len(), 2);
711 assert!(a.contains_key("a") && a.get("c").is_some() && a.get_mut("c").is_some());
712 a.get_or_insert("b", 42);
713 assert_eq!(a.len(), 3);
714 a.fmt();
715 }
716 let b = root.get_mut("b").unwrap();
717 let b = as_inline_table!(b);
718 assert!(b.is_empty());
719 b.get_or_insert("hello", "world");
720 assert_eq!(b.len(), 1);
721 b.fmt()
722 })
723 .produces_display(
724 r#"
725 a = { a = 2, c = 3, b = 42 }
726 b = { hello = "world" }
727 "#,
728 );
729 }
730
731 #[test]
test_remove_from_inline_table()732 fn test_remove_from_inline_table() {
733 given(
734 r#"
735 a = {a=2, c = 3, b = 42}
736 b = {'hello' = "world"}"#,
737 )
738 .running(|root| {
739 {
740 let a = root.get_mut("a").unwrap();
741 let a = as_inline_table!(a);
742 assert_eq!(a.len(), 3);
743 assert!(a.remove("c").is_some());
744 assert_eq!(a.len(), 2);
745 }
746 let b = root.get_mut("b").unwrap();
747 let b = as_inline_table!(b);
748 assert_eq!(b.len(), 1);
749 assert!(b.remove("hello").is_some());
750 assert!(b.is_empty());
751 })
752 .produces_display(
753 r#"
754 a = {a=2, b = 42}
755 b = {}
756 "#,
757 );
758 }
759
760 #[test]
test_as_table_like()761 fn test_as_table_like() {
762 given(
763 r#"
764 a = {a=2, c = 3, b = 42}
765 x = {}
766 [[bin]]
767 [b]
768 x = "y"
769 [empty]"#,
770 )
771 .running(|root| {
772 let a = root["a"].as_table_like();
773 assert!(a.is_some());
774 let a = a.unwrap();
775 assert_eq!(a.iter().count(), 3);
776 assert_eq!(a.len(), 3);
777 assert_eq!(a.get("a").and_then(Item::as_integer), Some(2));
778
779 let b = root["b"].as_table_like();
780 assert!(b.is_some());
781 let b = b.unwrap();
782 assert_eq!(b.iter().count(), 1);
783 assert_eq!(b.len(), 1);
784 assert_eq!(b.get("x").and_then(Item::as_str), Some("y"));
785
786 assert_eq!(root["x"].as_table_like().map(|t| t.iter().count()), Some(0));
787 assert_eq!(
788 root["empty"].as_table_like().map(|t| t.is_empty()),
789 Some(true)
790 );
791
792 assert!(root["bin"].as_table_like().is_none());
793 });
794 }
795
796 #[test]
test_inline_table_append()797 fn test_inline_table_append() {
798 let mut a = Value::from_iter(vec![
799 (parse_key!("a"), 1),
800 (parse_key!("b"), 2),
801 (parse_key!("c"), 3),
802 ]);
803 let a = a.as_inline_table_mut().unwrap();
804
805 let mut b = Value::from_iter(vec![
806 (parse_key!("c"), 4),
807 (parse_key!("d"), 5),
808 (parse_key!("e"), 6),
809 ]);
810 let b = b.as_inline_table_mut().unwrap();
811
812 a.extend(b.iter());
813 assert_eq!(a.len(), 5);
814 assert!(a.contains_key("e"));
815 assert_eq!(b.len(), 3);
816 }
817
818 #[test]
test_insert_dotted_into_std_table()819 fn test_insert_dotted_into_std_table() {
820 given("")
821 .running(|root| {
822 root["nixpkgs"] = table();
823
824 root["nixpkgs"]["src"] = table();
825 root["nixpkgs"]["src"]
826 .as_table_like_mut()
827 .unwrap()
828 .set_dotted(true);
829 root["nixpkgs"]["src"]["git"] = value("https://github.com/nixos/nixpkgs");
830 })
831 .produces_display(
832 r#"[nixpkgs]
833 src.git = "https://github.com/nixos/nixpkgs"
834 "#,
835 );
836 }
837
838 #[test]
test_insert_dotted_into_implicit_table()839 fn test_insert_dotted_into_implicit_table() {
840 given("")
841 .running(|root| {
842 root["nixpkgs"] = table();
843
844 root["nixpkgs"]["src"]["git"] = value("https://github.com/nixos/nixpkgs");
845 root["nixpkgs"]["src"]
846 .as_table_like_mut()
847 .unwrap()
848 .set_dotted(true);
849 })
850 .produces_display(
851 r#"[nixpkgs]
852 src.git = "https://github.com/nixos/nixpkgs"
853 "#,
854 );
855 }
856
857 #[test]
sorting_with_references()858 fn sorting_with_references() {
859 let values = vec!["foo", "qux", "bar"];
860 let mut array = toml_edit::Array::from_iter(values);
861 array.sort_by(|lhs, rhs| lhs.as_str().cmp(&rhs.as_str()));
862 }
863