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