tame/tamer/src/xir/flat/test.rs

450 lines
12 KiB
Rust

// Test XIRF representation
//
// Copyright (C) 2014-2022 Ryan Specialty Group, LLC.
//
// This file is part of TAME.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Integration tests for XIRF parser.
//!
//! These tests take place within the context of the XIR parsing framework,
//! so they are one layer of abstraction away from unit tests.
use std::assert_matches::assert_matches;
use super::*;
use crate::convert::ExpectInto;
use crate::parse::{ParseError, Parsed};
use crate::span::DUMMY_SPAN;
use crate::sym::GlobalSymbolIntern;
const S: Span = DUMMY_SPAN;
const S2: Span = S.offset_add(1).unwrap();
const S3: Span = S2.offset_add(1).unwrap();
const S4: Span = S3.offset_add(1).unwrap();
#[test]
fn empty_element_self_close() {
let name = ("ns", "elem").unwrap_into();
let toks = [XirToken::Open(name, S), XirToken::Close(None, S2)].into_iter();
let sut = parse::<1>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Open(name, S, Depth(0))),
Parsed::Object(Object::Close(None, S2, Depth(0))),
]),
sut.collect(),
);
}
// Same as above test, but with balanced closing instead of self
// closing.
#[test]
fn empty_element_balanced_close() {
let name = ("ns", "openclose").unwrap_into();
let toks =
[XirToken::Open(name, S), XirToken::Close(Some(name), S2)].into_iter();
let sut = parse::<1>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Open(name, S, Depth(0))),
Parsed::Object(Object::Close(Some(name), S2, Depth(0))),
]),
sut.collect(),
);
}
// More closing tags than opening.
//
// We cannot keep the token and throw our own error because this tag may be
// part of a parent context.
#[test]
fn extra_closing_tag() {
let name = ("ns", "openclose").unwrap_into();
let toks = [
// We need an opening tag to actually begin document parsing.
XirToken::Open(name, S),
XirToken::Close(Some(name), S2),
XirToken::Close(Some(name), S3),
]
.into_iter();
let sut = parse::<1>(toks);
assert_matches!(
sut.collect::<Result<Vec<Parsed<Object>>, _>>(),
Err(ParseError::UnexpectedToken(
XirToken::Close(Some(given_name), given_span),
_
)) if given_name == name && given_span == S3
);
}
// This should never happen, but let's operate in a sane way in case it ever
// does, since that's not the user's fault (that is, we shouldn't have
// gotten to XIRF).
#[test]
fn extra_self_closing_tag() {
let name = ("ns", "openclose").unwrap_into();
let toks = [
// We need an opening tag to actually begin document parsing.
XirToken::Open(name, S),
XirToken::Close(None, S2),
XirToken::Close(None, S3),
]
.into_iter();
let sut = parse::<1>(toks);
assert_matches!(
sut.collect::<Result<Vec<Parsed<Object>>, _>>(),
Err(ParseError::UnexpectedToken(XirToken::Close(None, given_span), _))
if given_span == S3,
);
}
// Unbalanced should result in error. This does not test what happens
// _after_ the error.
#[test]
fn empty_element_unbalanced_close() {
let open_name = "open".unwrap_into();
let close_name = "unbalanced_name".unwrap_into();
let toks = [
XirToken::Open(open_name, S),
XirToken::Close(Some(close_name), S2),
]
.into_iter();
let mut sut = parse::<1>(toks);
assert_eq!(
sut.next(),
Some(Ok(Parsed::Object(Object::Open(open_name, S, Depth(0)))))
);
assert_eq!(
sut.next(),
Some(Err(ParseError::StateError(StateError::UnbalancedTag {
open: (open_name, S),
close: (close_name, S2),
})))
);
}
// Testing depth value.
#[test]
fn single_empty_child() {
let name = ("ns", "openclose").unwrap_into();
let child = "child".unwrap_into();
let toks = [
XirToken::Open(name, S),
XirToken::Open(child, S2),
XirToken::Close(None, S3),
XirToken::Close(Some(name), S4),
]
.into_iter();
let sut = parse::<2>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Open(name, S, Depth(0))),
Parsed::Object(Object::Open(child, S2, Depth(1))),
Parsed::Object(Object::Close(None, S3, Depth(1))),
Parsed::Object(Object::Close(Some(name), S4, Depth(0))),
]),
sut.collect(),
);
}
#[test]
fn depth_exceeded() {
let name = ("ns", "openclose").unwrap_into();
let exceed = "exceed".unwrap_into();
let toks = [
XirToken::Open(name, S),
// This one exceeds the max depth, ...
XirToken::Open(exceed, S2),
]
.into_iter();
// ...which is set here: MAX_DEPTH here is 1
let mut sut = parse::<1>(toks);
assert_eq!(
Some(Ok(Parsed::Object(Object::Open(name, S, Depth(0))))),
sut.next()
);
assert_eq!(
Some(Err(ParseError::StateError(StateError::MaxDepthExceeded {
open: (exceed, S2),
max: Depth(1),
}))),
sut.next()
);
}
#[test]
fn empty_element_with_attrs() {
let name = ("ns", "elem").unwrap_into();
let attr1 = "a".unwrap_into();
let attr2 = "b".unwrap_into();
let val1 = "val1".intern();
let val2 = "val2".intern();
let toks = [
XirToken::Open(name, S),
XirToken::AttrName(attr1, S2),
XirToken::AttrValue(val1, S3),
XirToken::AttrName(attr2, S3),
XirToken::AttrValue(val2, S4),
XirToken::Close(None, S4),
]
.into_iter();
let sut = parse::<2>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Open(name, S, Depth(0))),
Parsed::Incomplete,
Parsed::Object(Object::Attr(Attr::new(attr1, val1, (S2, S3)))),
Parsed::Incomplete,
Parsed::Object(Object::Attr(Attr::new(attr2, val2, (S3, S4)))),
Parsed::Object(Object::Close(None, S4, Depth(0))),
]),
sut.collect(),
);
}
#[test]
fn child_element_after_attrs() {
let name = ("ns", "elem").unwrap_into();
let child = "child".unwrap_into();
let attr = "a".unwrap_into();
let val = "val".intern();
let toks = [
XirToken::Open(name, S),
XirToken::AttrName(attr, S),
XirToken::AttrValue(val, S2),
XirToken::Open(child, S),
XirToken::Close(None, S2),
XirToken::Close(Some(name), S3),
]
.into_iter();
let sut = parse::<2>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Open(name, S, Depth(0))),
Parsed::Incomplete,
Parsed::Object(Object::Attr(Attr::new(attr, val, (S, S2)))),
Parsed::Object(Object::Open(child, S, Depth(1))),
Parsed::Object(Object::Close(None, S2, Depth(1))),
Parsed::Object(Object::Close(Some(name), S3, Depth(0))),
]),
sut.collect(),
);
}
#[test]
fn element_with_empty_sibling_children() {
let parent = "parent".unwrap_into();
let childa = "childa".unwrap_into();
let childb = "childb".unwrap_into();
let toks = [
XirToken::Open(parent, S),
XirToken::Open(childa, S2),
XirToken::Close(None, S3),
XirToken::Open(childb, S2),
XirToken::Close(None, S3),
XirToken::Close(Some(parent), S2),
]
.into_iter();
let sut = parse::<2>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Open(parent, S, Depth(0))),
Parsed::Object(Object::Open(childa, S2, Depth(1))),
Parsed::Object(Object::Close(None, S3, Depth(1))),
Parsed::Object(Object::Open(childb, S2, Depth(1))),
Parsed::Object(Object::Close(None, S3, Depth(1))),
Parsed::Object(Object::Close(Some(parent), S2, Depth(0))),
]),
sut.collect(),
);
}
// Ensures that attributes do not cause the parent context to be lost.
#[test]
fn element_with_child_with_attributes() {
let parent = "parent".unwrap_into();
let child = "child".unwrap_into();
let attr = "attr".unwrap_into();
let value = "attr value".intern();
let toks = [
XirToken::Open(parent, S),
XirToken::Open(child, S),
XirToken::AttrName(attr, S),
XirToken::AttrValue(value, S2),
XirToken::Close(None, S3),
XirToken::Close(Some(parent), S3),
]
.into_iter();
let sut = parse::<2>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Open(parent, S, Depth(0))),
Parsed::Object(Object::Open(child, S, Depth(1))),
Parsed::Incomplete,
Parsed::Object(Object::Attr(Attr::new(attr, value, (S, S2)))),
Parsed::Object(Object::Close(None, S3, Depth(1))),
Parsed::Object(Object::Close(Some(parent), S3, Depth(0))),
]),
sut.collect(),
);
}
#[test]
fn element_with_text() {
let parent = "parent".unwrap_into();
let text = "inner text".into();
let toks = [
XirToken::Open(parent, S),
XirToken::Text(text, S2),
XirToken::Close(Some(parent), S3),
]
.into_iter();
let sut = parse::<1>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Open(parent, S, Depth(0))),
Parsed::Object(Object::Text(text, S2)),
Parsed::Object(Object::Close(Some(parent), S3, Depth(0))),
]),
sut.collect(),
);
}
#[test]
fn not_accepting_state_if_element_open() {
let name = "unclosed".unwrap_into();
let toks = [XirToken::Open(name, S)].into_iter();
let mut sut = parse::<1>(toks);
assert_eq!(
Some(Ok(Parsed::Object(Object::Open(name, S, Depth(0))))),
sut.next()
);
// Element was not closed.
assert_matches!(sut.next(), Some(Err(ParseError::UnexpectedEof(..))));
}
// XML permits comment nodes before and after the document root element.
#[test]
fn comment_before_or_after_root_ok() {
let name = "root".unwrap_into();
let cstart = "start comment".intern();
let cend = "end comment".intern();
let toks = [
XirToken::Comment(cstart, S),
XirToken::Open(name, S2),
XirToken::Close(None, S3),
XirToken::Comment(cend, S4),
]
.into_iter();
let sut = parse::<1>(toks);
assert_eq!(
Ok(vec![
Parsed::Object(Object::Comment(cstart, S)),
Parsed::Object(Object::Open(name, S2, Depth(0))),
Parsed::Object(Object::Close(None, S3, Depth(0))),
Parsed::Object(Object::Comment(cend, S4)),
]),
sut.collect(),
);
}
// But there must be no content at the end of the document after the closing
// root node.
// This does not test every applicable token;
// you can easily verify the actual implementation at a glance.
//
// This is just a dead parser state,
// since it's possible for XIRF to be composed and we want to return to
// the parent parser.
#[test]
fn content_after_root_close_error() {
let name = "root".unwrap_into();
let toks = [
XirToken::Open(name, S),
XirToken::Close(None, S2),
// Document ends here
XirToken::Open(name, S3),
]
.into_iter();
let sut = parse::<1>(toks);
assert_matches!(
sut.collect(),
Result::<Vec<Parsed<Object>>, _>::Err(ParseError::UnexpectedToken(
XirToken::Open(given_name, given_span),
_)) if given_name == name && given_span == S3
);
}
// Non-comment nodes cannot appear before the opening root tag.
#[test]
fn content_before_root_open_error() {
let text = "foo".intern();
let toks = [XirToken::Text(text, S)].into_iter();
let sut = parse::<1>(toks);
assert_eq!(
Result::<Vec<Parsed<Object>>, _>::Err(ParseError::StateError(
StateError::RootOpenExpected(XirToken::Text(text, S))
)),
sut.collect()
);
}