dimanche 4 février 2018

Writing maintainable tests for compiler phases

This question may end up a little vague, so apologies in advance. In short though: how do you write tests for compiler phases that don't break easily.

The compiler I'm working on consists of four phases with (mostly) well defined inputs and outputs, so:

source code -> lex() -> tokens -> parse() -> AST -> intermediate() -> IR -> generation() -> output

The existing tests for each phase simply compare outputs with expected results, e.g:

assert_eq!(
    lex("section { things }"),
    vec![Section, LeftBrace, Ident("things"), RightBrace]
);

The project is written in Rust, but the question is more general than that.

However, this gets far, far more verbose in later stages as the inputs and outputs are more complex:

assert_eq!(
    intermediate(Ast { root: SectionNode { attr: vec![AttributeNode { ... }] } }),
    SectionModel { things: vec![etc...] }
);

This means two things:

  1. Adding a new test requires a lot of typing/copy-pasting.
  2. Changes to almost any structure requires updating every test.

I'm currently side-stepping this in places by using source code as the input of the test, and lexing it and parsing it in the test, but this means that any error in the lexer would cause literally every test to fail which is... not ideal.

I feel like there must be a better way to test what is essentially a function with complex input/output structs, but I'm not exactly sure what it is.

Aucun commentaire:

Enregistrer un commentaire