procontest/
lib.rs

1use cli_test_dir::{CommandExt, TestDir};
2use std::{cmp, fmt, iter, usize};
3
4const BINARY_NAME: &str = "./main";
5
6const EPS: f64 = 1e-8;
7
8const PANE_MINIMUM_SIZE: usize = 20;
9
10#[derive(Debug, PartialEq)]
11pub enum TestResult {
12    Accepted,
13    PresentationError,
14    WrongAnswer(Box<WrongAnswer>),
15    RuntimeError(RuntimeErrorKind),
16}
17
18#[derive(Debug, PartialEq)]
19pub struct WrongAnswer {
20    pub context: Context,
21    pub details: Vec<WrongAnswerKind>,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct Span {
26    line: usize,
27    range: (usize, usize),
28}
29
30impl Span {
31    fn start(&self) -> usize {
32        self.range.0
33    }
34
35    fn end(&self) -> usize {
36        self.range.1
37    }
38}
39
40#[derive(Debug, PartialEq)]
41pub enum WrongAnswerKind {
42    NumOfLineDiffers {
43        expected: usize,
44        actual: usize,
45    },
46    NumOfTokenDiffers {
47        expected: usize,
48        actual: usize,
49        expected_span: Span,
50        actual_span: Span,
51    },
52    TokenDiffers {
53        expected: Token,
54        actual: Token,
55    },
56}
57
58#[derive(Debug, PartialEq, Eq)]
59pub enum RuntimeErrorKind {
60    CommandFailed,
61    InvalidUtf8,
62}
63
64#[macro_export]
65macro_rules! testcase {
66    (id: $id:ident) => {
67        $crate::testcase! {
68            id: $id,
69            stdin: include_str!(concat!(stringify!($id), "_in.txt")),
70            expect: include_str!(concat!(stringify!($id), "_out.txt")),
71        }
72    };
73    (id: $id:ident, stdin: $stdin:expr, expect: $expect:expr $(,)*) => {
74        #[test]
75        fn $id() {
76            let res = $crate::do_test(stringify!($id), $stdin, $expect);
77            if res != $crate::TestResult::Accepted {
78                panic!("{}", $crate::format(&res));
79            }
80        }
81    };
82}
83
84pub fn do_test(test_id: &str, stdin: &str, expected: &str) -> TestResult {
85    let test_dir = TestDir::new(BINARY_NAME, test_id);
86
87    let output = match test_dir.cmd().output_with_stdin(stdin) {
88        Ok(output) => output,
89        Err(_e) => return TestResult::RuntimeError(RuntimeErrorKind::CommandFailed),
90    };
91
92    let actual = match String::from_utf8(output.stdout) {
93        Ok(stdout) => stdout,
94        Err(_e) => return TestResult::RuntimeError(RuntimeErrorKind::InvalidUtf8),
95    };
96
97    let stderr = match String::from_utf8(output.stderr) {
98        Ok(stderr) => stderr,
99        Err(_e) => return TestResult::RuntimeError(RuntimeErrorKind::InvalidUtf8),
100    };
101
102    let expected = split_into_lines(&expected).map(|x| x.to_string()).collect();
103    let actual = split_into_lines(&actual).map(|x| x.to_string()).collect();
104
105    Context::new(expected, actual, stderr).verify()
106}
107
108#[derive(Debug, PartialEq)]
109enum VerifyResult {
110    Pass,
111    Fail(TestResult),
112}
113
114macro_rules! ensure_pass {
115    ($e:expr) => {
116        if let VerifyResult::Fail(e) = $e {
117            return e;
118        }
119    };
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct Context {
124    pub expected: Vec<String>,
125    pub actual: Vec<String>,
126    pub stderr: String,
127
128    is_presentation_error: bool,
129}
130
131impl Context {
132    pub fn new(expected: Vec<String>, actual: Vec<String>, stderr: String) -> Context {
133        Context {
134            expected,
135            actual,
136            stderr,
137            is_presentation_error: false,
138        }
139    }
140
141    pub fn verify(mut self) -> TestResult {
142        self.fix();
143        ensure_pass!(self.verify_num_lines(self.expected.len(), self.actual.len()));
144
145        let zipped = self.expected.iter().zip(self.actual.iter());
146        let mut errors = Vec::new();
147        for (lineno, (expected, actual)) in zipped.enumerate() {
148            errors.extend(self.verify_line(expected, actual, lineno));
149        }
150        if !errors.is_empty() {
151            return TestResult::WrongAnswer(Box::new(WrongAnswer {
152                context: self.clone(),
153                details: errors,
154            }));
155        }
156
157        if self.is_presentation_error {
158            return TestResult::PresentationError;
159        }
160
161        TestResult::Accepted
162    }
163
164    fn fix(&mut self) {
165        if self.expected.ends_with(&["".into()]) {
166            self.expected.pop();
167        }
168
169        if self.actual.ends_with(&["".into()]) {
170            self.actual.pop();
171        } else {
172            self.is_presentation_error = true;
173        }
174    }
175
176    fn verify_num_lines(&self, expected: usize, actual: usize) -> VerifyResult {
177        if expected != actual {
178            return VerifyResult::Fail(TestResult::WrongAnswer(Box::new(WrongAnswer {
179                context: self.clone(),
180                details: vec![WrongAnswerKind::NumOfLineDiffers { expected, actual }],
181            })));
182        }
183
184        VerifyResult::Pass
185    }
186
187    fn verify_line(
188        &self,
189        expected_line: &str,
190        actual_line: &str,
191        lineno: usize,
192    ) -> Vec<WrongAnswerKind> {
193        let expected = Token::parse_line(expected_line, lineno);
194        let actual = Token::parse_line(actual_line, lineno);
195
196        if expected.len() != actual.len() {
197            let expected_span = Span {
198                line: lineno,
199                range: (0, expected_line.len()),
200            };
201
202            let actual_span = Span {
203                line: lineno,
204                range: (0, actual_line.len()),
205            };
206
207            return vec![WrongAnswerKind::NumOfTokenDiffers {
208                expected: expected.len(),
209                actual: actual.len(),
210                expected_span,
211                actual_span,
212            }];
213        }
214
215        let mut errors = vec![];
216        for (expected, actual) in expected.iter().zip(actual.iter()) {
217            if !self.compare_token(expected, actual) {
218                errors.push(WrongAnswerKind::TokenDiffers {
219                    expected: expected.clone(),
220                    actual: actual.clone(),
221                });
222            }
223        }
224
225        errors
226    }
227
228    fn compare_token(&self, a: &Token, b: &Token) -> bool {
229        match (&a.kind, &b.kind) {
230            (TokenKind::String(a), TokenKind::String(b)) => a == b,
231            (TokenKind::Uint(a), TokenKind::Uint(b)) => a == b,
232            (TokenKind::Int(a), TokenKind::Int(b)) => a == b,
233            (TokenKind::Float(a), TokenKind::Float(b)) => (a - b).abs() < EPS,
234            _ => false,
235        }
236    }
237}
238
239fn split_into_lines(s: &str) -> impl Iterator<Item = &str> {
240    s.split('\n').map(|x| x.trim_end_matches('\r'))
241}
242
243#[derive(Debug, Clone, PartialEq)]
244pub struct Token {
245    kind: TokenKind,
246    span: Span,
247}
248
249impl Token {
250    fn new(kind: TokenKind, span: Span) -> Token {
251        Token { kind, span }
252    }
253}
254
255impl fmt::Display for Token {
256    fn fmt(&self, b: &mut fmt::Formatter) -> fmt::Result {
257        match &self.kind {
258            TokenKind::String(v) => write!(b, "{}", v),
259            TokenKind::Uint(v) => write!(b, "{}", v),
260            TokenKind::Int(v) => write!(b, "{}", v),
261            TokenKind::Float(v) => write!(b, "{}", v),
262        }
263    }
264}
265
266#[derive(Debug, Clone, PartialEq)]
267pub enum TokenKind {
268    String(String),
269    Uint(u64),
270    Int(i64),
271    Float(f64),
272}
273
274impl Token {
275    fn parse_line(line: &str, lineno: usize) -> Vec<Token> {
276        // if it contains two successive whitespace or starts with or ends with whitespace, treat
277        // entire line as string literal.
278        if line.contains("  ") || line.starts_with(' ') || line.ends_with(' ') {
279            return vec![Token::new(
280                TokenKind::String(line.into()),
281                Span {
282                    line: lineno,
283                    range: (0, line.len()),
284                },
285            )];
286        }
287
288        // otherwise, split tokens and parse each token
289        let mut iter = line.char_indices().peekable();
290
291        iter::from_fn(|| match iter.peek() {
292            None => None,
293            Some(_) => Some(
294                iter.by_ref()
295                    .skip_while(|(_, ch)| ch.is_whitespace())
296                    .take_while(|(_, ch)| !ch.is_whitespace())
297                    .fold(
298                        (usize::MAX, usize::MIN, "".to_string()),
299                        |(start, end, mut current), (columnno, ch)| {
300                            current.push(ch);
301                            (
302                                cmp::min(start, columnno),
303                                cmp::max(end, columnno + 1),
304                                current,
305                            )
306                        },
307                    ),
308            ),
309        })
310        .map(|(startno, endno, token)| {
311            Token::parse(
312                &token,
313                Span {
314                    line: lineno,
315                    range: (startno, endno),
316                },
317            )
318        })
319        .collect()
320    }
321
322    fn parse(token: &str, span: Span) -> Token {
323        // A token starting with zero is rarely intended to be a number so treat it as a stirng.
324        // If it were not handled specially, the value would be parsed as an integer.
325        if token != "0"
326            && token != "-0"
327            && !token.starts_with("0.")
328            && !token.starts_with("-0.")
329            && (token.starts_with('0') || token.starts_with("-0"))
330        {
331            return Token::new(TokenKind::String(token.into()), span);
332        }
333
334        if let Ok(uint) = token.parse() {
335            return Token::new(TokenKind::Uint(uint), span);
336        }
337
338        if let Ok(int) = token.parse() {
339            return Token::new(TokenKind::Int(int), span);
340        }
341
342        if let Ok(float) = token.parse() {
343            return Token::new(TokenKind::Float(float), span);
344        }
345
346        Token::new(TokenKind::String(token.into()), span)
347    }
348}
349
350pub fn format(result: &TestResult) -> String {
351    match result {
352        TestResult::Accepted => "Accepted.".into(),
353        TestResult::PresentationError => "Presentation error.".into(),
354        TestResult::WrongAnswer(wa) => format!(
355            "Wrong Answer.\n\n# expected stdout\n\n{}\n# actual stdout\n\n{}\n# errors\n\n{}\n",
356            joinl(&wa.context.expected),
357            joinl(&wa.context.actual),
358            format_wa(wa)
359        ),
360        TestResult::RuntimeError(re) => format!("Runtime Error: {}", format_re(re)),
361    }
362}
363
364fn format_wa(wa: &WrongAnswer) -> String {
365    let mut messages = Vec::new();
366    let mut expected_spans = Vec::new();
367    let mut actual_spans = Vec::new();
368    for detail in &wa.details {
369        let detail = match detail {
370            WrongAnswerKind::NumOfLineDiffers { expected, actual } => format!(
371                "The number of lines is different. expected: {}, actual: {}",
372                expected, actual
373            ),
374
375            WrongAnswerKind::NumOfTokenDiffers {
376                expected,
377                actual,
378                expected_span,
379                actual_span,
380            } => {
381                assert_eq!(expected_span.line, actual_span.line);
382                expected_spans.push(*expected_span);
383                actual_spans.push(*actual_span);
384                format!(
385                    "At line {}: the number of tokens is different. expected: {}, actual: {}",
386                    expected_span.line + 1,
387                    expected,
388                    actual
389                )
390            }
391
392            WrongAnswerKind::TokenDiffers { expected, actual } => {
393                assert_eq!(expected.span.line, actual.span.line);
394                expected_spans.push(expected.span);
395                actual_spans.push(actual.span);
396                format!(
397                    "At line {}: Token differs. expected: {}, actual: {}",
398                    expected.span.line + 1,
399                    expected,
400                    actual,
401                )
402            }
403        };
404        messages.push(format!("- {}", detail))
405    }
406
407    let messages = joinl(&messages);
408    let diff = format_diff(
409        &wa.context.expected,
410        &wa.context.actual,
411        &expected_spans,
412        &actual_spans,
413    );
414
415    format!("{}\n{}", messages, diff)
416}
417
418fn joinl(ss: &[String]) -> String {
419    let mut res = String::new();
420    for s in ss {
421        res.push_str(s);
422        res.push('\n');
423    }
424    res
425}
426
427fn format_diff(
428    expected: &[String],
429    actual: &[String],
430    expected_spans: &[Span],
431    actual_spans: &[Span],
432) -> String {
433    fn fallback(_expected: &[String], _actual: &[String]) -> String {
434        "WARNING: Cannot determine a terminal size or too narrow terminal.  Cannot use diff view."
435            .into()
436    }
437
438    let lineno_delim = " | ";
439    let max_lineno = cmp::max(expected.len(), actual.len());
440    let lineno_width = max_lineno.to_string().len();
441    let division_delim = " | ";
442    let decoration_width = lineno_width + lineno_delim.len() + division_delim.len();
443
444    use terminal_size::*;
445    let width = match terminal_size() {
446        Some((Width(w), _)) => w as usize,
447        None => return fallback(expected, actual),
448    };
449
450    if decoration_width >= width {
451        return fallback(expected, actual);
452    }
453    let half = (width - decoration_width) / 2;
454
455    use splitv::Pane;
456    let expected_len: Vec<_> = expected.iter().map(|x| x.len()).collect();
457    let actual_len: Vec<_> = actual.iter().map(|x| x.len()).collect();
458
459    let expected_len_max = expected_len.iter().max().copied().unwrap_or(0);
460    let actual_len_max = actual_len.iter().max().copied().unwrap_or(0);
461
462    let expected_pane_width = cmp::min(half, cmp::max(expected_len_max, PANE_MINIMUM_SIZE));
463    let actual_pane_width = cmp::min(half, cmp::max(actual_len_max, PANE_MINIMUM_SIZE));
464
465    let body = {
466        let linenos: Vec<_> = (1..=max_lineno).map(|x| x.to_string()).collect();
467        let lineno_pane = Pane {
468            lines: &linenos.iter().map(|x| x.as_str()).collect::<Vec<_>>(),
469            width: lineno_width,
470        };
471
472        let expected_pane = Pane {
473            lines: &expected.iter().map(|x| x.as_str()).collect::<Vec<_>>(),
474            width: expected_pane_width,
475        };
476        let actual_pane = Pane {
477            lines: &actual.iter().map(|x| x.as_str()).collect::<Vec<_>>(),
478            width: actual_pane_width,
479        };
480
481        splitv::splitv(
482            &[lineno_pane, expected_pane, actual_pane],
483            &[lineno_delim, division_delim],
484        )
485    };
486
487    let span = {
488        let organize_spans = |spans: &[Span]| -> Vec<Vec<(usize, usize)>> {
489            let mut organized = vec![Vec::new(); max_lineno];
490
491            for span in spans {
492                organized[span.line].push((span.start(), span.end()));
493            }
494
495            organized
496        };
497
498        let convert_span = |line_width: usize, spans: Vec<(usize, usize)>| -> String {
499            let mut line = " ".repeat(line_width);
500            for (start, end) in spans {
501                line.replace_range(start..end, &"^".repeat(end - start));
502            }
503            line
504        };
505
506        let lineno_pane = Pane {
507            lines: &vec![" "; max_lineno],
508            width: lineno_width,
509        };
510
511        let expected_lines = organize_spans(expected_spans)
512            .into_iter()
513            .enumerate()
514            .map(|(i, spans)| convert_span(expected_len.get(i).copied().unwrap_or(0), spans))
515            .collect::<Vec<_>>();
516        let expected_pane = Pane {
517            lines: &expected_lines
518                .iter()
519                .map(|x| x.as_str())
520                .collect::<Vec<_>>(),
521            width: expected_pane_width,
522        };
523
524        let actual_lines = organize_spans(actual_spans)
525            .into_iter()
526            .enumerate()
527            .map(|(i, spans)| convert_span(actual_len.get(i).copied().unwrap_or(0), spans))
528            .collect::<Vec<_>>();
529        let actual_pane = Pane {
530            lines: &actual_lines.iter().map(|x| x.as_str()).collect::<Vec<_>>(),
531            width: actual_pane_width,
532        };
533
534        splitv::splitv(
535            &[lineno_pane, expected_pane, actual_pane],
536            &[lineno_delim, division_delim],
537        )
538    };
539
540    use itertools::Itertools;
541    joinl(
542        &body
543            .into_iter()
544            .interleave(span.into_iter())
545            .collect::<Vec<_>>(),
546    )
547}
548
549fn format_re(re: &RuntimeErrorKind) -> String {
550    match re {
551        RuntimeErrorKind::CommandFailed => "The process did not exit successfully.".into(),
552        RuntimeErrorKind::InvalidUtf8 => "Process outputs invalid UTF-8.".into(),
553    }
554}
555
556#[cfg(test)]
557mod test {
558    use super::*;
559
560    #[allow(non_snake_case)]
561    fn S(s: &str) -> String {
562        String::from(s)
563    }
564
565    #[test]
566    fn accepted_simple() {
567        assert_eq!(
568            TestResult::Accepted,
569            Context::new(vec![S("1"), S("")], vec![S("1"), S("")], S("")).verify()
570        );
571
572        assert_eq!(
573            TestResult::Accepted,
574            Context::new(vec![S("1 "), S("")], vec![S("1 "), S("")], S("")).verify()
575        );
576
577        assert_eq!(
578            TestResult::Accepted,
579            Context::new(vec![S("1 2 3"), S("")], vec![S("1 2 3"), S("")], S("")).verify()
580        );
581
582        assert_eq!(
583            TestResult::Accepted,
584            Context::new(vec![S("011121"), S("")], vec![S("011121"), S("")], S("")).verify()
585        );
586
587        assert_eq!(
588            TestResult::Accepted,
589            Context::new(
590                vec![S("12312131231231235534254351121"), S("")],
591                vec![S("12312131231231235534254351121"), S("")],
592                S("")
593            )
594            .verify()
595        );
596
597        assert_eq!(
598            TestResult::Accepted,
599            Context::new(
600                vec![S("1"), S("2 3"), S("4 5"), S("")],
601                vec![S("1"), S("2 3"), S("4 5"), S("")],
602                S("")
603            )
604            .verify()
605        );
606    }
607
608    #[test]
609    fn accepted_float() {
610        assert_eq!(
611            TestResult::Accepted,
612            Context::new(
613                vec![S("0.123123123123123"), S("")],
614                vec![S("0.123123123123129"), S("")],
615                S("")
616            )
617            .verify()
618        );
619    }
620
621    #[test]
622    fn presentation_error() {
623        assert_eq! {
624            TestResult::PresentationError,
625            Context::new(vec![], vec![], S("")).verify()
626        }
627    }
628
629    fn fixed(ctx: &Context) -> Context {
630        let mut ctx = ctx.clone();
631        ctx.fix();
632        ctx
633    }
634
635    #[test]
636    fn wrong_answer_num_of_line_differs() {
637        let ctx = Context::new(vec![S("1"), S("2"), S("")], vec![S("2"), S("")], S(""));
638        assert_eq!(
639            TestResult::WrongAnswer(Box::new(WrongAnswer {
640                context: fixed(&ctx),
641                details: vec![WrongAnswerKind::NumOfLineDiffers {
642                    expected: 2,
643                    actual: 1,
644                }]
645            })),
646            ctx.verify(),
647        );
648    }
649
650    #[test]
651    fn wrong_answer_num_of_token_differs() {
652        let ctx = Context::new(vec![S("1 2"), S("")], vec![S("2"), S("")], S(""));
653        assert_eq!(
654            TestResult::WrongAnswer(Box::new(WrongAnswer {
655                context: fixed(&ctx),
656                details: vec![WrongAnswerKind::NumOfTokenDiffers {
657                    expected: 2,
658                    actual: 1,
659                    expected_span: Span {
660                        line: 0,
661                        range: (0, 3)
662                    },
663                    actual_span: Span {
664                        line: 0,
665                        range: (0, 1)
666                    },
667                }]
668            })),
669            ctx.verify(),
670        );
671
672        let ctx = Context::new(vec![S("1 2"), S("")], vec![S("2 3 4"), S("")], S(""));
673        assert_eq!(
674            TestResult::WrongAnswer(Box::new(WrongAnswer {
675                context: fixed(&ctx),
676                details: vec![WrongAnswerKind::NumOfTokenDiffers {
677                    expected: 2,
678                    actual: 3,
679                    expected_span: Span {
680                        line: 0,
681                        range: (0, 3)
682                    },
683                    actual_span: Span {
684                        line: 0,
685                        range: (0, 5)
686                    },
687                }]
688            })),
689            ctx.verify(),
690        );
691
692        let ctx = Context::new(vec![S("1 2"), S("")], vec![S("1  2"), S("")], S(""));
693        assert_eq!(
694            TestResult::WrongAnswer(Box::new(WrongAnswer {
695                context: fixed(&ctx),
696                details: vec![WrongAnswerKind::NumOfTokenDiffers {
697                    expected: 2,
698                    actual: 1,
699                    expected_span: Span {
700                        line: 0,
701                        range: (0, 3)
702                    },
703                    actual_span: Span {
704                        line: 0,
705                        range: (0, 4)
706                    },
707                }]
708            })),
709            ctx.verify(),
710        );
711    }
712
713    #[test]
714    fn wrong_answer_token_differs() {
715        let ctx = Context::new(vec![S("1"), S("")], vec![S("2"), S("")], S(""));
716        assert_eq!(
717            TestResult::WrongAnswer(Box::new(WrongAnswer {
718                context: fixed(&ctx),
719                details: vec![WrongAnswerKind::TokenDiffers {
720                    expected: Token::new(
721                        TokenKind::Uint(1),
722                        Span {
723                            line: 0,
724                            range: (0, 1)
725                        }
726                    ),
727                    actual: Token::new(
728                        TokenKind::Uint(2),
729                        Span {
730                            line: 0,
731                            range: (0, 1)
732                        }
733                    )
734                }]
735            })),
736            ctx.verify(),
737        );
738
739        let ctx = Context::new(vec![S("00"), S("")], vec![S("0"), S("")], S(""));
740        assert_eq!(
741            TestResult::WrongAnswer(Box::new(WrongAnswer {
742                context: fixed(&ctx),
743                details: vec![WrongAnswerKind::TokenDiffers {
744                    expected: Token::new(
745                        TokenKind::String(S("00")),
746                        Span {
747                            line: 0,
748                            range: (0, 2)
749                        }
750                    ),
751                    actual: Token::new(
752                        TokenKind::Uint(0),
753                        Span {
754                            line: 0,
755                            range: (0, 1)
756                        }
757                    )
758                }]
759            })),
760            ctx.verify(),
761        );
762
763        let ctx = Context::new(vec![S("1.0003"), S("")], vec![S("1.0002"), S("")], S(""));
764        assert_eq!(
765            TestResult::WrongAnswer(Box::new(WrongAnswer {
766                context: fixed(&ctx),
767                details: vec![WrongAnswerKind::TokenDiffers {
768                    expected: Token::new(
769                        TokenKind::Float(1.0003),
770                        Span {
771                            line: 0,
772                            range: (0, 6)
773                        }
774                    ),
775                    actual: Token::new(
776                        TokenKind::Float(1.0002),
777                        Span {
778                            line: 0,
779                            range: (0, 6)
780                        }
781                    )
782                }]
783            })),
784            ctx.verify(),
785        );
786
787        let ctx = Context::new(vec![S("-0.0003"), S("")], vec![S("-0.0002"), S("")], S(""));
788        assert_eq!(
789            TestResult::WrongAnswer(Box::new(WrongAnswer {
790                context: fixed(&ctx),
791                details: vec![WrongAnswerKind::TokenDiffers {
792                    expected: Token::new(
793                        TokenKind::Float(-0.0003),
794                        Span {
795                            line: 0,
796                            range: (0, 7)
797                        }
798                    ),
799                    actual: Token::new(
800                        TokenKind::Float(-0.0002),
801                        Span {
802                            line: 0,
803                            range: (0, 7)
804                        }
805                    )
806                }]
807            })),
808            ctx.verify(),
809        );
810
811        let ctx = Context::new(vec![S("-1"), S("")], vec![S("-01"), S("")], S(""));
812        assert_eq!(
813            TestResult::WrongAnswer(Box::new(WrongAnswer {
814                context: fixed(&ctx),
815                details: vec![WrongAnswerKind::TokenDiffers {
816                    expected: Token::new(
817                        TokenKind::Int(-1),
818                        Span {
819                            line: 0,
820                            range: (0, 2)
821                        }
822                    ),
823                    actual: Token::new(
824                        TokenKind::String(S("-01")),
825                        Span {
826                            line: 0,
827                            range: (0, 3)
828                        }
829                    )
830                }]
831            })),
832            ctx.verify(),
833        );
834
835        let ctx = Context::new(vec![S("3 2 -1"), S("")], vec![S("3 2 -01"), S("")], S(""));
836        assert_eq!(
837            TestResult::WrongAnswer(Box::new(WrongAnswer {
838                context: fixed(&ctx),
839                details: vec![WrongAnswerKind::TokenDiffers {
840                    expected: Token::new(
841                        TokenKind::Int(-1),
842                        Span {
843                            line: 0,
844                            range: (4, 6)
845                        }
846                    ),
847                    actual: Token::new(
848                        TokenKind::String(S("-01")),
849                        Span {
850                            line: 0,
851                            range: (4, 7)
852                        }
853                    )
854                }]
855            })),
856            ctx.verify(),
857        );
858
859        let ctx = Context::new(vec![S("1 2 -1"), S("")], vec![S("4 3 -01"), S("")], S(""));
860        assert_eq!(
861            TestResult::WrongAnswer(Box::new(WrongAnswer {
862                context: fixed(&ctx),
863                details: vec![
864                    WrongAnswerKind::TokenDiffers {
865                        expected: Token::new(
866                            TokenKind::Uint(1),
867                            Span {
868                                line: 0,
869                                range: (0, 1)
870                            }
871                        ),
872                        actual: Token::new(
873                            TokenKind::Uint(4),
874                            Span {
875                                line: 0,
876                                range: (0, 1)
877                            }
878                        )
879                    },
880                    WrongAnswerKind::TokenDiffers {
881                        expected: Token::new(
882                            TokenKind::Uint(2),
883                            Span {
884                                line: 0,
885                                range: (2, 3)
886                            }
887                        ),
888                        actual: Token::new(
889                            TokenKind::Uint(3),
890                            Span {
891                                line: 0,
892                                range: (2, 3)
893                            }
894                        )
895                    },
896                    WrongAnswerKind::TokenDiffers {
897                        expected: Token::new(
898                            TokenKind::Int(-1),
899                            Span {
900                                line: 0,
901                                range: (4, 6)
902                            }
903                        ),
904                        actual: Token::new(
905                            TokenKind::String(S("-01")),
906                            Span {
907                                line: 0,
908                                range: (4, 7)
909                            }
910                        )
911                    }
912                ]
913            })),
914            ctx.verify(),
915        );
916    }
917
918    #[test]
919    fn formatting() {
920        let ctx = Context::new(
921            vec![S("1 2 -1"), S("2   3"), S("asdf jkl fsd"), S("")],
922            vec![S("4 3 -01"), S("2 3"), S("asdf jkl fsh"), S("")],
923            S(""),
924        );
925        println!("{}", format(&ctx.verify()));
926    }
927}