Use more conservative circle detection
Fixes #1503

This implements the exact logic for circle detection from `cat` for `bat`. To test this out yourself, you will have to get the newest version from the [pull request](https://github.com/niklasmohrin/clircle/pull/6) on the `clircle` repository and replace the `clircle` dependency in the `Cargo.toml` with the path to your local copy.

I will still have to fix the tests and see what to do with windows. Now that the posix approach (only error if program will provably fail later on) is taken here, we will have to investigate the situation on windows more closely.

I might still change things, there are breaking changes in `clircle` already, so there's no reason to hold back on changes over there.

Let me know, what you think and if you know anything about this on windows!

**Describe the bug you encountered:**

```
▶ bat > /dev/null < /dev/null
[bat error]: The output file is also an input!
```

**What did you expect to happen instead?**

This is kind of a weird edge case, but running `bat` like this is actually useful for benchmarking. This is how we measure the startup time of `bat` in `tests/benchmarks/run-benchmarks.sh`. We do not specify `/dev/null` explicitly, but rather just run:
```
hyperfine "bat"
```
which attaches stdin/stout to `/dev/null` internally.

Maybe we should add an exception for `/dev/null`? `cat` does not complain.

FYI: @niklasmohrin 
Can not run bat with input and output attached to /dev/null
diff --git a/Cargo.lock b/Cargo.lock
index 8aaedd7768..3266dcad9f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -98,6 +98,7 @@ dependencies = [
  "git2",
  "globset",
  "lazy_static",
+ "nix",
  "path_abs",
  "predicates",
  "semver",
@@ -213,12 +214,12 @@ dependencies = [
 
 [[package]]
 name = "clircle"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e27a01e782190a8314e65cc94274d9bbcd52e05a9d15b437fe2b31259b854b0d"
+checksum = "e68bbd985a63de680ab4d1ad77b6306611a8f961b282c8b5ab513e6de934e396"
 dependencies = [
  "cfg-if",
- "nix",
+ "libc",
  "serde",
  "winapi",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index d67d2a9a60..05da0fa469 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,7 +48,7 @@ serde = { version = "1.0", features = ["derive"] }
 serde_yaml = "0.8"
 semver = "0.11"
 path_abs = { version = "0.5", default-features = false }
-clircle = "0.2.0"
+clircle = "0.3"
 bugreport = "0.3"
 dirs-next = { version = "2.0.0", optional = true }
 
@@ -79,6 +79,9 @@ predicates = "1.0.7"
 wait-timeout = "0.2.0"
 tempfile = "3.2.0"
 
+[target.'cfg(unix)'.dev-dependencies]
+nix = "0.19.1"
+
 [build-dependencies]
 clap = { version = "2.33", optional = true }
 
diff --git a/src/assets.rs b/src/assets.rs
index 5f374675aa..48f59da937 100644
--- a/src/assets.rs
+++ b/src/assets.rs
@@ -329,7 +329,7 @@ mod tests {
 
             let input = Input::ordinary_file(file_path.as_os_str());
             let dummy_stdin: &[u8] = &[];
-            let mut opened_input = input.open(dummy_stdin).unwrap();
+            let mut opened_input = input.open(dummy_stdin, None).unwrap();
 
             self.assets
                 .get_syntax(None, &mut opened_input, &self.syntax_mapping)
@@ -343,7 +343,7 @@ mod tests {
             let input = Input::from_reader(Box::new(BufReader::new(first_line.as_bytes())))
                 .with_name(Some(file_path.as_os_str()));
             let dummy_stdin: &[u8] = &[];
-            let mut opened_input = input.open(dummy_stdin).unwrap();
+            let mut opened_input = input.open(dummy_stdin, None).unwrap();
 
             self.assets
                 .get_syntax(None, &mut opened_input, &self.syntax_mapping)
@@ -367,7 +367,7 @@ mod tests {
 
         fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String {
             let input = Input::stdin().with_name(Some(OsStr::new(file_name)));
-            let mut opened_input = input.open(content).unwrap();
+            let mut opened_input = input.open(content, None).unwrap();
 
             self.assets
                 .get_syntax(None, &mut opened_input, &self.syntax_mapping)
@@ -523,7 +523,7 @@ mod tests {
 
         let input = Input::ordinary_file(file_path_symlink.as_os_str());
         let dummy_stdin: &[u8] = &[];
-        let mut opened_input = input.open(dummy_stdin).unwrap();
+        let mut opened_input = input.open(dummy_stdin, None).unwrap();
 
         assert_eq!(
             test.assets
diff --git a/src/controller.rs b/src/controller.rs
index a1e3f11935..a4d88c63fb 100644
--- a/src/controller.rs
+++ b/src/controller.rs
@@ -1,4 +1,3 @@
-use std::convert::TryFrom;
 use std::io::{self, Write};
 
 use crate::assets::HighlightingAssets;
@@ -15,6 +14,8 @@ use crate::output::OutputType;
 use crate::paging::PagingMode;
 use crate::printer::{InteractivePrinter, Printer, SimplePrinter};
 
+use clircle::Clircle;
+
 pub struct Controller<'a> {
     config: &'a Config<'a>,
     assets: &'a HighlightingAssets,
@@ -67,12 +68,10 @@ impl<'b> Controller<'b> {
         }
 
         let attached_to_pager = output_type.is_pager();
-        let rw_cycle_detected = !attached_to_pager && {
-            let identifiers: Vec<_> = inputs
-                .iter()
-                .flat_map(clircle::Identifier::try_from)
-                .collect();
-            clircle::stdout_among_inputs(&identifiers)
+        let stdout_identifier = if cfg!(windows) || attached_to_pager {
+            None
+        } else {
+            clircle::Identifier::stdout()
         };
 
         let writer = output_type.handle()?;
@@ -87,13 +86,8 @@ impl<'b> Controller<'b> {
             }
         };
 
-        if rw_cycle_detected {
-            print_error(&"The output file is also an input!".into(), writer);
-            return Ok(false);
-        }
-
         for (index, input) in inputs.into_iter().enumerate() {
-            match input.open(io::stdin().lock()) {
+            match input.open(io::stdin().lock(), stdout_identifier.as_ref()) {
                 Err(error) => {
                     print_error(&error, writer);
                     no_errors = false;
diff --git a/src/input.rs b/src/input.rs
index 1e7aec2f2b..0cc902cd9b 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -2,8 +2,8 @@ use std::convert::TryFrom;
 use std::ffi::{OsStr, OsString};
 use std::fs::File;
 use std::io::{self, BufRead, BufReader, Read};
-use std::path::Path;
 
+use clircle::{Clircle, Identifier};
 use content_inspector::{self, ContentType};
 
 use crate::error::*;
@@ -157,25 +157,55 @@ impl<'a> Input<'a> {
         &mut self.description
     }
 
-    pub(crate) fn open<R: BufRead + 'a>(self, stdin: R) -> Result<OpenedInput<'a>> {
+    pub(crate) fn open<R: BufRead + 'a>(
+        self,
+        stdin: R,
+        stdout_identifier: Option<&Identifier>,
+    ) -> Result<OpenedInput<'a>> {
         let description = self.description().clone();
         match self.kind {
-            InputKind::StdIn => Ok(OpenedInput {
-                kind: OpenedInputKind::StdIn,
-                description,
-                metadata: self.metadata,
-                reader: InputReader::new(stdin),
-            }),
+            InputKind::StdIn => {
+                if let Some(stdout) = stdout_identifier {
+                    let input_identifier = Identifier::try_from(clircle::Stdio::Stdin)
+                        .map_err(|e| format!("Stdin: Error identifying file: {}", e))?;
+                    if stdout.surely_conflicts_with(&input_identifier) {
+                        return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into());
+                    }
+                }
+
+                Ok(OpenedInput {
+                    kind: OpenedInputKind::StdIn,
+                    description,
+                    metadata: self.metadata,
+                    reader: InputReader::new(stdin),
+                })
+            }
+
             InputKind::OrdinaryFile(path) => Ok(OpenedInput {
                 kind: OpenedInputKind::OrdinaryFile(path.clone()),
                 description,
                 metadata: self.metadata,
                 reader: {
-                    let file = File::open(&path)
+                    let mut file = File::open(&path)
                         .map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
                     if file.metadata()?.is_dir() {
                         return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
                     }
+
+                    if let Some(stdout) = stdout_identifier {
+                        let input_identifier = Identifier::try_from(file).map_err(|e| {
+                            format!("{}: Error identifying file: {}", path.to_string_lossy(), e)
+                        })?;
+                        if stdout.surely_conflicts_with(&input_identifier) {
+                            return Err(format!(
+                                "IO circle detected. The input from '{}' is also an output. Aborting to avoid infinite loop.",
+                                path.to_string_lossy()
+                            )
+                            .into());
+                        }
+                        file = input_identifier.into_inner().expect("The file was lost in the clircle::Identifier, this should not have happended...");
+                    }
+
                     InputReader::new(BufReader::new(file))
                 },
             }),
@@ -189,18 +219,6 @@ impl<'a> Input<'a> {
     }
 }
 
-impl TryFrom<&'_ Input<'_>> for clircle::Identifier {
-    type Error = ();
-
-    fn try_from(input: &Input) -> std::result::Result<clircle::Identifier, ()> {
-        match input.kind {
-            InputKind::OrdinaryFile(ref path) => Self::try_from(Path::new(path)).map_err(|_| ()),
-            InputKind::StdIn => Self::try_from(clircle::Stdio::Stdin).map_err(|_| ()),
-            InputKind::CustomReader(_) => Err(()),
-        }
-    }
-}
-
 pub(crate) struct InputReader<'a> {
     inner: Box<dyn BufRead + 'a>,
     pub(crate) first_line: Vec<u8>,

diff --git a/tests/examples/cycle.txt b/tests/examples/cycle.txt
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs
index de67378465..3e3d029a18 100644
--- a/tests/integration_tests.rs
+++ b/tests/integration_tests.rs
@@ -1,28 +1,37 @@
-use assert_cmd::assert::OutputAssertExt;
 use assert_cmd::cargo::CommandCargoExt;
 use predicates::{prelude::predicate, str::PredicateStrExt};
 use serial_test::serial;
-use std::fs::File;
 use std::path::Path;
-use std::process::{Command, Stdio};
+use std::process::Command;
 use std::str::from_utf8;
 use tempfile::tempdir;
 
 #[cfg(unix)]
-use std::time::Duration;
+mod unix {
+    pub use std::fs::File;
+    pub use std::io::{self, Write};
+    pub use std::os::unix::io::FromRawFd;
+    pub use std::path::PathBuf;
+    pub use std::process::Stdio;
+    pub use std::thread;
+    pub use std::time::Duration;
+
+    pub use assert_cmd::assert::OutputAssertExt;
+    pub use nix::pty::{openpty, OpenptyResult};
+    pub use wait_timeout::ChildExt;
+
+    pub const SAFE_CHILD_PROCESS_CREATION_TIME: Duration = Duration::from_millis(100);
+    pub const CHILD_WAIT_TIMEOUT: Duration = Duration::from_secs(15);
+}
+#[cfg(unix)]
+use unix::*;
 
 mod utils;
 use utils::mocked_pagers;
 
 const EXAMPLES_DIR: &str = "tests/examples";
 
-#[cfg(unix)]
-const SAFE_CHILD_PROCESS_CREATION_TIME: Duration = Duration::from_millis(100);
-
-#[cfg(unix)]
-const CHILD_WAIT_TIMEOUT: Duration = Duration::from_secs(15);
-
-fn bat_raw_command() -> Command {
+fn bat_raw_command_with_config() -> Command {
     let mut cmd = Command::cargo_bin("bat").unwrap();
     cmd.current_dir("tests/examples");
     cmd.env_remove("PAGER");
@@ -34,14 +43,18 @@ fn bat_raw_command() -> Command {
     cmd
 }
 
+fn bat_raw_command() -> Command {
+    let mut cmd = bat_raw_command_with_config();
+    cmd.arg("--no-config");
+    cmd
+}
+
 fn bat_with_config() -> assert_cmd::Command {
-    assert_cmd::Command::from_std(bat_raw_command())
+    assert_cmd::Command::from_std(bat_raw_command_with_config())
 }
 
 fn bat() -> assert_cmd::Command {
-    let mut cmd = bat_with_config();
-    cmd.arg("--no-config");
-    cmd
+    assert_cmd::Command::from_std(bat_raw_command())
 }
 
 #[test]
@@ -210,40 +223,82 @@ fn line_range_multiple() {
         .stdout("line 1\nline 2\nline 4\n");
 }
 
+#[cfg(unix)]
+fn setup_temp_file(content: &[u8]) -> io::Result<(PathBuf, tempfile::TempDir)> {
+    let dir = tempfile::tempdir().expect("Couldn't create tempdir");
+    let path = dir.path().join("temp_file");
+    File::create(&path)?.write_all(content)?;
+    Ok((path, dir))
+}
+
+#[cfg(unix)]
 #[test]
-fn basic_io_cycle() {
-    let file_out = Stdio::from(File::open("tests/examples/cycle.txt").unwrap());
-    bat_raw_command()
+fn basic_io_cycle() -> io::Result<()> {
+    let (filename, dir) = setup_temp_file(b"I am not empty")?;
+
+    let file_out = Stdio::from(File::create(&filename)?);
+    let res = bat_raw_command()
         .arg("test.txt")
-        .arg("cycle.txt")
+        .arg(&filename)
         .stdout(file_out)
-        .assert()
-        .failure();
+        .assert();
+    drop(dir);
+    res.failure();
+    Ok(())
 }
 
+#[cfg(unix)]
 #[test]
-fn stdin_to_stdout_cycle() {
-    let file_out = Stdio::from(File::open("tests/examples/cycle.txt").unwrap());
-    let file_in = Stdio::from(File::open("tests/examples/cycle.txt").unwrap());
-    bat_raw_command()
-        .stdin(file_in)
+fn first_file_cyclic_is_ok() -> io::Result<()> {
+    let (filename, dir) = setup_temp_file(b"I am not empty")?;
+
+    let file_out = Stdio::from(File::create(&filename)?);
+    let res = bat_raw_command()
+        .arg(&filename)
         .arg("test.txt")
-        .arg("-")
         .stdout(file_out)
-        .assert()
-        .failure();
+        .assert();
+    drop(dir);
+    res.success();
+    Ok(())
 }
 
 #[cfg(unix)]
 #[test]
-fn no_args_doesnt_break() {
-    use std::io::Write;
-    use std::os::unix::io::FromRawFd;
-    use std::thread;
+fn empty_file_cycle_is_ok() -> io::Result<()> {
+    let (filename, dir) = setup_temp_file(b"I am not empty")?;
+
+    let file_out = Stdio::from(File::create(&filename)?);
+    let res = bat_raw_command()
+        .arg("empty.txt")
+        .arg(&filename)
+        .stdout(file_out)
+        .assert();
+    drop(dir);
+    res.success();
+    Ok(())
+}
 
-    use clircle::nix::pty::{openpty, OpenptyResult};
-    use wait_timeout::ChildExt;
+#[cfg(unix)]
+#[test]
+fn stdin_to_stdout_cycle() -> io::Result<()> {
+    let (filename, dir) = setup_temp_file(b"I am not empty")?;
+    let file_in = Stdio::from(File::open(&filename)?);
+    let file_out = Stdio::from(File::create(&filename)?);
+    let res = bat_raw_command()
+        .arg("test.txt")
+        .arg("-")
+        .stdin(file_in)
+        .stdout(file_out)
+        .assert();
+    drop(dir);
+    res.failure();
+    Ok(())
+}
 
+#[cfg(unix)]
+#[test]
+fn no_args_doesnt_break() {
     // To simulate bat getting started from the shell, a process is created with stdin and stdout
     // as the slave end of a pseudo terminal. Although both point to the same "file", bat should
     // not exit, because in this case it is safe to read and write to the same fd, which is why

{"syntax_mapping::basic": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_stdin_filename": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "less::test_parse_less_version_529": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::comments": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_partial_min": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_with_custom_mapping": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::quotes": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config_location_when_generating": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_open_high": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_none": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::default_config_should_include_all_lines": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "preprocessor::test_try_parse_utf8_char": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_basic": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::empty": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::multiple": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_first_line": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_is_case_sensitive": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "syntax_mapping::user_can_override_builtin_mappings": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "input::utf16le": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "input::basic": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::default_config_should_highlight_no_lines": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "less::test_parse_less_version_551": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "syntax_mapping::builtin_mappings": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_single": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_well_defined_mapping_for_duplicate_extensions": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_advanced": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_fail": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_same_for_inputkinds": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_all": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::multi_line": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config_location_test": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_partial_max": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_simple": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_for_symlinked_file": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::single": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "less::test_parse_less_version_wrong_program": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "less::test_parse_less_version_487": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_full": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_open_low": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_invalid_utf8": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"syntax_mapping::basic": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_stdin_filename": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "less::test_parse_less_version_529": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::comments": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_partial_min": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_with_custom_mapping": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::quotes": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config_location_when_generating": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_open_high": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_none": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::default_config_should_include_all_lines": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "preprocessor::test_try_parse_utf8_char": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_basic": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::empty": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::multiple": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_first_line": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_is_case_sensitive": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "syntax_mapping::user_can_override_builtin_mappings": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "input::utf16le": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "input::basic": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::default_config_should_highlight_no_lines": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "less::test_parse_less_version_551": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "syntax_mapping::builtin_mappings": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_single": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_well_defined_mapping_for_duplicate_extensions": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_advanced": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_fail": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_same_for_inputkinds": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_all": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::multi_line": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config_location_test": {"run": "NONE", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_partial_max": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_simple": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_for_symlinked_file": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "config::single": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "less::test_parse_less_version_wrong_program": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "less::test_parse_less_version_487": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_parse_full": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "line_range::test_ranges_open_low": {"run": "PASS", "test": "NONE", "fix": "PASS"}, "assets::tests::syntax_detection_invalid_utf8": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"passed_count": 39, "failed_count": 0, "skipped_count": 1, "passed_tests": ["syntax_mapping::basic", "assets::tests::syntax_detection_stdin_filename", "less::test_parse_less_version_529", "config::comments", "line_range::test_parse_partial_min", "assets::tests::syntax_detection_with_custom_mapping", "config::quotes", "filename_multiple_err", "line_range::test_ranges_open_high", "line_range::test_ranges_none", "config::default_config_should_include_all_lines", "preprocessor::test_try_parse_utf8_char", "assets::tests::syntax_detection_basic", "config::empty", "config::multiple", "assets::tests::syntax_detection_first_line", "assets::tests::syntax_detection_is_case_sensitive", "syntax_mapping::user_can_override_builtin_mappings", "input::utf16le", "input::basic", "config::default_config_should_highlight_no_lines", "less::test_parse_less_version_551", "syntax_mapping::builtin_mappings", "line_range::test_parse_single", "assets::tests::syntax_detection_well_defined_mapping_for_duplicate_extensions", "line_range::test_ranges_advanced", "line_range::test_parse_fail", "assets::tests::syntax_detection_same_for_inputkinds", "line_range::test_ranges_all", "config::multi_line", "line_range::test_parse_partial_max", "line_range::test_ranges_simple", "assets::tests::syntax_detection_for_symlinked_file", "config::single", "less::test_parse_less_version_wrong_program", "less::test_parse_less_version_487", "line_range::test_parse_full", "line_range::test_ranges_open_low", "assets::tests::syntax_detection_invalid_utf8"], "failed_tests": [], "skipped_tests": ["all_themes_are_present"]}
{"passed_count": 0, "failed_count": 0, "skipped_count": 0, "passed_tests": [], "failed_tests": [], "skipped_tests": []}
{"passed_count": 40, "failed_count": 0, "skipped_count": 1, "passed_tests": ["syntax_mapping::basic", "assets::tests::syntax_detection_stdin_filename", "less::test_parse_less_version_529", "config::comments", "line_range::test_parse_partial_min", "assets::tests::syntax_detection_with_custom_mapping", "config::quotes", "config_location_when_generating", "line_range::test_ranges_open_high", "line_range::test_ranges_none", "config::default_config_should_include_all_lines", "preprocessor::test_try_parse_utf8_char", "assets::tests::syntax_detection_basic", "config::empty", "config::multiple", "assets::tests::syntax_detection_first_line", "assets::tests::syntax_detection_is_case_sensitive", "syntax_mapping::user_can_override_builtin_mappings", "input::utf16le", "input::basic", "config::default_config_should_highlight_no_lines", "less::test_parse_less_version_551", "syntax_mapping::builtin_mappings", "line_range::test_parse_single", "assets::tests::syntax_detection_well_defined_mapping_for_duplicate_extensions", "line_range::test_ranges_advanced", "line_range::test_parse_fail", "assets::tests::syntax_detection_same_for_inputkinds", "line_range::test_ranges_all", "config::multi_line", "config_location_test", "line_range::test_parse_partial_max", "line_range::test_ranges_simple", "assets::tests::syntax_detection_for_symlinked_file", "config::single", "less::test_parse_less_version_wrong_program", "less::test_parse_less_version_487", "line_range::test_parse_full", "line_range::test_ranges_open_low", "assets::tests::syntax_detection_invalid_utf8"], "failed_tests": [], "skipped_tests": ["all_themes_are_present"]}