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