mirror of
https://seed.radicle.dev/rad:z6cFWeWpnZNHh9rUW8phgA3b5yGt.git
synced 2026-10-31 08:50:45 +08:00
radicle-git init
Port over all git relevant parts of the radicle-link repository[0]. The packages that are kept are: * git-ext * git-ref-format * git-trailers * link-git * macros * std-ext * test -- only the portions that test the above The development and testing scripts are also added for local and CI testing. [0]: https://github.com/radicle-dev/radicle-link/ Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// Copyright © 2022 The Radicle Link Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
/// Any unicode "word" is trivially a valid refname.
|
||||
pub fn trivial() -> impl Strategy<Value = String> {
|
||||
"\\w+"
|
||||
}
|
||||
|
||||
pub fn valid() -> impl Strategy<Value = String> {
|
||||
prop::collection::vec(trivial(), 1..20).prop_map(|xs| xs.join("/"))
|
||||
}
|
||||
|
||||
pub fn invalid_char() -> impl Strategy<Value = char> {
|
||||
prop_oneof![
|
||||
Just('\0'),
|
||||
Just('\\'),
|
||||
Just('~'),
|
||||
Just('^'),
|
||||
Just(':'),
|
||||
Just('?'),
|
||||
Just('[')
|
||||
]
|
||||
}
|
||||
|
||||
pub fn with_invalid_char() -> impl Strategy<Value = String> {
|
||||
("\\w*", invalid_char(), "\\w*").prop_map(|(mut pre, invalid, suf)| {
|
||||
pre.push(invalid);
|
||||
pre.push_str(&suf);
|
||||
pre
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ends_with_dot_lock() -> impl Strategy<Value = String> {
|
||||
"\\w*\\.lock"
|
||||
}
|
||||
|
||||
pub fn with_double_dot() -> impl Strategy<Value = String> {
|
||||
"\\w*\\.\\.\\w*"
|
||||
}
|
||||
|
||||
pub fn starts_with_dot() -> impl Strategy<Value = String> {
|
||||
"\\.\\w*"
|
||||
}
|
||||
|
||||
pub fn ends_with_dot() -> impl Strategy<Value = String> {
|
||||
"\\w+\\."
|
||||
}
|
||||
|
||||
pub fn with_control_char() -> impl Strategy<Value = String> {
|
||||
"\\w*[\x01-\x1F\x7F]+\\w*"
|
||||
}
|
||||
|
||||
pub fn with_space() -> impl Strategy<Value = String> {
|
||||
"\\w* +\\w*"
|
||||
}
|
||||
|
||||
pub fn with_consecutive_slashes() -> impl Strategy<Value = String> {
|
||||
"\\w*//\\w*"
|
||||
}
|
||||
|
||||
pub fn with_glob() -> impl Strategy<Value = String> {
|
||||
"\\w*\\*\\w*"
|
||||
}
|
||||
|
||||
pub fn multi_glob() -> impl Strategy<Value = String> {
|
||||
(
|
||||
prop::collection::vec(with_glob(), 2..5),
|
||||
prop::collection::vec(trivial(), 0..5),
|
||||
)
|
||||
.prop_map(|(mut globs, mut valids)| {
|
||||
globs.append(&mut valids);
|
||||
globs
|
||||
})
|
||||
.prop_shuffle()
|
||||
.prop_map(|xs| xs.join("/"))
|
||||
}
|
||||
|
||||
pub fn invalid() -> impl Strategy<Value = String> {
|
||||
fn path(s: impl Strategy<Value = String>) -> impl Strategy<Value = String> {
|
||||
prop::collection::vec(s, 1..20).prop_map(|xs| xs.join("/"))
|
||||
}
|
||||
|
||||
prop_oneof![
|
||||
Just(String::from("")),
|
||||
Just(String::from("@")),
|
||||
path(with_invalid_char()),
|
||||
path(ends_with_dot_lock()),
|
||||
path(with_double_dot()),
|
||||
path(starts_with_dot()),
|
||||
path(ends_with_dot()),
|
||||
path(with_control_char()),
|
||||
path(with_space()),
|
||||
path(with_consecutive_slashes()),
|
||||
path(trivial()).prop_map(|mut p| {
|
||||
p.push('/');
|
||||
p
|
||||
}),
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright © 2022 The Radicle Link Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate assert_matches;
|
||||
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
pub mod gen;
|
||||
#[cfg(test)]
|
||||
mod properties;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright © 2022 The Radicle Link Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use git_ref_format::{check_ref_format, Error, Options};
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::gen;
|
||||
|
||||
mod name;
|
||||
mod pattern;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn disallow_onelevel(input in gen::trivial(), allow_pattern in any::<bool>()) {
|
||||
assert_matches!(
|
||||
check_ref_format(Options {
|
||||
allow_onelevel: false,
|
||||
allow_pattern,
|
||||
},
|
||||
&input
|
||||
),
|
||||
Err(Error::OneLevel)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright © 2022 The Radicle Link Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use git_ref_format::{Error, RefStr, RefString};
|
||||
use proptest::prelude::*;
|
||||
use test_helpers::roundtrip;
|
||||
|
||||
use crate::gen;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn valid(input in gen::valid()) {
|
||||
assert_eq!(input.as_str(), RefStr::try_from_str(&input).unwrap().as_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_char(input in gen::with_invalid_char()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::InvalidChar(_)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_lock(input in gen::ends_with_dot_lock()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::DotLock))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_dot(input in gen::with_double_dot()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::DotDot))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn starts_dot(input in gen::starts_with_dot()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::StartsDot))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ends_dot(input in gen::ends_with_dot()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::EndsDot))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_char(input in gen::with_control_char()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::Control))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn space(input in gen::with_space()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::Space))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn consecutive_slashes(input in gen::with_consecutive_slashes()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::Slash))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glob(input in gen::with_glob()) {
|
||||
assert_matches!(RefString::try_from(input), Err(Error::InvalidChar('*')))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid(input in gen::invalid()) {
|
||||
assert_matches!(RefString::try_from(input), Err(_))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_components(input in gen::valid()) {
|
||||
assert_eq!(
|
||||
input.as_str(),
|
||||
RefStr::try_from_str(&input).unwrap().components().collect::<RefString>().as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json(input in gen::valid()) {
|
||||
roundtrip::json(RefString::try_from(input).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cbor(input in gen::valid()) {
|
||||
roundtrip::cbor(RefString::try_from(input).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright © 2022 The Radicle Link Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use git_ref_format::{refspec, Error};
|
||||
use proptest::prelude::*;
|
||||
use test_helpers::roundtrip;
|
||||
|
||||
use crate::gen;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn valid(input in gen::with_glob()) {
|
||||
assert_eq!(input.as_str(), refspec::PatternStr::try_from_str(&input).unwrap().as_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refname_is_pattern(input in gen::valid()) {
|
||||
assert_eq!(input.as_str(), refspec::PatternStr::try_from_str(&input).unwrap().as_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_more_than_one_star(input in gen::multi_glob()) {
|
||||
assert_matches!(refspec::PatternString::try_from(input), Err(Error::Pattern))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_refname_is_invalid_pattern(input in gen::invalid()) {
|
||||
assert_matches!(refspec::PatternString::try_from(input), Err(_))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_components(input in gen::with_glob()) {
|
||||
assert_eq!(
|
||||
input.as_str(),
|
||||
refspec::PatternStr::try_from_str(&input)
|
||||
.unwrap()
|
||||
.components()
|
||||
.collect::<Result<refspec::PatternString, _>>()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json(input in gen::with_glob()) {
|
||||
roundtrip::json(refspec::PatternString::try_from(input).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cbor(input in gen::with_glob()) {
|
||||
roundtrip::cbor(refspec::PatternString::try_from(input).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// Copyright © 2022 The Radicle Link Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use git_ref_format::{component, name, refname, refspec, Error, Qualified, RefStr, RefString};
|
||||
|
||||
#[test]
|
||||
fn refname_macro_works() {
|
||||
assert_eq!("refs/heads/main", refname!("refs/heads/main").as_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component_macro_works() {
|
||||
assert_eq!("self", name::component!("self").as_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_macro_works() {
|
||||
assert_eq!("refs/heads/*", refspec::pattern!("refs/heads/*").as_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_matches!(RefStr::try_from_str(""), Err(Error::Empty));
|
||||
assert_matches!(RefString::try_from("".to_owned()), Err(Error::Empty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join() {
|
||||
let s = name::REFS.join(name::HEADS);
|
||||
let t = s.join(name::MAIN);
|
||||
assert_eq!("refs/heads", s.as_str());
|
||||
assert_eq!("refs/heads/main", t.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_and() {
|
||||
assert_eq!(
|
||||
"refs/heads/this/that",
|
||||
name::REFS
|
||||
.join(name::HEADS)
|
||||
.and(refname!("this"))
|
||||
.and(refname!("that"))
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_prefix() {
|
||||
assert_eq!(
|
||||
"main",
|
||||
name::REFS_HEADS_MAIN
|
||||
.strip_prefix(refname!("refs/heads"))
|
||||
.unwrap()
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_prefix_not_prefix() {
|
||||
assert!(name::REFS_HEADS_MAIN
|
||||
.strip_prefix(refname!("refs/tags"))
|
||||
.is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified() {
|
||||
assert_eq!(
|
||||
"refs/heads/main",
|
||||
name::REFS_HEADS_MAIN.qualified().unwrap().as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_tag() {
|
||||
assert_eq!(
|
||||
"refs/tags/v1",
|
||||
refname!("refs/tags/v1").qualified().unwrap().as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_remote_tracking() {
|
||||
assert_eq!(
|
||||
"refs/remotes/origin/master",
|
||||
refname!("refs/remotes/origin/master")
|
||||
.qualified()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_qualified() {
|
||||
assert!(name::MAIN.qualified().is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_from_components() {
|
||||
assert_eq!(
|
||||
"refs/heads/main",
|
||||
Qualified::from_components(component::HEADS, component::MAIN, None).as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_from_components_with_iter() {
|
||||
assert_eq!(
|
||||
"refs/heads/foo/bar/baz",
|
||||
Qualified::from_components(
|
||||
component::HEADS,
|
||||
name::component!("foo"),
|
||||
[name::component!("bar"), name::component!("baz")]
|
||||
)
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_from_components_non_empty_iter() {
|
||||
let q = Qualified::from_components(component::HEADS, component::MAIN, None);
|
||||
let (refs, heads, main, mut empty) = q.non_empty_iter();
|
||||
assert!(empty.next().is_none());
|
||||
assert_eq!(("refs", "heads", "main"), (refs, heads, main))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_from_components_non_empty_components() {
|
||||
let q = Qualified::from_components(component::HEADS, component::MAIN, Some(component::MASTER));
|
||||
let (refs, heads, main, mut master) = q.non_empty_components();
|
||||
assert_eq!(
|
||||
(
|
||||
component::REFS,
|
||||
component::HEADS,
|
||||
component::MAIN,
|
||||
component::MASTER
|
||||
),
|
||||
(refs, heads, main, master.next().unwrap())
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespaced() {
|
||||
assert_eq!(
|
||||
"refs/namespaces/foo/refs/heads/main",
|
||||
refname!("refs/namespaces/foo/refs/heads/main")
|
||||
.namespaced()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_namespaced() {
|
||||
assert!(name::REFS_HEADS_MAIN.namespaced().is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_namespaced_because_not_qualified() {
|
||||
assert!(refname!("refs/namespaces/foo/banana")
|
||||
.namespaced()
|
||||
.is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_namespace() {
|
||||
assert_eq!(
|
||||
"refs/rad/id",
|
||||
refname!("refs/namespaces/xyz/refs/rad/id")
|
||||
.namespaced()
|
||||
.unwrap()
|
||||
.strip_namespace()
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strip_nested_namespaces() {
|
||||
let full = refname!("refs/namespaces/a/refs/namespaces/b/refs/heads/main");
|
||||
let namespaced = full.namespaced().unwrap();
|
||||
let strip_first = namespaced.strip_namespace();
|
||||
let nested = strip_first.namespaced().unwrap();
|
||||
let strip_second = nested.strip_namespace();
|
||||
|
||||
assert_eq!("a", namespaced.namespace().as_str());
|
||||
assert_eq!("b", nested.namespace().as_str());
|
||||
assert_eq!("refs/namespaces/b/refs/heads/main", strip_first.as_str());
|
||||
assert_eq!("refs/heads/main", strip_second.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_namespace() {
|
||||
assert_eq!(
|
||||
"refs/namespaces/foo/refs/heads/main",
|
||||
name::REFS_HEADS_MAIN
|
||||
.qualified()
|
||||
.unwrap()
|
||||
.add_namespace(refname!("foo").head())
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iter() {
|
||||
assert_eq!(
|
||||
vec!["refs", "heads", "main"],
|
||||
name::REFS_HEADS_MAIN.iter().collect::<Vec<_>>()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_pop() {
|
||||
let mut s = name::REFS.to_owned();
|
||||
s.push(name::HEADS);
|
||||
s.push(name::MAIN);
|
||||
|
||||
assert_eq!("refs/heads/main", s.as_str());
|
||||
assert!(s.pop());
|
||||
assert!(s.pop());
|
||||
assert_eq!("refs", s.as_str());
|
||||
assert!(!s.pop());
|
||||
assert_eq!("refs", s.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_pattern() {
|
||||
assert_eq!(
|
||||
"refs/heads/*",
|
||||
refname!("refs/heads")
|
||||
.to_pattern(refspec::pattern!("*"))
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_pattern() {
|
||||
assert_eq!(
|
||||
"refs/heads/*",
|
||||
refname!("refs/heads").with_pattern(refspec::STAR).as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_pattern_and() {
|
||||
assert_eq!(
|
||||
"refs/*/heads",
|
||||
refname!("refs")
|
||||
.with_pattern(refspec::STAR)
|
||||
.and(name::HEADS)
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect() {
|
||||
assert_eq!(
|
||||
"refs/heads/main",
|
||||
IntoIterator::into_iter([name::REFS, name::HEADS, name::MAIN])
|
||||
.collect::<RefString>()
|
||||
.as_str()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_components() {
|
||||
let a = name::REFS_HEADS_MAIN.to_owned();
|
||||
let b = a.components().collect();
|
||||
assert_eq!(a, b)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_pattern_duplicate_glob() {
|
||||
assert_matches!(
|
||||
IntoIterator::into_iter([
|
||||
refspec::Component::Normal(name::REFS),
|
||||
refspec::Component::Glob(None),
|
||||
refspec::Component::Glob(Some(refspec::pattern!("fo*").as_ref()))
|
||||
])
|
||||
.collect::<Result<_, _>>(),
|
||||
Err(refspec::DuplicateGlob)
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user