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 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 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 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}