These were missing and have already proven valuable since they have found
two bugs (fixed in previous patches).
There may be other behavior to test, but this gives us a decent coverage.
| hg-reviewers |
These were missing and have already proven valuable since they have found
two bugs (fixed in previous patches).
There may be other behavior to test, but this gives us a decent coverage.
| No Linters Available |
| No Unit Test Coverage |
| Path | Packages | |||
|---|---|---|---|---|
| M | rust/hg-core/src/dirstate_tree/dirstate_map.rs (414 lines) |
| Commit | Parents | Author | Summary | Date |
|---|---|---|---|---|
| 2f67e27c6b0d | d3f22150b883 | Raphaël Gomès | Tue, Mar 29, 12:21 PM |
| Status | Author | Revision | |
|---|---|---|---|
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare | ||
| Needs Review | Alphare |
| (b' ', 0, -1, mtime.truncated_seconds() as i32) | (b' ', 0, -1, mtime.truncated_seconds() as i32) | ||||
| } else { | } else { | ||||
| (b' ', 0, -1, -1) | (b' ', 0, -1, -1) | ||||
| }; | }; | ||||
| Ok(Some((node.full_path(map.on_disk)?, debug_tuple))) | Ok(Some((node.full_path(map.on_disk)?, debug_tuple))) | ||||
| })) | })) | ||||
| } | } | ||||
| } | } | ||||
| #[cfg(test)] | |||||
| mod tests { | |||||
| use super::*; | |||||
| /// Shortcut to return tracked descendants of a path. | |||||
| /// Panics if the path does not exist. | |||||
| fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 { | |||||
| let path = dbg!(HgPath::new(path)); | |||||
| let node = map.get_map().get_node(path); | |||||
| node.unwrap().unwrap().tracked_descendants_count() | |||||
| } | |||||
| /// Shortcut to return descendants with an entry. | |||||
| /// Panics if the path does not exist. | |||||
| fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 { | |||||
| let path = dbg!(HgPath::new(path)); | |||||
| let node = map.get_map().get_node(path); | |||||
| node.unwrap().unwrap().descendants_with_entry_count() | |||||
| } | |||||
| fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) { | |||||
| let path = dbg!(HgPath::new(path)); | |||||
| let node = map.get_map().get_node(path); | |||||
| assert!(node.unwrap().is_none()); | |||||
| } | |||||
| /// Shortcut for path creation in tests | |||||
| fn p(b: &[u8]) -> &HgPath { | |||||
| HgPath::new(b) | |||||
| } | |||||
| /// Test the very simple case a single tracked file | |||||
| #[test] | |||||
| fn test_tracked_descendants_simple() -> Result<(), DirstateError> { | |||||
| let mut map = OwningDirstateMap::new_empty(vec![]); | |||||
| assert_eq!(map.len(), 0); | |||||
| map.set_tracked(p(b"some/nested/path"))?; | |||||
| assert_eq!(map.len(), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0); | |||||
| map.set_untracked(p(b"some/nested/path"))?; | |||||
| assert_eq!(map.len(), 0); | |||||
| assert!(map.get_map().get_node(p(b"some"))?.is_none()); | |||||
| Ok(()) | |||||
| } | |||||
| /// Test the simple case of all tracked, but multiple files | |||||
| #[test] | |||||
| fn test_tracked_descendants_multiple() -> Result<(), DirstateError> { | |||||
| let mut map = OwningDirstateMap::new_empty(vec![]); | |||||
| map.set_tracked(p(b"some/nested/path"))?; | |||||
| map.set_tracked(p(b"some/nested/file"))?; | |||||
| // one layer without any files to test deletion cascade | |||||
| map.set_tracked(p(b"some/other/nested/path"))?; | |||||
| map.set_tracked(p(b"root_file"))?; | |||||
| map.set_tracked(p(b"some/file"))?; | |||||
| map.set_tracked(p(b"some/file2"))?; | |||||
| map.set_tracked(p(b"some/file3"))?; | |||||
| assert_eq!(map.len(), 7); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 6); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested"), 2); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0); | |||||
| map.set_untracked(p(b"some/nested/path"))?; | |||||
| assert_eq!(map.len(), 6); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 5); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); | |||||
| map.set_untracked(p(b"some/nested/file"))?; | |||||
| assert_eq!(map.len(), 5); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 4); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); | |||||
| assert_does_not_exist(&map, b"some_nested"); | |||||
| map.set_untracked(p(b"some/other/nested/path"))?; | |||||
| assert_eq!(map.len(), 4); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 3); | |||||
| assert_does_not_exist(&map, b"some/other"); | |||||
| map.set_untracked(p(b"root_file"))?; | |||||
| assert_eq!(map.len(), 3); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 3); | |||||
| assert_does_not_exist(&map, b"root_file"); | |||||
| map.set_untracked(p(b"some/file"))?; | |||||
| assert_eq!(map.len(), 2); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 2); | |||||
| assert_does_not_exist(&map, b"some/file"); | |||||
| map.set_untracked(p(b"some/file2"))?; | |||||
| assert_eq!(map.len(), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 1); | |||||
| assert_does_not_exist(&map, b"some/file2"); | |||||
| map.set_untracked(p(b"some/file3"))?; | |||||
| assert_eq!(map.len(), 0); | |||||
| assert_does_not_exist(&map, b"some/file3"); | |||||
| Ok(()) | |||||
| } | |||||
| /// Check with a mix of tracked and non-tracked items | |||||
| #[test] | |||||
| fn test_tracked_descendants_different() -> Result<(), DirstateError> { | |||||
| let mut map = OwningDirstateMap::new_empty(vec![]); | |||||
| // A file that was just added | |||||
| map.set_tracked(p(b"some/nested/path"))?; | |||||
| // This has no information, the dirstate should ignore it | |||||
| map.reset_state(p(b"some/file"), false, false, false, false, None)?; | |||||
| assert_does_not_exist(&map, b"some/file"); | |||||
| // A file that was removed | |||||
| map.reset_state( | |||||
| p(b"some/nested/file"), | |||||
| false, | |||||
| true, | |||||
| false, | |||||
| false, | |||||
| None, | |||||
| )?; | |||||
| assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked()); | |||||
| // Only present in p2 | |||||
| map.reset_state(p(b"some/file3"), false, false, true, false, None)?; | |||||
| assert!(!map.get(p(b"some/file3"))?.unwrap().tracked()); | |||||
| // A file that was merged | |||||
| map.reset_state(p(b"root_file"), true, true, true, false, None)?; | |||||
| assert!(map.get(p(b"root_file"))?.unwrap().tracked()); | |||||
| // A file that is added, with info from p2 | |||||
| // XXX is that actually possible? | |||||
| map.reset_state(p(b"some/file2"), true, false, true, false, None)?; | |||||
| assert!(map.get(p(b"some/file2"))?.unwrap().tracked()); | |||||
| // A clean file | |||||
| // One layer without any files to test deletion cascade | |||||
| map.reset_state( | |||||
| p(b"some/other/nested/path"), | |||||
| true, | |||||
| true, | |||||
| false, | |||||
| false, | |||||
| None, | |||||
| )?; | |||||
| assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked()); | |||||
| assert_eq!(map.len(), 6); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 3); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some"), 5); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0); | |||||
| assert_eq!( | |||||
| descendants_with_an_entry(&map, b"some/other/nested/path"), | |||||
| 0 | |||||
| ); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2); | |||||
| // might as well check this | |||||
| map.set_untracked(p(b"path/does/not/exist"))?; | |||||
| assert_eq!(map.len(), 6); | |||||
| map.set_untracked(p(b"some/other/nested/path"))?; | |||||
| // It is set untracked but not deleted since it held other information | |||||
| assert_eq!(map.len(), 6); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 2); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some"), 5); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2); | |||||
| map.set_untracked(p(b"some/nested/path"))?; | |||||
| // It is set untracked *and* deleted since it was only added | |||||
| assert_eq!(map.len(), 5); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some"), 4); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested"), 0); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1); | |||||
| assert_does_not_exist(&map, b"some/nested/path"); | |||||
| map.set_untracked(p(b"root_file"))?; | |||||
| // Untracked but not deleted | |||||
| assert_eq!(map.len(), 5); | |||||
| assert!(map.get(p(b"root_file"))?.is_some()); | |||||
| map.set_untracked(p(b"some/file2"))?; | |||||
| assert_eq!(map.len(), 5); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 0); | |||||
| assert!(map.get(p(b"some/file2"))?.is_some()); | |||||
| map.set_untracked(p(b"some/file3"))?; | |||||
| assert_eq!(map.len(), 5); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 0); | |||||
| assert!(map.get(p(b"some/file3"))?.is_some()); | |||||
| Ok(()) | |||||
| } | |||||
| /// Check that copies counter is correctly updated | |||||
| #[test] | |||||
| fn test_copy_source() -> Result<(), DirstateError> { | |||||
| let mut map = OwningDirstateMap::new_empty(vec![]); | |||||
| // Clean file | |||||
| map.reset_state(p(b"files/clean"), true, true, false, false, None)?; | |||||
| // Merged file | |||||
| map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?; | |||||
| // Removed file | |||||
| map.reset_state(p(b"removed"), false, true, false, false, None)?; | |||||
| // Added file | |||||
| map.reset_state(p(b"files/added"), true, false, false, false, None)?; | |||||
| // Add copy | |||||
| map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?; | |||||
| assert_eq!(map.copy_map_len(), 1); | |||||
| // Copy override | |||||
| map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?; | |||||
| assert_eq!(map.copy_map_len(), 1); | |||||
| // Multiple copies | |||||
| map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?; | |||||
| assert_eq!(map.copy_map_len(), 2); | |||||
| map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?; | |||||
| assert_eq!(map.copy_map_len(), 3); | |||||
| // Added, so the entry is completely removed | |||||
| map.set_untracked(p(b"files/added"))?; | |||||
| assert_does_not_exist(&map, b"files/added"); | |||||
| assert_eq!(map.copy_map_len(), 2); | |||||
| // Removed, so the entry is kept around, so is its copy | |||||
| map.set_untracked(p(b"removed"))?; | |||||
| assert!(map.get(p(b"removed"))?.is_some()); | |||||
| assert_eq!(map.copy_map_len(), 2); | |||||
| // Clean, so the entry is kept around, but not its copy | |||||
| map.set_untracked(p(b"files/clean"))?; | |||||
| assert!(map.get(p(b"files/clean"))?.is_some()); | |||||
| assert_eq!(map.copy_map_len(), 1); | |||||
| map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?; | |||||
| assert_eq!(map.copy_map_len(), 2); | |||||
| // Info from p2, so its copy source info is kept around | |||||
| map.set_untracked(p(b"files/from_p2"))?; | |||||
| assert!(map.get(p(b"files/from_p2"))?.is_some()); | |||||
| assert_eq!(map.copy_map_len(), 2); | |||||
| Ok(()) | |||||
| } | |||||
| /// Test with "on disk" data. For the sake of this test, the "on disk" data | |||||
| /// does not actually come from the disk, but it's opaque to the code being | |||||
| /// tested. | |||||
| #[test] | |||||
| fn test_on_disk() -> Result<(), DirstateError> { | |||||
| // First let's create some data to put "on disk" | |||||
| let mut map = OwningDirstateMap::new_empty(vec![]); | |||||
| // A file that was just added | |||||
| map.set_tracked(p(b"some/nested/added"))?; | |||||
| map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?; | |||||
| // A file that was removed | |||||
| map.reset_state( | |||||
| p(b"some/nested/removed"), | |||||
| false, | |||||
| true, | |||||
| false, | |||||
| false, | |||||
| None, | |||||
| )?; | |||||
| // Only present in p2 | |||||
| map.reset_state( | |||||
| p(b"other/p2_info_only"), | |||||
| false, | |||||
| false, | |||||
| true, | |||||
| false, | |||||
| None, | |||||
| )?; | |||||
| map.copy_map_insert( | |||||
| p(b"other/p2_info_only"), | |||||
| p(b"other/p2_info_copy_source"), | |||||
| )?; | |||||
| // A file that was merged | |||||
| map.reset_state(p(b"merged"), true, true, true, false, None)?; | |||||
| // A file that is added, with info from p2 | |||||
| // XXX is that actually possible? | |||||
| map.reset_state( | |||||
| p(b"other/added_with_p2"), | |||||
| true, | |||||
| false, | |||||
| true, | |||||
| false, | |||||
| None, | |||||
| )?; | |||||
| // One layer without any files to test deletion cascade | |||||
| // A clean file | |||||
| map.reset_state( | |||||
| p(b"some/other/nested/clean"), | |||||
| true, | |||||
| true, | |||||
| false, | |||||
| false, | |||||
| None, | |||||
| )?; | |||||
| let (packed, metadata, _should_append) = map.pack_v2(false)?; | |||||
| let packed_len = packed.len(); | |||||
| assert!(packed_len > 0); | |||||
| // Recreate "from disk" | |||||
| let mut map = OwningDirstateMap::new_v2( | |||||
| packed, | |||||
| packed_len, | |||||
| metadata.as_bytes(), | |||||
| )?; | |||||
| // Check that everything is accounted for | |||||
| assert!(map.contains_key(p(b"some/nested/added"))?); | |||||
| assert!(map.contains_key(p(b"some/nested/removed"))?); | |||||
| assert!(map.contains_key(p(b"merged"))?); | |||||
| assert!(map.contains_key(p(b"other/p2_info_only"))?); | |||||
| assert!(map.contains_key(p(b"other/added_with_p2"))?); | |||||
| assert!(map.contains_key(p(b"some/other/nested/clean"))?); | |||||
| assert_eq!( | |||||
| map.copy_map_get(p(b"some/nested/added"))?, | |||||
| Some(p(b"added_copy_source")) | |||||
| ); | |||||
| assert_eq!( | |||||
| map.copy_map_get(p(b"other/p2_info_only"))?, | |||||
| Some(p(b"other/p2_info_copy_source")) | |||||
| ); | |||||
| assert_eq!(tracked_descendants(&map, b"some"), 2); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some"), 3); | |||||
| assert_eq!(tracked_descendants(&map, b"other"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"other"), 2); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1); | |||||
| assert_eq!(tracked_descendants(&map, b"some/nested"), 1); | |||||
| assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2); | |||||
| assert_eq!(map.len(), 6); | |||||
| assert_eq!(map.get_map().unreachable_bytes, 0); | |||||
| assert_eq!(map.copy_map_len(), 2); | |||||
| // Shouldn't change anything since it's already not tracked | |||||
| map.set_untracked(p(b"some/nested/removed"))?; | |||||
| assert_eq!(map.get_map().unreachable_bytes, 0); | |||||
| match map.get_map().root { | |||||
| ChildNodes::InMemory(_) => { | |||||
| panic!("root should not have been mutated") | |||||
| } | |||||
| _ => (), | |||||
| } | |||||
| // We haven't mutated enough (nothing, actually), we should still be in | |||||
| // the append strategy | |||||
| assert!(map.get_map().write_should_append()); | |||||
| // But this mutates the structure, so there should be unreachable_bytes | |||||
| assert!(map.set_untracked(p(b"some/nested/added"))?); | |||||
| let unreachable_bytes = map.get_map().unreachable_bytes; | |||||
| assert!(unreachable_bytes > 0); | |||||
| match map.get_map().root { | |||||
| ChildNodes::OnDisk(_) => panic!("root should have been mutated"), | |||||
| _ => (), | |||||
| } | |||||
| // This should not mutate the structure either, since `root` has | |||||
| // already been mutated along with its direct children. | |||||
| map.set_untracked(p(b"merged"))?; | |||||
| assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes); | |||||
| match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() { | |||||
| NodeRef::InMemory(_, _) => { | |||||
| panic!("'other/added_with_p2' should not have been mutated") | |||||
| } | |||||
| _ => (), | |||||
| } | |||||
| // But this should, since it's in a different path | |||||
| // than `<root>some/nested/add` | |||||
| map.set_untracked(p(b"other/added_with_p2"))?; | |||||
| assert!(map.get_map().unreachable_bytes > unreachable_bytes); | |||||
| match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() { | |||||
| NodeRef::OnDisk(_) => { | |||||
| panic!("'other/added_with_p2' should have been mutated") | |||||
| } | |||||
| _ => (), | |||||
| } | |||||
| // We have rewritten most of the tree, we should create a new file | |||||
| assert!(!map.get_map().write_should_append()); | |||||
| Ok(()) | |||||
| } | |||||
| } | |||||