aboutsummaryrefslogtreecommitdiff
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/all.rs23
-rw-r--r--src/tests/bits.rs36
-rw-r--r--src/tests/complement.rs53
-rw-r--r--src/tests/contains.rs108
-rw-r--r--src/tests/difference.rs92
-rw-r--r--src/tests/empty.rs23
-rw-r--r--src/tests/eq.rs10
-rw-r--r--src/tests/extend.rs42
-rw-r--r--src/tests/flags.rs46
-rw-r--r--src/tests/fmt.rs97
-rw-r--r--src/tests/from_bits.rs45
-rw-r--r--src/tests/from_bits_retain.rs38
-rw-r--r--src/tests/from_bits_truncate.rs42
-rw-r--r--src/tests/from_name.rs42
-rw-r--r--src/tests/insert.rs91
-rw-r--r--src/tests/intersection.rs79
-rw-r--r--src/tests/intersects.rs91
-rw-r--r--src/tests/is_all.rs32
-rw-r--r--src/tests/is_empty.rs31
-rw-r--r--src/tests/iter.rs209
-rw-r--r--src/tests/parser.rs116
-rw-r--r--src/tests/remove.rs100
-rw-r--r--src/tests/symmetric_difference.rs110
-rw-r--r--src/tests/union.rs71
24 files changed, 1627 insertions, 0 deletions
diff --git a/src/tests/all.rs b/src/tests/all.rs
new file mode 100644
index 0000000..cceb93a
--- /dev/null
+++ b/src/tests/all.rs
@@ -0,0 +1,23 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(1 | 1 << 1 | 1 << 2, TestFlags::all);
+
+ case(0, TestZero::all);
+
+ case(0, TestEmpty::all);
+
+ case(!0, TestExternal::all);
+}
+
+#[track_caller]
+fn case<T: Flags>(expected: T::Bits, inherent: impl FnOnce() -> T)
+where
+ <T as Flags>::Bits: std::fmt::Debug + PartialEq,
+{
+ assert_eq!(expected, inherent().bits(), "T::all()");
+ assert_eq!(expected, T::all().bits(), "Flags::all()");
+}
diff --git a/src/tests/bits.rs b/src/tests/bits.rs
new file mode 100644
index 0000000..678f153
--- /dev/null
+++ b/src/tests/bits.rs
@@ -0,0 +1,36 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(0, TestFlags::empty(), TestFlags::bits);
+
+ case(1, TestFlags::A, TestFlags::bits);
+ case(1 | 1 << 1 | 1 << 2, TestFlags::ABC, TestFlags::bits);
+
+ case(!0, TestFlags::from_bits_retain(u8::MAX), TestFlags::bits);
+ case(1 << 3, TestFlags::from_bits_retain(1 << 3), TestFlags::bits);
+
+ case(1 << 3, TestZero::from_bits_retain(1 << 3), TestZero::bits);
+
+ case(1 << 3, TestEmpty::from_bits_retain(1 << 3), TestEmpty::bits);
+
+ case(
+ 1 << 4 | 1 << 6,
+ TestExternal::from_bits_retain(1 << 4 | 1 << 6),
+ TestExternal::bits,
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug>(
+ expected: T::Bits,
+ value: T,
+ inherent: impl FnOnce(&T) -> T::Bits,
+) where
+ T::Bits: std::fmt::Debug + PartialEq,
+{
+ assert_eq!(expected, inherent(&value), "{:?}.bits()", value);
+ assert_eq!(expected, Flags::bits(&value), "Flags::bits({:?})", value);
+}
diff --git a/src/tests/complement.rs b/src/tests/complement.rs
new file mode 100644
index 0000000..ac7a421
--- /dev/null
+++ b/src/tests/complement.rs
@@ -0,0 +1,53 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(0, TestFlags::all(), TestFlags::complement);
+ case(0, TestFlags::from_bits_retain(!0), TestFlags::complement);
+
+ case(1 | 1 << 1, TestFlags::C, TestFlags::complement);
+ case(
+ 1 | 1 << 1,
+ TestFlags::C | TestFlags::from_bits_retain(1 << 3),
+ TestFlags::complement,
+ );
+
+ case(
+ 1 | 1 << 1 | 1 << 2,
+ TestFlags::empty(),
+ TestFlags::complement,
+ );
+ case(
+ 1 | 1 << 1 | 1 << 2,
+ TestFlags::from_bits_retain(1 << 3),
+ TestFlags::complement,
+ );
+
+ case(0, TestZero::empty(), TestZero::complement);
+
+ case(0, TestEmpty::empty(), TestEmpty::complement);
+
+ case(1 << 2, TestOverlapping::AB, TestOverlapping::complement);
+
+ case(!0, TestExternal::empty(), TestExternal::complement);
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + std::ops::Not<Output = T> + Copy>(
+ expected: T::Bits,
+ value: T,
+ inherent: impl FnOnce(T) -> T,
+) where
+ T::Bits: std::fmt::Debug + PartialEq,
+{
+ assert_eq!(expected, inherent(value).bits(), "{:?}.complement()", value);
+ assert_eq!(
+ expected,
+ Flags::complement(value).bits(),
+ "Flags::complement({:?})",
+ value
+ );
+ assert_eq!(expected, (!value).bits(), "!{:?}", value);
+}
diff --git a/src/tests/contains.rs b/src/tests/contains.rs
new file mode 100644
index 0000000..12428dd
--- /dev/null
+++ b/src/tests/contains.rs
@@ -0,0 +1,108 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(
+ TestFlags::empty(),
+ &[
+ (TestFlags::empty(), true),
+ (TestFlags::A, false),
+ (TestFlags::B, false),
+ (TestFlags::C, false),
+ (TestFlags::from_bits_retain(1 << 3), false),
+ ],
+ TestFlags::contains,
+ );
+
+ case(
+ TestFlags::A,
+ &[
+ (TestFlags::empty(), true),
+ (TestFlags::A, true),
+ (TestFlags::B, false),
+ (TestFlags::C, false),
+ (TestFlags::ABC, false),
+ (TestFlags::from_bits_retain(1 << 3), false),
+ (TestFlags::from_bits_retain(1 | (1 << 3)), false),
+ ],
+ TestFlags::contains,
+ );
+
+ case(
+ TestFlags::ABC,
+ &[
+ (TestFlags::empty(), true),
+ (TestFlags::A, true),
+ (TestFlags::B, true),
+ (TestFlags::C, true),
+ (TestFlags::ABC, true),
+ (TestFlags::from_bits_retain(1 << 3), false),
+ ],
+ TestFlags::contains,
+ );
+
+ case(
+ TestFlags::from_bits_retain(1 << 3),
+ &[
+ (TestFlags::empty(), true),
+ (TestFlags::A, false),
+ (TestFlags::B, false),
+ (TestFlags::C, false),
+ (TestFlags::from_bits_retain(1 << 3), true),
+ ],
+ TestFlags::contains,
+ );
+
+ case(
+ TestZero::ZERO,
+ &[(TestZero::ZERO, true)],
+ TestZero::contains,
+ );
+
+ case(
+ TestOverlapping::AB,
+ &[
+ (TestOverlapping::AB, true),
+ (TestOverlapping::BC, false),
+ (TestOverlapping::from_bits_retain(1 << 1), true),
+ ],
+ TestOverlapping::contains,
+ );
+
+ case(
+ TestExternal::all(),
+ &[
+ (TestExternal::A, true),
+ (TestExternal::B, true),
+ (TestExternal::C, true),
+ (TestExternal::from_bits_retain(1 << 5 | 1 << 7), true),
+ ],
+ TestExternal::contains,
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + Copy>(
+ value: T,
+ inputs: &[(T, bool)],
+ mut inherent: impl FnMut(&T, T) -> bool,
+) {
+ for (input, expected) in inputs {
+ assert_eq!(
+ *expected,
+ inherent(&value, *input),
+ "{:?}.contains({:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ Flags::contains(&value, *input),
+ "Flags::contains({:?}, {:?})",
+ value,
+ input
+ );
+ }
+}
diff --git a/src/tests/difference.rs b/src/tests/difference.rs
new file mode 100644
index 0000000..6ce9c0b
--- /dev/null
+++ b/src/tests/difference.rs
@@ -0,0 +1,92 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(
+ TestFlags::A | TestFlags::B,
+ &[
+ (TestFlags::A, 1 << 1),
+ (TestFlags::B, 1),
+ (TestFlags::from_bits_retain(1 << 3), 1 | 1 << 1),
+ ],
+ TestFlags::difference,
+ );
+
+ case(
+ TestFlags::from_bits_retain(1 | 1 << 3),
+ &[
+ (TestFlags::A, 1 << 3),
+ (TestFlags::from_bits_retain(1 << 3), 1),
+ ],
+ TestFlags::difference,
+ );
+
+ case(
+ TestExternal::from_bits_retain(!0),
+ &[(TestExternal::A, 0b1111_1110)],
+ TestExternal::difference,
+ );
+
+ assert_eq!(
+ 0b1111_1110,
+ (TestExternal::from_bits_retain(!0) & !TestExternal::A).bits()
+ );
+
+ assert_eq!(
+ 0b1111_1110,
+ (TestFlags::from_bits_retain(!0).difference(TestFlags::A)).bits()
+ );
+
+ // The `!` operator unsets bits that don't correspond to known flags
+ assert_eq!(
+ 1 << 1 | 1 << 2,
+ (TestFlags::from_bits_retain(!0) & !TestFlags::A).bits()
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + std::ops::Sub<Output = T> + std::ops::SubAssign + Copy>(
+ value: T,
+ inputs: &[(T, T::Bits)],
+ mut inherent: impl FnMut(T, T) -> T,
+) where
+ T::Bits: std::fmt::Debug + PartialEq + Copy,
+{
+ for (input, expected) in inputs {
+ assert_eq!(
+ *expected,
+ inherent(value, *input).bits(),
+ "{:?}.difference({:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ Flags::difference(value, *input).bits(),
+ "Flags::difference({:?}, {:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ (value - *input).bits(),
+ "{:?} - {:?}",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ value -= *input;
+ value
+ }
+ .bits(),
+ "{:?} -= {:?}",
+ value,
+ input,
+ );
+ }
+}
diff --git a/src/tests/empty.rs b/src/tests/empty.rs
new file mode 100644
index 0000000..57fb1c7
--- /dev/null
+++ b/src/tests/empty.rs
@@ -0,0 +1,23 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(0, TestFlags::empty);
+
+ case(0, TestZero::empty);
+
+ case(0, TestEmpty::empty);
+
+ case(0, TestExternal::empty);
+}
+
+#[track_caller]
+fn case<T: Flags>(expected: T::Bits, inherent: impl FnOnce() -> T)
+where
+ <T as Flags>::Bits: std::fmt::Debug + PartialEq,
+{
+ assert_eq!(expected, inherent().bits(), "T::empty()");
+ assert_eq!(expected, T::empty().bits(), "Flags::empty()");
+}
diff --git a/src/tests/eq.rs b/src/tests/eq.rs
new file mode 100644
index 0000000..9779af7
--- /dev/null
+++ b/src/tests/eq.rs
@@ -0,0 +1,10 @@
+use super::*;
+
+#[test]
+fn cases() {
+ assert_eq!(TestFlags::empty(), TestFlags::empty());
+ assert_eq!(TestFlags::all(), TestFlags::all());
+
+ assert!(TestFlags::from_bits_retain(1) < TestFlags::from_bits_retain(2));
+ assert!(TestFlags::from_bits_retain(2) > TestFlags::from_bits_retain(1));
+}
diff --git a/src/tests/extend.rs b/src/tests/extend.rs
new file mode 100644
index 0000000..869dc17
--- /dev/null
+++ b/src/tests/extend.rs
@@ -0,0 +1,42 @@
+use super::*;
+
+#[test]
+fn cases() {
+ let mut flags = TestFlags::empty();
+
+ flags.extend(TestFlags::A);
+
+ assert_eq!(TestFlags::A, flags);
+
+ flags.extend(TestFlags::A | TestFlags::B | TestFlags::C);
+
+ assert_eq!(TestFlags::ABC, flags);
+
+ flags.extend(TestFlags::from_bits_retain(1 << 5));
+
+ assert_eq!(TestFlags::ABC | TestFlags::from_bits_retain(1 << 5), flags);
+}
+
+mod external {
+ use super::*;
+
+ #[test]
+ fn cases() {
+ let mut flags = TestExternal::empty();
+
+ flags.extend(TestExternal::A);
+
+ assert_eq!(TestExternal::A, flags);
+
+ flags.extend(TestExternal::A | TestExternal::B | TestExternal::C);
+
+ assert_eq!(TestExternal::ABC, flags);
+
+ flags.extend(TestExternal::from_bits_retain(1 << 5));
+
+ assert_eq!(
+ TestExternal::ABC | TestExternal::from_bits_retain(1 << 5),
+ flags
+ );
+ }
+}
diff --git a/src/tests/flags.rs b/src/tests/flags.rs
new file mode 100644
index 0000000..7a625b3
--- /dev/null
+++ b/src/tests/flags.rs
@@ -0,0 +1,46 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ let flags = TestFlags::FLAGS
+ .iter()
+ .map(|flag| (flag.name(), flag.value().bits()))
+ .collect::<Vec<_>>();
+
+ assert_eq!(
+ vec![
+ ("A", 1u8),
+ ("B", 1 << 1),
+ ("C", 1 << 2),
+ ("ABC", 1 | 1 << 1 | 1 << 2),
+ ],
+ flags,
+ );
+
+ assert_eq!(0, TestEmpty::FLAGS.iter().count());
+}
+
+mod external {
+ use super::*;
+
+ #[test]
+ fn cases() {
+ let flags = TestExternal::FLAGS
+ .iter()
+ .map(|flag| (flag.name(), flag.value().bits()))
+ .collect::<Vec<_>>();
+
+ assert_eq!(
+ vec![
+ ("A", 1u8),
+ ("B", 1 << 1),
+ ("C", 1 << 2),
+ ("ABC", 1 | 1 << 1 | 1 << 2),
+ ("", !0),
+ ],
+ flags,
+ );
+ }
+}
diff --git a/src/tests/fmt.rs b/src/tests/fmt.rs
new file mode 100644
index 0000000..ed45718
--- /dev/null
+++ b/src/tests/fmt.rs
@@ -0,0 +1,97 @@
+use super::*;
+
+#[test]
+fn cases() {
+ case(TestFlags::empty(), "TestFlags(0x0)", "0", "0", "0", "0");
+ case(TestFlags::A, "TestFlags(A)", "1", "1", "1", "1");
+ case(
+ TestFlags::all(),
+ "TestFlags(A | B | C)",
+ "7",
+ "7",
+ "7",
+ "111",
+ );
+ case(
+ TestFlags::from_bits_retain(1 << 3),
+ "TestFlags(0x8)",
+ "8",
+ "8",
+ "10",
+ "1000",
+ );
+ case(
+ TestFlags::A | TestFlags::from_bits_retain(1 << 3),
+ "TestFlags(A | 0x8)",
+ "9",
+ "9",
+ "11",
+ "1001",
+ );
+
+ case(TestZero::ZERO, "TestZero(0x0)", "0", "0", "0", "0");
+ case(
+ TestZero::ZERO | TestZero::from_bits_retain(1),
+ "TestZero(0x1)",
+ "1",
+ "1",
+ "1",
+ "1",
+ );
+
+ case(TestZeroOne::ONE, "TestZeroOne(ONE)", "1", "1", "1", "1");
+
+ case(
+ TestOverlapping::from_bits_retain(1 << 1),
+ "TestOverlapping(0x2)",
+ "2",
+ "2",
+ "2",
+ "10",
+ );
+
+ case(
+ TestExternal::from_bits_retain(1 | 1 << 1 | 1 << 3),
+ "TestExternal(A | B | 0x8)",
+ "B",
+ "b",
+ "13",
+ "1011",
+ );
+
+ case(
+ TestExternal::all(),
+ "TestExternal(A | B | C | 0xf8)",
+ "FF",
+ "ff",
+ "377",
+ "11111111",
+ );
+
+ case(
+ TestExternalFull::all(),
+ "TestExternalFull(0xff)",
+ "FF",
+ "ff",
+ "377",
+ "11111111",
+ );
+}
+
+#[track_caller]
+fn case<
+ T: std::fmt::Debug + std::fmt::UpperHex + std::fmt::LowerHex + std::fmt::Octal + std::fmt::Binary,
+>(
+ value: T,
+ debug: &str,
+ uhex: &str,
+ lhex: &str,
+ oct: &str,
+ bin: &str,
+) {
+ assert_eq!(debug, format!("{:?}", value));
+ assert_eq!(uhex, format!("{:X}", value));
+ assert_eq!(lhex, format!("{:x}", value));
+ assert_eq!(oct, format!("{:o}", value));
+ assert_eq!(bin, format!("{:b}", value));
+}
diff --git a/src/tests/from_bits.rs b/src/tests/from_bits.rs
new file mode 100644
index 0000000..dada9af
--- /dev/null
+++ b/src/tests/from_bits.rs
@@ -0,0 +1,45 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(Some(0), 0, TestFlags::from_bits);
+ case(Some(1), 1, TestFlags::from_bits);
+ case(
+ Some(1 | 1 << 1 | 1 << 2),
+ 1 | 1 << 1 | 1 << 2,
+ TestFlags::from_bits,
+ );
+
+ case(None, 1 << 3, TestFlags::from_bits);
+ case(None, 1 | 1 << 3, TestFlags::from_bits);
+
+ case(Some(1 | 1 << 1), 1 | 1 << 1, TestOverlapping::from_bits);
+
+ case(Some(1 << 1), 1 << 1, TestOverlapping::from_bits);
+
+ case(Some(1 << 5), 1 << 5, TestExternal::from_bits);
+}
+
+#[track_caller]
+fn case<T: Flags>(
+ expected: Option<T::Bits>,
+ input: T::Bits,
+ inherent: impl FnOnce(T::Bits) -> Option<T>,
+) where
+ <T as Flags>::Bits: std::fmt::Debug + PartialEq,
+{
+ assert_eq!(
+ expected,
+ inherent(input).map(|f| f.bits()),
+ "T::from_bits({:?})",
+ input
+ );
+ assert_eq!(
+ expected,
+ T::from_bits(input).map(|f| f.bits()),
+ "Flags::from_bits({:?})",
+ input
+ );
+}
diff --git a/src/tests/from_bits_retain.rs b/src/tests/from_bits_retain.rs
new file mode 100644
index 0000000..1ae28a6
--- /dev/null
+++ b/src/tests/from_bits_retain.rs
@@ -0,0 +1,38 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(0, TestFlags::from_bits_retain);
+ case(1, TestFlags::from_bits_retain);
+ case(1 | 1 << 1 | 1 << 2, TestFlags::from_bits_retain);
+
+ case(1 << 3, TestFlags::from_bits_retain);
+ case(1 | 1 << 3, TestFlags::from_bits_retain);
+
+ case(1 | 1 << 1, TestOverlapping::from_bits_retain);
+
+ case(1 << 1, TestOverlapping::from_bits_retain);
+
+ case(1 << 5, TestExternal::from_bits_retain);
+}
+
+#[track_caller]
+fn case<T: Flags>(input: T::Bits, inherent: impl FnOnce(T::Bits) -> T)
+where
+ <T as Flags>::Bits: std::fmt::Debug + PartialEq,
+{
+ assert_eq!(
+ input,
+ inherent(input).bits(),
+ "T::from_bits_retain({:?})",
+ input
+ );
+ assert_eq!(
+ input,
+ T::from_bits_retain(input).bits(),
+ "Flags::from_bits_retain({:?})",
+ input
+ );
+}
diff --git a/src/tests/from_bits_truncate.rs b/src/tests/from_bits_truncate.rs
new file mode 100644
index 0000000..e4f3e53
--- /dev/null
+++ b/src/tests/from_bits_truncate.rs
@@ -0,0 +1,42 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(0, 0, TestFlags::from_bits_truncate);
+ case(1, 1, TestFlags::from_bits_truncate);
+ case(
+ 1 | 1 << 1 | 1 << 2,
+ 1 | 1 << 1 | 1 << 2,
+ TestFlags::from_bits_truncate,
+ );
+
+ case(0, 1 << 3, TestFlags::from_bits_truncate);
+ case(1, 1 | 1 << 3, TestFlags::from_bits_truncate);
+
+ case(1 | 1 << 1, 1 | 1 << 1, TestOverlapping::from_bits_truncate);
+
+ case(1 << 1, 1 << 1, TestOverlapping::from_bits_truncate);
+
+ case(1 << 5, 1 << 5, TestExternal::from_bits_truncate);
+}
+
+#[track_caller]
+fn case<T: Flags>(expected: T::Bits, input: T::Bits, inherent: impl FnOnce(T::Bits) -> T)
+where
+ <T as Flags>::Bits: std::fmt::Debug + PartialEq,
+{
+ assert_eq!(
+ expected,
+ inherent(input).bits(),
+ "T::from_bits_truncate({:?})",
+ input
+ );
+ assert_eq!(
+ expected,
+ T::from_bits_truncate(input).bits(),
+ "Flags::from_bits_truncate({:?})",
+ input
+ );
+}
diff --git a/src/tests/from_name.rs b/src/tests/from_name.rs
new file mode 100644
index 0000000..1d9a4e4
--- /dev/null
+++ b/src/tests/from_name.rs
@@ -0,0 +1,42 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(Some(1), "A", TestFlags::from_name);
+ case(Some(1 << 1), "B", TestFlags::from_name);
+ case(Some(1 | 1 << 1 | 1 << 2), "ABC", TestFlags::from_name);
+
+ case(None, "", TestFlags::from_name);
+ case(None, "a", TestFlags::from_name);
+ case(None, "0x1", TestFlags::from_name);
+ case(None, "A | B", TestFlags::from_name);
+
+ case(Some(0), "ZERO", TestZero::from_name);
+
+ case(Some(2), "二", TestUnicode::from_name);
+
+ case(None, "_", TestExternal::from_name);
+
+ case(None, "", TestExternal::from_name);
+}
+
+#[track_caller]
+fn case<T: Flags>(expected: Option<T::Bits>, input: &str, inherent: impl FnOnce(&str) -> Option<T>)
+where
+ <T as Flags>::Bits: std::fmt::Debug + PartialEq,
+{
+ assert_eq!(
+ expected,
+ inherent(input).map(|f| f.bits()),
+ "T::from_name({:?})",
+ input
+ );
+ assert_eq!(
+ expected,
+ T::from_name(input).map(|f| f.bits()),
+ "Flags::from_name({:?})",
+ input
+ );
+}
diff --git a/src/tests/insert.rs b/src/tests/insert.rs
new file mode 100644
index 0000000..b18cd17
--- /dev/null
+++ b/src/tests/insert.rs
@@ -0,0 +1,91 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(
+ TestFlags::empty(),
+ &[
+ (TestFlags::A, 1),
+ (TestFlags::A | TestFlags::B, 1 | 1 << 1),
+ (TestFlags::empty(), 0),
+ (TestFlags::from_bits_retain(1 << 3), 1 << 3),
+ ],
+ TestFlags::insert,
+ TestFlags::set,
+ );
+
+ case(
+ TestFlags::A,
+ &[
+ (TestFlags::A, 1),
+ (TestFlags::empty(), 1),
+ (TestFlags::B, 1 | 1 << 1),
+ ],
+ TestFlags::insert,
+ TestFlags::set,
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + Copy>(
+ value: T,
+ inputs: &[(T, T::Bits)],
+ mut inherent_insert: impl FnMut(&mut T, T),
+ mut inherent_set: impl FnMut(&mut T, T, bool),
+) where
+ T::Bits: std::fmt::Debug + PartialEq + Copy,
+{
+ for (input, expected) in inputs {
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ inherent_insert(&mut value, *input);
+ value
+ }
+ .bits(),
+ "{:?}.insert({:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ Flags::insert(&mut value, *input);
+ value
+ }
+ .bits(),
+ "Flags::insert({:?}, {:?})",
+ value,
+ input
+ );
+
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ inherent_set(&mut value, *input, true);
+ value
+ }
+ .bits(),
+ "{:?}.set({:?}, true)",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ Flags::set(&mut value, *input, true);
+ value
+ }
+ .bits(),
+ "Flags::set({:?}, {:?}, true)",
+ value,
+ input
+ );
+ }
+}
diff --git a/src/tests/intersection.rs b/src/tests/intersection.rs
new file mode 100644
index 0000000..10a8ae9
--- /dev/null
+++ b/src/tests/intersection.rs
@@ -0,0 +1,79 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(
+ TestFlags::empty(),
+ &[(TestFlags::empty(), 0), (TestFlags::all(), 0)],
+ TestFlags::intersection,
+ );
+
+ case(
+ TestFlags::all(),
+ &[
+ (TestFlags::all(), 1 | 1 << 1 | 1 << 2),
+ (TestFlags::A, 1),
+ (TestFlags::from_bits_retain(1 << 3), 0),
+ ],
+ TestFlags::intersection,
+ );
+
+ case(
+ TestFlags::from_bits_retain(1 << 3),
+ &[(TestFlags::from_bits_retain(1 << 3), 1 << 3)],
+ TestFlags::intersection,
+ );
+
+ case(
+ TestOverlapping::AB,
+ &[(TestOverlapping::BC, 1 << 1)],
+ TestOverlapping::intersection,
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + std::ops::BitAnd<Output = T> + std::ops::BitAndAssign + Copy>(
+ value: T,
+ inputs: &[(T, T::Bits)],
+ mut inherent: impl FnMut(T, T) -> T,
+) where
+ T::Bits: std::fmt::Debug + PartialEq + Copy,
+{
+ for (input, expected) in inputs {
+ assert_eq!(
+ *expected,
+ inherent(value, *input).bits(),
+ "{:?}.intersection({:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ Flags::intersection(value, *input).bits(),
+ "Flags::intersection({:?}, {:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ (value & *input).bits(),
+ "{:?} & {:?}",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ value &= *input;
+ value
+ }
+ .bits(),
+ "{:?} &= {:?}",
+ value,
+ input,
+ );
+ }
+}
diff --git a/src/tests/intersects.rs b/src/tests/intersects.rs
new file mode 100644
index 0000000..fe90798
--- /dev/null
+++ b/src/tests/intersects.rs
@@ -0,0 +1,91 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(
+ TestFlags::empty(),
+ &[
+ (TestFlags::empty(), false),
+ (TestFlags::A, false),
+ (TestFlags::B, false),
+ (TestFlags::C, false),
+ (TestFlags::from_bits_retain(1 << 3), false),
+ ],
+ TestFlags::intersects,
+ );
+
+ case(
+ TestFlags::A,
+ &[
+ (TestFlags::empty(), false),
+ (TestFlags::A, true),
+ (TestFlags::B, false),
+ (TestFlags::C, false),
+ (TestFlags::ABC, true),
+ (TestFlags::from_bits_retain(1 << 3), false),
+ (TestFlags::from_bits_retain(1 | (1 << 3)), true),
+ ],
+ TestFlags::intersects,
+ );
+
+ case(
+ TestFlags::ABC,
+ &[
+ (TestFlags::empty(), false),
+ (TestFlags::A, true),
+ (TestFlags::B, true),
+ (TestFlags::C, true),
+ (TestFlags::ABC, true),
+ (TestFlags::from_bits_retain(1 << 3), false),
+ ],
+ TestFlags::intersects,
+ );
+
+ case(
+ TestFlags::from_bits_retain(1 << 3),
+ &[
+ (TestFlags::empty(), false),
+ (TestFlags::A, false),
+ (TestFlags::B, false),
+ (TestFlags::C, false),
+ (TestFlags::from_bits_retain(1 << 3), true),
+ ],
+ TestFlags::intersects,
+ );
+
+ case(
+ TestOverlapping::AB,
+ &[
+ (TestOverlapping::AB, true),
+ (TestOverlapping::BC, true),
+ (TestOverlapping::from_bits_retain(1 << 1), true),
+ ],
+ TestOverlapping::intersects,
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + Copy>(
+ value: T,
+ inputs: &[(T, bool)],
+ mut inherent: impl FnMut(&T, T) -> bool,
+) {
+ for (input, expected) in inputs {
+ assert_eq!(
+ *expected,
+ inherent(&value, *input),
+ "{:?}.intersects({:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ Flags::intersects(&value, *input),
+ "Flags::intersects({:?}, {:?})",
+ value,
+ input
+ );
+ }
+}
diff --git a/src/tests/is_all.rs b/src/tests/is_all.rs
new file mode 100644
index 0000000..382a458
--- /dev/null
+++ b/src/tests/is_all.rs
@@ -0,0 +1,32 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(false, TestFlags::empty(), TestFlags::is_all);
+ case(false, TestFlags::A, TestFlags::is_all);
+
+ case(true, TestFlags::ABC, TestFlags::is_all);
+
+ case(
+ true,
+ TestFlags::ABC | TestFlags::from_bits_retain(1 << 3),
+ TestFlags::is_all,
+ );
+
+ case(true, TestZero::empty(), TestZero::is_all);
+
+ case(true, TestEmpty::empty(), TestEmpty::is_all);
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug>(expected: bool, value: T, inherent: impl FnOnce(&T) -> bool) {
+ assert_eq!(expected, inherent(&value), "{:?}.is_all()", value);
+ assert_eq!(
+ expected,
+ Flags::is_all(&value),
+ "Flags::is_all({:?})",
+ value
+ );
+}
diff --git a/src/tests/is_empty.rs b/src/tests/is_empty.rs
new file mode 100644
index 0000000..92165f1
--- /dev/null
+++ b/src/tests/is_empty.rs
@@ -0,0 +1,31 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(true, TestFlags::empty(), TestFlags::is_empty);
+
+ case(false, TestFlags::A, TestFlags::is_empty);
+ case(false, TestFlags::ABC, TestFlags::is_empty);
+ case(
+ false,
+ TestFlags::from_bits_retain(1 << 3),
+ TestFlags::is_empty,
+ );
+
+ case(true, TestZero::empty(), TestZero::is_empty);
+
+ case(true, TestEmpty::empty(), TestEmpty::is_empty);
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug>(expected: bool, value: T, inherent: impl FnOnce(&T) -> bool) {
+ assert_eq!(expected, inherent(&value), "{:?}.is_empty()", value);
+ assert_eq!(
+ expected,
+ Flags::is_empty(&value),
+ "Flags::is_empty({:?})",
+ value
+ );
+}
diff --git a/src/tests/iter.rs b/src/tests/iter.rs
new file mode 100644
index 0000000..54b1d27
--- /dev/null
+++ b/src/tests/iter.rs
@@ -0,0 +1,209 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+#[cfg(not(miri))] // Very slow in miri
+fn roundtrip() {
+ for a in 0u8..=255 {
+ for b in 0u8..=255 {
+ let f = TestFlags::from_bits_retain(a | b);
+
+ assert_eq!(f, f.iter().collect::<TestFlags>());
+ assert_eq!(
+ TestFlags::from_bits_truncate(f.bits()),
+ f.iter_names().map(|(_, f)| f).collect::<TestFlags>()
+ );
+
+ let f = TestExternal::from_bits_retain(a | b);
+
+ assert_eq!(f, f.iter().collect::<TestExternal>());
+ }
+ }
+}
+
+mod collect {
+ use super::*;
+
+ #[test]
+ fn cases() {
+ assert_eq!(0, [].into_iter().collect::<TestFlags>().bits());
+
+ assert_eq!(1, [TestFlags::A,].into_iter().collect::<TestFlags>().bits());
+
+ assert_eq!(
+ 1 | 1 << 1 | 1 << 2,
+ [TestFlags::A, TestFlags::B | TestFlags::C,]
+ .into_iter()
+ .collect::<TestFlags>()
+ .bits()
+ );
+
+ assert_eq!(
+ 1 | 1 << 3,
+ [
+ TestFlags::from_bits_retain(1 << 3),
+ TestFlags::empty(),
+ TestFlags::A,
+ ]
+ .into_iter()
+ .collect::<TestFlags>()
+ .bits()
+ );
+
+ assert_eq!(
+ 1 << 5 | 1 << 7,
+ [
+ TestExternal::empty(),
+ TestExternal::from_bits_retain(1 << 5),
+ TestExternal::from_bits_retain(1 << 7),
+ ]
+ .into_iter()
+ .collect::<TestExternal>()
+ .bits()
+ );
+ }
+}
+
+mod iter {
+ use super::*;
+
+ #[test]
+ fn cases() {
+ case(&[], TestFlags::empty(), TestFlags::iter);
+
+ case(&[1], TestFlags::A, TestFlags::iter);
+ case(&[1, 1 << 1], TestFlags::A | TestFlags::B, TestFlags::iter);
+ case(
+ &[1, 1 << 1, 1 << 3],
+ TestFlags::A | TestFlags::B | TestFlags::from_bits_retain(1 << 3),
+ TestFlags::iter,
+ );
+
+ case(&[1, 1 << 1, 1 << 2], TestFlags::ABC, TestFlags::iter);
+ case(
+ &[1, 1 << 1, 1 << 2, 1 << 3],
+ TestFlags::ABC | TestFlags::from_bits_retain(1 << 3),
+ TestFlags::iter,
+ );
+
+ case(
+ &[1 | 1 << 1 | 1 << 2],
+ TestFlagsInvert::ABC,
+ TestFlagsInvert::iter,
+ );
+
+ case(&[], TestZero::ZERO, TestZero::iter);
+
+ case(
+ &[1, 1 << 1, 1 << 2, 0b1111_1000],
+ TestExternal::all(),
+ TestExternal::iter,
+ );
+ }
+
+ #[track_caller]
+ fn case<T: Flags + std::fmt::Debug + IntoIterator<Item = T> + Copy>(
+ expected: &[T::Bits],
+ value: T,
+ inherent: impl FnOnce(&T) -> crate::iter::Iter<T>,
+ ) where
+ T::Bits: std::fmt::Debug + PartialEq,
+ {
+ assert_eq!(
+ expected,
+ inherent(&value).map(|f| f.bits()).collect::<Vec<_>>(),
+ "{:?}.iter()",
+ value
+ );
+ assert_eq!(
+ expected,
+ Flags::iter(&value).map(|f| f.bits()).collect::<Vec<_>>(),
+ "Flags::iter({:?})",
+ value
+ );
+ assert_eq!(
+ expected,
+ value.into_iter().map(|f| f.bits()).collect::<Vec<_>>(),
+ "{:?}.into_iter()",
+ value
+ );
+ }
+}
+
+mod iter_names {
+ use super::*;
+
+ #[test]
+ fn cases() {
+ case(&[], TestFlags::empty(), TestFlags::iter_names);
+
+ case(&[("A", 1)], TestFlags::A, TestFlags::iter_names);
+ case(
+ &[("A", 1), ("B", 1 << 1)],
+ TestFlags::A | TestFlags::B,
+ TestFlags::iter_names,
+ );
+ case(
+ &[("A", 1), ("B", 1 << 1)],
+ TestFlags::A | TestFlags::B | TestFlags::from_bits_retain(1 << 3),
+ TestFlags::iter_names,
+ );
+
+ case(
+ &[("A", 1), ("B", 1 << 1), ("C", 1 << 2)],
+ TestFlags::ABC,
+ TestFlags::iter_names,
+ );
+ case(
+ &[("A", 1), ("B", 1 << 1), ("C", 1 << 2)],
+ TestFlags::ABC | TestFlags::from_bits_retain(1 << 3),
+ TestFlags::iter_names,
+ );
+
+ case(
+ &[("ABC", 1 | 1 << 1 | 1 << 2)],
+ TestFlagsInvert::ABC,
+ TestFlagsInvert::iter_names,
+ );
+
+ case(&[], TestZero::ZERO, TestZero::iter_names);
+
+ case(
+ &[("A", 1)],
+ TestOverlappingFull::A,
+ TestOverlappingFull::iter_names,
+ );
+ case(
+ &[("A", 1), ("D", 1 << 1)],
+ TestOverlappingFull::A | TestOverlappingFull::D,
+ TestOverlappingFull::iter_names,
+ );
+ }
+
+ #[track_caller]
+ fn case<T: Flags + std::fmt::Debug>(
+ expected: &[(&'static str, T::Bits)],
+ value: T,
+ inherent: impl FnOnce(&T) -> crate::iter::IterNames<T>,
+ ) where
+ T::Bits: std::fmt::Debug + PartialEq,
+ {
+ assert_eq!(
+ expected,
+ inherent(&value)
+ .map(|(n, f)| (n, f.bits()))
+ .collect::<Vec<_>>(),
+ "{:?}.iter_names()",
+ value
+ );
+ assert_eq!(
+ expected,
+ Flags::iter_names(&value)
+ .map(|(n, f)| (n, f.bits()))
+ .collect::<Vec<_>>(),
+ "Flags::iter_names({:?})",
+ value
+ );
+ }
+}
diff --git a/src/tests/parser.rs b/src/tests/parser.rs
new file mode 100644
index 0000000..b370785
--- /dev/null
+++ b/src/tests/parser.rs
@@ -0,0 +1,116 @@
+use super::*;
+
+use crate::{
+ parser::{from_str, to_writer},
+ Flags,
+};
+
+#[test]
+#[cfg(not(miri))] // Very slow in miri
+fn roundtrip() {
+ let mut s = String::new();
+
+ for a in 0u8..=255 {
+ for b in 0u8..=255 {
+ let f = TestFlags::from_bits_retain(a | b);
+
+ s.clear();
+ to_writer(&f, &mut s).unwrap();
+
+ assert_eq!(f, from_str::<TestFlags>(&s).unwrap());
+ }
+ }
+}
+
+mod from_str {
+ use super::*;
+
+ #[test]
+ fn valid() {
+ assert_eq!(0, from_str::<TestFlags>("").unwrap().bits());
+
+ assert_eq!(1, from_str::<TestFlags>("A").unwrap().bits());
+ assert_eq!(1, from_str::<TestFlags>(" A ").unwrap().bits());
+ assert_eq!(
+ 1 | 1 << 1 | 1 << 2,
+ from_str::<TestFlags>("A | B | C").unwrap().bits()
+ );
+ assert_eq!(
+ 1 | 1 << 1 | 1 << 2,
+ from_str::<TestFlags>("A\n|\tB\r\n| C ").unwrap().bits()
+ );
+ assert_eq!(
+ 1 | 1 << 1 | 1 << 2,
+ from_str::<TestFlags>("A|B|C").unwrap().bits()
+ );
+
+ assert_eq!(1 << 3, from_str::<TestFlags>("0x8").unwrap().bits());
+ assert_eq!(1 | 1 << 3, from_str::<TestFlags>("A | 0x8").unwrap().bits());
+ assert_eq!(
+ 1 | 1 << 1 | 1 << 3,
+ from_str::<TestFlags>("0x1 | 0x8 | B").unwrap().bits()
+ );
+
+ assert_eq!(
+ 1 | 1 << 1,
+ from_str::<TestUnicode>("一 | 二").unwrap().bits()
+ );
+ }
+
+ #[test]
+ fn invalid() {
+ assert!(from_str::<TestFlags>("a")
+ .unwrap_err()
+ .to_string()
+ .starts_with("unrecognized named flag"));
+ assert!(from_str::<TestFlags>("A & B")
+ .unwrap_err()
+ .to_string()
+ .starts_with("unrecognized named flag"));
+
+ assert!(from_str::<TestFlags>("0xg")
+ .unwrap_err()
+ .to_string()
+ .starts_with("invalid hex flag"));
+ assert!(from_str::<TestFlags>("0xffffffffffff")
+ .unwrap_err()
+ .to_string()
+ .starts_with("invalid hex flag"));
+ }
+}
+
+mod to_writer {
+ use super::*;
+
+ #[test]
+ fn cases() {
+ assert_eq!("", write(TestFlags::empty()));
+ assert_eq!("A", write(TestFlags::A));
+ assert_eq!("A | B | C", write(TestFlags::all()));
+ assert_eq!("0x8", write(TestFlags::from_bits_retain(1 << 3)));
+ assert_eq!(
+ "A | 0x8",
+ write(TestFlags::A | TestFlags::from_bits_retain(1 << 3))
+ );
+
+ assert_eq!("", write(TestZero::ZERO));
+
+ assert_eq!("ABC", write(TestFlagsInvert::all()));
+
+ assert_eq!("A", write(TestOverlappingFull::C));
+ assert_eq!(
+ "A | D",
+ write(TestOverlappingFull::C | TestOverlappingFull::D)
+ );
+ }
+
+ fn write<F: Flags>(value: F) -> String
+ where
+ F::Bits: crate::parser::WriteHex,
+ {
+ let mut s = String::new();
+
+ to_writer(&value, &mut s).unwrap();
+ s
+ }
+}
diff --git a/src/tests/remove.rs b/src/tests/remove.rs
new file mode 100644
index 0000000..574b1ed
--- /dev/null
+++ b/src/tests/remove.rs
@@ -0,0 +1,100 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(
+ TestFlags::empty(),
+ &[
+ (TestFlags::A, 0),
+ (TestFlags::empty(), 0),
+ (TestFlags::from_bits_retain(1 << 3), 0),
+ ],
+ TestFlags::remove,
+ TestFlags::set,
+ );
+
+ case(
+ TestFlags::A,
+ &[
+ (TestFlags::A, 0),
+ (TestFlags::empty(), 1),
+ (TestFlags::B, 1),
+ ],
+ TestFlags::remove,
+ TestFlags::set,
+ );
+
+ case(
+ TestFlags::ABC,
+ &[
+ (TestFlags::A, 1 << 1 | 1 << 2),
+ (TestFlags::A | TestFlags::C, 1 << 1),
+ ],
+ TestFlags::remove,
+ TestFlags::set,
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + Copy>(
+ value: T,
+ inputs: &[(T, T::Bits)],
+ mut inherent_remove: impl FnMut(&mut T, T),
+ mut inherent_set: impl FnMut(&mut T, T, bool),
+) where
+ T::Bits: std::fmt::Debug + PartialEq + Copy,
+{
+ for (input, expected) in inputs {
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ inherent_remove(&mut value, *input);
+ value
+ }
+ .bits(),
+ "{:?}.remove({:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ Flags::remove(&mut value, *input);
+ value
+ }
+ .bits(),
+ "Flags::remove({:?}, {:?})",
+ value,
+ input
+ );
+
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ inherent_set(&mut value, *input, false);
+ value
+ }
+ .bits(),
+ "{:?}.set({:?}, false)",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ Flags::set(&mut value, *input, false);
+ value
+ }
+ .bits(),
+ "Flags::set({:?}, {:?}, false)",
+ value,
+ input
+ );
+ }
+}
diff --git a/src/tests/symmetric_difference.rs b/src/tests/symmetric_difference.rs
new file mode 100644
index 0000000..75e9123
--- /dev/null
+++ b/src/tests/symmetric_difference.rs
@@ -0,0 +1,110 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(
+ TestFlags::empty(),
+ &[
+ (TestFlags::empty(), 0),
+ (TestFlags::all(), 1 | 1 << 1 | 1 << 2),
+ (TestFlags::from_bits_retain(1 << 3), 1 << 3),
+ ],
+ TestFlags::symmetric_difference,
+ TestFlags::toggle,
+ );
+
+ case(
+ TestFlags::A,
+ &[
+ (TestFlags::empty(), 1),
+ (TestFlags::A, 0),
+ (TestFlags::all(), 1 << 1 | 1 << 2),
+ ],
+ TestFlags::symmetric_difference,
+ TestFlags::toggle,
+ );
+
+ case(
+ TestFlags::A | TestFlags::B | TestFlags::from_bits_retain(1 << 3),
+ &[
+ (TestFlags::ABC, 1 << 2 | 1 << 3),
+ (TestFlags::from_bits_retain(1 << 3), 1 | 1 << 1),
+ ],
+ TestFlags::symmetric_difference,
+ TestFlags::toggle,
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + std::ops::BitXor<Output = T> + std::ops::BitXorAssign + Copy>(
+ value: T,
+ inputs: &[(T, T::Bits)],
+ mut inherent_sym_diff: impl FnMut(T, T) -> T,
+ mut inherent_toggle: impl FnMut(&mut T, T),
+) where
+ T::Bits: std::fmt::Debug + PartialEq + Copy,
+{
+ for (input, expected) in inputs {
+ assert_eq!(
+ *expected,
+ inherent_sym_diff(value, *input).bits(),
+ "{:?}.symmetric_difference({:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ Flags::symmetric_difference(value, *input).bits(),
+ "Flags::symmetric_difference({:?}, {:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ (value ^ *input).bits(),
+ "{:?} ^ {:?}",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ value ^= *input;
+ value
+ }
+ .bits(),
+ "{:?} ^= {:?}",
+ value,
+ input,
+ );
+
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ inherent_toggle(&mut value, *input);
+ value
+ }
+ .bits(),
+ "{:?}.toggle({:?})",
+ value,
+ input,
+ );
+
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ Flags::toggle(&mut value, *input);
+ value
+ }
+ .bits(),
+ "{:?}.toggle({:?})",
+ value,
+ input,
+ );
+ }
+}
diff --git a/src/tests/union.rs b/src/tests/union.rs
new file mode 100644
index 0000000..6190681
--- /dev/null
+++ b/src/tests/union.rs
@@ -0,0 +1,71 @@
+use super::*;
+
+use crate::Flags;
+
+#[test]
+fn cases() {
+ case(
+ TestFlags::empty(),
+ &[
+ (TestFlags::A, 1),
+ (TestFlags::all(), 1 | 1 << 1 | 1 << 2),
+ (TestFlags::empty(), 0),
+ (TestFlags::from_bits_retain(1 << 3), 1 << 3),
+ ],
+ TestFlags::union,
+ );
+
+ case(
+ TestFlags::A | TestFlags::C,
+ &[
+ (TestFlags::A | TestFlags::B, 1 | 1 << 1 | 1 << 2),
+ (TestFlags::A, 1 | 1 << 2),
+ ],
+ TestFlags::union,
+ );
+}
+
+#[track_caller]
+fn case<T: Flags + std::fmt::Debug + std::ops::BitOr<Output = T> + std::ops::BitOrAssign + Copy>(
+ value: T,
+ inputs: &[(T, T::Bits)],
+ mut inherent: impl FnMut(T, T) -> T,
+) where
+ T::Bits: std::fmt::Debug + PartialEq + Copy,
+{
+ for (input, expected) in inputs {
+ assert_eq!(
+ *expected,
+ inherent(value, *input).bits(),
+ "{:?}.union({:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ Flags::union(value, *input).bits(),
+ "Flags::union({:?}, {:?})",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ (value | *input).bits(),
+ "{:?} | {:?}",
+ value,
+ input
+ );
+ assert_eq!(
+ *expected,
+ {
+ let mut value = value;
+ value |= *input;
+ value
+ }
+ .bits(),
+ "{:?} |= {:?}",
+ value,
+ input,
+ );
+ }
+}