450 lines
12 KiB
Rust
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()
|
|
);
|
|
}
|