I’m embarrassed by how much code I cut from my test suite

Memphis (8 Part Series)

1 A REPL for fat-finger friendly typing
2 Building for WebAssembly
4 more parts…
3 An interpreter inside an interpreter
4 Improving memory efficiency in a working interpreter
5 How I added support for nested functions in Python bytecode
6 Typed integers in Rust for safer Python bytecode compilation
7 I left corporate and still do roadmaps + a Memphis update
8 I’m embarrassed by how much code I cut from my test suite

My parser test suite was a house of cards I’ve never been prouder to see collapse. I found a pattern that worked, kept working, and then worked a little too well. Until rust-analyzer couldn’t process my file anymore. Best practice says to refactor before your LSP craps out, but alas. Here’s how I saved several thousand lines of test code in my Memphis parser.

Designing an expressive parser test suite

I’m going to break all rules of writing and tell this story Benjamin Button style.

We begin with this idyllic test case. Wouldn’t it be lovely to verify our AST in an expressive yet relaxed way? Yes, it is lovely.

<span>#[test]</span>
<span>fn</span> <span>expression</span><span>()</span> <span>{</span>
<span>let</span> <span>input</span> <span>=</span> <span>"2 + 3 * (4 - 1)"</span><span>;</span>
<span>let</span> <span>expected_ast</span> <span>=</span> <span>bin_op!</span><span>(</span>
<span>int!</span><span>(</span><span>2</span><span>),</span>
<span>Add</span><span>,</span>
<span>bin_op!</span><span>(</span><span>int!</span><span>(</span><span>3</span><span>),</span> <span>Mul</span><span>,</span> <span>bin_op!</span><span>(</span><span>int!</span><span>(</span><span>4</span><span>),</span> <span>Sub</span><span>,</span> <span>int!</span><span>(</span><span>1</span><span>)))</span>
<span>);</span>
<span>assert_ast_eq!</span><span>(</span><span>input</span><span>,</span> <span>expected_ast</span><span>,</span> <span>Expr</span><span>);</span>
<span>let</span> <span>input</span> <span>=</span> <span>"2 // 3"</span><span>;</span>
<span>let</span> <span>expected_ast</span> <span>=</span> <span>bin_op!</span><span>(</span><span>int!</span><span>(</span><span>2</span><span>),</span> <span>IntegerDiv</span><span>,</span> <span>int!</span><span>(</span><span>3</span><span>));</span>
<span>assert_ast_eq!</span><span>(</span><span>input</span><span>,</span> <span>expected_ast</span><span>,</span> <span>Expr</span><span>);</span>
<span>}</span>
<span>#[test]</span>
<span>fn</span> <span>expression</span><span>()</span> <span>{</span>
    <span>let</span> <span>input</span> <span>=</span> <span>"2 + 3 * (4 - 1)"</span><span>;</span>
    <span>let</span> <span>expected_ast</span> <span>=</span> <span>bin_op!</span><span>(</span>
        <span>int!</span><span>(</span><span>2</span><span>),</span>
        <span>Add</span><span>,</span>
        <span>bin_op!</span><span>(</span><span>int!</span><span>(</span><span>3</span><span>),</span> <span>Mul</span><span>,</span> <span>bin_op!</span><span>(</span><span>int!</span><span>(</span><span>4</span><span>),</span> <span>Sub</span><span>,</span> <span>int!</span><span>(</span><span>1</span><span>)))</span>
    <span>);</span>

    <span>assert_ast_eq!</span><span>(</span><span>input</span><span>,</span> <span>expected_ast</span><span>,</span> <span>Expr</span><span>);</span>

    <span>let</span> <span>input</span> <span>=</span> <span>"2 // 3"</span><span>;</span>
    <span>let</span> <span>expected_ast</span> <span>=</span> <span>bin_op!</span><span>(</span><span>int!</span><span>(</span><span>2</span><span>),</span> <span>IntegerDiv</span><span>,</span> <span>int!</span><span>(</span><span>3</span><span>));</span>

    <span>assert_ast_eq!</span><span>(</span><span>input</span><span>,</span> <span>expected_ast</span><span>,</span> <span>Expr</span><span>);</span>
<span>}</span>
#[test] fn expression() { let input = "2 + 3 * (4 - 1)"; let expected_ast = bin_op!( int!(2), Add, bin_op!(int!(3), Mul, bin_op!(int!(4), Sub, int!(1))) ); assert_ast_eq!(input, expected_ast, Expr); let input = "2 // 3"; let expected_ast = bin_op!(int!(2), IntegerDiv, int!(3)); assert_ast_eq!(input, expected_ast, Expr); }

Enter fullscreen mode Exit fullscreen mode

This test wasn’t all sunflowers and rainbows. This 16-line fact-checker used to come in at a bloated 38 lines.

Have a peep yourself.

<span>#[test]</span>
<span>fn</span> <span>expression</span><span>()</span> <span>{</span>
<span>let</span> <span>input</span> <span>=</span> <span>"2 + 3 * (4 - 1)"</span><span>;</span>
<span>let</span> <span>context</span> <span>=</span> <span>init</span><span>(</span><span>input</span><span>);</span>
<span>let</span> <span>expected_ast</span> <span>=</span> <span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
<span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>2</span><span>)),</span>
<span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>Add</span><span>,</span>
<span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
<span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>3</span><span>)),</span>
<span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>Mul</span><span>,</span>
<span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
<span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>4</span><span>)),</span>
<span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>Sub</span><span>,</span>
<span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>1</span><span>)),</span>
<span>}),</span>
<span>}),</span>
<span>};</span>
<span>match</span> <span>context</span><span>.parse_oneshot</span><span>::</span><span><</span><span>Expr</span><span>></span><span>()</span> <span>{</span>
<span>Err</span><span>(</span><span>e</span><span>)</span> <span>=></span> <span>panic!</span><span>(</span><span>"Parser error: {:?}"</span><span>,</span> <span>e</span><span>),</span>
<span>Ok</span><span>(</span><span>ast</span><span>)</span> <span>=></span> <span>assert_eq!</span><span>(</span><span>ast</span><span>,</span> <span>expected_ast</span><span>),</span>
<span>}</span>
<span>let</span> <span>input</span> <span>=</span> <span>"2 // 3"</span><span>;</span>
<span>let</span> <span>context</span> <span>=</span> <span>init</span><span>(</span><span>input</span><span>);</span>
<span>let</span> <span>expected_ast</span> <span>=</span> <span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
<span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>2</span><span>)),</span>
<span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>IntegerDiv</span><span>,</span>
<span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>3</span><span>)),</span>
<span>};</span>
<span>match</span> <span>context</span><span>.parse_oneshot</span><span>::</span><span><</span><span>Expr</span><span>></span><span>()</span> <span>{</span>
<span>Err</span><span>(</span><span>e</span><span>)</span> <span>=></span> <span>panic!</span><span>(</span><span>"Parser error: {:?}"</span><span>,</span> <span>e</span><span>),</span>
<span>Ok</span><span>(</span><span>ast</span><span>)</span> <span>=></span> <span>assert_eq!</span><span>(</span><span>ast</span><span>,</span> <span>expected_ast</span><span>),</span>
<span>}</span>
<span>}</span>
<span>#[test]</span>
<span>fn</span> <span>expression</span><span>()</span> <span>{</span>
    <span>let</span> <span>input</span> <span>=</span> <span>"2 + 3 * (4 - 1)"</span><span>;</span>
    <span>let</span> <span>context</span> <span>=</span> <span>init</span><span>(</span><span>input</span><span>);</span>

    <span>let</span> <span>expected_ast</span> <span>=</span> <span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
        <span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>2</span><span>)),</span>
        <span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>Add</span><span>,</span>
        <span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
            <span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>3</span><span>)),</span>
            <span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>Mul</span><span>,</span>
            <span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
                <span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>4</span><span>)),</span>
                <span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>Sub</span><span>,</span>
                <span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>1</span><span>)),</span>
            <span>}),</span>
        <span>}),</span>
    <span>};</span>

    <span>match</span> <span>context</span><span>.parse_oneshot</span><span>::</span><span><</span><span>Expr</span><span>></span><span>()</span> <span>{</span>
        <span>Err</span><span>(</span><span>e</span><span>)</span> <span>=></span> <span>panic!</span><span>(</span><span>"Parser error: {:?}"</span><span>,</span> <span>e</span><span>),</span>
        <span>Ok</span><span>(</span><span>ast</span><span>)</span> <span>=></span> <span>assert_eq!</span><span>(</span><span>ast</span><span>,</span> <span>expected_ast</span><span>),</span>
    <span>}</span>

    <span>let</span> <span>input</span> <span>=</span> <span>"2 // 3"</span><span>;</span>
    <span>let</span> <span>context</span> <span>=</span> <span>init</span><span>(</span><span>input</span><span>);</span>

    <span>let</span> <span>expected_ast</span> <span>=</span> <span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
        <span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>2</span><span>)),</span>
        <span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>IntegerDiv</span><span>,</span>
        <span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>Expr</span><span>::</span><span>Integer</span><span>(</span><span>3</span><span>)),</span>
    <span>};</span>

    <span>match</span> <span>context</span><span>.parse_oneshot</span><span>::</span><span><</span><span>Expr</span><span>></span><span>()</span> <span>{</span>
        <span>Err</span><span>(</span><span>e</span><span>)</span> <span>=></span> <span>panic!</span><span>(</span><span>"Parser error: {:?}"</span><span>,</span> <span>e</span><span>),</span>
        <span>Ok</span><span>(</span><span>ast</span><span>)</span> <span>=></span> <span>assert_eq!</span><span>(</span><span>ast</span><span>,</span> <span>expected_ast</span><span>),</span>
    <span>}</span>
<span>}</span>
#[test] fn expression() { let input = "2 + 3 * (4 - 1)"; let context = init(input); let expected_ast = Expr::BinaryOperation { left: Box::new(Expr::Integer(2)), op: BinOp::Add, right: Box::new(Expr::BinaryOperation { left: Box::new(Expr::Integer(3)), op: BinOp::Mul, right: Box::new(Expr::BinaryOperation { left: Box::new(Expr::Integer(4)), op: BinOp::Sub, right: Box::new(Expr::Integer(1)), }), }), }; match context.parse_oneshot::<Expr>() { Err(e) => panic!("Parser error: {:?}", e), Ok(ast) => assert_eq!(ast, expected_ast), } let input = "2 // 3"; let context = init(input); let expected_ast = Expr::BinaryOperation { left: Box::new(Expr::Integer(2)), op: BinOp::IntegerDiv, right: Box::new(Expr::Integer(3)), }; match context.parse_oneshot::<Expr>() { Err(e) => panic!("Parser error: {:?}", e), Ok(ast) => assert_eq!(ast, expected_ast), } }

Enter fullscreen mode Exit fullscreen mode

The tool I used to reduce my boilerplate was declarative macros, Rust’s way of generating Rust code at compile time.

By the end of my cleanup trance, I improved these areas:

  • expressing Python types (int, str, bool, list, set, tuple)
  • expressing operations (binary, unary, and logical ops)
  • wrapping the actual entrypoint to parse the input
  • wrapping error handling for the common case

The result? Shorter tests and clearer intentions.

Expressing Python types

I can now write int!(3) instead of Expr::Integer(3). Blah blah blah so what.

This one doesn’t save a whole lot, but let’s look at two more.

I can now write list![int!(1), int!(2), int!(3)] and set![int!(1), int!(2), int!(3)]. Glancing at the implementation for those macros, we see another key.

<span>macro_rules!</span> <span>list</span> <span>{</span>
<span>(</span><span>$</span><span>(</span><span>$expr:expr</span><span>),</span><span>*</span> <span>$</span><span>(,)</span><span>?</span><span>)</span> <span>=></span> <span>{</span>
<span>Expr</span><span>::</span><span>List</span><span>(</span><span>vec!</span><span>[</span>
<span>$</span><span>(</span><span>$expr</span><span>),</span><span>*</span>
<span>])</span>
<span>};</span>
<span>}</span>
<span>macro_rules!</span> <span>set</span> <span>{</span>
<span>(</span><span>$</span><span>(</span><span>$expr:expr</span><span>),</span><span>*</span> <span>$</span><span>(,)</span><span>?</span><span>)</span> <span>=></span> <span>{</span>
<span>Expr</span><span>::</span><span>Set</span><span>(</span><span>HashSet</span><span>::</span><span>from</span><span>([</span>
<span>$</span><span>(</span><span>$expr</span><span>),</span><span>*</span>
<span>]))</span>
<span>};</span>
<span>}</span>
<span>macro_rules!</span> <span>list</span> <span>{</span>
    <span>(</span><span>$</span><span>(</span><span>$expr:expr</span><span>),</span><span>*</span> <span>$</span><span>(,)</span><span>?</span><span>)</span> <span>=></span> <span>{</span>
        <span>Expr</span><span>::</span><span>List</span><span>(</span><span>vec!</span><span>[</span>
            <span>$</span><span>(</span><span>$expr</span><span>),</span><span>*</span>
        <span>])</span>
    <span>};</span>
<span>}</span>

<span>macro_rules!</span> <span>set</span> <span>{</span>
    <span>(</span><span>$</span><span>(</span><span>$expr:expr</span><span>),</span><span>*</span> <span>$</span><span>(,)</span><span>?</span><span>)</span> <span>=></span> <span>{</span>
        <span>Expr</span><span>::</span><span>Set</span><span>(</span><span>HashSet</span><span>::</span><span>from</span><span>([</span>
            <span>$</span><span>(</span><span>$expr</span><span>),</span><span>*</span>
        <span>]))</span>
    <span>};</span>
<span>}</span>
macro_rules! list { ($($expr:expr),* $(,)?) => { Expr::List(vec![ $($expr),* ]) }; } macro_rules! set { ($($expr:expr),* $(,)?) => { Expr::Set(HashSet::from([ $($expr),* ])) }; }

Enter fullscreen mode Exit fullscreen mode

My parser tests no longer care that an Expr::List accepts a Vec, while a Expr::Set accepts a HashSet. One could argue I don’t need the HashSet at all because this is just the AST, not the evaluation stage of the interpreter. In that case, I could change the underlying representation by updating the macro.

Expressing operations

This one starts to get really fun. I can now write bin_op!(var!("a"), BitwiseAnd, var!("b")), which expands to the following.

<span>macro_rules!</span> <span>bin_op</span> <span>{</span>
<span>(</span><span>$left:expr</span><span>,</span> <span>$op:ident</span><span>,</span> <span>$right:expr</span><span>)</span> <span>=></span> <span>{</span>
<span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
<span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>$left</span><span>),</span>
<span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>$op</span><span>,</span>
<span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>$right</span><span>),</span>
<span>}</span>
<span>};</span>
<span>}</span>
<span>macro_rules!</span> <span>bin_op</span> <span>{</span>
    <span>(</span><span>$left:expr</span><span>,</span> <span>$op:ident</span><span>,</span> <span>$right:expr</span><span>)</span> <span>=></span> <span>{</span>
        <span>Expr</span><span>::</span><span>BinaryOperation</span> <span>{</span>
            <span>left</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>$left</span><span>),</span>
            <span>op</span><span>:</span> <span>BinOp</span><span>::</span><span>$op</span><span>,</span>
            <span>right</span><span>:</span> <span>Box</span><span>::</span><span>new</span><span>(</span><span>$right</span><span>),</span>
        <span>}</span>
    <span>};</span>
<span>}</span>
macro_rules! bin_op { ($left:expr, $op:ident, $right:expr) => { Expr::BinaryOperation { left: Box::new($left), op: BinOp::$op, right: Box::new($right), } }; }

Enter fullscreen mode Exit fullscreen mode

Not only does this macro hide the two Box initializations (necessary in a recursive enum), it also allows us to write BitwiseAnd rather than BinOp::BitwiseAnd, all without sacrificing any type checking.

Wrapping the parser entrypoint

Previously, I had to write this, which isn’t awful, but is also awful.

<span>let</span> <span>context</span> <span>=</span> <span>init</span><span>(</span><span>"2 + 3"</span><span>);</span>
<span>match</span> <span>context</span><span>.parse_oneshot</span><span>::</span><span><</span><span>Expr</span><span>></span><span>()</span> <span>{</span>
<span>...</span>
<span>}</span>
<span>let</span> <span>context</span> <span>=</span> <span>init</span><span>(</span><span>"2 + 3"</span><span>);</span>

<span>match</span> <span>context</span><span>.parse_oneshot</span><span>::</span><span><</span><span>Expr</span><span>></span><span>()</span> <span>{</span>
   <span>...</span>
<span>}</span>
let context = init("2 + 3"); match context.parse_oneshot::<Expr>() { ... }

Enter fullscreen mode Exit fullscreen mode

We’re working with a MemphisContext object here, another bloated structure I use to orchestrate the whole evaluation flow. The problem is, this is an evolving interface. I learned the hard way that without a level of indirection in my tests, I’d have to tweak this pattern constantly. Across hundreds of tests, that is obnoxious.

The new approach uses a straight forward parse! macro.

<span>let</span> <span>ast</span> <span>=</span> <span>parse!</span><span>(</span>$<span>input</span><span>,</span> <span>Statement</span><span>);</span>
<span>assert_stmt_eq!</span><span>(</span><span>ast</span><span>,</span> $<span>expected</span><span>);</span>
<span>let</span> <span>ast</span> <span>=</span> <span>parse!</span><span>(</span>$<span>input</span><span>,</span> <span>Statement</span><span>);</span>
<span>assert_stmt_eq!</span><span>(</span><span>ast</span><span>,</span> $<span>expected</span><span>);</span>
let ast = parse!($input, Statement); assert_stmt_eq!(ast, $expected);

Enter fullscreen mode Exit fullscreen mode

Wrapping the happy path error handling

As Yogi Berra famously said, 90% of unit tests are one-half mental.

I applied this philosophy to design parse! to handle the happy path, the roughly 90% of my parser tests I expect to be able to parse their Python input successfully. Since these are tests and nothing matters, we can fail loudly on any unexpected exceptions and return the AST quietly otherwise.

<span>macro_rules!</span> <span>parse</span> <span>{</span>
<span>(</span><span>$input:expr</span><span>,</span> <span>$pattern:ident</span><span>)</span> <span>=></span> <span>{</span>
<span>match</span> <span>init</span><span>(</span><span>$input</span><span>)</span><span>.parse_oneshot</span><span>::</span><span><</span><span>$pattern</span><span>></span><span>()</span> <span>{</span>
<span>Err</span><span>(</span><span>e</span><span>)</span> <span>=></span> <span>panic!</span><span>(</span><span>"Parser error: {:?}"</span><span>,</span> <span>e</span><span>),</span>
<span>Ok</span><span>(</span><span>ast</span><span>)</span> <span>=></span> <span>ast</span><span>,</span>
<span>}</span>
<span>};</span>
<span>}</span>
<span>macro_rules!</span> <span>parse</span> <span>{</span>
    <span>(</span><span>$input:expr</span><span>,</span> <span>$pattern:ident</span><span>)</span> <span>=></span> <span>{</span>
        <span>match</span> <span>init</span><span>(</span><span>$input</span><span>)</span><span>.parse_oneshot</span><span>::</span><span><</span><span>$pattern</span><span>></span><span>()</span> <span>{</span>
            <span>Err</span><span>(</span><span>e</span><span>)</span> <span>=></span> <span>panic!</span><span>(</span><span>"Parser error: {:?}"</span><span>,</span> <span>e</span><span>),</span>
            <span>Ok</span><span>(</span><span>ast</span><span>)</span> <span>=></span> <span>ast</span><span>,</span>
        <span>}</span>
    <span>};</span>
<span>}</span>
macro_rules! parse { ($input:expr, $pattern:ident) => { match init($input).parse_oneshot::<$pattern>() { Err(e) => panic!("Parser error: {:?}", e), Ok(ast) => ast, } }; }

Enter fullscreen mode Exit fullscreen mode

And for those 10% of tests where we expect a parse error? This will do just fine.

<span>macro_rules!</span> <span>expect_error</span> <span>{</span>
<span>(</span><span>$input:expr</span><span>,</span> <span>$pattern:ident</span><span>)</span> <span>=></span> <span>{</span>
<span>match</span> <span>init</span><span>(</span><span>$input</span><span>)</span><span>.parse_oneshot</span><span>::</span><span><</span><span>$pattern</span><span>></span><span>()</span> <span>{</span>
<span>Ok</span><span>(</span><span>_</span><span>)</span> <span>=></span> <span>panic!</span><span>(</span><span>"Expected a ParserError!"</span><span>),</span>
<span>Err</span><span>(</span><span>e</span><span>)</span> <span>=></span> <span>e</span><span>,</span>
<span>}</span>
<span>};</span>
<span>}</span>
<span>macro_rules!</span> <span>expect_error</span> <span>{</span>
    <span>(</span><span>$input:expr</span><span>,</span> <span>$pattern:ident</span><span>)</span> <span>=></span> <span>{</span>
        <span>match</span> <span>init</span><span>(</span><span>$input</span><span>)</span><span>.parse_oneshot</span><span>::</span><span><</span><span>$pattern</span><span>></span><span>()</span> <span>{</span>
            <span>Ok</span><span>(</span><span>_</span><span>)</span> <span>=></span> <span>panic!</span><span>(</span><span>"Expected a ParserError!"</span><span>),</span>
            <span>Err</span><span>(</span><span>e</span><span>)</span> <span>=></span> <span>e</span><span>,</span>
        <span>}</span>
    <span>};</span>
<span>}</span>
macro_rules! expect_error { ($input:expr, $pattern:ident) => { match init($input).parse_oneshot::<$pattern>() { Ok(_) => panic!("Expected a ParserError!"), Err(e) => e, } }; }

Enter fullscreen mode Exit fullscreen mode

This is what it now looks like to confirm an improperly structured dict. While I love match statements, I love even more no longer having to write them.

<span>let</span> <span>input</span> <span>=</span> <span>"{ 2, **second }"</span><span>;</span>
<span>let</span> <span>e</span> <span>=</span> <span>expect_error!</span><span>(</span><span>input</span><span>,</span> <span>Expr</span><span>);</span>
<span>assert_eq!</span><span>(</span><span>e</span><span>,</span> <span>ParserError</span><span>::</span><span>SyntaxError</span><span>);</span>
<span>let</span> <span>input</span> <span>=</span> <span>"{ 2, **second }"</span><span>;</span>
<span>let</span> <span>e</span> <span>=</span> <span>expect_error!</span><span>(</span><span>input</span><span>,</span> <span>Expr</span><span>);</span>
<span>assert_eq!</span><span>(</span><span>e</span><span>,</span> <span>ParserError</span><span>::</span><span>SyntaxError</span><span>);</span>
let input = "{ 2, **second }"; let e = expect_error!(input, Expr); assert_eq!(e, ParserError::SyntaxError);

Enter fullscreen mode Exit fullscreen mode

The End

I’m embarrassed I didn’t do these steps sooner. Please learn from my mistakes and treat your tests the way you wish to be treated.


Subscribe & Save [on nothing]

Want a software career that actually feels meaningful? I wrote a free 5-day email course on honing your craft, aligning your work with your values, and building for yourself. Or just not hating your job! Get it here.

Build [With Me]

I mentor software engineers to navigate technical challenges and career growth in a supportive, sometimes silly environment. If you’re interested, you can explore my mentorship programs.

Elsewhere [From Scratch]

In addition to mentoring, I also write about neurodivergence and meaningful work. Less code and the same number of jokes.

Memphis (8 Part Series)

1 A REPL for fat-finger friendly typing
2 Building for WebAssembly
4 more parts…
3 An interpreter inside an interpreter
4 Improving memory efficiency in a working interpreter
5 How I added support for nested functions in Python bytecode
6 Typed integers in Rust for safer Python bytecode compilation
7 I left corporate and still do roadmaps + a Memphis update
8 I’m embarrassed by how much code I cut from my test suite

原文链接:I’m embarrassed by how much code I cut from my test suite

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
pride relates more to our opinion of ourselves, vanity to what we would have others think of us.
骄傲多半涉及我们自己怎样看待自己,而虚荣则涉及我们想别人怎样看我们
评论 抢沙发

请登录后发表评论

    暂无评论内容