mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-27 07:10:58 +08:00
Merge remote-tracking branch 'han/test-reorg'
* han/test-reorg: radicle-surf: test structure compliance.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 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 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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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));
|
||||
// }
|
||||
@@ -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")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 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 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(())
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user