Merge remote-tracking branch 'han/test-reorg'

* han/test-reorg:
  radicle-surf: test structure compliance.
This commit is contained in:
Fintan Halpenny
2022-09-29 18:00:32 +01:00
21 changed files with 2105 additions and 2042 deletions
+4
View File
@@ -15,6 +15,10 @@ include = [
"data/git-platinum.tgz",
]
[lib]
test = false
doctest = false
[features]
serialize = ["serde"]
# NOTE: testing `test_submodule_failure` on GH actions
-236
View File
@@ -594,239 +594,3 @@ impl Diff {
}
}
}
#[cfg(test)]
mod tests {
use crate::{
diff::*,
file_system::{unsound, *},
};
use pretty_assertions::assert_eq;
#[test]
fn test_create_file() {
let directory = Directory::root();
let mut new_directory = Directory::root();
new_directory.insert_file(unsound::path::new("banana.rs"), File::new(b"use banana"));
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![CreateFile {
path: Path::with_root(&[unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
}],
deleted: vec![],
copied: vec![],
moved: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
#[test]
fn test_delete_file() {
let mut directory = Directory::root();
directory.insert_file(unsound::path::new("banana.rs"), File::new(b"use banana"));
let new_directory = Directory::root();
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![],
deleted: vec![DeleteFile {
path: Path::with_root(&[unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
}],
moved: vec![],
copied: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
/* TODO(fintan): Move is not detected yet
#[test]
fn test_moved_file() {
let mut directory = Directory::root();
directory.insert_file(&unsound::path::new("mod.rs"), File::new(b"use banana"));
let mut new_directory = Directory::root();
new_directory.insert_file(&unsound::path::new("banana.rs"), File::new(b"use banana"));
let diff = Diff::diff(directory, new_directory).expect("diff failed");
assert_eq!(diff, Diff::new())
}
*/
#[test]
fn test_modify_file() {
let mut directory = Directory::root();
directory.insert_file(unsound::path::new("banana.rs"), File::new(b"use banana"));
let mut new_directory = Directory::root();
new_directory.insert_file(unsound::path::new("banana.rs"), File::new(b"use banana;"));
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![ModifiedFile {
path: Path::with_root(&[unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
eof: None,
}],
};
assert_eq!(diff, expected_diff)
}
#[test]
fn test_create_directory() {
let directory = Directory::root();
let mut new_directory = Directory::root();
new_directory.insert_file(
unsound::path::new("src/banana.rs"),
File::new(b"use banana"),
);
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![CreateFile {
path: Path::with_root(&[
unsound::label::new("src"),
unsound::label::new("banana.rs"),
]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
}],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
#[test]
fn test_delete_directory() {
let mut directory = Directory::root();
directory.insert_file(
unsound::path::new("src/banana.rs"),
File::new(b"use banana"),
);
let new_directory = Directory::root();
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![],
deleted: vec![DeleteFile {
path: Path::with_root(&[
unsound::label::new("src"),
unsound::label::new("banana.rs"),
]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
}],
moved: vec![],
copied: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
#[test]
fn test_modify_file_directory() {
let mut directory = Directory::root();
directory.insert_file(
unsound::path::new("src/banana.rs"),
File::new(b"use banana"),
);
let mut new_directory = Directory::root();
new_directory.insert_file(
unsound::path::new("src/banana.rs"),
File::new(b"use banana;"),
);
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![ModifiedFile {
path: Path::with_root(&[
unsound::label::new("src"),
unsound::label::new("banana.rs"),
]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
eof: None,
}],
};
assert_eq!(diff, expected_diff)
}
/* TODO(fintan): Tricky stuff
#[test]
fn test_disjoint_directories() {
let mut directory = Directory::root();
directory.insert_file(
&unsound::path::new("foo/src/banana.rs"),
File::new(b"use banana"),
);
let mut other_directory = Directory::root();
other_directory.insert_file(
&unsound::path::new("bar/src/pineapple.rs"),
File::new(b"use pineapple"),
);
let diff = Diff::diff(directory, other_directory).expect("diff failed");
let expected_diff = Diff {
created: vec![CreateFile(Path::from_labels(
unsound::label::new("bar"),
&[
unsound::label::new("src"),
unsound::label::new("pineapple.rs"),
],
))],
deleted: vec![DeleteFile(Path::from_labels(
unsound::label::new("foo"),
&[unsound::label::new("src"), unsound::label::new("banana.rs")],
))],
moved: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
*/
}
-75
View File
@@ -229,78 +229,3 @@ impl<'a> TryFrom<git2::Diff<'a>> for Diff {
Ok(diff)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_both_missing_eof_newline() {
let buf = r#"
diff --git a/.env b/.env
index f89e4c0..7c56eb7 100644
--- a/.env
+++ b/.env
@@ -1 +1 @@
-hello=123
\ No newline at end of file
+hello=1234
\ No newline at end of file
"#;
let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
let diff = Diff::try_from(diff).unwrap();
assert_eq!(diff.modified[0].eof, Some(EofNewLine::BothMissing));
}
#[test]
fn test_none_missing_eof_newline() {
let buf = r#"
diff --git a/.env b/.env
index f89e4c0..7c56eb7 100644
--- a/.env
+++ b/.env
@@ -1 +1 @@
-hello=123
+hello=1234
"#;
let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
let diff = Diff::try_from(diff).unwrap();
assert_eq!(diff.modified[0].eof, None);
}
// TODO(xphoniex): uncomment once libgit2 has fixed the bug
//#[test]
fn test_old_missing_eof_newline() {
let buf = r#"
diff --git a/.env b/.env
index f89e4c0..7c56eb7 100644
--- a/.env
+++ b/.env
@@ -1 +1 @@
-hello=123
\ No newline at end of file
+hello=1234
"#;
let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
let diff = Diff::try_from(diff).unwrap();
assert_eq!(diff.modified[0].eof, Some(EofNewLine::OldMissing));
}
// TODO(xphoniex): uncomment once libgit2 has fixed the bug
//#[test]
fn test_new_missing_eof_newline() {
let buf = r#"
diff --git a/.env b/.env
index f89e4c0..7c56eb7 100644
--- a/.env
+++ b/.env
@@ -1 +1 @@
-hello=123
+hello=1234
\ No newline at end of file
"#;
let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
let diff = Diff::try_from(diff).unwrap();
assert_eq!(diff.modified[0].eof, Some(EofNewLine::NewMissing));
}
}
+2 -197
View File
@@ -504,7 +504,8 @@ impl Directory {
}
}
pub(crate) fn from_hash_map(files: HashMap<Path, NonEmpty<(Label, File)>>) -> Self {
/// Creates a `Directory` from a HashMap `files`.
pub fn from_hash_map(files: HashMap<Path, NonEmpty<(Label, File)>>) -> Self {
let mut directory: Self = Directory::root();
for (path, files) in files.into_iter() {
@@ -524,199 +525,3 @@ impl Directory {
directory
}
}
#[cfg(test)]
pub mod tests {
#[cfg(test)]
mod list_directory {
use crate::file_system::{unsound, Directory, File, SystemType};
#[test]
fn root_files() {
let mut directory = Directory::root();
directory.insert_file(
unsound::path::new("foo.hs"),
File::new(b"module BananaFoo ..."),
);
directory.insert_file(
unsound::path::new("bar.hs"),
File::new(b"module BananaBar ..."),
);
directory.insert_file(
unsound::path::new("baz.hs"),
File::new(b"module BananaBaz ..."),
);
assert_eq!(
directory.list_directory(),
vec![
SystemType::file(unsound::label::new("bar.hs")),
SystemType::file(unsound::label::new("baz.hs")),
SystemType::file(unsound::label::new("foo.hs")),
]
);
}
}
#[cfg(test)]
mod find_file {
use crate::file_system::{unsound, *};
#[test]
fn in_root() {
let file = File::new(b"module Banana ...");
let mut directory = Directory::root();
directory.insert_file(unsound::path::new("foo.hs"), file.clone());
assert_eq!(
directory.find_file(unsound::path::new("foo.hs")),
Some(file)
);
}
#[test]
fn file_does_not_exist() {
let file_path = unsound::path::new("bar.hs");
let file = File::new(b"module Banana ...");
let mut directory = Directory::root();
directory.insert_file(unsound::path::new("foo.hs"), file);
assert_eq!(directory.find_file(file_path), None)
}
}
#[cfg(test)]
mod directory_size {
use crate::file_system::{unsound, Directory, File};
use nonempty::NonEmpty;
#[test]
fn root_directory_files() {
let mut root = Directory::root();
root.insert_files(
&[],
NonEmpty::from((
(
unsound::label::new("main.rs"),
File::new(b"println!(\"Hello, world!\")"),
),
vec![(
unsound::label::new("lib.rs"),
File::new(b"struct Hello(String)"),
)],
)),
);
assert_eq!(root.size(), 45);
}
}
#[cfg(test)]
mod properties {
use crate::file_system::{unsound, *};
use nonempty::NonEmpty;
use proptest::{collection, prelude::*};
use std::collections::HashMap;
#[test]
fn test_all_directories_and_files() {
let mut directory_map = HashMap::new();
let path1 = unsound::path::new("foo/bar/baz");
let file1 = (unsound::label::new("monadic.rs"), File::new(&[]));
let file2 = (unsound::label::new("oscoin.rs"), File::new(&[]));
directory_map.insert(path1, NonEmpty::from((file1, vec![file2])));
let path2 = unsound::path::new("foor/bar/quuz");
let file3 = (unsound::label::new("radicle.rs"), File::new(&[]));
directory_map.insert(path2, NonEmpty::new(file3));
assert!(prop_all_directories_and_files(directory_map))
}
fn label_strategy() -> impl Strategy<Value = Label> {
// ASCII regex, excluding '/' because of posix file paths
"[ -.|0-~]+".prop_map(|label| unsound::label::new(&label))
}
fn path_strategy(max_size: usize) -> impl Strategy<Value = Path> {
(
label_strategy(),
collection::vec(label_strategy(), 0..max_size),
)
.prop_map(|(label, labels)| Path((label, labels).into()))
}
fn file_strategy() -> impl Strategy<Value = (Label, File)> {
// ASCII regex, see: https://catonmat.net/my-favorite-regex
(label_strategy(), "[ -~]*")
.prop_map(|(name, contents)| (name, File::new(contents.as_bytes())))
}
fn directory_map_strategy(
path_size: usize,
n_files: usize,
map_size: usize,
) -> impl Strategy<Value = HashMap<Path, NonEmpty<(Label, File)>>> {
collection::hash_map(
path_strategy(path_size),
collection::vec(file_strategy(), 1..n_files).prop_map(|files| {
NonEmpty::from_slice(&files).expect("Strategy generated files of length 0")
}),
0..map_size,
)
}
// TODO(fintan): This is a bit slow. Could be time to benchmark some functions.
proptest! {
#[test]
fn prop_test_all_directories_and_files(directory_map in directory_map_strategy(10, 10, 10)) {
prop_all_directories_and_files(directory_map);
}
}
fn prop_all_directories_and_files(
directory_map: HashMap<Path, NonEmpty<(Label, File)>>,
) -> bool {
let mut new_directory_map = HashMap::new();
for (path, files) in directory_map {
new_directory_map.insert(path.clone(), files);
}
let directory = Directory::from_hash_map(new_directory_map.clone());
for (directory_path, files) in new_directory_map {
for (file_name, _) in files.iter() {
let mut path = directory_path.clone();
if directory.find_directory(path.clone()).is_none() {
eprintln!("Search Directory: {:#?}", directory);
eprintln!("Path to find: {:#?}", path);
return false;
}
path.push(file_name.clone());
if directory.find_file(path.clone()).is_none() {
eprintln!("Search Directory: {:#?}", directory);
eprintln!("Path to find: {:#?}", path);
return false;
}
}
}
true
}
#[test]
fn test_file_name_is_same_as_root() {
// This test ensures that if the name is the same the root of the
// directory, that search_path.split_last() doesn't toss away the prefix.
let path = unsound::path::new("foo/bar/~");
let mut directory_map = HashMap::new();
directory_map.insert(path, NonEmpty::new((Label::root(), File::new(b"root"))));
assert!(prop_all_directories_and_files(directory_map));
}
}
}
-28
View File
@@ -440,31 +440,3 @@ impl TryFrom<path::PathBuf> for Path {
Ok(path)
}
}
#[cfg(test)]
mod tests {
#[cfg(test)]
mod path {
use crate::file_system::unsound;
#[test]
fn split_last_root_and_foo() {
let path = unsound::path::new("foo");
assert_eq!(path.split_last(), (vec![], unsound::label::new("foo")));
}
#[test]
fn split_last_same_labels() {
// An interesting case for when first == last, but doesn't imply a singleton
// Path.
let path = unsound::path::new("foo/bar/foo");
assert_eq!(
path.split_last(),
(
vec![unsound::label::new("foo"), unsound::label::new("bar")],
unsound::label::new("foo")
)
);
}
}
}
+2 -1
View File
@@ -103,8 +103,9 @@ pub mod syntax;
#[cfg(feature = "syntax")]
pub use syntax::SYNTAX_SET;
pub mod tree;
// Private modules
mod nonempty;
mod tree;
pub use crate::vcs::git;
+6 -694
View File
@@ -15,6 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#![allow(missing_docs)]
use crate::nonempty::split_last;
use nonempty::NonEmpty;
use std::cmp::Ordering;
@@ -152,7 +154,7 @@ impl<K, A> SubTree<K, A> {
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Tree<K, A>(pub(crate) NonEmpty<SubTree<K, A>>);
pub struct Tree<K, A>(pub NonEmpty<SubTree<K, A>>);
impl<K, A> From<Tree<K, A>> for Forest<K, A> {
fn from(tree: Tree<K, A>) -> Self {
@@ -161,17 +163,17 @@ impl<K, A> From<Tree<K, A>> for Forest<K, A> {
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Forest<K, A>(pub(crate) Option<Tree<K, A>>);
pub struct Forest<K, A>(pub Option<Tree<K, A>>);
impl<K, A> Tree<K, A> {
/// Create a new `Tree` containing a single `Branch` given
/// the key and sub-tree.
fn branch(key: K, forest: Self) -> Self {
pub fn branch(key: K, forest: Self) -> Self {
Tree(NonEmpty::new(SubTree::branch(key, forest)))
}
/// Create a new `Tree` containing a single `Node`.
fn node(key: K, value: A) -> Self {
pub fn node(key: K, value: A) -> Self {
Tree(NonEmpty::new(SubTree::Node { key, value }))
}
@@ -499,693 +501,3 @@ impl<K, A> Forest<K, A> {
self.0.iter().flat_map(|trees| trees.iter_keys())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[derive(Debug, Clone, PartialEq, Eq)]
struct TestNode {
id: u32,
}
#[test]
fn test_is_empty() {
let mut tree = Forest::root();
assert!(tree.is_empty());
let a_node = TestNode { id: 1 };
tree.insert(NonEmpty::new(String::from("a")), a_node);
assert!(!tree.is_empty());
}
#[test]
fn test_insert_root_node() {
let a_label = String::from("a");
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
tree.insert(NonEmpty::new(a_label), a_node.clone());
assert_eq!(tree, Forest(Some(Tree::node(String::from("a"), a_node))));
}
#[test]
fn test_insert_with_prepending_root_nodes() {
let a_label = String::from("a");
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 2 };
tree.insert_with(
NonEmpty::new(a_label.clone()),
NonEmpty::new(a_node.clone()),
|nodes| nodes.insert(0, a_node.clone()),
);
tree.insert_with(
NonEmpty::new(a_label),
NonEmpty::new(b_node.clone()),
|nodes| nodes.insert(0, b_node.clone()),
);
assert_eq!(
tree,
Forest(Some(Tree::node(
String::from("a"),
NonEmpty::from((b_node, vec![a_node]))
)))
);
}
#[test]
fn test_insert_with_prepending_branch_nodes() {
let a_label = String::from("a");
let b_label = String::from("b");
let path = NonEmpty::from((a_label, vec![b_label]));
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 2 };
tree.insert_with(path.clone(), NonEmpty::new(a_node.clone()), |nodes| {
nodes.insert(0, a_node.clone())
});
tree.insert_with(path, NonEmpty::new(b_node.clone()), |nodes| {
nodes.insert(0, b_node.clone())
});
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::node(String::from("b"), NonEmpty::from((b_node, vec![a_node])))
)))
);
}
#[test]
fn test_insert_single_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let path = NonEmpty::from((a_label, vec![b_label, c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(path, c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(String::from("b"), Tree::node(String::from("c"), c_node))
)))
);
}
#[test]
fn test_insert_two_nodes() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let d_path = NonEmpty::from((a_label, vec![b_label, d_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(c_path, c_node.clone());
let d_node = TestNode { id: 3 };
tree.insert(d_path, d_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree(NonEmpty::from((
SubTree::Node {
key: String::from("c"),
value: c_node
},
vec![SubTree::Node {
key: String::from("d"),
value: d_node
}]
)))
)
)))
);
}
#[test]
fn test_insert_replaces_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let c_path = NonEmpty::from((a_label, vec![b_label, c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(c_path.clone(), c_node);
let new_c_node = TestNode { id: 3 };
tree.insert(c_path, new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree(NonEmpty::new(SubTree::Node {
key: String::from("c"),
value: new_c_node
},))
)
)))
);
}
#[test]
fn test_insert_replaces_root_node() {
let c_label = String::from("c");
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(NonEmpty::new(c_label.clone()), c_node);
let new_c_node = TestNode { id: 3 };
tree.insert(NonEmpty::new(c_label), new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::node(String::from("c"), new_c_node)))
);
}
#[test]
fn test_insert_replaces_branch_node() {
let a_label = String::from("a");
let c_label = String::from("c");
let c_path = NonEmpty::from((a_label, vec![c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(c_path.clone(), c_node);
let new_c_node = TestNode { id: 3 };
tree.insert(c_path, new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::node(String::from("c"), new_c_node),
)))
);
}
#[test]
fn test_insert_replaces_branch_with_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(c_path, c_node);
let new_c_node = TestNode { id: 3 };
tree.insert(NonEmpty::from((a_label, vec![b_label])), new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::node(String::from("b"), new_c_node),
)))
);
}
#[test]
fn test_insert_replaces_node_with_branch() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let b_path = NonEmpty::from((a_label.clone(), vec![b_label.clone()]));
let c_path = NonEmpty::from((a_label, vec![b_label, c_label]));
let mut tree = Forest::root();
let b_node = TestNode { id: 1 };
tree.insert(b_path, b_node);
let new_c_node = TestNode { id: 3 };
tree.insert(c_path, new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree(NonEmpty::new(SubTree::Node {
key: String::from("c"),
value: new_c_node
},))
)
)))
);
}
#[test]
fn test_insert_replaces_node_with_branch_foo() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let b_path = NonEmpty::from((a_label.clone(), vec![b_label.clone()]));
let d_path = NonEmpty::from((a_label, vec![b_label, c_label, d_label]));
let mut tree = Forest::root();
let b_node = TestNode { id: 1 };
tree.insert(b_path, b_node);
let d_node = TestNode { id: 3 };
tree.insert(d_path, d_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree::branch(String::from("c"), Tree::node(String::from("d"), d_node))
)
)))
);
}
#[test]
fn test_insert_two_nodes_out_of_order() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let d_path = NonEmpty::from((a_label, vec![b_label, d_label]));
let mut tree = Forest::root();
let d_node = TestNode { id: 3 };
tree.insert(d_path, d_node.clone());
let c_node = TestNode { id: 1 };
tree.insert(c_path, c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree(NonEmpty::from((
SubTree::Node {
key: String::from("c"),
value: c_node
},
vec![SubTree::Node {
key: String::from("d"),
value: d_node
}]
)))
)
)))
);
}
#[test]
fn test_insert_branch() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let e_label = String::from("e");
let f_label = String::from("f");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let d_path = NonEmpty::from((a_label.clone(), vec![b_label, d_label]));
let f_path = NonEmpty::from((a_label, vec![e_label, f_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
let d_node = TestNode { id: 3 };
let f_node = TestNode { id: 2 };
tree.insert(d_path, d_node.clone());
tree.insert(c_path, c_node.clone());
tree.insert(f_path, f_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree(NonEmpty::from((
SubTree::Branch {
key: String::from("b"),
forest: Box::new(Tree(NonEmpty::from((
SubTree::Node {
key: String::from("c"),
value: c_node
},
vec![SubTree::Node {
key: String::from("d"),
value: d_node
}]
))))
},
vec![SubTree::Branch {
key: String::from("e"),
forest: Box::new(Tree::node(String::from("f"), f_node))
},]
)))
)))
);
}
#[test]
fn test_insert_two_branches() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let e_label = String::from("e");
let f_label = String::from("f");
let c_path = NonEmpty::from((a_label, vec![b_label, c_label]));
let f_path = NonEmpty::from((d_label, vec![e_label, f_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
let f_node = TestNode { id: 2 };
tree.insert(c_path, c_node.clone());
tree.insert(f_path, f_node.clone());
assert_eq!(
tree,
Forest(Some(Tree(NonEmpty::from((
SubTree::Branch {
key: String::from("a"),
forest: Box::new(Tree::branch(
String::from("b"),
Tree::node(String::from("c"), c_node)
)),
},
vec![SubTree::Branch {
key: String::from("d"),
forest: Box::new(Tree::branch(
String::from("e"),
Tree::node(String::from("f"), f_node)
))
}]
)))))
);
}
#[test]
fn test_insert_branches_and_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let e_label = String::from("e");
let f_label = String::from("f");
let g_label = String::from("g");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let d_path = NonEmpty::from((a_label.clone(), vec![b_label, d_label]));
let e_path = NonEmpty::from((a_label.clone(), vec![e_label]));
let g_path = NonEmpty::from((a_label, vec![f_label, g_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
let d_node = TestNode { id: 3 };
let e_node = TestNode { id: 2 };
let g_node = TestNode { id: 2 };
tree.insert(d_path, d_node.clone());
tree.insert(c_path, c_node.clone());
tree.insert(e_path, e_node.clone());
tree.insert(g_path, g_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree(NonEmpty::from((
SubTree::Branch {
key: String::from("b"),
forest: Box::new(Tree(NonEmpty::from((
SubTree::Node {
key: String::from("c"),
value: c_node
},
vec![SubTree::Node {
key: String::from("d"),
value: d_node
}]
))))
},
vec![
SubTree::Node {
key: String::from("e"),
value: e_node
},
SubTree::Branch {
key: String::from("f"),
forest: Box::new(Tree::node(String::from("g"), g_node))
},
]
)))
)))
);
}
#[test]
fn test_find_root_node() {
let a_label = String::from("a");
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
tree.insert(NonEmpty::new(a_label), a_node.clone());
assert_eq!(
tree.find(NonEmpty::new(String::from("a"))),
Some(&SubTree::Node {
key: String::from("a"),
value: a_node
})
);
assert_eq!(tree.find(NonEmpty::new(String::from("b"))), None);
}
#[test]
fn test_find_branch_and_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let path = NonEmpty::from((a_label, vec![b_label, c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(path, c_node.clone());
assert_eq!(
tree.find(NonEmpty::new(String::from("a"))),
Some(&SubTree::Branch {
key: String::from("a"),
forest: Box::new(Tree::branch(
String::from("b"),
Tree::node(String::from("c"), c_node.clone())
))
})
);
assert_eq!(
tree.find(NonEmpty::from((String::from("a"), vec![String::from("b")]))),
Some(&SubTree::Branch {
key: String::from("b"),
forest: Box::new(Tree::node(String::from("c"), c_node.clone()))
})
);
assert_eq!(
tree.find(NonEmpty::from((
String::from("a"),
vec![String::from("b"), String::from("c")]
))),
Some(&SubTree::Node {
key: String::from("c"),
value: c_node
})
);
assert_eq!(tree.find(NonEmpty::new(String::from("b"))), None);
assert_eq!(
tree.find(NonEmpty::from((String::from("a"), vec![String::from("c")]))),
None
);
}
#[test]
fn test_maximum_by_root_nodes() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(NonEmpty::new(String::from("a")), a_node.clone());
tree.insert(NonEmpty::new(String::from("b")), b_node.clone());
assert_eq!(tree.maximum_by(|a, b| a.id.cmp(&b.id)), Some(&b_node));
assert_eq!(
tree.maximum_by(|a, b| a.id.cmp(&b.id).reverse()),
Some(&a_node)
);
}
#[test]
fn test_maximum_by_branch_and_node() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(
NonEmpty::from((String::from("c"), vec![String::from("a")])),
a_node.clone(),
);
tree.insert(NonEmpty::new(String::from("b")), b_node.clone());
assert_eq!(tree.maximum_by(|a, b| a.id.cmp(&b.id)), Some(&b_node));
assert_eq!(
tree.maximum_by(|a, b| a.id.cmp(&b.id).reverse()),
Some(&a_node)
);
}
#[test]
fn test_maximum_by_branch_and_branch() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(
NonEmpty::from((String::from("c"), vec![String::from("a")])),
a_node.clone(),
);
tree.insert(
NonEmpty::from((String::from("d"), vec![String::from("a")])),
b_node.clone(),
);
assert_eq!(tree.maximum_by(|a, b| a.id.cmp(&b.id)), Some(&b_node));
assert_eq!(
tree.maximum_by(|a, b| a.id.cmp(&b.id).reverse()),
Some(&a_node)
);
}
#[test]
fn test_maximum_by_branch_nodes() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(
NonEmpty::from((String::from("c"), vec![String::from("a")])),
a_node.clone(),
);
tree.insert(
NonEmpty::from((String::from("c"), vec![String::from("b")])),
b_node.clone(),
);
assert_eq!(tree.maximum_by(|a, b| a.id.cmp(&b.id)), Some(&b_node));
assert_eq!(
tree.maximum_by(|a, b| a.id.cmp(&b.id).reverse()),
Some(&a_node)
);
}
#[test]
fn test_fold_root_nodes() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(NonEmpty::new(String::from("a")), a_node);
tree.insert(NonEmpty::new(String::from("b")), b_node);
assert_eq!(tree.iter().fold(0, |b, a| a.id + b), 4);
}
}
+5
View File
@@ -157,6 +157,11 @@ impl<Repo, A, Error> Browser<Repo, A, Error> {
self.history.clone()
}
/// Get the current `History` the `Browser` is viewing as a ref.
pub fn as_history(&self) -> &History<A> {
&self.history
}
/// Set the `History` the `Browser` should view.
pub fn set(&mut self, history: History<A>) {
self.history = history;
+2 -590
View File
@@ -69,14 +69,14 @@ pub use radicle_git_ext::Oid;
/// Provides ways of selecting a particular reference/revision.
mod reference;
pub use reference::{Ref, Rev};
pub use reference::{ParseError, Ref, Rev};
mod repo;
pub use repo::{History, Repository, RepositoryRef};
pub mod error;
mod ext;
pub mod ext;
/// Provides the data for talking about branches.
pub mod branch;
@@ -1181,591 +1181,3 @@ impl<'a> Browser<'a> {
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "gh-actions"))]
#[test]
// An issue with submodules, see: https://github.com/radicle-dev/radicle-surf/issues/54
fn test_submodule_failure() {
let repo = Repository::new("..").unwrap();
let browser = Browser::new(&repo, Branch::local("main")).unwrap();
browser.get_directory().unwrap();
}
#[cfg(test)]
mod namespace {
use super::*;
use pretty_assertions::{assert_eq, assert_ne};
#[test]
fn switch_to_banana() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let mut browser = Browser::new_with_namespace(
&repo,
&Namespace::try_from("golden")?,
Branch::local("master"),
)?;
let history = browser.history.clone();
browser.branch(Branch::local("banana"))?;
assert_ne!(history, browser.history);
Ok(())
}
#[test]
fn me_namespace() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let browser = Browser::new(&repo, Branch::local("master"))?;
let history = browser.history.clone();
assert_eq!(browser.which_namespace(), Ok(None));
let browser = browser
.switch_namespace(&Namespace::try_from("me")?, Branch::local("feature/#1194"))?;
assert_eq!(
browser.which_namespace(),
Ok(Some(Namespace::try_from("me")?))
);
assert_eq!(history, browser.history);
let expected_branches: Vec<Branch> = vec![Branch::local("feature/#1194")];
let mut branches = browser.list_branches(RefScope::Local)?;
branches.sort();
assert_eq!(expected_branches, branches);
let expected_branches: Vec<Branch> = vec![Branch::remote("feature/#1194", "fein")];
let mut branches = browser.list_branches(RefScope::Remote {
name: Some("fein".to_string()),
})?;
branches.sort();
assert_eq!(expected_branches, branches);
Ok(())
}
#[test]
fn golden_namespace() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let browser = Browser::new(&repo, Branch::local("master"))?;
let history = browser.history.clone();
assert_eq!(browser.which_namespace(), Ok(None));
let golden_browser = browser
.switch_namespace(&Namespace::try_from("golden")?, Branch::local("master"))?;
assert_eq!(
golden_browser.which_namespace(),
Ok(Some(Namespace::try_from("golden")?))
);
assert_eq!(history, golden_browser.history);
let expected_branches: Vec<Branch> =
vec![Branch::local("banana"), Branch::local("master")];
let mut branches = golden_browser.list_branches(RefScope::Local)?;
branches.sort();
assert_eq!(expected_branches, branches);
let expected_branches: Vec<Branch> = vec![
Branch::remote("fakie/bigspin", "kickflip"),
Branch::remote("heelflip", "kickflip"),
Branch::remote("v0.1.0", "kickflip"),
];
let mut branches = golden_browser.list_branches(RefScope::Remote {
name: Some("kickflip".to_string()),
})?;
branches.sort();
assert_eq!(expected_branches, branches);
Ok(())
}
#[test]
fn silver_namespace() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let browser = Browser::new(&repo, Branch::local("master"))?;
let history = browser.history.clone();
assert_eq!(browser.which_namespace(), Ok(None));
let silver_browser = browser.switch_namespace(
&Namespace::try_from("golden/silver")?,
Branch::local("master"),
)?;
assert_eq!(
silver_browser.which_namespace(),
Ok(Some(Namespace::try_from("golden/silver")?))
);
assert_ne!(history, silver_browser.history);
let expected_branches: Vec<Branch> = vec![Branch::local("master")];
let mut branches = silver_browser.list_branches(RefScope::All)?;
branches.sort();
assert_eq!(expected_branches, branches);
Ok(())
}
}
#[cfg(test)]
mod rev {
use super::{Branch, Browser, Error, Oid, Repository, TagName};
use std::str::FromStr;
// **FIXME**: This seems to break occasionally on
// buildkite. For some reason the commit
// 3873745c8f6ffb45c990eb23b491d4b4b6182f95, which is on master
// (currently HEAD), is not found. It seems to load the history
// with d6880352fc7fda8f521ae9b7357668b17bb5bad5 as the HEAD.
//
// To temporarily fix this, we need to select "New Build" from the build kite
// build page that's failing.
// * Under "Message" put whatever you want.
// * Under "Branch" put in the branch you're working on.
// * Expand "Options" and select "clean checkout".
#[test]
fn _master() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(Branch::remote("master", "origin"))?;
let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
assert!(
browser
.history
.find(|commit| if commit.id == commit1 {
Some(commit.clone())
} else {
None
})
.is_some(),
"commit_id={}, history =\n{:#?}",
commit1,
browser.history
);
let commit2 = Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?;
assert!(
browser
.history
.find(|commit| if commit.id == commit2 {
Some(commit.clone())
} else {
None
})
.is_some(),
"commit_id={}, history =\n{:#?}",
commit2,
browser.history
);
Ok(())
}
#[test]
fn commit() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?)?;
let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
assert!(browser
.history
.find(|commit| if commit.id == commit1 {
Some(commit.clone())
} else {
None
})
.is_some());
Ok(())
}
#[test]
fn commit_parents() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?)?;
let commit = browser.history.first();
assert_eq!(
commit.parents,
vec![Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?]
);
Ok(())
}
#[test]
fn commit_short() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(browser.oid("3873745c8")?)?;
let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
assert!(browser
.history
.find(|commit| if commit.id == commit1 {
Some(commit.clone())
} else {
None
})
.is_some());
Ok(())
}
#[test]
fn tag() -> Result<(), Error> {
let repo = Repository::new("./data/git-platinum")?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(TagName::new("v0.2.0"))?;
let commit1 = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?;
assert_eq!(browser.history.first().id, commit1);
Ok(())
}
}
#[cfg(test)]
mod last_commit {
use crate::{
file_system::{unsound, Path},
vcs::git::{Branch, Browser, Oid, Repository},
};
use std::str::FromStr;
#[test]
fn readme_missing_and_memory() {
let repo = Repository::new("./data/git-platinum")
.expect("Could not retrieve ./data/git-platinum as git repository");
let mut browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
// Set the browser history to the initial commit
let commit = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")
.expect("Failed to parse SHA");
browser.commit(commit).unwrap();
let head_commit = browser.get().0.first().clone();
// memory.rs is commited later so it should not exist here.
let memory_last_commit = browser
.last_commit(Path::with_root(&[
unsound::label::new("src"),
unsound::label::new("memory.rs"),
]))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(memory_last_commit, None);
// README.md exists in this commit.
let readme_last_commit = browser
.last_commit(Path::with_root(&[unsound::label::new("README.md")]))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(readme_last_commit, Some(head_commit.id));
}
#[test]
fn folder_svelte() {
let repo = Repository::new("./data/git-platinum")
.expect("Could not retrieve ./data/git-platinum as git repository");
let mut browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
// Check that last commit is the actual last commit even if head commit differs.
let commit = Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97")
.expect("Could not parse SHA");
browser.commit(commit).unwrap();
let expected_commit_id =
Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap();
let folder_svelte = browser
.last_commit(unsound::path::new("~/examples/Folder.svelte"))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(folder_svelte, Some(expected_commit_id));
}
#[test]
fn nest_directory() {
let repo = Repository::new("./data/git-platinum")
.expect("Could not retrieve ./data/git-platinum as git repository");
let mut browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
// Check that last commit is the actual last commit even if head commit differs.
let commit = Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97")
.expect("Failed to parse SHA");
browser.commit(commit).unwrap();
let expected_commit_id =
Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977").unwrap();
let nested_directory_tree_commit_id = browser
.last_commit(unsound::path::new(
"~/this/is/a/really/deeply/nested/directory/tree",
))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(nested_directory_tree_commit_id, Some(expected_commit_id));
}
#[test]
#[cfg(not(windows))]
fn can_get_last_commit_for_special_filenames() {
let repo = Repository::new("./data/git-platinum")
.expect("Could not retrieve ./data/git-platinum as git repository");
let mut browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
// Check that last commit is the actual last commit even if head commit differs.
let commit = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02")
.expect("Failed to parse SHA");
browser.commit(commit).unwrap();
let expected_commit_id =
Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap();
let backslash_commit_id = browser
.last_commit(unsound::path::new("~/special/faux\\path"))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(backslash_commit_id, Some(expected_commit_id));
let ogre_commit_id = browser
.last_commit(unsound::path::new("~/special/👹👹👹"))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(ogre_commit_id, Some(expected_commit_id));
}
#[test]
fn root() {
let repo = Repository::new("./data/git-platinum")
.expect("Could not retrieve ./data/git-platinum as git repository");
let browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
let root_last_commit_id = browser
.last_commit(Path::root())
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(root_last_commit_id, Some(browser.get().first().id));
}
}
#[cfg(test)]
mod diff {
use crate::{diff::*, vcs::git::*};
use std::str::FromStr;
#[test]
fn test_initial_diff() -> Result<(), Error> {
use file_system::*;
use pretty_assertions::assert_eq;
let oid = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?;
let repo = Repository::new("./data/git-platinum")?;
let commit = repo.0.find_commit(oid.into()).unwrap();
assert!(commit.parents().count() == 0);
assert!(commit.parent(0).is_err());
let bro = Browser::new(&repo, Branch::local("master"))?;
let diff = bro.initial_diff(oid)?;
let expected_diff = Diff {
created: vec![CreateFile {
path: Path::with_root(&[unsound::label::new("README.md")]),
diff: FileDiff::Plain {
hunks: vec![Hunk {
header: Line(b"@@ -0,0 +1 @@\n".to_vec()),
lines: vec![
LineDiff::addition(b"This repository is a data source for the Upstream front-end tests.\n".to_vec(), 1),
]
}].into()
},
}],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![],
};
assert_eq!(expected_diff, diff);
Ok(())
}
#[test]
fn test_diff() -> Result<(), Error> {
use file_system::*;
use pretty_assertions::assert_eq;
let repo = Repository::new("./data/git-platinum")?;
let commit = repo
.0
.find_commit(git2::Oid::from_str(
"80bacafba303bf0cdf6142921f430ff265f25095",
)?)
.unwrap();
let parent = commit.parent(0)?;
let bro = Browser::new(&repo, Branch::local("master"))?;
let diff = bro.diff(parent.id().into(), commit.id().into())?;
let expected_diff = Diff {
created: vec![],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![ModifiedFile {
path: Path::with_root(&[unsound::label::new("README.md")]),
diff: FileDiff::Plain {
hunks: vec![Hunk {
header: Line(b"@@ -1 +1,2 @@\n".to_vec()),
lines: vec![
LineDiff::deletion(b"This repository is a data source for the Upstream front-end tests.\n".to_vec(), 1),
LineDiff::addition(b"This repository is a data source for the Upstream front-end tests and the\n".to_vec(), 1),
LineDiff::addition(b"[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n".to_vec(), 2),
]
}].into()
},
eof: None,
}]
};
assert_eq!(expected_diff, diff);
Ok(())
}
#[cfg(feature = "serialize")]
#[test]
fn test_diff_serde() -> Result<(), Error> {
use file_system::*;
let diff = Diff {
created: vec![CreateFile{path: unsound::path::new("LICENSE"), diff: FileDiff::Plain { hunks: Hunks::default() }}],
deleted: vec![],
moved: vec![
MoveFile {
old_path: unsound::path::new("CONTRIBUTING"),
new_path: unsound::path::new("CONTRIBUTING.md")
}
],
copied: vec![],
modified: vec![ModifiedFile {
path: Path::with_root(&[unsound::label::new("README.md")]),
diff: FileDiff::Plain {
hunks: vec![Hunk {
header: Line(b"@@ -1 +1,2 @@\n".to_vec()),
lines: vec![
LineDiff::deletion(b"This repository is a data source for the Upstream front-end tests.\n".to_vec(), 1),
LineDiff::addition(b"This repository is a data source for the Upstream front-end tests and the\n".to_vec(), 1),
LineDiff::addition(b"[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n".to_vec(), 2),
LineDiff::context(b"\n".to_vec(), 3, 4),
]
}].into()
},
eof: None,
}]
};
let eof: Option<u8> = None;
let json = serde_json::json!({
"created": [{"path": "LICENSE", "diff": {
"type": "plain",
"hunks": []
},
}],
"deleted": [],
"moved": [{ "oldPath": "CONTRIBUTING", "newPath": "CONTRIBUTING.md" }],
"copied": [],
"modified": [{
"path": "README.md",
"diff": {
"type": "plain",
"hunks": [{
"header": "@@ -1 +1,2 @@\n",
"lines": [
{ "lineNum": 1,
"line": "This repository is a data source for the Upstream front-end tests.\n",
"type": "deletion"
},
{ "lineNum": 1,
"line": "This repository is a data source for the Upstream front-end tests and the\n",
"type": "addition"
},
{ "lineNum": 2,
"line": "[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n",
"type": "addition"
},
{ "lineNumOld": 3, "lineNumNew": 4,
"line": "\n",
"type": "context"
}
]
}]
},
"eof" : eof,
}]
});
assert_eq!(serde_json::to_value(&diff).unwrap(), json);
Ok(())
}
}
#[cfg(test)]
mod threading {
use crate::vcs::git::*;
use pretty_assertions::assert_eq;
use std::sync::{Mutex, MutexGuard};
#[test]
fn basic_test() -> Result<(), Error> {
let shared_repo = Mutex::new(Repository::new("./data/git-platinum")?);
let locked_repo: MutexGuard<Repository> = shared_repo.lock().unwrap();
let bro = Browser::new(&*locked_repo, Branch::local("master"))?;
let mut branches = bro.list_branches(RefScope::All)?;
branches.sort();
assert_eq!(
branches,
vec![
Branch::remote("HEAD", "origin"),
Branch::remote("dev", "origin"),
Branch::local("dev"),
Branch::remote("master", "origin"),
Branch::local("master"),
Branch::remote("orange/pineapple", "banana"),
Branch::remote("pineapple", "banana"),
]
);
Ok(())
}
}
}
-31
View File
@@ -197,34 +197,3 @@ impl<'repo> TryFrom<git2::Reference<'repo>> for Branch {
}
}
}
#[cfg(feature = "serialize")]
#[cfg(test)]
pub mod tests {
use proptest::prelude::*;
use test_helpers::roundtrip;
use super::{Branch, BranchName, BranchType};
proptest! {
#[test]
fn prop_test_branch(branch in branch_strategy()) {
roundtrip::json(branch)
}
}
fn branch_strategy() -> impl Strategy<Value = Branch> {
prop_oneof![
any::<String>().prop_map(|name| Branch {
name: BranchName::new(&name),
locality: BranchType::Local
}),
(any::<String>(), any::<String>()).prop_map(|(name, remote_name)| Branch {
name: BranchName::new(&name),
locality: BranchType::Remote {
name: Some(remote_name),
},
})
]
}
}
+1 -38
View File
@@ -20,7 +20,7 @@ use radicle_git_ext::Oid;
use std::{convert::TryFrom, str};
#[cfg(feature = "serialize")]
use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
/// `Author` is the static information of a [`git2::Signature`].
#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
@@ -166,40 +166,3 @@ impl<'repo> TryFrom<git2::Commit<'repo>> for Commit {
})
}
}
#[cfg(feature = "serialize")]
#[cfg(test)]
pub mod tests {
use proptest::prelude::*;
use radicle_git_ext::Oid;
use test_helpers::roundtrip;
use super::{Author, Commit};
#[cfg(feature = "serialize")]
proptest! {
#[test]
fn prop_test_commits(commit in commits_strategy()) {
roundtrip::json(commit)
}
}
fn commits_strategy() -> impl Strategy<Value = Commit> {
("[a-fA-F0-9]{40}", any::<String>(), any::<i64>()).prop_map(|(id, text, time)| Commit {
id: Oid::from_str(&id).unwrap(),
author: Author {
name: text.clone(),
email: text.clone(),
time: git2::Time::new(time, 0),
},
committer: Author {
name: text.clone(),
email: text.clone(),
time: git2::Time::new(time, 0),
},
message: text.clone(),
summary: text,
parents: vec![Oid::from_str(&id).unwrap(), Oid::from_str(&id).unwrap()],
})
}
}
+2 -45
View File
@@ -15,6 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#![allow(missing_docs)]
/// Try to strip any refs/namespaces, refs/heads, refs/remotes, and
/// refs/tags. If this fails we return the original string.
pub fn try_extract_refname(spec: &str) -> Result<String, String> {
@@ -64,48 +66,3 @@ pub fn is_remote(reference: &git2::Reference) -> bool {
None => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_extract_refname() {
assert_eq!(try_extract_refname("refs/heads/dev"), Ok("dev".to_string()));
assert_eq!(
try_extract_refname("refs/heads/master"),
Ok("master".to_string())
);
assert_eq!(
try_extract_refname("refs/remotes/banana/pineapple"),
Ok("banana/pineapple".to_string())
);
assert_eq!(
try_extract_refname("refs/remotes/origin/master"),
Ok("origin/master".to_string())
);
assert_eq!(
try_extract_refname("refs/namespaces/golden/refs/heads/banana"),
Ok("banana".to_string())
);
assert_eq!(
try_extract_refname("refs/namespaces/golden/refs/tags/v0.1.0"),
Ok("v0.1.0".to_string())
);
assert_eq!(
try_extract_refname("refs/namespaces/golden/refs/namespaces/silver/refs/heads/master"),
Ok("master".to_string())
);
assert_eq!(
try_extract_refname("refs/namespaces/golden/refs/remotes/kickflip/heads/heelflip"),
Ok("kickflip/heelflip".to_string())
);
}
}
+2 -104
View File
@@ -118,8 +118,10 @@ impl fmt::Display for Ref {
}
}
/// Error when parsing a ref.
#[derive(Debug, PartialEq, Eq, Error)]
pub enum ParseError {
/// The parsed ref is malformed.
#[error("the ref provided '{0}' was malformed")]
MalformedRef(String),
}
@@ -196,107 +198,3 @@ impl str::FromStr for Ref {
parser::parse(reference).map_err(|_| ParseError::MalformedRef(reference.to_owned()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn parse_ref() -> Result<(), ParseError> {
assert_eq!(
Ref::from_str("refs/remotes/origin/master"),
Ok(Ref::RemoteBranch {
remote: "origin".to_string(),
name: BranchName::new("master")
})
);
assert_eq!(
Ref::from_str("refs/heads/master"),
Ok(Ref::LocalBranch {
name: BranchName::new("master"),
})
);
assert_eq!(
Ref::from_str("refs/heads/i-am-hyphenated"),
Ok(Ref::LocalBranch {
name: BranchName::new("i-am-hyphenated"),
})
);
assert_eq!(
Ref::from_str("refs/heads/prefix/i-am-hyphenated"),
Ok(Ref::LocalBranch {
name: BranchName::new("prefix/i-am-hyphenated"),
})
);
assert_eq!(
Ref::from_str("refs/tags/v0.0.1"),
Ok(Ref::Tag {
name: TagName::new("v0.0.1")
})
);
assert_eq!(
Ref::from_str("refs/namespaces/moi/refs/remotes/origin/master"),
Ok(Ref::Namespace {
namespace: "moi".to_string(),
reference: Box::new(Ref::RemoteBranch {
remote: "origin".to_string(),
name: BranchName::new("master")
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/moi/refs/namespaces/toi/refs/tags/v1.0.0"),
Ok(Ref::Namespace {
namespace: "moi".to_string(),
reference: Box::new(Ref::Namespace {
namespace: "toi".to_string(),
reference: Box::new(Ref::Tag {
name: TagName::new("v1.0.0")
})
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/me/refs/heads/feature/#1194"),
Ok(Ref::Namespace {
namespace: "me".to_string(),
reference: Box::new(Ref::LocalBranch {
name: BranchName::new("feature/#1194"),
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/me/refs/remotes/fein/heads/feature/#1194"),
Ok(Ref::Namespace {
namespace: "me".to_string(),
reference: Box::new(Ref::RemoteBranch {
remote: "fein".to_string(),
name: BranchName::new("heads/feature/#1194"),
})
})
);
assert_eq!(
Ref::from_str("refs/remotes/master"),
Err(ParseError::MalformedRef("refs/remotes/master".to_owned())),
);
assert_eq!(
Ref::from_str("refs/namespaces/refs/remotes/origin/master"),
Err(ParseError::MalformedRef(
"refs/namespaces/refs/remotes/origin/master".to_owned()
)),
);
Ok(())
}
}
+3 -3
View File
@@ -182,8 +182,8 @@ impl<'a> RepositoryRef<'a> {
Ok(self.repo_ref.set_namespace(namespace)?)
}
/// Get a particular `Commit`.
pub(super) fn get_commit(&self, oid: Oid) -> Result<git2::Commit<'a>, Error> {
/// Get a particular `git2::Commit` of `oid`.
pub fn get_git2_commit(&self, oid: Oid) -> Result<git2::Commit<'a>, Error> {
let commit = self.repo_ref.find_commit(oid.into())?;
Ok(commit)
}
@@ -365,7 +365,7 @@ impl<'a> Vcs<Commit, Error> for RepositoryRef<'a> {
match history_id {
Rev::Ref(reference) => self.reference(reference, |_| None),
Rev::Oid(oid) => {
let commit = self.get_commit(oid)?;
let commit = self.get_git2_commit(oid)?;
self.commit_to_history(commit)
},
}
+35
View File
@@ -0,0 +1,35 @@
[package]
name = "radicle-surf-test"
version = "0.1.0"
edition = "2021"
license = "GPL-3.0-or-later"
publish = false
[lib]
test = true
[features]
serialize = []
test = []
[dev-dependencies]
nonempty = "0.5"
pretty_assertions = "1.3.0"
proptest = "0.9"
serde_json = "1"
[dev-dependencies.git2]
version = "0.15.0"
default-features = false
features = ["vendored-libgit2"]
[dev-dependencies.radicle-git-ext]
path = "../../radicle-git-ext"
[dev-dependencies.radicle-surf]
path = ".."
features = ["serialize"]
[dev-dependencies.test-helpers]
path = "../../test/test-helpers"
+298
View File
@@ -0,0 +1,298 @@
// Copyright © 2022 The Radicle Git Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//! Unit tests for radicle_surf::diff
use pretty_assertions::assert_eq;
use radicle_surf::{
diff::*,
file_system::{unsound, *},
};
#[test]
fn test_create_file() {
let directory = Directory::root();
let mut new_directory = Directory::root();
new_directory.insert_file(unsound::path::new("banana.rs"), File::new(b"use banana"));
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![CreateFile {
path: Path::with_root(&[unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
}],
deleted: vec![],
copied: vec![],
moved: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
#[test]
fn test_delete_file() {
let mut directory = Directory::root();
directory.insert_file(unsound::path::new("banana.rs"), File::new(b"use banana"));
let new_directory = Directory::root();
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![],
deleted: vec![DeleteFile {
path: Path::with_root(&[unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
}],
moved: vec![],
copied: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
/* TODO(fintan): Move is not detected yet
#[test]
fn test_moved_file() {
let mut directory = Directory::root();
directory.insert_file(&unsound::path::new("mod.rs"), File::new(b"use banana"));
let mut new_directory = Directory::root();
new_directory.insert_file(&unsound::path::new("banana.rs"), File::new(b"use banana"));
let diff = Diff::diff(directory, new_directory).expect("diff failed");
assert_eq!(diff, Diff::new())
}
*/
#[test]
fn test_modify_file() {
let mut directory = Directory::root();
directory.insert_file(unsound::path::new("banana.rs"), File::new(b"use banana"));
let mut new_directory = Directory::root();
new_directory.insert_file(unsound::path::new("banana.rs"), File::new(b"use banana;"));
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![ModifiedFile {
path: Path::with_root(&[unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
eof: None,
}],
};
assert_eq!(diff, expected_diff)
}
#[test]
fn test_create_directory() {
let directory = Directory::root();
let mut new_directory = Directory::root();
new_directory.insert_file(
unsound::path::new("src/banana.rs"),
File::new(b"use banana"),
);
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![CreateFile {
path: Path::with_root(&[unsound::label::new("src"), unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
}],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
#[test]
fn test_delete_directory() {
let mut directory = Directory::root();
directory.insert_file(
unsound::path::new("src/banana.rs"),
File::new(b"use banana"),
);
let new_directory = Directory::root();
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![],
deleted: vec![DeleteFile {
path: Path::with_root(&[unsound::label::new("src"), unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
}],
moved: vec![],
copied: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
#[test]
fn test_modify_file_directory() {
let mut directory = Directory::root();
directory.insert_file(
unsound::path::new("src/banana.rs"),
File::new(b"use banana"),
);
let mut new_directory = Directory::root();
new_directory.insert_file(
unsound::path::new("src/banana.rs"),
File::new(b"use banana;"),
);
let diff = Diff::diff(directory, new_directory);
let expected_diff = Diff {
created: vec![],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![ModifiedFile {
path: Path::with_root(&[unsound::label::new("src"), unsound::label::new("banana.rs")]),
diff: FileDiff::Plain {
hunks: Hunks::default(),
},
eof: None,
}],
};
assert_eq!(diff, expected_diff)
}
/* TODO(fintan): Tricky stuff
#[test]
fn test_disjoint_directories() {
let mut directory = Directory::root();
directory.insert_file(
&unsound::path::new("foo/src/banana.rs"),
File::new(b"use banana"),
);
let mut other_directory = Directory::root();
other_directory.insert_file(
&unsound::path::new("bar/src/pineapple.rs"),
File::new(b"use pineapple"),
);
let diff = Diff::diff(directory, other_directory).expect("diff failed");
let expected_diff = Diff {
created: vec![CreateFile(Path::from_labels(
unsound::label::new("bar"),
&[
unsound::label::new("src"),
unsound::label::new("pineapple.rs"),
],
))],
deleted: vec![DeleteFile(Path::from_labels(
unsound::label::new("foo"),
&[unsound::label::new("src"), unsound::label::new("banana.rs")],
))],
moved: vec![],
modified: vec![],
};
assert_eq!(diff, expected_diff)
}
*/
#[test]
fn test_both_missing_eof_newline() {
let buf = r#"
diff --git a/.env b/.env
index f89e4c0..7c56eb7 100644
--- a/.env
+++ b/.env
@@ -1 +1 @@
-hello=123
\ No newline at end of file
+hello=1234
\ No newline at end of file
"#;
let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
let diff = Diff::try_from(diff).unwrap();
assert_eq!(diff.modified[0].eof, Some(EofNewLine::BothMissing));
}
#[test]
fn test_none_missing_eof_newline() {
let buf = r#"
diff --git a/.env b/.env
index f89e4c0..7c56eb7 100644
--- a/.env
+++ b/.env
@@ -1 +1 @@
-hello=123
+hello=1234
"#;
let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
let diff = Diff::try_from(diff).unwrap();
assert_eq!(diff.modified[0].eof, None);
}
// TODO(xphoniex): uncomment once libgit2 has fixed the bug
//#[test]
// fn test_old_missing_eof_newline() {
// let buf = r#"
// diff --git a/.env b/.env
// index f89e4c0..7c56eb7 100644
// --- a/.env
// +++ b/.env
// @@ -1 +1 @@
// -hello=123
// \ No newline at end of file
// +hello=1234
// "#;
// let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
// let diff = Diff::try_from(diff).unwrap();
// assert_eq!(diff.modified[0].eof, Some(EofNewLine::OldMissing));
// }
// TODO(xphoniex): uncomment once libgit2 has fixed the bug
//#[test]
// fn test_new_missing_eof_newline() {
// let buf = r#"
// diff --git a/.env b/.env
// index f89e4c0..7c56eb7 100644
// --- a/.env
// +++ b/.env
// @@ -1 +1 @@
// -hello=123
// +hello=1234
// \ No newline at end of file
// "#;
// let diff = git2::Diff::from_buffer(buf.as_bytes()).unwrap();
// let diff = Diff::try_from(diff).unwrap();
// assert_eq!(diff.modified[0].eof, Some(EofNewLine::NewMissing));
// }
+222
View File
@@ -0,0 +1,222 @@
// Copyright © 2022 The Radicle Git Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//! Unit tests for radicle_surf::file_system
#[cfg(test)]
mod list_directory {
use radicle_surf::file_system::{unsound, Directory, File, SystemType};
#[test]
fn root_files() {
let mut directory = Directory::root();
directory.insert_file(
unsound::path::new("foo.hs"),
File::new(b"module BananaFoo ..."),
);
directory.insert_file(
unsound::path::new("bar.hs"),
File::new(b"module BananaBar ..."),
);
directory.insert_file(
unsound::path::new("baz.hs"),
File::new(b"module BananaBaz ..."),
);
assert_eq!(
directory.list_directory(),
vec![
SystemType::file(unsound::label::new("bar.hs")),
SystemType::file(unsound::label::new("baz.hs")),
SystemType::file(unsound::label::new("foo.hs")),
]
);
}
}
#[cfg(test)]
mod find_file {
use radicle_surf::file_system::{unsound, *};
#[test]
fn in_root() {
let file = File::new(b"module Banana ...");
let mut directory = Directory::root();
directory.insert_file(unsound::path::new("foo.hs"), file.clone());
assert_eq!(
directory.find_file(unsound::path::new("foo.hs")),
Some(file)
);
}
#[test]
fn file_does_not_exist() {
let file_path = unsound::path::new("bar.hs");
let file = File::new(b"module Banana ...");
let mut directory = Directory::root();
directory.insert_file(unsound::path::new("foo.hs"), file);
assert_eq!(directory.find_file(file_path), None)
}
}
#[cfg(test)]
mod directory_size {
use nonempty::NonEmpty;
use radicle_surf::file_system::{unsound, Directory, File};
#[test]
fn root_directory_files() {
let mut root = Directory::root();
root.insert_files(
&[],
NonEmpty::from((
(
unsound::label::new("main.rs"),
File::new(b"println!(\"Hello, world!\")"),
),
vec![(
unsound::label::new("lib.rs"),
File::new(b"struct Hello(String)"),
)],
)),
);
assert_eq!(root.size(), 45);
}
}
#[cfg(test)]
mod properties {
use nonempty::NonEmpty;
use proptest::{collection, prelude::*};
use radicle_surf::file_system::{unsound, *};
use std::collections::HashMap;
#[test]
fn test_all_directories_and_files() {
let mut directory_map = HashMap::new();
let path1 = unsound::path::new("foo/bar/baz");
let file1 = (unsound::label::new("monadic.rs"), File::new(&[]));
let file2 = (unsound::label::new("oscoin.rs"), File::new(&[]));
directory_map.insert(path1, NonEmpty::from((file1, vec![file2])));
let path2 = unsound::path::new("foor/bar/quuz");
let file3 = (unsound::label::new("radicle.rs"), File::new(&[]));
directory_map.insert(path2, NonEmpty::new(file3));
assert!(prop_all_directories_and_files(directory_map))
}
fn label_strategy() -> impl Strategy<Value = Label> {
// ASCII regex, excluding '/' because of posix file paths
"[ -.|0-~]+".prop_map(|label| unsound::label::new(&label))
}
fn path_strategy(max_size: usize) -> impl Strategy<Value = Path> {
(
label_strategy(),
collection::vec(label_strategy(), 0..max_size),
)
.prop_map(|(label, labels)| Path((label, labels).into()))
}
fn file_strategy() -> impl Strategy<Value = (Label, File)> {
// ASCII regex, see: https://catonmat.net/my-favorite-regex
(label_strategy(), "[ -~]*")
.prop_map(|(name, contents)| (name, File::new(contents.as_bytes())))
}
fn directory_map_strategy(
path_size: usize,
n_files: usize,
map_size: usize,
) -> impl Strategy<Value = HashMap<Path, NonEmpty<(Label, File)>>> {
collection::hash_map(
path_strategy(path_size),
collection::vec(file_strategy(), 1..n_files).prop_map(|files| {
NonEmpty::from_slice(&files).expect("Strategy generated files of length 0")
}),
0..map_size,
)
}
// TODO(fintan): This is a bit slow. Could be time to benchmark some functions.
proptest! {
#[test]
fn prop_test_all_directories_and_files(directory_map in directory_map_strategy(10, 10, 10)) {
prop_all_directories_and_files(directory_map);
}
}
fn prop_all_directories_and_files(
directory_map: HashMap<Path, NonEmpty<(Label, File)>>,
) -> bool {
let mut new_directory_map = HashMap::new();
for (path, files) in directory_map {
new_directory_map.insert(path.clone(), files);
}
let directory = Directory::from_hash_map(new_directory_map.clone());
for (directory_path, files) in new_directory_map {
for (file_name, _) in files.iter() {
let mut path = directory_path.clone();
if directory.find_directory(path.clone()).is_none() {
eprintln!("Search Directory: {:#?}", directory);
eprintln!("Path to find: {:#?}", path);
return false;
}
path.push(file_name.clone());
if directory.find_file(path.clone()).is_none() {
eprintln!("Search Directory: {:#?}", directory);
eprintln!("Path to find: {:#?}", path);
return false;
}
}
}
true
}
#[test]
fn test_file_name_is_same_as_root() {
// This test ensures that if the name is the same the root of the
// directory, that search_path.split_last() doesn't toss away the prefix.
let path = unsound::path::new("foo/bar/~");
let mut directory_map = HashMap::new();
directory_map.insert(path, NonEmpty::new((Label::root(), File::new(b"root"))));
assert!(prop_all_directories_and_files(directory_map));
}
}
#[cfg(test)]
mod path {
use radicle_surf::file_system::unsound;
#[test]
fn split_last_root_and_foo() {
let path = unsound::path::new("foo");
assert_eq!(path.split_last(), (vec![], unsound::label::new("foo")));
}
#[test]
fn split_last_same_labels() {
// An interesting case for when first == last, but doesn't imply a singleton
// Path.
let path = unsound::path::new("foo/bar/foo");
assert_eq!(
path.split_last(),
(
vec![unsound::label::new("foo"), unsound::label::new("bar")],
unsound::label::new("foo")
)
);
}
}
+811
View File
@@ -0,0 +1,811 @@
// Copyright © 2022 The Radicle Git Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//! Unit tests for radicle_surf::vcs::git and its submodules.
#[cfg(feature = "serialize")]
use radicle_surf::git::{Author, BranchType, Commit};
use radicle_surf::{
diff::*,
file_system::{unsound, Path},
git::{
error::Error,
Branch,
BranchName,
Browser,
Namespace,
Oid,
RefScope,
Repository,
TagName,
},
};
const GIT_PLATINUM: &str = "../data/git-platinum";
#[cfg(not(feature = "gh-actions"))]
#[test]
// An issue with submodules, see: https://github.com/radicle-dev/radicle-surf/issues/54
fn test_submodule_failure() {
let repo = Repository::new("../..").unwrap();
let browser = Browser::new(&repo, Branch::local("main")).unwrap();
browser.get_directory().unwrap();
}
#[cfg(test)]
mod namespace {
use super::*;
use pretty_assertions::{assert_eq, assert_ne};
#[test]
fn switch_to_banana() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let mut browser = Browser::new_with_namespace(
&repo,
&Namespace::try_from("golden")?,
Branch::local("master"),
)?;
let history = browser.get();
browser.branch(Branch::local("banana"))?;
assert_ne!(history, browser.get());
Ok(())
}
#[test]
fn me_namespace() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let browser = Browser::new(&repo, Branch::local("master"))?;
let history = browser.get();
assert_eq!(browser.which_namespace(), Ok(None));
let browser = browser
.switch_namespace(&Namespace::try_from("me")?, Branch::local("feature/#1194"))?;
assert_eq!(
browser.which_namespace(),
Ok(Some(Namespace::try_from("me")?))
);
assert_eq!(history, browser.get());
let expected_branches: Vec<Branch> = vec![Branch::local("feature/#1194")];
let mut branches = browser.list_branches(RefScope::Local)?;
branches.sort();
assert_eq!(expected_branches, branches);
let expected_branches: Vec<Branch> = vec![Branch::remote("feature/#1194", "fein")];
let mut branches = browser.list_branches(RefScope::Remote {
name: Some("fein".to_string()),
})?;
branches.sort();
assert_eq!(expected_branches, branches);
Ok(())
}
#[test]
fn golden_namespace() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let browser = Browser::new(&repo, Branch::local("master"))?;
let history = browser.get();
assert_eq!(browser.which_namespace(), Ok(None));
let golden_browser =
browser.switch_namespace(&Namespace::try_from("golden")?, Branch::local("master"))?;
assert_eq!(
golden_browser.which_namespace(),
Ok(Some(Namespace::try_from("golden")?))
);
assert_eq!(history, golden_browser.get());
let expected_branches: Vec<Branch> = vec![Branch::local("banana"), Branch::local("master")];
let mut branches = golden_browser.list_branches(RefScope::Local)?;
branches.sort();
assert_eq!(expected_branches, branches);
let expected_branches: Vec<Branch> = vec![
Branch::remote("fakie/bigspin", "kickflip"),
Branch::remote("heelflip", "kickflip"),
Branch::remote("v0.1.0", "kickflip"),
];
let mut branches = golden_browser.list_branches(RefScope::Remote {
name: Some("kickflip".to_string()),
})?;
branches.sort();
assert_eq!(expected_branches, branches);
Ok(())
}
#[test]
fn silver_namespace() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let browser = Browser::new(&repo, Branch::local("master"))?;
let history = browser.get();
assert_eq!(browser.which_namespace(), Ok(None));
let silver_browser = browser.switch_namespace(
&Namespace::try_from("golden/silver")?,
Branch::local("master"),
)?;
assert_eq!(
silver_browser.which_namespace(),
Ok(Some(Namespace::try_from("golden/silver")?))
);
assert_ne!(history, silver_browser.get());
let expected_branches: Vec<Branch> = vec![Branch::local("master")];
let mut branches = silver_browser.list_branches(RefScope::All)?;
branches.sort();
assert_eq!(expected_branches, branches);
Ok(())
}
}
#[cfg(test)]
mod rev {
use super::*;
use std::str::FromStr;
// **FIXME**: This seems to break occasionally on
// buildkite. For some reason the commit
// 3873745c8f6ffb45c990eb23b491d4b4b6182f95, which is on master
// (currently HEAD), is not found. It seems to load the history
// with d6880352fc7fda8f521ae9b7357668b17bb5bad5 as the HEAD.
//
// To temporarily fix this, we need to select "New Build" from the build kite
// build page that's failing.
// * Under "Message" put whatever you want.
// * Under "Branch" put in the branch you're working on.
// * Expand "Options" and select "clean checkout".
#[test]
fn _master() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(Branch::remote("master", "origin"))?;
let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
assert!(
browser
.as_history()
.find(|commit| if commit.id == commit1 {
Some(commit.clone())
} else {
None
})
.is_some(),
"commit_id={}, history =\n{:#?}",
commit1,
browser.as_history()
);
let commit2 = Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?;
assert!(
browser
.as_history()
.find(|commit| if commit.id == commit2 {
Some(commit.clone())
} else {
None
})
.is_some(),
"commit_id={}, history =\n{:#?}",
commit2,
browser.as_history()
);
Ok(())
}
#[test]
fn commit() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?)?;
let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
assert!(browser
.as_history()
.find(|commit| if commit.id == commit1 {
Some(commit.clone())
} else {
None
})
.is_some());
Ok(())
}
#[test]
fn commit_parents() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?)?;
let commit = browser.as_history().first();
assert_eq!(
commit.parents,
vec![Oid::from_str("d6880352fc7fda8f521ae9b7357668b17bb5bad5")?]
);
Ok(())
}
#[test]
fn commit_short() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(browser.oid("3873745c8")?)?;
let commit1 = Oid::from_str("3873745c8f6ffb45c990eb23b491d4b4b6182f95")?;
assert!(browser
.as_history()
.find(|commit| if commit.id == commit1 {
Some(commit.clone())
} else {
None
})
.is_some());
Ok(())
}
#[test]
fn tag() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let mut browser = Browser::new(&repo, Branch::local("master"))?;
browser.rev(TagName::new("v0.2.0"))?;
let commit1 = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977")?;
assert_eq!(browser.as_history().first().id, commit1);
Ok(())
}
}
#[cfg(test)]
mod last_commit {
use super::*;
use std::str::FromStr;
#[test]
fn readme_missing_and_memory() {
let repo = Repository::new(GIT_PLATINUM)
.expect("Could not retrieve ./data/git-platinum as git repository");
let mut browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
// Set the browser history to the initial commit
let commit =
Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3").expect("Failed to parse SHA");
browser.commit(commit).unwrap();
let head_commit = browser.get().0.first().clone();
// memory.rs is commited later so it should not exist here.
let memory_last_commit = browser
.last_commit(Path::with_root(&[
unsound::label::new("src"),
unsound::label::new("memory.rs"),
]))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(memory_last_commit, None);
// README.md exists in this commit.
let readme_last_commit = browser
.last_commit(Path::with_root(&[unsound::label::new("README.md")]))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(readme_last_commit, Some(head_commit.id));
}
#[test]
fn folder_svelte() {
let repo = Repository::new(GIT_PLATINUM)
.expect("Could not retrieve ./data/git-platinum as git repository");
let mut browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
// Check that last commit is the actual last commit even if head commit differs.
let commit =
Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Could not parse SHA");
browser.commit(commit).unwrap();
let expected_commit_id = Oid::from_str("f3a089488f4cfd1a240a9c01b3fcc4c34a4e97b2").unwrap();
let folder_svelte = browser
.last_commit(unsound::path::new("~/examples/Folder.svelte"))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(folder_svelte, Some(expected_commit_id));
}
#[test]
fn nest_directory() {
let repo = Repository::new(GIT_PLATINUM)
.expect("Could not retrieve ./data/git-platinum as git repository");
let mut browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
// Check that last commit is the actual last commit even if head commit differs.
let commit =
Oid::from_str("19bec071db6474af89c866a1bd0e4b1ff76e2b97").expect("Failed to parse SHA");
browser.commit(commit).unwrap();
let expected_commit_id = Oid::from_str("2429f097664f9af0c5b7b389ab998b2199ffa977").unwrap();
let nested_directory_tree_commit_id = browser
.last_commit(unsound::path::new(
"~/this/is/a/really/deeply/nested/directory/tree",
))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(nested_directory_tree_commit_id, Some(expected_commit_id));
}
#[test]
#[cfg(not(windows))]
fn can_get_last_commit_for_special_filenames() {
let repo = Repository::new(GIT_PLATINUM)
.expect("Could not retrieve ./data/git-platinum as git repository");
let mut browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
// Check that last commit is the actual last commit even if head commit differs.
let commit =
Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").expect("Failed to parse SHA");
browser.commit(commit).unwrap();
let expected_commit_id = Oid::from_str("a0dd9122d33dff2a35f564d564db127152c88e02").unwrap();
let backslash_commit_id = browser
.last_commit(unsound::path::new("~/special/faux\\path"))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(backslash_commit_id, Some(expected_commit_id));
let ogre_commit_id = browser
.last_commit(unsound::path::new("~/special/👹👹👹"))
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(ogre_commit_id, Some(expected_commit_id));
}
#[test]
fn root() {
let repo = Repository::new(GIT_PLATINUM)
.expect("Could not retrieve ./data/git-platinum as git repository");
let browser =
Browser::new(&repo, Branch::local("master")).expect("Could not initialise Browser");
let root_last_commit_id = browser
.last_commit(Path::root())
.expect("Failed to get last commit")
.map(|commit| commit.id);
assert_eq!(root_last_commit_id, Some(browser.get().first().id));
}
}
#[cfg(test)]
mod diff {
use super::*;
use pretty_assertions::assert_eq;
use std::str::FromStr;
#[test]
fn test_initial_diff() -> Result<(), Error> {
let oid = Oid::from_str("d3464e33d75c75c99bfb90fa2e9d16efc0b7d0e3")?;
let repo = Repository::new(GIT_PLATINUM)?;
// let commit = repo.0.find_commit(oid).unwrap();
let commit = repo.as_ref().get_git2_commit(oid).unwrap();
assert!(commit.parents().count() == 0);
assert!(commit.parent(0).is_err());
let bro = Browser::new(&repo, Branch::local("master"))?;
let diff = bro.initial_diff(oid)?;
let expected_diff = Diff {
created: vec![CreateFile {
path: Path::with_root(&[unsound::label::new("README.md")]),
diff: FileDiff::Plain {
hunks: vec![Hunk {
header: Line::from(b"@@ -0,0 +1 @@\n".to_vec()),
lines: vec![LineDiff::addition(
b"This repository is a data source for the Upstream front-end tests.\n"
.to_vec(),
1,
)],
}]
.into(),
},
}],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![],
};
assert_eq!(expected_diff, diff);
Ok(())
}
#[test]
fn test_diff() -> Result<(), Error> {
let repo = Repository::new(GIT_PLATINUM)?;
let commit = repo
.as_ref()
.get_git2_commit(Oid::from_str("80bacafba303bf0cdf6142921f430ff265f25095")?)
.unwrap();
let parent = commit.parent(0)?;
let bro = Browser::new(&repo, Branch::local("master"))?;
let diff = bro.diff(parent.id().into(), commit.id().into())?;
let expected_diff = Diff {
created: vec![],
deleted: vec![],
moved: vec![],
copied: vec![],
modified: vec![ModifiedFile {
path: Path::with_root(&[unsound::label::new("README.md")]),
diff: FileDiff::Plain {
hunks: vec![Hunk {
header: Line::from(b"@@ -1 +1,2 @@\n".to_vec()),
lines: vec![
LineDiff::deletion(b"This repository is a data source for the Upstream front-end tests.\n".to_vec(), 1),
LineDiff::addition(b"This repository is a data source for the Upstream front-end tests and the\n".to_vec(), 1),
LineDiff::addition(b"[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n".to_vec(), 2),
]
}].into()
},
eof: None,
}]
};
assert_eq!(expected_diff, diff);
Ok(())
}
#[cfg(feature = "serialize")]
#[test]
fn test_diff_serde() -> Result<(), Error> {
let diff = Diff {
created: vec![CreateFile{path: unsound::path::new("LICENSE"), diff: FileDiff::Plain { hunks: Hunks::default() }}],
deleted: vec![],
moved: vec![
MoveFile {
old_path: unsound::path::new("CONTRIBUTING"),
new_path: unsound::path::new("CONTRIBUTING.md")
}
],
copied: vec![],
modified: vec![ModifiedFile {
path: Path::with_root(&[unsound::label::new("README.md")]),
diff: FileDiff::Plain {
hunks: vec![Hunk {
header: Line::from(b"@@ -1 +1,2 @@\n".to_vec()),
lines: vec![
LineDiff::deletion(b"This repository is a data source for the Upstream front-end tests.\n".to_vec(), 1),
LineDiff::addition(b"This repository is a data source for the Upstream front-end tests and the\n".to_vec(), 1),
LineDiff::addition(b"[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n".to_vec(), 2),
LineDiff::context(b"\n".to_vec(), 3, 4),
]
}].into()
},
eof: None,
}]
};
let eof: Option<u8> = None;
let json = serde_json::json!({
"created": [{"path": "LICENSE", "diff": {
"type": "plain",
"hunks": []
},
}],
"deleted": [],
"moved": [{ "oldPath": "CONTRIBUTING", "newPath": "CONTRIBUTING.md" }],
"copied": [],
"modified": [{
"path": "README.md",
"diff": {
"type": "plain",
"hunks": [{
"header": "@@ -1 +1,2 @@\n",
"lines": [
{ "lineNum": 1,
"line": "This repository is a data source for the Upstream front-end tests.\n",
"type": "deletion"
},
{ "lineNum": 1,
"line": "This repository is a data source for the Upstream front-end tests and the\n",
"type": "addition"
},
{ "lineNum": 2,
"line": "[`radicle-surf`](https://github.com/radicle-dev/git-platinum) unit tests.\n",
"type": "addition"
},
{ "lineNumOld": 3, "lineNumNew": 4,
"line": "\n",
"type": "context"
}
]
}]
},
"eof" : eof,
}]
});
assert_eq!(serde_json::to_value(&diff).unwrap(), json);
Ok(())
}
}
#[cfg(test)]
mod threading {
use super::*;
use std::sync::{Mutex, MutexGuard};
#[test]
fn basic_test() -> Result<(), Error> {
let shared_repo = Mutex::new(Repository::new(GIT_PLATINUM)?);
let locked_repo: MutexGuard<Repository> = shared_repo.lock().unwrap();
let bro = Browser::new(&*locked_repo, Branch::local("master"))?;
let mut branches = bro.list_branches(RefScope::All)?;
branches.sort();
assert_eq!(
branches,
vec![
Branch::remote("HEAD", "origin"),
Branch::remote("dev", "origin"),
Branch::local("dev"),
Branch::remote("master", "origin"),
Branch::local("master"),
Branch::remote("orange/pineapple", "banana"),
Branch::remote("pineapple", "banana"),
]
);
Ok(())
}
}
#[cfg(feature = "serialize")]
#[cfg(test)]
mod commit {
use super::{Author, Commit};
use proptest::prelude::*;
use radicle_git_ext::Oid;
use std::str::FromStr;
use test_helpers::roundtrip;
#[cfg(feature = "serialize")]
proptest! {
#[test]
fn prop_test_commits(commit in commits_strategy()) {
roundtrip::json(commit)
}
}
fn commits_strategy() -> impl Strategy<Value = Commit> {
("[a-fA-F0-9]{40}", any::<String>(), any::<i64>()).prop_map(|(id, text, time)| Commit {
id: Oid::from_str(&id).unwrap(),
author: Author {
name: text.clone(),
email: text.clone(),
time: git2::Time::new(time, 0),
},
committer: Author {
name: text.clone(),
email: text.clone(),
time: git2::Time::new(time, 0),
},
message: text.clone(),
summary: text,
parents: vec![Oid::from_str(&id).unwrap(), Oid::from_str(&id).unwrap()],
})
}
}
#[cfg(feature = "serialize")]
#[cfg(test)]
mod branch {
use super::*;
use proptest::prelude::*;
use test_helpers::roundtrip;
proptest! {
#[test]
fn prop_test_branch(branch in branch_strategy()) {
roundtrip::json(branch)
}
}
fn branch_strategy() -> impl Strategy<Value = Branch> {
prop_oneof![
any::<String>().prop_map(|name| Branch {
name: BranchName::new(&name),
locality: BranchType::Local
}),
(any::<String>(), any::<String>()).prop_map(|(name, remote_name)| Branch {
name: BranchName::new(&name),
locality: BranchType::Remote {
name: Some(remote_name),
},
})
]
}
}
#[cfg(test)]
mod ext {
use radicle_surf::vcs::git::ext::*;
#[test]
fn test_try_extract_refname() {
assert_eq!(try_extract_refname("refs/heads/dev"), Ok("dev".to_string()));
assert_eq!(
try_extract_refname("refs/heads/master"),
Ok("master".to_string())
);
assert_eq!(
try_extract_refname("refs/remotes/banana/pineapple"),
Ok("banana/pineapple".to_string())
);
assert_eq!(
try_extract_refname("refs/remotes/origin/master"),
Ok("origin/master".to_string())
);
assert_eq!(
try_extract_refname("refs/namespaces/golden/refs/heads/banana"),
Ok("banana".to_string())
);
assert_eq!(
try_extract_refname("refs/namespaces/golden/refs/tags/v0.1.0"),
Ok("v0.1.0".to_string())
);
assert_eq!(
try_extract_refname("refs/namespaces/golden/refs/namespaces/silver/refs/heads/master"),
Ok("master".to_string())
);
assert_eq!(
try_extract_refname("refs/namespaces/golden/refs/remotes/kickflip/heads/heelflip"),
Ok("kickflip/heelflip".to_string())
);
}
}
#[cfg(test)]
mod reference {
use super::*;
use radicle_surf::vcs::git::{ParseError, Ref};
use std::str::FromStr;
#[test]
fn parse_ref() -> Result<(), ParseError> {
assert_eq!(
Ref::from_str("refs/remotes/origin/master"),
Ok(Ref::RemoteBranch {
remote: "origin".to_string(),
name: BranchName::new("master")
})
);
assert_eq!(
Ref::from_str("refs/heads/master"),
Ok(Ref::LocalBranch {
name: BranchName::new("master"),
})
);
assert_eq!(
Ref::from_str("refs/heads/i-am-hyphenated"),
Ok(Ref::LocalBranch {
name: BranchName::new("i-am-hyphenated"),
})
);
assert_eq!(
Ref::from_str("refs/heads/prefix/i-am-hyphenated"),
Ok(Ref::LocalBranch {
name: BranchName::new("prefix/i-am-hyphenated"),
})
);
assert_eq!(
Ref::from_str("refs/tags/v0.0.1"),
Ok(Ref::Tag {
name: TagName::new("v0.0.1")
})
);
assert_eq!(
Ref::from_str("refs/namespaces/moi/refs/remotes/origin/master"),
Ok(Ref::Namespace {
namespace: "moi".to_string(),
reference: Box::new(Ref::RemoteBranch {
remote: "origin".to_string(),
name: BranchName::new("master")
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/moi/refs/namespaces/toi/refs/tags/v1.0.0"),
Ok(Ref::Namespace {
namespace: "moi".to_string(),
reference: Box::new(Ref::Namespace {
namespace: "toi".to_string(),
reference: Box::new(Ref::Tag {
name: TagName::new("v1.0.0")
})
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/me/refs/heads/feature/#1194"),
Ok(Ref::Namespace {
namespace: "me".to_string(),
reference: Box::new(Ref::LocalBranch {
name: BranchName::new("feature/#1194"),
})
})
);
assert_eq!(
Ref::from_str("refs/namespaces/me/refs/remotes/fein/heads/feature/#1194"),
Ok(Ref::Namespace {
namespace: "me".to_string(),
reference: Box::new(Ref::RemoteBranch {
remote: "fein".to_string(),
name: BranchName::new("heads/feature/#1194"),
})
})
);
assert_eq!(
Ref::from_str("refs/remotes/master"),
Err(ParseError::MalformedRef("refs/remotes/master".to_owned())),
);
assert_eq!(
Ref::from_str("refs/namespaces/refs/remotes/origin/master"),
Err(ParseError::MalformedRef(
"refs/namespaces/refs/remotes/origin/master".to_owned()
)),
);
Ok(())
}
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright © 2022 The Radicle Git Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
#[cfg(test)]
mod git;
#[cfg(test)]
mod diff;
#[cfg(test)]
mod file_system;
#[cfg(test)]
mod tree;
+692
View File
@@ -0,0 +1,692 @@
// Copyright © 2022 The Radicle Git Contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//! Unit tests for radicle_surf::tree.
use nonempty::NonEmpty;
use pretty_assertions::assert_eq;
use radicle_surf::tree::{Forest, SubTree, Tree};
#[derive(Debug, Clone, PartialEq, Eq)]
struct TestNode {
id: u32,
}
#[test]
fn test_is_empty() {
let mut tree = Forest::root();
assert!(tree.is_empty());
let a_node = TestNode { id: 1 };
tree.insert(NonEmpty::new(String::from("a")), a_node);
assert!(!tree.is_empty());
}
#[test]
fn test_insert_root_node() {
let a_label = String::from("a");
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
tree.insert(NonEmpty::new(a_label), a_node.clone());
assert_eq!(tree, Forest(Some(Tree::node(String::from("a"), a_node))));
}
#[test]
fn test_insert_with_prepending_root_nodes() {
let a_label = String::from("a");
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 2 };
tree.insert_with(
NonEmpty::new(a_label.clone()),
NonEmpty::new(a_node.clone()),
|nodes| nodes.insert(0, a_node.clone()),
);
tree.insert_with(
NonEmpty::new(a_label),
NonEmpty::new(b_node.clone()),
|nodes| nodes.insert(0, b_node.clone()),
);
assert_eq!(
tree,
Forest(Some(Tree::node(
String::from("a"),
NonEmpty::from((b_node, vec![a_node]))
)))
);
}
#[test]
fn test_insert_with_prepending_branch_nodes() {
let a_label = String::from("a");
let b_label = String::from("b");
let path = NonEmpty::from((a_label, vec![b_label]));
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 2 };
tree.insert_with(path.clone(), NonEmpty::new(a_node.clone()), |nodes| {
nodes.insert(0, a_node.clone())
});
tree.insert_with(path, NonEmpty::new(b_node.clone()), |nodes| {
nodes.insert(0, b_node.clone())
});
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::node(String::from("b"), NonEmpty::from((b_node, vec![a_node])))
)))
);
}
#[test]
fn test_insert_single_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let path = NonEmpty::from((a_label, vec![b_label, c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(path, c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(String::from("b"), Tree::node(String::from("c"), c_node))
)))
);
}
#[test]
fn test_insert_two_nodes() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let d_path = NonEmpty::from((a_label, vec![b_label, d_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(c_path, c_node.clone());
let d_node = TestNode { id: 3 };
tree.insert(d_path, d_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree(NonEmpty::from((
SubTree::Node {
key: String::from("c"),
value: c_node
},
vec![SubTree::Node {
key: String::from("d"),
value: d_node
}]
)))
)
)))
);
}
#[test]
fn test_insert_replaces_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let c_path = NonEmpty::from((a_label, vec![b_label, c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(c_path.clone(), c_node);
let new_c_node = TestNode { id: 3 };
tree.insert(c_path, new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree(NonEmpty::new(SubTree::Node {
key: String::from("c"),
value: new_c_node
},))
)
)))
);
}
#[test]
fn test_insert_replaces_root_node() {
let c_label = String::from("c");
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(NonEmpty::new(c_label.clone()), c_node);
let new_c_node = TestNode { id: 3 };
tree.insert(NonEmpty::new(c_label), new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::node(String::from("c"), new_c_node)))
);
}
#[test]
fn test_insert_replaces_branch_node() {
let a_label = String::from("a");
let c_label = String::from("c");
let c_path = NonEmpty::from((a_label, vec![c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(c_path.clone(), c_node);
let new_c_node = TestNode { id: 3 };
tree.insert(c_path, new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::node(String::from("c"), new_c_node),
)))
);
}
#[test]
fn test_insert_replaces_branch_with_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(c_path, c_node);
let new_c_node = TestNode { id: 3 };
tree.insert(NonEmpty::from((a_label, vec![b_label])), new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::node(String::from("b"), new_c_node),
)))
);
}
#[test]
fn test_insert_replaces_node_with_branch() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let b_path = NonEmpty::from((a_label.clone(), vec![b_label.clone()]));
let c_path = NonEmpty::from((a_label, vec![b_label, c_label]));
let mut tree = Forest::root();
let b_node = TestNode { id: 1 };
tree.insert(b_path, b_node);
let new_c_node = TestNode { id: 3 };
tree.insert(c_path, new_c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree(NonEmpty::new(SubTree::Node {
key: String::from("c"),
value: new_c_node
},))
)
)))
);
}
#[test]
fn test_insert_replaces_node_with_branch_foo() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let b_path = NonEmpty::from((a_label.clone(), vec![b_label.clone()]));
let d_path = NonEmpty::from((a_label, vec![b_label, c_label, d_label]));
let mut tree = Forest::root();
let b_node = TestNode { id: 1 };
tree.insert(b_path, b_node);
let d_node = TestNode { id: 3 };
tree.insert(d_path, d_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree::branch(String::from("c"), Tree::node(String::from("d"), d_node))
)
)))
);
}
#[test]
fn test_insert_two_nodes_out_of_order() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let d_path = NonEmpty::from((a_label, vec![b_label, d_label]));
let mut tree = Forest::root();
let d_node = TestNode { id: 3 };
tree.insert(d_path, d_node.clone());
let c_node = TestNode { id: 1 };
tree.insert(c_path, c_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree::branch(
String::from("b"),
Tree(NonEmpty::from((
SubTree::Node {
key: String::from("c"),
value: c_node
},
vec![SubTree::Node {
key: String::from("d"),
value: d_node
}]
)))
)
)))
);
}
#[test]
fn test_insert_branch() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let e_label = String::from("e");
let f_label = String::from("f");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let d_path = NonEmpty::from((a_label.clone(), vec![b_label, d_label]));
let f_path = NonEmpty::from((a_label, vec![e_label, f_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
let d_node = TestNode { id: 3 };
let f_node = TestNode { id: 2 };
tree.insert(d_path, d_node.clone());
tree.insert(c_path, c_node.clone());
tree.insert(f_path, f_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree(NonEmpty::from((
SubTree::Branch {
key: String::from("b"),
forest: Box::new(Tree(NonEmpty::from((
SubTree::Node {
key: String::from("c"),
value: c_node
},
vec![SubTree::Node {
key: String::from("d"),
value: d_node
}]
))))
},
vec![SubTree::Branch {
key: String::from("e"),
forest: Box::new(Tree::node(String::from("f"), f_node))
},]
)))
)))
);
}
#[test]
fn test_insert_two_branches() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let e_label = String::from("e");
let f_label = String::from("f");
let c_path = NonEmpty::from((a_label, vec![b_label, c_label]));
let f_path = NonEmpty::from((d_label, vec![e_label, f_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
let f_node = TestNode { id: 2 };
tree.insert(c_path, c_node.clone());
tree.insert(f_path, f_node.clone());
assert_eq!(
tree,
Forest(Some(Tree(NonEmpty::from((
SubTree::Branch {
key: String::from("a"),
forest: Box::new(Tree::branch(
String::from("b"),
Tree::node(String::from("c"), c_node)
)),
},
vec![SubTree::Branch {
key: String::from("d"),
forest: Box::new(Tree::branch(
String::from("e"),
Tree::node(String::from("f"), f_node)
))
}]
)))))
);
}
#[test]
fn test_insert_branches_and_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let d_label = String::from("d");
let e_label = String::from("e");
let f_label = String::from("f");
let g_label = String::from("g");
let c_path = NonEmpty::from((a_label.clone(), vec![b_label.clone(), c_label]));
let d_path = NonEmpty::from((a_label.clone(), vec![b_label, d_label]));
let e_path = NonEmpty::from((a_label.clone(), vec![e_label]));
let g_path = NonEmpty::from((a_label, vec![f_label, g_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
let d_node = TestNode { id: 3 };
let e_node = TestNode { id: 2 };
let g_node = TestNode { id: 2 };
tree.insert(d_path, d_node.clone());
tree.insert(c_path, c_node.clone());
tree.insert(e_path, e_node.clone());
tree.insert(g_path, g_node.clone());
assert_eq!(
tree,
Forest(Some(Tree::branch(
String::from("a"),
Tree(NonEmpty::from((
SubTree::Branch {
key: String::from("b"),
forest: Box::new(Tree(NonEmpty::from((
SubTree::Node {
key: String::from("c"),
value: c_node
},
vec![SubTree::Node {
key: String::from("d"),
value: d_node
}]
))))
},
vec![
SubTree::Node {
key: String::from("e"),
value: e_node
},
SubTree::Branch {
key: String::from("f"),
forest: Box::new(Tree::node(String::from("g"), g_node))
},
]
)))
)))
);
}
#[test]
fn test_find_root_node() {
let a_label = String::from("a");
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
tree.insert(NonEmpty::new(a_label), a_node.clone());
assert_eq!(
tree.find(NonEmpty::new(String::from("a"))),
Some(&SubTree::Node {
key: String::from("a"),
value: a_node
})
);
assert_eq!(tree.find(NonEmpty::new(String::from("b"))), None);
}
#[test]
fn test_find_branch_and_node() {
let a_label = String::from("a");
let b_label = String::from("b");
let c_label = String::from("c");
let path = NonEmpty::from((a_label, vec![b_label, c_label]));
let mut tree = Forest::root();
let c_node = TestNode { id: 1 };
tree.insert(path, c_node.clone());
assert_eq!(
tree.find(NonEmpty::new(String::from("a"))),
Some(&SubTree::Branch {
key: String::from("a"),
forest: Box::new(Tree::branch(
String::from("b"),
Tree::node(String::from("c"), c_node.clone())
))
})
);
assert_eq!(
tree.find(NonEmpty::from((String::from("a"), vec![String::from("b")]))),
Some(&SubTree::Branch {
key: String::from("b"),
forest: Box::new(Tree::node(String::from("c"), c_node.clone()))
})
);
assert_eq!(
tree.find(NonEmpty::from((
String::from("a"),
vec![String::from("b"), String::from("c")]
))),
Some(&SubTree::Node {
key: String::from("c"),
value: c_node
})
);
assert_eq!(tree.find(NonEmpty::new(String::from("b"))), None);
assert_eq!(
tree.find(NonEmpty::from((String::from("a"), vec![String::from("c")]))),
None
);
}
#[test]
fn test_maximum_by_root_nodes() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(NonEmpty::new(String::from("a")), a_node.clone());
tree.insert(NonEmpty::new(String::from("b")), b_node.clone());
assert_eq!(tree.maximum_by(|a, b| a.id.cmp(&b.id)), Some(&b_node));
assert_eq!(
tree.maximum_by(|a, b| a.id.cmp(&b.id).reverse()),
Some(&a_node)
);
}
#[test]
fn test_maximum_by_branch_and_node() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(
NonEmpty::from((String::from("c"), vec![String::from("a")])),
a_node.clone(),
);
tree.insert(NonEmpty::new(String::from("b")), b_node.clone());
assert_eq!(tree.maximum_by(|a, b| a.id.cmp(&b.id)), Some(&b_node));
assert_eq!(
tree.maximum_by(|a, b| a.id.cmp(&b.id).reverse()),
Some(&a_node)
);
}
#[test]
fn test_maximum_by_branch_and_branch() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(
NonEmpty::from((String::from("c"), vec![String::from("a")])),
a_node.clone(),
);
tree.insert(
NonEmpty::from((String::from("d"), vec![String::from("a")])),
b_node.clone(),
);
assert_eq!(tree.maximum_by(|a, b| a.id.cmp(&b.id)), Some(&b_node));
assert_eq!(
tree.maximum_by(|a, b| a.id.cmp(&b.id).reverse()),
Some(&a_node)
);
}
#[test]
fn test_maximum_by_branch_nodes() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(
NonEmpty::from((String::from("c"), vec![String::from("a")])),
a_node.clone(),
);
tree.insert(
NonEmpty::from((String::from("c"), vec![String::from("b")])),
b_node.clone(),
);
assert_eq!(tree.maximum_by(|a, b| a.id.cmp(&b.id)), Some(&b_node));
assert_eq!(
tree.maximum_by(|a, b| a.id.cmp(&b.id).reverse()),
Some(&a_node)
);
}
#[test]
fn test_fold_root_nodes() {
let mut tree = Forest::root();
let a_node = TestNode { id: 1 };
let b_node = TestNode { id: 3 };
tree.insert(NonEmpty::new(String::from("a")), a_node);
tree.insert(NonEmpty::new(String::from("b")), b_node);
assert_eq!(tree.iter().fold(0, |b, a| a.id + b), 4);
}
+4
View File
@@ -29,3 +29,7 @@ features = ["test"]
[dev-dependencies.link-git-test]
path = "../link-git/t"
features = ["test"]
[dev-dependencies.radicle-surf-test]
path = "../radicle-surf/t"
features = ["test"]