/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::borrow::Cow;
use std::env::VarError;
use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;

use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use once_cell::sync::Lazy;
use parking_lot::RwLock;

#[derive(PartialEq, Debug, Clone, Copy)]
pub struct Identity {
    user: UserIdentity,
    repo: RepoIdentity,
}

#[derive(PartialEq, Debug, Clone, Copy)]
struct UserIdentity {
    /// Name of the binary. Used for showing help messages
    ///
    /// Example: `Checkout failed. Resume with 'sl goto --continue'`
    cli_name: &'static str,

    /// Name of the product
    product_name: &'static str,

    /// Full name of the product
    long_product_name: &'static str,

    /// Prefix of environment variables related to current repo.
    env_prefix: &'static str,

    /// Subdirectory below user's config directory. The user's config directory depends on platform:
    ///
    /// |Platform | Value                                 | Example                                  |
    /// | ------- | ------------------------------------- | ---------------------------------------- |
    /// | Linux   | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config/sapling              |
    /// | macOS   | `$HOME`/Library/Preferences           | /Users/Alice/Library/Preferences/sapling |
    /// | Windows | `{FOLDERID_RoamingAppData}`           | C:\Users\Alice\AppData\Roaming\sapling   |
    ///
    /// If None, config file is directly in home directory.
    config_user_directory: Option<&'static str>,

    /// User config file names to look for inside of `config_directory`.
    config_user_files: &'static [&'static str],

    /// System config file path, typically installed by package or system administrator.
    /// On Windows, `config_system_path` is a path fragment that gets appended to %PROGRAMDATA%.
    config_system_path: &'static str,

    /// Disables any configuration settings that might change the default output, including but not
    /// being limited to encoding, defaults, verbose mode, debug mode, quiet mode, and tracebacks
    ///
    /// See `<cli_name> help scripting`` for more details
    scripting_env_var: &'static str,

    /// If this environment variable is set, its value is considered the only file to look into for
    /// system and user configs
    scripting_config_env_var: &'static str,

    /// Comma-separated list of features to preserve if `scripting_env_var` is enabled
    scripting_except_env_var: &'static str,
}

#[derive(PartialEq, Debug, Clone, Copy)]
struct RepoIdentity {
    /// Metadata directory of the current identity. If this directory exists in the current repo, it
    /// implies that the repo is using this identity.
    dot_dir: &'static str,

    /// Config file for the repo; located inside of `dot_dir`.
    ///
    /// Examples: `config`, `hgrc`
    config_repo_file: &'static str,
}

impl Identity {
    pub fn cli_name(&self) -> &'static str {
        self.user.cli_name
    }

    pub fn product_name(&self) -> &'static str {
        self.user.product_name
    }

    pub fn long_product_name(&self) -> &'static str {
        self.user.long_product_name
    }

    pub fn dot_dir(&self) -> &'static str {
        self.repo.dot_dir
    }

    pub fn config_repo_file(&self) -> &'static str {
        self.repo.config_repo_file
    }

    pub fn env_prefix(&self) -> &'static str {
        self.user.env_prefix
    }

    pub const fn env_name_static(&self, suffix: &str) -> Option<&'static str> {
        // Use byte slice to workaround const_fn limitation.
        let bsuffix = suffix.as_bytes();
        match bsuffix {
            b"CONFIG" => Some(self.user.scripting_config_env_var),
            b"PLAIN" => Some(self.user.scripting_env_var),
            b"PLAINEXCEPT" => Some(self.user.scripting_except_env_var),
            _ => None,
        }
    }

    pub fn env_name(&self, suffix: &str) -> Cow<'static, str> {
        match self.env_name_static(suffix) {
            Some(name) => Cow::Borrowed(name),
            None => Cow::Owned([self.user.env_prefix, suffix].concat()),
        }
    }

    pub fn env_var(&self, suffix: &str) -> Option<Result<String, VarError>> {
        let var_name = self.env_name(suffix);
        match std::env::var(var_name.as_ref()) {
            Err(err) if err == VarError::NotPresent => None,
            Err(err) => Some(Err(err)),
            Ok(val) => Some(Ok(val)),
        }
    }

    pub fn user_config_paths(&self) -> Vec<PathBuf> {
        let config_dir = match self.user.config_user_directory {
            None => match home_dir() {
                None => return Vec::new(),
                Some(hd) => hd,
            },
            Some(subdir) => {
                let config_dir = if cfg!(windows) {
                    std::env::var("APPDATA")
                        .map_or_else(|_| dirs::config_dir(), |x| Some(PathBuf::from(x)))
                } else {
                    dirs::config_dir()
                };
                match config_dir {
                    None => return Vec::new(),
                    Some(config_dir) => config_dir.join(subdir),
                }
            }
        };

        self.user
            .config_user_files
            .iter()
            .map(|f| config_dir.join(f))
            .collect()
    }

    pub fn system_config_path(&self) -> Option<PathBuf> {
        if cfg!(windows) {
            std::env::var("PROGRAMDATA")
                .ok()
                .map(|p| PathBuf::from(p).join(self.user.config_system_path))
        } else {
            Some(self.user.config_system_path.into())
        }
    }

    pub fn punch(&self, tmpl: &str) -> String {
        tmpl.replace("@prog@", self.cli_name())
            .replace("@Product@", self.product_name())
    }
}

fn home_dir() -> Option<PathBuf> {
    if cfg!(windows) {
        // dirs::home_dir doesn't respect USERPROFILE. Check it for
        // compatibility with tests.
        if let Some(user_profile) = std::env::var_os("USERPROFILE") {
            return Some(user_profile.into());
        }
    }

    dirs::home_dir()
}

impl std::fmt::Display for Identity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.user.cli_name)
    }
}

const HG: Identity = Identity {
    user: UserIdentity {
        cli_name: "hg",
        product_name: "Mercurial",
        long_product_name: "Mercurial Distributed SCM",
        env_prefix: "HG",
        config_user_directory: None,
        config_user_files: &[
            ".hgrc",
            #[cfg(windows)]
            "mercurial.ini",
        ],
        #[cfg(windows)]
        config_system_path: r"Facebook\Mercurial\system.rc",
        #[cfg(not(windows))]
        config_system_path: "/etc/mercurial/system.rc",
        scripting_env_var: "HGPLAIN",
        scripting_config_env_var: "HGRCPATH",
        scripting_except_env_var: "HGPLAINEXCEPT",
    },

    repo: RepoIdentity {
        dot_dir: ".hg",
        config_repo_file: "hgrc",
    },
};

const SL: Identity = Identity {
    user: UserIdentity {
        cli_name: "sl",
        product_name: "Sapling",
        long_product_name: "Sapling SCM",
        env_prefix: "SL_",
        config_user_directory: Some("sapling"),
        config_user_files: &["sapling.conf"],
        #[cfg(windows)]
        config_system_path: r"Sapling\system.conf",
        #[cfg(not(windows))]
        config_system_path: "/etc/sapling/system.conf",
        scripting_env_var: "SL_AUTOMATION",
        scripting_config_env_var: "SL_CONFIG_PATH",
        scripting_except_env_var: "SL_AUTOMATION_EXCEPT",
    },

    repo: RepoIdentity {
        dot_dir: ".sl",
        config_repo_file: "config",
    },
};

#[cfg(test)]
const TEST: Identity = Identity {
    user: UserIdentity {
        cli_name: "test",
        product_name: "Test",
        long_product_name: "Testing SCM",
        env_prefix: "TEST",
        config_user_directory: None,
        config_user_files: &["test.conf"],
        config_system_path: "test",
        scripting_env_var: "TEST_SCRIPT",
        scripting_config_env_var: "TEST_RC_PATH",
        scripting_except_env_var: "TEST_SCRIPT_EXCEPT",
    },

    repo: RepoIdentity {
        dot_dir: ".test",
        config_repo_file: "config",
    },
};

#[cfg(all(not(feature = "sl_only"), not(test)))]
mod idents {
    use super::*;

    pub fn all() -> &'static [Identity] {
        &[SL, HG]
    }
}

#[cfg(feature = "sl_only")]
mod idents {
    use super::*;

    pub fn all() -> &'static [Identity] {
        if in_test() { &[SL, HG] } else { &[SL] }
    }
}

#[cfg(test)]
pub mod idents {
    use super::*;

    pub fn all() -> &'static [Identity] {
        &[HG, SL, TEST]
    }
}

static DEFAULT: Lazy<RwLock<Identity>> = Lazy::new(|| RwLock::new(compute_default()));

pub use idents::all;

pub fn default() -> Identity {
    *DEFAULT.read()
}

pub fn reset_default() {
    *DEFAULT.write() = compute_default();
}

/// Default `Identity` based on the current executable name.
fn compute_default() -> Identity {
    let path = std::env::current_exe().expect("current_exe() should not fail");
    let file_name = path
        .file_name()
        .expect("file_name() on current_exe() should not fail");
    let file_name = file_name.to_string_lossy();
    let (ident, reason) = (|| {
        let env_override = all()
            .iter()
            .find_map(|id| id.env_var("IDENTITY"))
            .map(|v| v.ok())
            .flatten();

        for ident in all() {
            if Some(ident.user.cli_name) == env_override.as_deref() {
                return (*ident, "env var");
            }
        }

        for ident in all() {
            if file_name.contains(ident.user.cli_name) {
                return (*ident, "contains");
            }
        }

        // Special case: for fbcode/eden/testlib/ tests the "current_exe"
        // could be "python3.8". Use "hg" to maintain test compatibility.
        // If we updated the tests, the special case can be dropped.
        if file_name.starts_with("python") {
            return (HG, "python");
        }

        // Fallback to SL if current_exe does not provide information.
        (SL, "fallback")
    })();

    tracing::info!(
        identity = ident.user.cli_name,
        argv0 = file_name.as_ref(),
        reason,
        "identity from argv0"
    );

    ident
}

/// CLI name to be used in user facing messaging.
pub fn cli_name() -> &'static str {
    DEFAULT.read().cli_name()
}

/// Sniff the given path for the existence of "{path}/.hg" or
/// "{path}/.sl" directories, yielding the sniffed Identity, if any.
/// Only permissions errors are propagated.
pub fn sniff_dir(path: &Path) -> Result<Option<Identity>> {
    for id in all() {
        let test_path = path.join(id.repo.dot_dir);
        tracing::trace!(path=%path.display(), "sniffing dir");
        match fs::metadata(&test_path) {
            Ok(md) if md.is_dir() => {
                tracing::debug!(id=%id, path=%path.display(), "sniffed repo dir");

                // Combine DEFAULT's user facing attributes w/ id's repo attributes.
                let mut mix = *DEFAULT.read();
                mix.repo = id.repo;

                return Ok(Some(mix));
            }
            Err(err) if err.kind() == io::ErrorKind::PermissionDenied => {
                // Propagate permission error checking dot dir so we
                // don't infer the wrong identity. Ideally this would
                // be an allowlist of errors, but unstable errors like
                // NotADirectory are unmatchable for now.
                return Err::<_, Error>(err.into()).with_context(|| {
                    format!("error sniffing {} for identity", test_path.display())
                });
            }
            _ => {}
        };
    }

    Ok(None)
}

/// Like sniff_dir, but returns an error instead of None.
pub fn must_sniff_dir(path: &Path) -> Result<Identity> {
    sniff_dir(path)?.with_context(|| format!("repo {} missing dot dir", path.display()))
}

/// Recursively sniff path and its ancestors for the first directory
///  containing a ".hg" or ".sl" directory. The ancestor directory and
///  corresponding Identity are returned, if any. Only permission
///  errors are propagated.
pub fn sniff_root(path: &Path) -> Result<Option<(PathBuf, Identity)>> {
    tracing::debug!(start=%path.display(), "sniffing for repo root");

    let mut path = Some(path);

    while let Some(p) = path {
        if let Some(ident) = sniff_dir(p)? {
            return Ok(Some((p.to_path_buf(), ident)));
        }

        path = p.parent();
    }

    Ok(None)
}

pub fn env_var(var_suffix: &str) -> Option<Result<String, VarError>> {
    let current_id = DEFAULT.read();

    // Always prefer current identity.
    if let Some(res) = current_id.env_var(var_suffix) {
        return Some(res);
    }

    // Backwards compat for old env vars.
    for id in all() {
        if let Some(res) = id.env_var(var_suffix) {
            return Some(res);
        }
    }

    None
}

pub fn try_env_var(var_suffix: &str) -> Result<String, VarError> {
    match env_var(var_suffix) {
        Some(result) => result,
        None => Err(VarError::NotPresent),
    }
}

pub fn debug_env_var(name: &str) -> Option<(String, String)> {
    for maybe_name in [format!("SL_{name}"), format!("EDENSCM_{name}")] {
        if let Ok(val) = std::env::var(&maybe_name) {
            return Some((maybe_name, val));
        }
    }

    if name == "LOG" && in_test() {
        std::env::var(name).ok().map(|v| (name.to_string(), v))
    } else {
        None
    }
}

fn in_test() -> bool {
    std::env::var("TESTTMP").is_ok()
}

#[cfg(test)]
mod test {
    use std::fs;

    use super::*;

    #[test]
    fn test_sniff_dir() -> Result<()> {
        let dir = tempfile::tempdir()?;

        assert!(sniff_dir(&dir.path().join("doesn't exist"))?.is_none());

        {
            let root = dir.path().join("default");
            fs::create_dir_all(root.join(default().dot_dir()))?;

            assert_eq!(sniff_dir(&root)?.unwrap(), default());
        }

        {
            let root = dir.path().join("test1");
            fs::create_dir_all(root.join(TEST.dot_dir()))?;

            let sniffed = sniff_dir(&root)?.unwrap();
            assert_eq!(sniffed.repo, TEST.repo);
            assert_eq!(sniffed.user, default().user);
        }

        // Make sure we don't error out on bundle file (e.g. "hg -R some_bundle ...").
        {
            let bundle = dir.path().join("foo/bundle.hg");
            fs::create_dir_all(bundle.parent().unwrap())?;
            let _ = fs::File::create(&bundle).unwrap();
            assert!(sniff_dir(&bundle)?.is_none());
        }

        #[cfg(unix)]
        {
            let root = dir.path().join("bad_perms");
            let dot_dir = root.join(default().dot_dir());
            fs::create_dir_all(&dot_dir)?;

            // Sanity.
            assert!(sniff_dir(&root).is_ok());

            let perm = std::os::unix::fs::PermissionsExt::from_mode(0o0);
            fs::File::open(&root)?.set_permissions(perm)?;

            // Make sure we error out if we can't read the dot dir.
            assert!(sniff_dir(&root).is_err());
        }

        Ok(())
    }

    #[test]
    fn test_sniff_root() -> Result<()> {
        let dir = tempfile::tempdir()?;

        let root = dir.path().join("root");

        assert!(sniff_root(&root)?.is_none());

        let dot_dir = root.join(TEST.dot_dir());
        fs::create_dir_all(&dot_dir)?;

        let (sniffed_root, sniffed_ident) = sniff_root(&root)?.unwrap();
        assert_eq!(sniffed_root, root);
        assert_eq!(sniffed_ident.repo, TEST.repo);
        assert_eq!(sniffed_ident.user, default().user);

        let abc = root.join("a/b/c");
        fs::create_dir_all(&abc)?;

        let (sniffed_root, sniffed_ident) = sniff_root(&root)?.unwrap();
        assert_eq!(sniffed_root, root);
        assert_eq!(sniffed_ident.repo, TEST.repo);
        assert_eq!(sniffed_ident.user, default().user);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::sync::Arc;

use indexedlog::index::InsertKey;
use indexedlog::index::InsertValue;
use indexedlog::index::OpenOptions;
use minibench::bench;
use minibench::elapsed;
use minibench::measure;
use minibench::Measure;
use rand_chacha::rand_core::RngCore;
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaChaRng;
use tempfile::tempdir;

const N: usize = 204800;

/// Generate random buffer
fn gen_buf(size: usize) -> Vec<u8> {
    let mut buf = vec![0u8; size];
    ChaChaRng::seed_from_u64(0).fill_bytes(buf.as_mut());
    buf
}

/// Default open options: 4K checksum chunk
fn open_opts() -> OpenOptions {
    let mut open_opts = OpenOptions::new();
    open_opts.checksum_chunk_size_logarithm(12);
    open_opts
}

fn main() {
    bench("index insertion (owned key)", || {
        let dir = tempdir().unwrap();
        let mut idx = open_opts().open(dir.path().join("i")).expect("open");
        let buf = gen_buf(N * 20);
        elapsed(move || {
            for i in 0..N {
                idx.insert(&&buf[20 * i..20 * (i + 1)], i as u64)
                    .expect("insert");
            }
        })
    });

    bench("index insertion (referred key)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut idx = open_opts()
            .key_buf(Some(Arc::new(buf.clone())))
            .open(dir.path().join("i"))
            .expect("open");
        elapsed(move || {
            for i in 0..N {
                idx.insert(&&buf[20 * i..20 * (i + 1)], i as u64)
                    .expect("insert");
            }
        })
    });

    bench("index flush", || {
        let dir = tempdir().unwrap();
        let mut idx = open_opts().open(dir.path().join("i")).expect("open");
        let buf = gen_buf(N * 20);
        for i in 0..N {
            idx.insert(&&buf[20 * i..20 * (i + 1)], i as u64)
                .expect("insert");
        }
        elapsed(|| {
            idx.flush().expect("flush");
        })
    });

    {
        let dir = tempdir().unwrap();
        let mut idx = open_opts().open(dir.path().join("i")).expect("open");
        let buf = gen_buf(N * 20);
        for i in 0..N {
            idx.insert(&&buf[20 * i..20 * (i + 1)], i as u64)
                .expect("insert");
        }

        bench("index lookup (memory)", || {
            elapsed(|| {
                for i in 0..N {
                    idx.get(&&buf[20 * i..20 * (i + 1)]).expect("lookup");
                }
            })
        });

        bench("index prefix scan (2B)", || {
            elapsed(|| {
                for _ in 0..(N / 3) {
                    idx.scan_prefix([0x33, 0x33]).unwrap().count();
                }
            })
        });

        bench("index prefix scan (1B)", || {
            elapsed(|| {
                for _ in 0..(N / 807) {
                    idx.scan_prefix([0x33]).unwrap().count();
                }
            })
        });
    }

    {
        let dir = tempdir().unwrap();
        let mut idx = open_opts()
            .checksum_enabled(false)
            .open(dir.path().join("i"))
            .expect("open");
        let buf = gen_buf(N * 20);
        for i in 0..N {
            idx.insert(&&buf[20 * i..20 * (i + 1)], i as u64)
                .expect("insert");
        }
        idx.flush().expect("flush");

        bench("index lookup (disk, no verify)", || {
            elapsed(|| {
                for i in 0..N {
                    idx.get(&&buf[20 * i..20 * (i + 1)]).expect("lookup");
                }
            })
        });

        bench("index prefix scan (2B, disk)", || {
            elapsed(|| {
                for _ in 0..(N / 3) {
                    idx.scan_prefix([0x33, 0x33]).unwrap().count();
                }
            })
        });

        bench("index prefix scan (1B, disk)", || {
            elapsed(|| {
                for _ in 0..(N / 807) {
                    idx.scan_prefix([0x33]).unwrap().count();
                }
            })
        });
    }

    bench("index lookup (disk, verified)", || {
        let dir = tempdir().unwrap();
        let mut idx = open_opts().open(dir.path().join("i")).expect("open");
        let buf = gen_buf(N * 20);
        for i in 0..N {
            idx.insert(&&buf[20 * i..20 * (i + 1)], i as u64)
                .expect("insert");
        }
        idx.flush().expect("flush");
        elapsed(move || {
            for i in 0..N {
                idx.get(&&buf[20 * i..20 * (i + 1)]).expect("lookup");
            }
        })
    });

    bench("index size (5M owned keys)", || {
        const N: usize = 5000000;
        let dir = tempdir().unwrap();
        let mut idx = open_opts().open(dir.path().join("i")).expect("open");
        let buf = gen_buf(N * 20);
        for i in 0..N {
            idx.insert(&&buf[20 * i..20 * (i + 1)], i as u64)
                .expect("insert");
        }
        measure::Bytes::measure(|| idx.flush().unwrap())
    });

    bench("index size (5M referred keys)", || {
        const N: usize = 5000000;
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut idx = open_opts()
            .key_buf(Some(Arc::new(buf.clone())))
            .open(dir.path().join("i"))
            .expect("open");
        for i in 0..N {
            let ext_key = InsertKey::Reference((i as u64 * 20, 20));
            idx.insert_advanced(ext_key, InsertValue::Prepend(i as u64))
                .expect("insert");
        }
        measure::Bytes::measure(|| idx.flush().unwrap())
    });
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::path::Path;

use indexedlog::log::IndexDef;
use indexedlog::log::IndexOutput;
use indexedlog::log::Log;
use minibench::bench;
use minibench::elapsed;
use minibench::measure;
use minibench::Measure;
use rand_chacha::rand_core::RngCore;
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaChaRng;
use tempfile::tempdir;

const N: usize = 204800;

/// Generate random buffer
fn gen_buf(size: usize) -> Vec<u8> {
    let mut buf = vec![0u8; size];
    ChaChaRng::seed_from_u64(0).fill_bytes(buf.as_mut());
    buf
}

/// Get a `Log` with index defined.
fn log_with_index(path: &Path, lag: u64) -> Log {
    let index_func = |_data: &[u8]| vec![IndexOutput::Reference(0..20)];
    let index_def = IndexDef::new("i", index_func).lag_threshold(lag);
    Log::open(path, vec![index_def]).unwrap()
}

/// Measure both elapsed and IO.
type MeasureSync = measure::Both<measure::WallClock, measure::IO>;

fn main() {
    bench("log insertion", || {
        let dir = tempdir().unwrap();
        let mut log = Log::open(dir.path(), vec![]).unwrap();
        let buf = gen_buf(N * 20);
        elapsed(move || {
            for i in 0..N {
                log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
            }
        })
    });

    bench("log insertion with index", || {
        let dir = tempdir().unwrap();
        let mut log = log_with_index(dir.path(), 0);
        let buf = gen_buf(N * 20);
        elapsed(move || {
            for i in 0..N {
                log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
            }
        })
    });

    bench("log sync (write N, no index)", || {
        let dir = tempdir().unwrap();
        let mut log = Log::open(dir.path(), vec![]).unwrap();
        let buf = gen_buf(N * 20);
        for i in 0..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        MeasureSync::measure(|| {
            log.sync().unwrap();
        })
    });

    bench("log sync (write N)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), 0);
        // Write one entry to make things more interesting.
        log.append(&buf[0..20]).unwrap();
        log.sync().unwrap();
        for i in 1..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        MeasureSync::measure(|| {
            log.sync().unwrap();
        })
    });

    bench("log sync (write 1, update index)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), 0);
        log.append(&buf[0..20]).unwrap();
        log.sync().unwrap();
        log.append(&buf[20..40]).unwrap();
        MeasureSync::measure(|| {
            log.sync().unwrap();
        })
    });

    bench("log sync (write 1, not update index)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), u64::max_value());
        log.append(&buf[0..20]).unwrap();
        log.sync().unwrap();
        log.append(&buf[20..40]).unwrap();
        MeasureSync::measure(|| {
            log.sync().unwrap();
        })
    });

    bench("log sync (read, no-op)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), 0);
        for i in 0..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        log.sync().unwrap();
        let mut log = log_with_index(dir.path(), 0);
        MeasureSync::measure(|| {
            log.sync().unwrap();
        })
    });

    bench("log sync (read, new log 0, index lag N)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), u64::max_value());
        for i in 0..(N / 2) {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        log.sync().unwrap();
        MeasureSync::measure(|| {
            log.sync().unwrap();
        })
    });

    bench("log sync (read, new log 1, index lag N)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), u64::max_value());
        for i in 1..(N / 2) {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        log.sync().unwrap();
        let mut log2 = log_with_index(dir.path(), u64::max_value());
        log2.append(&buf[0..20]).unwrap();
        log2.sync().unwrap();
        MeasureSync::measure(|| {
            log.sync().unwrap();
        })
    });

    bench("log sync (read, new log N, index lag 0)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), 0);
        let mut log2 = log_with_index(dir.path(), 0);
        for i in 0..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        log.sync().unwrap();
        MeasureSync::measure(|| {
            log2.sync().unwrap();
        })
    });

    bench("log sync (read, new log N, index lag N)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), u64::max_value());
        let mut log2 = log_with_index(dir.path(), u64::max_value());
        for i in 0..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        log.sync().unwrap();
        MeasureSync::measure(|| {
            log2.sync().unwrap();
        })
    });

    bench("log sync (read 1, write N)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), 0);
        let mut log2 = log_with_index(dir.path(), 0);
        log.append(&buf[0..20]).unwrap();
        log.sync().unwrap();
        log2.append(&buf[20..40]).unwrap();
        for i in 2..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        log2.sync().unwrap();
        MeasureSync::measure(|| {
            log.sync().unwrap();
        })
    });

    bench("log sync (read N, write 1)", || {
        let dir = tempdir().unwrap();
        let buf = gen_buf(N * 20);
        let mut log = log_with_index(dir.path(), 0);
        let mut log2 = log_with_index(dir.path(), 0);
        log.append(&buf[0..20]).unwrap();
        log.sync().unwrap();
        log2.append(&buf[20..40]).unwrap();
        for i in 2..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        log.sync().unwrap();
        MeasureSync::measure(|| {
            log2.sync().unwrap();
        })
    });

    bench("log iteration (memory)", || {
        let dir = tempdir().unwrap();
        let mut log = Log::open(dir.path(), vec![]).unwrap();
        let buf = gen_buf(N * 20);
        for i in 0..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        elapsed(move || {
            log.iter().count();
        })
    });

    bench("log iteration (disk)", || {
        let dir = tempdir().unwrap();
        let mut log = Log::open(dir.path(), vec![]).unwrap();
        let buf = gen_buf(N * 20);
        for i in 0..N {
            log.append(&buf[20 * i..20 * (i + 1)]).unwrap();
        }
        log.sync().unwrap();
        let log = Log::open(dir.path(), vec![]).unwrap();
        elapsed(move || {
            log.iter().count();
        })
    });
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use indexedlog::base16::Base16Iter;
use indexedlog::lock::ScopedFileLock;
use indexedlog::utils::open_dir;
use minibench::bench;
use minibench::elapsed;
use tempfile::tempdir;

fn main() {
    bench("base16 iterating 1M bytes", || {
        let x = vec![4u8; 1000000];
        elapsed(|| {
            let y: u8 = Base16Iter::from_base256(&x).sum();
            assert_eq!(y, (4 * 1000000) as u8);
        })
    });

    bench("lock a directory", || {
        let dir = tempdir().unwrap();
        let mut file = open_dir(dir.path()).unwrap();
        elapsed(|| {
            let _lock = ScopedFileLock::new(&mut file, true).unwrap();
        })
    });

    bench("open and lock a directory", || {
        let dir = tempdir().unwrap();
        elapsed(|| {
            let mut file = open_dir(dir.path()).unwrap();
            let _lock = ScopedFileLock::new(&mut file, true).unwrap();
        })
    });
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! Base16 iterator.

/// Iterating through base16 bytes (0 to 15).
#[derive(Debug, Copy, Clone)]
pub struct Base16Iter<'a, T: 'a>(&'a T, usize, usize);

impl<'a, T: AsRef<[u8]>> Base16Iter<'a, T> {
    /// Convert base256 binary sequence to a base16 iterator.
    pub fn from_base256(binary: &'a T) -> Self {
        let len = binary.as_ref().len() * 2;
        Base16Iter(binary, 0, len)
    }
}

/// Base16 iterator for `[u8]`
impl<'a, T: AsRef<[u8]>> Iterator for Base16Iter<'a, T> {
    type Item = u8;

    #[inline]
    fn next(&mut self) -> Option<u8> {
        if self.2 <= self.1 {
            None
        } else {
            let i = self.1;
            self.1 = i + 1;
            let v = self.0.as_ref()[i / 2];
            if i & 1 == 0 { v >> 4 } else { v & 0xf }.into()
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        let len = self.len();
        (len, Some(len))
    }

    #[inline]
    fn count(self) -> usize {
        self.len()
    }
}

impl<'a, T: AsRef<[u8]>> DoubleEndedIterator for Base16Iter<'a, T> {
    #[inline]
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.2 <= self.1 {
            None
        } else {
            let i = self.2 - 1;
            self.2 = i;
            let v = self.0.as_ref()[i / 2];
            if i & 1 == 0 { v >> 4 } else { v & 0xf }.into()
        }
    }
}

impl<'a, T: AsRef<[u8]>> ExactSizeIterator for Base16Iter<'a, T> {
    #[inline]
    fn len(&self) -> usize {
        self.2 - self.1
    }
}

impl<'a, T: AsRef<[u8]>> Base16Iter<'a, T> {
    #[inline]
    pub fn skip(self, n: usize) -> Self {
        Base16Iter(self.0, self.1 + n, self.2)
    }

    #[inline]
    pub fn take(self, n: usize) -> Self {
        let end = self.2.min(self.1 + n);
        Base16Iter(self.0, self.1, end)
    }
}

/// Convert base16 to base256. base16 must have 2 * N items.
///
/// Panic if base16 has 2 * N + 1 items.
pub(crate) fn base16_to_base256(base16: &[u8]) -> Vec<u8> {
    assert!(base16.len() & 1 == 0);
    let mut bytes = Vec::with_capacity(base16.len() / 2);
    let mut next_byte: u8 = 0;
    for (i, b16) in base16.iter().cloned().enumerate() {
        if i & 1 == 0 {
            next_byte = b16 << 4;
        } else {
            bytes.push(next_byte | b16);
        }
    }
    bytes
}

/// Convert a single hex digit to base16 value.
///
/// Return 16 if the digit is invalid.
#[inline]
pub(crate) fn single_hex_to_base16(ch: u8) -> u8 {
    // Does not depend on ASCII order (ex. b'1' - b'0' == 1).
    // Compiler can turn this into a lookup table. It is fast when cached.
    match ch {
        b'0' => 0,
        b'1' => 1,
        b'2' => 2,
        b'3' => 3,
        b'4' => 4,
        b'5' => 5,
        b'6' => 6,
        b'7' => 7,
        b'8' => 8,
        b'9' => 9,
        b'a' | b'A' => 10,
        b'b' | b'B' => 11,
        b'c' | b'C' => 12,
        b'd' | b'D' => 13,
        b'e' | b'E' => 14,
        b'f' | b'F' => 15,
        _ => 16,
    }
}

#[cfg(test)]
mod tests {
    use quickcheck::quickcheck;

    use super::*;

    quickcheck! {
        fn check_skip_rev(src: Vec<u8>) -> bool {
            let iter = Base16Iter::from_base256(&src);
            let full: Vec<u8> = iter.clone().collect();
            let rev: Vec<u8> = iter.clone().rev().collect();
            (0..full.len()).all(|i| {
                let v: Vec<u8> = iter.clone().skip(i).collect();
                let r: Vec<u8> = iter.clone().skip(i).rev().rev().rev().collect();
                v[..] == full[i..] && r[..] == rev[..(rev.len() - i)]
            })
        }

        fn check_roundtrip(src: Vec<u8>) -> bool {
            let iter = Base16Iter::from_base256(&src);
            base16_to_base256(&iter.collect::<Vec<u8>>()) == src
        }
    }

    // The below patterns (skip, zip, rev; skip, take, rev) are used in radix.rs.
    // Make sure they work at iterator level without needing an extra container.

    #[test]
    fn test_zip_skip_rev() {
        let x = [0x12, 0x34, 0x56, 0x21u8];
        let y = [0x78, 0x90, 0xab, 0xcdu8];
        let i = Base16Iter::from_base256(&x)
            .skip(2)
            .zip(Base16Iter::from_base256(&y).skip(3))
            .rev(); // .rev() works directly
        let v: Vec<(u8, u8)> = i.collect();
        assert_eq!(v.capacity(), v.len());
        assert_eq!(v, vec![(2, 0xd), (6, 0xc), (5, 0xb), (4, 0xa), (3, 0)]);
    }

    #[test]
    fn test_skip_take_rev() {
        let x = [0x12, 0x34, 0x56u8];
        let i = Base16Iter::from_base256(&x).skip(3).take(3).rev(); // .rev() works directly
        let v: Vec<u8> = i.collect();
        assert_eq!(v, vec![6, 5, 4]);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! Default configs for indexed log.

use std::sync::atomic;
use std::sync::atomic::AtomicI64;
use std::sync::atomic::AtomicU32;

/// If set to true, prefer symlinks to normal files for atomic_write. This avoids
/// states where the metadata file is empty in theory.
///
/// Be careful with cases like mixing using ntfs-3g and Windows NTFS on files - they
/// might use different forms of symlink and are incompatible with this feature.
pub static SYMLINK_ATOMIC_WRITE: atomic::AtomicBool = atomic::AtomicBool::new(cfg!(test));

/// If set to true, enable fsync for writing.
static ENFORCE_FSYNC: atomic::AtomicBool = atomic::AtomicBool::new(false);

/// Default chmod mode for directories.
/// u: rwx g:rws o:r-x
pub static CHMOD_DIR: AtomicI64 = AtomicI64::new(0o2775);

// XXX: This works around https://github.com/Stebalien/tempfile/pull/61.
/// Default chmod mode for atomic_write files.
pub static CHMOD_FILE: AtomicI64 = AtomicI64::new(0o664);

/// Default maximum chain length for index. See `index::OpenOptions::checksum_max_chain_len`.
pub static INDEX_CHECKSUM_MAX_CHAIN_LEN: AtomicU32 = AtomicU32::new(10);

/// Set whether to fsync globally. fsync will be performed if either the local
/// or global fsync flag is set.
pub fn set_global_fsync(flag: bool) {
    ENFORCE_FSYNC.store(flag, atomic::Ordering::Release);
}

/// Get the fsync flag set by `set_global_fsync`.
pub fn get_global_fsync() -> bool {
    ENFORCE_FSYNC.load(atomic::Ordering::Acquire)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! Errors used by the crate
//!
//! See [`Error`] for the main type.

use std::fmt;
use std::io;
use std::path::Path;

// Error design goals:
// - Callsites can test whether an error is caused by data corruption or other
//   issues (for example, permission or resource issues).
//   This is important because it allows callsites (including within the
//   crate like RotateLog) to decide whether to remove the bad data and
//   try auto recovery.
// - The library can change error structure internals. That means accesses
//   to the error object are via public methods instead of struct or enum
//   fields. `Error` is the only opaque public error type.
// - Compatible with std Error. Therefore anyhow::Error is supported too.

/// Represents all possible errors that can occur when using indexedlog.
pub struct Error {
    // Boxing makes `Result<Error, _>` smaller.
    inner: Box<Inner>,
}

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Default)]
struct Inner {
    sources: Vec<Box<dyn std::error::Error + Send + Sync + 'static>>,
    messages: Vec<String>,
    is_corruption: bool,
    io_error_kind: Option<io::ErrorKind>,
}

impl Error {
    /// Return `true` if the error is considered as (filesystem) data
    /// corruption.
    ///
    /// Application can use this information to decide whether to try the
    /// expensive `repair` API.
    ///
    /// Data corruption is in general when the data can be confidently read,
    /// but does not meet integrity expectation. Note: a "file not found" error
    /// is a piece of information that is "confidently read", because it's
    /// unexpected to get a different error by using different users, or
    /// process settings. So "file not found" is not always a corruption.
    ///
    /// For example, those are data corruption errors:
    /// - XxHash checksum does not match the data
    /// - Some data says "read this file in 100..200 byte range", but logically,
    ///   the file only has 150 bytes.
    /// - A byte is expected to only be 1 or 2. But it turns out to be 0.
    /// - Both file "a" and "b" are expected to exist in a directory, but "a"
    ///   can be found, while "b" does not exist.
    ///
    /// Those are not data corruption:
    /// - Cannot open a file due to permission issues or exceeding the file
    ///   descriptor limit.
    /// - Programming errors and API misuse. For example, a user-provided
    ///   index function says `data[5..10] is the index key` while `data`
    /// - Both file "a" and "b" are expected to exist in a directory, but "a"
    ///   can be found, while "b" cannot be opened due to permission issues.
    ///
    /// It's expected that data corruption can *only* happen when the managed
    /// files or directories are changed without using this crate.  For example,
    /// a hard reboot, deleting lock files while multiple processes are
    /// attempting to write, deleting or changing files in some ways. Issues
    /// like "disk is full", "permission errors", "process killed at random
    /// time" are expected to not cause data corruption (but only data loss).
    pub fn is_corruption(&self) -> bool {
        self.inner.is_corruption
    }

    pub fn io_error_kind(&self) -> io::ErrorKind {
        self.inner.io_error_kind.unwrap_or(io::ErrorKind::Other)
    }

    // Following methods are used by this crate only.
    // External code should not construct or modify `Error`.

    pub(crate) fn message(mut self, message: impl ToString) -> Self {
        self.inner.messages.push(message.to_string());
        self
    }

    pub(crate) fn source(self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
        self.source_dyn(Box::new(source))
    }

    fn source_dyn(mut self, source: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
        // Inherit the data corruption flag.
        if let Some(err) = source.downcast_ref::<Error>() {
            if err.is_corruption() {
                self = self.mark_corruption();
            }
        }

        self.inner.sources.push(source);
        self
    }

    pub(crate) fn mark_corruption(mut self) -> Self {
        self.inner.is_corruption = true;
        self
    }

    pub(crate) fn blank() -> Self {
        Error {
            inner: Default::default(),
        }
    }

    /// A ProgrammingError that breaks some internal assumptions.
    /// For example, passing an invalid parameter to an API.
    #[inline(never)]
    pub(crate) fn programming(message: impl ToString) -> Self {
        Self::blank().message(format!("ProgrammingError: {}", message.to_string()))
    }

    /// A data corruption error with path.
    ///
    /// If there is an [`IOError`], use [`IoResultExt::context`] instead.
    #[inline(never)]
    pub(crate) fn corruption(path: &Path, message: impl ToString) -> Self {
        let message = format!("{:?}: {}", path, message.to_string());
        Self::blank().mark_corruption().message(message)
    }

    /// An error with a path that is not a data corruption.
    ///
    /// If there is an [`IOError`], use [`IoResultExt::context`] instead.
    #[inline(never)]
    pub(crate) fn path(path: &Path, message: impl ToString) -> Self {
        let message = format!("{:?}: {}", path, message.to_string());
        Self::blank().message(message)
    }

    /// Wrap a dynamic stdlib error.
    #[inline(never)]
    pub(crate) fn wrap(
        err: Box<dyn std::error::Error + Send + Sync + 'static>,
        message: impl LazyToString,
    ) -> Self {
        Self::blank()
            .message(message.to_string_costly())
            .source_dyn(err)
    }
}

/// Create an Error from a message.
impl From<&str> for Error {
    fn from(s: &str) -> Self {
        Self::blank().message(s)
    }
}

/// Create an Error from a message and another error.
impl<E: std::error::Error + Send + Sync + 'static> From<(&str, E)> for Error {
    fn from(s: (&str, E)) -> Self {
        Self::blank().message(s.0).source(s.1)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut lines = Vec::new();
        for message in &self.inner.messages {
            lines.push(message.to_string());
        }
        if !self.inner.sources.is_empty() {
            lines.push(format!("Caused by {} errors:", self.inner.sources.len()));
            for source in &self.inner.sources {
                lines.push(indent(format!("{}", source), 2, '-'));
            }
        }
        write!(f, "{}", lines.join("\n"))
    }
}

impl fmt::Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut lines = Vec::new();
        for message in &self.inner.messages {
            lines.push(message.to_string());
        }
        if self.is_corruption() {
            lines.push("(This error is considered as a data corruption)".to_string())
        }
        if !self.inner.sources.is_empty() {
            lines.push(format!("Caused by {} errors:", self.inner.sources.len()));
            for source in &self.inner.sources {
                lines.push(indent(format!("{:?}", source), 2, '-'));
            }
        }
        write!(f, "{}", lines.join("\n"))
    }
}

fn indent(s: String, spaces: usize, first_line_prefix: char) -> String {
    if spaces == 0 {
        s
    } else {
        format!(
            "{}{}{}",
            first_line_prefix,
            " ".repeat(spaces - 1),
            s.lines()
                .collect::<Vec<_>>()
                .join(&format!("\n{}", " ".repeat(spaces)))
        )
    }
}

pub(crate) trait ResultExt<T> {
    /// Mark the error as data corruption.
    fn corruption(self) -> Self;

    /// Add a string message as context.
    fn context<S: LazyToString>(self, message: S) -> Self;

    /// Add an error source.
    fn source<E: std::error::Error + Send + Sync + 'static>(self, source: E) -> Self;
}

impl<T> ResultExt<T> for Result<T> {
    fn corruption(self) -> Self {
        self.map_err(|err| err.mark_corruption())
    }

    fn context<S: LazyToString>(self, message: S) -> Self {
        self.map_err(|err| err.message(message.to_string_costly()))
    }

    fn source<E: std::error::Error + Send + Sync + 'static>(self, source: E) -> Self {
        self.map_err(|err| err.source(source))
    }
}

impl std::error::Error for Error {
    // This 'Error' type is designed to be opaque (internal states are
    // private, including inner errors), and takes responsibility
    // of displaying a -chain- tree of errors. So it might be desirable
    // not implementing `source` here, and expose public APIs for all
    // use-needs.
}

pub(crate) trait IoResultExt<T> {
    /// Wrap [`io::Result`] in [`Result`] with extra context about filesystem
    /// path and the operation name.
    ///
    /// Mark InvalidData and UnexpectedEof as data corruption automatically.
    ///
    /// Consider using [`ResultExt::corruption`] to mark the error as data
    /// corruption if appropriate.
    fn context<TS: LazyToString>(self, path: &Path, message: TS) -> Result<T>;

    /// Wrap an infallible Result. For example, writing to memory.
    fn infallible(self) -> Result<T>;
}

impl<T> IoResultExt<T> for std::io::Result<T> {
    fn context<TS: LazyToString>(self, path: &Path, message: TS) -> Result<T> {
        self.map_err(|err| {
            use std::io::ErrorKind;
            let kind = err.kind().clone();
            let corruption = match kind {
                // For example, try to mmap 200 bytes, but the file
                // only has 100 bytes. This is unlikely caused by
                // non-data-corruption issues.
                ErrorKind::UnexpectedEof | ErrorKind::InvalidData => true,
                _ => false,
            };
            let is_eperm = kind == ErrorKind::PermissionDenied;

            let mut err = Error::blank().source(err).message(format!(
                "{:?}: {}",
                path,
                message.to_string_costly()
            ));
            if corruption {
                err = err.mark_corruption();
            }
            err.inner.io_error_kind = Some(kind);

            // Provide more context for PermissionDenied
            if is_eperm {
                #[cfg(unix)]
                #[allow(clippy::redundant_closure_call)]
                {
                    let add_stat_context = |path: &Path, err: Error| {
                        use std::os::unix::fs::MetadataExt;
                        if let Ok(meta) = path.metadata() {
                            let msg = format!(
                                "stat({:?}) = dev:{} ino:{} mode:0o{:o} uid:{} gid:{} mtime:{}",
                                path,
                                meta.dev(),
                                meta.ino(),
                                meta.mode(),
                                meta.uid(),
                                meta.gid(),
                                meta.mtime()
                            );
                            err.message(msg)
                        } else {
                            err
                        }
                    };
                    err = add_stat_context(path, err);
                    // For EPERM on mkdir, parent directory stat is useful.
                    if let Some(parent) = path.parent() {
                        err = add_stat_context(&parent, err);
                    }
                }
            }

            err
        })
    }

    fn infallible(self) -> Result<T> {
        self.map_err(|err| Error::blank().source(err).message("Unexpected failure"))
    }
}

pub(crate) trait LazyToString {
    fn to_string_costly(&self) -> String;
}

// &'static str is cheap.
impl LazyToString for &'static str {
    fn to_string_costly(&self) -> String {
        (*self).to_string()
    }
}

impl<F: Fn() -> String> LazyToString for F {
    fn to_string_costly(&self) -> String {
        self()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_format() {
        let mut e = Error::blank();

        assert_eq!(format!("{}", &e), "");

        // Attach messages.

        e = e.message("Error Message 1");
        e = e.message("Error Message 2");
        assert_eq!(
            format!("{}", &e),
            r#"Error Message 1
Error Message 2"#
        );

        // Attach error sources.
        e = e.source(Error::blank().message("Inner Error 1"));
        e = e.source(
            Error::blank()
                .message("Inner Error 2")
                .source(Error::blank().message("Nested Error 1")),
        );
        assert_eq!(
            format!("{}", &e),
            r#"Error Message 1
Error Message 2
Caused by 2 errors:
- Inner Error 1
- Inner Error 2
  Caused by 1 errors:
  - Nested Error 1"#
        );

        // Mark as data corruption.
        e = e.mark_corruption();
        assert_eq!(
            format!("{:?}", &e),
            r#"Error Message 1
Error Message 2
(This error is considered as a data corruption)
Caused by 2 errors:
- Inner Error 1
- Inner Error 2
  Caused by 1 errors:
  - Nested Error 1"#
        );
    }

    #[test]
    fn test_result_ext() {
        let result: Result<()> = Err(Error::blank()).corruption();
        assert!(result.unwrap_err().is_corruption());
    }

    #[test]
    fn test_inherit_corruption() {
        assert!(!Error::blank().is_corruption());
        assert!(!Error::blank().source(Error::blank()).is_corruption());
        assert!(
            Error::blank()
                .source(Error::blank().mark_corruption())
                .is_corruption()
        );
        assert!(
            Error::blank()
                .source(Error::blank().source(Error::blank().mark_corruption()))
                .is_corruption()
        );
    }

    #[test]
    fn test_io_result_ext() {
        let err = io_result().context(Path::new("a.txt"), "cannot open for reading");
        assert_eq!(
            format!("{}", err.unwrap_err()),
            r#""a.txt": cannot open for reading
Caused by 1 errors:
- io::Error: something wrong happened"#
        );

        let name = "b.txt";
        let err = io_result().context(Path::new(&name), || format!("cannot open {}", &name));
        assert_eq!(
            format!("{}", err.unwrap_err()),
            r#""b.txt": cannot open b.txt
Caused by 1 errors:
- io::Error: something wrong happened"#
        );
    }

    fn io_result() -> std::io::Result<()> {
        Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            "io::Error: something wrong happened",
        ))
    }
}