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);