on 448bfaf help sign in
bat/tests/integration_tests.rs
1437 lines 37 kB history raw
1use assert_cmd::cargo::CommandCargoExt;
2use predicates::boolean::PredicateBooleanExt;
3use predicates::{prelude::predicate, str::PredicateStrExt};
4use serial_test::serial;
5use std::path::Path;
6use std::process::Command;
7use std::str::from_utf8;
8use tempfile::tempdir;
10#[cfg(unix)]
11mod unix {
12 pub use std::fs::File;
13 pub use std::io::{self, Write};
14 pub use std::os::unix::io::FromRawFd;
15 pub use std::path::PathBuf;
16 pub use std::process::Stdio;
17 pub use std::thread;
18 pub use std::time::Duration;
20 pub use assert_cmd::assert::OutputAssertExt;
21 pub use nix::pty::{openpty, OpenptyResult};
22 pub use wait_timeout::ChildExt;
24 pub const SAFE_CHILD_PROCESS_CREATION_TIME: Duration = Duration::from_millis(100);
25 pub const CHILD_WAIT_TIMEOUT: Duration = Duration::from_secs(15);
27#[cfg(unix)]
28use unix::*;
30mod utils;
31use utils::mocked_pagers;
33const EXAMPLES_DIR: &str = "tests/examples";
35fn bat_raw_command_with_config() -> Command {
36 let mut cmd = Command::cargo_bin("bat").unwrap();
37 cmd.current_dir("tests/examples");
38 cmd.env_remove("BAT_CACHE_PATH");
39 cmd.env_remove("BAT_CONFIG_DIR");
40 cmd.env_remove("BAT_CONFIG_PATH");
41 cmd.env_remove("BAT_OPTS");
42 cmd.env_remove("BAT_PAGER");
43 cmd.env_remove("BAT_STYLE");
44 cmd.env_remove("BAT_TABS");
45 cmd.env_remove("BAT_THEME");
46 cmd.env_remove("COLORTERM");
47 cmd.env_remove("NO_COLOR");
48 cmd.env_remove("PAGER");
49 cmd
52fn bat_raw_command() -> Command {
53 let mut cmd = bat_raw_command_with_config();
54 cmd.arg("--no-config");
55 cmd
58fn bat_with_config() -> assert_cmd::Command {
59 assert_cmd::Command::from_std(bat_raw_command_with_config())
62fn bat() -> assert_cmd::Command {
63 assert_cmd::Command::from_std(bat_raw_command())
66#[test]
67fn basic() {
68 bat()
69 .arg("test.txt")
70 .assert()
71 .success()
72 .stdout("hello world\n")
73 .stderr("");
76#[test]
77fn stdin() {
78 bat()
79 .write_stdin("foo\nbar\n")
80 .assert()
81 .success()
82 .stdout("foo\nbar\n");
85#[test]
86fn concatenate() {
87 bat()
88 .arg("test.txt")
89 .arg("test.txt")
90 .assert()
91 .success()
92 .stdout("hello world\nhello world\n");
95#[test]
96fn concatenate_stdin() {
97 bat()
98 .arg("test.txt")
99 .arg("-")
100 .arg("test.txt")
101 .write_stdin("stdin\n")
102 .assert()
103 .success()
104 .stdout("hello world\nstdin\nhello world\n");
107#[test]
108fn concatenate_empty_first() {
109 bat()
110 .arg("empty.txt")
111 .arg("test.txt")
112 .assert()
113 .success()
114 .stdout("hello world\n");
117#[test]
118fn concatenate_empty_last() {
119 bat()
120 .arg("test.txt")
121 .arg("empty.txt")
122 .assert()
123 .success()
124 .stdout("hello world\n");
127#[test]
128fn concatenate_empty_both() {
129 bat()
130 .arg("empty.txt")
131 .arg("empty.txt")
132 .assert()
133 .success()
134 .stdout("");
137#[test]
138fn concatenate_empty_between() {
139 bat()
140 .arg("test.txt")
141 .arg("empty.txt")
142 .arg("test.txt")
143 .assert()
144 .success()
145 .stdout("hello world\nhello world\n");
148#[test]
149fn concatenate_empty_first_and_last() {
150 bat()
151 .arg("empty.txt")
152 .arg("test.txt")
153 .arg("empty.txt")
154 .assert()
155 .success()
156 .stdout("hello world\n");
159#[test]
160fn concatenate_single_line() {
161 bat()
162 .arg("single-line.txt")
163 .arg("single-line.txt")
164 .assert()
165 .success()
166 .stdout("Single LineSingle Line");
169#[test]
170fn concatenate_single_line_empty() {
171 bat()
172 .arg("single-line.txt")
173 .arg("empty.txt")
174 .arg("single-line.txt")
175 .assert()
176 .success()
177 .stdout("Single LineSingle Line");
180#[test]
181fn line_numbers() {
182 bat()
183 .arg("multiline.txt")
184 .arg("--style=numbers")
185 .arg("--decorations=always")
186 .assert()
187 .success()
188 .stdout(" 1 line 1\n 2 line 2\n 3 line 3\n 4 line 4\n");
191#[test]
192fn line_range_2_3() {
193 bat()
194 .arg("multiline.txt")
195 .arg("--line-range=2:3")
196 .assert()
197 .success()
198 .stdout("line 2\nline 3\n");
201#[test]
202fn line_range_first_two() {
203 bat()
204 .arg("multiline.txt")
205 .arg("--line-range=:2")
206 .assert()
207 .success()
208 .stdout("line 1\nline 2\n");
211#[test]
212fn line_range_last_3() {
213 bat()
214 .arg("multiline.txt")
215 .arg("--line-range=2:")
216 .assert()
217 .success()
218 .stdout("line 2\nline 3\nline 4\n");
221#[test]
222fn line_range_multiple() {
223 bat()
224 .arg("multiline.txt")
225 .arg("--line-range=1:2")
226 .arg("--line-range=4:4")
227 .assert()
228 .success()
229 .stdout("line 1\nline 2\nline 4\n");
232#[cfg(unix)]
233fn setup_temp_file(content: &[u8]) -> io::Result<(PathBuf, tempfile::TempDir)> {
234 let dir = tempfile::tempdir().expect("Couldn't create tempdir");
235 let path = dir.path().join("temp_file");
236 File::create(&path)?.write_all(content)?;
237 Ok((path, dir))
240#[cfg(unix)]
241#[test]
242fn basic_io_cycle() -> io::Result<()> {
243 let (filename, dir) = setup_temp_file(b"I am not empty")?;
245 let file_out = Stdio::from(File::create(&filename)?);
246 let res = bat_raw_command()
247 .arg("test.txt")
248 .arg(&filename)
249 .stdout(file_out)
250 .assert();
251 drop(dir);
252 res.failure();
253 Ok(())
256#[cfg(unix)]
257#[test]
258fn first_file_cyclic_is_ok() -> io::Result<()> {
259 let (filename, dir) = setup_temp_file(b"I am not empty")?;
261 let file_out = Stdio::from(File::create(&filename)?);
262 let res = bat_raw_command()
263 .arg(&filename)
264 .arg("test.txt")
265 .stdout(file_out)
266 .assert();
267 drop(dir);
268 res.success();
269 Ok(())
272#[cfg(unix)]
273#[test]
274fn empty_file_cycle_is_ok() -> io::Result<()> {
275 let (filename, dir) = setup_temp_file(b"I am not empty")?;
277 let file_out = Stdio::from(File::create(&filename)?);
278 let res = bat_raw_command()
279 .arg("empty.txt")
280 .arg(&filename)
281 .stdout(file_out)
282 .assert();
283 drop(dir);
284 res.success();
285 Ok(())
288#[cfg(unix)]
289#[test]
290fn stdin_to_stdout_cycle() -> io::Result<()> {
291 let (filename, dir) = setup_temp_file(b"I am not empty")?;
292 let file_in = Stdio::from(File::open(&filename)?);
293 let file_out = Stdio::from(File::create(&filename)?);
294 let res = bat_raw_command()
295 .arg("test.txt")
296 .arg("-")
297 .stdin(file_in)
298 .stdout(file_out)
299 .assert();
300 drop(dir);
301 res.failure();
302 Ok(())
305#[cfg(unix)]
306#[test]
307fn no_args_doesnt_break() {
308 // To simulate bat getting started from the shell, a process is created with stdin and stdout
309 // as the slave end of a pseudo terminal. Although both point to the same "file", bat should
310 // not exit, because in this case it is safe to read and write to the same fd, which is why
311 // this test exists.
312 let OpenptyResult { master, slave } = openpty(None, None).expect("Couldn't open pty.");
313 let mut master = unsafe { File::from_raw_fd(master) };
314 let stdin = unsafe { Stdio::from_raw_fd(slave) };
315 let stdout = unsafe { Stdio::from_raw_fd(slave) };
317 let mut child = bat_raw_command()
318 .stdin(stdin)
319 .stdout(stdout)
320 .spawn()
321 .expect("Failed to start.");
323 // Some time for the child process to start and to make sure, that we can poll the exit status.
324 // Although this waiting period is not necessary, it is best to keep it in and be absolutely
325 // sure, that the try_wait does not error later.
326 thread::sleep(SAFE_CHILD_PROCESS_CREATION_TIME);
328 // The child process should be running and waiting for input,
329 // therefore no exit status should be available.
330 let exit_status = child
331 .try_wait()
332 .expect("Error polling exit status, this should never happen.");
333 assert!(exit_status.is_none());
335 // Write Ctrl-D (end of transmission) to the pty.
336 master
337 .write_all(&[0x04])
338 .expect("Couldn't write EOT character to master end.");
340 let exit_status = child
341 .wait_timeout(CHILD_WAIT_TIMEOUT)
342 .expect("Error polling exit status, this should never happen.")
343 .expect("Exit status not set, but the child should have exited already.");
345 assert!(exit_status.success());
348#[test]
349fn tabs_numbers() {
350 bat()
351 .arg("tabs.txt")
352 .arg("--tabs=4")
353 .arg("--style=numbers")
354 .arg("--decorations=always")
355 .assert()
356 .success()
357 .stdout(
358 " 1 1 2 3 4
359 2 1 ?
360 3 22 ?
361 4 333 ?
362 5 4444 ?
363 6 55555 ?
364 7 666666 ?
365 8 7777777 ?
366 9 88888888 ?
367",
368 );
371#[test]
372fn tabs_passthrough_wrapped() {
373 bat()
374 .arg("tabs.txt")
375 .arg("--tabs=0")
376 .arg("--style=plain")
377 .arg("--decorations=always")
378 .assert()
379 .success()
380 .stdout(
381 " 1 2 3 4
3821 ?
38322 ?
384333 ?
3854444 ?
38655555 ?
387666666 ?
3887777777 ?
38988888888 ?
390",
391 );
394#[test]
395fn tabs_4_wrapped() {
396 bat()
397 .arg("tabs.txt")
398 .arg("--tabs=4")
399 .arg("--style=plain")
400 .arg("--decorations=always")
401 .assert()
402 .success()
403 .stdout(
404 " 1 2 3 4
4051 ?
40622 ?
407333 ?
4084444 ?
40955555 ?
410666666 ?
4117777777 ?
41288888888 ?
413",
414 );
417#[test]
418fn tabs_8_wrapped() {
419 bat()
420 .arg("tabs.txt")
421 .arg("--tabs=8")
422 .arg("--style=plain")
423 .arg("--decorations=always")
424 .assert()
425 .success()
426 .stdout(
427 " 1 2 3 4
4281 ?
42922 ?
430333 ?
4314444 ?
43255555 ?
433666666 ?
4347777777 ?
43588888888 ?
436",
437 );
440#[test]
441fn tabs_passthrough() {
442 bat()
443 .arg("tabs.txt")
444 .arg("--tabs=0")
445 .arg("--style=plain")
446 .arg("--decorations=always")
447 .assert()
448 .success()
449 .stdout(
450 " 1 2 3 4
4511 ?
45222 ?
453333 ?
4544444 ?
45555555 ?
456666666 ?
4577777777 ?
45888888888 ?
459",
460 );
463#[test]
464fn tabs_4() {
465 bat()
466 .arg("tabs.txt")
467 .arg("--tabs=4")
468 .arg("--style=plain")
469 .arg("--decorations=always")
470 .assert()
471 .success()
472 .stdout(
473 " 1 2 3 4
4741 ?
47522 ?
476333 ?
4774444 ?
47855555 ?
479666666 ?
4807777777 ?
48188888888 ?
482",
483 );
486#[test]
487fn tabs_8() {
488 bat()
489 .arg("tabs.txt")
490 .arg("--tabs=8")
491 .arg("--style=plain")
492 .arg("--decorations=always")
493 .assert()
494 .success()
495 .stdout(
496 " 1 2 3 4
4971 ?
49822 ?
499333 ?
5004444 ?
50155555 ?
502666666 ?
5037777777 ?
50488888888 ?
505",
506 );
509#[test]
510fn fail_non_existing() {
511 bat().arg("non-existing-file").assert().failure();
514#[test]
515fn fail_directory() {
516 bat().arg("sub_directory").assert().failure();
519#[test]
520fn do_not_exit_directory() {
521 bat()
522 .arg("sub_directory")
523 .arg("test.txt")
524 .assert()
525 .stdout("hello world\n")
526 .failure();
529#[test]
530fn pager_basic() {
531 bat()
532 .env("PAGER", "echo pager-output")
533 .arg("--paging=always")
534 .arg("test.txt")
535 .assert()
536 .success()
537 .stdout(predicate::eq("pager-output\n").normalize());
540#[test]
541fn pager_overwrite() {
542 bat()
543 .env("PAGER", "echo other-pager")
544 .env("BAT_PAGER", "echo pager-output")
545 .arg("--paging=always")
546 .arg("test.txt")
547 .assert()
548 .success()
549 .stdout(predicate::eq("pager-output\n").normalize());
552#[test]
553fn pager_disable() {
554 bat()
555 .env("PAGER", "echo other-pager")
556 .env("BAT_PAGER", "")
557 .arg("--paging=always")
558 .arg("test.txt")
559 .assert()
560 .success()
561 .stdout(predicate::eq("hello world\n").normalize());
564#[test]
565fn env_var_pager_value_bat() {
566 bat()
567 .env("PAGER", "bat")
568 .arg("--paging=always")
569 .arg("test.txt")
570 .assert()
571 .success()
572 .stdout(predicate::eq("hello world\n").normalize());
575#[test]
576fn env_var_bat_pager_value_bat() {
577 bat()
578 .env("BAT_PAGER", "bat")
579 .arg("--paging=always")
580 .arg("test.txt")
581 .assert()
582 .failure()
583 .stderr(predicate::str::contains("bat as a pager is disallowed"));
586#[test]
587fn pager_value_bat() {
588 bat()
589 .arg("--pager=bat")
590 .arg("--paging=always")
591 .arg("test.txt")
592 .assert()
593 .failure()
594 .stderr(predicate::str::contains("bat as a pager is disallowed"));
597/// We shall use less instead of most if PAGER is used since PAGER
598/// is a generic env var
599#[test]
600#[serial] // Because of PATH
601fn pager_most_from_pager_env_var() {
602 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
603 // If the output is not "I am most" then we know 'most' is not used
604 bat()
605 .env("PAGER", mocked_pagers::from("most"))
606 .arg("--paging=always")
607 .arg("test.txt")
608 .assert()
609 .success()
610 .stdout(predicate::eq("hello world\n").normalize());
611 });
614/// If the bat-specific BAT_PAGER is used, obey the wish of the user
615/// and allow 'most'
616#[test]
617#[serial] // Because of PATH
618fn pager_most_from_bat_pager_env_var() {
619 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
620 bat()
621 .env("BAT_PAGER", mocked_pagers::from("most"))
622 .arg("--paging=always")
623 .arg("test.txt")
624 .assert()
625 .success()
626 .stdout(predicate::str::contains("I am most"));
627 });
630/// Same reasoning with --pager as with BAT_PAGER
631#[test]
632#[serial] // Because of PATH
633fn pager_most_from_pager_arg() {
634 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
635 bat()
636 .arg("--paging=always")
637 .arg(format!("--pager={}", mocked_pagers::from("most")))
638 .arg("test.txt")
639 .assert()
640 .success()
641 .stdout(predicate::str::contains("I am most"));
642 });
645/// Make sure the logic for 'most' applies even if an argument is passed
646#[test]
647#[serial] // Because of PATH
648fn pager_most_with_arg() {
649 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
650 bat()
651 .env("PAGER", format!("{} -w", mocked_pagers::from("most")))
652 .arg("--paging=always")
653 .arg("test.txt")
654 .assert()
655 .success()
656 .stdout(predicate::eq("hello world\n").normalize());
657 });
660/// Sanity check that 'more' is treated like 'most'
661#[test]
662#[serial] // Because of PATH
663fn pager_more() {
664 mocked_pagers::with_mocked_versions_of_more_and_most_in_path(|| {
665 bat()
666 .env("PAGER", mocked_pagers::from("more"))
667 .arg("--paging=always")
668 .arg("test.txt")
669 .assert()
670 .success()
671 .stdout(predicate::eq("hello world\n").normalize());
672 });
675#[test]
676fn alias_pager_disable() {
677 bat()
678 .env("PAGER", "echo other-pager")
679 .arg("-P")
680 .arg("test.txt")
681 .assert()
682 .success()
683 .stdout(predicate::eq("hello world\n").normalize());
686#[test]
687fn alias_pager_disable_long_overrides_short() {
688 bat()
689 .env("PAGER", "echo pager-output")
690 .arg("-P")
691 .arg("--paging=always")
692 .arg("test.txt")
693 .assert()
694 .success()
695 .stdout(predicate::eq("pager-output\n").normalize());
698#[test]
699fn pager_failed_to_parse() {
700 bat()
701 .env("BAT_PAGER", "mismatched-quotes 'a")
702 .arg("--paging=always")
703 .arg("test.txt")
704 .assert()
705 .failure()
706 .stderr(predicate::str::contains("Could not parse pager command"));
709#[test]
710fn diagnostic_sanity_check() {
711 bat()
712 .arg("--diagnostic")
713 .assert()
714 .success()
715 .stdout(predicate::str::contains("BAT_PAGER="))
716 .stderr("");
719#[test]
720fn config_location_test() {
721 bat_with_config()
722 .env("BAT_CONFIG_PATH", "bat.conf")
723 .arg("--config-file")
724 .assert()
725 .success()
726 .stdout("bat.conf\n");
728 bat_with_config()
729 .env("BAT_CONFIG_PATH", "not-existing.conf")
730 .arg("--config-file")
731 .assert()
732 .success()
733 .stdout("not-existing.conf\n");
736#[test]
737fn config_location_when_generating() {
738 let tmp_dir = tempdir().expect("can create temporary directory");
739 let tmp_config_path = tmp_dir.path().join("should-be-created.conf");
741 // Create the file with bat
742 bat_with_config()
743 .env("BAT_CONFIG_PATH", tmp_config_path.to_str().unwrap())
744 .arg("--generate-config-file")
745 .assert()
746 .success()
747 .stdout(
748 predicate::str::is_match("Success! Config file written to .*should-be-created.conf\n")
749 .unwrap(),
750 );
752 // Now we expect the file to exist. If it exists, we assume contents are correct
753 assert!(tmp_config_path.exists());
756#[test]
757fn config_location_from_bat_config_dir_variable() {
758 bat_with_config()
759 .env("BAT_CONFIG_DIR", "conf/")
760 .arg("--config-file")
761 .assert()
762 .success()
763 .stdout(predicate::str::is_match("conf/config\n").unwrap());
766#[test]
767fn config_read_arguments_from_file() {
768 bat_with_config()
769 .env("BAT_CONFIG_PATH", "bat.conf")
770 .arg("test.txt")
771 .assert()
772 .success()
773 .stdout(predicate::eq("dummy-pager-from-config\n").normalize());
776#[test]
777fn utf16() {
778 // The output will be converted to UTF-8 with a leading UTF-8 BOM
779 bat()
780 .arg("--plain")
781 .arg("--decorations=always")
782 .arg("test_UTF-16LE.txt")
783 .assert()
784 .success()
785 .stdout(std::str::from_utf8(b"\xEF\xBB\xBFhello world\n").unwrap());
788#[test]
789fn can_print_file_named_cache() {
790 bat_with_config()
791 .arg("cache")
792 .assert()
793 .success()
794 .stdout("test\n")
795 .stderr("");
798#[test]
799fn can_print_file_named_cache_with_additional_argument() {
800 bat_with_config()
801 .arg("cache")
802 .arg("test.txt")
803 .assert()
804 .success()
805 .stdout("test\nhello world\n")
806 .stderr("");
809#[test]
810fn can_print_file_starting_with_cache() {
811 bat_with_config()
812 .arg("cache.c")
813 .assert()
814 .success()
815 .stdout("test\n")
816 .stderr("");
819#[test]
820fn does_not_print_unwanted_file_named_cache() {
821 bat_with_config().arg("cach").assert().failure();
824#[test]
825fn accepts_no_custom_assets_arg() {
826 // Just make sure --no-custom-assets is considered a valid arg
827 // Don't bother to actually verify that it works
828 bat()
829 .arg("--no-custom-assets")
830 .arg("test.txt")
831 .assert()
832 .success();
835#[test]
836fn unicode_wrap() {
837 bat_with_config()
838 .arg("unicode-wrap.txt")
839 .arg("--style=numbers,snip")
840 .arg("--decorations=always")
841 .arg("--terminal-width=40")
842 .assert()
843 .success()
844 .stdout(
845 " 1 ビタミンA ビタミンD ビタミンE ビ
846 タミンK ビタミンB1 ビタミンB2 ナ
847 イアシン パントテン酸 ビタミンB6
848 ビタミンB12 葉酸 ビオチン ビタ
849 ミンC
850 2
851 3 고양이 고양이 고양이 고양이 고양이
852 고양이 고양이 고양이 고양이 고양이
853 고양이 고양이 고양이 고양이 고양이
854 고양이 고양이 고양이 고양이 고양이
855 고양이 고양이 고양이 고양이 고양이
856 고양이 고양이 고양이 고양이 고양이
857 고양이 고양이 고양이 고양이 고양이
858 고양이 고양이 고양이 고양이 고양이
859 고양이
860 4
861 5 1 บวก 2 บวก 3 บวก 4 บวก 5 บวก 6 บวก
862 7 บวก 8 บวก 9 บวก 10 บวก 11 บวก 12
863 บวก 13 บวก 14 บวก 15 บวก 16 บวก 17
864 บวก 18 บวก 19 บวก 20
865 6
866 7 Бельгия Болгария Чехия Дания Герман
867 ия Эстония Ирландия Греция Испания
868 Франция Хорватия Италия Кипр Латвия
869 Литва Люксембург Венгрия Мальта Ни
870 дерланды Австрия Польша Португалия
871 Румыния Словения Словакия Финляндия
872 Швеция Великобритания
873",
874 );
877#[test]
878fn snip() {
879 bat()
880 .arg("multiline.txt")
881 .arg("--style=numbers,snip")
882 .arg("--decorations=always")
883 .arg("--line-range=1:2")
884 .arg("--line-range=4:")
885 .arg("--terminal-width=80")
886 .assert()
887 .success()
888 .stdout(
889 " 1 line 1
890 2 line 2
891 ...─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ 8< ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
892 4 line 4
893",
894 );
897#[test]
898fn empty_file_leads_to_empty_output_with_grid_enabled() {
899 bat()
900 .arg("empty.txt")
901 .arg("--style=grid")
902 .arg("--decorations=always")
903 .arg("--terminal-width=80")
904 .assert()
905 .success()
906 .stdout("");
909#[test]
910fn empty_file_leads_to_empty_output_with_rule_enabled() {
911 bat()
912 .arg("empty.txt")
913 .arg("--style=rule")
914 .arg("--decorations=always")
915 .arg("--terminal-width=80")
916 .assert()
917 .success()
918 .stdout("");
921#[test]
922fn header_basic() {
923 bat()
924 .arg("test.txt")
925 .arg("--decorations=always")
926 .arg("--style=header")
927 .arg("-r=0:0")
928 .arg("--file-name=foo")
929 .assert()
930 .success()
931 .stdout("File: foo\n")
932 .stderr("");
935#[test]
936fn header_full_basic() {
937 bat()
938 .arg("test.txt")
939 .arg("--decorations=always")
940 .arg("--style=header-filename,header-filesize")
941 .arg("-r=0:0")
942 .arg("--file-name=foo")
943 .assert()
944 .success()
945 .stdout("File: foo\nSize: 12 B\n")
946 .stderr("");
949#[test]
950fn header_binary() {
951 bat()
952 .arg("test.binary")
953 .arg("--decorations=always")
954 .arg("--style=header")
955 .arg("-r=0:0")
956 .arg("--file-name=foo")
957 .assert()
958 .success()
959 .stdout("File: foo <BINARY>\n")
960 .stderr("");
963#[test]
964fn header_full_binary() {
965 bat()
966 .arg("test.binary")
967 .arg("--decorations=always")
968 .arg("--style=header-filename,header-filesize")
969 .arg("-r=0:0")
970 .arg("--file-name=foo")
971 .assert()
972 .success()
973 .stdout("File: foo <BINARY>\nSize: 4 B\n")
974 .stderr("");
977#[test]
978fn filename_stdin() {
979 bat()
980 .arg("--decorations=always")
981 .arg("--style=header")
982 .arg("-r=0:0")
983 .arg("-")
984 .write_stdin("stdin\n")
985 .arg("--file-name=foo")
986 .assert()
987 .success()
988 .stdout("File: foo\n")
989 .stderr("");
992#[test]
993fn filename_stdin_binary() {
994 let vec = vec![0; 1];
995 bat_with_config()
996 .arg("--decorations=always")
997 .arg("--style=header")
998 .write_stdin(vec)
999 .arg("--file-name=foo")
1000 .assert()
1001 .success()
1002 .stdout("File: foo <BINARY>\n")
1003 .stderr("");
1006#[test]
1007fn filename_multiple_ok() {
1008 bat()
1009 .arg("--decorations=always")
1010 .arg("--style=header")
1011 .arg("-r=0:0")
1012 .arg("test.txt")
1013 .arg("--file-name=foo")
1014 .arg("single-line.txt")
1015 .arg("--file-name=bar")
1016 .assert()
1017 .success()
1018 .stdout("File: foo\n\nFile: bar\n")
1019 .stderr("");
1022#[test]
1023fn filename_multiple_err() {
1024 bat()
1025 .arg("--decorations=always")
1026 .arg("--style=header")
1027 .arg("-r=0:0")
1028 .arg("test.txt")
1029 .arg("--file-name=foo")
1030 .arg("single-line.txt")
1031 .assert()
1032 .failure();
1035#[test]
1036fn header_padding() {
1037 bat()
1038 .arg("--decorations=always")
1039 .arg("--style=header")
1040 .arg("test.txt")
1041 .arg("single-line.txt")
1042 .assert()
1043 .stdout("File: test.txt\nhello world\n\nFile: single-line.txt\nSingle Line\n")
1044 .stderr("");
1047#[test]
1048fn header_full_padding() {
1049 bat()
1050 .arg("--decorations=always")
1051 .arg("--style=header-filename,header-filesize")
1052 .arg("test.txt")
1053 .arg("single-line.txt")
1054 .assert()
1055 .stdout("File: test.txt\nSize: 12 B\nhello world\n\nFile: single-line.txt\nSize: 11 B\nSingle Line\n")
1056 .stderr("");
1059#[test]
1060fn header_padding_rule() {
1061 bat()
1062 .arg("--decorations=always")
1063 .arg("--style=header,rule")
1064 .arg("--terminal-width=80")
1065 .arg("test.txt")
1066 .arg("single-line.txt")
1067 .assert()
1068 .stdout(
1069 "File: test.txt
1070hello world
1071────────────────────────────────────────────────────────────────────────────────
1072File: single-line.txt
1073Single Line
1076 .stderr("");
1079#[test]
1080fn header_full_padding_rule() {
1081 bat()
1082 .arg("--decorations=always")
1083 .arg("--style=header-filename,header-filesize,rule")
1084 .arg("--terminal-width=80")
1085 .arg("test.txt")
1086 .arg("single-line.txt")
1087 .assert()
1088 .stdout(
1089 "File: test.txt
1090Size: 12 B
1091hello world
1092────────────────────────────────────────────────────────────────────────────────
1093File: single-line.txt
1094Size: 11 B
1095Single Line
1098 .stderr("");
1101#[test]
1102fn grid_overrides_rule() {
1103 bat()
1104 .arg("--decorations=always")
1105 .arg("--style=grid,rule")
1106 .arg("--terminal-width=80")
1107 .arg("test.txt")
1108 .arg("single-line.txt")
1109 .assert()
1110 .stdout(
1111 "\
1112────────────────────────────────────────────────────────────────────────────────
1113hello world
1114────────────────────────────────────────────────────────────────────────────────
1115────────────────────────────────────────────────────────────────────────────────
1116Single Line
1117────────────────────────────────────────────────────────────────────────────────
1120 .stderr(
1121 "\x1b[33m[bat warning]\x1b[0m: Style 'rule' is a subset of style 'grid', 'rule' will not be visible.\n",
1122 );
1125#[cfg(target_os = "linux")]
1126#[test]
1127fn file_with_invalid_utf8_filename() {
1128 use std::ffi::OsStr;
1129 use std::fs::File;
1130 use std::io::Write;
1131 use std::os::unix::ffi::OsStrExt;
1133 let tmp_dir = tempdir().expect("can create temporary directory");
1134 let file_path = tmp_dir
1135 .path()
1136 .join(OsStr::from_bytes(b"test-invalid-utf8-\xC3(.rs"));
1138 let mut file = File::create(&file_path).expect("can create temporary file");
1139 writeln!(file, "dummy content").expect("can write to file");
1142 bat()
1143 .arg(file_path.as_os_str())
1144 .assert()
1145 .success()
1146 .stdout("dummy content\n");
1149#[test]
1150fn do_not_panic_regression_tests() {
1151 for filename in &[
1152 "issue_28.md",
1153 "issue_190.md",
1154 "issue_314.hs",
1155 "issue_914.rb",
1156 "issue_915.vue",
1157 ] {
1158 bat()
1159 .arg("--color=always")
1160 .arg(&format!("regression_tests/{}", filename))
1161 .assert()
1162 .success();
1166#[test]
1167fn do_not_detect_different_syntax_for_stdin_and_files() {
1168 let file = "regression_tests/issue_985.js";
1170 let cmd_for_file = bat()
1171 .arg("--color=always")
1172 .arg("--map-syntax=*.js:Markdown")
1173 .arg(&format!("--file-name={}", file))
1174 .arg("--style=plain")
1175 .arg(file)
1176 .assert()
1177 .success();
1179 let cmd_for_stdin = bat()
1180 .arg("--color=always")
1181 .arg("--map-syntax=*.js:Markdown")
1182 .arg("--style=plain")
1183 .arg(&format!("--file-name={}", file))
1184 .pipe_stdin(Path::new(EXAMPLES_DIR).join(file))
1185 .unwrap()
1186 .assert()
1187 .success();
1189 assert_eq!(
1190 from_utf8(&cmd_for_file.get_output().stdout).expect("output is valid utf-8"),
1191 from_utf8(&cmd_for_stdin.get_output().stdout).expect("output is valid utf-8")
1192 );
1195#[test]
1196fn no_first_line_fallback_when_mapping_to_invalid_syntax() {
1197 let file = "regression_tests/first_line_fallback.invalid-syntax";
1199 bat()
1200 .arg("--color=always")
1201 .arg("--map-syntax=*.invalid-syntax:InvalidSyntax")
1202 .arg(&format!("--file-name={}", file))
1203 .arg("--style=plain")
1204 .arg(file)
1205 .assert()
1206 .failure()
1207 .stderr(predicate::str::contains("unknown syntax: 'InvalidSyntax'"));
1210#[test]
1211fn show_all_mode() {
1212 bat()
1213 .arg("--show-all")
1214 .arg("nonprintable.txt")
1215 .assert()
1216 .stdout("hello·world␊\n├──┤␍␀␇␈␛")
1217 .stderr("");
1220#[test]
1221fn no_paging_arg() {
1222 bat()
1223 .arg("--no-paging")
1224 .arg("--color=never")
1225 .arg("--decorations=never")
1226 .arg("single-line.txt")
1227 .assert()
1228 .success()
1229 .stdout("Single Line");
1232#[test]
1233fn no_paging_short_arg() {
1234 bat()
1235 .arg("-P")
1236 .arg("--color=never")
1237 .arg("--decorations=never")
1238 .arg("single-line.txt")
1239 .assert()
1240 .success()
1241 .stdout("Single Line");
1244#[test]
1245fn no_pager_arg() {
1246 bat()
1247 .arg("--no-pager")
1248 .arg("--color=never")
1249 .arg("--decorations=never")
1250 .arg("single-line.txt")
1251 .assert()
1252 .success()
1253 .stdout("Single Line");
1256#[test]
1257fn plain_mode_does_not_add_nonexisting_newline() {
1258 bat()
1259 .arg("--paging=never")
1260 .arg("--color=never")
1261 .arg("--decorations=always")
1262 .arg("--style=plain")
1263 .arg("single-line.txt")
1264 .assert()
1265 .success()
1266 .stdout("Single Line");
1269// Regression test for https://github.com/sharkdp/bat/issues/299
1270#[test]
1271fn grid_for_file_without_newline() {
1272 bat()
1273 .arg("--paging=never")
1274 .arg("--color=never")
1275 .arg("--terminal-width=80")
1276 .arg("--wrap=never")
1277 .arg("--decorations=always")
1278 .arg("--style=full")
1279 .arg("single-line.txt")
1280 .assert()
1281 .success()
1282 .stdout(
1283 "\
1284───────┬────────────────────────────────────────────────────────────────────────
1285 │ File: single-line.txt
1286 │ Size: 11 B
1287───────┼────────────────────────────────────────────────────────────────────────
1288 1 │ Single Line
1289───────┴────────────────────────────────────────────────────────────────────────
1292 .stderr("");
1295// For ANSI theme, use underscore as a highlighter
1296#[test]
1297fn ansi_highlight_underline() {
1298 bat()
1299 .arg("--paging=never")
1300 .arg("--color=never")
1301 .arg("--terminal-width=80")
1302 .arg("--wrap=never")
1303 .arg("--decorations=always")
1304 .arg("--theme=ansi")
1305 .arg("--style=plain")
1306 .arg("--highlight-line=1")
1307 .write_stdin("Ansi Underscore Test\nAnother Line")
1308 .assert()
1309 .success()
1310 .stdout("\x1B[4mAnsi Underscore Test\n\x1B[24mAnother Line")
1311 .stderr("");
1314// Ensure that ANSI passthrough is emitted properly for both wrapping and non-wrapping printer.
1315#[test]
1316fn ansi_passthrough_emit() {
1317 for wrapping in &["never", "character"] {
1318 bat()
1319 .arg("--paging=never")
1320 .arg("--color=never")
1321 .arg("--terminal-width=80")
1322 .arg(format!("--wrap={}", wrapping))
1323 .arg("--decorations=always")
1324 .arg("--style=plain")
1325 .write_stdin("\x1B[33mColor\nColor \x1B[m\nPlain\n")
1326 .assert()
1327 .success()
1328 .stdout("\x1B[33m\x1B[33mColor\n\x1B[33mColor \x1B[m\nPlain\n")
1329 .stderr("");
1333#[test]
1334fn ignored_suffix_arg() {
1335 bat()
1336 .arg("-f")
1337 .arg("-p")
1338 .arg("test.json~")
1339 .assert()
1340 .success()
1341 .stdout("\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mtest\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;186mvalue\u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m")
1342 .stderr("");
1344 bat()
1345 .arg("-f")
1346 .arg("-p")
1347 .arg("--ignored-suffix=.suffix")
1348 .arg("test.json.suffix")
1349 .assert()
1350 .success()
1351 .stdout("\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mtest\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;186mvalue\u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m")
1352 .stderr("");
1354 bat()
1355 .arg("-f")
1356 .arg("-p")
1357 .arg("test.json.suffix")
1358 .assert()
1359 .success()
1360 .stdout("\u{1b}[38;5;231m{\"test\": \"value\"}\u{1b}[0m")
1361 .stderr("");
1364#[test]
1365fn all_global_git_config_locations_syntax_mapping_work() {
1366 let fake_home = Path::new(EXAMPLES_DIR).join("git").canonicalize().unwrap();
1367 let expected = "\u{1b}[38;5;231m[\u{1b}[0m\u{1b}[38;5;149muser\u{1b}[0m\u{1b}[38;5;231m]\u{1b}[0m
1368\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;231memail\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;203m=\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186mfoo@bar.net\u{1b}[0m
1369\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;231mname\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;203m=\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186mfoobar\u{1b}[0m
1372 bat()
1373 .env("XDG_CONFIG_HOME", fake_home.join(".config").as_os_str())
1374 .arg("-f")
1375 .arg("-p")
1376 .arg("git/.config/git/config")
1377 .assert()
1378 .success()
1379 .stdout(expected)
1380 .stderr("");
1382 bat()
1383 .env("HOME", fake_home.as_os_str())
1384 .arg("-f")
1385 .arg("-p")
1386 .arg("git/.config/git/config")
1387 .assert()
1388 .success()
1389 .stdout(expected)
1390 .stderr("");
1392 bat()
1393 .env("HOME", fake_home.as_os_str())
1394 .arg("-f")
1395 .arg("-p")
1396 .arg("git/.gitconfig")
1397 .assert()
1398 .success()
1399 .stdout(expected)
1400 .stderr("");
1403#[test]
1404fn acknowledgements() {
1405 bat()
1406 .arg("--acknowledgements")
1407 .assert()
1408 .success()
1409 .stdout(
1410 // Just some sanity checking that avoids names of persons, except our own Keith Hall :)
1411 predicate::str::contains(
1412 "Copyright (c) 2018-2021 bat-developers (https://github.com/sharkdp/bat).",
1414 .and(predicate::str::contains(
1415 "Copyright (c) 2012-2020 The Sublime CMake authors",
1416 ))
1417 .and(predicate::str::contains(
1418 "Copyright 2014-2015 SaltStack Team",
1419 ))
1420 .and(predicate::str::contains(
1421 "Copyright (c) 2013-present Dracula Theme",
1422 ))
1423 .and(predicate::str::contains(
1424 "## syntaxes/01_Packages/Rust/LICENSE.txt",
1425 ))
1426 .and(predicate::str::contains(
1427 "## syntaxes/02_Extra/http-request-response/LICENSE",
1428 ))
1429 .and(predicate::str::contains(
1430 "## themes/dracula-sublime/LICENSE",
1431 ))
1432 .and(predicate::str::contains("Copyright (c) 2017 b123400"))
1433 .and(predicate::str::contains("Copyright (c) 2021 Keith Hall"))
1434 .and(predicate::str::contains("Copyright 2014 Clams")),
1436 .stderr("");