bitflags-lite 0.1.0

Lightweight generics-based bitflags
Documentation
//! Tests for `flag.rs` file.

#![expect(clippy::unwrap_used, reason = "okay in tests")]
#![allow(unused_crate_dependencies, reason = "okay in tests")]

use core::cmp::Ordering;
use core::fmt::{self, LowerHex};
use core::hash::Hash;
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};

use bitflags_lite::{
    BitFlag, BitFlagExt, BitFlagFromFlagsError, BitFlags, BitFlagsSpec, NameMatcher,
};

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
struct MyU8(pub u8);

impl Not for MyU8 {
    type Output = Self;

    #[inline]
    fn not(self) -> Self::Output {
        Self(!self.0)
    }
}

impl BitAnd<Self> for MyU8 {
    type Output = Self;

    #[inline]
    fn bitand(self, rhs: Self) -> Self::Output {
        Self(self.0 & rhs.0)
    }
}

impl BitAndAssign<Self> for MyU8 {
    #[inline]
    fn bitand_assign(&mut self, rhs: Self) {
        self.0 &= rhs.0;
    }
}

impl BitOr<Self> for MyU8 {
    type Output = Self;

    #[inline]
    fn bitor(self, rhs: Self) -> Self::Output {
        Self(self.0 | rhs.0)
    }
}

impl BitOrAssign<Self> for MyU8 {
    #[inline]
    fn bitor_assign(&mut self, rhs: Self) {
        self.0 |= rhs.0;
    }
}

impl BitXor<Self> for MyU8 {
    type Output = Self;

    #[inline]
    fn bitxor(self, rhs: Self) -> Self::Output {
        Self(self.0 ^ rhs.0)
    }
}

impl BitXorAssign<Self> for MyU8 {
    #[inline]
    fn bitxor_assign(&mut self, rhs: Self) {
        self.0 ^= rhs.0;
    }
}

impl LowerHex for MyU8 {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        <u8 as LowerHex>::fmt(&self.0, f)
    }
}

impl bitflags_lite::util::IntOpsExt for MyU8 {
    const ZERO: Self = Self(0);
    const ONE: Self = Self(1);
    const BITS: u8 = 8;

    #[inline]
    fn checked_shl(&self, rhs: u32) -> Option<Self> {
        Some(Self(u8::checked_shl(self.0, rhs)?))
    }

    #[inline]
    fn count_ones(&self) -> u32 {
        u8::count_ones(self.0)
    }

    #[inline]
    fn lowest_one(&self) -> Option<u32> {
        u8::lowest_one(self.0)
    }

    #[inline]
    fn highest_one(&self) -> Option<u32> {
        u8::highest_one(self.0)
    }

    #[inline]
    fn from_str_radix(src: &str, radix: u32) -> Result<Self, core::num::ParseIntError> {
        Ok(Self(u8::from_str_radix(src, radix)?))
    }
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
struct PermsTag;
type Perm = BitFlag<MyU8, PermsTag>;
type Perms = BitFlags<MyU8, PermsTag>;

impl BitFlagsSpec<MyU8> for PermsTag {
    const VALID_BITS_MASK: MyU8 = MyU8((1 << 3) - 1);
    const FLAG_NAMES: &[&[&str]] = &[&["read"], &["write"], &["exec", "execute"]];
    const DEFAULT_NAME_MATCHER: NameMatcher = NameMatcher::Exact;
}

const NONE: Perms = Perms::empty();

#[test]
fn test_flag_from() {
    for bit in 0..=255 {
        assert_eq!(Perm::from_bit_retain(bit).bit(), bit);
    }

    for bit in 0..=2 {
        assert_eq!(Perm::try_from_bit(bit).unwrap().bit(), bit);
    }
    for bit in 3..=255 {
        assert!(Perm::try_from_bit(bit).is_none());
    }
}

#[test]
fn test_flag_try_from() {
    let read: Perm = Perm::try_from_bit(0).unwrap();
    let write: Perm = Perm::try_from_bit(1).unwrap();
    let exec: Perm = Perm::try_from_bit(2).unwrap();

    assert_eq!(Perm::try_from_bits(MyU8(0)), None);
    assert_eq!(Perm::try_from_bits(MyU8(1)), Some(read));
    assert_eq!(Perm::try_from_bits(MyU8(2)), Some(write));
    assert_eq!(Perm::try_from_bits(MyU8(3)), None);
    assert_eq!(Perm::try_from_bits(MyU8(4)), Some(exec));
    assert_eq!(Perm::try_from_bits(MyU8(5)), None);
    assert_eq!(Perm::try_from_bits(MyU8(6)), None);
    assert_eq!(Perm::try_from_bits(MyU8(7)), None);
    assert_eq!(Perm::try_from_bits(MyU8(8)), None);

    assert_eq!(Perm::try_from_bits_retain(MyU8(0)), None);
    assert_eq!(Perm::try_from_bits_retain(MyU8(1)), Some(read));
    assert_eq!(Perm::try_from_bits_retain(MyU8(2)), Some(write));
    assert_eq!(Perm::try_from_bits_retain(MyU8(3)), None);
    assert_eq!(Perm::try_from_bits_retain(MyU8(4)), Some(exec));
    assert_eq!(Perm::try_from_bits_retain(MyU8(5)), None);
    assert_eq!(Perm::try_from_bits_retain(MyU8(6)), None);
    assert_eq!(Perm::try_from_bits_retain(MyU8(7)), None);
    assert_eq!(Perm::try_from_bits_retain(MyU8(8)).unwrap().bit(), 3);
    assert_eq!(Perm::try_from_bits_retain(MyU8(16)).unwrap().bit(), 4);
    assert_eq!(Perm::try_from_bits_retain(MyU8(32)).unwrap().bit(), 5);

    assert_eq!(Perm::try_from_flags((read | write) - (read | write)), None);
    assert_eq!(Perm::try_from_flags(read.into()), Some(read));
    assert_eq!(Perm::try_from_flags(write.into()), Some(write));
    assert_eq!(Perm::try_from_flags(read | write), None);
    assert_eq!(Perm::try_from_flags(exec.into()), Some(exec));
    assert_eq!(Perm::try_from_flags(read | exec), None);
    assert_eq!(Perm::try_from_flags(write | exec), None);
    assert_eq!(Perm::try_from_flags(read | write | exec), None);
    assert_eq!(
        Perm::try_from_flags(Perms::from_bits_retain(MyU8(0x8))),
        None
    );

    assert_eq!(
        Perm::try_from((read | write) - (read | write)),
        Err(BitFlagFromFlagsError)
    );
    assert_eq!(Perm::try_from(Perms::from(read)), Ok(read));
    assert_eq!(Perm::try_from(Perms::from(write)), Ok(write));
    assert_eq!(Perm::try_from(read | write), Err(BitFlagFromFlagsError));
    assert_eq!(Perm::try_from(Perms::from(exec)), Ok(exec));
    assert_eq!(Perm::try_from(read | exec), Err(BitFlagFromFlagsError));
    assert_eq!(Perm::try_from(write | exec), Err(BitFlagFromFlagsError));
    assert_eq!(
        Perm::try_from(read | write | exec),
        Err(BitFlagFromFlagsError)
    );
    assert_eq!(
        Perm::try_from(Perms::from_bits_retain(MyU8(0x8))),
        Err(BitFlagFromFlagsError)
    );

    assert_eq!(
        Perm::try_from((read | write) - (read | write))
            .err()
            .unwrap()
            .to_string(),
        "flags do not contain exactly one valid bit"
    );
}

#[test]
fn test_flag_name() {
    let read: Perm = Perm::try_from_bit(0).unwrap();
    let write: Perm = Perm::try_from_bit(1).unwrap();
    let exec: Perm = Perm::try_from_bit(2).unwrap();

    assert_eq!(read.name(), Some("read"));
    assert_eq!(write.name(), Some("write"));
    assert_eq!(exec.name(), Some("exec"));
    assert_eq!(Perm::from_bit_retain(3).name(), None);

    assert_eq!(read.name_and_aliases(), &["read"]);
    assert_eq!(write.name_and_aliases(), &["write"]);
    assert_eq!(exec.name_and_aliases(), &["exec", "execute"]);
    assert_eq!(Perm::from_bit_retain(3).name_and_aliases(), &[""; 0]);
}

#[test]
fn test_flag_predicates() {
    for bits in 0..=2 {
        assert!(Perm::from_bit_retain(bits).is_valid());
    }
    for bits in 3..=0xFF {
        assert!(!Perm::from_bit_retain(bits).is_valid());
    }
}

#[test]
fn test_flag_mask() {
    assert_eq!(Perm::from_bit_retain(0).mask(), Some(MyU8(0x01)));
    assert_eq!(Perm::from_bit_retain(1).mask(), Some(MyU8(0x02)));
    assert_eq!(Perm::from_bit_retain(2).mask(), Some(MyU8(0x04)));
    assert_eq!(Perm::from_bit_retain(3).mask(), Some(MyU8(0x08)));
    assert_eq!(Perm::from_bit_retain(4).mask(), Some(MyU8(0x10)));
    assert_eq!(Perm::from_bit_retain(5).mask(), Some(MyU8(0x20)));
    assert_eq!(Perm::from_bit_retain(6).mask(), Some(MyU8(0x40)));
    assert_eq!(Perm::from_bit_retain(7).mask(), Some(MyU8(0x80)));
    assert_eq!(Perm::from_bit_retain(8).mask(), None);
    assert_eq!(Perm::from_bit_retain(9).mask(), None);
}

#[test]
fn test_flag_math_ops() {
    let read: Perm = Perm::try_from_bit(0).unwrap();
    let write: Perm = Perm::try_from_bit(1).unwrap();
    let exec: Perm = Perm::try_from_bit(2).unwrap();

    assert_eq!(read.intersection(read), Perms::from(read));
    assert_eq!(read.intersection(write), NONE);

    assert_eq!(read.union(read), Perms::from(read));
    assert_eq!(read.union(write), Perms::from(read | write));

    assert_eq!(read.difference(read), NONE);
    assert_eq!(read.difference(write), Perms::from(read));

    assert_eq!(read.symmetric_difference(read), NONE);
    assert_eq!(read.symmetric_difference(write), Perms::from(read | write));

    assert_eq!(read.complement(), Perms::from(write | exec));
    assert_eq!(write.complement(), Perms::from(read | exec));
}

#[test]
fn test_flag_basic_traits() {
    let read: Perm = Perm::try_from_bit(0).unwrap();
    let write: Perm = Perm::try_from_bit(1).unwrap();
    let exec: Perm = Perm::try_from_bit(2).unwrap();

    assert_eq!(read.clone(), read);
    #[expect(clippy::manual_assert_eq, reason = "explicit equality test")]
    (assert!(read == read));
    assert_eq!(read.cmp(&read), Ordering::Equal);
    assert_eq!(read.partial_cmp(&read), Some(Ordering::Equal));

    assert!(read < write);
    assert_eq!(read.cmp(&write), Ordering::Less);
    assert_eq!(read.partial_cmp(&write), Some(Ordering::Less));

    let mut set = [
        Perm::from_bit_retain(4),
        read,
        write,
        exec,
        read,
        write,
        exec,
        write,
        exec,
        exec,
    ];
    set.sort_unstable();
    assert_eq!(
        set,
        [
            read,
            read,
            write,
            write,
            write,
            exec,
            exec,
            exec,
            exec,
            Perm::from_bit_retain(4),
        ]
    );
}