tame/tamer/src/obj/xmlo/reader/test.rs

757 lines
21 KiB
Rust

// Tests xmlo object file reader
//
// Copyright (C) 2014-2023 Ryan Specialty, 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/>.
use std::assert_matches::assert_matches;
use super::*;
use crate::{
convert::ExpectInto,
num::Dtype,
obj::xmlo::SymType,
parse::{ParseError, ParseState, Parsed},
span::{dummy::*, Span},
sym::GlobalSymbolIntern,
xir::{
flat::{
test::{attr, close, close_empty, open},
Depth, XirfToken as Xirf,
},
QName,
},
};
type Sut = XmloReader;
use Parsed::{Incomplete, Object as O};
use XmloToken::*;
#[test]
fn fails_on_invalid_root() {
let tok = open("not-a-valid-package-node", S1, Depth(0));
let mut sut = Sut::parse([tok.clone()].into_iter());
assert_matches!(
sut.next(),
Some(Err(ParseError::StateError(XmloError::UnexpectedRoot(etok)))) if etok == tok
);
}
fn common_parses_package_attrs(package: QName) {
let name = "/pkgroot".into();
let relroot = "../../".into();
let elig = "elig-class-yields".into();
#[rustfmt::skip]
let toks = [
open(package, S1, Depth(0)),
attr("name", name, (S2, S3)),
attr("__rootpath", relroot, (S2, S3)),
attr("program", crate::sym::st::raw::L_TRUE, (S3, S4)),
attr(("preproc", "elig-class-yields"), elig, (S3, S4)),
close(Some(package), S2, Depth(0)),
]
.into_iter();
let sut = Sut::parse(toks);
assert_eq!(
#[rustfmt::skip]
Ok(vec![
O(PkgStart(S1)),
O(PkgName(SPair(name, S3))),
O(PkgRootPath(SPair(relroot, S3))),
// Span for the program flag is the attr name,
// rather than the value,
// since the value is just a boolean and does not provide as
// useful of context.
O(PkgProgramFlag(S3)),
O(PkgEligClassYields(SPair(elig, S4))),
Incomplete,
]),
sut.collect(),
);
}
// The linker does not [yet] parse namespaces.
#[test]
fn parses_package_attrs_without_ns_prefix() {
common_parses_package_attrs("package".unwrap_into());
}
// The linker does not [yet] parse namespaces.
#[test]
fn parses_package_attrs_with_ns_prefix() {
common_parses_package_attrs(("lv", "package").unwrap_into());
}
// For compatibility with XSLT-based compiler.
#[test]
fn adds_missing_leading_slash_to_canonical_name() {
let name = "needs/leading".into();
#[rustfmt::skip]
let toks = [
open(QN_PACKAGE, S1, Depth(0)),
attr("name", name, (S2, S3)),
close(Some(QN_PACKAGE), S2, Depth(0)),
]
.into_iter();
let sut = Sut::parse(toks);
assert_eq!(
#[rustfmt::skip]
Ok(vec![
O(PkgStart(S1)),
O(PkgName(SPair("/needs/leading".into(), S3))),
Incomplete,
]),
sut.collect(),
);
}
// Maintains BC with existing system,
// but this ought to reject in the future.
#[test]
fn ignores_unknown_package_attr() {
let name = "/pkgroot".into();
#[rustfmt::skip]
let toks = [
open(QN_PACKAGE, S1, Depth(0)),
attr("name", name, (S2, S3)),
// This is ignored.
attr("unknown", name, (S2, S3)),
close(Some(QN_PACKAGE), S2, Depth(0)),
]
.into_iter();
let sut = Sut::parse(toks);
assert_eq!(
#[rustfmt::skip]
Ok(vec![
O(PkgStart(S1)),
O(PkgName(SPair(name, S3))),
Incomplete, // The unknown attribute
Incomplete,
]),
sut.collect(),
);
}
#[test]
fn symtable_err_missing_sym_name() {
let toks = [
open(QN_P_SYM, S1, Depth(0)),
// No attributes, but importantly, no name.
close(Some(QN_P_SYMTABLE), S2, Depth(0)),
]
.into_iter();
let mut sut = SymtableState::parse(toks);
assert_eq!(sut.next(), Some(Ok(Incomplete)),);
assert_eq!(
sut.next(),
Some(Err(ParseError::StateError(XmloError::UnassociatedSym(S1)))),
);
}
/// The span expected by the below `preproc:sym` tests for the emitted object.
const SSYM: Span = S1;
/// The span expected by failures associated with symbol attributes.
const SATTRVAL: Span = S4;
macro_rules! symtable_tests {
($($name:ident: [$($key:ident=$val:literal),*] => $expect:expr)*) => {
$(
#[test]
fn $name() {
let name = stringify!($name).intern();
let toks = [
open(QN_P_SYM, SSYM, Depth(0)),
attr(QN_NAME, name, (S2, S3)),
$(
attr(
stringify!($key),
$val.unwrap_into(),
(S3, SATTRVAL)
),
)*
close(Some(QN_P_SYM), S2, Depth(0)),
]
.into_iter();
assert_eq!(
match $expect {
Ok(expected) =>
Ok(vec![
Incomplete, // Opening tag
Incomplete, // @name
$(
// For each attribute ($key here is necessary
// for macro iteration).
#[allow(unused)]
#[doc=stringify!($key)]
Incomplete,
)*
O(SymDecl(
SPair(name, S3),
expected
)),
]),
Err(expected) => Err(ParseError::StateError(expected)),
},
SymtableState::parse(toks).collect(),
);
}
)*
}
}
symtable_tests! {
src: [src="foo/bar/baz"] => Ok(SymAttrs {
// see macro for src relpath
src: Some("foo/bar/baz".intern()),
..Default::default()
})
// note that this doesn't test every type; we're not going to
// duplicate the mapping for all of them here
tycgen: [type="cgen"] => Ok(SymAttrs {
ty: Some(SymType::Cgen),
..Default::default()
})
badtype: [type="bad"] => Err(
XmloError::InvalidType("bad".into(), SATTRVAL)
)
dim_0: [dim="0"] => Ok(SymAttrs {
dim: Some(Dim::Scalar),
..Default::default()
})
dim_1: [dim="1"] => Ok(SymAttrs {
dim: Some(Dim::Vector),
..Default::default()
})
dim_2: [dim="2"] => Ok(SymAttrs {
dim: Some(Dim::Matrix),
..Default::default()
})
dim_highnum: [dim="3"] => Err(
XmloError::InvalidDim("3".into(), SATTRVAL)
)
dim_nonum: [dim="X1"] => Err(
XmloError::InvalidDim("X1".into(), SATTRVAL)
)
dtyboolean: [dtype="boolean"] => Ok(SymAttrs {
dtype: Some(Dtype::Boolean),
..Default::default()
})
dtyinteger: [dtype="integer"] => Ok(SymAttrs {
dtype: Some(Dtype::Integer),
..Default::default()
})
dtyfloat: [dtype="float"] => Ok(SymAttrs {
dtype: Some(Dtype::Float),
..Default::default()
})
dtyempty: [dtype="empty"] => Ok(SymAttrs {
dtype: Some(Dtype::Empty),
..Default::default()
})
dtybad: [dtype="bad"] => Err(
XmloError::InvalidDtype("bad".into(), SATTRVAL)
)
extern_true: [extern="true"] => Ok(SymAttrs {
extern_: true,
..Default::default()
})
// The compiler will never produce nonsense values, so we'll just
// provide a sane default rather than adding extra checks (and
// hopefully we don't regret this)
extern_crap: [extern="nonsense"] => Ok(SymAttrs {
extern_: false,
..Default::default()
})
parent: [parent="foo"] => Ok(SymAttrs {
parent: Some("foo".intern()),
..Default::default()
})
yields: [yields="yield"] => Ok(SymAttrs {
yields: Some("yield".intern()),
..Default::default()
})
desc: [desc="Description"] => Ok(SymAttrs {
desc: Some("Description".into()),
..Default::default()
})
r#virtual: [virtual="true"] => Ok(SymAttrs {
virtual_: true,
..Default::default()
})
r#override: [isoverride="true"] => Ok(SymAttrs {
override_: true,
..Default::default()
})
// Multiple attributes at once
multi: [src="foo", type="class", dim="1", dtype="float", extern="true"]
=> Ok(SymAttrs {
// see macro for src relpath
src: Some("foo".intern()),
ty: Some(SymType::Class),
dim: Some(Dim::Vector),
dtype: Some(Dtype::Float),
extern_: true,
..Default::default()
})
}
// Can't be tested using the above macro because of the attr name.
#[test]
fn symtable_sym_generated_true() {
let name = "generated_true".into();
#[rustfmt::skip]
let toks = [
open(QN_P_SYM, SSYM, Depth(0)),
attr(QN_NAME, name, (S2, S3)),
attr(
("preproc", "generated"),
raw::L_TRUE,
(S3, S4),
),
close(Some(QN_P_SYM), S2, Depth(0)),
]
.into_iter();
let expected = SymAttrs {
generated: true,
..Default::default()
};
assert_eq!(
#[rustfmt::skip]
Ok(vec![
Incomplete, // Opening tag
Incomplete, // @name
Incomplete, // @preproc:generated
O(SymDecl(SPair(name, S3), expected)),
]),
SymtableState::parse(toks).collect(),
);
}
// `map` symbols include information about their source
// fields.
#[test]
fn symtable_map_from() {
let name = "sym-map-from".into();
let map_from = "from-a".into();
#[rustfmt::skip]
let toks = [
open(QN_P_SYM, SSYM, Depth(0)),
attr(QN_NAME, name, (S2, S3)),
attr(QN_TYPE, raw::L_MAP, (S3, S4)),
// <preproc:from>
open(QN_P_FROM, S2, Depth(1)),
attr(QN_NAME, map_from, (S2, S3)),
close_empty(S4, Depth(1)),
// />
close(Some(QN_P_SYM), S2, Depth(0)),
]
.into_iter();
let expected = SymAttrs {
ty: Some(SymType::Map),
from: Some(map_from),
..Default::default()
};
assert_eq!(
Ok(vec![
Incomplete, // Opening tag
Incomplete, // @name
Incomplete, // @type
Incomplete, // <preproc:from
Incomplete, // @name
Incomplete, // />
O(SymDecl(SPair(name, S3), expected)),
]),
SymtableState::parse(toks).collect(),
);
}
#[test]
fn symtable_map_from_missing_name() {
let name = "sym-map-from-missing".into();
#[rustfmt::skip]
let toks = [
open(QN_P_SYM, SSYM, Depth(0)),
attr(QN_NAME, name, (S2, S3)),
attr(QN_TYPE, raw::L_MAP, (S3, S4)),
// <preproc:from>
open(QN_P_FROM, S2, Depth(1)),
// @name missing
close_empty(S4, Depth(1)),
// />
close(Some(QN_P_SYM), S2, Depth(0)),
]
.into_iter();
assert_eq!(
Err(ParseError::StateError(XmloError::MapFromNameMissing(name, S2))), // />
SymtableState::parse(toks)
.collect::<Result<Vec<Parsed<<SymtableState as ParseState>::Object>>, _>>(),
);
}
// Multiple `from` nodes used to be a thing but are no longer utilized.
#[test]
fn symtable_map_from_multiple() {
let name = "sym-map-from-missing".into();
#[rustfmt::skip]
let toks = [
open(QN_P_SYM, SSYM, Depth(0)),
attr(QN_NAME, name, (S2, S3)),
attr(QN_TYPE, raw::L_MAP, (S3, S4)),
// <preproc:from>
open(QN_P_FROM, S2, Depth(1)),
attr(QN_NAME, "ok".into(), (S2, S3)),
close_empty(S4, Depth(1)),
// />
// <preproc:from> again (err)
open(QN_P_FROM, S3, Depth(1)),
attr(QN_NAME, "bad".into(), (S2, S3)),
close_empty(S4, Depth(1)),
// />
close(Some(QN_P_SYM), S2, Depth(0)),
]
.into_iter();
assert_eq!(
Err(ParseError::StateError(XmloError::MapFromMultiple(name, S3))),
SymtableState::parse(toks)
.collect::<Result<Vec<Parsed<<SymtableState as ParseState>::Object>>, _>>(),
);
}
#[test]
fn sym_dep_event() {
let name = "depsym".into();
let dep1 = "dep1".into();
let dep2 = "dep2".into();
#[rustfmt::skip]
let toks = [
open(QN_P_SYM_DEP, S1, Depth(0)),
attr(QN_NAME, name, (S2, S3)),
// <preproc:sym-ref
open(QN_P_SYM_REF, S2, Depth(1)),
attr(QN_NAME, dep1, (S3, S4)),
close_empty(S4, Depth(1)),
// />
// <preproc:sym-ref
open(QN_P_SYM_REF, S3, Depth(1)),
attr(QN_NAME, dep2, (S4, S5)),
close_empty(S4, Depth(1)),
// />
close(Some(QN_P_SYM_DEP), S5, Depth(0)),
]
.into_iter();
assert_eq!(
Ok(vec![
Incomplete, // <preproc:sym-ref
O(SymDepStart(SPair(name, S1))), // @name
Incomplete, // <preproc:sym-ref
O(Symbol(SPair(dep1, S4))), // @name
Incomplete, // />
Incomplete, // <preproc:sym-ref
O(Symbol(SPair(dep2, S5))), // @name
Incomplete, // />
Incomplete, // </preproc:sym-dep>
]),
SymDepsState::parse(toks).collect()
);
}
#[test]
fn sym_dep_missing_name() {
let toks = [
open(QN_P_SYM_DEP, S1, Depth(0)),
// missing @name, causes error
open(QN_P_SYM_REF, S2, Depth(1)),
]
.into_iter();
assert_eq!(
Err(ParseError::StateError(XmloError::UnassociatedSymDep(S1))),
SymDepsState::parse(toks)
.collect::<Result<Vec<Parsed<<SymDepsState as ParseState>::Object>>, _>>(),
);
}
#[test]
fn sym_ref_missing_name() {
let name = "depsym".into();
#[rustfmt::skip]
let toks = [
open(QN_P_SYM_DEP, S1, Depth(0)),
attr(QN_NAME, name, (S2, S3)),
open(QN_P_SYM_REF, S2, Depth(1)),
// missing @name, causes error
close_empty(S3, Depth(1)),
]
.into_iter();
assert_eq!(
Err(ParseError::StateError(XmloError::MalformedSymRef(name, S2))),
SymDepsState::parse(toks)
.collect::<Result<Vec<Parsed<XmloToken>>, _>>(),
);
}
#[test]
fn sym_fragment_event() {
let id1 = "fragsym1".into();
let id2 = "fragsym2".into();
let frag1 = "fragment text 1".into();
let frag2 = "fragment text 2".into();
#[rustfmt::skip]
let toks = [
// first
open(QN_P_FRAGMENT, S1, Depth(0)),
attr(QN_ID, id1, (S2, S3)),
Xirf::Text(Text(frag1, S4), Depth(1)),
close(Some(QN_P_FRAGMENT), S5, Depth(0)),
// second
open(QN_P_FRAGMENT, S2, Depth(0)),
attr(QN_ID, id2, (S3, S4)),
Xirf::Text(Text(frag2, S5), Depth(1)),
close(Some(QN_P_FRAGMENT), S5, Depth(0)),
]
.into_iter();
assert_eq!(
Ok(vec![
Incomplete, // <preproc:fragment
Incomplete, // @id
O(Fragment(SPair(id1, S1), frag1)), // text
Incomplete, // </preproc:fragment>
Incomplete, // <preproc:fragment
Incomplete, // @id
O(Fragment(SPair(id2, S2), frag2)), // text
Incomplete, // </preproc:fragment>
]),
FragmentsState::parse(toks).collect()
);
}
#[test]
fn sym_fragment_missing_id() {
let toks = [
open(QN_P_FRAGMENT, S1, Depth(0)),
// missing @id
Xirf::Text(Text("text".into(), S4), Depth(1)),
]
.into_iter();
assert_eq!(
Err(ParseError::StateError(XmloError::UnassociatedFragment(S1))),
FragmentsState::parse(toks)
.collect::<Result<Vec<Parsed<XmloToken>>, _>>(),
);
}
// Yes, this happened.
#[test]
fn sym_fragment_empty_id() {
let toks = [
open(QN_P_FRAGMENT, S1, Depth(0)),
// empty @id
attr(QN_ID, "".into(), (S3, S4)),
Xirf::Text(Text("text".into(), S4), Depth(1)),
]
.into_iter();
assert_eq!(
Err(ParseError::StateError(XmloError::UnassociatedFragment(S1))),
FragmentsState::parse(toks)
.collect::<Result<Vec<Parsed<XmloToken>>, _>>(),
);
}
// TODO: Re-enable after compiler bug is resolved.
// See implementation.
//#[test]
fn _sym_fragment_missing_text() {
let id = "fragsym".into();
let toks = [
open(QN_P_FRAGMENT, S1, Depth(0)),
attr(QN_ID, id, (S3, S4)),
// missing text
close(Some(QN_P_FRAGMENT), S5, Depth(0)),
]
.into_iter();
assert_eq!(
Err(ParseError::StateError(XmloError::MissingFragmentText(
id, S1
))),
FragmentsState::parse(toks)
.collect::<Result<Vec<Parsed<XmloToken>>, _>>(),
);
}
/// Very lightly test the default parser composition.
///
/// This test should do just enough to verify that parser state stitching has
/// occurred.
#[test]
fn xmlo_composite_parsers_header() {
let sym_name = "sym".into();
let symdep_name = "symdep".into();
let symfrag_id = "symfrag".into();
let frag = "fragment text".into();
#[rustfmt::skip]
let toks_header = [
open(QN_PACKAGE, S1, Depth(0)),
open(QN_P_SYMTABLE, S2, Depth(1)),
open(QN_P_SYM, S3, Depth(2)),
attr(QN_NAME, sym_name, (S2, S3)),
close_empty(S4, Depth(2)),
close(Some(QN_P_SYMTABLE), S4, Depth(1)),
open(QN_P_SYM_DEPS, S2, Depth(1)),
open(QN_P_SYM_DEP, S3, Depth(3)),
attr(QN_NAME, symdep_name, (S2, S3)),
close(Some(QN_P_SYM_DEP), S4, Depth(3)),
close(Some(QN_P_SYM_DEPS), S3, Depth(1)),
open(QN_P_FRAGMENTS, S2, Depth(1)),
open(QN_P_FRAGMENT, S4, Depth(2)),
attr(QN_ID, symfrag_id, (S2, S3)),
Xirf::Text(Text(frag, S5), Depth(3)),
close(Some(QN_P_FRAGMENT), S4, Depth(2)),
close(Some(QN_P_FRAGMENTS), S3, Depth(1)),
// No closing root node:
// ensure that we can just end at the header without parsing further).
]
.into_iter();
let sut = Sut::parse(toks_header);
#[rustfmt::skip]
assert_eq!(
Ok(vec![
O(PkgStart(S1)),
O(SymDecl(SPair(sym_name, S3), Default::default(),)),
O(SymDepStart(SPair(symdep_name, S3))),
O(SymDepEnd(S3)),
O(Fragment(SPair(symfrag_id, S4), frag)),
O(Eoh(S3)),
]),
sut.filter(|parsed| match parsed {
Ok(Incomplete) => false,
_ => true,
})
.collect(),
);
}
#[test]
fn xmlo_end_after_sym_deps_before_fragments() {
let sym_name = "sym".into();
let symdep_name = "symdep".into();
#[rustfmt::skip]
let toks_header = [
open(QN_PACKAGE, S1, Depth(0)),
open(QN_P_SYMTABLE, S2, Depth(1)),
open(QN_P_SYM, S3, Depth(2)),
attr(QN_NAME, sym_name, (S2, S3)),
close_empty(S4, Depth(2)),
close(Some(QN_P_SYMTABLE), S4, Depth(1)),
open(QN_P_SYM_DEPS, S2, Depth(1)),
open(QN_P_SYM_DEP, S3, Depth(3)),
attr(QN_NAME, symdep_name, (S2, S3)),
close(Some(QN_P_SYM_DEP), S4, Depth(3)),
close(Some(QN_P_SYM_DEPS), S3, Depth(1)),
// End before fragments.
]
.into_iter();
let sut = Sut::parse(toks_header);
#[rustfmt::skip]
assert_eq!(
Ok(vec![
O(PkgStart(S1)),
O(SymDecl(SPair(sym_name, S3), Default::default(),)),
O(SymDepStart(SPair(symdep_name, S3))),
O(SymDepEnd(S3)),
]),
sut.filter(|parsed| match parsed {
Ok(Incomplete) => false,
_ => true,
})
.collect(),
);
}