Skip to content

Commit 7c9aaa3

Browse files
committed
Finish bitfield test suite
1 parent f9157d8 commit 7c9aaa3

16 files changed

+434
-2
lines changed

bitfield/tests/01-specifier-types.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
//
4141
// Create a trait called bitfield::Specifier with an associated constant BITS,
4242
// and write a function-like procedural macro to define some types B1 through
43-
// B64 with corresponding impls of the Specifier trait.
43+
// B64 with corresponding impls of the Specifier trait. The B* types can be
44+
// anything since we don't need them to carry any meaning outside of a
45+
// #[bitfield] struct definition; an uninhabited enum like `pub enum B1 {}`
46+
// would work best.
4447
//
4548
// Be aware that crates that have the "proc-macro" crate type are not allowed to
4649
// export anything other than procedural macros. The project skeleton for this

bitfield/tests/03-INCOMPLETE

Lines changed: 0 additions & 1 deletion
This file was deleted.

bitfield/tests/03-accessors.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Generate getters and setters that manipulate the right range of bits
2+
// corresponding to each field. Refer to the bitfield project introduction in
3+
// the readme for a diagram of how the bit-level mapping is expected to work;
4+
// the diagram uses the same field sizes as the struct in the test case below.
5+
//
6+
// Depending on your implementation, it's possible that this will require adding
7+
// some associated types, associated constants, or associated functions to your
8+
// bitfield::Specifier trait next to the existing Specifier::BITS constant, but
9+
// it may not.
10+
//
11+
// If it's easier for now, you can use u64 as the argument type for all the
12+
// setters and return type for all the getters. We will follow up with a more
13+
// precise signature in a later test case.
14+
15+
use bitfield::*;
16+
17+
#[bitfield]
18+
pub struct MyFourBytes {
19+
a: B1,
20+
b: B3,
21+
c: B4,
22+
d: B24,
23+
}
24+
25+
fn main() {
26+
let mut bitfield = MyFourBytes::new();
27+
assert_eq!(0, bitfield.get_a());
28+
assert_eq!(0, bitfield.get_b());
29+
assert_eq!(0, bitfield.get_c());
30+
assert_eq!(0, bitfield.get_d());
31+
32+
bitfield.set_c(14);
33+
assert_eq!(0, bitfield.get_a());
34+
assert_eq!(0, bitfield.get_b());
35+
assert_eq!(14, bitfield.get_c());
36+
assert_eq!(0, bitfield.get_d());
37+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Make it so that a bitfield with a size not a multiple of 8 bits will not
2+
// compile.
3+
//
4+
// Aim to make the error message as relevant and free of distractions as you can
5+
// get. The stderr file next to this test case should give some idea as to the
6+
// approach taken by the reference implementation for this project, but feel
7+
// free to overwrite the stderr file to match the implementation you come up
8+
// with.
9+
//
10+
// ---
11+
// Tangent
12+
//
13+
// There is only one profound insight about Rust macro development, and this
14+
// test case begins to touch on it: what makes someone an "expert at macros"
15+
// mostly has nothing to do with how good they are "at macros".
16+
//
17+
// 95% of what enables people to write powerful and user-friendly macro
18+
// libraries is in their mastery of everything else about Rust outside of
19+
// macros, and their creativity to put together ordinary language features in
20+
// interesting ways that may not occur in handwritten code.
21+
//
22+
// You may occasionally come across procedural macros that you feel are really
23+
// advanced or magical. If you ever feel this way, I encourage you to take a
24+
// closer look and you'll discover that as far as the macro implementation
25+
// itself is concerned, none of those libraries are doing anything remotely
26+
// interesting. They always just parse some input in a boring way, crawl some
27+
// syntax trees in a boring way to find out about the input, and paste together
28+
// some output code in a boring way exactly like what you've been doing so far.
29+
// In fact once you've made it this far in the workshop, it's okay to assume you
30+
// basically know everything there is to know about the mechanics of writing
31+
// procedural macros.
32+
//
33+
// To the extent that there are any tricks to macro development, all of them
34+
// revolve around *what* code the macros emit, not *how* the macros emit the
35+
// code. This realization can be surprising to people who entered into macro
36+
// development with a vague notion of procedural macros as a "compiler plugin"
37+
// which they imagine must imply all sorts of complicated APIs for *how* to
38+
// integrate with the rest of the compiler. That's not how it works. The only
39+
// thing macros do is emit code that could have been written by hand. If you
40+
// couldn't have come up with some piece of tricky code from one of those
41+
// magical macros, learning more "about macros" won't change that; but learning
42+
// more about every other part of Rust will. Inversely, once you come up with
43+
// what code you want to generate, writing the macro to generate it is generally
44+
// the easy part.
45+
46+
use bitfield::*;
47+
48+
type A = B1;
49+
type B = B3;
50+
type C = B4;
51+
type D = B23;
52+
53+
#[bitfield]
54+
pub struct NotQuiteFourBytes {
55+
a: A,
56+
b: B,
57+
c: C,
58+
d: D,
59+
}
60+
61+
fn main() {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
error[E0277]: the trait bound `bitfield::checks::SevenMod8: bitfield::checks::TotalSizeIsMultipleOfEightBits` is not satisfied
2+
--> $DIR/04-multiple-of-8bits.rs:53:1
3+
|
4+
53 | #[bitfield]
5+
| ^^^^^^^^^^^ the trait `bitfield::checks::TotalSizeIsMultipleOfEightBits` is not implemented for `bitfield::checks::SevenMod8`
6+
7+
For more information about this error, try `rustc --explain E0277`.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// For getters and setters, we would like for the signature to be in terms of
2+
// the narrowest unsigned integer type that can hold the right number of bits.
3+
// That means the accessors for B1 through B8 would use u8, B9 through B16 would
4+
// use u16 etc.
5+
6+
use bitfield::*;
7+
use std::mem::size_of_val;
8+
9+
type A = B1;
10+
type B = B3;
11+
type C = B4;
12+
type D = B24;
13+
14+
#[bitfield]
15+
pub struct MyFourBytes {
16+
a: A,
17+
b: B,
18+
c: C,
19+
d: D,
20+
}
21+
22+
fn main() {
23+
let mut x = MyFourBytes::new();
24+
25+
// I am testing the signatures in this roundabout way to avoid making it
26+
// possible to pass this test with a generic signature that is inconvenient
27+
// for callers, such as `fn get_a<T: From<u64>>(&self) -> T`.
28+
29+
let a = 1;
30+
x.set_a(a); // expect fn(&mut MyFourBytes, u8)
31+
let b = 1;
32+
x.set_b(b);
33+
let c = 1;
34+
x.set_c(c);
35+
let d = 1;
36+
x.set_d(d); // expect fn(&mut MyFourBytes, u32)
37+
38+
assert_eq!(size_of_val(&a), 1);
39+
assert_eq!(size_of_val(&b), 1);
40+
assert_eq!(size_of_val(&c), 1);
41+
assert_eq!(size_of_val(&d), 4);
42+
43+
assert_eq!(size_of_val(&x.get_a()), 1); // expect fn(&MyFourBytes) -> u8
44+
assert_eq!(size_of_val(&x.get_b()), 1);
45+
assert_eq!(size_of_val(&x.get_c()), 1);
46+
assert_eq!(size_of_val(&x.get_d()), 4); // expect fn(&MyFourBytes) -> u32
47+
}

bitfield/tests/06-enums.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// For some bitfield members, working with them as enums will make more sense to
2+
// the user than working with them as integers. We will require enums that have
3+
// a power-of-two number of variants so that they exhaustively cover a fixed
4+
// range of bits.
5+
//
6+
// // Works like B3, but getter and setter signatures will use
7+
// // the enum instead of u8.
8+
// #[derive(BitfieldSpecifier)]
9+
// enum DeliveryMode {
10+
// Fixed = 0b000,
11+
// Lowest = 0b001,
12+
// SMI = 0b010,
13+
// RemoteRead = 0b011,
14+
// NMI = 0b100,
15+
// Init = 0b101,
16+
// Startup = 0b110,
17+
// External = 0b111,
18+
// }
19+
//
20+
// For this test case it is okay to require that every enum variant has an
21+
// explicit discriminant that is an integer literal. We will relax this
22+
// requirement in a later test case.
23+
//
24+
// Optionally if you are interested, come up with a way to support enums with a
25+
// number of variants that is not a power of two, but this is not necessary for
26+
// the test suite. Maybe there could be a #[bits = N] attribute that determines
27+
// the bit width of the specifier, and the getter (only for such enums) would
28+
// return Result<T, Unrecognized> with the raw value accessible through the
29+
// error type as u64:
30+
//
31+
// #[derive(BitfieldSpecifier)]
32+
// #[bits = 4]
33+
// enum SmallPrime {
34+
// Two = 0b0010,
35+
// Three = 0b0011,
36+
// Five = 0b0101,
37+
// Seven = 0b0111,
38+
// Eleven = 0b1011,
39+
// Thirteen = 0b1101,
40+
// }
41+
//
42+
// ...
43+
// let mut bitfield = MyBitfield::new();
44+
// assert_eq!(0, bitfield.small_prime().unwrap_err().raw_value());
45+
//
46+
// bitfield.set_small_prime(SmallPrime::Seven);
47+
// let p = bitfield.small_prime().unwrap_or(SmallPrime::Two);
48+
49+
use bitfield::*;
50+
51+
#[bitfield]
52+
pub struct RedirectionTableEntry {
53+
acknowledged: bool,
54+
trigger_mode: TriggerMode,
55+
delivery_mode: DeliveryMode,
56+
reserved: B3,
57+
}
58+
59+
#[derive(BitfieldSpecifier, Debug, PartialEq)]
60+
pub enum TriggerMode {
61+
Edge = 0,
62+
Level = 1,
63+
}
64+
65+
#[derive(BitfieldSpecifier, Debug, PartialEq)]
66+
pub enum DeliveryMode {
67+
Fixed = 0b000,
68+
Lowest = 0b001,
69+
SMI = 0b010,
70+
RemoteRead = 0b011,
71+
NMI = 0b100,
72+
Init = 0b101,
73+
Startup = 0b110,
74+
External = 0b111,
75+
}
76+
77+
fn main() {
78+
assert_eq!(std::mem::size_of::<RedirectionTableEntry>(), 1);
79+
80+
// Initialized to all 0 bits.
81+
let mut entry = RedirectionTableEntry::new();
82+
assert_eq!(entry.get_acknowledged(), false);
83+
assert_eq!(entry.get_trigger_mode(), TriggerMode::Edge);
84+
assert_eq!(entry.get_delivery_mode(), DeliveryMode::Fixed);
85+
86+
entry.set_acknowledged(true);
87+
entry.set_delivery_mode(DeliveryMode::SMI);
88+
assert_eq!(entry.get_acknowledged(), true);
89+
assert_eq!(entry.get_trigger_mode(), TriggerMode::Edge);
90+
assert_eq!(entry.get_delivery_mode(), DeliveryMode::SMI);
91+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// For bitfield use limited to a single binary, such as a space optimization for
2+
// some in-memory data structure, we may not care what exact bit representation
3+
// is used for enums.
4+
//
5+
// Make your BitfieldSpecifier derive macro for enums use the underlying
6+
// discriminant determined by the Rust compiler as the bit representation. Do
7+
// not assume that the compiler uses any particular scheme like PREV+1 for
8+
// implicit discriminants; make sure your implementation respects Rust's choice
9+
// of discriminant regardless of what scheme Rust uses. This is important for
10+
// performance so that the getter and setter both compile down to very simple
11+
// machine code after optimizations.
12+
//
13+
// Do not worry about what happens if discriminants are outside of the range
14+
// 0..2^BITS. We will do a compile-time check in a later test case to ensure
15+
// they are in range.
16+
17+
use bitfield::*;
18+
19+
#[bitfield]
20+
pub struct RedirectionTableEntry {
21+
delivery_mode: DeliveryMode,
22+
reserved: B5,
23+
}
24+
25+
const F: isize = 3;
26+
const G: isize = 0;
27+
28+
#[derive(BitfieldSpecifier, Debug, PartialEq)]
29+
pub enum DeliveryMode {
30+
Fixed = F,
31+
Lowest,
32+
SMI,
33+
RemoteRead,
34+
NMI,
35+
Init = G,
36+
Startup,
37+
External,
38+
}
39+
40+
fn main() {
41+
assert_eq!(std::mem::size_of::<RedirectionTableEntry>(), 1);
42+
43+
// Initialized to all 0 bits.
44+
let mut entry = RedirectionTableEntry::new();
45+
assert_eq!(entry.get_delivery_mode(), DeliveryMode::Init);
46+
47+
entry.set_delivery_mode(DeliveryMode::Lowest);
48+
assert_eq!(entry.get_delivery_mode(), DeliveryMode::Lowest);
49+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Bitfield enums with a number of variants other than a power of two should
2+
// fail to compile.
3+
//
4+
// (Or, if you implemented the optional #[bits = N] enum approach mentioned in
5+
// the explanation of test case 06, then enums with non-power-of-two variants
6+
// without a #[bits = N] attribute should fail to compile.)
7+
8+
use bitfield::*;
9+
10+
#[derive(BitfieldSpecifier)]
11+
pub enum Bad {
12+
Zero,
13+
One,
14+
Two,
15+
}
16+
17+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: BitfieldSpecifier expected a number of variants which is a power of 2
2+
--> $DIR/08-non-power-of-two.rs:10:10
3+
|
4+
10 | #[derive(BitfieldSpecifier)]
5+
| ^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)