Skip to content

Removing outer elements in nested Vec produces unexpected results #23

Open
@xlambein

Description

I've noticed what I think is a bug in autosurgeon.

In the test that follows, I create a data structure with a Vec of 3 strings, fork it, remove one element in one document and another in the other document, then merge them back and get a document where both elements are removed, as expected:

#[test]
fn remove_vec_of_strings() {
    #[derive(Hydrate, Reconcile)]
    struct Data {
        rows: Vec<String>,
    }

    // Create data with 3 rows
    let mut data1 = Data {
        rows: vec!["hello".to_owned(), "world".to_owned(), "foobar".to_owned()],
    };
    let mut doc1 = AutoCommit::new();
    reconcile(&mut doc1, &data1).unwrap();

    // Fork into another document
    let mut doc2 = doc1.fork();
    let mut data2: Data = hydrate(&doc2).unwrap();

    // Remove row 0 in first document, and row 1 in second
    data1.rows.remove(0);
    data2.rows.remove(1);
    reconcile(&mut doc1, data1).unwrap();
    reconcile(&mut doc2, data2).unwrap();

    // Merge documents
    doc1.merge(&mut doc2).unwrap();
    let data_merged: Data = hydrate(&doc1).unwrap();

    // Both rows 0 and 1 have been removed
    assert_eq!(data_merged.rows.len(), 1);
    assert_eq!(&data_merged.rows[0], "foobar");
}

However, in the next test, I do the exact same, but the document contains a Vec of three Vecs of bytes. Here, the results are different: we get a document with two elements, in which some values are scrambled. Note that I'm aware of the existence of ByteVec, but for this example I'm not using them.

#[test]
fn remove_vec_of_bytes() {
    #[derive(Hydrate, Reconcile)]
    struct Data {
        rows: Vec<Vec<u8>>,
    }

    // Same as above
    let mut data1 = Data {
        rows: vec![b"hello".to_vec(), b"world".to_vec(), b"foobar".to_vec()],
    };
    let mut doc1 = AutoCommit::new();
    reconcile(&mut doc1, &data1).unwrap();

    let mut doc2 = doc1.fork();
    let mut data2: Data = hydrate(&doc2).unwrap();

    data1.rows.remove(0);
    data2.rows.remove(1);

    reconcile(&mut doc1, data1).unwrap();
    reconcile(&mut doc2, data2).unwrap();
    doc1.merge(&mut doc2).unwrap();
    let data_merged: Data = hydrate(&doc1).unwrap();

    // There are two rows, with the following values:
    assert_eq!(data_merged.rows.len(), 2);
    assert_eq!(&data_merged.rows[0], b"world");
    assert_eq!(&data_merged.rows[1], b"ffoobaobar");
    // Instead, I'd expect to have the same results as the previous test
}

I tried doing things manually with automerge, i.e. creating a document with nested lists, forking, removing elements, and merging again, but I don't get any weird results there, which leads me to conclude that autosurgeon's implementation must have an issue somewhere---or perhaps that I misunderstood something.

Same test again, using `automerge` directly:
#[test]
fn remove_vec_of_bytes_automerge() {
    let mut doc1 = AutoCommit::new();
    let rows = doc1
        .put_object(automerge::ROOT, "rows", automerge::ObjType::List)
        .unwrap();

    let row = doc1
        .insert_object(&rows, 0, automerge::ObjType::List)
        .unwrap();
    for (i, b) in b"hello".into_iter().enumerate() {
        doc1.insert(&row, i, *b as u64).unwrap();
    }
    let row = doc1
        .insert_object(&rows, 1, automerge::ObjType::List)
        .unwrap();
    for (i, b) in b"world".into_iter().enumerate() {
        doc1.insert(&row, i, *b as u64).unwrap();
    }
    let row = doc1
        .insert_object(&rows, 2, automerge::ObjType::List)
        .unwrap();
    for (i, b) in b"foobar".into_iter().enumerate() {
        doc1.insert(&row, i, *b as u64).unwrap();
    }

    let mut doc2 = doc1.fork();

    doc1.delete(&rows, 0).unwrap();
    doc2.delete(&rows, 1).unwrap();
    assert_eq!(doc1.length(&rows), 2);
    assert_eq!(doc2.length(&rows), 2);

    doc1.merge(&mut doc2).unwrap();
    assert_eq!(doc1.length(&rows), 1);
    assert_eq!(
        doc1.list_range(doc1.get(&rows, 0).unwrap().unwrap().1, ..)
            .map(|(_, value, _)| value.to_u64().unwrap() as u8)
            .collect::<Vec<_>>(),
        b"foobar"
    );
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions