Merge rust-bitcoin/rust-bitcoin#1513: Add mutation testing to the `locktime` module

ca471557a5 locktime: Add mutation testing (Tobin C. Harding)
26c0da41b4 locktime: Add inline to public functions (Tobin C. Harding)

Pull request description:

  Add mutation testing to the `locktime` module and add unit tests to cover all mutants and ensure they are killed.

ACKs for top commit:
  sanket1729:
    ACK ca471557a5. How stable are the mutation tests?
  apoelstra:
    ACK ca471557a5

Tree-SHA512: 46a59c90fc25b0c803e96f7c5b98bd39055f7835e45ba137b2a01ad4221a676c54bc228b9ef7663b7300bb4260a6c2c80a0820c4f1bf0987650e1e2bd699f62d
This commit is contained in:
Andrew Poelstra 2022-12-30 22:08:46 +00:00
commit 3e4a299615
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 174 additions and 3 deletions

View File

@ -14,6 +14,9 @@ use core::str::FromStr;
use bitcoin_internals::write_err; use bitcoin_internals::write_err;
#[cfg(all(test, mutate))]
use mutagen::mutate;
use crate::consensus::encode::{self, Decodable, Encodable}; use crate::consensus::encode::{self, Decodable, Encodable};
use crate::error::ParseIntError; use crate::error::ParseIntError;
use crate::io::{self, Read, Write}; use crate::io::{self, Read, Write};
@ -198,6 +201,7 @@ impl LockTime {
/// } /// }
/// ```` /// ````
#[inline] #[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_satisfied_by(&self, height: Height, time: Time) -> bool { pub fn is_satisfied_by(&self, height: Height, time: Time) -> bool {
use LockTime::*; use LockTime::*;
@ -226,6 +230,8 @@ impl LockTime {
/// let check = LockTime::from_consensus(100 + 1); /// let check = LockTime::from_consensus(100 + 1);
/// assert!(lock_time.is_implied_by(check)); /// assert!(lock_time.is_implied_by(check));
/// ``` /// ```
#[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_implied_by(&self, other: LockTime) -> bool { pub fn is_implied_by(&self, other: LockTime) -> bool {
use LockTime::*; use LockTime::*;
@ -273,18 +279,21 @@ impl LockTime {
impl_parse_str_through_int!(LockTime, from_consensus); impl_parse_str_through_int!(LockTime, from_consensus);
impl From<Height> for LockTime { impl From<Height> for LockTime {
#[inline]
fn from(h: Height) -> Self { fn from(h: Height) -> Self {
LockTime::Blocks(h) LockTime::Blocks(h)
} }
} }
impl From<Time> for LockTime { impl From<Time> for LockTime {
#[inline]
fn from(t: Time) -> Self { fn from(t: Time) -> Self {
LockTime::Seconds(t) LockTime::Seconds(t)
} }
} }
impl PartialOrd for LockTime { impl PartialOrd for LockTime {
#[inline]
fn partial_cmp(&self, other: &LockTime) -> Option<Ordering> { fn partial_cmp(&self, other: &LockTime) -> Option<Ordering> {
use LockTime::*; use LockTime::*;
@ -317,6 +326,7 @@ impl fmt::Display for LockTime {
impl FromHexStr for LockTime { impl FromHexStr for LockTime {
type Error = Error; type Error = Error;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> { fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let packed_lock_time = crate::parse::hex_u32(s)?; let packed_lock_time = crate::parse::hex_u32(s)?;
Ok(Self::from_consensus(packed_lock_time)) Ok(Self::from_consensus(packed_lock_time))
@ -431,6 +441,7 @@ impl fmt::Display for Height {
impl FromHexStr for Height { impl FromHexStr for Height {
type Error = Error; type Error = Error;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> { fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let height = crate::parse::hex_u32(s)?; let height = crate::parse::hex_u32(s)?;
Self::from_consensus(height) Self::from_consensus(height)
@ -441,6 +452,7 @@ impl FromHexStr for Height {
impl FromStr for Height { impl FromStr for Height {
type Err = Error; type Err = Error;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let n = parse::int(s)?; let n = parse::int(s)?;
Height::from_consensus(n) Height::from_consensus(n)
@ -450,6 +462,7 @@ impl FromStr for Height {
impl TryFrom<&str> for Height { impl TryFrom<&str> for Height {
type Error = Error; type Error = Error;
#[inline]
fn try_from(s: &str) -> Result<Self, Self::Error> { fn try_from(s: &str) -> Result<Self, Self::Error> {
let n = parse::int(s)?; let n = parse::int(s)?;
Height::from_consensus(n) Height::from_consensus(n)
@ -459,6 +472,7 @@ impl TryFrom<&str> for Height {
impl TryFrom<String> for Height { impl TryFrom<String> for Height {
type Error = Error; type Error = Error;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> { fn try_from(s: String) -> Result<Self, Self::Error> {
let n = parse::int(s)?; let n = parse::int(s)?;
Height::from_consensus(n) Height::from_consensus(n)
@ -524,6 +538,7 @@ impl fmt::Display for Time {
impl FromHexStr for Time { impl FromHexStr for Time {
type Error = Error; type Error = Error;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> { fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let time = crate::parse::hex_u32(s)?; let time = crate::parse::hex_u32(s)?;
Time::from_consensus(time) Time::from_consensus(time)
@ -533,6 +548,7 @@ impl FromHexStr for Time {
impl FromStr for Time { impl FromStr for Time {
type Err = Error; type Err = Error;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let n = parse::int(s)?; let n = parse::int(s)?;
Time::from_consensus(n) Time::from_consensus(n)
@ -542,6 +558,7 @@ impl FromStr for Time {
impl TryFrom<&str> for Time { impl TryFrom<&str> for Time {
type Error = Error; type Error = Error;
#[inline]
fn try_from(s: &str) -> Result<Self, Self::Error> { fn try_from(s: &str) -> Result<Self, Self::Error> {
let n = parse::int(s)?; let n = parse::int(s)?;
Time::from_consensus(n) Time::from_consensus(n)
@ -551,6 +568,7 @@ impl TryFrom<&str> for Time {
impl TryFrom<String> for Time { impl TryFrom<String> for Time {
type Error = Error; type Error = Error;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> { fn try_from(s: String) -> Result<Self, Self::Error> {
let n = parse::int(s)?; let n = parse::int(s)?;
Time::from_consensus(n) Time::from_consensus(n)
@ -606,18 +624,21 @@ impl std::error::Error for Error {
} }
impl From<ConversionError> for Error { impl From<ConversionError> for Error {
#[inline]
fn from(e: ConversionError) -> Self { fn from(e: ConversionError) -> Self {
Error::Conversion(e) Error::Conversion(e)
} }
} }
impl From<OperationError> for Error { impl From<OperationError> for Error {
#[inline]
fn from(e: OperationError) -> Self { fn from(e: OperationError) -> Self {
Error::Operation(e) Error::Operation(e)
} }
} }
impl From<ParseIntError> for Error { impl From<ParseIntError> for Error {
#[inline]
fn from(e: ParseIntError) -> Self { fn from(e: ParseIntError) -> Self {
Error::Parse(e) Error::Parse(e)
} }
@ -775,4 +796,90 @@ mod tests {
let result = Height::from_hex_str(hex); let result = Height::from_hex_str(hex);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test]
fn parses_correctly_to_height_or_time() {
let lock = LockTime::from_consensus(750_000);
assert!(lock.is_block_height());
assert!(!lock.is_block_time());
let t: u32 = 1653195600; // May 22nd, 5am UTC.
let lock = LockTime::from_consensus(t);
assert!(!lock.is_block_height());
assert!(lock.is_block_time());
}
#[test]
fn satisfied_by_height() {
let lock = LockTime::from_consensus(750_000);
let height = Height::from_consensus(800_000).expect("failed to parse height");
let t: u32 = 1653195600; // May 22nd, 5am UTC.
let time = Time::from_consensus(t).expect("invalid time value");
assert!(lock.is_satisfied_by(height, time))
}
#[test]
fn satisfied_by_time() {
let lock = LockTime::from_consensus(1053195600);
let t: u32 = 1653195600; // May 22nd, 5am UTC.
let time = Time::from_consensus(t).expect("invalid time value");
let height = Height::from_consensus(800_000).expect("failed to parse height");
assert!(lock.is_satisfied_by(height, time))
}
#[test]
fn satisfied_by_same_height() {
let h = 750_000;
let lock = LockTime::from_consensus(h);
let height = Height::from_consensus(h).expect("failed to parse height");
let t: u32 = 1653195600; // May 22nd, 5am UTC.
let time = Time::from_consensus(t).expect("invalid time value");
assert!(lock.is_satisfied_by(height, time))
}
#[test]
fn satisfied_by_same_time() {
let t: u32 = 1653195600; // May 22nd, 5am UTC.
let lock = LockTime::from_consensus(t);
let time = Time::from_consensus(t).expect("invalid time value");
let height = Height::from_consensus(800_000).expect("failed to parse height");
assert!(lock.is_satisfied_by(height, time))
}
#[test]
fn height_correctly_implies() {
let lock = LockTime::from_consensus(750_005);
assert!(!lock.is_implied_by(LockTime::from_consensus(750_004)));
assert!(lock.is_implied_by(LockTime::from_consensus(750_005)));
assert!(lock.is_implied_by(LockTime::from_consensus(750_006)));
}
#[test]
fn time_correctly_implies() {
let t: u32 = 1700000005;
let lock = LockTime::from_consensus(t);
assert!(!lock.is_implied_by(LockTime::from_consensus(1700000004)));
assert!(lock.is_implied_by(LockTime::from_consensus(1700000005)));
assert!(lock.is_implied_by(LockTime::from_consensus(1700000006)));
}
#[test]
fn incorrect_units_do_not_imply() {
let lock = LockTime::from_consensus(750_005);
assert!(!lock.is_implied_by(LockTime::from_consensus(1700000004)));
}
} }

View File

@ -10,6 +10,9 @@
use core::fmt; use core::fmt;
use core::convert::TryFrom; use core::convert::TryFrom;
#[cfg(all(test, mutate))]
use mutagen::mutate;
#[cfg(docsrs)] #[cfg(docsrs)]
use crate::relative; use crate::relative;
@ -52,6 +55,8 @@ impl LockTime {
/// let height_and_time = (current_time(), current_height()); // tuple order does not matter. /// let height_and_time = (current_time(), current_height()); // tuple order does not matter.
/// assert!(lock.is_satisfied_by(current_height(), current_time())); /// assert!(lock.is_satisfied_by(current_height(), current_time()));
/// ``` /// ```
#[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_satisfied_by(&self, h: Height, t: Time) -> bool { pub fn is_satisfied_by(&self, h: Height, t: Time) -> bool {
if let Ok(true) = self.is_satisfied_by_height(h) { if let Ok(true) = self.is_satisfied_by_height(h) {
true true
@ -92,6 +97,8 @@ impl LockTime {
/// }; /// };
/// assert!(satisfied); /// assert!(satisfied);
/// ``` /// ```
#[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_implied_by(&self, other: LockTime) -> bool { pub fn is_implied_by(&self, other: LockTime) -> bool {
use LockTime::*; use LockTime::*;
@ -119,6 +126,7 @@ impl LockTime {
/// assert!(lock.is_satisfied_by_height(Height::from(height+1)).expect("a height")); /// assert!(lock.is_satisfied_by_height(Height::from(height+1)).expect("a height"));
/// ``` /// ```
#[inline] #[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_satisfied_by_height(&self, h: Height) -> Result<bool, Error> { pub fn is_satisfied_by_height(&self, h: Height) -> Result<bool, Error> {
use LockTime::*; use LockTime::*;
@ -145,6 +153,7 @@ impl LockTime {
/// assert!(lock.is_satisfied_by_time(Time::from_512_second_intervals(intervals + 10)).expect("a time")); /// assert!(lock.is_satisfied_by_time(Time::from_512_second_intervals(intervals + 10)).expect("a time"));
/// ``` /// ```
#[inline] #[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_satisfied_by_time(&self, t: Time) -> Result<bool, Error> { pub fn is_satisfied_by_time(&self, t: Time) -> Result<bool, Error> {
use LockTime::*; use LockTime::*;
@ -170,7 +179,6 @@ impl From<Time> for LockTime {
} }
impl fmt::Display for LockTime { impl fmt::Display for LockTime {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTime::*; use LockTime::*;
@ -210,7 +218,6 @@ impl From<u16> for Height {
} }
impl fmt::Display for Height { impl fmt::Display for Height {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f) fmt::Display::fmt(&self.0, f)
} }
@ -256,7 +263,6 @@ impl Time {
} }
impl fmt::Display for Time { impl fmt::Display for Time {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f) fmt::Display::fmt(&self.0, f)
} }
@ -295,3 +301,61 @@ impl std::error::Error for Error {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn satisfied_by_height() {
let height = Height::from(10);
let time = Time::from_512_second_intervals(70);
let lock = LockTime::from(height);
assert!(!lock.is_satisfied_by(Height::from(9), time));
assert!(lock.is_satisfied_by(Height::from(10), time));
assert!(lock.is_satisfied_by(Height::from(11), time));
}
#[test]
fn satisfied_by_time() {
let height = Height::from(10);
let time = Time::from_512_second_intervals(70);
let lock = LockTime::from(time);
assert!(!lock.is_satisfied_by(height, Time::from_512_second_intervals(69)));
assert!(lock.is_satisfied_by(height, Time::from_512_second_intervals(70)));
assert!(lock.is_satisfied_by(height, Time::from_512_second_intervals(71)));
}
#[test]
fn height_correctly_implies() {
let height = Height::from(10);
let lock = LockTime::from(height);
assert!(!lock.is_implied_by(LockTime::from(Height::from(9))));
assert!(lock.is_implied_by(LockTime::from(Height::from(10))));
assert!(lock.is_implied_by(LockTime::from(Height::from(11))));
}
#[test]
fn time_correctly_implies() {
let time = Time::from_512_second_intervals(70);
let lock = LockTime::from(time);
assert!(!lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(69))));
assert!(lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(70))));
assert!(lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(71))));
}
#[test]
fn incorrect_units_do_not_imply() {
let time = Time::from_512_second_intervals(70);
let height = Height::from(10);
let lock = LockTime::from(time);
assert!(!lock.is_implied_by(LockTime::from(height)));
}
}