#[test]
fn component_is_windows_device() {
    for device in ["con", "CONIN$", "lpt1.txt", "AUX", "Prn", "NUL", "COM9", "nul.a.b "] {
        assert!(gix_validate::path::component_is_windows_device(device.into()));
    }
    for not_device in ["coni", "CONIN", "lpt", "AUXi", "aPrn", "NULl", "COM", "a.nul.b "] {
        assert!(!gix_validate::path::component_is_windows_device(not_device.into()));
    }
}

mod component {
    use gix_validate::path::component;

    const NO_OPTS: component::Options = component::Options {
        protect_windows: false,
        protect_hfs: false,
        protect_ntfs: false,
    };
    const ALL_OPTS: component::Options = component::Options {
        protect_windows: true,
        protect_hfs: true,
        protect_ntfs: true,
    };

    mod valid {
        use bstr::ByteSlice;
        use gix_validate::path::{component, component::Mode::Symlink};

        use crate::path::component::{ALL_OPTS, NO_OPTS};
        macro_rules! mktest {
            ($name:ident, $input:expr) => {
                mktest!($name, $input, ALL_OPTS);
            };
            ($name:ident, $input:expr, $opts:expr) => {
                #[test]
                fn $name() {
                    assert!(gix_validate::path::component($input.as_bstr(), None, $opts).is_ok())
                }
            };
            ($name:ident, $input:expr, $mode:expr, $opts:expr) => {
                #[test]
                fn $name() {
                    assert!(gix_validate::path::component($input.as_bstr(), Some($mode), $opts).is_ok())
                }
            };
        }

        const UNIX_OPTS: component::Options = component::Options {
            protect_windows: false,
            protect_hfs: true,
            protect_ntfs: true,
        };

        mktest!(ascii, b"ascii-only_and-that");
        mktest!(unicode, "😁👍👌".as_bytes());
        mktest!(backslashes_on_unix, br"\", UNIX_OPTS);
        mktest!(drive_letters_on_unix, b"c:", UNIX_OPTS);
        mktest!(virtual_drive_letters_on_unix, "֍:".as_bytes(), UNIX_OPTS);
        mktest!(unc_path_on_unix, br"\\?\pictures", UNIX_OPTS);
        mktest!(not_dot_git_longer, b".gitu", NO_OPTS);
        mktest!(not_dot_git_longer_all, b".gitu");
        mktest!(not_dot_gitmodules_shorter, b".gitmodule", Symlink, NO_OPTS);
        mktest!(not_dot_gitmodules_shorter_all, b".gitmodule", Symlink, ALL_OPTS);
        mktest!(not_dot_gitmodules_longer, b".gitmodulesa", Symlink, NO_OPTS);
        mktest!(not_dot_gitmodules_longer_all, b".gitmodulesa", Symlink, ALL_OPTS);
        mktest!(dot_gitmodules_as_file, b".gitmodules", UNIX_OPTS);
        mktest!(
            starts_with_dot_git_with_backslashes_on_linux,
            br".git\hooks\pre-commit",
            UNIX_OPTS
        );
        mktest!(not_dot_git_shorter, b".gi", NO_OPTS);
        mktest!(not_dot_git_shorter_ntfs_8_3, b"gi~1");
        mktest!(not_dot_git_longer_ntfs_8_3, b"gitu~1");
        mktest!(not_dot_git_shorter_ntfs_8_3_disabled, b"git~1", NO_OPTS);
        mktest!(not_dot_git_longer_hfs, ".g\u{200c}itu".as_bytes());
        mktest!(not_dot_git_shorter_hfs, ".g\u{200c}i".as_bytes());
        mktest!(com_0_lower, b"com0");
        mktest!(com_without_number_0_lower, b"comm");
        mktest!(conout_without_dollar_with_extension, b"conout.c");
        mktest!(conin_without_dollar_with_extension, b"conin.c");
        mktest!(conin_without_dollar, b"conin");
        mktest!(not_con, b"com");
        mktest!(also_not_con, b"co");
        mktest!(con_as_middle, b"x.CON.zip");
        mktest!(con_after_space, b" CON");
        mktest!(con_after_space_mixed, b" coN.tar.xz");
        mktest!(not_nul, b"null");
        mktest!(
            not_dot_gitmodules_shorter_hfs,
            ".gitm\u{200c}odule".as_bytes(),
            Symlink,
            UNIX_OPTS
        );
        mktest!(dot_gitmodules_as_file_hfs, ".g\u{200c}itmodules".as_bytes(), UNIX_OPTS);
        mktest!(dot_gitmodules_ntfs_8_3_disabled, b"gItMOD~1", Symlink, NO_OPTS);
        mktest!(
            not_dot_gitmodules_longer_hfs,
            "\u{200c}.gitmodulesa".as_bytes(),
            Symlink,
            UNIX_OPTS
        );
    }

    mod invalid {
        use bstr::ByteSlice;
        use gix_validate::path::component::{Error, Mode::Symlink};

        use crate::path::component::{ALL_OPTS, NO_OPTS};

        macro_rules! mktest {
            ($name:ident, $input:expr, $expected:pat) => {
                mktest!($name, $input, $expected, ALL_OPTS);
            };
            ($name:ident, $input:expr, $expected:pat, $opts:expr) => {
                #[test]
                fn $name() {
                    match gix_validate::path::component($input.as_bstr(), None, $opts) {
                        Err($expected) => {}
                        got => panic!("Wanted {}, got {:?}", stringify!($expected), got),
                    }
                }
            };
            ($name:ident, $input:expr, $expected:pat, $mode:expr, $opts:expr) => {
                #[test]
                fn $name() {
                    match gix_validate::path::component($input.as_bstr(), Some($mode), $opts) {
                        Err($expected) => {}
                        got => panic!("Wanted {}, got {:?}", stringify!($expected), got),
                    }
                }
            };
        }

        mktest!(empty, b"", Error::Empty);
        mktest!(dot_git_lower, b".git", Error::DotGitDir, NO_OPTS);
        mktest!(dot_git_lower_hfs, ".g\u{200c}it".as_bytes(), Error::DotGitDir);
        mktest!(dot_git_mixed_hfs_simple, b".Git", Error::DotGitDir);
        mktest!(dot_git_upper, b".GIT", Error::DotGitDir, NO_OPTS);
        mktest!(
            starts_with_dot_git_with_backslashes_on_windows,
            br".git\hooks\pre-commit",
            Error::PathSeparator
        );
        mktest!(dot_git_upper_hfs, ".GIT\u{200e}".as_bytes(), Error::DotGitDir);
        mktest!(dot_git_upper_ntfs_8_3, b"GIT~1", Error::DotGitDir);
        mktest!(dot_git_mixed, b".gIt", Error::DotGitDir, NO_OPTS);
        mktest!(dot_git_mixed_ntfs_8_3, b"gIt~1", Error::DotGitDir);
        mktest!(
            dot_gitmodules_mixed,
            b".gItmodules",
            Error::SymlinkedGitModules,
            Symlink,
            NO_OPTS
        );
        mktest!(dot_git_mixed_hfs, "\u{206e}.gIt".as_bytes(), Error::DotGitDir);
        mktest!(
            dot_git_ntfs_8_3_numbers_only,
            b"~1000000",
            Error::SymlinkedGitModules,
            Symlink,
            ALL_OPTS
        );
        mktest!(
            dot_git_ntfs_8_3_numbers_only_too,
            b"~9999999",
            Error::SymlinkedGitModules,
            Symlink,
            ALL_OPTS
        );
        mktest!(
            dot_gitmodules_mixed_hfs,
            "\u{206e}.gItmodules".as_bytes(),
            Error::SymlinkedGitModules,
            Symlink,
            ALL_OPTS
        );
        mktest!(
            dot_gitmodules_mixed_ntfs_8_3,
            b"gItMOD~1",
            Error::SymlinkedGitModules,
            Symlink,
            ALL_OPTS
        );
        mktest!(
            dot_gitmodules_mixed_ntfs_stream,
            b".giTmodUles:$DATA",
            Error::SymlinkedGitModules,
            Symlink,
            ALL_OPTS
        );
        mktest!(
            dot_gitmodules_lower_ntfs_stream_default_implicit,
            b".gitmodules::$DATA",
            Error::SymlinkedGitModules,
            Symlink,
            ALL_OPTS
        );
        mktest!(
            ntfs_stream_default_implicit,
            b"file::$DATA",
            Error::WindowsIllegalCharacter
        );
        mktest!(
            ntfs_stream_explicit,
            b"file:ANYTHING_REALLY:$DATA",
            Error::WindowsIllegalCharacter
        );
        mktest!(
            dot_gitmodules_lower_ntfs_stream,
            b".gitmodules:$DATA:$DATA",
            Error::SymlinkedGitModules,
            Symlink,
            ALL_OPTS
        );
        mktest!(
            not_gitmodules_trailing_space,
            b".gitmodules x ",
            Error::WindowsIllegalCharacter
        );
        mktest!(
            not_gitmodules_trailing_stream,
            b".gitmodules,:$DATA",
            Error::WindowsIllegalCharacter
        );
        mktest!(path_separator_slash_between, b"a/b", Error::PathSeparator);
        mktest!(path_separator_slash_leading, b"/a", Error::PathSeparator);
        mktest!(path_separator_slash_trailing, b"a/", Error::PathSeparator);
        mktest!(path_separator_slash_only, b"/", Error::PathSeparator);
        mktest!(slashes_on_windows, b"/", Error::PathSeparator, ALL_OPTS);
        mktest!(backslashes_on_windows, br"\", Error::PathSeparator, ALL_OPTS);
        mktest!(path_separator_backslash_between, br"a\b", Error::PathSeparator);
        mktest!(path_separator_backslash_leading, br"\a", Error::PathSeparator);
        mktest!(path_separator_backslash_trailing, br"a\", Error::PathSeparator);
        mktest!(aux_mixed, b"Aux", Error::WindowsReservedName);
        mktest!(aux_with_extension, b"aux.c", Error::WindowsReservedName);
        mktest!(com_lower, b"com1", Error::WindowsReservedName);
        mktest!(com_upper_with_extension, b"COM9.c", Error::WindowsReservedName);
        mktest!(trailing_space, b"win32 ", Error::WindowsIllegalCharacter);
        mktest!(trailing_dot, b"win32.", Error::WindowsIllegalCharacter);
        mktest!(trailing_dot_dot, b"win32 . .", Error::WindowsIllegalCharacter);
        mktest!(colon_inbetween, b"colon:separates", Error::WindowsIllegalCharacter);
        mktest!(left_arrow, b"arrow<left", Error::WindowsIllegalCharacter);
        mktest!(right_arrow, b"arrow>right", Error::WindowsIllegalCharacter);
        mktest!(apostrophe, b"a\"b", Error::WindowsIllegalCharacter);
        mktest!(pipe, b"a|b", Error::WindowsIllegalCharacter);
        mktest!(questionmark, b"a?b", Error::WindowsIllegalCharacter);
        mktest!(asterisk, b"a*b", Error::WindowsIllegalCharacter);
        mktest!(lpt_mixed_with_number, b"LPt8", Error::WindowsReservedName);
        mktest!(nul_mixed, b"NuL", Error::WindowsReservedName);
        mktest!(prn_mixed_with_extension, b"PrN.abc", Error::WindowsReservedName);
        mktest!(con, b"CON", Error::WindowsReservedName);
        mktest!(con_with_extension, b"CON.abc", Error::WindowsReservedName);
        mktest!(con_with_middle, b"CON.tar.xz", Error::WindowsReservedName);
        mktest!(con_mixed_with_middle, b"coN.tar.xz ", Error::WindowsReservedName);
        mktest!(dot_dot, b"..", Error::Relative);
        mktest!(dot_dot_no_opts, b"..", Error::Relative, NO_OPTS);
        mktest!(single_dot, b".", Error::Relative);
        mktest!(single_dot_no_opts, b".", Error::Relative, NO_OPTS);
        mktest!(
            conout_mixed_with_extension,
            b"ConOut$  .xyz",
            Error::WindowsReservedName
        );
        mktest!(conin_mixed, b"conIn$  ", Error::WindowsReservedName);
        mktest!(drive_letters, b"c:", Error::WindowsPathPrefix, ALL_OPTS);
        mktest!(
            virtual_drive_letters,
            "֍:".as_bytes(),
            Error::WindowsPathPrefix,
            ALL_OPTS
        );
        mktest!(unc_net_path, br"\\host", Error::PathSeparator, ALL_OPTS);
        mktest!(unc_path, br"\\?\pictures", Error::PathSeparator, ALL_OPTS);
        mktest!(unc_device_path, br"\\.\pictures", Error::PathSeparator, ALL_OPTS);
        mktest!(unc_nt_obj_path, br"\??\pictures", Error::PathSeparator, ALL_OPTS);

        #[test]
        fn ntfs_gitmodules() {
            for invalid in [
                ".gitmodules",
                ".Gitmodules",
                ".gitmoduleS",
                ".gitmodules ",
                ".gitmodules.",
                ".gitmodules  ",
                ".gitmodules. ",
                ".gitmodules .",
                ".gitmodules..",
                ".gitmodules   ",
                ".gitmodules.  ",
                ".gitmodules . ",
                ".gitmodules  .",
                ".Gitmodules ",
                ".Gitmodules.",
                ".Gitmodules  ",
                ".Gitmodules. ",
                ".Gitmodules .",
                ".Gitmodules..",
                ".Gitmodules   ",
                ".Gitmodules.  ",
                ".Gitmodules . ",
                ".Gitmodules  .",
                "GITMOD~1",
                "gitmod~1",
                "GITMOD~2",
                "giTmod~3",
                "GITMOD~4",
                "GITMOD~1 ",
                "gitMod~2.",
                "GITMOD~3  ",
                "gitmod~4. ",
                "GITMoD~1 .",
                "gitmod~2   ",
                "GITMOD~3.  ",
                "gitmoD~4 . ",
                "GI7EBA~1",
                "gi7eba~9",
                "GI7EB~10",
                "GI7EB~11",
                "GI7EB~99",
                "GI7EB~10",
                "GI7E~100",
                "GI7E~101",
                "GI7E~999",
                ".gitmodules:$DATA",
                "gitmod~4 . :$DATA",
            ] {
                match gix_validate::path::component(invalid.into(), Some(Symlink), ALL_OPTS) {
                    Ok(_) => {
                        unreachable!("{invalid:?} should not validate successfully")
                    }
                    Err(err) => {
                        assert!(matches!(err, Error::SymlinkedGitModules));
                    }
                }
            }

            for valid in [
                ".gitmodules x",
                ".gitmodules .x",
                " .gitmodules",
                "..gitmodules",
                "gitmodules",
                ".gitmodule",
                ".gitmodules .x",
                "GI7EBA~",
                "GI7EBA~0",
                "GI7EBA~~1",
                "GI7EBA~X",
                "Gx7EBA~1",
                "GI7EBX~1",
                "GI7EB~1",
                "GI7EB~01",
                "GI7EB~1X",
            ] {
                gix_validate::path::component(valid.into(), Some(Symlink), ALL_OPTS)
                    .unwrap_or_else(|_| panic!("{valid:?} should have been valid"));
            }
        }
    }
}
