sigrefs/git/properties: Restore property tests
These tests fell under the radar when refactoring. Restore them to the latest code so that these properties are tested.
This commit is contained in:
parent
92347a307f
commit
b482845e71
|
|
@ -28,9 +28,12 @@ pub use git2::{
|
||||||
AnnotatedCommit, Diff, DiffFindOptions, DiffOptions, DiffStats, MergeAnalysis, MergeOptions,
|
AnnotatedCommit, Diff, DiffFindOptions, DiffOptions, DiffStats, MergeAnalysis, MergeOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Re-exports for `radicle-cli`.
|
|
||||||
pub mod build {
|
pub mod build {
|
||||||
|
// Re-exports for `radicle-cli`.
|
||||||
pub use git2::build::CheckoutBuilder;
|
pub use git2::build::CheckoutBuilder;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) use git2::build::TreeUpdateBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod transport {
|
pub(crate) mod transport {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
pub mod object;
|
pub mod object;
|
||||||
pub mod reference;
|
pub mod reference;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod properties;
|
||||||
|
|
||||||
use crypto::PublicKey;
|
use crypto::PublicKey;
|
||||||
use radicle_git_metadata::author::Author;
|
use radicle_git_metadata::author::Author;
|
||||||
use radicle_git_metadata::author::Time;
|
use radicle_git_metadata::author::Time;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use crypto::{signature, test::signer::MockSigner, PublicKey, Signer as _};
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crypto::test::signer::MockSigner;
|
||||||
|
use crypto::{PublicKey, Signer as _, signature};
|
||||||
use qcheck::TestResult;
|
use qcheck::TestResult;
|
||||||
use qcheck_macros::quickcheck;
|
use qcheck_macros::quickcheck;
|
||||||
use radicle_core::{NodeId, RepoId};
|
use radicle_core::{NodeId, RepoId};
|
||||||
|
|
@ -6,12 +9,16 @@ use radicle_git_metadata::author::{Author, Time};
|
||||||
use radicle_oid::Oid;
|
use radicle_oid::Oid;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
use crate::storage::refs::sigrefs::{
|
use crate::git;
|
||||||
read::{CheckpointReason, Latest, SignedRefsReader, Tip},
|
use crate::storage::refs::sigrefs::VerifiedCommit;
|
||||||
write::{Committer, SignedRefsWriter, Update},
|
use crate::storage::refs::sigrefs::read::{SignedRefsReader, Tip};
|
||||||
};
|
use crate::storage::refs::sigrefs::write::{SignedRefsWriter, Update};
|
||||||
use crate::storage::refs::Refs;
|
use crate::storage::refs::{IDENTITY_ROOT, Refs};
|
||||||
|
|
||||||
|
use super::Committer;
|
||||||
|
|
||||||
|
/// Newtype wrapper around [`Vec`] to keep the [`Arbitrary`] implementation
|
||||||
|
/// bounded to a smaller size.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct BoundedVec<T>(Vec<T>);
|
struct BoundedVec<T>(Vec<T>);
|
||||||
|
|
||||||
|
|
@ -58,24 +65,69 @@ fn mock_committer() -> Committer {
|
||||||
Committer::new(mock_author())
|
Committer::new(mock_author())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup() -> (TempDir, git2::Repository) {
|
/// A helper structure that sets up a minimal Radicle repository.
|
||||||
let dir = TempDir::new().unwrap();
|
struct Repository {
|
||||||
let repo = git2::Repository::init_bare(dir.path()).unwrap();
|
raw: git::raw::Repository,
|
||||||
(dir, repo)
|
rid: RepoId,
|
||||||
|
root: Oid,
|
||||||
|
_tmp: TempDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mock_root() -> RepoId {
|
impl Repository {
|
||||||
RepoId::from(Oid::from_sha1([1; 20]))
|
fn new() -> Repository {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let repo = git::raw::Repository::init_bare(dir.path()).unwrap();
|
||||||
|
let (root, rid) = Self::write_identity(&repo);
|
||||||
|
Repository {
|
||||||
|
raw: repo,
|
||||||
|
rid,
|
||||||
|
root,
|
||||||
|
_tmp: dir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a mock blob that represents the identity document, and creates a
|
||||||
|
/// commit in the repository that contains this blob.
|
||||||
|
fn write_identity(repo: &git::raw::Repository) -> (Oid, RepoId) {
|
||||||
|
let blob = repo.blob(b"identity root").unwrap();
|
||||||
|
let empty = {
|
||||||
|
let empty = repo.treebuilder(None).unwrap();
|
||||||
|
let tree = empty.write().unwrap();
|
||||||
|
repo.find_tree(tree).unwrap()
|
||||||
|
};
|
||||||
|
let tree = {
|
||||||
|
let mut tb = git::raw::build::TreeUpdateBuilder::new();
|
||||||
|
tb.upsert(
|
||||||
|
Path::new("embeds").join(*crate::identity::doc::PATH),
|
||||||
|
blob,
|
||||||
|
git::raw::FileMode::Blob,
|
||||||
|
);
|
||||||
|
let tree = tb.create_updated(repo, &empty).unwrap();
|
||||||
|
repo.find_tree(tree).unwrap()
|
||||||
|
};
|
||||||
|
let author = git::raw::Signature::now("testy", "testy@example.com").unwrap();
|
||||||
|
let root = repo
|
||||||
|
.commit(
|
||||||
|
Some(IDENTITY_ROOT.as_str()),
|
||||||
|
&author,
|
||||||
|
&author,
|
||||||
|
"identity root",
|
||||||
|
&tree,
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
(root.into(), RepoId::from(blob))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_log(
|
fn write_log(
|
||||||
refs: Refs,
|
refs: Refs,
|
||||||
|
rid: RepoId,
|
||||||
namespace: NodeId,
|
namespace: NodeId,
|
||||||
signer: &MockSigner,
|
signer: &MockSigner,
|
||||||
repo: &git2::Repository,
|
repo: &git::raw::Repository,
|
||||||
) -> Update {
|
) -> Update {
|
||||||
SignedRefsWriter::new(namespace, signer, repo)
|
SignedRefsWriter::new(refs, rid, namespace, repo, signer)
|
||||||
.with_refs(refs)
|
|
||||||
.write(
|
.write(
|
||||||
mock_committer(),
|
mock_committer(),
|
||||||
"test commit".to_string(),
|
"test commit".to_string(),
|
||||||
|
|
@ -84,36 +136,42 @@ fn write_log(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_log(namespace: NodeId, verifier: &Verifier, repo: &git2::Repository) -> Latest {
|
fn read_log(
|
||||||
SignedRefsReader::new(mock_root(), Tip::Reference(namespace), repo, verifier)
|
rid: RepoId,
|
||||||
|
namespace: NodeId,
|
||||||
|
verifier: &Verifier,
|
||||||
|
repo: &git::raw::Repository,
|
||||||
|
) -> VerifiedCommit {
|
||||||
|
SignedRefsReader::new(rid, Tip::Reference(namespace), repo, verifier)
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn initial_commit_roundtrip(refs: Refs) -> bool {
|
fn initial_commit_roundtrip(mut refs: Refs) -> bool {
|
||||||
let (_dir, repo) = setup();
|
let Repository {
|
||||||
|
raw: repo,
|
||||||
|
rid,
|
||||||
|
root,
|
||||||
|
_tmp,
|
||||||
|
} = Repository::new();
|
||||||
|
refs.insert(IDENTITY_ROOT.to_ref_string(), root);
|
||||||
let signer = MockSigner::default();
|
let signer = MockSigner::default();
|
||||||
let namespace = *signer.public_key();
|
let namespace = *signer.public_key();
|
||||||
let verifier = Verifier::new(&signer);
|
let verifier = Verifier::new(&signer);
|
||||||
|
|
||||||
let update = write_log(refs.clone(), namespace, &signer, &repo);
|
let update = write_log(refs.clone(), rid, namespace, &signer, &repo);
|
||||||
let head_oid = match update {
|
let head_oid = match update {
|
||||||
Update::Changed { ref entry } => *entry.oid(),
|
Update::Changed { ref entry, .. } => entry.oid(),
|
||||||
Update::Unchanged { .. } => return false,
|
Update::Unchanged { .. } => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let Latest {
|
let verified_commit = read_log(rid, namespace, &verifier, &repo);
|
||||||
refs: expected,
|
let head = *verified_commit.commit().oid();
|
||||||
checkpoint,
|
let parent = verified_commit.commit().parent().copied();
|
||||||
..
|
let new_refs = verified_commit.into_sigrefs_at(namespace);
|
||||||
} = read_log(namespace, &verifier, &repo);
|
|
||||||
|
|
||||||
checkpoint.head() == head_oid
|
head == head_oid && parent.is_none() && *new_refs == refs
|
||||||
&& checkpoint.ancestor() == head_oid
|
|
||||||
&& checkpoint.reason() == CheckpointReason::Root
|
|
||||||
&& expected == refs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
|
|
@ -123,56 +181,72 @@ fn chain_roundtrip(chain: BoundedVec<Refs>) -> TestResult {
|
||||||
return TestResult::discard();
|
return TestResult::discard();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_dir, repo) = setup();
|
let Repository {
|
||||||
|
raw: repo,
|
||||||
|
rid,
|
||||||
|
root,
|
||||||
|
_tmp,
|
||||||
|
} = Repository::new();
|
||||||
let signer = MockSigner::default();
|
let signer = MockSigner::default();
|
||||||
let namespace = *signer.public_key();
|
let namespace = *signer.public_key();
|
||||||
let verifier = Verifier::new(&signer);
|
let verifier = Verifier::new(&signer);
|
||||||
|
|
||||||
let mut last_changed_head = None;
|
let mut last_changed_head = None;
|
||||||
|
let mut expected_parent = None;
|
||||||
|
|
||||||
for refs in chain {
|
for mut refs in chain {
|
||||||
let update = write_log(refs.clone(), namespace, &signer, &repo);
|
refs.insert(IDENTITY_ROOT.to_ref_string(), root);
|
||||||
|
let update = write_log(refs.clone(), rid, namespace, &signer, &repo);
|
||||||
|
|
||||||
if let Update::Changed { ref entry } = update {
|
if let Update::Changed { ref entry, .. } = update {
|
||||||
last_changed_head = Some(*entry.oid());
|
last_changed_head = Some(entry.oid());
|
||||||
}
|
}
|
||||||
|
|
||||||
let Latest {
|
let verified_commit = read_log(rid, namespace, &verifier, &repo);
|
||||||
refs: expected,
|
let head = *verified_commit.commit().oid();
|
||||||
checkpoint,
|
let parent = verified_commit.commit().parent().copied();
|
||||||
..
|
let new_refs = verified_commit.into_sigrefs_at(namespace);
|
||||||
} = read_log(namespace, &verifier, &repo);
|
|
||||||
|
|
||||||
if refs != expected {
|
if refs != *new_refs {
|
||||||
return TestResult::failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkpoint.reason() != CheckpointReason::Root {
|
|
||||||
return TestResult::failed();
|
return TestResult::failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(expected_head) = last_changed_head {
|
if let Some(expected_head) = last_changed_head {
|
||||||
if checkpoint.head() != expected_head {
|
if head != expected_head {
|
||||||
return TestResult::failed();
|
return TestResult::error(format!(
|
||||||
|
"expected commit to be {expected_head}, but found {head}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if parent != expected_parent {
|
||||||
|
return TestResult::error(format!(
|
||||||
|
"expected parent commit to be {expected_parent:?}, but found {parent:?}"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
expected_parent = Some(head);
|
||||||
}
|
}
|
||||||
|
|
||||||
TestResult::passed()
|
TestResult::passed()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[quickcheck]
|
#[quickcheck]
|
||||||
fn idempotent_write(refs: Refs) -> bool {
|
fn idempotent_write(mut refs: Refs) -> bool {
|
||||||
let (_dir, repo) = setup();
|
let Repository {
|
||||||
|
raw: repo,
|
||||||
|
rid,
|
||||||
|
root,
|
||||||
|
_tmp,
|
||||||
|
} = Repository::new();
|
||||||
|
refs.insert(IDENTITY_ROOT.to_ref_string(), root);
|
||||||
let signer = MockSigner::default();
|
let signer = MockSigner::default();
|
||||||
let namespace = *signer.public_key();
|
let namespace = *signer.public_key();
|
||||||
|
|
||||||
let first = write_log(refs.clone(), namespace, &signer, &repo);
|
let first = write_log(refs.clone(), rid, namespace, &signer, &repo);
|
||||||
let head_oid = match first {
|
let head_oid = match first {
|
||||||
Update::Changed { ref entry } => *entry.oid(),
|
Update::Changed { ref entry, .. } => entry.oid(),
|
||||||
Update::Unchanged { .. } => return false,
|
Update::Unchanged { .. } => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let second = write_log(refs, namespace, &signer, &repo);
|
let second = write_log(refs, rid, namespace, &signer, &repo);
|
||||||
matches!(second, Update::Unchanged { commit, .. } if commit == head_oid)
|
matches!(second, Update::Unchanged { verified } if *verified.commit().oid() == head_oid)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,11 @@ pub struct Commit {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Commit {
|
impl Commit {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(super) fn oid(&self) -> Oid {
|
||||||
|
self.oid
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(super) fn into_refs(self) -> Refs {
|
pub(super) fn into_refs(self) -> Refs {
|
||||||
self.refs
|
self.refs
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue