whiley_test_file/
lib.rs

1//! A library for parsing Whiley test files according to
2//! [RFC#110](https://github.com/Whiley/RFCs/blob/master/text/0110-test-file-format.md)
3//! which are used for testing the [Whiley
4//! compiler](https://github.com/Whiley/WhileyCompiler).  Each test
5//! describes a sequence of modifications to one of more Whiley files,
6//! along with the expected outcomes (e.g. errors, warnings, etc).  An
7//! example test file is the following:
8//! ```text
9//! whiley.verify = false
10//! boogie.timeout = 1000
11//! ================
12//! >>> main.whiley
13//! method main():
14//! >>> other.whiley
15//! import main
16//! ---
17//! E101 main.whiley 1,2
18//! E302 main.whiley 2,2:3
19//! ================
20//! <<< other.whiley
21//! >>> main.whiley 1:1
22//! method main()
23//!     skip
24//! ---
25//! ```
26//! This is a test involving two files: `main.whiley` and `other.whiley`.
27//! The initial frame sets the contents of `main.whiley` to `method
28//! main()` and the contents of `other.whiley` to `import main`.
29//! Furthermore, compiling this frame is expected to produce two errors
30//! (`E101` and `E302`).  The second frame deletes file `other.whiley` and
31//! updates the contents of `main.whiley`.  Furthermore, compiling the
32//! snapshot at this point is not expected to produce any errors.
33//!
34//! ```
35//! use std::fs;
36//! use whiley_test_file::WhileyTestFile;
37//!
38//! fn load(filename: &str) {
39//!     // Read the test file
40//!     let input = fs::read_to_string(filename).unwrap();
41//!     // Parse test file
42//!     let test_file = WhileyTestFile::new(&input).unwrap();
43//!     // ...
44//! }
45//! ```
46
47// Hidden modules
48mod parser;
49
50use parser::Parser;
51use std::collections::HashMap;
52use std::result;
53
54// ===============================================================
55// Error
56// ===============================================================
57
58#[derive(Clone, Copy, Debug, PartialEq)]
59pub enum Error {
60    UnexpectedEof,
61    InvalidConfigOption,
62    InvalidConfigValue,
63    InvalidIntValue,
64    InvalidStringValue,
65    InvalidAction,
66    InvalidRange,
67    InvalidMarker,
68    InvalidErrorCode,
69    InvalidCoordinate,
70}
71
72pub type Result<T> = result::Result<T, Error>;
73
74// ===============================================================
75// Test File
76// ===============================================================
77
78pub struct WhileyTestFile<'a> {
79    config: Config<'a>,
80    frames: Vec<Frame<'a>>,
81}
82
83impl<'a> WhileyTestFile<'a> {
84    pub fn new(input: &'a str) -> Result<WhileyTestFile<'a>> {
85        // Construct parser
86        let mut parser = Parser::new(input);
87        // Parse file (with errors)
88        let wtf = parser.parse()?;
89        // Done
90        Ok(wtf)
91    }
92
93    /// Get configuration option associated with the given key.
94    pub fn get(&self, key: &str) -> Option<&Value> {
95        self.config.get(key)
96    }
97
98    /// Get number of frames in this test file.
99    pub fn size(&self) -> usize {
100        self.frames.len()
101    }
102
103    /// Get nth frame within this test file.
104    pub fn frame(&self, n: usize) -> &Frame {
105        &self.frames[n]
106    }
107
108    /// Get configuration option which is expected to be an integer.
109    /// If its not an integer, or no such key exists, `None` is
110    /// returned.
111    pub fn get_int(&self, key: &str) -> Option<i64> {
112        match self.config.get(key) {
113            Some(&Value::Int(i)) => Some(i),
114            _ => None,
115        }
116    }
117
118    /// Get configuration option which is expected to be an boolean.
119    /// If its not a boolean, or no such key exists, `None` is
120    /// returned.
121    pub fn get_bool(&self, key: &str) -> Option<bool> {
122        match self.config.get(key) {
123            Some(&Value::Bool(b)) => Some(b),
124            _ => None,
125        }
126    }
127
128    /// Get configuration option which is expected to be a string If
129    /// its not a string, or no such key exists, `None` is returned.
130    pub fn get_str(&self, key: &str) -> Option<&'a str> {
131        match &self.config.get(key) {
132            Some(&Value::String(s)) => Some(s),
133            _ => None,
134        }
135    }
136
137    /// Obtain an iterator to the frames of this test file.
138    pub fn iter<'b>(&'b self) -> std::slice::Iter<'b, Frame> {
139        self.frames.iter()
140    }
141}
142
143// ===============================================================
144// Config
145// ===============================================================
146
147#[derive(Clone, Debug, PartialEq)]
148pub enum Value<'a> {
149    String(&'a str),
150    Int(i64),
151    Bool(bool),
152}
153type Config<'a> = HashMap<&'a str, Value<'a>>;
154
155// ===============================================================
156// Frame
157// ===============================================================
158
159/// Represents a frame within a testfile.  Each frame identifies a
160/// number of _actions_ which operate on the state at that point,
161/// along with zero or more expected _markers_ (e.g. error messages).
162/// The set of actions includes _inserting_ and _removing_ lines on a
163/// specific file.  Actions are applied in the order of appearance,
164/// though they are not expected to overlap.
165pub struct Frame<'a> {
166    pub actions: Vec<Action<'a>>,
167    pub markers: Vec<Marker<'a>>,
168}
169
170// ===============================================================
171// Action
172// ===============================================================
173
174/// Represents an atomic action which can be applied to a source file,
175/// such as inserting or replacing lines within the file.
176#[derive(Debug, PartialEq)]
177pub enum Action<'a> {
178    CREATE(&'a str, Vec<&'a str>),
179    REMOVE(&'a str),
180    INSERT(&'a str, Range, Vec<&'a str>),
181}
182
183impl<'a> Action<'a> {
184    pub fn lines(&self) -> &[&'a str] {
185        match self {
186            Action::CREATE(_, lines) => lines,
187            Action::INSERT(_, _, lines) => lines,
188            _ => {
189                panic!("no line information!");
190            }
191        }
192    }
193
194    pub fn range(&self) -> &Range {
195        match self {
196            Action::INSERT(_, r, _) => r,
197            _ => {
198                panic!("no range information!");
199            }
200        }
201    }
202}
203
204// ===============================================================
205// Marker
206// ===============================================================
207
208/// Identifies an expected error at a location in a given source file.
209pub struct Marker<'a> {
210    pub errno: u16,
211    pub filename: &'a str,
212    pub location: Coordinate,
213}
214
215// ===============================================================
216// Coordinate
217// ===============================================================
218
219/// Identifies a specific range of characters within a file.
220#[derive(Clone, Copy, Debug, PartialEq)]
221pub struct Coordinate(pub usize, pub Range);
222
223// ===============================================================
224// Range
225// ===============================================================
226
227/// Represents an interval (e.g. of characters within a line).
228#[derive(Clone, Copy, Debug, PartialEq)]
229pub struct Range(pub usize, pub usize);