Skip to content

Commit

Permalink
add quickcheck tests
Browse files Browse the repository at this point in the history
  • Loading branch information
asmello committed Mar 31, 2024
1 parent b7b5ded commit cac285e
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 4 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ url = { version = "2", optional = true }
[features]
default = ["std"]
std = ["serde/std", "serde_json/std", "fluent-uri?/std"]

[dev-dependencies]
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
32 changes: 32 additions & 0 deletions src/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use crate::{PointerBuf, Token};
use quickcheck::Arbitrary;

impl Arbitrary for Token {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
Self::new(String::arbitrary(g))
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let s = self.to_string();
Box::new(s.shrink().map(Self::new))
}
}

impl Arbitrary for PointerBuf {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let size = usize::arbitrary(g) % g.size();
Self::from_tokens((0..size).map(|_| Token::arbitrary(g)).collect::<Vec<_>>())
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let tokens: Vec<_> = self.tokens().collect();
Box::new((0..self.count()).map(move |i| {
let subset: Vec<_> = tokens
.iter()
.enumerate()
.filter_map(|(j, t)| (i != j).then_some(t.clone()))
.collect();
Self::from_tokens(subset)
}))
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ pub mod prelude;

mod tokens;
pub use tokens::*;

#[cfg(test)]
mod arbitrary;
155 changes: 151 additions & 4 deletions src/pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,15 +386,17 @@ impl Pointer {
/// Finds the commonality between this and another `Pointer`.
pub fn intersection<'a>(&'a self, other: &Self) -> &'a Self {
let mut last_slash = 0;
for (i, (a, b)) in self.0.chars().zip(other.0.chars()).enumerate() {
let mut idx = 0;
for (a, b) in self.0.bytes().zip(other.0.bytes()) {
if a != b {
return Self::new(&self.0[..last_slash]);
}
if a == '/' {
last_slash = i;
if a == b'/' {
last_slash = idx;
}
idx += 1;
}
self
Self::new(&self.0[..idx])
}

/// Attempts to delete a `serde_json::Value` based upon the path in this
Expand Down Expand Up @@ -988,6 +990,8 @@ impl core::fmt::Display for PointerBuf {

#[cfg(test)]
mod tests {
use quickcheck::TestResult;
use quickcheck_macros::quickcheck;
use serde_json::json;

use crate::{Resolve, ResolveMut};
Expand Down Expand Up @@ -1562,11 +1566,154 @@ mod tests {
}
}

#[test]
fn intersect() {
let base = Pointer::from_static("/foo/bar");
let a = Pointer::from_static("/foo/bar/qux");
let b = Pointer::from_static("/foo/bar");
assert_eq!(a.intersection(b), base);
}

#[test]
#[cfg(feature = "fluent-uri")]
fn test_try_from_fluent_uri() {
let uri = fluent_uri::Uri::parse("#/foo/bar").unwrap();
let ptr = PointerBuf::try_from(&uri).unwrap();
assert_eq!(ptr, "/foo/bar");
}

#[quickcheck]
fn qc_pop_and_push(mut ptr: PointerBuf) -> bool {
let original_ptr = ptr.clone();
let mut tokens = Vec::with_capacity(ptr.count());
while let Some(token) = ptr.pop_back() {
tokens.push(token);
}
if dbg!(ptr.count() != 0)
|| dbg!(!ptr.is_root())
|| dbg!(ptr.last().is_some())
|| dbg!(ptr.first().is_some())
{
return false;
}
for token in tokens.drain(..) {
ptr.push_front(token);
}
if ptr != original_ptr {
return false;
}
while let Some(token) = ptr.pop_front() {
tokens.push(token);
}
if dbg!(ptr.count() != 0)
|| dbg!(!ptr.is_root())
|| dbg!(ptr.last().is_some())
|| dbg!(ptr.first().is_some())
{
return false;
}
for token in tokens {
ptr.push_back(token);
}
ptr == original_ptr
}

#[quickcheck]
fn qc_split(ptr: PointerBuf) -> bool {
if let Some((head, tail)) = ptr.split_front() {
{
let Some(first) = ptr.first() else {
return false;
};
if first != head {
return false;
}
}
{
let mut copy = ptr.clone();
copy.pop_front();
if copy != tail {
return false;
}
}
{
let mut buf = tail.to_buf();
buf.push_front(head.clone());
if buf != ptr {
return false;
}
}
{
let fmt = format!("/{}{tail}", head.encoded());
if Pointer::parse(&fmt).unwrap() != ptr {
return false;
}
}
} else {
return ptr.is_root()
&& ptr.count() == 0
&& ptr.last().is_none()
&& ptr.first().is_none();
}
if let Some((head, tail)) = ptr.split_back() {
{
let Some(last) = ptr.last() else {
return false;
};
if last != tail {
return false;
}
}
{
let mut copy = ptr.clone();
copy.pop_back();
if copy != head {
return false;
}
}
{
let mut buf = head.to_buf();
buf.push_back(tail.clone());
if buf != ptr {
return false;
}
}
{
let fmt = format!("{head}/{}", tail.encoded());
if Pointer::parse(&fmt).unwrap() != ptr {
return false;
}
}
if Some(head) != ptr.parent() {
return false;
}
} else {
return ptr.is_root()
&& ptr.count() == 0
&& ptr.last().is_none()
&& ptr.first().is_none();
}
true
}

#[quickcheck]
fn qc_from_tokens(tokens: Vec<Token>) -> bool {
let buf = PointerBuf::from_tokens(&tokens);
let reconstructed: Vec<_> = buf.tokens().collect();
tokens == reconstructed
}

#[quickcheck]
fn qc_intersection(base: PointerBuf, suffix_0: PointerBuf, suffix_1: PointerBuf) -> TestResult {
if suffix_0.first() == suffix_1.first() {
// base must be the true intersection
return TestResult::discard();
}
let mut a = base.clone();
a.append(&suffix_0);
let mut b = base.clone();
b.append(&suffix_1);
let isect = a.intersection(&b);
TestResult::from_bool(isect == base)
}
}
13 changes: 13 additions & 0 deletions src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,3 +437,16 @@ impl Into<&str> for Escaped {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;

#[quickcheck]
fn encode_decode(token: Token) -> bool {
let encoded = token.encoded();
let decoded = Token::from_encoded(encoded);
token == decoded
}
}

0 comments on commit cac285e

Please sign in to comment.