]> git.lizzy.rs Git - rust.git/blob - src/librustc_expand/tests.rs
Use better bound names in `-Zverbose` mode
[rust.git] / src / librustc_expand / tests.rs
1 use rustc_parse::{new_parser_from_source_str, parser::Parser, source_file_to_stream};
2 use rustc_span::source_map::{FilePathMapping, SourceMap};
3 use rustc_span::{BytePos, MultiSpan, Span};
4 use syntax::ast;
5 use syntax::sess::ParseSess;
6 use syntax::tokenstream::TokenStream;
7 use syntax::with_default_globals;
8
9 use rustc_data_structures::sync::Lrc;
10 use rustc_errors::emitter::EmitterWriter;
11 use rustc_errors::{Handler, PResult};
12
13 use std::io;
14 use std::io::prelude::*;
15 use std::iter::Peekable;
16 use std::path::{Path, PathBuf};
17 use std::str;
18 use std::sync::{Arc, Mutex};
19
20 /// Map string to parser (via tts).
21 fn string_to_parser(ps: &ParseSess, source_str: String) -> Parser<'_> {
22     new_parser_from_source_str(ps, PathBuf::from("bogofile").into(), source_str)
23 }
24
25 crate fn with_error_checking_parse<'a, T, F>(s: String, ps: &'a ParseSess, f: F) -> T
26 where
27     F: FnOnce(&mut Parser<'a>) -> PResult<'a, T>,
28 {
29     let mut p = string_to_parser(&ps, s);
30     let x = f(&mut p).unwrap();
31     p.sess.span_diagnostic.abort_if_errors();
32     x
33 }
34
35 /// Maps a string to tts, using a made-up filename.
36 crate fn string_to_stream(source_str: String) -> TokenStream {
37     let ps = ParseSess::new(FilePathMapping::empty());
38     source_file_to_stream(
39         &ps,
40         ps.source_map().new_source_file(PathBuf::from("bogofile").into(), source_str),
41         None,
42     )
43     .0
44 }
45
46 /// Parses a string, returns a crate.
47 crate fn string_to_crate(source_str: String) -> ast::Crate {
48     let ps = ParseSess::new(FilePathMapping::empty());
49     with_error_checking_parse(source_str, &ps, |p| p.parse_crate_mod())
50 }
51
52 /// Does the given string match the pattern? whitespace in the first string
53 /// may be deleted or replaced with other whitespace to match the pattern.
54 /// This function is relatively Unicode-ignorant; fortunately, the careful design
55 /// of UTF-8 mitigates this ignorance. It doesn't do NKF-normalization(?).
56 crate fn matches_codepattern(a: &str, b: &str) -> bool {
57     let mut a_iter = a.chars().peekable();
58     let mut b_iter = b.chars().peekable();
59
60     loop {
61         let (a, b) = match (a_iter.peek(), b_iter.peek()) {
62             (None, None) => return true,
63             (None, _) => return false,
64             (Some(&a), None) => {
65                 if rustc_lexer::is_whitespace(a) {
66                     break; // Trailing whitespace check is out of loop for borrowck.
67                 } else {
68                     return false;
69                 }
70             }
71             (Some(&a), Some(&b)) => (a, b),
72         };
73
74         if rustc_lexer::is_whitespace(a) && rustc_lexer::is_whitespace(b) {
75             // Skip whitespace for `a` and `b`.
76             scan_for_non_ws_or_end(&mut a_iter);
77             scan_for_non_ws_or_end(&mut b_iter);
78         } else if rustc_lexer::is_whitespace(a) {
79             // Skip whitespace for `a`.
80             scan_for_non_ws_or_end(&mut a_iter);
81         } else if a == b {
82             a_iter.next();
83             b_iter.next();
84         } else {
85             return false;
86         }
87     }
88
89     // Check if a has *only* trailing whitespace.
90     a_iter.all(rustc_lexer::is_whitespace)
91 }
92
93 /// Advances the given peekable `Iterator` until it reaches a non-whitespace character.
94 fn scan_for_non_ws_or_end<I: Iterator<Item = char>>(iter: &mut Peekable<I>) {
95     while iter.peek().copied().map(|c| rustc_lexer::is_whitespace(c)) == Some(true) {
96         iter.next();
97     }
98 }
99
100 /// Identifies a position in the text by the n'th occurrence of a string.
101 struct Position {
102     string: &'static str,
103     count: usize,
104 }
105
106 struct SpanLabel {
107     start: Position,
108     end: Position,
109     label: &'static str,
110 }
111
112 crate struct Shared<T: Write> {
113     pub data: Arc<Mutex<T>>,
114 }
115
116 impl<T: Write> Write for Shared<T> {
117     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
118         self.data.lock().unwrap().write(buf)
119     }
120
121     fn flush(&mut self) -> io::Result<()> {
122         self.data.lock().unwrap().flush()
123     }
124 }
125
126 fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &str) {
127     with_default_globals(|| {
128         let output = Arc::new(Mutex::new(Vec::new()));
129
130         let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
131         source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned());
132
133         let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end);
134         let mut msp = MultiSpan::from_span(primary_span);
135         for span_label in span_labels {
136             let span = make_span(&file_text, &span_label.start, &span_label.end);
137             msp.push_span_label(span, span_label.label.to_string());
138             println!("span: {:?} label: {:?}", span, span_label.label);
139             println!("text: {:?}", source_map.span_to_snippet(span));
140         }
141
142         let emitter = EmitterWriter::new(
143             Box::new(Shared { data: output.clone() }),
144             Some(source_map.clone()),
145             false,
146             false,
147             false,
148             None,
149             false,
150         );
151         let handler = Handler::with_emitter(true, None, Box::new(emitter));
152         handler.span_err(msp, "foo");
153
154         assert!(
155             expected_output.chars().next() == Some('\n'),
156             "expected output should begin with newline"
157         );
158         let expected_output = &expected_output[1..];
159
160         let bytes = output.lock().unwrap();
161         let actual_output = str::from_utf8(&bytes).unwrap();
162         println!("expected output:\n------\n{}------", expected_output);
163         println!("actual output:\n------\n{}------", actual_output);
164
165         assert!(expected_output == actual_output)
166     })
167 }
168
169 fn make_span(file_text: &str, start: &Position, end: &Position) -> Span {
170     let start = make_pos(file_text, start);
171     let end = make_pos(file_text, end) + end.string.len(); // just after matching thing ends
172     assert!(start <= end);
173     Span::with_root_ctxt(BytePos(start as u32), BytePos(end as u32))
174 }
175
176 fn make_pos(file_text: &str, pos: &Position) -> usize {
177     let mut remainder = file_text;
178     let mut offset = 0;
179     for _ in 0..pos.count {
180         if let Some(n) = remainder.find(&pos.string) {
181             offset += n;
182             remainder = &remainder[n + 1..];
183         } else {
184             panic!("failed to find {} instances of {:?} in {:?}", pos.count, pos.string, file_text);
185         }
186     }
187     offset
188 }
189
190 #[test]
191 fn ends_on_col0() {
192     test_harness(
193         r#"
194 fn foo() {
195 }
196 "#,
197         vec![SpanLabel {
198             start: Position { string: "{", count: 1 },
199             end: Position { string: "}", count: 1 },
200             label: "test",
201         }],
202         r#"
203 error: foo
204  --> test.rs:2:10
205   |
206 2 |   fn foo() {
207   |  __________^
208 3 | | }
209   | |_^ test
210
211 "#,
212     );
213 }
214
215 #[test]
216 fn ends_on_col2() {
217     test_harness(
218         r#"
219 fn foo() {
220
221
222   }
223 "#,
224         vec![SpanLabel {
225             start: Position { string: "{", count: 1 },
226             end: Position { string: "}", count: 1 },
227             label: "test",
228         }],
229         r#"
230 error: foo
231  --> test.rs:2:10
232   |
233 2 |   fn foo() {
234   |  __________^
235 3 | |
236 4 | |
237 5 | |   }
238   | |___^ test
239
240 "#,
241     );
242 }
243 #[test]
244 fn non_nested() {
245     test_harness(
246         r#"
247 fn foo() {
248   X0 Y0
249   X1 Y1
250   X2 Y2
251 }
252 "#,
253         vec![
254             SpanLabel {
255                 start: Position { string: "X0", count: 1 },
256                 end: Position { string: "X2", count: 1 },
257                 label: "`X` is a good letter",
258             },
259             SpanLabel {
260                 start: Position { string: "Y0", count: 1 },
261                 end: Position { string: "Y2", count: 1 },
262                 label: "`Y` is a good letter too",
263             },
264         ],
265         r#"
266 error: foo
267  --> test.rs:3:3
268   |
269 3 |      X0 Y0
270   |  ____^__-
271   | | ___|
272   | ||
273 4 | ||   X1 Y1
274 5 | ||   X2 Y2
275   | ||____^__- `Y` is a good letter too
276   |  |____|
277   |       `X` is a good letter
278
279 "#,
280     );
281 }
282
283 #[test]
284 fn nested() {
285     test_harness(
286         r#"
287 fn foo() {
288   X0 Y0
289   Y1 X1
290 }
291 "#,
292         vec![
293             SpanLabel {
294                 start: Position { string: "X0", count: 1 },
295                 end: Position { string: "X1", count: 1 },
296                 label: "`X` is a good letter",
297             },
298             SpanLabel {
299                 start: Position { string: "Y0", count: 1 },
300                 end: Position { string: "Y1", count: 1 },
301                 label: "`Y` is a good letter too",
302             },
303         ],
304         r#"
305 error: foo
306  --> test.rs:3:3
307   |
308 3 |      X0 Y0
309   |  ____^__-
310   | | ___|
311   | ||
312 4 | ||   Y1 X1
313   | ||____-__^ `X` is a good letter
314   | |_____|
315   |       `Y` is a good letter too
316
317 "#,
318     );
319 }
320
321 #[test]
322 fn different_overlap() {
323     test_harness(
324         r#"
325 fn foo() {
326   X0 Y0 Z0
327   X1 Y1 Z1
328   X2 Y2 Z2
329   X3 Y3 Z3
330 }
331 "#,
332         vec![
333             SpanLabel {
334                 start: Position { string: "Y0", count: 1 },
335                 end: Position { string: "X2", count: 1 },
336                 label: "`X` is a good letter",
337             },
338             SpanLabel {
339                 start: Position { string: "Z1", count: 1 },
340                 end: Position { string: "X3", count: 1 },
341                 label: "`Y` is a good letter too",
342             },
343         ],
344         r#"
345 error: foo
346  --> test.rs:3:6
347   |
348 3 |      X0 Y0 Z0
349   |   ______^
350 4 |  |   X1 Y1 Z1
351   |  |_________-
352 5 | ||   X2 Y2 Z2
353   | ||____^ `X` is a good letter
354 6 | |    X3 Y3 Z3
355   | |_____- `Y` is a good letter too
356
357 "#,
358     );
359 }
360
361 #[test]
362 fn triple_overlap() {
363     test_harness(
364         r#"
365 fn foo() {
366   X0 Y0 Z0
367   X1 Y1 Z1
368   X2 Y2 Z2
369 }
370 "#,
371         vec![
372             SpanLabel {
373                 start: Position { string: "X0", count: 1 },
374                 end: Position { string: "X2", count: 1 },
375                 label: "`X` is a good letter",
376             },
377             SpanLabel {
378                 start: Position { string: "Y0", count: 1 },
379                 end: Position { string: "Y2", count: 1 },
380                 label: "`Y` is a good letter too",
381             },
382             SpanLabel {
383                 start: Position { string: "Z0", count: 1 },
384                 end: Position { string: "Z2", count: 1 },
385                 label: "`Z` label",
386             },
387         ],
388         r#"
389 error: foo
390  --> test.rs:3:3
391   |
392 3 |       X0 Y0 Z0
393   |  _____^__-__-
394   | | ____|__|
395   | || ___|
396   | |||
397 4 | |||   X1 Y1 Z1
398 5 | |||   X2 Y2 Z2
399   | |||____^__-__- `Z` label
400   |  ||____|__|
401   |   |____|  `Y` is a good letter too
402   |        `X` is a good letter
403
404 "#,
405     );
406 }
407
408 #[test]
409 fn triple_exact_overlap() {
410     test_harness(
411         r#"
412 fn foo() {
413   X0 Y0 Z0
414   X1 Y1 Z1
415   X2 Y2 Z2
416 }
417 "#,
418         vec![
419             SpanLabel {
420                 start: Position { string: "X0", count: 1 },
421                 end: Position { string: "X2", count: 1 },
422                 label: "`X` is a good letter",
423             },
424             SpanLabel {
425                 start: Position { string: "X0", count: 1 },
426                 end: Position { string: "X2", count: 1 },
427                 label: "`Y` is a good letter too",
428             },
429             SpanLabel {
430                 start: Position { string: "X0", count: 1 },
431                 end: Position { string: "X2", count: 1 },
432                 label: "`Z` label",
433             },
434         ],
435         r#"
436 error: foo
437  --> test.rs:3:3
438   |
439 3 | /   X0 Y0 Z0
440 4 | |   X1 Y1 Z1
441 5 | |   X2 Y2 Z2
442   | |    ^
443   | |    |
444   | |    `X` is a good letter
445   | |____`Y` is a good letter too
446   |      `Z` label
447
448 "#,
449     );
450 }
451
452 #[test]
453 fn minimum_depth() {
454     test_harness(
455         r#"
456 fn foo() {
457   X0 Y0 Z0
458   X1 Y1 Z1
459   X2 Y2 Z2
460   X3 Y3 Z3
461 }
462 "#,
463         vec![
464             SpanLabel {
465                 start: Position { string: "Y0", count: 1 },
466                 end: Position { string: "X1", count: 1 },
467                 label: "`X` is a good letter",
468             },
469             SpanLabel {
470                 start: Position { string: "Y1", count: 1 },
471                 end: Position { string: "Z2", count: 1 },
472                 label: "`Y` is a good letter too",
473             },
474             SpanLabel {
475                 start: Position { string: "X2", count: 1 },
476                 end: Position { string: "Y3", count: 1 },
477                 label: "`Z`",
478             },
479         ],
480         r#"
481 error: foo
482  --> test.rs:3:6
483   |
484 3 |      X0 Y0 Z0
485   |   ______^
486 4 |  |   X1 Y1 Z1
487   |  |____^_-
488   | ||____|
489   | |     `X` is a good letter
490 5 | |    X2 Y2 Z2
491   | |____-______- `Y` is a good letter too
492   |  ____|
493   | |
494 6 | |    X3 Y3 Z3
495   | |________- `Z`
496
497 "#,
498     );
499 }
500
501 #[test]
502 fn non_overlaping() {
503     test_harness(
504         r#"
505 fn foo() {
506   X0 Y0 Z0
507   X1 Y1 Z1
508   X2 Y2 Z2
509   X3 Y3 Z3
510 }
511 "#,
512         vec![
513             SpanLabel {
514                 start: Position { string: "X0", count: 1 },
515                 end: Position { string: "X1", count: 1 },
516                 label: "`X` is a good letter",
517             },
518             SpanLabel {
519                 start: Position { string: "Y2", count: 1 },
520                 end: Position { string: "Z3", count: 1 },
521                 label: "`Y` is a good letter too",
522             },
523         ],
524         r#"
525 error: foo
526  --> test.rs:3:3
527   |
528 3 | /   X0 Y0 Z0
529 4 | |   X1 Y1 Z1
530   | |____^ `X` is a good letter
531 5 |     X2 Y2 Z2
532   |  ______-
533 6 | |   X3 Y3 Z3
534   | |__________- `Y` is a good letter too
535
536 "#,
537     );
538 }
539
540 #[test]
541 fn overlaping_start_and_end() {
542     test_harness(
543         r#"
544 fn foo() {
545   X0 Y0 Z0
546   X1 Y1 Z1
547   X2 Y2 Z2
548   X3 Y3 Z3
549 }
550 "#,
551         vec![
552             SpanLabel {
553                 start: Position { string: "Y0", count: 1 },
554                 end: Position { string: "X1", count: 1 },
555                 label: "`X` is a good letter",
556             },
557             SpanLabel {
558                 start: Position { string: "Z1", count: 1 },
559                 end: Position { string: "Z3", count: 1 },
560                 label: "`Y` is a good letter too",
561             },
562         ],
563         r#"
564 error: foo
565  --> test.rs:3:6
566   |
567 3 |      X0 Y0 Z0
568   |   ______^
569 4 |  |   X1 Y1 Z1
570   |  |____^____-
571   | ||____|
572   | |     `X` is a good letter
573 5 | |    X2 Y2 Z2
574 6 | |    X3 Y3 Z3
575   | |___________- `Y` is a good letter too
576
577 "#,
578     );
579 }
580
581 #[test]
582 fn multiple_labels_primary_without_message() {
583     test_harness(
584         r#"
585 fn foo() {
586   a { b { c } d }
587 }
588 "#,
589         vec![
590             SpanLabel {
591                 start: Position { string: "b", count: 1 },
592                 end: Position { string: "}", count: 1 },
593                 label: "",
594             },
595             SpanLabel {
596                 start: Position { string: "a", count: 1 },
597                 end: Position { string: "d", count: 1 },
598                 label: "`a` is a good letter",
599             },
600             SpanLabel {
601                 start: Position { string: "c", count: 1 },
602                 end: Position { string: "c", count: 1 },
603                 label: "",
604             },
605         ],
606         r#"
607 error: foo
608  --> test.rs:3:7
609   |
610 3 |   a { b { c } d }
611   |   ----^^^^-^^-- `a` is a good letter
612
613 "#,
614     );
615 }
616
617 #[test]
618 fn multiple_labels_secondary_without_message() {
619     test_harness(
620         r#"
621 fn foo() {
622   a { b { c } d }
623 }
624 "#,
625         vec![
626             SpanLabel {
627                 start: Position { string: "a", count: 1 },
628                 end: Position { string: "d", count: 1 },
629                 label: "`a` is a good letter",
630             },
631             SpanLabel {
632                 start: Position { string: "b", count: 1 },
633                 end: Position { string: "}", count: 1 },
634                 label: "",
635             },
636         ],
637         r#"
638 error: foo
639  --> test.rs:3:3
640   |
641 3 |   a { b { c } d }
642   |   ^^^^-------^^ `a` is a good letter
643
644 "#,
645     );
646 }
647
648 #[test]
649 fn multiple_labels_primary_without_message_2() {
650     test_harness(
651         r#"
652 fn foo() {
653   a { b { c } d }
654 }
655 "#,
656         vec![
657             SpanLabel {
658                 start: Position { string: "b", count: 1 },
659                 end: Position { string: "}", count: 1 },
660                 label: "`b` is a good letter",
661             },
662             SpanLabel {
663                 start: Position { string: "a", count: 1 },
664                 end: Position { string: "d", count: 1 },
665                 label: "",
666             },
667             SpanLabel {
668                 start: Position { string: "c", count: 1 },
669                 end: Position { string: "c", count: 1 },
670                 label: "",
671             },
672         ],
673         r#"
674 error: foo
675  --> test.rs:3:7
676   |
677 3 |   a { b { c } d }
678   |   ----^^^^-^^--
679   |       |
680   |       `b` is a good letter
681
682 "#,
683     );
684 }
685
686 #[test]
687 fn multiple_labels_secondary_without_message_2() {
688     test_harness(
689         r#"
690 fn foo() {
691   a { b { c } d }
692 }
693 "#,
694         vec![
695             SpanLabel {
696                 start: Position { string: "a", count: 1 },
697                 end: Position { string: "d", count: 1 },
698                 label: "",
699             },
700             SpanLabel {
701                 start: Position { string: "b", count: 1 },
702                 end: Position { string: "}", count: 1 },
703                 label: "`b` is a good letter",
704             },
705         ],
706         r#"
707 error: foo
708  --> test.rs:3:3
709   |
710 3 |   a { b { c } d }
711   |   ^^^^-------^^
712   |       |
713   |       `b` is a good letter
714
715 "#,
716     );
717 }
718
719 #[test]
720 fn multiple_labels_secondary_without_message_3() {
721     test_harness(
722         r#"
723 fn foo() {
724   a  bc  d
725 }
726 "#,
727         vec![
728             SpanLabel {
729                 start: Position { string: "a", count: 1 },
730                 end: Position { string: "b", count: 1 },
731                 label: "`a` is a good letter",
732             },
733             SpanLabel {
734                 start: Position { string: "c", count: 1 },
735                 end: Position { string: "d", count: 1 },
736                 label: "",
737             },
738         ],
739         r#"
740 error: foo
741  --> test.rs:3:3
742   |
743 3 |   a  bc  d
744   |   ^^^^----
745   |   |
746   |   `a` is a good letter
747
748 "#,
749     );
750 }
751
752 #[test]
753 fn multiple_labels_without_message() {
754     test_harness(
755         r#"
756 fn foo() {
757   a { b { c } d }
758 }
759 "#,
760         vec![
761             SpanLabel {
762                 start: Position { string: "a", count: 1 },
763                 end: Position { string: "d", count: 1 },
764                 label: "",
765             },
766             SpanLabel {
767                 start: Position { string: "b", count: 1 },
768                 end: Position { string: "}", count: 1 },
769                 label: "",
770             },
771         ],
772         r#"
773 error: foo
774  --> test.rs:3:3
775   |
776 3 |   a { b { c } d }
777   |   ^^^^-------^^
778
779 "#,
780     );
781 }
782
783 #[test]
784 fn multiple_labels_without_message_2() {
785     test_harness(
786         r#"
787 fn foo() {
788   a { b { c } d }
789 }
790 "#,
791         vec![
792             SpanLabel {
793                 start: Position { string: "b", count: 1 },
794                 end: Position { string: "}", count: 1 },
795                 label: "",
796             },
797             SpanLabel {
798                 start: Position { string: "a", count: 1 },
799                 end: Position { string: "d", count: 1 },
800                 label: "",
801             },
802             SpanLabel {
803                 start: Position { string: "c", count: 1 },
804                 end: Position { string: "c", count: 1 },
805                 label: "",
806             },
807         ],
808         r#"
809 error: foo
810  --> test.rs:3:7
811   |
812 3 |   a { b { c } d }
813   |   ----^^^^-^^--
814
815 "#,
816     );
817 }
818
819 #[test]
820 fn multiple_labels_with_message() {
821     test_harness(
822         r#"
823 fn foo() {
824   a { b { c } d }
825 }
826 "#,
827         vec![
828             SpanLabel {
829                 start: Position { string: "a", count: 1 },
830                 end: Position { string: "d", count: 1 },
831                 label: "`a` is a good letter",
832             },
833             SpanLabel {
834                 start: Position { string: "b", count: 1 },
835                 end: Position { string: "}", count: 1 },
836                 label: "`b` is a good letter",
837             },
838         ],
839         r#"
840 error: foo
841  --> test.rs:3:3
842   |
843 3 |   a { b { c } d }
844   |   ^^^^-------^^
845   |   |   |
846   |   |   `b` is a good letter
847   |   `a` is a good letter
848
849 "#,
850     );
851 }
852
853 #[test]
854 fn single_label_with_message() {
855     test_harness(
856         r#"
857 fn foo() {
858   a { b { c } d }
859 }
860 "#,
861         vec![SpanLabel {
862             start: Position { string: "a", count: 1 },
863             end: Position { string: "d", count: 1 },
864             label: "`a` is a good letter",
865         }],
866         r#"
867 error: foo
868  --> test.rs:3:3
869   |
870 3 |   a { b { c } d }
871   |   ^^^^^^^^^^^^^ `a` is a good letter
872
873 "#,
874     );
875 }
876
877 #[test]
878 fn single_label_without_message() {
879     test_harness(
880         r#"
881 fn foo() {
882   a { b { c } d }
883 }
884 "#,
885         vec![SpanLabel {
886             start: Position { string: "a", count: 1 },
887             end: Position { string: "d", count: 1 },
888             label: "",
889         }],
890         r#"
891 error: foo
892  --> test.rs:3:3
893   |
894 3 |   a { b { c } d }
895   |   ^^^^^^^^^^^^^
896
897 "#,
898     );
899 }
900
901 #[test]
902 fn long_snippet() {
903     test_harness(
904         r#"
905 fn foo() {
906   X0 Y0 Z0
907   X1 Y1 Z1
908 1
909 2
910 3
911 4
912 5
913 6
914 7
915 8
916 9
917 10
918   X2 Y2 Z2
919   X3 Y3 Z3
920 }
921 "#,
922         vec![
923             SpanLabel {
924                 start: Position { string: "Y0", count: 1 },
925                 end: Position { string: "X1", count: 1 },
926                 label: "`X` is a good letter",
927             },
928             SpanLabel {
929                 start: Position { string: "Z1", count: 1 },
930                 end: Position { string: "Z3", count: 1 },
931                 label: "`Y` is a good letter too",
932             },
933         ],
934         r#"
935 error: foo
936   --> test.rs:3:6
937    |
938 3  |      X0 Y0 Z0
939    |   ______^
940 4  |  |   X1 Y1 Z1
941    |  |____^____-
942    | ||____|
943    | |     `X` is a good letter
944 5  | |  1
945 6  | |  2
946 7  | |  3
947 ...  |
948 15 | |    X2 Y2 Z2
949 16 | |    X3 Y3 Z3
950    | |___________- `Y` is a good letter too
951
952 "#,
953     );
954 }
955
956 #[test]
957 fn long_snippet_multiple_spans() {
958     test_harness(
959         r#"
960 fn foo() {
961   X0 Y0 Z0
962 1
963 2
964 3
965   X1 Y1 Z1
966 4
967 5
968 6
969   X2 Y2 Z2
970 7
971 8
972 9
973 10
974   X3 Y3 Z3
975 }
976 "#,
977         vec![
978             SpanLabel {
979                 start: Position { string: "Y0", count: 1 },
980                 end: Position { string: "Y3", count: 1 },
981                 label: "`Y` is a good letter",
982             },
983             SpanLabel {
984                 start: Position { string: "Z1", count: 1 },
985                 end: Position { string: "Z2", count: 1 },
986                 label: "`Z` is a good letter too",
987             },
988         ],
989         r#"
990 error: foo
991   --> test.rs:3:6
992    |
993 3  |      X0 Y0 Z0
994    |   ______^
995 4  |  | 1
996 5  |  | 2
997 6  |  | 3
998 7  |  |   X1 Y1 Z1
999    |  |_________-
1000 8  | || 4
1001 9  | || 5
1002 10 | || 6
1003 11 | ||   X2 Y2 Z2
1004    | ||__________- `Z` is a good letter too
1005 ...   |
1006 15 |  | 10
1007 16 |  |   X3 Y3 Z3
1008    |  |_______^ `Y` is a good letter
1009
1010 "#,
1011     );
1012 }