mount-fstab 0.1.1

Type-safe /etc/fstab parsing, editing, and validation library
Documentation
use mount_fstab::escape::{decode_escapes, encode_escapes};

#[test]
fn decode_space() {
    assert_eq!(decode_escapes(r"\040"), " ");
}

#[test]
fn decode_tab() {
    assert_eq!(decode_escapes(r"\011"), "\t");
}

#[test]
fn decode_newline() {
    assert_eq!(decode_escapes(r"\012"), "\n");
}

#[test]
fn decode_backslash_literal() {
    assert_eq!(decode_escapes(r"\\"), "\\");
}

#[test]
fn decode_backslash_octal_134() {
    assert_eq!(decode_escapes(r"\134"), "\\");
}

#[test]
fn decode_mixed() {
    assert_eq!(decode_escapes(r"foo\040bar\011baz"), "foo bar\tbaz");
}

#[test]
fn decode_plain_text_passthrough() {
    assert_eq!(decode_escapes("hello world"), "hello world");
}

#[test]
fn decode_empty() {
    assert_eq!(decode_escapes(""), "");
}

#[test]
fn decode_trailing_backslash() {
    // Trailing backslash with nothing after is unchanged per glibc behavior
    assert_eq!(decode_escapes(r"hello\"), r"hello\");
}

#[test]
fn decode_unknown_escape_preserved() {
    // Unknown escape sequences are preserved as-is
    assert_eq!(decode_escapes(r"\999"), r"\999");
    assert_eq!(decode_escapes(r"\x"), r"\x");
}

#[test]
fn encode_space() {
    assert_eq!(encode_escapes("hello world"), r"hello\040world");
}

#[test]
fn encode_tab() {
    assert_eq!(encode_escapes("a\tb"), r"a\011b");
}

#[test]
fn encode_newline() {
    assert_eq!(encode_escapes("a\nb"), r"a\012b");
}

#[test]
fn encode_backslash() {
    assert_eq!(encode_escapes(r"a\b"), r"a\\b");
}

#[test]
fn encode_multiple_specials() {
    assert_eq!(encode_escapes("a b\tc\nd\\e"), r"a\040b\011c\012d\\e");
}

#[test]
fn encode_plain_text_passthrough() {
    assert_eq!(encode_escapes("hello"), "hello");
}

#[test]
fn roundtrip_decode_encode() {
    let original = "/mnt/My Drive with spaces";
    let encoded = encode_escapes(original);
    let decoded = decode_escapes(&encoded);
    assert_eq!(decoded, original);
}

#[test]
fn roundtrip_encode_decode() {
    let encoded = r"UUID=3e6be9de\\8139\\11d1\\9106\\a43f08d823a6";
    let decoded = decode_escapes(encoded);
    let re_encoded = encode_escapes(&decoded);
    assert_eq!(re_encoded, encoded);
}

#[test]
fn decode_escape_in_middle_of_field() {
    assert_eq!(decode_escapes(r"foo\040bar"), "foo bar");
}

#[test]
fn decode_multiple_escapes_in_one_field() {
    assert_eq!(decode_escapes(r"a\040b\040c"), "a b c");
}

#[test]
fn decode_all_five_escapes_in_order() {
    // \040 (space), \011 (tab), \012 (newline), \\ (backslash), \134 (alt backslash)
    assert_eq!(decode_escapes(r"\040\011\012\\\134"), " \t\n\\\\");
}

#[test]
fn decode_escaped_equal_sign() {
    // = is not a reserved char, should pass through
    assert_eq!(decode_escapes(r"key\075value"), "key=value");
}

#[test]
fn decode_zero_byte() {
    // \000 is valid octal
    assert_eq!(decode_escapes(r"\000"), "\u{0000}");
}

#[test]
fn decode_all_octal_values() {
    // All valid 3-digit octal values should work (000-377)
    for byte in 0u8..=255u8 {
        let octal = format!("\\{:03o}", byte);
        let expected = char::from(byte).to_string();
        let decoded = decode_escapes(&octal);

        if byte.is_ascii() {
            assert_eq!(decoded, expected, "mismatch for octal {:03o}", byte);
        }
    }
}

#[test]
fn decode_single_backslash_with_space_after() {
    // \ followed by space — unknown escape, both preserved
    assert_eq!(decode_escapes(r"\ "), r"\ ");
}

#[test]
fn encode_only_targets_reserved_chars() {
    // Only space, tab, newline, backslash should be encoded
    let input = "abc123!@#$%^&*()_+-=[]{}|;:',.<>?/~`";
    assert_eq!(encode_escapes(input), input);
}

#[test]
fn encode_then_decode_is_identity_for_any_string() {
    let cases = [
        "simple",
        "with space",
        "with\ttab",
        "with\nnewline",
        r"with\backslash",
        "\u{0000}null",
        "unicode: café 日本語",
    ];
    for original in cases {
        assert_eq!(
            decode_escapes(&encode_escapes(original)),
            original,
            "roundtrip failed for: {original:?}"
        );
    }
}

#[test]
fn decode_preserves_utf8() {
    assert_eq!(decode_escapes("café"), "café");
    assert_eq!(decode_escapes("ñoño"), "ñoño");
}

#[test]
fn encode_preserves_utf8() {
    assert_eq!(encode_escapes("café"), "café");
    assert_eq!(encode_escapes("日本語"), "日本語");
}