tapconsooomer/lib.rs
1extern crate pest;
2#[macro_use]
3extern crate pest_derive;
4
5use anyhow::{anyhow, Result};
6use pest::{
7 iterators::{Pair, Pairs},
8 Parser,
9};
10use serde::Serialize;
11
12#[derive(Parser)]
13#[grammar = "tap14.pest"]
14pub struct TAPParser;
15
16/// The TAP [`Preamble`] declares the start of a TAP document.
17#[derive(Debug, Serialize)]
18pub struct Preamble<'a> {
19 /// TAP specification version. Can be any semantic version string (e.g. `14` or `14.1.3`).
20 pub version: &'a str,
21}
22
23/// The [`Plan`] tells how many tests will be run, or how many tests have run.
24#[derive(Debug, Serialize)]
25pub struct Plan<'a> {
26 /// ID of first planned test. _Should_ always start with `1`.
27 pub first: i32,
28 /// ID of last planned test. A value of `0` _should_ indicate no tests were executed.
29 pub last: i32,
30 /// Arbitrary string which _should_ indicate why the certain tests were skipped.
31 pub reason: Option<&'a str>,
32}
33
34/// The body of the TAP document.
35#[derive(Debug, Serialize)]
36pub struct Body<'a> {
37 /// List of [`Statement`]s.
38 statements: Vec<Statement<'a>>,
39}
40
41/// A [`Pragma`] provides information to the document parser/interpreter.
42///
43/// # Note
44///
45/// Due to the PEG parsing approach, pragmas have no effect.
46#[derive(Debug, Serialize)]
47pub struct Pragma<'a> {
48 /// If present, declares if the given `option` should be enabled or disabled.
49 pub flag: Option<bool>,
50 /// Pragma option identifier.
51 pub option: &'a str,
52}
53
54/// Marks an emergency exit of the test procedure.
55#[derive(Debug, Serialize)]
56pub struct BailOut<'a> {
57 /// Optional reason for bailing out of the test procedure.
58 pub reason: Option<&'a str>,
59}
60
61/// Directive keys supported by [`Directive`].
62#[derive(Debug, Serialize)]
63pub enum Key {
64 /// Test was skipped
65 Skip,
66 /// Test has a to-do.
67 Todo,
68}
69
70/// A [`Directive`] gives some meta-data about the execution of a [`Test`].
71#[derive(Debug, Serialize)]
72pub struct Directive<'a> {
73 /// A directive key, declaring the nature of this [`Directive`].
74 pub key: Key,
75 /// A reason why this test was [`Key::Skip`]ped or why it is a [`Key::Todo`].
76 pub reason: Option<&'a str>,
77}
78
79/// A [`Test`] declaring the result of some test-case.
80#[derive(Debug, Serialize)]
81pub struct Test<'a> {
82 /// Result of the test.
83 pub result: bool,
84 /// Number of the test.
85 pub number: Option<i32>,
86 /// Description of the test.
87 pub description: Option<&'a str>,
88 /// Directive detailing this tests meta-execution.
89 pub directive: Option<Directive<'a>>,
90 /// List of YAML lines detailing the test execution.
91 pub yaml: Yaml<'a>,
92}
93
94/// [`Subtest`]s provide a way to nest one TAP14 stream inside another. This may be used in a variety of ways, depending on
95/// the test harness.
96#[derive(Debug, Serialize)]
97pub struct Subtest<'a> {
98 /// Name of the subtest, declared by a comment at the start of the [`Subtest`].
99 pub name: Option<&'a str>,
100 /// The [`Plan`] of the [`Subtest`].
101 pub plan: Plan<'a>,
102 /// Main [`Body`] of the [`Subtest`].
103 pub body: Vec<Statement<'a>>,
104}
105
106/// An enumeration of all possible TAP constructs that can be part of a [`Body`].
107#[derive(Debug, Serialize)]
108pub enum Statement<'a> {
109 /// Any text not captured by another [`Statement`] variant.
110 #[serde(rename = "anything")]
111 Anything(&'a str),
112 /// A [`BailOut`] statement.
113 #[serde(rename = "bail_out")]
114 BailOut(BailOut<'a>),
115 /// A [`Pragma`] statement.
116 #[serde(rename = "pragma")]
117 Pragma(Pragma<'a>),
118 /// A [`Subtest`] statement.
119 #[serde(rename = "subtest")]
120 Subtest(Subtest<'a>),
121 /// A [`Test`] statement.
122 #[serde(rename = "test")]
123 Test(Test<'a>),
124}
125
126/// A [`Document`] represents the root of any TAP document. It's the main point of interaction for users of this API.
127#[derive(Debug, Serialize)]
128pub struct Document<'a> {
129 /// The document's preamble.
130 pub preamble: Preamble<'a>,
131 /// The document's top-level plan declaration.
132 pub plan: Plan<'a>,
133 /// The document's top-level [`Body`] as a collection of [`Statement`]s. Some [`Statement`]s, like [`Subtest`] may
134 /// declare _nested_ [`Body`]s.
135 pub body: Vec<Statement<'a>>,
136}
137
138#[derive(Debug)]
139enum DocumentContent<'a> {
140 Plan(Plan<'a>),
141 Body(Vec<Statement<'a>>),
142}
143
144#[derive(Debug)]
145enum SubtestContent<'a> {
146 Plan(Plan<'a>),
147 Statement(Statement<'a>),
148}
149
150type Yaml<'a> = Vec<&'a str>;
151
152impl<'a> Preamble<'a> {
153 fn parse(mut pairs: Pairs<'a, Rule>) -> Self {
154 Self {
155 version: pairs.next().unwrap().as_str(),
156 }
157 }
158
159 /// Parse [`Preamble`] from a `&str`.
160 ///
161 /// # Examples
162 ///
163 /// Parsing a TAP version 14 preamble may look like this:
164 ///
165 /// ```
166 /// use tapconsooomer::Preamble;
167 ///
168 /// let content = "TAP version 14";
169 /// let preamble = Preamble::parse_from_str(content).expect("Parser error");
170 /// assert_eq!(preamble.version, "14");
171 /// ```
172 ///
173 /// Semantic versioning is supported aswell:
174 ///
175 /// ```
176 /// use tapconsooomer::Preamble;
177 ///
178 /// let content = "TAP version 13.1";
179 /// let preamble = Preamble::parse_from_str(content).expect("Parser error");
180 /// assert_eq!(preamble.version, "13.1");
181 /// ```
182 pub fn parse_from_str(content: &'a str) -> Result<Self> {
183 TAPParser::parse(Rule::preamble, content)?
184 .next()
185 .map(Pair::into_inner)
186 .map(Self::parse)
187 .ok_or_else(|| anyhow!("Can't parse '{}'", content))
188 }
189}
190
191impl<'a> Plan<'a> {
192 fn parse(mut pairs: Pairs<'a, Rule>) -> Result<Self> {
193 Ok(Self {
194 first: pairs.next().unwrap().as_str().parse()?,
195 last: pairs.next().unwrap().as_str().parse()?,
196 reason: pairs.next().map(|r| r.as_str()),
197 })
198 }
199
200 /// Parse [`Plan`] from a `&str`.
201 ///
202 /// # Examples
203 ///
204 /// Parsing a TAP plan may look like this:
205 ///
206 /// ```
207 /// use tapconsooomer::Plan;
208 ///
209 /// let content = "1..5 # TODO: Test 3 may fail";
210 /// let plan = Plan::parse_from_str(content).expect("Parser error");
211 /// assert_eq!(plan.first, 1);
212 /// assert_eq!(plan.last, 5);
213 /// assert_eq!(plan.reason, Some("TODO: Test 3 may fail"));
214 /// ```
215 ///
216 /// Note, [`Plan::reason`] is optional:
217 ///
218 /// ```
219 /// use tapconsooomer::Plan;
220 ///
221 /// let content = "1..5";
222 /// let plan = Plan::parse_from_str(content).expect("Parser error");
223 /// assert_eq!(plan.first, 1);
224 /// assert_eq!(plan.last, 5);
225 /// assert_eq!(plan.reason, None);
226 /// ```
227 pub fn parse_from_str(content: &'a str) -> Result<Self> {
228 TAPParser::parse(Rule::plan, content)?
229 .next()
230 .map(Pair::into_inner)
231 .map(Self::parse)
232 .ok_or_else(|| anyhow!("Can't parse '{}'", content))?
233 }
234}
235
236impl<'a> Directive<'a> {
237 fn parse(mut pairs: Pairs<'a, Rule>) -> Result<Self> {
238 let key = pairs.next().unwrap().as_str().to_lowercase();
239 Ok(Self {
240 key: match key.as_str() {
241 "skip" => Ok(Key::Skip),
242 "todo" => Ok(Key::Todo),
243 _ => Err(anyhow!("Directive key '{}' must be 'skip' or 'todo'", key)),
244 }?,
245 reason: pairs.next().map(|p| p.as_str()),
246 })
247 }
248
249 /// Parse [`Directive`] from a `&str`.
250 ///
251 /// # Examples
252 ///
253 /// Parsing a TAP directive may look like this:
254 ///
255 /// ```
256 /// use tapconsooomer::Directive;
257 /// use tapconsooomer::Key;
258 ///
259 /// let content = "# SKIP hardware requirements not met";
260 /// let directive = Directive::parse_from_str(content).expect("Parser error");
261 /// assert!(matches!(directive.key, Key::Skip));
262 /// assert_eq!(directive.reason, Some("hardware requirements not met"));
263 /// ```
264 ///
265 /// Note, [`Directive::reason`] is optional:
266 ///
267 /// ```
268 /// use tapconsooomer::Directive;
269 /// use tapconsooomer::Key;
270 ///
271 /// let content = "# TODO";
272 /// let directive = Directive::parse_from_str(content).expect("Parser error");
273 /// assert!(matches!(directive.key, Key::Todo));
274 /// assert_eq!(directive.reason, None);
275 /// ```
276 pub fn parse_from_str(content: &'a str) -> Result<Self> {
277 TAPParser::parse(Rule::directive, content)?
278 .next()
279 .map(Pair::into_inner)
280 .map(Self::parse)
281 .ok_or_else(|| anyhow!("Can't parse '{}'", content))?
282 }
283}
284
285impl<'a> Test<'a> {
286 fn parse(mut pairs: Pairs<'a, Rule>) -> Result<Self> {
287 let pair = pairs.next().unwrap();
288 let result = match pair.as_str().to_lowercase().as_str() {
289 "ok" => Ok(true),
290 "not ok" => Ok(false),
291 _ => Err(anyhow!(
292 "Result '{}' must be 'ok' or 'not ok'",
293 pair.as_str()
294 )),
295 }?;
296 let mut number: Option<i32> = None;
297 let mut description = None;
298 let mut directive = None;
299 let mut yaml = Vec::new();
300 for pair in pairs {
301 match pair.as_rule() {
302 Rule::number => number = pair.as_str().parse::<i32>().ok(),
303 Rule::description => description = Some(pair.as_str()),
304 Rule::directive => directive = Directive::parse(pair.into_inner()).ok(),
305 Rule::yaml_block => {
306 yaml.append(&mut { pair.into_inner().map(|p| p.as_str()).collect() })
307 }
308 _ => unreachable!(),
309 };
310 }
311 Ok(Self {
312 result,
313 number,
314 description,
315 directive,
316 yaml,
317 })
318 }
319
320 /// Parse [`Test`] from a `&str`.
321 ///
322 /// # Examples
323 ///
324 /// Parsing a TAP test may look like this:
325 ///
326 /// ```
327 /// use tapconsooomer::Test;
328 ///
329 /// let content = "not ok 1 - foo()";
330 /// let test = Test::parse_from_str(content).expect("Parser error");
331 /// assert_eq!(test.result, false);
332 /// assert_eq!(test.number, Some(1));
333 /// assert_eq!(test.description, Some("foo()"));
334 /// assert_eq!(test.directive.is_none(), true);
335 /// assert_eq!(test.yaml.len(), 0);
336 /// ```
337 ///
338 /// Note, many attributes of a TAP test are optional. A minimal TAP test may look like this:
339 ///
340 /// ```
341 /// use tapconsooomer::Test;
342 ///
343 /// let content = "ok";
344 /// let test = Test::parse_from_str(content).expect("Parser error");
345 /// assert_eq!(test.result, true);
346 /// assert_eq!(test.number, None);
347 /// assert_eq!(test.description, None);
348 /// assert_eq!(test.directive.is_none(), true);
349 /// assert_eq!(test.yaml.len(), 0);
350 /// ```
351 ///
352 /// TAP tests may also optionally contain a YAML block. While no parsing of actual YAML syntax is performed, the
353 /// parser captures each line inside the YAML block in a [`Vec`]:
354 ///
355 /// ```
356 /// use tapconsooomer::Test;
357 ///
358 /// let content = concat!(
359 /// "not ok 2 - bar()\n",
360 /// " ---\n",
361 /// " message: invalid input\n",
362 /// " status: failed\n",
363 /// " ...\n",
364 /// );
365 /// let test = Test::parse_from_str(content).expect("Parser error");
366 /// assert_eq!(test.result, false);
367 /// assert_eq!(test.number, Some(2));
368 /// assert_eq!(test.description, Some("bar()"));
369 /// assert_eq!(test.directive.is_none(), true);
370 /// assert_eq!(test.yaml.len(), 2);
371 /// assert_eq!(test.yaml, ["message: invalid input", "status: failed"])
372 /// ```
373 pub fn parse_from_str(content: &'a str) -> Result<Self> {
374 TAPParser::parse(Rule::test, content)?
375 .next()
376 .map(Pair::into_inner)
377 .map(Self::parse)
378 .ok_or_else(|| anyhow!("Can't parse '{}'", content))?
379 }
380}
381
382impl<'a> BailOut<'a> {
383 fn parse(mut pairs: Pairs<'a, Rule>) -> Result<Self> {
384 Ok(Self {
385 reason: pairs.next().map(|p| p.as_str()),
386 })
387 }
388
389 /// Parse [`BailOut`] from a `&str`.
390 ///
391 /// # Examples
392 ///
393 /// Parsing a TAP bail-out may look like this:
394 ///
395 /// ```
396 /// use tapconsooomer::BailOut;
397 ///
398 /// let content = "Bail out! Hardware overheating";
399 /// let bail_out = BailOut::parse_from_str(content).expect("Parser error");
400 /// assert_eq!(bail_out.reason, Some("Hardware overheating"));
401 /// ```
402 ///
403 /// Note, [`BailOut::reason`] is optional:
404 ///
405 /// ```
406 /// use tapconsooomer::BailOut;
407 ///
408 /// let content = "Bail out!";
409 /// let bail_out = BailOut::parse_from_str(content).expect("Parser error");
410 /// assert_eq!(bail_out.reason.is_none(), true);
411 /// ```
412 pub fn parse_from_str(content: &'a str) -> Result<Self> {
413 TAPParser::parse(Rule::bail_out, content)?
414 .next()
415 .map(Pair::into_inner)
416 .map(Self::parse)
417 .ok_or_else(|| anyhow!("Can't parse '{}'", content))?
418 }
419}
420
421impl<'a> Pragma<'a> {
422 pub fn parse(mut pairs: Pairs<'a, Rule>) -> Result<Self> {
423 let mut pair = pairs.next().unwrap();
424 let flag = match pair.as_rule() {
425 Rule::flag => {
426 println!("{}", pair.as_str());
427 let result = match pair.as_str() {
428 "+" => Some(true),
429 "-" => Some(false),
430 _ => unreachable!(),
431 };
432 pair = pairs.next().unwrap();
433 result
434 }
435 _ => None,
436 };
437 Ok(Self {
438 flag,
439 option: pair.as_str(),
440 })
441 }
442
443 /// Parse [`Pragma`] from a `&str`.
444 ///
445 /// # Examples
446 ///
447 /// Parsing a TAP pragma may look like this:
448 ///
449 /// ```
450 /// use tapconsooomer::Pragma;
451 ///
452 /// let content = "pragma +strict";
453 /// let pragma = Pragma::parse_from_str(content).expect("Parser error");
454 /// assert_eq!(pragma.flag, Some(true));
455 /// assert_eq!(pragma.option, "strict");
456 /// ```
457 ///
458 /// Note, [`Pragma::flag`] is optional:
459 ///
460 /// ```
461 /// use tapconsooomer::Pragma;
462 ///
463 /// let content = "pragma foo";
464 /// let pragma = Pragma::parse_from_str(content).expect("Parser error");
465 /// assert_eq!(pragma.flag.is_none(), true);
466 /// assert_eq!(pragma.option, "foo");
467 /// ```
468 pub fn parse_from_str(content: &'a str) -> Result<Self> {
469 TAPParser::parse(Rule::pragma, content)?
470 .next()
471 .map(Pair::into_inner)
472 .map(Self::parse)
473 .ok_or_else(|| anyhow!("Can't parse '{}'", content))?
474 }
475}
476
477impl<'a> Subtest<'a> {
478 pub fn parse(mut pairs: Pairs<'a, Rule>) -> Result<Self> {
479 let pair = pairs.next().unwrap();
480 let name = match pair.as_rule() {
481 Rule::name => Some(pair.as_str()),
482 _ => None,
483 };
484
485 let mut contents = vec![]; /* FIXME: There has to be a better way to split plan and body! */
486
487 /* Consume first pair if not consumed by 'name'. */
488 if name.is_none() {
489 contents.push(match pair.as_rule() {
490 Rule::plan => Plan::parse(pair.into_inner()).map(SubtestContent::Plan),
491 _ => Statement::parse(pair).map(SubtestContent::Statement),
492 }?)
493 }
494
495 /* Now consume the rest of the pairs. */
496 let mut rest: Vec<SubtestContent> = pairs
497 .map(|p| match p.as_rule() {
498 Rule::plan => Plan::parse(p.into_inner()).map(SubtestContent::Plan),
499 _ => Statement::parse(p).map(SubtestContent::Statement),
500 })
501 .collect::<Result<Vec<SubtestContent>>>()?;
502 contents.append(&mut rest);
503
504 let mut p = None;
505 let mut i = 0;
506 while i < contents.len() {
507 /* TODO: Use drain_filter once stable. */
508 if matches!(contents[i], SubtestContent::Plan(_)) {
509 p = Some(match contents.remove(i) {
510 SubtestContent::Plan(p) => p,
511 SubtestContent::Statement(_) => unreachable!(),
512 })
513 }
514 i += 1;
515 }
516 let plan = p.unwrap();
517 let body = contents
518 .into_iter()
519 .filter_map(|s| match s {
520 SubtestContent::Plan(_) => None,
521 SubtestContent::Statement(s) => Some(s),
522 })
523 .collect();
524
525 Ok(Self { name, plan, body })
526 }
527
528 /// Parse [`Subtest`] from a `&str`.
529 ///
530 /// # Examples
531 ///
532 /// Parsing a TAP subtest may look like this:
533 ///
534 /// ```
535 /// use tapconsooomer::Subtest;
536 ///
537 /// let content = concat!(
538 /// "# Subtest: foo\n",
539 /// " 1..2\n",
540 /// " ok 1 - bar\n",
541 /// " ok 2 - tuna\n",
542 /// );
543 /// let subtest = Subtest::parse_from_str(content).expect("Parser error");
544 /// assert_eq!(subtest.name, Some("foo"));
545 /// assert_eq!(subtest.plan.first, 1);
546 /// assert_eq!(subtest.plan.last, 2);
547 /// assert_eq!(subtest.body.len(), 2);
548 /// ```
549 ///
550 /// Note, the comment declaring [`Subtest::name`] is optional:
551 ///
552 /// ```
553 /// use tapconsooomer::Subtest;
554 ///
555 /// let content = concat!(
556 /// " 1..4\n",
557 /// " ok 1 - foo\n",
558 /// " ok 2 - bar\n",
559 /// " ok 3 - foobar\n",
560 /// " ok 4 - catfood\n",
561 /// );
562 /// let subtest = Subtest::parse_from_str(content).expect("Parser error");
563 /// assert_eq!(subtest.name.is_none(), true);
564 /// assert_eq!(subtest.plan.first, 1);
565 /// assert_eq!(subtest.plan.last, 4);
566 /// assert_eq!(subtest.body.len(), 4);
567 /// ```
568 ///
569 /// So is the order in which [`Plan`] and [`Body`] are declared:
570 ///
571 /// ```
572 /// use tapconsooomer::Subtest;
573 ///
574 /// let content = concat!(
575 /// " ok 1 - hello world\n",
576 /// " 1..1\n",
577 /// );
578 /// let subtest = Subtest::parse_from_str(content).expect("Parser error");
579 /// assert_eq!(subtest.name.is_none(), true);
580 /// assert_eq!(subtest.plan.first, 1);
581 /// assert_eq!(subtest.plan.last, 1);
582 /// assert_eq!(subtest.body.len(), 1);
583 /// ```
584 pub fn parse_from_str(content: &'a str) -> Result<Self> {
585 TAPParser::parse(Rule::subtest, content)?
586 .next()
587 .map(Pair::into_inner)
588 .map(Self::parse)
589 .ok_or_else(|| anyhow!("Can't parse '{}'", content))?
590 }
591}
592
593impl<'a> Statement<'a> {
594 pub fn parse(pair: Pair<'a, Rule>) -> Result<Self> {
595 match pair.as_rule() {
596 Rule::test => Ok(Self::Test(Test::parse(pair.into_inner())?)),
597 Rule::bail_out => Ok(Self::BailOut(BailOut::parse(pair.into_inner())?)),
598 Rule::pragma => Ok(Self::Pragma(Pragma::parse(pair.into_inner())?)),
599 Rule::subtest => Ok(Self::Subtest(Subtest::parse(pair.into_inner())?)),
600 Rule::anything => Ok(Self::Anything(pair.as_str())),
601 _ => unreachable!(),
602 }
603 }
604
605 /// Parse [`Statement`] from a `&str`.
606 ///
607 /// # Examples
608 ///
609 /// Parsing a TAP statement may look like this:
610 ///
611 /// ```
612 /// use tapconsooomer::Statement;
613 ///
614 /// let test = "ok 1 - foo::bar()";
615 /// let stmt = Statement::parse_from_str(test).expect("Parser error");
616 /// assert!(matches!(stmt, Statement::Test(..)));
617 ///
618 /// let anything = "hello world";
619 /// let stmt = Statement::parse_from_str(anything).expect("Parser error");
620 /// assert!(matches!(stmt, Statement::Anything(..)));
621 ///
622 /// let bail_out = "Bail Out!";
623 /// let stmt = Statement::parse_from_str(bail_out).expect("Parser error");
624 /// assert!(matches!(stmt, Statement::BailOut(..)));
625 ///
626 /// let pragma = "pragma -strict";
627 /// let stmt = Statement::parse_from_str(pragma).expect("Parser error");
628 /// assert!(matches!(stmt, Statement::Pragma(..)));
629 ///
630 /// let subtest = concat!(
631 /// "# Subtest: foo\n",
632 /// " 1..0 # skipped\n",
633 /// );
634 /// let stmt = Statement::parse_from_str(subtest).expect("Parser error");
635 /// assert!(matches!(stmt, Statement::Subtest(..)));
636 /// ```
637 pub fn parse_from_str(content: &'a str) -> Result<Self> {
638 TAPParser::parse(Rule::statement, content)?
639 .next()
640 .map(Self::parse)
641 .ok_or_else(|| anyhow!("Can't parse '{}'", content))?
642 }
643}
644
645impl<'a> DocumentContent<'a> {
646 fn parse(pair: Pair<'a, Rule>) -> Result<Self> {
647 Ok(match pair.as_rule() {
648 Rule::plan => Self::Plan(Plan::parse(pair.into_inner())?),
649 Rule::body => {
650 let statements: Result<Vec<_>> = pair.into_inner().map(Statement::parse).collect();
651 Self::Body(statements?)
652 }
653 _ => unreachable!(),
654 })
655 }
656}
657
658impl<'a> Document<'a> {
659 pub fn parse(mut pairs: Pairs<'a, Rule>) -> Result<Self> {
660 let preamble = Preamble::parse(pairs.next().unwrap().into_inner());
661
662 let content1 = DocumentContent::parse(pairs.next().unwrap())?;
663 let content2 = DocumentContent::parse(pairs.next().unwrap())?;
664 let (plan, body) = match (content1, content2) {
665 (DocumentContent::Plan(p), DocumentContent::Body(b)) => (p, b),
666 (DocumentContent::Body(b), DocumentContent::Plan(p)) => (p, b),
667 _ => unreachable!(),
668 };
669
670 Ok(Self {
671 preamble,
672 plan,
673 body,
674 })
675 }
676
677 /// Parse [`Document`] from a `&str`.
678 ///
679 /// # Examples
680 ///
681 /// Parsing a TAP document may look like this:
682 ///
683 /// ```
684 /// use tapconsooomer::Document;
685 ///
686 /// let content = concat!(
687 /// "TAP version 14\n",
688 /// "1..1\n",
689 /// "ok 1 - foo()\n",
690 /// );
691 /// let doc = Document::parse_from_str(content).expect("Parser error");
692 /// assert_eq!(doc.preamble.version, "14");
693 /// assert_eq!(doc.body.len(), 1);
694 /// ```
695 ///
696 /// The order in which [`Body`] and [`Plan`] are declared is unimportant:
697 ///
698 /// ```
699 /// use tapconsooomer::Document;
700 ///
701 /// let content = concat!(
702 /// "TAP version 14\n",
703 /// "ok 1 - foo()\n",
704 /// "ok 2 - bar()\n",
705 /// "1..2\n",
706 /// );
707 /// let doc = Document::parse_from_str(content).expect("Parser error");
708 /// assert_eq!(doc.preamble.version, "14");
709 /// assert_eq!(doc.body.len(), 2);
710 /// ```
711 pub fn parse_from_str(content: &'a str) -> Result<Self> {
712 TAPParser::parse(Rule::document, content)?
713 .next()
714 .map(Pair::into_inner)
715 .map(Self::parse)
716 .ok_or_else(|| anyhow!("Can't parse '{}'", content))?
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use pest::consumes_to;
723 use pest::parses_to;
724 use std::fs;
725
726 use super::*;
727
728 #[test]
729 fn test_version() {
730 parses_to! {
731 parser: TAPParser,
732 input : "TAP version 14",
733 rule: Rule::preamble,
734 tokens: [
735 preamble(0, 14, [
736 version(12, 14)
737 ])
738 ]
739 }
740 }
741
742 #[test]
743 fn test_version_mixed_case() {
744 parses_to! {
745 parser: TAPParser,
746 input : "tAp VeRsIoN 123",
747 rule: Rule::preamble,
748 tokens: [
749 preamble(0, 15, [
750 version(12, 15)
751 ])
752 ]
753 }
754 }
755
756 #[test]
757 fn test_plan() {
758 parses_to! {
759 parser: TAPParser,
760 input : "1..2",
761 rule: Rule::plan,
762 tokens: [
763 plan(0, 4, [
764 first(0, 1), last(3, 4)
765 ])
766 ]
767 }
768 }
769
770 #[test]
771 fn test_plan_with_reason() {
772 parses_to! {
773 parser: TAPParser,
774 input : "1..20 # generated",
775 rule: Rule::plan,
776 tokens: [
777 plan(0, 17, [
778 first(0, 1), last(3, 5), reason(8, 17)
779 ])
780 ]
781 }
782 }
783
784 #[test]
785 fn test_ok_plain() {
786 parses_to! {
787 parser: TAPParser,
788 input : "ok",
789 rule: Rule::test,
790 tokens: [
791 test(0, 2, [
792 result(0, 2)
793 ])
794 ]
795 }
796 }
797
798 #[test]
799 fn test_not_ok_plain() {
800 parses_to! {
801 parser: TAPParser,
802 input : "not ok",
803 rule: Rule::test,
804 tokens: [
805 test(0, 6, [
806 result(0, 6)
807 ])
808 ]
809 }
810 }
811
812 #[test]
813 fn test_not_ok_plain_mixed_case() {
814 parses_to! {
815 parser: TAPParser,
816 input : "nOt Ok",
817 rule: Rule::test,
818 tokens: [
819 test(0, 6, [
820 result(0, 6)
821 ])
822 ]
823 }
824 }
825
826 #[test]
827 fn test_ok_with_number() {
828 parses_to! {
829 parser: TAPParser,
830 input : "ok 123",
831 rule: Rule::test,
832 tokens: [
833 test(0, 6, [
834 result(0, 2), number(3, 6)
835 ])
836 ]
837 }
838 }
839
840 #[test]
841 fn test_ok_with_description() {
842 parses_to! {
843 parser: TAPParser,
844 input : "ok - hello world",
845 rule: Rule::test,
846 tokens: [
847 test(0, 16, [
848 result(0, 2), description(5, 16)
849 ])
850 ]
851 }
852 }
853
854 #[test]
855 fn test_not_ok_with_description() {
856 parses_to! {
857 parser: TAPParser,
858 input : "not ok - hello world",
859 rule: Rule::test,
860 tokens: [
861 test(0, 20, [
862 result(0, 6), description(9, 20)
863 ])
864 ]
865 }
866 }
867
868 #[test]
869 fn test_ok_with_description_no_dash() {
870 parses_to! {
871 parser: TAPParser,
872 input : "ok hello world",
873 rule: Rule::test,
874 tokens: [
875 test(0, 14, [
876 result(0, 2), description(3, 14)
877 ])
878 ]
879 }
880 }
881
882 #[test]
883 fn test_ok_with_directive_skip() {
884 parses_to! {
885 parser: TAPParser,
886 input : "ok # skip",
887 rule: Rule::test,
888 tokens: [
889 test(0, 9, [
890 result(0, 2), directive(3, 9, [
891 key(5, 9)
892 ])
893 ])
894 ]
895 }
896 }
897
898 #[test]
899 fn test_not_ok_with_directive_todo_reason() {
900 parses_to! {
901 parser: TAPParser,
902 input : "not ok # todo this is a reason",
903 rule: Rule::test,
904 tokens: [
905 test(0, 30, [
906 result(0, 6), directive(7, 30, [
907 key(9, 13), reason(14, 30)
908 ])
909 ])
910 ]
911 }
912 }
913
914 #[test]
915 fn test_not_ok_with_number_directive_skip_reason() {
916 parses_to! {
917 parser: TAPParser,
918 input : "not ok 42 # todo this is a reason",
919 rule: Rule::test,
920 tokens: [
921 test(0, 33, [
922 result(0, 6), number(7, 9), directive(10, 33, [
923 key(12, 16), reason(17, 33)
924 ])
925 ])
926 ]
927 }
928 }
929
930 #[test]
931 fn test_ok_with_number_description_directive_skip_reason() {
932 parses_to! {
933 parser: TAPParser,
934 input : "ok 1 - hello world # skip this is a reason",
935 rule: Rule::test,
936 tokens: [
937 test(0, 42, [
938 result(0, 2), number(3, 4), description(7, 19), directive(19, 42, [
939 key(21, 25),
940 reason(26, 42)
941 ])
942 ])
943 ]
944 }
945 }
946
947 #[test]
948 fn test_bail_out() {
949 parses_to! {
950 parser: TAPParser,
951 input : "bail out!",
952 rule: Rule::bail_out,
953 tokens: [
954 bail_out(0, 9)
955 ]
956 }
957 }
958
959 #[test]
960 fn test_bail_out_mixed_case() {
961 parses_to! {
962 parser: TAPParser,
963 input : "BaIl OuT!",
964 rule: Rule::bail_out,
965 tokens: [
966 bail_out(0, 9)
967 ]
968 }
969 }
970
971 #[test]
972 fn test_bail_out_with_reason() {
973 parses_to! {
974 parser: TAPParser,
975 input : "bail out! something went terribly wrong",
976 rule: Rule::bail_out,
977 tokens: [
978 bail_out(0, 39, [
979 reason(10, 39)
980 ])
981 ]
982 }
983 }
984
985 #[test]
986 fn test_pragma() {
987 parses_to! {
988 parser: TAPParser,
989 input : "pragma -strict",
990 rule: Rule::pragma,
991 tokens: [
992 pragma(0, 14, [
993 flag(7, 8), option(8, 14)
994 ])
995 ]
996 }
997 }
998
999 #[test]
1000 fn test_pragma_mixed_case() {
1001 parses_to! {
1002 parser: TAPParser,
1003 input : "pRaGmA +strict",
1004 rule: Rule::pragma,
1005 tokens: [
1006 pragma(0, 14, [
1007 flag(7, 8), option(8, 14)
1008 ])
1009 ]
1010 }
1011 }
1012
1013 #[test]
1014 fn test_pragma_no_flag() {
1015 parses_to! {
1016 parser: TAPParser,
1017 input : "pragma allow_anything",
1018 rule: Rule::pragma,
1019 tokens: [
1020 pragma(0, 21, [
1021 option(7, 21)
1022 ])
1023 ]
1024 }
1025 }
1026
1027 #[test]
1028 fn test_subtest_with_empty_declaration() {
1029 parses_to! {
1030 parser: TAPParser,
1031 input : r#"# subtest
1032 1..20"#,
1033 rule: Rule::subtest,
1034 tokens: [
1035 subtest(0, 23, [
1036 plan(18, 23, [
1037 first(18, 19), last(21, 23)
1038 ])
1039 ])
1040 ]
1041 }
1042 }
1043
1044 #[test]
1045 fn test_subtest_with_declaration() {
1046 parses_to! {
1047 parser: TAPParser,
1048 input : concat!(
1049 "# Subtest: This is a subtest\n",
1050 " 1..20"
1051 ),
1052 rule: Rule::subtest,
1053 tokens: [
1054 subtest(0, 38, [
1055 name(11, 28),
1056 plan(33, 38, [
1057 first(33, 34), last(36, 38)
1058 ])
1059 ])
1060 ]
1061 }
1062 }
1063
1064 #[test]
1065 fn test_subtest_without_declaration() {
1066 parses_to! {
1067 parser: TAPParser,
1068 input : " 1..20",
1069 rule: Rule::subtest,
1070 tokens: [
1071 subtest(0, 9, [
1072 plan(4, 9, [
1073 first(4, 5), last(7, 9)
1074 ])
1075 ])
1076 ]
1077 }
1078 }
1079
1080 #[test]
1081 fn test_subtest_with_plan_body() {
1082 parses_to! {
1083 parser: TAPParser,
1084 input : concat!(
1085 " 1..21\n",
1086 " ok 1 - hello world\n",
1087 ),
1088 rule: Rule::subtest,
1089 tokens: [
1090 subtest(0, 33, [
1091 plan(4, 9, [
1092 first(4, 5), last(7, 9)
1093 ]),
1094 test(14, 32, [
1095 result(14, 16), number(17, 18), description(21, 32)
1096 ])
1097 ])
1098 ]
1099 }
1100 }
1101
1102 #[test]
1103 fn test_subtest_with_body_plan() {
1104 parses_to! {
1105 parser: TAPParser,
1106 input : concat!(
1107 " ok 1 - hello world\n",
1108 " 1..21\n",
1109 ),
1110 rule: Rule::subtest,
1111 tokens: [
1112 subtest(0, 32, [
1113 test(4, 22, [
1114 result(4, 6), number(7, 8), description(11, 22)
1115 ]),
1116 plan(27, 32, [
1117 first(27, 28), last(30, 32)
1118 ])
1119 ])
1120 ]
1121 }
1122 }
1123
1124 #[test]
1125 fn test_comment() {
1126 parses_to! {
1127 parser: TAPParser,
1128 input : "# this is a comment",
1129 rule: Rule::COMMENT,
1130 tokens: [
1131 ]
1132 }
1133 }
1134
1135 #[test]
1136 fn test_yaml_block() {
1137 parses_to! {
1138 parser: TAPParser,
1139 input : concat!(
1140 " ---\n",
1141 " YAML line 1\n",
1142 " YAML line 2\n",
1143 " YAML line 3\n",
1144 " ok 1 - this is considered YAML\n",
1145 " \r\n",
1146 " 0..1 # even this is YAML\n",
1147 " ...\n",
1148 ),
1149 rule: Rule::yaml_block,
1150 tokens: [
1151 yaml_block(0, 123, [
1152 yaml(8, 19),
1153 yaml(22, 33),
1154 yaml(36, 47),
1155 yaml(50, 80),
1156 yaml(93, 117),
1157 ])
1158 ]
1159 }
1160 }
1161
1162 #[test]
1163 fn test_common() {
1164 let contents = fs::read_to_string("examples/common.tap").expect("Failed to read file");
1165
1166 parses_to! {
1167 parser: TAPParser,
1168 input : &contents,
1169 rule: Rule::document,
1170 tokens: [
1171 document(0, 318, [
1172 preamble(0, 14, [
1173 version(12, 14)
1174 ]),
1175 plan(15, 19, [
1176 first(15, 16), last(18, 19)
1177 ]),
1178 body(19, 318, [
1179 test(93, 131, [
1180 result(93, 95),
1181 number(96, 97),
1182 description(100, 122),
1183 directive(122, 131, [
1184 key(123, 127), reason(128, 131)
1185 ])
1186 ]),
1187 test(132, 171, [
1188 result(132, 134),
1189 number(135, 136),
1190 description(139, 158),
1191 directive(158, 171, [
1192 key(160, 164), reason(165, 171)
1193 ])
1194 ]),
1195 test(172, 209, [
1196 result(172, 174), number(175, 176), description(179, 200)
1197 ]),
1198 test(210, 252, [
1199 result(210, 212), number(213, 214), description(217, 252)
1200 ]),
1201 test(253, 294, [
1202 result(253, 255), number(256, 257), description(260, 294)
1203 ]),
1204 test(295, 317, [
1205 result(295, 297), number(298, 299), description(302, 317)
1206 ]),
1207 ])
1208 ])
1209 ]
1210 }
1211 }
1212
1213 #[test]
1214 fn test_cascading() {
1215 let contents = fs::read_to_string("examples/cascading.tap").expect("Failed to read file");
1216
1217 parses_to! {
1218 parser: TAPParser,
1219 input : &contents,
1220 rule: Rule::document,
1221 tokens: [
1222 document(0, 300, [
1223 preamble(0, 14, [
1224 version(12, 14)
1225 ]),
1226 plan(15, 26, [
1227 first(15, 16), last(18, 19), reason(22, 26)
1228 ]),
1229 body(26, 300, [
1230 test(27, 45, [
1231 result(27, 29), number(30, 31), description(34, 45)
1232 ]),
1233 subtest(46, 295, [
1234 name(57, 74),
1235 plan(77, 89, [
1236 first(77, 78), last(80, 81), reason(84, 89)
1237 ]),
1238 subtest(92, 273, [
1239 plan(94, 106, [
1240 first(94, 95), last(97, 98), reason(101, 106)
1241 ]),
1242 test(111, 130, [
1243 result(111, 113), number(114, 115), description(118, 130)
1244 ]),
1245 anything(135, 236),
1246 test(241, 272, [
1247 result(241, 247),
1248 number(248, 249),
1249 directive(250, 272, [
1250 key(252, 256),
1251 reason(257, 272)
1252 ])
1253 ])
1254 ]),
1255 test(275, 294, [
1256 result(275, 277), number(278, 279), description(282, 294)
1257 ])
1258 ]),
1259 test(295, 299, [
1260 result(295, 297), number(298, 299),
1261 ]),
1262 ])
1263 ])
1264 ]
1265 }
1266 }
1267
1268 #[test]
1269 fn test_yaml() {
1270 let contents = fs::read_to_string("examples/yaml.tap").expect("Failed to read file");
1271
1272 parses_to! {
1273 parser: TAPParser,
1274 input : &contents,
1275 rule: Rule::document,
1276 tokens: [
1277 document(0, 534, [
1278 preamble(0, 14, [
1279 version(12, 14)
1280 ]),
1281 plan(15, 31, [
1282 first(15, 16), last(18, 19), reason(22, 31)
1283 ]),
1284 body(31, 534, [
1285 test(32, 344, [
1286 result(32, 38),
1287 number(39, 40),
1288 description(43, 58),
1289 yaml_block(59, 344, [
1290 yaml(67, 136),
1291 yaml(139, 153),
1292 yaml(156, 162),
1293 yaml(165, 198),
1294 yaml(201, 213),
1295 yaml(217, 224),
1296 yaml(227, 260),
1297 yaml(263, 289),
1298 yaml(292, 295),
1299 yaml(298, 324),
1300 yaml(327, 338),
1301 ])
1302 ]),
1303 test(345, 366, [
1304 result(345, 347), number(348, 349), description(352, 366)
1305 ]),
1306 subtest(367, 534, [
1307 name(378, 402),
1308 plan(405, 420, [
1309 first(405, 406), last(408, 409), reason(412, 420)
1310 ]),
1311 test(423, 519, [
1312 result(423, 425),
1313 number(426, 427),
1314 description(430, 444),
1315 yaml_block(445, 519, [
1316 yaml(461, 509)
1317 ]),
1318 ]),
1319 test(522, 533, [
1320 result(522, 524), number(525, 526), description(529, 533)
1321 ])
1322 ])
1323 ])
1324 ])
1325 ]
1326 }
1327 }
1328}