247 lines · 8.7 KB · Rust
RawDownload
use filetime::FileTime;use git_index::{entry, extension, verify::extensions::no_find, write, write::Options, State, Version}; use crate::{fixture_index_path, index::Fixture::*, loose_file_path}; /// Round-trips should eventually be possible for all files we have, as we write them back exactly as they were read.#[test]fn roundtrips() -> crate::Result {    let input = [        (Loose("extended-flags"), only_tree_ext()),        (Loose("conflicting-file"), only_tree_ext()),        (Loose("very-long-path"), only_tree_ext()),        (            Generated("v2"),            options_with(write::Extensions::Given {                tree_cache: true,                end_of_index_entry: true,            }),        ),        (Generated("V2_empty"), only_tree_ext()),        (Generated("v2_more_files"), only_tree_ext()),        (Generated("v2_all_file_kinds"), only_tree_ext()),    ];     for (fixture, options) in input {        let (path, fixture) = match fixture {            Generated(name) => (fixture_index_path(name), name),            Loose(name) => (loose_file_path(name), name),        };        let expected = git_index::File::at(&path, git_hash::Kind::Sha1, Default::default())?;        let expected_bytes = std::fs::read(&path)?;        let mut out_bytes = Vec::new();         let (actual_version, _digest) = expected.write_to(&mut out_bytes, options)?;        let (actual, _) = State::from_bytes(&out_bytes, FileTime::now(), git_hash::Kind::Sha1, Default::default())?;         compare_states_against_baseline(&actual, actual_version, &expected, options, fixture);        compare_raw_bytes(&out_bytes, &expected_bytes, fixture);    }    Ok(())} #[test]fn roundtrips_sparse_index() -> crate::Result {    // NOTE: I initially tried putting these fixtures into the main roundtrip test above,    // but the call to `compare_raw_bytes` panics. It seems like git is using a different    // ordering when it comes to writing the tree extension. Need to investigate more, hence    // the separate test for now.    //    //          git                     gitoxide    //    //          treeroot                treeroot    //            | d                     | c1    //            | d/c4                  | c1/c2    //            | c1                    | c1/c3    //            | c1/c2                 | d    //            | c1/c3                 | d/c4    //     let input = [        ("v3_skip_worktree", only_tree_ext()),        ("v3_sparse_index_non_cone", only_tree_ext()),        ("v3_sparse_index", only_tree_ext()),        ("v2_sparse_index_no_dirs", only_tree_ext()),    ];     for (fixture, options) in input {        let path = fixture_index_path(fixture);        let expected = git_index::File::at(&path, git_hash::Kind::Sha1, Default::default())?;        let _expected_bytes = std::fs::read(&path)?;        let mut out_bytes = Vec::new();         let (actual_version, _) = expected.write_to(&mut out_bytes, options)?;        let (actual, _) = State::from_bytes(&out_bytes, FileTime::now(), git_hash::Kind::Sha1, Default::default())?;         compare_states_against_baseline(&actual, actual_version, &expected, options, fixture);        // TODO: make this work and re-enable it, once this is done the fixtures can be merged into the main "roundtrip" test        // compare_raw_bytes(&out_bytes, &_expected_bytes, fixture);    }    Ok(())} #[test]fn state_comparisons_with_various_extension_configurations() {    for fixture in [        Loose("extended-flags"),        Loose("conflicting-file"),        Loose("very-long-path"),        Loose("FSMN"),        Loose("REUC"),        Loose("UNTR-with-oids"),        Loose("UNTR"),        Generated("V2_empty"),        Generated("v2"),        Generated("v2_more_files"),        Generated("v2_all_file_kinds"),        Generated("v2_split_index"),        // TODO: this failes because git allows to configure the index version while gitoxide doesn't        //       the fixture artificially sets the version to V4 and gitoxide writes it back out as the lowest required verison, V2        // Generated("v4_more_files_IEOT"),        Generated("v3_skip_worktree"),        Generated("v3_sparse_index_non_cone"),        Generated("v3_sparse_index"),        // TODO: this fails because git writes the sdir extension in this case while gitoxide doesn't        // Generated("v2_sparse_index_no_dirs"),    ] {        for options in [            options_with(write::Extensions::None),            options_with(write::Extensions::All),            options_with(write::Extensions::Given {                tree_cache: true,                end_of_index_entry: false,            }),            options_with(write::Extensions::Given {                tree_cache: false,                end_of_index_entry: true,            }),        ] {            let path = fixture.to_path();            let fixture = fixture.to_name();            let expected = git_index::File::at(&path, git_hash::Kind::Sha1, Default::default()).unwrap();             let mut out = Vec::<u8>::new();            let (actual_version, _digest) = expected.write_to(&mut out, options).unwrap();             let (actual, _) =                State::from_bytes(&out, FileTime::now(), git_hash::Kind::Sha1, Default::default()).unwrap();            compare_states(&actual, actual_version, &expected, options, fixture);        }    }} #[test]fn extended_flags_automatically_upgrade_the_version_to_avoid_data_loss() -> crate::Result {    let mut expected = git_index::File::at(fixture_index_path("v2"), git_hash::Kind::Sha1, Default::default())?;    assert_eq!(expected.version(), Version::V2);    expected.entries_mut()[0].flags.insert(entry::Flags::EXTENDED);     let mut buf = Vec::new();    let (actual_version, _digest) = expected.write_to(&mut buf, Default::default())?;    assert_eq!(actual_version, Version::V3, "extended flags need V3");     Ok(())} fn compare_states_against_baseline(    actual: &State,    actual_version: Version,    expected: &State,    options: Options,    fixture: &str,) {    compare_states(actual, actual_version, expected, options, fixture);     assert_eq!(        actual.tree(),        expected.tree(),        "tree extension mismatch, actual vs expected in {:?}",        fixture    );} fn compare_states(actual: &State, actual_version: Version, expected: &State, options: Options, fixture: &str) {    actual.verify_entries().expect("valid");    actual.verify_extensions(false, no_find).expect("valid");     assert_eq!(        actual.version(),        actual_version,        "version mismatch, read vs written, in {:?}",        fixture    );    assert_eq!(        actual.tree(),        options            .extensions            .should_write(extension::tree::SIGNATURE)            .and_then(|_| expected.tree()),        "tree extension mismatch, actual vs option in {:?}",        fixture    );     // As `write_to` does / should not mutate we can test those properties here.    // Anything that can be configured has to be tested separately when comparing againt baseline    assert_eq!(        actual.version(),        expected.version(),        "version mismatch, actual vs expected, in {:?}",        fixture    );    assert_eq!(        actual.is_sparse(),        expected.is_sparse(),        "sparse index entries extension mismatch in {:?}",        fixture    );    assert_eq!(        actual.entries().len(),        expected.entries().len(),        "entry count mismatch in {:?}",        fixture    );    assert_eq!(        actual.entries(),        expected.entries(),        "entries mismatch in {:?}",        fixture    );    assert_eq!(        actual.path_backing(),        expected.path_backing(),        "path_backing mismatch in {:?}",        fixture    );} fn compare_raw_bytes(generated: &[u8], expected: &[u8], fixture: &str) {    assert_eq!(generated.len(), expected.len(), "file length mismatch in {:?}", fixture);     let print_range = 10;    for (index, (a, b)) in generated.iter().zip(expected.iter()).enumerate() {        if a != b {            let range_left = index.saturating_sub(print_range);            let range_right = (index + print_range).min(generated.len());            let generated = &generated[range_left..range_right];            let expected = &expected[range_left..range_right];             panic! {"\n\nRoundtrip failed for index in fixture {:?} at position {:?}\n\            \t  Actual: ... {:?} ...\n\            \tExpected: ... {:?} ...\n\n\            ", &fixture, index, generated, expected}        }    }} fn only_tree_ext() -> Options {    Options {        extensions: write::Extensions::Given {            end_of_index_entry: false,            tree_cache: true,        },    }} fn options_with(extensions: write::Extensions) -> Options {    Options { extensions }}