1use crate::{check_args, molt_ok, prelude::Interp, MoltResult, ResultCode, Value};
26use std::{env, fs, path::PathBuf};
27
28pub fn test_harness<Ctx>(
62 interp: &mut Interp<(Ctx, TestCtx)>,
63 args: &[String],
64) -> Result<(), ()> {
65 println!("Molt {} -- Test Harness", env!("CARGO_PKG_VERSION"));
67
68 if args.is_empty() {
70 eprintln!("missing test script");
71 return Err(());
72 }
73
74 let path = PathBuf::from(&args[0]);
75
76 match fs::read_to_string(&args[0]) {
81 Ok(script) => {
82 if let Some(parent) = path.parent() {
83 let _ = env::set_current_dir(parent);
84 }
85
86 if let Err(exception) = interp.eval(&script) {
87 if exception.code() == ResultCode::Error {
88 eprintln!("{}", exception.value());
89 return Err(());
90 } else {
91 eprintln!("Unexpected eval return: {:?}", exception);
92 return Err(());
93 }
94 }
95 }
96 Err(e) => {
97 println!("{}", e);
98 return Err(());
99 }
100 }
101
102 let ctx = &mut interp.context.1;
104 println!(
105 "\n{} tests, {} passed, {} failed, {} errors",
106 ctx.num_tests, ctx.num_passed, ctx.num_failed, ctx.num_errors
107 );
108
109 if ctx.num_failed + ctx.num_errors == 0 {
110 Ok(())
111 } else {
112 Err(())
113 }
114}
115
116pub struct TestCtx {
117 num_tests: usize,
118 num_passed: usize,
119 num_failed: usize,
120 num_errors: usize,
121}
122
123impl TestCtx {
124 pub fn new() -> Self {
125 Self {
126 num_tests: 0,
127 num_passed: 0,
128 num_failed: 0,
129 num_errors: 0,
130 }
131 }
132}
133
134#[derive(Eq, PartialEq, Debug)]
135enum Code {
136 Ok,
137 Error,
138}
139
140impl std::fmt::Display for Code {
141 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
142 match self {
143 Code::Ok => write!(f, "-ok"),
144 Code::Error => write!(f, "-error"),
145 }
146 }
147}
148
149#[derive(Debug)]
150struct TestInfo {
151 name: String,
152 description: String,
153 setup: String,
154 body: String,
155 cleanup: String,
156 code: Code,
157 expect: String,
158}
159
160impl TestInfo {
161 fn new(name: &str, description: &str) -> Self {
162 Self {
163 name: name.into(),
164 description: description.into(),
165 setup: String::new(),
166 body: String::new(),
167 cleanup: String::new(),
168 code: Code::Ok,
169 expect: String::new(),
170 }
171 }
172
173 fn print_failure(&self, got_code: &str, received: &str) {
174 println!("\n*** FAILED {} {}", self.name, self.description);
175 println!("Expected {} <{}>", self.code.to_string(), self.expect);
176 println!("Received {} <{}>", got_code, received);
177 }
178
179 fn print_error(&self, result: &MoltResult) {
180 println!("\n*** ERROR {} {}", self.name, self.description);
181 println!("Expected {} <{}>", self.code.to_string(), self.expect);
182
183 match result {
184 Ok(val) => println!("Received -ok <{}>", val),
185 Err(exception) => match exception.code() {
186 ResultCode::Error => println!("Received -error <{}>", exception.value()),
187 ResultCode::Return => {
188 println!("Received -return <{}>", exception.value())
189 }
190 ResultCode::Break => println!("Received -break <>"),
191 ResultCode::Continue => println!("Received -continue <>"),
192 _ => unimplemented!(),
193 },
194 }
195 }
196
197 fn print_helper_error(&self, part: &str, msg: &str) {
198 println!("\n*** ERROR (in {}) {} {}", part, self.name, self.description);
199 println!(" {}", msg);
200 }
201}
202
203pub fn test_cmd<Ctx>(interp: &mut Interp<(Ctx, TestCtx)>, argv: &[Value]) -> MoltResult {
212 check_args(1, argv, 4, 0, "name description args...")?;
214
215 let arg = argv[3].as_str();
217 if arg.starts_with('-') {
218 fancy_test(interp, argv)
219 } else {
220 simple_test(interp, argv)
221 }
222}
223
224fn simple_test<Ctx>(interp: &mut Interp<(Ctx, TestCtx)>, argv: &[Value]) -> MoltResult {
226 check_args(1, argv, 6, 6, "name description script -ok|-error result")?;
227
228 let mut info = TestInfo::new(argv[1].as_str(), argv[2].as_str());
230 info.body = argv[3].to_string();
231 info.expect = argv[5].to_string();
232
233 let code = argv[4].as_str();
234
235 info.code = if code == "-ok" {
236 Code::Ok
237 } else if code == "-error" {
238 Code::Error
239 } else {
240 incr_errors(interp);
241 info.print_helper_error("test command", &format!("invalid option: \"{}\"", code));
242
243 return molt_ok!();
244 };
245
246 run_test(interp, &info);
248 molt_ok!()
249}
250
251fn fancy_test<Ctx>(interp: &mut Interp<(Ctx, TestCtx)>, argv: &[Value]) -> MoltResult {
253 check_args(1, argv, 4, 0, "name description option value ?option value...?")?;
254
255 let mut info = TestInfo::new(argv[1].as_str(), argv[2].as_str());
257 let mut iter = argv[3..].iter();
258 loop {
259 let opt = iter.next();
260 if opt.is_none() {
261 break;
262 }
263 let opt = opt.unwrap().as_str();
264
265 let val = iter.next();
266 if val.is_none() {
267 incr_errors(interp);
268 info.print_helper_error(
269 "test command",
270 &format!("missing value for {}", opt),
271 );
272 return molt_ok!();
273 }
274 let val = val.unwrap().as_str();
275
276 match opt {
277 "-setup" => info.setup = val.to_string(),
278 "-body" => info.body = val.to_string(),
279 "-cleanup" => info.cleanup = val.to_string(),
280 "-ok" => {
281 info.code = Code::Ok;
282 info.expect = val.to_string();
283 }
284 "-error" => {
285 info.code = Code::Error;
286 info.expect = val.to_string();
287 }
288 _ => {
289 incr_errors(interp);
290 info.print_helper_error(
291 "test command",
292 &format!("invalid option: \"{}\"", val),
293 );
294 return molt_ok!();
295 }
296 }
297 }
298
299 run_test(interp, &info);
301 molt_ok!()
302}
303
304fn run_test<Ctx>(interp: &mut Interp<(Ctx, TestCtx)>, info: &TestInfo) {
306 interp.push_scope();
308
309 if let Err(exception) = interp.eval(&info.setup) {
313 if exception.code() == ResultCode::Error {
314 info.print_helper_error("-setup", exception.value().as_str());
315 }
316 }
317 let body = Value::from(&info.body);
323 let result = interp.eval_value(&body);
324
325 if let Err(exception) = interp.eval(&info.cleanup) {
327 if exception.code() == ResultCode::Error {
328 info.print_helper_error("-cleanup", exception.value().as_str());
329 }
330 }
331 interp.pop_scope();
337
338 let ctx = &mut interp.context.1;
340 ctx.num_tests += 1;
341
342 match &result {
343 Ok(out) => {
344 if info.code == Code::Ok {
345 if *out == Value::from(&info.expect) {
346 ctx.num_passed += 1;
347 } else {
348 ctx.num_failed += 1;
349 info.print_failure("-ok", &out.to_string());
350 }
351 return;
352 }
353 }
354 Err(exception) => {
355 if info.code == Code::Error {
356 if exception.value() == Value::from(&info.expect) {
357 ctx.num_passed += 1;
358 } else {
359 ctx.num_failed += 1;
360 info.print_failure("-error", exception.value().as_str());
361 }
362 return;
363 }
364 }
365 }
366 ctx.num_errors += 1;
367 info.print_error(&result);
368}
369
370fn incr_errors<Ctx>(interp: &mut Interp<(Ctx, TestCtx)>) {
372 interp.context.1.num_errors += 1;
373}